diff --git a/application/package.json b/application/package.json index 70759b014c968ceee30c36be65feaba134bca716..b4311e85688ffb92d44079fcd24da4f15950dbe2 100644 --- a/application/package.json +++ b/application/package.json @@ -1,6 +1,6 @@ { "name": "@schukai/monster", - "version": "0.1.17", + "version": "0.1.18", "description": "Monster is a simple library for creating fast, robust and lightweight websites.", "keywords": [ "framework", diff --git a/development/monster.temp b/development/monster.temp deleted file mode 100644 index 1f57bde84836eaa56f32a9904b1b33261a1ba0b2..0000000000000000000000000000000000000000 --- a/development/monster.temp +++ /dev/null @@ -1,44068 +0,0 @@ -'use strict'; - -/** - * @author schukai GmbH - */ - -import {isObject} from "../types/is.mjs"; -import {AbstractConstraint} from "./abstract.mjs"; - -export {IsObject} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * ``` - * <script type="module"> - * import {IsObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/isobject.mjs'; - * console.log(new IsObject()) - * </script> - * ``` - * - * @example - * - * import {IsObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/isobject.mjs'; - * - * new IsObject() - * .isValid({}) - * .then(()=>console.log(true)); - * // ↦ true - * - * - * new IsObject() - * .isValid(99) - * .catch(e=>console.log(e)); - * // ↦ 99 - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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); - } - - return Promise.reject(value); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractConstraint} from "./abstract.mjs"; - -export {Invalid} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The invalid constraint allows an always invalid query to be performed. this constraint is mainly intended for testing. - * - * ``` - * <script type="module"> - * import {Invalid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/invalid.mjs'; - * new Invalid(); - * </script> - * ``` - * - * @example - * - * import {Invalid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/invalid.mjs'; - * - * new Invalid().isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ false - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractOperator} from "./abstractoperator.mjs"; - -export {AndOperator} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The AndOperator is used to link several contraints. The constraint is fulfilled if all constraints of the operators are fulfilled. - * - * ``` - * <script type="module"> - * import {AndOperator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/andoperator.mjs'; - * new AndOperator(); - * </script> - * ``` - * - * @example - * - * import {Valid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/valid.mjs'; - * import {Invalid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/invalid.mjs'; - * import {AndOperator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/andoperator.mjs'; - * - * new AndOperator( - * new Valid(), new Valid()).isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ true - * - * new AndOperator( - * new Invalid(), new Valid()).isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ false - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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)]); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractOperator} from "./abstractoperator.mjs"; - -export {OrOperator} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The OrOperator is used to link several constraints. The constraint is fulfilled if one of the constraints is fulfilled. - * - * ``` - * <script type="module"> - * import {OrOperator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraint/oroperator.mjs'; - * new OrOperator(); - * </script> - * ``` - * - * @example - * - * import {Valid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/valid.mjs'; - * import {Invalid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/invalid.mjs'; - * import {OrOperator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/oroperator.mjs'; - * - * new OrOperator( - * new Valid(), new Invalid()).isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ true - * - * new OrOperator( - * new Invalid(), new Invalid()).isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ false - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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; - - return new Promise(function (resolve, reject) { - let a, 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.operantB.isValid(value) - .then(function () { - resolve(); - }).catch(function () { - b = false; - /** b has already been evaluated and was not true */ - if (a === false) { - reject(); - } - }); - }); - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {Base} from '../types/base.mjs'; - -export {AbstractConstraint} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The abstract constraint defines the api for all constraints. mainly the method isValid() is defined. - * - * derived classes must implement the method isValid(). - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @summary The abstract constraint - */ -class AbstractConstraint extends Base { - - /** - * - */ - constructor() { - super(); - } - - /** - * this method must return a promise containing the result of the check. - * - * @param {*} value - * @returns {Promise} - */ - isValid(value) { - return Promise.reject(value); - } -} -'use strict'; - -/** - * Constraints are used to define conditions that must be met by the value of a variable so that the value can be transferred to the system. - * - * @namespace Monster.Constraints - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - -import {isArray} from "../types/is.mjs"; -import {AbstractConstraint} from "./abstract.mjs"; - -export {IsArray} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * ``` - * <script type="module"> - * import {IsArray} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/isarray.mjs'; - * console.log(new IsArray()) - * </script> - * ``` - * - * @example - * - * import {IsArray} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/isarray.mjs'; - * - * new IsArray() - * .isValid([]) - * .then(()=>console.log(true)); - * // ↦ true - * - * new IsArray() - * .isValid(99) - * .catch(e=>console.log(e)); - * // ↦ 99 - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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); - } - - return Promise.reject(value); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractConstraint} from "./abstract.mjs"; - -export {AbstractOperator} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * Operators allow you to link constraints together. for example, you can check whether a value is an object or an array. each operator has two operands that are linked together. - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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(); - - if (!(operantA instanceof AbstractConstraint) || !(operantB instanceof AbstractConstraint)) { - throw new TypeError("parameters must be from type AbstractConstraint") - } - - this.operantA = operantA; - this.operantB = operantB; - - } - - -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractConstraint} from "./abstract.mjs"; - -export {Valid} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The valid constraint allows an always valid query to be performed. this constraint is mainly intended for testing. - * - * ``` - * <script type="module"> - * import {Valid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/valid.mjs'; - * new Valid(); - * </script> - * ``` - * - * @example - * - * import {Valid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/valid.mjs'; - * - * new Valid().isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ true - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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); - } - -} -/** - * @license - * Copyright 2021 schukai GmbH - * SPDX-License-Identifier: AGPL-3.0-only or COMMERCIAL - * @author schukai GmbH - */ - - -/** - * Main namespace for Monster. - * - * @namespace Monster - * @author schukai GmbH - */ -'use strict'; - -import './constants.mjs'; -// find packages/monster/source/ -type f -name "*.mjs" -not -name "*namespace*" -not -iname "monster.mjs" -import './constraints/isobject.mjs'; -import './constraints/valid.mjs'; -import './constraints/invalid.mjs'; -import './constraints/abstractoperator.mjs'; -import './constraints/oroperator.mjs'; -import './constraints/andoperator.mjs'; -import './constraints/abstract.mjs'; -import './constraints/isarray.mjs'; -import './logging/logger.mjs'; -import './logging/handler.mjs'; -import './logging/logentry.mjs'; -import './logging/handler/console.mjs'; -import './text/formatter.mjs'; -import './dom/resource/script.mjs'; -import './dom/resource/data.mjs'; -import './dom/resource/link/stylesheet.mjs'; -import './dom/resource/link.mjs'; -import './dom/resource.mjs'; -import './dom/updater.mjs'; -import './dom/attributes.mjs'; -import './dom/template.mjs'; -import './dom/util.mjs'; -import './dom/ready.mjs'; -import './dom/resourcemanager.mjs'; -import './dom/locale.mjs'; -import './dom/customcontrol.mjs'; -import './dom/constants.mjs'; -import './dom/assembler.mjs'; -import './dom/theme.mjs'; -import './dom/worker/factory.mjs'; -import './dom/focusmanager.mjs'; -import './dom/events.mjs'; -import './dom/customelement.mjs'; -import './i18n/formatter.mjs'; -import './i18n/providers/fetch.mjs'; -import './i18n/translations.mjs'; -import './i18n/locale.mjs'; -import './i18n/provider.mjs'; -import './types/queue.mjs'; -import './types/binary.mjs'; -import './types/regex.mjs'; -import './types/observer.mjs'; -import './types/observerlist.mjs'; -import './types/basewithoptions.mjs'; -import './types/is.mjs'; -import './types/proxyobserver.mjs'; -import './types/uniquequeue.mjs'; -import './types/node.mjs'; -import './types/tokenlist.mjs'; -import './types/typeof.mjs'; -import './types/uuid.mjs'; -import './types/mediatype.mjs'; -import './types/dataurl.mjs'; -import './types/base.mjs'; -import './types/version.mjs'; -import './types/nodelist.mjs'; -import './types/id.mjs'; -import './types/randomid.mjs'; -import './types/noderecursiveiterator.mjs'; -import './types/validate.mjs'; -import './types/stack.mjs'; -import './util/deadmansswitch.mjs'; -import './util/comparator.mjs'; -import './util/trimspaces.mjs'; -import './util/clone.mjs'; -import './util/freeze.mjs'; -import './util/processing.mjs'; -import './constants.mjs'; -import './data/pathfinder.mjs'; -import './data/pipe.mjs'; -import './data/extend.mjs'; -import './data/diff.mjs'; -import './data/buildmap.mjs'; -import './data/datasource.mjs'; -import './data/buildtree.mjs'; -import './data/transformer.mjs'; -import './data/datasource/storage.mjs'; -import './data/datasource/restapi.mjs'; -import './data/datasource/storage/sessionstorage.mjs'; -import './data/datasource/storage/localstorage.mjs'; -import './data/datasource/restapi/writeerror.mjs'; -import './math/random.mjs'; - -export {Monster} - -/** - * This class has no other purpose than to exist. - * - * @since 2.0.0 - * @copyright schukai GmbH - * @memberOf Monster - */ -class Monster { - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -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'; - -export {Logger, ALL, TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF}; - -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const ALL = 255; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const TRACE = 64; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const DEBUG = 32; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const INFO = 16; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const WARN = 8; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const ERROR = 4; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const FATAL = 2; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const OFF = 0; - -/** - * The logger is a class that takes care of logging. - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/logging/logger.mjs'; - * new Logger() - * </script> - * ``` - * - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Logging - */ -class Logger extends Base { - - /** - * - */ - 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") - } - - 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") - } - - 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() { - triggerLog.apply(this, [TRACE, ...arguments]); - 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() { - triggerLog.apply(this, [DEBUG, ...arguments]); - 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() { - triggerLog.apply(this, [INFO, ...arguments]); - 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() { - triggerLog.apply(this, [WARN, ...arguments]); - 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() { - triggerLog.apply(this, [ERROR, ...arguments]); - 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() { - triggerLog.apply(this, [FATAL, ...arguments]); - return this; - }; - - - /** - * 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'; - - return 'unknown'; - }; - - /** - * 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; - - return 0; - }; - - -} - - -/** - * Log triggern - * - * @param {integer} loglevel - * @param {*} args - * @returns {Logger} - * @private - */ -function triggerLog(loglevel, ...args) { - var logger = this; - - for (let handler of logger.handler) { - handler.log(new LogEntry(loglevel, args)) - } - - return logger; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {Base} from '../types/base.mjs'; -import {validateInteger} from '../types/validate.mjs'; - -export {LogEntry} - -/** - * A log entry for the logger - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/logging/logentry.mjs'; - * console.log(new LogEntry()) - * </script> - * ``` - * - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Logging - */ - class LogEntry extends Base { - /** - * - * @param {Integer} loglevel - * @param {...*} args - */ - constructor(loglevel, ...args) { - super(); - validateInteger(loglevel); - - this.loglevel = loglevel - this.arguments = args - } - - /** - * - * @returns {integerr} - */ - getLogLevel() { - return this.loglevel - } - - /** - * - * @returns {array} - */ - getArguments() { - return this.arguments - } - -} -'use strict'; - -/** - * Namespace for logging. - * - * @namespace Monster.Logging - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @namespace Monster.Logging.Handler - * @memberOf Monster.Logging - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../../types/base.mjs'; -import {getGlobalObject} from "../../types/global.mjs"; -import {Handler} from '../handler.mjs'; -import {LogEntry} from "../logentry.mjs"; - -export {ConsoleHandler} - -/** - * You can create an object of the class simply by using the namespace `new Monster.Logging.Handler.ConsoleHandler()`. - * - * ``` - * <script type="module"> - * import {ConsoleHandler} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/logging/handler/console.mjs'; - * console.log(new ConsoleHandler()) - * </script> - * ``` - * - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Logging.Handler - */ - class ConsoleHandler extends Handler { - constructor() { - super(); - } - - - /** - * 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; - console.log(entry.toString()); - return true; - } - - return false; - } - -} - - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {validateInstance, validateInteger} from "../types/validate.mjs"; -import {LogEntry} from "./logentry.mjs"; -import {ALL, DEBUG, ERROR, FATAL, INFO, OFF, TRACE, WARN} from "./logger.mjs"; - -export {Handler} - -/** - * The log handler is the interface between the log entries and the log listeners. - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/logging/handler.mjs'; - * console.log(new Handler()) - * </script> - * ``` - * - * @since 1.5.0 - * @copyright schukai GmbH - * @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; - }; - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; -import {Pipe} from "../data/pipe.mjs"; - -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {isObject, isString} from "../types/is.mjs"; -import {validateArray, validateString} from "../types/validate.mjs"; -import {getMonsterVersion} from "../types/version.mjs"; - -export {Formatter} - -/** - * @private - * @type {symbol} - */ -const internalObjectSymbol = Symbol('internalObject'); - -/** - * @private - * @type {symbol} - */ -const watchdogSymbol = Symbol('watchdog'); - -/** - * @private - * @type {symbol} - */ -const markerOpenIndexSymbol = Symbol('markerOpenIndex'); - -/** - * @private - * @type {symbol} - */ -const markerCloseIndexSymbol = Symbol('markercloseIndex'); - -/** - * @private - * @type {symbol} - */ -const workingDataSymbol = Symbol('workingData'); - - -/** - * Messages can be formatted with the formatter. To do this, an object with the values must be passed to the formatter. The message can then contain placeholders. - * - * Look at the example below. The placeholders use the logic of Pipe. - * - * ``` - * <script type="module"> - * import {Formatter} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/text/formatter.mjs'; - * new Formatter() - * </script> - * ``` - * - * ## Marker in marker - * - * Markers can be nested. Here, the inner marker is resolved first `${subkey} ↦ 1 = ${mykey2}` and then the outer marker `${mykey2}`. - * - * ``` - * const text = '${mykey${subkey}}'; - * let obj = { - * mykey2: "1", - * subkey: "2" - * }; - * - * new Formatter(obj).format(text); - * // ↦ 1 - * ``` - * - * ## Callbacks - * - * The values in a formatter can be adjusted via the commands of the `Transformer` or the`Pipe`. - * There is also the possibility to use callbacks. - * - * const formatter = new Formatter({x: '1'}, { - * callbacks: { - * quote: (value) => { - * return '"' + value + '"' - * } - * } - * }); - * - * formatter.format('${x | call:quote}')) - * // ↦ "1" - * - * ## Marker with parameter - * - * A string can also bring its own values. These must then be separated from the key by a separator `::`. - * The values themselves must be specified in key/value pairs. The key must be separated from the value by a separator `=`. - * - * When using a pipe, you must pay attention to the separators. - * - * @example - * - * import {Formatter} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/text/formatter.mjs'; - * - * new Formatter({ - * a: { - * b: { - * c: "Hello" - * }, - * d: "world", - * } - * }).format("${a.b.c} ${a.d | ucfirst}!"); // with pipe - * - * // ↦ Hello World! - * - * @since 1.12.0 - * @copyright schukai GmbH - * @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); - } - -} - -/** - * @private - * @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; -} - -/** - * @private - * @since 1.12.0 - * @param 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") - return; - } - - 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(''); -} -'use strict'; - -/** - * Namespace for texts. - * - * @namespace Monster.Text - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {extend} from "../../../data/extend.mjs"; -import {Link} from "../link.mjs"; - -export {Stylesheet} - -/** - * This class is used by the resource manager to embed external resources. - * - * ``` - * <script type="module"> - * import {Style} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource/link/stylesheet.mjs'; - * new Stylesheet() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Resource - * @summary A Resource class - * @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' - }) - } - -} - -'use strict'; - -/** - * In this namespace you will find classes and methods for links - * - * @namespace Monster.DOM.Resource.Link - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * In this namespace you will find classes and methods for handling resources. - * - * @namespace Monster.DOM.Resource - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -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 -} from "../constants.mjs"; -import {KEY_DOCUMENT, KEY_QUERY, referenceSymbol, Resource} from "../resource.mjs"; - -export {Data} - -/** - * This class is used by the resource manager to embed data. - * - * ``` - * <script type="module"> - * import {Data} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource/data.mjs'; - * new Data() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Resource - * @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; - } - - /** - * @return {string} - */ - static getURLAttribute() { - return ATTRIBUTE_SRC - } - -} - -/** - * @private - * @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; -} - - -/** - * @private - * @return {Promise} - * 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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {extend} from "../../data/extend.mjs"; -import { - 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"; - -export {Link} - -/** - * This class is used by the resource manager to embed external resources. - * - * ``` - * <script type="module"> - * import {Link} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource/link.mjs'; - * new Link() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Resource - * @summary A Resource class - * @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 - }) - } - - /** - * - * @return {Monster.DOM.Resource.Link} - */ - create() { - createElement.call(this); - return this; - } - - /** - * @return {string} - */ - static getURLAttribute() { - return ATTRIBUTE_HREF - } - -} - -/** - * @private - * @return {Monster.DOM.Resource.Link} - */ -function createElement() { - const self = this; - - 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); - } - } - - return self; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {extend} from "../../data/extend.mjs"; -import { - 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"; - -export {Script} - -/** - * This class is used by the resource manager to embed scripts. - * - * ``` - * <script type="module"> - * import {Script} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource/script.mjs'; - * new Script() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Resource - * @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', - }) - } - - /** - * - * @return {Monster.DOM.Resource.Script} - */ - create() { - createElement.call(this); - return this; - } - - /** - * @return {string} - */ - static getURLAttribute() { - return ATTRIBUTE_SRC - } - -} - -/** - * @private - * @return {Monster.DOM.Resource.Script} - */ -function createElement() { - const self = this; - - 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); - } - } - - - return self; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -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 -} from "../dom/constants.mjs"; - -import {Base} from "../types/base.mjs"; -import {isArray, isInstance, isIterable} from "../types/is.mjs"; -import {Observer} from "../types/observer.mjs"; -import {ProxyObserver} from "../types/proxyobserver.mjs"; -import {validateArray, validateInstance} from "../types/validate.mjs"; -import {clone} from "../util/clone.mjs"; -import {trimSpaces} from "../util/trimspaces.mjs"; -import {findTargetElementFromEvent} from "./events.mjs"; -import {findDocumentTemplate} from "./template.mjs"; -import {getDocument} from "./util.mjs"; - -export {Updater} - -/** - * The updater class connects an object with the dom. In this way, structures and contents in the DOM can be programmatically adapted via attributes. - * - * For example, to include a string from an object, the attribute `data-monster-replace` can be used. - * a further explanation can be found under {@tutorial dom-based-templating-implementation}. - * - * Changes to attributes are made only when the direct values are changed. If you want to assign changes to other values - * as well, you have to insert the attribute `data-monster-select-this`. This should be done with care, as it can reduce performance. - * - * ``` - * <script type="module"> - * import {Updater} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/updater.mjs'; - * new Updater() - * </script> - * ``` - * - * @example - * - * import {Updater} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/updater.mjs'; - * - * // First we prepare the html document. - * // This is done here via script, but can also be inserted into the document as pure html. - * // To do this, simply insert the tag <h1 data-monster-replace="path:headline"></h1>. - * const body = document.querySelector('body'); - * const headline = document.createElement('h1'); - * headline.setAttribute('data-monster-replace','path:headline') - * body.appendChild(headline); - * - * // the data structure - * let obj = { - * headline: "Hello World", - * }; - * - * // Now comes the real magic. we pass the updater the parent HTMLElement - * // and the desired data structure. - * const updater = new Updater(body, obj); - * updater.run(); - * - * // Now you can change the data structure and the HTML will follow these changes. - * const subject = updater.getSubject(); - * subject['headline'] = "Hello World!" - * - * @since 1.8.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} the value is not iterable - * @throws {Error} pipes are not allowed when cloning a node. - * @throws {Error} no template was found with the specified key. - * @throws {Error} the maximum depth for the recursion is reached. - * @throws {TypeError} value is not a object - * @throws {TypeError} value is not an instance of HTMLElement - * @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; - } - -} - -/** - * @private - * @since 1.9.0 - * @return {function - * @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; - } - } -} - -/** - * @private - */ -const symbol = Symbol('EventHandler'); - -/** - * @private - * @return {function} - * @this Updater - * @throws {Error} the bind argument must start as a value with a path - */ -function getControlEventHandler() { - - const self = this; - - 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); - - if (element === undefined) { - return; - } - - retrieveAndSetValue.call(self, element); - - } - - return self[symbol]; - - -} - -/** - * @throws {Error} the bind argument must start as a value with a path - * @param {HTMLElement} element - * @return void - * @memberOf Monster.DOM - * @private - */ -function retrieveAndSetValue(element) { - - const self = this; - - const pathfinder = new Pathfinder(self[internalSymbol].subject.getSubject()); - - let path = element.getAttribute(ATTRIBUTE_UPDATER_BIND); - - if (path.indexOf('path:') !== 0) { - throw new Error('the bind argument must start as a value with a path'); - } - - path = path.substr(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); - } -} - -/** - * @since 1.27.0 - * @return void - * @private - */ -function retrieveFromBindings() { - const self = this; - - if (self[internalSymbol].element.matches('[' + ATTRIBUTE_UPDATER_BIND + ']')) { - retrieveAndSetValue.call(self, element) - } - - for (const [, element] of self[internalSymbol].element.querySelectorAll('[' + ATTRIBUTE_UPDATER_BIND + ']').entries()) { - retrieveAndSetValue.call(self, element) - } - -} - -/** - * @private - * @since 1.8.0 - * @param {object} change - * @return {void} - */ -function removeElement(change) { - const self = this; - - for (const [, element] of self[internalSymbol].element.querySelectorAll(':scope [' + ATTRIBUTE_UPDATER_REMOVE + ']').entries()) { - element.parentNode.removeChild(element); - } -} - -/** - * @private - * @since 1.8.0 - * @param {object} change - * @return {void} - * @throws {Error} the value is not iterable - * @throws {Error} pipes are not allowed when cloning a node. - * @throws {Error} no template was found with the specified key. - * @throws {Error} the maximum depth for the recursion is reached. - * @this Updater - */ -function insertElement(change) { - const self = this; - const subject = self[internalSymbol].subject.getRealSubject(); - const document = getDocument(); - - 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); - 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.'); - } - - } - - -} - -/** - * - * @private - * @since 1.8.0 - * @param {HTMLElement} container - * @param {string} key - * @param {string} ref - * @param {string} path - * @throws {Error} no template was found with the specified key. - */ -function appendNewDocumentFragment(container, key, ref, path) { - - 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); - } - - container.appendChild(node); - } -} - -/** - * @private - * @since 1.10.0 - * @param {HTMLElement} node - * @param {string} key - * @param {string} 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); - } - } -} - -/** - * @private - * @since 1.8.0 - * @param {object} change - * @return {void} - * @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); - } - } - } - - -} - -/** - * @private - * @since 1.8.0 - * @param {HTMLElement} container - * @param {array} parts - * @param {object} subject - * @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:"]'; - 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; - } - - } - - - } - -} - -/** - * @private - * @since 1.8.0 - * @param {string} path - * @param {object} change - * @return {void} - */ -function updateAttributes(change) { - const subject = this[internalSymbol].subject.getRealSubject(); - let p = clone(change?.['path']); - runUpdateAttributes.call(this, this[internalSymbol].element, p, subject); -} - -/** - * @private - * @param {HTMLElement} container - * @param {array} parts - * @param {object} subject - * @return {void} - * @this Updater - */ -function runUpdateAttributes(container, parts, subject) { - - const self = this; - - if (!isArray(parts)) return; - parts = clone(parts); - - let mem = new WeakSet; - - while (parts.length > 0) { - const current = parts.join('.'); - parts.pop(); - - let iterator = new Set; - - const query = '[' + ATTRIBUTE_UPDATER_SELECT_THIS + '], [' + ATTRIBUTE_UPDATER_ATTRIBUTES + '*="path:' + current + '"], [' + ATTRIBUTE_UPDATER_ATTRIBUTES + '^="static:"]'; - - const e = container.querySelectorAll(query); - - if (e.length > 0) { - iterator = new Set( - [...e] - ) - } - - if (container.matches(query)) { - iterator.add(container); - } - - for (const [element] of iterator.entries()) { - - if (mem.has(element)) return; - mem.add(element) - - 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)); - - let pipe = new Pipe(cmd); - - 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); - } - - - if (value === undefined) { - element.removeAttribute(name) - - } else if (element.getAttribute(name) !== value) { - element.setAttribute(name, value); - } - - handleInputControlAttributeUpdate.call(this, element, name, value); - - } - } - } - -} - -/** - * @private - * @param {HTMLElement|*} element - * @param {string} name - * @param {string|number|undefined} value - * @return {void} - * @this Updater - */ - -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); - } - } - -} -'use strict'; - -import {extend} from "../data/extend.mjs"; -/** - * @author schukai GmbH - */ - -import {ATTRIBUTE_VALUE} from "./constants.mjs"; -import {CustomElement, attributeObserverSymbol} from "./customelement.mjs"; - -export {CustomControl} - -/** - * @private - * @type {symbol} - */ -const attachedInternalSymbol = Symbol('attachedInternal'); - -/** - * To define a new HTML control we need the power of CustomElement - * - * IMPORTANT: after defining a `CustomElement`, the `registerCustomElement` method must be called - * with the new class name. only then will the tag defined via the `getTag` method be made known to the DOM. - * - * <img src="./images/customcontrol-class.png"> - * - * This control uses `attachInternals()` to integrate the control into a form. - * If the target environment does not support this method, the [polyfill](https://www.npmjs.com/package/element-internals-polyfill ) can be used. - * - * You can create the object via the function `document.createElement()`. - * - * ``` - * <script type="module"> - * import {CustomControl} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * document.createElement('monster-') - * </script> - * ``` - * - * @startuml customcontrol-class.png - * skinparam monochrome true - * skinparam shadowing false - * HTMLElement <|-- CustomElement - * CustomElement <|-- CustomControl - * @enduml - * - * @summary A base class for customcontrols based on CustomElement - * @see {@link https://www.npmjs.com/package/element-internals-polyfill} - * @see {@link https://github.com/WICG/webcomponents} - * @see {@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements} - * @since 1.14.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - */ -class CustomControl extends CustomElement { - - /** - * 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 ElementInternals is not supported and a polyfill is necessary - * @summary create new Instance - */ - constructor() { - super(); - - if (typeof this['attachInternals'] === 'function') { - /** - * currently only supported by chrome - * @property {Object} - * @private - */ - this[attachedInternalSymbol] = this.attachInternals(); - } - - initObserver.call(this); - - } - - /** - * This method determines which attributes are to be monitored by `attributeChangedCallback()`. - * - * @return {string[]} - * @since 1.15.0 - */ - static get observedAttributes() { - const list = super.observedAttributes; - list.push(ATTRIBUTE_VALUE); - return list; - } - - /** - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} - * @since 1.14.0 - * @return {boolean} - */ - static get formAssociated() { - return true; - } - - /** - * Derived classes can override and extend this method as follows. - * - * ``` - * get defaults() { - * return extends{}, super.defaults, { - * myValue:true - * }); - * } - * ``` - * - * @see {@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example} - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} - * @return {object} - * @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) - * - * @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 return the value of the control. - * - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @param {*} value - * @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 {@link https://developer.mozilla.org/en-US/docs/Web/API/ValidityState} - * @see {@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 {CustomStateSet} - * @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(); - } - -} - -/** - * @private - * @return {object} - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - * @this CustomControl - */ -function getInternal() { - const self = this; - - if (!(attachedInternalSymbol in this)) { - throw new Error('ElementInternals is not supported and a polyfill is necessary'); - } - - return this[attachedInternalSymbol]; -} - -/** - * @private - * @return {object} - * @this CustomControl - */ -function initObserver() { - const self = this; - - // value - self[attributeObserverSymbol]['value'] = () => { - self.setOption('value', self.getAttribute('value')); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {parseLocale} from "../i18n/locale.mjs"; - -import {getDocument} from "./util.mjs"; - -export {getLocaleOfDocument} - -/** - * @private - * @type {string} - */ -const DEFAULT_LANGUAGE = 'en'; - -/** - * With this function you can read the language version set by the document. - * For this the attribute `lang` in the html tag is read. If no attribute is set, `en` is used as default. - * - * ```html - * <html lang="en"> - * ``` - * - * You can call the function via the monster namespace `new Monster.DOM.getLocaleOfDocument()`. - * - * ``` - * <script type="module"> - * import {getLocaleOfDocument} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/locale.mjs'; - * new getLocaleOfDocument() - * </script> - * ``` - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {TypeError} value is not a string - * @throws {Error} unsupported locale - * @summary Tries to determine the locale used - */ -function getLocaleOfDocument() { - - 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) - } - } - - return parseLocale(DEFAULT_LANGUAGE); -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {getGlobalObject} from '../types/global.mjs'; -import {validateString} from "../types/validate.mjs"; -import {ATTRIBUTE_THEME_NAME, DEFAULT_THEME} from "./constants.mjs"; - -export {Theme, getDocumentTheme} - -/** - * The Theme class provides the functionality for the theme. - * - * ``` - * <script type="module"> - * import {Theme} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/theme.mjs'; - * console.log(new Theme()) - * </script> - * ``` - * - * @example - * - * import {getDocumentTheme} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/theme.mjs'; - * - * const theme = getDocumentTheme(); - * console.log(theme.getName()); - * // ↦ monster - * - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @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; - } - - /** - * - * @returns {string} - */ - getName() { - return this.name; - } - -} - -/** - * The theming used in the document can be defined via the html-tag. - * The theming is specified via the attribute `data-monster-theme-name`. - * - * As name for a theme all characters are valid, which are also allowed for a HTMLElement-ID. - * - * ``` - * <html data-monster-theme-name="my-theme"> - * ``` - * - * the default theme name is `monster`. - * - * @return {Theme} - * @memberOf Monster.DOM - * @since 1.7.0 - */ -function getDocumentTheme() { - 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; - } - } - - return new Theme(name); - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalStateSymbol, internalSymbol,} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {getGlobalObject} from "../types/global.mjs"; -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"; - -export {Resource, KEY_DOCUMENT, KEY_QUERY, referenceSymbol} - -/** - * @private - * @type {string} - */ -const KEY_DOCUMENT = 'document'; - -/** - * @private - * @type {string} - */ -const KEY_QUERY = 'query'; - -/** - * @private - * @type {string} - */ -const KEY_TIMEOUT = 'timeout'; - -/** - * @private - * @type {symbol} - */ -const referenceSymbol = Symbol('reference'); - -/** - * This class is the base class for all resources to be loaded. - * - * ``` - * <script type="module"> - * import {Resource} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource.mjs'; - * new Resource() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @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"); - } - -} - - -/** - * @private - * @return {Promise} - * 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') - } - - addEvents.call(self); - targetNode.appendChild(self[referenceSymbol]); - - return self; -} - -/** - * @private - * @return {addEvents} - */ -function addEvents() { - const self = this; - - 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', - }) - - 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); - - return self; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {extend} from "../data/extend.mjs"; -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {getGlobalObject} from "../types/global.mjs"; -import {isArray} from "../types/is.mjs"; -import {ATTRIBUTE_HREF, ATTRIBUTE_SRC} from "./constants.mjs"; -import {Resource} from "./resource.mjs"; -import {Data} from "./resource/data.mjs"; -import {Stylesheet} from "./resource/link/stylesheet.mjs"; -import {Script} from "./resource/script.mjs"; - -export {ResourceManager} - -/** - * The ResourceManager is a singleton that manages all resources. - * - * ``` - * <script type="module"> - * import {Resource} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resourcemanager.mjs'; - * new ResourceManager() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @summary A Resource class - */ - class ResourceManager extends BaseWithOptions { - - /** - * - * @param {Object} options - * throw {Error} unsupported document type - */ - constructor(options) { - super(options); - - if (!(this.getOption('document') instanceof Document)) { - throw new Error('unsupported document type') - } - - - } - - /** - * @property {string} baseurl - */ - getBaseURL() { - this.getOption('document')?.baseURL; - } - - /** - * - * @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 defaults() { - return Object.assign({}, super.defaults, { - 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); - } - - -} - -/** - * @private - * @param {string} method - * @return {Array} - */ -function runResourceMethod(method) { - const self = this; - - const result = []; - - 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') - } - - result.push(resource[method]()); - } - - } - - return result; -} - -/** - * - * @param {string} type - * @param {string|URL} url - * @param [Object|undefined} options - * @return {Monster.DOM.ResourceManager} - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {getGlobal} from "../types/global.mjs"; -import {validateString} from "../types/validate.mjs"; - -export {getDocument, getWindow, getDocumentFragmentFromString} - -/** - * this method fetches the document object - * - * ``` - * <script type="module"> - * import {getDocument} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/util.mjs'; - * console.log(getDocument()) - * </script> - * ``` - * - * in nodejs this functionality can be performed with [jsdom](https://www.npmjs.com/package/jsdom). - * - * ``` - * import {JSDOM} from "jsdom" - * if (typeof window !== "object") { - * const {window} = new JSDOM('', { - * url: 'http://example.com/', - * pretendToBeVisual: true - * }); - * - * [ - * 'self', - * 'document', - * 'Document', - * 'Node', - * 'Element', - * 'HTMLElement', - * 'DocumentFragment', - * 'DOMParser', - * 'XMLSerializer', - * 'NodeFilter', - * 'InputEvent', - * 'CustomEvent' - * ].forEach(key => (getGlobal()[key] = window[key])); - * } - * ``` - * - * @returns {object} - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} not supported environment - */ -function getDocument() { - let document = getGlobal()?.['document']; - if (typeof document !== 'object') { - throw new Error("not supported environment") - } - - return document; -} - -/** - * this method fetches the window object - * - * ``` - * <script type="module"> - * import {getWindow} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/util.mjs'; - * console.log(getWindow(null)) - * </script> - * ``` - * - * in nodejs this functionality can be performed with [jsdom](https://www.npmjs.com/package/jsdom). - * - * ``` - * import {JSDOM} from "jsdom" - * if (typeof window !== "object") { - * const {window} = new JSDOM('', { - * url: 'http://example.com/', - * pretendToBeVisual: true - * }); - * - * getGlobal()['window']=window; - * - * [ - * 'self', - * 'document', - * 'Document', - * 'Node', - * 'Element', - * 'HTMLElement', - * 'DocumentFragment', - * 'DOMParser', - * 'XMLSerializer', - * 'NodeFilter', - * 'InputEvent', - * 'CustomEvent' - * ].forEach(key => (getGlobal()[key] = window[key])); - * } - * ``` - * - * @returns {object} - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} not supported environment - */ -function getWindow() { - let window = getGlobal()?.['window']; - if (typeof window !== 'object') { - throw new Error("not supported environment") - } - - return window; -} - - -/** - * this method fetches the document object - * - * ``` - * <script type="module"> - * import {getDocumentFragmentFromString} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/util.mjs'; - * console.log(getDocumentFragmentFromString('<div></div>')) - * </script> - * ``` - * - * in nodejs this functionality can be performed with [jsdom](https://www.npmjs.com/package/jsdom). - * - * ``` - * import {JSDOM} from "jsdom" - * if (typeof window !== "object") { - * const {window} = new JSDOM('', { - * url: 'http://example.com/', - * pretendToBeVisual: true - * }); - * - * [ - * 'self', - * 'document', - * 'Document', - * 'Node', - * 'Element', - * 'HTMLElement', - * 'DocumentFragment', - * 'DOMParser', - * 'XMLSerializer', - * 'NodeFilter', - * 'InputEvent', - * 'CustomEvent' - * ].forEach(key => (getGlobal()[key] = window[key])); - * } - * ``` - * - * @returns {DocumentFragment} - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} not supported environment - * @throws {TypeError} value is not a string - */ -function getDocumentFragmentFromString(html) { - validateString(html); - - const document = getDocument(); - const template = document.createElement('template'); - template.innerHTML = html; - - return template.content; -} -'use strict'; - - -/** - * @author schukai GmbH - */ - -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, - -} - -/** - * default theme - * @memberOf Monster.DOM - * @type {string} - */ -const DEFAULT_THEME = 'monster'; - -/** - * @memberOf Monster.DOM - * @since 1.8.0 - * @type {string} - */ -const ATTRIBUTE_PREFIX = 'data-monster-'; - -/** - * This is the name of the attribute to pass options to a control - * - * @memberOf Monster.DOM - * @since 1.8.0 - * @type {string} - */ -const ATTRIBUTE_OPTIONS = ATTRIBUTE_PREFIX + 'options'; - -/** - * This is the name of the attribute to pass options to a control - * - * @memberOf Monster.DOM - * @since 1.30.0 - * @type {string} - */ -const ATTRIBUTE_OPTIONS_SELECTOR = ATTRIBUTE_PREFIX + 'options-selector'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_THEME_PREFIX = ATTRIBUTE_PREFIX + 'theme-'; - -/** - * @memberOf Monster.DOM - * @type {string} - */ -const ATTRIBUTE_THEME_NAME = ATTRIBUTE_THEME_PREFIX + 'name'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_ATTRIBUTES = ATTRIBUTE_PREFIX + 'attributes'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.27.1 - */ -const ATTRIBUTE_UPDATER_SELECT_THIS = ATTRIBUTE_PREFIX + 'select-this'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_REPLACE = ATTRIBUTE_PREFIX + 'replace'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_INSERT = ATTRIBUTE_PREFIX + 'insert'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_INSERT_REFERENCE = ATTRIBUTE_PREFIX + 'insert-reference'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_REMOVE = ATTRIBUTE_PREFIX + 'remove'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.9.0 - */ -const ATTRIBUTE_UPDATER_BIND = ATTRIBUTE_PREFIX + 'bind'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.27.0 - */ -const ATTRIBUTE_TEMPLATE_PREFIX = ATTRIBUTE_PREFIX + 'template-prefix'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.14.0 - */ -const ATTRIBUTE_ROLE = ATTRIBUTE_PREFIX + 'role'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.24.0 - */ -const ATTRIBUTE_DISABLED = 'disabled'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.24.0 - */ -const ATTRIBUTE_VALUE = 'value'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.9.0 - */ -const ATTRIBUTE_OBJECTLINK = ATTRIBUTE_PREFIX + 'objectlink'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.24.0 - */ -const ATTRIBUTE_ERRORMESSAGE = ATTRIBUTE_PREFIX + 'error'; - -/** - * @memberOf Monster.DOM - * @type {symbol} - * @since 1.24.0 - */ -const objectUpdaterLinkSymbol = Symbol('monsterUpdater'); - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const TAG_SCRIPT = 'script'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const TAG_STYLE = 'style'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const TAG_LINK = 'link'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ - -const ATTRIBUTE_ID = 'id'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ - -const ATTRIBUTE_CLASS = 'class'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_TITLE = 'title'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_SRC = 'src'; -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_HREF = 'href'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_TYPE = 'type'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_NONCE = 'nonce'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_TRANSLATE = 'translate'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_TABINDEX = 'tabindex'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_SPELLCHECK = 'spellcheck'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_SLOT = 'slot'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_PART = 'part'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_LANG = 'lang'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMTYPE = 'itemtype'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMSCOPE = 'itemscope'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMREF = 'itemref'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMID = 'itemid'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMPROP = 'itemprop'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_IS = 'is'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_INPUTMODE = 'inputmode'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ACCESSKEY = 'accesskey'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_AUTOCAPITALIZE = 'autocapitalize'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_AUTOFOCUS = 'autofocus'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_CONTENTEDITABLE = 'contenteditable'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_DIR = 'dir'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_DRAGGABLE = 'draggable'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ENTERKEYHINT = 'enterkeyhint'; -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_EXPORTPARTS = 'exportparts'; -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_HIDDEN = 'hidden'; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; -import {Pathfinder} from "../data/pathfinder.mjs"; - -import {parseDataURL} from "../types/dataurl.mjs"; -import {getGlobalObject} from "../types/global.mjs"; -import {isArray, isFunction, 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 {clone} from "../util/clone.mjs"; -import {addAttributeToken, addToObjectLink, getLinkedObjects, hasObjectLink} from "./attributes.mjs"; -import { - ATTRIBUTE_DISABLED, - ATTRIBUTE_ERRORMESSAGE, - ATTRIBUTE_OPTIONS, - ATTRIBUTE_OPTIONS_SELECTOR, - objectUpdaterLinkSymbol -} from "./constants.mjs"; -import {findDocumentTemplate, Template} from "./template.mjs"; -import {Updater} from "./updater.mjs"; - -export {CustomElement, initMethodSymbol, assembleMethodSymbol, attributeObserverSymbol, registerCustomElement, assignUpdaterToElement} - -/** - * @memberOf Monster.DOM - * @type {symbol} - */ -const initMethodSymbol = Symbol('initMethodSymbol'); - -/** - * @memberOf Monster.DOM - * @type {symbol} - */ -const assembleMethodSymbol = Symbol('assembleMethodSymbol'); - -/** - * this symbol holds the attribute observer callbacks. The key is the attribute name. - * @memberOf Monster.DOM - * @type {symbol} - */ -const attributeObserverSymbol = Symbol('attributeObserver'); - - -/** - * HTMLElement - * @external HTMLElement - * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement - * - * @startuml customelement-sequencediagram.png - * skinparam monochrome true - * skinparam shadowing false - * - * autonumber - * - * Script -> DOM: element = document.createElement('my-element') - * DOM -> CustomElement: constructor() - * CustomElement -> CustomElement: [initMethodSymbol]() - * - * CustomElement --> DOM: Element - * DOM --> Script : element - * - * - * Script -> DOM: document.querySelector('body').append(element) - * - * DOM -> CustomElement : connectedCallback() - * - * note right CustomElement: is only called at\nthe first connection - * CustomElement -> CustomElement : [assembleMethodSymbol]() - * - * ... ... - * - * autonumber - * - * Script -> DOM: document.querySelector('monster-confirm-button').parentNode.removeChild(element) - * DOM -> CustomElement: disconnectedCallback() - * - * - * @enduml - * - * @startuml customelement-class.png - * skinparam monochrome true - * skinparam shadowing false - * HTMLElement <|-- CustomElement - * @enduml - */ - - -/** - * To define a new HTML element we need the power of CustomElement - * - * IMPORTANT: after defining a `CustomElement`, the `registerCustomElement` method must be called - * with the new class name. only then will the tag defined via the `getTag` method be made known to the DOM. - * - * <img src="./images/customelement-class.png"> - * - * You can create the object via the function `document.createElement()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * document.createElement('monster-') - * </script> - * ``` - * - * ## Interaction - * - * <img src="./images/customelement-sequencediagram.png"> - * - * ## Styling - * - * For optimal display of custom-elements the pseudo-class :defined can be used. - * - * To prevent the custom elements from being displayed and flickering until the control is registered, it is recommended to create a css directive. - * - * In the simplest case, you can simply hide the control. - * - * ``` - * <style> - * - * my-custom-element:not(:defined) { - * display: none; - * } - * - * my-custom-element:defined { - * display: flex; - * } - * - * </style> - * ``` - * - * Alternatively you can also display a loader - * - * ``` - * my-custom-element:not(:defined) { - * display: flex; - * box-shadow: 0 4px 10px 0 rgba(33, 33, 33, 0.15); - * border-radius: 4px; - * height: 200px; - * position: relative; - * overflow: hidden; - * } - * - * my-custom-element:not(:defined)::before { - * content: ''; - * display: block; - * position: absolute; - * left: -150px; - * top: 0; - * height: 100%; - * width: 150px; - * background: linear-gradient(to right, transparent 0%, #E8E8E8 50%, transparent 100%); - * animation: load 1s cubic-bezier(0.4, 0.0, 0.2, 1) infinite; - * } - * - * @keyframes load { - * from { - * left: -150px; - * } - * to { - * left: 100%; - * } - * } - * - * my-custom-element:defined { - * display: flex; - * } - * ``` - * - * @example - * - * // In the example the the user can use his own template by creating a template in the DOM with the ID `my-custom-element`. - * // You can also specify a theme (for example `mytheme`), then it will search for the ID `my-custom-element-mytheme` and - * // if not available for the ID `my-custom-element`. - * - * class MyCustomElement extends CustomElement { - * - * static getTag() { - * return "my-custom-element" - * } - * - * } - * - * // ↦ <my-custom-element></my-custom-element> - * - * @see https://github.com/WICG/webcomponents - * @see https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @extends external:HTMLElement - * @summary A base class for HTML5 customcontrols - */ -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. - * - * @throws {Error} the options attribute does not contain a valid json definition. - * @since 1.7.0 - */ - constructor() { - super(); - this[internalSymbol] = new ProxyObserver({'options': extend({}, this.defaults)}); - this[attributeObserverSymbol] = {}; - initOptionObserver.call(this); - this[initMethodSymbol](); - } - - /** - * This method determines which attributes are to be monitored by `attributeChangedCallback()`. - * - * @return {string[]} - * @since 1.15.0 - */ - static get observedAttributes() { - return [ATTRIBUTE_OPTIONS, ATTRIBUTE_DISABLED]; - } - - /** - * Derived classes can override and extend this method as follows. - * - * ``` - * get defaults() { - * return Object.assign({}, super.defaults, { - * myValue:true - * }); - * } - * ``` - * - * To set the options via the html tag the attribute data-monster-options must be set. - * As value a JSON object with the desired values must be defined. - * - * Since 1.18.0 the JSON can be specified as a DataURI. - * - * ``` - * new Monster.Types.DataUrl(btoa(JSON.stringify({ - * shadowMode: 'open', - * delegatesFocus: true, - * templates: { - * main: undefined - * } - * })),'application/json',true).toString() - * ``` - * - * The attribute data-monster-options-selector can be used to access a script tag that contains additional configuration. - * - * As value a selector must be specified, which belongs to a script tag and contains the configuration as json. - * - * ``` - * <script id="id-for-this-config" type="application/json"> - * { - * "config-key": "config-value" - * } - * </script> - * ``` - * - * The individual configuration values can be found in the table. - * - * @property {boolean} disabled=false Object The Boolean disabled attribute, when present, makes the element not mutable, focusable, or even submitted with the form. - * @property {string} shadowMode=open `open` Elements of the shadow root are accessible from JavaScript outside the root, for example using. `close` Denies access to the node(s) of a closed shadow root from JavaScript outside it - * @property {Boolean} delegatesFocus=true A boolean that, when set to true, specifies behavior that 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 Templates - * @property {string} templates.main=undefined Main template - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow - * @since 1.8.0 - */ - get defaults() { - return { - ATTRIBUTE_DISABLED: this.getAttribute(ATTRIBUTE_DISABLED), - shadowMode: 'open', - delegatesFocus: true, - templates: { - main: undefined - } - }; - } - - /** - * There is no check on the name by this class. the developer is responsible for assigning an appropriate tag. - * if the name is not valid, registerCustomElement() will issue an error - * - * @link https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name - * @return {string} - * @throws {Error} the method getTag must be overwritten by the derived class. - * @since 1.7.0 - */ - static getTag() { - throw new Error("the method getTag must be overwritten by the derived class."); - } - - /** - * At this point a `CSSStyleSheet` object can be returned. If the environment does not - * support a constructor, then an object can also be built using the following detour. - * - * If `undefined` is returned then the shadowRoot does not get a stylesheet. - * - * ``` - * const doc = document.implementation.createHTMLDocument('title'); - * - * let style = doc.createElement("style"); - * style.innerHTML="p{color:red;}"; - * - * // WebKit Hack - * style.appendChild(document.createTextNode("")); - * // Add the <style> element to the page - * doc.head.appendChild(style); - * return doc.styleSheets[0]; - * ; - * ``` - * - * @return {CSSStyleSheet|CSSStyleSheet[]|string|undefined} - */ - 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; - } - - /** - * Is called once when the object is included in the DOM for the first time. - * - * @return {CustomElement} - * @since 1.8.0 - */ - [assembleMethodSymbol]() { - - const self = this; - let elements, nodeList; - - const AttributeOptions = getOptionsFromAttributes.call(self); - if (isObject(AttributeOptions) && Object.keys(AttributeOptions).length > 0) { - self.setOptions(AttributeOptions); - } - - const ScriptOptions = getOptionsFromScriptTag.call(self); - if (isObject(ScriptOptions) && Object.keys(ScriptOptions).length > 0) { - self.setOptions(ScriptOptions); - } - - - if (self.getOption('shadowMode', false) !== false) { - try { - initShadowRoot.call(self); - elements = self.shadowRoot.childNodes; - - } catch (e) { - - } - - try { - initCSSStylesheet.call(this); - } catch (e) { - addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString()); - } - } - - if (!(elements instanceof NodeList)) { - if (!(elements instanceof NodeList)) { - initHtmlContent.call(this); - elements = this.childNodes; - } - } - - try { - nodeList = new Set([ - ...elements, - ...getSlottedElements.call(self) - ]) - } catch (e) { - nodeList = elements - } - - assignUpdaterToElement.call(self, nodeList, clone(self[internalSymbol].getRealSubject()['options'])); - return self; - } - - /** - * Called every time the element is inserted into the DOM. Useful for running setup code, such as - * fetching resources or rendering. Generally, you should try to delay work until this time. - * - * @return {void} - * @since 1.7.0 - */ - connectedCallback() { - let self = this; - if (!hasObjectLink(self, objectUpdaterLinkSymbol)) { - 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; - - const callback = self[attributeObserverSymbol]?.[attrName]; - - if (isFunction(callback)) { - callback.call(self, newVal, oldVal); - } - - } - - /** - * - * @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); - - } - -} - -/** - * @private - * @param {String|undefined} query - * @param {String|undefined|null} name name of the slot (if the parameter is undefined, all slots are searched, if the parameter has the value null, all slots without a name are searched. if a string is specified, the slots with this name are searched.) - * @return {*} - * @this CustomElement - * @since 1.23.0 - * @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; -} - -/** - * @this CustomElement - * @private - * @param {Node} node - * @return {boolean} - */ -function containChildNode(node) { - const self = this; - - if (self.contains(node)) { - return true; - } - - for (const [, e] of Object.entries(self.childNodes)) { - if (e.contains(node)) { - return true; - } - - containChildNode.call(e, node); - } - - - return false; -} - -/** - * @since 1.15.0 - * @private - * @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, objectUpdaterLinkSymbol)) { - return; - } - // inform every element - const updaters = getLinkedObjects(self, objectUpdaterLinkSymbol); - - 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); - } - } - - -} - -/** - * @private - * @return {object} - * @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; - -} - -/** - * @private - * @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 {}; -} - -/** - * @private - * @param data - * @return {Object} - */ -function parseOptionsJSON(data) { - - const self = this, 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) { - - } - - try { - let obj = JSON.parse(data); - return validateObject(obj); - } catch (e) { - throw e; - } - - - return obj; -} - -/** - * @private - * @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) { - this.innerHTML = html; - } - - } - - return this; - -} - -/** - * @private - * @return {CustomElement} - * @memberOf Monster.DOM - * @this CustomElement - * @since 1.16.0 - * @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; - -} - -/** - * @private - * @return {CustomElement} - * @throws {Error} html is not set. - * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow - * @memberOf Monster.DOM - * @since 1.8.0 - */ -function initShadowRoot() { - - let template, 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; - } - - this.shadowRoot.innerHTML = html; - return this; -} - -/** - * This method registers a new element. The string returned by `CustomElement.getTag()` is used as the tag. - * - * @param {CustomElement} element - * @return {void} - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {DOMException} Failed to execute 'define' on 'CustomElementRegistry': is not a valid custom element name - */ -function registerCustomElement(element) { - validateFunction(element); - getGlobalObject('customElements').define(element.getTag(), element); -} - - -/** - * - * @param element - * @param object - * @return {Promise[]} - * @since 1.23.0 - * @memberOf Monster.DOM - */ -function assignUpdaterToElement(elements, object) { - - const updaters = new Set; - - if (elements instanceof NodeList) { - elements = new Set([ - ...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(this, objectUpdaterLinkSymbol, updaters); - } - - return result; -} -'use strict'; - -/** - * In this namespace you will find classes and methods for handling the DOM. - * - * @namespace Monster.DOM - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - - -import {getGlobalFunction} from "../types/global.mjs"; -import {TokenList} from "../types/tokenlist.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 -} - -/** - * Get the closest object link of a node - * - * if a node is specified without a object link, a recursive search upwards is performed until the corresponding - * object link is found, or undefined is returned. - * - * ``` - * <script type="module"> - * import {getUpdaterFromNode} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/updater.mjs'; - * console.log(findClosestObjectLink()) - * </script> - * ``` - * - * @param {HTMLElement} element - * @return {HTMLElement|undefined} - * @since 1.10.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {TypeError} value is not an instance of HTMLElement - */ -function findClosestObjectLink(element) { - return findClosestByAttribute(element, ATTRIBUTE_OBJECTLINK); -} - -/** - * Adds a class attribute to an element. - * - * ``` - * <script type="module"> - * import {addToObjectLink} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * addToObjectLink(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {Symbol} symbol - * @param {Object} object - * @return {boolean} - */ -function addToObjectLink(element, symbol, object) { - - validateInstance(element, HTMLElement); - validateSymbol(symbol) - - if (element?.[symbol] === undefined) { - element[symbol] = new Set; - } - - addAttributeToken(element, ATTRIBUTE_OBJECTLINK, symbol.toString()); - element[symbol].add(object); - return element; - -} - -/** - * Removes an object from an element - * - * ``` - * <script type="module"> - * import {removeObjectLink} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * removeObjectLink(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {Symbol} symbol - * @return {boolean} - */ -function removeObjectLink(element, symbol) { - - validateInstance(element, HTMLElement); - validateSymbol(symbol) - - if (element?.[symbol] === undefined) { - return element - } - - removeAttributeToken(element, ATTRIBUTE_OBJECTLINK, symbol.toString()); - delete element[symbol]; - return element; - -} - - -/** - * Checks if an element has an object link - * - * ``` - * <script type="module"> - * import {hasObjectLink} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * hasObjectLink(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {Symbol} 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()); - -} - -/** - * The ObjectLink can be used to attach objects to HTMLElements. The elements are kept in a set under a unique - * symbol and can be read via an iterator {@see {@link getLinkedObjects}}. - * - * In addition, elements with an objectLink receive the attribute `data-monster-objectlink`. - * - * With the method {@see {@link addToObjectLink}} the objects can be added. - * - * ``` - * <script type="module"> - * import {getLinkedObjects} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * getLinkedObjects(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {Symbol} symbol - * @return {Iterator} - * @throws {Error} there is no object link for symbol - */ -function getLinkedObjects(element, symbol) { - - validateInstance(element, HTMLElement); - validateSymbol(symbol) - - if (element?.[symbol] === undefined) { - throw new Error('there is no object link for ' + symbol.toString()); - } - - return element?.[symbol][Symbol.iterator](); - -} - - -/** - * With this method tokens in an attribute can be switched on or off. For example, classes can be switched on and off in the elements class attribute. - * - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {toggleAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * toggleAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} token - * @return {HTMLElement} - */ -function toggleAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token) - validateString(key) - - if (!element.hasAttribute(key)) { - element.setAttribute(key, token); - return element; - } - - element.setAttribute(key, new TokenList(element.getAttribute(key)).toggle(token).toString()); - - return element -} - -/** - * This method can be used to add a token to an attribute. Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {addAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * addAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} token - * @return {HTMLElement} - */ -function addAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token) - validateString(key) - - if (!element.hasAttribute(key)) { - element.setAttribute(key, token); - return element; - } - - element.setAttribute(key, new TokenList(element.getAttribute(key)).add(token).toString()); - - return element -} - -/** - * This function can be used to remove tokens from an attribute. - * - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {removeAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * removeAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} token - * @return {HTMLElement} - */ -function removeAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token) - validateString(key) - - if (!element.hasAttribute(key)) { - return element; - } - - element.setAttribute(key, new TokenList(element.getAttribute(key)).remove(token).toString()); - - return element -} - -/** - * This method can be used to determine whether an attribute has a token. - * - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {containsAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * containsAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} token - * @return {boolean} - */ -function containsAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token) - validateString(key) - - if (!element.hasAttribute(key)) { - return false; - } - - return new TokenList(element.getAttribute(key)).contains(token); - -} - -/** - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {replaceAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * replaceAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} from - * @param {string} to - * @return {HTMLElement} - */ -function replaceAttributeToken(element, key, from, to) { - validateInstance(element, HTMLElement); - validateString(from) - validateString(to) - validateString(key) - - if (!element.hasAttribute(key)) { - return element; - } - - element.setAttribute(key, new TokenList(element.getAttribute(key)).replace(from, to).toString()); - - return element -} - -/** - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {clearAttributeTokens} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * clearAttributeTokens(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @return {HTMLElement} - */ -function clearAttributeTokens(element, key) { - validateInstance(element, HTMLElement); - validateString(key) - - if (!element.hasAttribute(key)) { - return element; - } - - element.setAttribute(key, ""); - - return element -} - -/** - * This function searches, starting from an `HTMLElemement`, for the next element that has a certain attribute. - * - * ```html - * <div data-my-attribute="2" id="2"> - * <div id="1"></div> - * </div> - * ``` - * - * ```javascript - * // if no value is specified (undefined), then only the attribute is checked. - * findClosestByAttribute(document.getElementById('1'),'data-my-attribute'); // ↦ node with id 2 - * findClosestByAttribute(document.getElementById('2'),'data-my-attribute'); // ↦ node with id 2 - * - * // if a value is specified, for example an empty string, then the name and the value are checked. - * findClosestByAttribute(document.getElementById('1'),'data-my-attribute', ''); // ↦ undefined - * findClosestByAttribute(document.getElementById('1'),'data-my-attribute', '2'); // ↦ node with id 2 - * ``` - * - * ``` - * <script type="module"> - * import {findClosestByAttribute} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * findClosestByAttribute(); - * </script> - * ``` - * - * @since 1.14.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string|undefined} value - * @return {HTMLElement|undefined} - * @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; -} - -/** - * This function searches, starting from an `HTMLElemement`, for the next element that has a certain attribute. - * - * ```html - * <div class="myclass" id="2"> - * <div id="1"></div> - * </div> - * ``` - * - * ```javascript - * // if no value is specified (undefined), then only the attribute is checked. - * findClosestByClass(document.getElementById('1'),'myclass'); // ↦ node with id 2 - * findClosestByClass(document.getElementById('2'),'myclass'); // ↦ node with id 2 - * ``` - * - * ``` - * <script type="module"> - * import {findClosestByClass} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * findClosestByClass(); - * </script> - * ``` - * - * @since 1.27.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} className - * @return {HTMLElement|undefined} - * @summary find closest node - */ -function findClosestByClass(element, className) { - validateInstance(element, getGlobalFunction('HTMLElement')); - - if (element?.classList?.contains(validateString(className))) { - return element; - } - - let result = element.closest('.' + className); - if (result instanceof HTMLElement) { - return result; - } - - return undefined; -} -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {isArray, isObject} from "../types/is.mjs"; -import {validateInstance, validateString} from "../types/validate.mjs"; -import {getDocument} from "./util.mjs"; - -export {fireEvent, fireCustomEvent, findTargetElementFromEvent} - -/** - * The function sends an event - * - * ``` - * <script type="module"> - * import {fireEvent} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/events.mjs'; - * fireEvent() - * </script> - * ``` - * - * @param {HTMLElement|HTMLCollection|NodeList} element - * @param {string} type - * @return {void} - * @since 1.10.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {TypeError} value is not an instance of HTMLElement or HTMLCollection - * @summary Construct and send and event - */ -function fireEvent(element, type) { - - const document = getDocument(); - - if (element instanceof HTMLElement) { - - if (type === 'click') { - element.click(); - return; - } - - let event = new Event(validateString(type), { - bubbles: true, - cancelable: 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') - } - -} - -/** - * You can call the function via the monster namespace `new Monster.DOM.fireCustomEvent()`. - * - * ``` - * <script type="module"> - * import {fireCustomEvent} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/events.mjs'; - * fireCustomEvent() - * </script> - * ``` - * - * @param {HTMLElement|HTMLCollection|NodeList} element - * @param {string} type - * @return {void} - * @since 1.29.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {TypeError} value is not an instance of HTMLElement or HTMLCollection - * @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, - 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') - } - -} - -/** - * This function gets the path `Event.composedPath()` from an event and tries to find the next element - * up the tree `element.closest()` with the attribute and value. If no value, or a value that is undefined or null, - * is specified, only the attribute is searched. - * - * ``` - * <script type="module"> - * import {findTargetElementFromEvent} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/events.mjs'; - * findTargetElementFromEvent() - * </script> - * ``` - * - * @since 1.14.0 - * @param {Event} event - * @param {string} attributeName - * @param {string|null|undefined} attributeValue - * @throws {Error} unsupported event - * @memberOf Monster.DOM - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not an instance of HTMLElement - * @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; - -} -'use strict'; - - -/** - * @author schukai GmbH - */ - - -import {internalSymbol} from "../../constants.mjs"; -import {Base} from "../../types/base.mjs"; -import {getGlobal, getGlobalFunction} from "../../types/global.mjs"; -import {isFunction} from "../../types/is.mjs"; -import {validateInstance, validateString} from "../../types/validate.mjs"; - -export {Factory} - -/** - * A factory for creating worker instances. - * - * ``` - * <script type="module"> - * import {Factory} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/worker/factory.mjs'; - * console.log(new Factory()) - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Worker - * @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; - } - - -} -'use strict'; - -/** - * In this namespace you will find classes and methods for handling the DOM. - * - * @namespace Monster.DOM.Worker - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {getGlobalFunction, getGlobalObject} from '../types/global.mjs'; -import {validateInstance, validateString} from "../types/validate.mjs"; -import {ATTRIBUTE_TEMPLATE_PREFIX} from "./constants.mjs"; -import {getDocumentTheme} from "./theme.mjs"; - -export {Template} - -/** - * The template class provides methods for creating templates. - * - * ``` - * <script type="module"> - * import {Template} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/template.mjs'; - * new Template() - * </script> - * ``` - * - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @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; - } - - /** - * - * @returns {HTMLTemplateElement} - */ - getTemplateElement() { - return this.template; - } - - /** - * - * @return {DocumentFragment} - * @throws {TypeError} value is not an instance of - */ - createDocumentFragment() { - return this.template.content.cloneNode(true); - } - -} - -/** - * This method loads a template with the given ID and returns it. - * - * To do this, it first reads the theme of the document and looks for the `data-monster-theme-name` attribute in the HTML tag. - * - * ``` - * <html data-monster-theme-name="my-theme"> - * ``` - * - * If no theme was specified, the default theme is `monster`. - * - * Now it is looked if there is a template with the given ID and theme `id-theme` and if yes it is returned. - * If there is no template a search for a template with the given ID `id` is done. If this is also not found, an error is thrown. - * - * You can call the method via the monster namespace `Monster.DOM.findDocumentTemplate()`. - * - * ``` - * <script type="module"> - * import {findTemplate} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/template.mjs'; - * findDocumentTemplate() - * </script> - * ``` - * - * @example - * - * import { findDocumentTemplate } from "https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/template.mjs"; - * - * const template = document.createElement("template"); - * template.id = "myTemplate"; - * template.innerHTML = "<p>my default template</p>"; - * document.body.appendChild(template); - * - * const themedTemplate = document.createElement("template"); - * themedTemplate.id = "myTemplate-myTheme"; - * themedTemplate.innerHTML = "<p>my themed template</p>"; - * document.body.appendChild(themedTemplate); - * - * // loads the temple and since no theme is set the default template - * const template1 = findDocumentTemplate("myTemplate"); - * console.log(template1.createDocumentFragment()); - * // ↦ '<p>my default template</p>' - * - * // now we set our own theme - * document - * .querySelector("html") - * .setAttribute("data-monster-theme-name", "myTheme"); - * - * // now we don't get the default template, - * // but the template with the theme in the id - * const template2 = findDocumentTemplate("myTemplate"); - * console.log(template2.createDocumentFragment()); - * // ↦ '<p>my themed template</p>' - * - * @param {string} id - * @param {Node} currentNode - * @return {Monster.DOM.Template} - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} template id not found. - * @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.") -} - -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {getDocument, getWindow} from "./util.mjs"; - -export {domReady, windowReady} - -/** - * This variable is a promise that is fulfilled as soon as the dom is available. - * - * The DOMContentLoaded event is fired when the original HTML document is fully loaded and parsed - * without waiting for stylesheets, images, and subframes to finish loading. - * - * ``` - * <script type="module"> - * import {domReady} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/ready.mjs'; - * domReady().then(()=>{ - * // ... - * }) - * </script> - * ``` - * - * @since 1.31.0 - * @memberOf Monster.DOM - * @summary variable to check if dom is ready - * @type {Promise} - * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event - * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState - */ -const domReady = new Promise(resolve => { - - const document = getDocument(); - - if (document.readyState === "loading") { - document.addEventListener('DOMContentLoaded', resolve); - } else { - resolve(); - } -}); - - -/** - * This variable is a promise that is fulfilled as soon as the windows is available. - * - * The load event fires when the entire page is loaded, including all dependent resources such as stylesheets, - * assets, and images. Unlike DOMContentLoaded, which fires as soon as the DOM of the page is loaded, - * without waiting for the resources to finish loading. - * - * ``` - * <script type="module"> - * import {windowReady} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/ready.mjs'; - * windowReady().then(()=>{ - * // ... - * }) - * </script> - * ``` - * - * @since 1.31.0 - * @memberOf Monster.DOM - * @summary variable to check if window is ready - * @type {Promise} - * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event - * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState - */ -const windowReady = new Promise(resolve => { - - const document = getDocument(); - const window = getWindow(); - - if (document.readyState === 'complete') { - resolve(); - } else { - window.addEventListener('load', resolve); - } -}); -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {extend} from "../data/extend.mjs"; -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {getGlobalObject} from "../types/global.mjs"; -import {isArray} from "../types/is.mjs"; -import {Stack} from "../types/stack.mjs"; -import {validateInstance, validateString} from "../types/validate.mjs"; - -export {FocusManager} - -/** - * @private - * @type {string} - */ -const KEY_DOCUMENT = 'document'; - -/** - * @private - * @type {string} - */ -const KEY_CONTEXT = 'context'; - - -/** - * @private - * @type {Symbol} - */ -const stackSymbol = Symbol('stack'); - - -/** - * With the focusmanager the focus can be stored in a document, recalled and moved. - * - * ``` - * <script type="module"> - * import {FocusManager} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/focusmanager.mjs'; - * new FocusManager() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} unsupported locale - * @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(); - } - - /** - * @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; - } - - -} - - - - - - - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {getGlobalFunction} from "../types/global.mjs"; -import {ProxyObserver} from "../types/proxyobserver.mjs"; -import {validateInstance, validateString} from "../types/validate.mjs"; - -export {ATTRIBUTEPREFIX,Assembler} - -/** - * attribute prefix - * - * @type {string} - * @memberOf Monster.DOM - */ -const ATTRIBUTEPREFIX = "data-monster-"; - -/** - * Assembler class - * - * ``` - * <script type="module"> - * import {Assembler} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/assembler.mjs'; - * console.log(new Assembler()) - * </script> - * ``` - * - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @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 {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; - } - - /** - * - * @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; - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from "../types/base.mjs"; -import {isObject, isString} from "../types/is.mjs"; -import {validateInstance, validateInteger, validateObject, validateString} from "../types/validate.mjs"; -import {Locale, parseLocale} from "./locale.mjs"; - -export {Translations} - -/** - * With this class you can manage translations and access the keys. - * - * ``` - * <script type="module"> - * import {Translations} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/translations.mjs'; - * new Translations() - * </script> - * ``` - * - * @example - * - * import {Translations} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/translations.mjs'; - * import {parseLocale} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/locale.mjs'; - * - * const translation = new Translations(parseLocale('en-GB')); - * - * translation.assignTranslations({ - * text1: "click", - * text2: { - * 'one': 'click once', - * 'other': 'click n times' - * } - * }); - * - * console.log(translation.getText('text1')); - * // ↦ click - * - * console.log(translation.getPluralRuleText('text2',1)); - * // -> click once - * console.log(translation.getPluralRuleText('text2',2)); - * // -> click n times - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n - * @see https://datatracker.ietf.org/doc/html/rfc3066 - */ -class Translations extends Base { - - /** - * - * @param {Locale} locale - */ - constructor(locale) { - super(); - - if (isString(locale)) { - locale = parseLocale(locale); - } - - this.locale = validateInstance(locale, Locale); - this.storage = new Map(); - - } - - - /** - * 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 handlig 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]); - } - - 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); - - for (const [k, v] of Object.entries(translations)) { - this.setText(k, v); - } - - return this; - - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from "../types/base.mjs"; -import {validateString} from "../types/validate.mjs"; -import {clone} from "../util/clone.mjs"; - -export {Locale, parseLocale} - -/** - * @memberOf Monster.I18n - * @type {symbol} - */ -const propertiesSymbol = Symbol('properties'); - -/** - * @type {symbol} - * @memberOf Monster.I18n - */ -const localeStringSymbol = Symbol('localeString'); - -/** - * The Locale class is a base class for the language classes. - * - * ``` - * <script type="module"> - * import {Locale} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/locale.mjs'; - * new Locale() - * </script> - * ``` - * - * RFC - * - * ``` - * A Language-Tag consists of: - * langtag ; generated tag - * -or- private-use ; a private use tag - * - * langtag = (language - * ["-" script] - * ["-" region] - * *("-" variant) - * *("-" extension) - * ["-" privateuse]) - * - * language = "en", "ale", or a registered value - * - * script = "Latn", "Cyrl", "Hant" ISO 15924 codes - * - * region = "US", "CS", "FR" ISO 3166 codes - * "419", "019", or UN M.49 codes - * - * variant = "rozaj", "nedis", "1996", multiple subtags can be used in a tag - * - * extension = single letter followed by additional subtags; more than one extension - * may be used in a language tag - * - * private-use = "x-" followed by additional subtags, as many as are required - * Note that these can start a tag or appear at the end (but not - * in the middle) - * ``` - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n - * @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(); - - 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); - - if (s.length === 0) { - throw new Error('unsupported locale'); - } - - this[localeStringSymbol] = s.join('-'); - - } - - /** - * @return {string} - */ - get localeString() { - return this[localeStringSymbol]; - } - - /** - * @return {string|undefined} - */ - get language() { - return this[propertiesSymbol].language; - } - - /** - * @return {string|undefined} - */ - get region() { - return this[propertiesSymbol].region; - } - - /** - * @return {string|undefined} - */ - get script() { - return this[propertiesSymbol].script; - } - - /** - * @return {string|undefined} - */ - get variants() { - return this[propertiesSymbol].variants; - } - - /** - * @return {string|undefined} - */ - get extlang() { - return this[propertiesSymbol].extlang; - } - - /** - * @return {string|undefined} - */ - get privateUse() { - return this[propertiesSymbol].privateValue; - } - - - /** - * @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]) - } - - -} - -/** - * @typedef {Object} LocaleMap - * @property {string} language - * @property {string} script - * @property {string} region - * @property {string} variants - * @property {string} extlang - * @property {string} privateUse - * @memberOf Monster.I18n - */ - -/** - * Parse local according to rfc4646 standard - * - * Limitations: The regex cannot handle multiple variants or private. - * - * You can call the method via the monster namespace `Monster.I18n.createLocale()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * new Monster.I18n.createLocale() - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {createLocale} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/locale.mjs'; - * createLocale() - * </script> - * ``` - * - * RFC - * - * ``` - * The syntax of the language tag in ABNF [RFC4234] is: - * - * Language-Tag = langtag - * / privateuse ; private use tag - * / grandfathered ; grandfathered registrations - * - * langtag = (language - * ["-" script] - * ["-" region] - * *("-" variant) - * *("-" extension) - * ["-" privateuse]) - * - * language = (2*3ALPHA [ extlang ]) ; shortest ISO 639 code - * / 4ALPHA ; reserved for future use - * / 5*8ALPHA ; registered language subtag - * - * extlang = *3("-" 3ALPHA) ; reserved for future use - * - * script = 4ALPHA ; ISO 15924 code - * - * region = 2ALPHA ; ISO 3166 code - * / 3DIGIT ; UN M.49 code - * - * variant = 5*8alphanum ; registered variants - * / (DIGIT 3alphanum) - * - * extension = singleton 1*("-" (2*8alphanum)) - * - * singleton = %x41-57 / %x59-5A / %x61-77 / %x79-7A / DIGIT - * ; "a"-"w" / "y"-"z" / "A"-"W" / "Y"-"Z" / "0"-"9" - * ; Single letters: x/X is reserved for private use - * - * privateuse = ("x"/"X") 1*("-" (1*8alphanum)) - * - * grandfathered = 1*3ALPHA 1*2("-" (2*8alphanum)) - * ; grandfathered registration - * ; Note: i is the only singleton - * ; that starts a grandfathered tag - * - * alphanum = (ALPHA / DIGIT) ; letters and numbers - * - * Figure 1: Language Tag ABNF - * ``` - * - * @param {string} locale - * @returns {Locale} - * @since 1.14.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n - * @throws {TypeError} value is not a string - * @throws {Error} unsupported locale - */ -function parseLocale(locale) { - - locale = validateString(locale).replace(/_/g, "-"); - - let language, region, variants, parts, script, extlang, - regexRegular = "(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)", - 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)", - regexGrandfathered = "(" + regexIrregular + "|" + regexRegular + ")", - regexPrivateUse = "(x(-[A-Za-z0-9]{1,8})+)", - regexSingleton = "[0-9A-WY-Za-wy-z]", - regexExtension = "(" + regexSingleton + "(-[A-Za-z0-9]{2,8})+)", - regexVariant = "([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})", - regexRegion = "([A-Za-z]{2}|[0-9]{3})", - regexScript = "([A-Za-z]{4})", - regexExtlang = "([A-Za-z]{3}(-[A-Za-z]{3}){0,2})", - regexLanguage = "(([A-Za-z]{2,3}(-" + regexExtlang + ")?)|[A-Za-z]{4}|[A-Za-z]{5,8})", - regexLangtag = "(" + regexLanguage + "(-" + regexScript + ")?" + "(-" + regexRegion + ")?" + "(-" + regexVariant + ")*" + "(-" + regexExtension + ")*" + "(-" + regexPrivateUse + ")?" + ")", - regexLanguageTag = "^(" + regexGrandfathered + "|" + regexLangtag + "|" + regexPrivateUse + ")$", - regex = new RegExp(regexLanguageTag), match; - - - 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[6] !== undefined) { - language = match[6]; - - parts = language.split('-'); - if (parts.length > 1) { - language = parts[0]; - extlang = parts[1]; - } - - } - - if (match[14] !== undefined) { - region = match[14]; - } - - if (match[12] !== undefined) { - script = match[12]; - } - - if (match[16] !== undefined) { - variants = match[16]; - } - - return new Locale(language, region, script, variants, extlang); - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {internalSymbol} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; - -import {Formatter as TextFormatter} from "../text/formatter.mjs"; -import {validateInstance, validateString} from "../types/validate.mjs"; -import {Translations} from "./translations.mjs"; - -export {Formatter} - -/** - * @private - * @type {symbol} - */ -const internalTranslationSymbol = Symbol('internalTranslation') - -/** - * The Formatter extends the Text.Formatter with the possibility to replace the key by a translation. - * - * ``` - * <script type="module"> - * import {Formatter} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/formatter.mjs'; - * new Formatter() - * </script> - * ``` - * - * @example - * - * import {Formatter} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/formatter.mjs'; - * import {Translations} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/translations.mjs'; - * - * const translations = new Translations('en') - * .assignTranslations({ - * thekey: "${animal} has eaten the ${food}!" - * }); - * - * new Formatter({}, translations).format("thekey:animal=dog::food=cake") - * // ↦ dog has eaten the cake! - * - * @since 1.26.0 - * @copyright schukai GmbH - * @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); - } - - /** - * @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) - - 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(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(); - - - let assembledText = openMarker + 'static:' + translationKey + ' | call:i18n'; - if (parameter.length > 0) { - assembledText += '::' + parameter; - } - assembledText += closeMarker; - return super.format(assembledText); - } - - -} -'use strict'; - -/** - * In this namespace you will find classes and methods for handling locale and localized texts. - * - * @namespace Monster.I18n.Providers - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../constants.mjs"; -import {extend} from "../../data/extend.mjs"; -import {Formatter} from "../../text/formatter.mjs"; -import {getGlobalFunction} from "../../types/global.mjs"; -import {isInstance, isString} from "../../types/is.mjs"; -import {validateObject, validateString} from "../../types/validate.mjs"; -import {parseLocale} from "../locale.mjs"; -import {Provider} from "../provider.mjs"; -import {Translations} from "../translations.mjs"; - -export {Fetch} - -/** - * The fetch provider retrieves a JSON file from the given URL and returns a translation object. - * - * ``` - * <script type="module"> - * import {Fetch} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/providers/fetch.mjs'; - * new Fetch() - * </script> - * ``` - * - * @example <caption>das ist ein test</caption> - * - * import {Fetch} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/providers/fetch.mjs'; - * - * // fetch from API - * const translation = new Fetch('https://example.com/${language}.json').getTranslation('en-GB'); - * // ↦ https://example.com/en.json - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n.Providers - * @see {@link https://datatracker.ietf.org/doc/html/rfc3066} - * @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); - - if (isInstance(url, URL)) { - url = url.toString(); - } - - if (options === undefined) { - options = {}; - } - - validateString(url); - - /** - * @property {string} - */ - this.url = url; - - /** - * @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 { - 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 - } - } - - } - - /** - * - * @param {Locale|string} locale - * @return {Promise} - */ - getTranslations(locale) { - - if (isString(locale)) { - locale = parseLocale(locale); - } - - 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); - }); - - } - - -} - -'use strict'; - -/** - * In this namespace you will find classes and methods for handling locale and localized texts. - * - * @namespace Monster.I18n - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {Locale} from "./locale.mjs" -import {Translations} from "./translations.mjs" - -export {Provider} - -/** - * A provider makes a translation object available. - * - * ``` - * <script type="module"> - * import {Provider} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/provider.mjs'; - * new Provider() - * </script> - * ``` - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n - * @see {@link https://datatracker.ietf.org/doc/html/rfc3066} - */ -class Provider extends BaseWithOptions { - - /** - * @param {Locale|string} locale - * @return {Promise} - */ - getTranslations(locale) { - return new Promise((resolve, reject) => { - try { - resolve(new Translations(locale)); - } catch (e) { - reject(e); - } - - }); - } - -} -'use strict'; - -/** - * Property-Keys - * @author schukai GmbH - */ - -export { - internalSymbol, - internalStateSymbol -} - - -/** - * @private - * @type {symbol} - * @memberOf Monster - * @since 1.24.0 - */ -const internalSymbol = Symbol('internalData'); - -/** - * @private - * @type {symbol} - * @memberOf Monster - * @since 1.25.0 - */ -const internalStateSymbol = Symbol('state'); - -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {Base} from "./base.mjs"; -import {isString} from "./is.mjs"; -import {validateArray, validateString} from "./validate.mjs"; - -export {MediaType, parseMediaType} - -/** - * @private - * @type {symbol} - */ -const internal = Symbol('internal'); - -/** - * @typedef {Object} Parameter - * @property {string} key - * @property {string} value - * @memberOf Monster.Types - */ - - -/** - * You can create an object via the monster namespace `new Monster.Types.MediaType()`. - * - * ``` - * <script type="module"> - * import {MediaType} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/mediatype.mjs'; - * console.log(new MediaType()) - * </script> - * ``` - * - * @since 1.8.0 - * @copyright schukai GmbH - * @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); - } - - - } - - /** - * @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(';') : ''); - } - -} - -/** - * You can call the function via the monster namespace `Monster.Types.parseMediaType()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * console.log(Monster.Types.parseMediaType()) - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {parseMediaType} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/dataurl.mjs'; - * console.log(parseMediaType()) - * </script> - * ``` - * - * Specification: - * - * ``` - * dataurl := "data:" [ mediatype ] [ ";base64" ] "," data - * mediatype := [ type "/" subtype ] *( ";" parameter ) - * data := *urlchar - * parameter := attribute "=" value - * ``` - * - * @param {String} mediatype - * @return {Monster.Types.MediaType} - * @see https://datatracker.ietf.org/doc/html/rfc2045#section-5.1 - * @throws {TypeError} the mimetype can not be parsed - * @throws {TypeError} blank value is not allowed - * @throws {TypeError} malformed data url - * @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 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']; - - if (subtype === "" || type === "") { - throw new TypeError('blank value is not allowed'); - } - - return new MediaType(type, subtype, parseParameter(parameter)); - - -} - -/** - * @private - * @since 1.18.0 - * @param {String} parameter - * @return {Monster.Types.Parameter[]|undefined} - * @memberOf Monster.Types - */ -function parseParameter(parameter) { - - if (!isString(parameter)) { - return undefined; - } - - let result = []; - - parameter.split(';').forEach((entry) => { - - entry = entry.trim(); - if (entry === "") { - return; - } - - const kv = entry.split('=') - - 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 - }) - - - }) - - return result; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -export {typeOf} - -/** - * The built-in typeof method is known to have some historical weaknesses. This function tries to provide a better and more accurate result. - * - * ``` - * <script type="module"> - * import {typeOf} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/typeof.mjs'; - * console.log(typeOf()) - * </script> - * ``` - * - * @example - * - * import {typeOf} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/typeof.mjs'; - * - * console.log(typeOf(undefined)); // ↦ undefined - * console.log(typeOf("")); // ↦ string - * console.log(typeOf(5)); // ↦ number - * console.log(typeOf({})); // ↦ object - * console.log(typeOf([])); // ↦ array - * console.log(typeOf(new Map)); // ↦ map - * console.log(typeOf(true)); // ↦ boolean - * - * @param {*} value - * @return {string} - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @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(); - } - - const results = (/^(class|function)\s+(\w+)/).exec(value.constructor.toString()); - type = (results && results.length > 2) ? results[2] : ''; - } - - return type.toLowerCase(); -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {Observer} from "./observer.mjs"; -import {validateInstance} from "./validate.mjs"; - -export {ObserverList} - -/** - * With the help of the ObserverList class, observer can be managed. - * - * ``` - * <script type="module"> - * import {ObserverList} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/observerlist.mjs'; - * console.log(ObserverList()) - * console.log(ObserverList()) - * </script> - * ``` - * - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -class ObserverList extends Base { - - /** - * - */ - constructor() { - super(); - this.observers = []; - } - - /** - * - * @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; - }; - - /** - * - * @param {Observer} observer - * @return {ObserverList} - * @throws {TypeError} value is not an instance of Observer - */ - detach(observer) { - validateInstance(observer, Observer) - - var i = 0, l = this.observers.length; - for (; i < l; i++) { - if (this.observers[i] === observer) { - this.observers.splice(i, 1); - } - } - - return this; - }; - - /** - * - * @param {Observer} observer - * @return {boolean} - * @throws {TypeError} value is not an instance of Observer - */ - contains(observer) { - validateInstance(observer, Observer) - var i = 0, 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 = [] - - let i = 0, l = this.observers.length; - for (; i < l; i++) { - pomises.push(this.observers[i].update(subject)); - } - - return Promise.all(pomises); - }; - -} -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {random} from "../math/random.mjs"; -import {getGlobal} from "./global.mjs"; -import {ID} from "./id.mjs"; - -export {RandomID} - -/** - * @private - * @type {number} - */ -let internalCounter = 0; - -/** - * The `RandomID` class provides a unique ID for an item. - * - * ``` - * <script type="module"> - * import {RandomID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/randomid.mjs'; - * console.log(new RandomID()) - * </script> - * ``` - * - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary class to generate random numbers - */ -class RandomID extends ID { - - /** - * create new object - */ - constructor() { - super(); - - 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; - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -import {random} from "../math/random.mjs"; -import {isObject} from '../types/is.mjs'; -import {Base} from "./base.mjs"; -import {getGlobalObject} from "./global.mjs"; - -export {UUID} - -/** - * The UUID class makes it possible to get a unique UUID for an object. - * - * ``` - * <script type="module"> - * import {Base} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/uuid.mjs'; - * new UUID() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {Error} unsupported - */ - class UUID extends Base { - - /** - * - */ - constructor() { - super(); - - let uuid = createWithCrypto(); - - if (uuid === undefined) { - uuid = createWithRandom(); - } - - - if (uuid === undefined) { - throw new Error('unsupported') - } - - this[internalSymbol] = { - value: uuid - } - - } - - /** - * - * @return {string} - */ - toString() { - return this[internalSymbol]['value']; - } - - -} - -/** - * @private - * @return {string|undefined} - */ -function createWithRandom() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = random(0, 65000) * 16 | 0, - v = ((c === 'x') ? r : (r & 0x3 | 0x8)); - return v.toString(16)[0]; - }) -} - - -/** - * @private - * @return {string|undefined} - */ -function createWithCrypto() { - const crypt = getGlobalObject('crypto'); - if (!isObject(crypt)) return; - if (typeof crypt?.['randomUUID']) return; - return crypt.randomUUID(); -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {isObject} from './is.mjs'; -import {TokenList} from './tokenlist.mjs'; -import {UniqueQueue} from './uniquequeue.mjs'; - -export {Observer} - -/** - * An observer manages a callback function - * - * ``` - * import {Observer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/observer.mjs'; - * new Observer() - * ``` - * - * The update method is called with the subject object as this pointer. For this reason the callback should not - * be an arrow function, because it gets the this pointer of its own context. - * - * ``` - * new Observer(()=>{ - * // this is not subject - * }) - * - * new Observer(function() { - * // this is subject - * }) - * ``` - * - * Additional arguments can be passed to the callback. To do this, simply specify them. - * - * ``` - * Observer(function(a, b, c) { - * console.log(a, b, c); // ↦ "a", 2, true - * }, "a", 2, true) - * ``` - * - * The callback function must have as many parameters as arguments are given. - * - * @example - * - * import {Observer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/observer.mjs'; - * - * const observer = new Observer(function(a, b, c) { - * console.log(this, a, b, c); // ↦ "a", 2, true - * }, "a", 2, true); - * - * observer.update({value:true}).then(()=>{}); - * // ↦ {value: true} "a" 2 true - * - * @since 1.0.0 - * @copyright schukai GmbH - * @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(); - } - - /** - * - * @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) - - }); - - }; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {isIterable, isString} from '../types/is.mjs'; -import {validateFunction, validateString} from '../types/validate.mjs'; -import {Base} from './base.mjs'; - -export {TokenList} - -/** - * A tokenlist allows you to manage tokens (individual character strings such as css classes in an attribute string). - * - * The tokenlist offers various functions to manipulate values. For example, you can add, remove or replace a class in a CSS list. - * - * ``` - * <script type="module"> - * import {TokenList} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/tokenlist.mjs'; - * new TokenList("myclass row") - * </script> - * ``` - * - * This class implements the [iteration protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols). - * - * ``` - * typeof new TokenList("myclass row")[Symbol.iterator]; - * // ↦ "function" - * ``` - * - * @since 1.2.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -class TokenList extends Base { - - /** - * - * @param {array|string|iteratable} init - */ - constructor(init) { - super(); - this.tokens = new Set(); - - if (typeof init !== "undefined") { - this.add(init); - } - - } - - /** - * 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() - - return { - next: () => { - if (index < entries.length) { - return {value: entries?.[index++], done: false} - } else { - return {done: true} - } - } - } - } - - /** - * Returns true if it contains token, otherwise false - * - * ``` - * new TokenList("start middle end").contains('start')); // ↦ true - * new TokenList("start middle end").contains('end')); // ↦ true - * new TokenList("start middle end").contains('xyz')); // ↦ false - * new TokenList("start middle end").contains(['end','start','middle'])); // ↦ true - * new TokenList("start middle end").contains(['end','start','xyz'])); // ↦ false - * ``` - * - * @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; - } - - return false; - } - - /** - * add tokens - * - * ``` - * new TokenList().add("abc xyz").toString(); // ↦ "abc xyz" - * new TokenList().add(["abc","xyz"]).toString(); // ↦ "abc xyz" - * new TokenList().add(undefined); // ↦ add nothing - * ``` - * - * @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; - } - - /** - * remove all tokens - * - * @returns {TokenList} - */ - clear() { - this.tokens.clear(); - return this; - } - - /** - * Removes token - * - * ``` - * new TokenList("abc xyz").remove("xyz").toString(); // ↦ "abc" - * new TokenList("abc xyz").remove(["xyz"]).toString(); // ↦ "abc" - * new TokenList("abc xyz").remove(undefined); // ↦ remove nothing - * ``` - * - * @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; - } - - /** - * 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; - - a.splice(i, 1, newToken); - this.tokens = new Set(); - this.add(a); - - return this; - - - } - - /** - * Removes token from string. If token doesn't exist it's added. - * - * ``` - * new TokenList("abc def ghi").toggle("def xyz").toString(); // ↦ "abc ghi xyz" - * new TokenList("abc def ghi").toggle(["abc","xyz"]).toString(); // ↦ "def ghi xyz" - * new TokenList().toggle(undefined); // ↦ nothing - * ``` - * - * @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; - - } - - /** - * 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; - } - - /** - * returns the individual tokens separated by a blank character - * - * @returns {string} - */ - toString() { - return this.entries().join(' '); - } - -} - -/** - * @private - * @param token - * @returns {toggleValue} - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; - -export {Queue} - -/** - * You can create the instance via the monster namespace `new Monster.Types.Queue()`. - * - * ``` - * <script type="module"> - * import {Queue} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/queue.mjs'; - * new Queue() - * </script> - * ``` - * - * @example - * - * import {Queue} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/queue.mjs'; - * - * const queue = new Queue; - * - * queue.add(2); - * queue.add(true); - * queue.add("Hello"); - * queue.add(4.5); - * - * console.log(queue.poll()); - * // ↦ 2 - * console.log(queue.poll()); - * // ↦ true - * console.log(queue.poll()); - * // ↦ "Hello" - * console.log(queue.poll()); - * // ↦ 4.5 - * console.log(queue.poll()); - * // ↦ undefined - * - * - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary A Queue (Fifo) - */ -class Queue extends Base { - - /** - * - */ - constructor() { - super(); - this.data = []; - } - - - /** - * @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; - } - - 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; - } - - /** - * 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(); - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; - -export {Stack} - -/** - * You can call the method via the monster namespace `new Monster.Types.Queue()`. - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/stack.mjs'; - * console.log(new Stack()) - * </script> - * ``` - * - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -class Stack extends Base { - - /** - * - */ - constructor() { - super(); - this.data = []; - } - - - /** - * @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; - } - - 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; - } - - /** - * 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(); - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ -import {validateString} from "./validate.mjs"; - -export {toBinary, fromBinary} - -/** - * You can call the function via the monster namespace `Monster.Types.toBinary()`. - * - * ``` - * <script type="module"> - * import {toBinary} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/binary.mjs'; - * toBinary() - * </script> - * ``` - * - * @param {String} binary - * @return {String} - * @throws {TypeError} value is not a string - * @memberOf Monster.Types - * @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 charCodes = new Uint8Array(codeUnits.buffer); - let result = ''; - - for (let i = 0; i < charCodes.byteLength; i++) { - result += String.fromCharCode(charCodes[i]); - } - - return result; -} - -/** - * You can call the function via the monster namespace `Monster.Types.fromBinary()`. - * - * ``` - * <script type="module"> - * import {fromBinary} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/binary.mjs'; - * fromBinary() - * </script> - * ``` - * - * @param {String} binary - * @return {String} - * @throws {TypeError} value is not a string - * @memberOf Monster.Types - * @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; -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; -import {Pathfinder} from "../data/pathfinder.mjs"; - -import {Base} from "./base.mjs"; -import {validateObject} from "./validate.mjs"; - -export {BaseWithOptions} - -/** - * This is the base class with options from which some monster classes are derived. - * - * This class is actually only used as a base class. - * - * ```html - * <script type="module"> - * import {BaseWithOptions} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/basewithoptions.mjs'; - * new BaseWithOptions() - * </script> - * ``` - * - * Classes that require the possibility of options can be derived directly from this class. - * Derived classes almost always override the `defaul` getter with their own values. - * - * ```javascript - * class My extends BaseWithOptions { - * get defaults() { - * return Object.assign({}, super.defaults, { - * mykey: true - * }); - * } - * } - * ``` - * - * The class was formerly called Object. - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -class BaseWithOptions extends Base { - - /** - * - * @param {object} options - */ - constructor(options) { - super(); - - if (options === undefined) { - 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 {} - } - - /** - * 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) { - - } - - if (value === undefined) return defaultValue; - return value; - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {validateString} from "./validate.mjs"; - -export {escapeString} - -/** - * This function prefixes all special characters that may appear in a regex with a slash. - * - * ``` - * <script type="module"> - * import {escapeString} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * escapeString() - * </script> - * ``` - * - * @param {string} value - * @return {string} - * @since 1.26.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a string - */ -function escapeString(value) { - return validateString(value) - .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') - .replace(/-/g, '\\x2d'); -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {isArray, isObject, isPrimitive} from "./is.mjs"; -import {Observer} from "./observer.mjs"; -import {ObserverList} from "./observerlist.mjs"; -import {validateObject} from "./validate.mjs"; -import {extend} from "../data/extend.mjs"; - -export {ProxyObserver} - -/** - * An observer manages a callback function - * - * ``` - * <script type="module"> - * import {ProxyObserver} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/proxyobserver.mjs'; - * new ProxyObserver() - * </script> - * ``` - * - * With the ProxyObserver you can attach observer for observation. with each change at the object to be observed an update takes place. - * - * this also applies to nested objects. - * - * @example - * - * import {ProxyObserver} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/proxyobserver.mjs'; - * import {Observer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/observer.mjs'; - * import {isObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * - * const o = new Observer(function () { - * if (isObject(this) && this instanceof ProxyObserver) { - * // do something (this ist ProxyObserver) - * const subject = this.getSubject(); - * console.log(subject); - * } - * }); - * - * let realSubject = { - * a: { - * b: { - * c: true - * }, - * d: 9 - * } - * } - * - * const p = new ProxyObserver(realSubject); - * p.attachObserver(o); - * const s = p.getSubject(); - * s.a.b.c = false; - * - * @since 1.0.0 - * @copyright schukai GmbH - * @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; - } - - /** - * 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} - */ - getSubject() { - return this.subject - } - - /** - * @since 1.24.0 - * @param {Object} obj - * @return {Monster.Types.ProxyObserver} - */ - setSubject(obj) { - - let i, 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 proxied object - * - * @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) - } - -} - -/** - * - * @returns {{defineProperty: (function(*=, *=, *=): *), setPrototypeOf: (function(*, *=): boolean), set: (function(*, *, *, *): boolean), get: ((function(*=, *=, *=): (undefined))|*), deleteProperty: ((function(*, *): (boolean))|*)}} - * @private - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {isArray, isInstance} from "./is.mjs"; -import {Node} from "./node.mjs"; -import {validateInstance} from "./validate.mjs"; - -export {NodeList} - -/** - * You can create the instance via the monster namespace `new Monster.Types.NodeList()`. - * - * ``` - * <script type="module"> - * import {NodeList} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/nodelist.mjs'; - * new NodeList() - * </script> - * ``` - * - * @since 1.26.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @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'); - } - } - - /** - * - * @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)); - return false; - } - - /** - * @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; - } -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; - -export {Version, getMonsterVersion} - -/** - * The version object contains a sematic version number - * - * ``` - * <script type="module"> - * import {Version} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/version.mjs'; - * console.log(new Version('1.2.3')) // ↦ 1.2.3 - * console.log(new Version('1')) // ↦ 1.0.0 - * </script> - * ``` - * - * @example - * - * import {Version} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/version.mjs'; - * - * new Version('1.0.0') // ↦ 1.0.0 - * new Version(1) // ↦ 1.0.0 - * new Version(1, 0, 0) // ↦ 1.0.0 - * new Version('1.2.3', 4, 5) // ↦ 1.4.5 - * - * @since 1.0.0 - * @author schukai GmbH - * @copyright schukai GmbH - * @memberOf Monster.Types - * @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"); - } - - } - - /** - * - * @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 - * then 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; - -/** - * Version of monster - * - * You can call the method via the monster namespace `Monster.getVersion()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * console.log(Monster.getVersion()) - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {getMonsterVersion} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/version.mjs'; - * console.log(getMonsterVersion()) - * </script> - * ``` - * - * @returns {Monster.Types.Version} - * @since 1.0.0 - * @copyright schukai GmbH - * @author schukai GmbH - * @memberOf Monster - */ -function getMonsterVersion() { - if (monsterVersion instanceof Version) { - return monsterVersion; - } - /**#@+ dont touch, replaced by make with package.json version */ - monsterVersion = new Version('1.31.0') - /**#@-*/ - - return monsterVersion; - -} -'use strict'; - -/** - * Namespace for types. - * - * @namespace Monster.Types - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {}; - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {isPrimitive} from "./is.mjs"; -import {NodeList} from './nodelist.mjs'; -import {validateInstance} from './validate.mjs'; - -export {Node} - -/** - * @private - * @type {symbol} - */ -const internalValueSymbol = Symbol('internalData'); - -/** - * @private - * @type {symbol} - */ -const treeStructureSymbol = Symbol('treeStructure'); - - -/** - * You can create the instance via the monster namespace `new Monster.Types.Node()`. - * - * ``` - * <script type="module"> - * import {Node} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/node.mjs'; - * new Node() - * </script> - * ``` - * - * @since 1.26.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary A Node Class - * @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 - } - - } - - /** - * @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, - 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"); - } - -} - -/** - * @private - * @param {Node} node - * @param {int} operand - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; - -import {Base} from './base.mjs'; -import {isInstance} from "./is.mjs"; -import {Node} from "./node.mjs"; -import {NodeList} from "./nodelist.mjs"; -import {validateInstance} from "./validate.mjs"; - -export {NodeRecursiveIterator} - -/** - * @private - * @type {symbol} - */ -const isNodeListSymbol = Symbol('isNodeList'); - -/** - * You can create the instance via the monster namespace `new Monster.Types.NodeRecursiveIterator()`. - * - * ``` - * <script type="module"> - * import {NodeRecursiveIterator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/noderecursiveiterator.mjs'; - * new NodeRecursiveIterator() - * </script> - * ``` - * - * @example - * import {Node} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/node.mjs'; - * import {NodeRecursiveIterator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/noderecursiveiterator.mjs'; - * - * const node =new Node('1') - * - * // 1 - * // 2 - * // ├ 2.1 - * // ├ 2.2 - * // └ 2.3 - * // 3 - * // 4 - * // ├ 4.1 - * // └ 4.2 - * node.appendChild( - * (new Node('2')) - * .appendChild(new Node('2.1')) - * .appendChild(new Node('2.2')) - * .appendChild(new Node('2.3'))) - * .appendChild(new Node('3')) - * .appendChild(new Node('4') - * .appendChild(new Node('4.1')) - * .appendChild(new Node('4.2'))); - * - * const iterator = new NodeRecursiveIterator(node); - * - * const result = []; - * for (const n of iterator) { - * result.push(n.value); - * } - * - * // ↦ ['1', '2', '2.1', '2.2', '2.3', '3', '4', '4.1', '4.2'] - * - * @since 1.26.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary An iterator to run recursively through a tree of nodes - */ - class NodeRecursiveIterator extends Base { - - /** - * @param {Node} [data] - */ - constructor(node) { - super(); - - this[isNodeListSymbol] = false; - - // 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); - } - - /** - * @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]; - } - - if (this[internalSymbol].hasChildNodes()) { - let childNodes = this[internalSymbol].childNodes; - - for (let node of childNodes) { - yield* new NodeRecursiveIterator(node); - } - } - - return; - } - - /** - * @param {function} callback - * @return {Monster.Types.NodeRecursiveIterator} - */ - forEach(callback) { - for (const node of this) { - callback(node); - } - return this; - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {validateFunction, validateObject, validateString} from "./validate.mjs"; - -export {getGlobal, getGlobalObject, getGlobalFunction} - -/** - * @type {objec} - * @private - */ -let globalReference; - -/** - * @private - * @throws {Error} unsupported environment. - */ -(function () { - - if (typeof globalThis === 'object') { - globalReference = globalThis; - 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 - }); - - if (typeof __monster__ === 'object') { - __monster__.globalThis = __monster__; - delete Object.prototype.__monster__; - - globalReference = globalThis; - return; - } - - try { - globalReference = Function('return this')(); - } catch (e) { - - } - - throw new Error("unsupported environment.") - - -}()); - -/** - * Return globalThis - * - * If globalThis is not available, it will be polyfilled - * - * @since 1.6.0 - * @memberOf Monster.Types - * @returns {objec} globalThis - */ -function getGlobal() { - return globalReference; -} - -/** - * Return global object or throw Error - * - * You can call the method via the monster namespace `Monster.Types.getGlobalObject()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.30.0/dist/monster.mjs'; - * Monster.Types.getGlobalObject('document') - * // ↦ { } - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {getGlobalObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.30.0/dist/modules/types/global.mjs'; - * getGlobalObject('document') - * // ↦ { } - * </script> - * ``` - * - * @since 1.6.0 - * @memberOf Monster.Types - * @param {string} name - * @returns {objec} - * @throws {Error} the object is not defined - * @throws {TypeError} value is not a object - * @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; -} - -/** - * Return global function or throw Error - * - * You can call the method via the monster namespace `Monster.Types.getGlobalFunction()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.30.0/dist/monster.mjs'; - * console.log(Monster.Types.getGlobalFunction('parseInt')) // ↦ f parseInt() { } - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {getGlobalFunction} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.30.0/dist/modules/types/global.mjs'; - * console.log(getGlobalFunction('parseInt')) // ↦ f parseInt() { } - * </script> - * ``` - * - * @since 1.6.0 - * @memberOf Monster.Types - * @param {string} name - * @return {objec} - * @throws {TypeError} value is not a function - * @throws {Error} the function is not defined - * @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; -} - - - - -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {Base} from "./base.mjs"; -import {isString} from "./is.mjs"; -import {MediaType, parseMediaType} from "./mediatype.mjs"; -import {validateBoolean, validateInstance, validateString} from "./validate.mjs"; - -export {DataUrl, parseDataURL} - -/** - * @private - * @type {symbol} - */ -const internal = Symbol('internal'); - - -/** - * You can create an object via the monster namespace `new Monster.Types.DataUrl()`. - * - * ``` - * <script type="module"> - * import {DataUrl} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/dataurl.mjs'; - * new DataUrl() - * </script> - * ``` - * - * @since 1.8.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs - * @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) - } - - - } - - 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; - } - -} - -/** - * You can call the function via the monster namespace `Monster.Types.parseDataURL()`. - * - * ``` - * <script type="module"> - * import {parseDataURL} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/dataurl.mjs'; - * parseDataURL(...) - * </script> - * ``` - * - * Specification: - * - * ``` - * dataurl := "data:" [ mediatype ] [ ";base64" ] "," data - * mediatype := [ type "/" subtype ] *( ";" parameter ) - * data := *urlchar - * parameter := attribute "=" value - * ``` - * - * @param {String} dataurl - * @return {Monster.Types.DataUrl} - * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs - * @see https://datatracker.ietf.org/doc/html/rfc2397 - * @throws {TypeError} incorrect or missing data protocol - * @throws {TypeError} malformed data url - * @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); - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import { - isArray, - isBoolean, - isFunction, - isInstance, - isInteger, - isIterable, - isObject, - isPrimitive, - isString, - isSymbol -} from './is.mjs'; - -export { - validateIterable, - validatePrimitive, - validateBoolean, - validateString, - validateObject, - validateInstance, - validateArray, - validateSymbol, - validateFunction, - validateInteger -} - -/** - * This method checks if the type matches the primitive type. this function is identical to isPrimitive() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateIterable} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateIterable('2')) // ↦ TypeError - * console.log(validateIterable([])) // ↦ value - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.2.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a primitive - * @see {@link isPrimitive} - * @see {@link Monster.Types.isPrimitive} - * @see {@link Monster.Types#isPrimitive} - */ -function validateIterable(value) { - if (!isIterable(value)) { - throw new TypeError('value is not iterable') - } - return value -} - -/** - * This method checks if the type matches the primitive type. this function is identical to isPrimitive() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validatePrimitive} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validatePrimitive('2')) // ↦ value - * console.log(validatePrimitive([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a primitive - * @see {@link isPrimitive} - * @see {@link Monster.Types.isPrimitive} - * @see {@link Monster.Types#isPrimitive} - */ -function validatePrimitive(value) { - if (!isPrimitive(value)) { - throw new TypeError('value is not a primitive') - } - return value -} - -/** - * This method checks if the type matches the boolean type. this function is identical to isBoolean() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateBoolean} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateBoolean(false)) // ↦ value - * console.log(validateBoolean('2')) // ↦ TypeError - * console.log(validateBoolean([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - - * @throws {TypeError} value is not primitive - */ -function validateBoolean(value) { - if (!isBoolean(value)) { - throw new TypeError('value is not a boolean') - } - return value -} - -/** - * This method checks if the type matches the string type. this function is identical to isString() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateString} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateString('2')) // ↦ value - * console.log(validateString([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a string - */ -function validateString(value) { - if (!isString(value)) { - throw new TypeError('value is not a string') - } - return value -} - - -/** - * This method checks if the type matches the object type. this function is identical to isObject() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateObject({})) // ↦ value - * console.log(validateObject('2')) // ↦ TypeError - * console.log(validateObject([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a object - */ -function validateObject(value) { - if (!isObject(value)) { - throw new TypeError('value is not a object') - } - return value -} - -/** - * This method checks if the type matches the object instance. - * - * ``` - * <script type="module"> - * import {validateInstance} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateInstance({}, Object)) // ↦ value - * console.log(validateInstance('2', Object)) // ↦ TypeError - * console.log(validateInstance([], Object)) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @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 (n) { - n = " " + n; - } - - throw new TypeError('value is not an instance of' + n) - } - return value -} - -/** - * This method checks if the type matches the array type. this function is identical to isArray() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateArray} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateArray('2')) // ↦ TypeError - * console.log(validateArray([])) // ↦ value - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not an array - */ -function validateArray(value) { - if (!isArray(value)) { - throw new TypeError('value is not an array') - } - return value -} - -/** - * This method checks if the type matches the symbol type. this function is identical to isSymbol() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateSymbol} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateSymbol('2')) // ↦ TypeError - * console.log(validateSymbol()) // ↦ value - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not an symbol - */ -function validateSymbol(value) { - if (!isSymbol(value)) { - throw new TypeError('value is not an symbol') - } - return value -} - -/** - * This method checks if the type matches the function type. this function is identical to isFunction() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateFunction} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateFunction(()=>{})) // ↦ value - * console.log(validateFunction('2')) // ↦ TypeError - * console.log(validateFunction([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a function - */ -function validateFunction(value) { - if (!isFunction(value)) { - throw new TypeError('value is not a function') - } - return value -} - -/** - * This method checks if the type is an integer. this function is identical to isInteger() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateFunction} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateInteger(true)) // ↦ TypeError - * console.log(validateInteger('2')) // ↦ TypeError - * console.log(validateInteger(2)) // ↦ value - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not an integer - */ -function validateInteger(value) { - if (!isInteger(value)) { - throw new TypeError('value is not an integer') - } - return value -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Queue} from "./queue.mjs"; -import {validateObject} from "./validate.mjs"; - -export {UniqueQueue} - -/** - * A UniqueQueue is a queue that contains items only once. - * - * ``` - * <script type="module"> - * import {UniqueQueue} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/uniquequeue.mjs'; - * new UniqueQueue() - * </script> - * ``` - * - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary A queue for unique values - */ - class UniqueQueue extends Queue { - - /** - * - */ - constructor() { - super(); - this.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); - - if (!this.unique.has(value)) { - this.unique.add(value); - super.add(value); - } - - return this; - } - - /** - * remove all entries - * - * @returns {Queue} - */ - clear() { - super.clear(); - this.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.unique.delete(value); - return value; - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -export {Base} - -/** - * This is the base class from which all monster classes are derived. - * - * ``` - * <script type="module"> - * import {Base} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/base.mjs'; - * new Base() - * </script> - * ``` - * - * The class was formerly called Object. - * - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ - class Base extends Object { - - /** - * - * @returns {string} - */ - toString() { - return JSON.stringify(this); - }; - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -export {isIterable, isPrimitive, isSymbol, isBoolean, isString, isObject, isInstance, isArray, isFunction, isInteger} - - -/** - * With this function you can check if a value is iterable. - * - * This method is used in the library to have consistent names. - * - * You can call the method via the monster namespace `Monster.Types.isPrimitive()`. - * - * ``` - * <script type="module"> - * import {isIterable} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isIterable(null) // ↦ false - * isIterable('hello') // ↦ true - * isIterable([]) // ↦ true - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.2.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isIterable(value) { - if (value === undefined) return false; - if (value === null) return false; - return typeof value?.[Symbol.iterator] === 'function'; -} - - -/** - * Checks whether the value passed is a primitive (string, number, boolean, NaN, undefined, null or symbol) - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isPrimitive} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isPrimitive('2')) // ↦ true - * isPrimitive([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isPrimitive(value) { - var type; - - if (value === undefined || value === null) { - return true; - } - - type = typeof value; - - if (type === 'string' || type === 'number' || type === 'boolean' || type === 'symbol') { - return true; - } - - return false; -} - -/** - * Checks whether the value passed is a symbol - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isSymbol} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isSymbol(Symbol('a'))) // ↦ true - * isSymbol([]) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isSymbol(value) { - return ('symbol' === typeof value) ? true : false; -} - -/** - * Checks whether the value passed is a boolean. - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isBoolean} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isBoolean('2')) // ↦ false - * isBoolean([])) // ↦ false - * isBoolean(2>4)) // ↦ true - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isBoolean(value) { - - if (value === true || value === false) { - return true; - } - - return false; -} - -/** - * Checks whether the value passed is a string - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isString} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isString('2')) // ↦ true - * isString([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isString(value) { - if (value === undefined || typeof value !== 'string') { - return false; - } - return true; -} - -/** - * Checks whether the value passed is a object - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isObject('2')) // ↦ false - * isObject([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isObject(value) { - - if (isArray(value)) return false; - if (isPrimitive(value)) return false; - - if (typeof value === 'object') { - return true; - } - - return false; -} - -/** - * Checks whether the value passed is a object and instance of instance. - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isInstance} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isInstance('2')) // ↦ false - * isInstance([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @param {*} instance - * @returns {boolean} - * @since 1.5.0 - * @copyright schukai GmbH - * @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; -} - -/** - * Checks whether the value passed is a array - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isArray} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isArray('2')) // ↦ false - * isArray([])) // ↦ true - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @see https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray - */ -function isArray(value) { - return Array.isArray(value); -} - -/** - * Checks whether the value passed is a function - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isFunction} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isFunction(()=>{}) // ↦ true - * isFunction('2')) // ↦ false - * isFunction([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isFunction(value) { - if (isArray(value)) return false; - if (isPrimitive(value)) return false; - - if (typeof value === 'function') { - return true; - } - - return false; - -} - -/** - * Checks whether the value passed is an integer. - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isInteger} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isInteger(()=>{}) // ↦ true - * isInteger('2')) // ↦ false - * isInteger(2)) // ↦ true - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isInteger(value) { - return Number.isInteger(value); -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {validateString} from "./validate.mjs"; - -export {ID} - -/** - * @private - * @type {Map<string, integer>} - */ -let internalCounter = new Map; - -/** - * With the id class, sequences of ids can be created. for this purpose, an internal counter is incremented for each prefix. - * thus, the first id with the prefix `myid` will be `myid1` and the second id `myid2`. - * The ids are the same for every call, for example on a web page. - * - * So the ids can also be used for navigation. you just have to take care that the order stays the same. - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/id.mjs'; - * console.log(new ID()) - * </script> - * ``` - * - * As of version 1.6.0 there is the new RandomID. this ID class is continuous from now on. - * - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary Automatic generation of ids - */ - class ID extends Base { - - /** - * create new id with prefix - * - * @param {string} prefix - */ - constructor(prefix) { - super(); - - if (prefix === undefined) { - prefix = "id"; - } - - validateString(prefix); - - if (!internalCounter.has(prefix)) { - internalCounter.set(prefix, 1); - } - - let count = internalCounter.get(prefix); - this.id = prefix + count; - - internalCounter.set(prefix, ++count); - } - - /** - * @return {string} - */ - toString() { - return this.id; - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {ID} from "../types/id.mjs"; -import {isObject} from "../types/is.mjs"; -import {validateString} from "../types/validate.mjs"; - -export {trimSpaces} - -/** - * This special trim function allows to trim spaces that have been protected by a special escape character. - * - * ``` - * <script type="module"> - * import {trimSpaces} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/trimspaces.mjs'; - * trimSpaces(' hello \\ ') - * </script> - * ``` - * - * Hint: One stroke is escaped by the javascript interpreter, the second stroke escapes the stroke. - * - * ```text - * a\ b ↦ a b - * a\\ b ↦ a\ b - * ``` - * - * @since 1.24.0 - * @memberOf Monster.Util - * @copyright schukai GmbH - * @param {string} value - * @return {string} - * @throws {TypeError} value is not a string - */ - function trimSpaces(value) { - - validateString(value); - - let placeholder = new Map; - const regex = /((?<pattern>\\(?<char>.)){1})/mig; - - // 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; - } - - 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); - } - - } - - value = value.trim(); - placeholder.forEach((v, k) => { - value = value.replace(k, '\\' + v) - }) - - return value; - -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; - -import {Base} from "../types/base.mjs"; -import {isInteger} from "../types/is.mjs"; -import {validateFunction, validateInteger} from "../types/validate.mjs"; - -export {DeadMansSwitch} - -/** - * The dead man's switch allows to set a timer which can be reset again and again within a defined period of time. - * - * ``` - * <script type="module"> - * import {DeadMansSwitch} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/deadmansswitch.mjs'; - * new DeadMansSwitch(); - * </script> - * ``` - * - * @example - * import {DeadMansSwitch} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/deadmansswitch.mjs'; - * - * const deadmansswitch = new DeadMansSwitch(100, ()=>{ - * console.log('yeah!') - * // ↦ "yeah!" - * }) - * - * deadmansswitch.touch(); // from here wait again 100 ms - * deadmansswitch.touch(200); // from here wait 200 ms - * - * @copyright schukai GmbH - * @since 1.29.0 - * @memberOf Monster.Util - * @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(); - - init.call(this, validateInteger(delay), validateFunction(callback)); - } - - /** - * - * @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') - } - - clearTimeout(this[internalSymbol]['timer']); - - initCallback.call(this); - - return this; - } -} - -/** - * @private - */ -function initCallback() { - - const self = this; - - self[internalSymbol]['timer'] = setTimeout(() => { - self[internalSymbol]['isAlreadyRun'] = true; - self[internalSymbol]['callback'](); - }, self[internalSymbol]['delay']) -} - -/** - * @private - * @param {integer} delay - * @param {function} callback - */ -function init(delay, callback) { - const self = this; - - self[internalSymbol] = { - callback, - delay, - isAlreadyRun: false, - timer: undefined - }; - - initCallback.call(self); - -} - - - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {validateObject} from '../types/validate.mjs'; - -export {deepFreeze} - -/** - * Deep freeze a object - * - * ``` - * <script type="module"> - * import {deepFreeze} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/freeze.mjs'; - * deepFreeze({}) - * </script> - * ``` - * - * @param {object} object object to be freeze - * @since 1.0.0 - * @returns {object} - * @memberOf Monster.Util - * @copyright schukai GmbH - * @throws {TypeError} value is not a object - */ - function deepFreeze(object) { - - validateObject(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]; - - object[name] = (value && typeof value === "object") ? - deepFreeze(value) : value; - } - - return Object.freeze(object); -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {isFunction} from '../types/is.mjs'; - -export {Comparator} - -/** - * The comparator allows a comparison function to be abstracted. - * - * ``` - * <script type="module"> - * import {Comparator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/comparator.mjs'; - * console.log(new Comparator()) - * </script> - * ``` - * - * The following are some examples of the application of the class. - * - * ``` - * new Comparator().lessThanOrEqual(2, 5) // ↦ true - * new Comparator().greaterThan(4, 2) // ↦ true - * new Comparator().equal(4, 4) // ↦ true - * new Comparator().equal(4, 5) // ↦ false - * ``` - * - * You can also pass your own comparison function, and thus define the comparison function. - * - * ``` - * new Comparator(function (a, b) { - * if (a.v === b.v) return 0; - * return a.v < b.v ? -1 : 1; - * }).equal({v: 2}, {v: 2}); // ↦ true - * ``` - * - * @example - * - * import {Comparator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/comparator.mjs'; - * - * console.log(new Comparator().lessThanOrEqual(2, 5)) - * // ↦ true - * console.log(new Comparator().greaterThan(4, 2)) - * // ↦ true - * console.log(new Comparator().equal(4, 4)) - * // ↦ true - * console.log(new Comparator().equal(4, 5)) - * // ↦ false - * - * @since 1.3.0 - * @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(); - - 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") - } - - 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; - } - - /** - * 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 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 b - * - * @param {*} a - * @param {*} b - * - * @return {boolean} - */ - lessThan(a, b) { - return this.compare(a, b) < 0; - } - - -} - - -/** - * This is the description for the callback function used by the operator - * - * ``` - * new Comparator(function (a, b) { - * if (a.v === b.v) return 0; - * return a.v < b.v ? -1 : 1; - * }).equal({v: 2}, {v: 2}); // ↦ true - * ``` - * - * @callback Monster.Util~exampleCallback - * @param {*} a - * @param {*} b - * @return {integer} -1, 0 or 1 - * @memberOf Monster.Util - * @see Monster.Util.Comparator - */ - -'use strict'; - -/** - * Namespace for utilities. - * - * @namespace Monster.Util - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - - -import {getGlobal} from '../types/global.mjs'; -import {isArray, isFunction, isObject, isPrimitive} from '../types/is.mjs'; -import {typeOf} from "../types/typeof.mjs"; -import {validateObject} from "../types/validate.mjs"; - -export {clone} - -/** - * With this function, objects can be cloned. - * The entire object tree is run through. - * - * Proxy, Element, HTMLDocument and DocumentFragment instances are not cloned. - * Global objects such as windows are also not cloned, - * - * If an object has a method `getClone()`, this method is used to create the clone. - * - * ``` - * <script type="module"> - * import {clone} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/clone.mjs'; - * clone({}) - * </script> - * ``` - * - * @param {*} obj object to be cloned - * @returns {*} - * @since 1.0.0 - * @memberOf Monster.Util - * @copyright schukai GmbH - * @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."); -} - -/** - * - * @param {object} obj - * @returns {object} - * @private - */ -function cloneObject(obj) { - - validateObject(obj); - - const constructor = obj?.['constructor']; - - /** Object has clone method */ - if(typeOf(constructor)==='function') { - const prototype = constructor?.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; -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -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} - -/** - * @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) - }; - } - - /** - * @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); - - - }) - - } -} - -/** - * This class allows to execute several functions in order. - * - * 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 - * - * The result of `run()` is a promise. - * - * ``` - * <script type="module"> - * import {Processing} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/processing.mjs'; - * new Processing(); - * </script> - * ``` - * - * @example - * import {Processing} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/processing.mjs'; - * - * let startTime = +new Date(); - * - * new Processing((url)=>{ - * return fetch(url) - * },(response)=>{ - * // do something with the response - * console.log(response.status, +new Date()-startTime) - * },200,()=>{ - * // this function is called 200 seconds after fetch is received. - * console.log('finished', +new Date()-startTime) - * return 'done' - * }).run('https://monsterjs.org/assets/world.json').then(r=>{ - * console.log(r) - * // ↦ "done" - * }) - * - * @copyright schukai GmbH - * @since 1.21.0 - * @memberOf Monster.Util - * @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 - * - * @param {int} timeout Timeout - * @param {function} callback Callback - * @throw {TypeError} the arguments must be either integer or functions - */ - constructor() { - super(); - - this[internalSymbol] = { - queue: new Queue - }; - - let time = 0 - - for (const [, arg] of Object.entries(arguments)) { - 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; - } - - - /** - * Executes the defined functions in order. - * - * @param {*} data - * @return {Promise} - */ - run(data) { - const self = this; - if (this[internalSymbol].queue.isEmpty()) { - return Promise.resolve(data); - } - - return this[internalSymbol].queue.poll().run(data).then((result) => { - return self.run(result); - }); - - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {isArray, isObject} from "../types/is.mjs"; -import {typeOf} from "../types/typeof.mjs"; - -export {extend} - -/** - * Extend copies all enumerable own properties from one or - * more source objects to a target object. It returns the modified target object. - * - * ``` - * <script type="module"> - * import {extend} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/extend.mjs'; - * extend(a, b) - * </script> - * ``` - * - * @param {object} target - * @param {object} - * @return {object} - * @since 1.10.0 - * @copyright schukai GmbH - * @memberOf Monster.Data - * @throws {Error} unsupported argument - * @throws {Error} type mismatch - */ -function extend() { - let o, i; - - for (i = 0; i < arguments.length; i++) { - let a = arguments[i]; - - if (!(isObject(a) || isArray(a))) { - throw new Error('unsupported argument ' + JSON.stringify(a)); - } - - if (o === undefined) { - o = a; - continue; - } - - for (let k in a) { - - let v = a?.[k]; - - 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) + ")"); - } - } - - o[k] = extend(o[k], v); - - } else { - o[k] = v; - } - - } - } - - return o; -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {Base} from '../types/base.mjs'; -import {validateString} from '../types/validate.mjs'; -import {Transformer} from './transformer.mjs'; - -export {Pipe} - -const DELIMITER = '|'; - -/** - * The pipe class makes it possible to combine several processing steps. - * - * ``` - * <script type="module"> - * import {Pipe} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pipe.mjs'; - * new Pipe() - * </script> - * ``` - * - * A pipe consists of commands whose input and output are connected with the pipe symbol `|`. - * - * With the Pipe, processing steps can be combined. Here, the value of an object is accessed via the pathfinder (path command). - * the word is then converted to uppercase letters and a prefix Hello is added. the two backslash safe the space char. - * - * @example - * import {Pipe} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pipe.mjs'; - * - * let obj = { - * a: { - * b: { - * c: { - * d: "world" - * } - * } - * } - * } - * - * console.log(new Pipe('path:a.b.c.d | toupper | prefix:Hello\\ ').run(obj)); - * // ↦ Hello WORLD - * - * @since 1.5.0 - * @copyright schukai GmbH - * @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); - - 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); - } - - return this; - } - - /** - * run a pipe - * - * @param {*} value - * @returns {*} - */ - run(value) { - return this.pipe.reduce((accumulator, transformer, currentIndex, array) => { - return transformer.run(accumulator); - }, value); - } -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {isFunction, isObject, isString} from "../types/is.mjs"; -import {validateString} from "../types/validate.mjs"; -import {clone} from "../util/clone.mjs"; -import {DELIMITER, Pathfinder, WILDCARD} from "./pathfinder.mjs"; - -export {buildMap, PARENT, assembleParts} - -/** - * @type {string} - * @memberOf Monster.Data - */ -const PARENT = '^'; - - -/** - * With the help of the function `buildMap()`, maps can be easily created from data objects. - * - * Either a simple definition `a.b.c` or a template `${a.b.c}` can be specified as the path. - * Key and value can be either a definition or a template. The key does not have to be defined. - * - * ``` - * <script type="module"> - * import {buildMap} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/buildmap.mjs'; - * console.log(buildMap()) - * </script> - * ``` - * - * The templates determine the appearance of the keys and the value of the map. Either a single value `id` can be taken or a composite key `${id} ${name}` can be used. - * - * If you want to access values of the parent data set, you have to use the `^` character `${id} ${^.name}`. - * - * @example - * - * import {buildMap} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/buildmap.mjs'; - * // a typical data structure as reported by an api - * - * let map; - * let obj = { - * "data": [ - * { - * "id": 10, - * "name": "Cassandra", - * "address": { - * "street": "493-4105 Vulputate Street", - * "city": "Saumur", - * "zip": "52628" - * } - * }, - * { - * "id": 20, - * "name": "Holly", - * "address": { - * "street": "1762 Eget Rd.", - * "city": "Schwalbach", - * "zip": "952340" - * } - * }, - * { - * "id": 30, - * "name": "Guy", - * "address": { - * "street": "957-388 Sollicitudin Avenue", - * "city": "Panchià", - * "zip": "420729" - * } - * } - * ] - * }; - * - * // The function is passed this data structure and with the help of the selector `'data.*'` the data to be considered are selected. - * // The key is given by a simple definition `'id'` and the value is given by a template `'${name} (${address.zip} ${address.city})'`. - * map = buildMap(obj, 'data.*', '${name} (${address.zip} ${address.city})', 'id'); - * console.log(map); - * - * // ↦ Map(3) { - * // '10' => 'Cassandra (52628 Saumur)', - * // '20' => 'Holly (952340 Schwalbach)', - * // '30' => 'Guy (420729 Panchià)' - * // } - * - * // If no key is specified, the key from the selection, here the array index, is taken. - * map = buildMap(obj, 'data.*', '${name} (${address.zip} ${address.city})'); - * console.log(map); - * - * // ↦ Map(3) { - * // '0' => 'Cassandra (52628 Saumur)', - * // '1' => 'Holly (952340 Schwalbach)', - * // '2' => 'Guy (420729 Panchià)' - * // } - * - * // a filter (function(value, key) {}) can be specified to accept only defined entries. - * map = buildMap(obj, 'data.*', '${name} (${address.zip} ${address.city})', 'id', function (value, key) { - * return (value['id'] >= 20) ? true : false - * }); - * console.log(map); - * - * // ↦ Map(2) { - * // 20 => 'Holly (952340 Schwalbach)', - * // 30 => 'Guy (420729 Panchià)' - * // } - * - * @param {*} subject - * @param {string|Monster.Data~exampleSelectorCallback} selector - * @param {string} [valueTemplate] - * @param {string} [keyTemplate] - * @param {Monster.Data~exampleFilterCallback} [filter] - * @return {*} - * @memberOf Monster.Data - * @throws {TypeError} value is neither a string nor a function - * @throws {TypeError} the selector callback must 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); - }); - -} - - -/** - * @private - * @param {*} subject - * @param {string|Monster.Data~exampleSelectorCallback} selector - * @param {Monster.Data~exampleFilterCallback} [filter] - * @param {function} callback - * @return {Map} - * @throws {TypeError} selector is neither a string nor a function - */ -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; -} - -/** - * @private - * @param subject - * @param selector - * @param key - * @param parentMap - * @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 = "", 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; - -} - - -/** - * With the help of this filter callback, values can be filtered out. Only if the filter function returns true, the value is taken for the map. - * - * @callback Monster.Data~exampleFilterCallback - * @param {*} value Value - * @param {string} key Key - * @memberOf Monster.Data - * @see {@link Monster.Data.buildMap} - */ - -/** - * Alternatively to a string selector a callback can be specified. this must return a map. - * - * @example - * import {buildMap} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/buildmap.mjs'; - * - * let obj = { - * "data": [ - * { - * "id": 10, - * "name": "Cassandra", - * "enrichment": { - * variants: [ - * { - * sku: 1, label: "XXS", price: [ - * {vk: '12.12 €'}, - * {vk: '12.12 €'} - * ] - * }, - * { - * sku: 2, label: "XS", price: [ - * {vk: '22.12 €'}, - * {vk: '22.12 €'} - * ] - * }, - * { - * sku: 3, label: "S", price: [ - * {vk: '32.12 €'}, - * {vk: '32.12 €'} - * ] - * }, - * { - * sku: 4, label: "L", price: [ - * {vk: '42.12 €'}, - * {vk: '42.12 €'} - * ] - * } - * ] - * - * } - * }, - * { - * "id": 20, - * "name": "Yessey!", - * "enrichment": { - * variants: [ - * { - * sku: 1, label: "XXS", price: [ - * {vk: '12.12 €'}, - * {vk: '12.12 €'} - * ] - * }, - * { - * sku: 2, label: "XS", price: [ - * {vk: '22.12 €'}, - * {vk: '22.12 €'} - * ] - * }, - * { - * sku: 3, label: "S", price: [ - * {vk: '32.12 €'}, - * {vk: '32.12 €'} - * ] - * }, - * { - * sku: 4, label: "L", price: [ - * {vk: '42.12 €'}, - * {vk: '42.12 €'} - * ] - * } - * ] - * - * } - * } - * ] - * }; - * - * let callback = function (subject) { - * let m = new Map; - * - * for (const [i, b] of Object.entries(subject.data)) { - * - * let key1 = i; - * - * for (const [j, c] of Object.entries(b.enrichment.variants)) { - * let key2 = j; - * - * for (const [k, d] of Object.entries(c.price)) { - * - * let key3 = k; - * - * d.name = b.name; - * d.label = c.label; - * d.id = [key1, key2, key3].join('.'); - * - * m.set(d.id, d); - * } - * - * } - * } - * return m; - * } - * - * let map = buildMap(obj, callback, '${name} ${vk}', '${id}') - * - * // ↦ Map(3) { - * // "0.0.0":"Cassandra 12.12 €", - * // "0.0.1":"Cassandra 12.12 €", - * // "0.1.0":"Cassandra 22.12 €", - * // "0.1.1":"Cassandra 22.12 €", - * // "0.2.0":"Cassandra 32.12 €", - * // "0.2.1":"Cassandra 32.12 €", - * // "0.3.0":"Cassandra 42.12 €", - * // "0.3.1":"Cassandra 42.12 €", - * // "1.0.0":"Yessey! 12.12 €", - * // "1.0.1":"Yessey! 12.12 €", - * // "1.1.0":"Yessey! 22.12 €", - * // "1.1.1":"Yessey! 22.12 €", - * // "1.2.0":"Yessey! 32.12 €", - * // "1.2.1":"Yessey! 32.12 €", - * // "1.3.0":"Yessey! 42.12 €", - * // "1.3.1":"Yessey! 42.12 €" - * // } - * - * @callback Monster.Data~exampleSelectorCallback - * @param {*} subject subject - * @return Map - * @since 1.17.0 - * @memberOf Monster.Data - * @see {@link Monster.Data.buildMap} - */ - -/** - * @private - * @param {*} subject - * @param {string|undefined} definition - * @param {*} defaultValue - * @return {*} - */ -function build(subject, definition, defaultValue) { - 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)]; - - let finder = new Pathfinder(subject); - - if (array.length === 0) { - return finder.getVia(definition); - } - - array.forEach((a) => { - let groups = a?.['groups']; - let placeholder = groups?.['placeholder'] - if (placeholder === undefined) return; - - let path = groups?.['path'] - - let v = finder.getVia(path); - if (v === undefined) v = defaultValue; - - definition = definition.replaceAll(placeholder, v); - - - }) - - return definition; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; - -import {Base} from "../types/base.mjs"; -import {parseDataURL} from "../types/dataurl.mjs"; -import {isString} from "../types/is.mjs"; -import {ProxyObserver} from "../types/proxyobserver.mjs"; -import {validateObject} from "../types/validate.mjs"; -import {extend} from "./extend.mjs"; -import {Pathfinder} from "./pathfinder.mjs"; - -export {Datasource} - -/** - * @private - * @type {symbol} - * @memberOf Monster.Data - * @since 1.24.0 - */ -const internalDataSymbol = Symbol('internalData'); - -/** - * The datasource class is the basis for dealing with different data sources. - * It provides a unified interface for accessing data - * - * ``` - * <script type="module"> - * import {Datasource} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource.mjs'; - * new Datasource() - * </script> - * ``` - * - * @example - * - * import {Datasource} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource.mjs' - * - * class MyDatasource extends Datasource { - * - * } - * - * const ds = new MyDatasource(); - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data - * @summary The datasource class encapsulates the access to data objects. - */ -class Datasource extends Base { - - /** - * - */ - 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; - } - -} - -/** - * @private - * @param data - * @return {Object} - * @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 {}; -} -'use strict'; - -/** - * In this namespace you will find classes and methods for handling data. - * - * @namespace Monster.Data - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - - -import {isArray, isObject} from "../types/is.mjs"; -import {Node} from "../types/node.mjs"; -import {NodeList} from "../types/nodelist.mjs"; -import {assembleParts} from "./buildmap.mjs"; -import {extend} from "./extend.mjs"; - -export {buildTree} - -/** - * @private - * @type {symbol} - */ -const parentSymbol = Symbol('parent'); - -/** - * @private - * @type {symbol} - */ -const rootSymbol = Symbol('root'); - -/** - * @typedef {Object} buildTreeOptions - * @property {array} options.rootReferences=[null, undefined] defines the values for elements without parents - * @property {Monster.Data~exampleFilterCallback} options.filter filtering of the values - * @memberOf Monster.Data - */ - -/** - * With the help of the function `buildTree()`, nodes can be easily created from data objects. - * - * ``` - * <script type="module"> - * import {buildTree} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/buildtree.mjs'; - * buildTree() - * </script> - * ``` - * - * @param {*} subject - * @param {string|Monster.Data~exampleSelectorCallback} selector - * @param {string} idKey - * @param {string} parentIDKey - * @param {buildTreeOptions} [options] - * @return {*} - * @memberOf Monster.Data - * @throws {TypeError} value is neither a string nor a function - * @throws {TypeError} the selector callback must return a map - * @throws {Error} the object has no value for the specified id - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {getGlobal, getGlobalObject} from "../types/global.mjs"; -import {ID} from '../types/id.mjs'; -import {isArray, isObject, isString} from '../types/is.mjs'; -import { - validateFunction, - validateInteger, - validateObject, - validatePrimitive, - validateString -} from '../types/validate.mjs'; -import {clone} from "../util/clone.mjs"; -import {Pathfinder} from "./pathfinder.mjs"; - -export {Transformer} - -/** - * The transformer class is a swiss army knife for manipulating values. especially in combination with the pipe, processing chains can be built up. - * - * ``` - * <script type="module"> - * import {Transformer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/transformer.mjs'; - * new Transformer() - * </script> - * ``` - * - * A simple example is the conversion of all characters to lowercase. for this purpose the command `tolower` must be used. - * - * ``` - * let t = new Transformer('tolower').run('ABC'); // ↦ abc - * ``` - * - * **all commands** - * - * in the following table all commands, parameters and existing aliases are described. - * - * | command | parameter | alias | description | - * |:-------------|:---------------------------|:------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| - * | to-base64 | | base64, btob | Converts the value to base64 | - * | from-base64 | | atob | Converts the value from base64 | - * | call | function:param1:param2:... | | Calling a callback function. The function can be defined in three places: either globally, in the context `addCallback` or in the passed object | - * | default | value:type | ?? | If the value is undefined the first argument is returned, otherwise the value. The third optional parameter specifies the desired type. If no type is specified, string is used. Valid types are bool, string, int, float, undefined and object. An object default value must be specified as a base64 encoded json string. (since 1.12.0) | - * | debug | | | the passed value is output (console) and returned | - * | empty | | | Return empty String "" | - * | first-key | default | | Can be applied to objects and returns the value of the first key. All keys of the object are fetched and sorted. (since 1.23.0) | - * | fromjson | | | Type conversion from a JSON string (since 1.12.0) | - * | if | statement1:statement2 | ? | Is the ternary operator, the first parameter is the valid statement, the second is the false part. To use the current value in the queue, you can set the value keyword. On the other hand, if you want to have the static string "value", you have to put one backslash \\ in front of it and write value. the follow values are true: 'on', true, 'true'. If you want to have a space, you also have to write \\ in front of the space. | - * | index | key:default | property, key | Fetches a value from an object, an array, a map or a set | - * | last-key | default | | Can be applied to objects and returns the value of the last key. All keys of the object are fetched and sorted. (since 1.23.0) | - * | length | | count | Length of the string or entries of an array or object | - * | nop | | | Do nothing | - * | nth-key | index:default | | Can be applied to objects and returns the value of the nth key. All keys of the object are fetched and sorted. (since 1.23.0) | - * | nth-last-key | index:default | | Can be applied to objects and returns the value of the nth key from behind. All keys of the object are fetched and sorted. (since 1.23.0) | - * | path | path | | The access to an object is done via a Pathfinder object | - * | path-exists | path | | Check if the specified path is available in the value (since 1.24.0) | - * | plaintext | | plain | All HTML tags are removed (*) | - * | prefix | text | | Adds a prefix | - * | rawurlencode | | | URL coding | - * | static | | none | The Arguments value is used and passed to the value. Special characters \ <space> and : can be quotet by a preceding \. | - * | substring | start:length | | Returns a substring | - * | suffix | text | | Adds a suffix | - * | tointeger | | | Type conversion to an integer value | - * | tojson | | | Type conversion to a JSON string (since 1.8.0) | - * | tolower | | strtolower, tolowercase | The input value is converted to lowercase letters | - * | tostring | | | Type conversion to a string. | - * | toupper | | strtoupper, touppercase | The input value is converted to uppercase letters | - * | trim | | | Remove spaces at the beginning and end | - * | ucfirst | | | First character large | - * | ucwords | | | Any word beginning large | - * | undefined | | | Return undefined | - * | uniqid | | | Creates a string with a unique value (**) - * - * (*) for this functionality the extension [jsdom](https://www.npmjs.com/package/jsdom) must be loaded in the nodejs context. - * - * ``` - * // polyfill - * if (typeof window !== "object") { - * const {window} = new JSDOM('', { - * url: 'http://example.com/', - * pretendToBeVisual: true - * }); - * - * [ - * 'self', - * 'document', - * 'Node', - * 'Element', - * 'HTMLElement', - * 'DocumentFragment', - * 'DOMParser', - * 'XMLSerializer', - * 'NodeFilter', - * 'InputEvent', - * 'CustomEvent' - * ].forEach(key => (global[key] = window[key])); - * } - * ``` - * - * (**) for this command the crypt library is necessary in the nodejs context. - * - * ``` - * import * as Crypto from "@peculiar/webcrypto"; - * global['crypto'] = new Crypto.Crypto(); - * ``` - * - * @example - * - * import {Transformer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/transformer.mjs'; - * - * const transformer = new Transformer("tolower") - * - * console.log(transformer.run("HELLO")) - * // ↦ hello - * - * console.log(transformer.run("WORLD")) - * // ↦ world - * - * @since 1.5.0 - * @copyright schukai GmbH - * @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} command - * @returns {array} - * @private - */ -function disassemble(command) { - - validateString(command); - - let placeholder = new Map; - const regex = /((?<pattern>\\(?<char>.)){1})/mig; - - // 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 -} - -/** - * tries to make a string out of value and if this succeeds to return it back - * - * @param {*} value - * @returns {string} - * @private - */ -function convertToString(value) { - - if (isObject(value) && value.hasOwnProperty('toString')) { - value = value.toString(); - } - - validateString(value) - return value; -} - -/** - * - * @param {*} value - * @returns {*} - * @private - * @throws {Error} unknown command - * @throws {TypeError} unsupported type - * @throws {Error} type not supported - * @throws {Error} missing key parameter - */ -function transform(value) { - - const console = getGlobalObject('console'); - - let args = clone(this.args); - let key, defaultValue; - - switch (this.command) { - - case 'static': - return this.args.join(':'); - - case 'tolower': - case 'strtolower': - case 'tolowercase': - validateString(value) - return value.toLowerCase(); - - 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 'tojson': - return JSON.stringify(value); - - 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 (falseStatement === 'value') { - falseStatement = value; - } - if (falseStatement === '\\value') { - falseStatement = 'value'; - } - - 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 '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") - - - default: - throw new Error("unknown command " + this.command) - } - - return value; -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {isArray, isInteger, isObject, isPrimitive} from '../types/is.mjs'; -import {Stack} from "../types/stack.mjs"; -import {validateInteger, validateString} from '../types/validate.mjs'; - -export {Pathfinder, DELIMITER, WILDCARD} - -/** - * path separator - * - * @private - * @type {string} - */ -const DELIMITER = '.'; - -/** - * @private - * @type {string} - */ -const WILDCARD = '*'; - -/** - * Pathfinder is a class to find a path to an object. - * - * ``` - * <script type="module"> - * import {Pathfinder} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pathfinder.mjs'; - * console.log(new Pathfinder()) - * </script> - * ``` - * - * With the help of the pathfinder, values can be read and written from an object construct. - * - * ``` - * new Pathfinder({ - * a: { - * b: { - * f: [ - * { - * g: false, - * } - * ], - * } - * } - * }).getVia("a.b.f.0.g"); // ↦ false - * ``` - * - * if a value is not present or has the wrong type, a corresponding exception is thrown. - * - * ``` - * new Pathfinder({}).getVia("a.b.f.0.g"); // ↦ Error - * ``` - * - * The `Pathfinder.exists()` method can be used to check whether access to the path is possible. - * - * ``` - * new Pathfinder({}).exists("a.b.f.0.g"); // ↦ false - * ``` - * - * pathfinder can also be used to build object structures. to do this, the `Pathfinder.setVia()` method must be used. - * - * ``` - * obj = {}; - * new Pathfinder(obj).setVia('a.b.0.c', true); // ↦ {a:{b:[{c:true}]}} - * ``` - * - * @example - * - * import {Pathfinder} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pathfinder.mjs'; - * - * let value = new Pathfinder({ - * a: { - * b: { - * f: [ - * { - * g: false, - * } - * ], - * } - * } - * }).getVia("a.b.f.0.g"); - * - * console.log(value); - * // ↦ false - * - * try { - * new Pathfinder({}).getVia("a.b.f.0.g"); - * } catch(e) { - * console.log(e.toString()); - * // ↦ Error: the journey is not at its end (b.f.0.g) - * } - * - * @example - * - * import {Pathfinder} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pathfinder.mjs'; - * - * let p = new Pathfinder({ - * a: { - * x: [ - * {c: 1}, {c: 2} - * ], - * y: true - * }, - * b: { - * x: [ - * {c: 1, d: false}, {c: 2} - * ], - * y: true - * }, - * }); - * - * let r = p.getVia("*.x.*.c"); - * console.log(r); - * - * @since 1.4.0 - * @copyright schukai GmbH - * @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} 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, validateString(path)); - } - - /** - * - * @param {string} 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) { - validateString(path); - setValueViaPath.call(this, this.object, path, value); - return this; - } - - /** - * Delete Via Path - * - * @param {string} 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) { - validateString(path); - deleteValueViaPath.call(this, this.object, path); - return this; - } - - /** - * - * @param {string} 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) { - validateString(path); - try { - getValueViaPath.call(this, this.object, path, true); - return true; - } catch (e) { - - } - - return false; - } - -} - - -/** - * - * @param {*} subject - * @param {string} path - * @param {string} check - * @return {Map} - * @throws {TypeError} unsupported type - * @throws {Error} the journey is not at its end - * @throws {Error} unsupported action for this data type - * @private - */ -function iterate(subject, path, check) { - - const result = new Map; - - 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; - - -} - -/** - * - * @param {*} subject - * @param [string} path - * @param [boolean} check - * @returns {*} - * @throws {TypeError} unsupported type - * @throws {Error} the journey is not at its end - * @throws {Error} unsupported action for this data type - * @private - */ -function getValueViaPath(subject, path, check) { - - if (path === "") { - return subject; - } - - let 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) - -} - -/** - * - * @param object - * @param path - * @param value - * @returns {void} - * @throws {TypeError} unsupported type - * @throws {TypeError} unsupported type - * @throws {Error} the journey is not at its end - * @throws {Error} unsupported action for this data type - * @private - */ -function setValueViaPath(object, path, value) { - - validateString(path); - - let 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, object, 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, object, current, obj); - } - - let anchor = getValueViaPath.call(this, object, subpath); - - if (!isObject(object) && !isArray(object)) { - throw TypeError("unsupported type: " + typeof object); - } - - 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); - } - - -} - -/** - * @private - * @param {object} object - * @param {string} key - * @param {*} value - */ -function assignProperty(object, key, value) { - - if (!object.hasOwnProperty(key)) { - object[key] = value; - return; - } - - if (value === undefined) { - delete object[key]; - } - - object[key] = value; - -} - -/** - * - * @param object - * @param path - * @returns {void} - * @throws {TypeError} unsupported type - * @throws {TypeError} unsupported type - * @throws {Error} the journey is not at its end - * @throws {Error} unsupported action for this data type - * @since 1.6.0 - * @private - */ -function deleteValueViaPath(object, path) { - - const parts = path.split(DELIMITER) - let last = parts.pop(); - const subpath = parts.join(DELIMITER); - - const anchor = getValueViaPath.call(this, object, 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]; - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {isArray, isObject} from "../types/is.mjs"; -import {typeOf} from "../types/typeof.mjs"; - -export {diff} - -/** - * With the diff function you can perform the change of one object to another. The result shows the changes of the second object to the first object. - * - * The operator `add` means that something has been added to the second object. `delete` means that something has been deleted from the second object compared to the first object. - * - * ``` - * <script type="module"> - * import {Diff} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/diff.mjs'; - * Diff(a, b) - * </script> - * ``` - * - * @example - * - * import {Diff} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/diff.mjs'; - * - * // given are two objects x and y. - * - * let x = { - * a: 1, - * b: "Hello!" - * } - * - * let y = { - * a: 2, - * c: true - * } - * - * // These two objects can be compared with each other. - * - * console.log(Diff(x, y)); - * - * // the result is then the following - * - * // - * // [ - * // { - * // operator: 'update', - * // path: [ 'a' ], - * // first: { value: 1, type: 'number' }, - * // second: { value: 2, type: 'number' } - * // }, - * // { - * // operator: 'delete', - * // path: [ 'b' ], - * // first: { value: 'Hello!', type: 'string' } - * // }, - * // { - * // operator: 'add', - * // path: [ 'c' ], - * // second: { value: true, type: 'boolean' } - * // } - * // ] - * - * @param {*} first - * @param {*} second - * @return {array} - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.Data - */ -function diff(first, second) { - return doDiff(first, second) -} - -/** - * @private - * @param a - * @param b - * @param type - * @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))); -} - -/** - * @private - * @param a - * @param b - * @param path - * @param diff - * @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; - -} - -/** - * - * @param {*} a - * @param {*} b - * @param {string} operator - * @param {array} path - * @return {{path: array, operator: string}} - * @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; -} - -/** - * @private - * @param {*} a - * @param {*} b - * @return {boolean} - */ -function isNotEqual(a, b) { - - if (typeof a !== typeof b) { - return true; - } - - if (a instanceof Date && b instanceof Date) { - return a.getTime() !== b.getTime(); - } - - return a !== b; -} - -/** - * @private - * @param {*} a - * @param {*} 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; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../constants.mjs"; -import {isObject} from "../../types/is.mjs"; -import {Datasource} from "../datasource.mjs"; -import {Pathfinder} from "../pathfinder.mjs"; -import {Pipe} from "../pipe.mjs"; -import {WriteError} from "./restapi/writeerror.mjs"; - -export {RestAPI} - -/** - * The RestAPI is a class that enables a REST API server. - * - * ``` - * <script type="module"> - * import {RestAPI} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/restapi.mjs'; - * new RestAPI() - * </script> - * ``` - * - * @example - * - * import {RestAPI} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/restapi.mjs'; - * - * const ds = new RestAPI({ - * url: 'https://httpbin.org/get' - * },{ - * url: 'https://httpbin.org/post' - * }); - * - * ds.set({flag:true}) - * ds.write().then(()=>console.log('done')); - * ds.read().then(()=>console.log('done')); - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource - * @summary The LocalStorage class encapsulates the access to data objects. - */ -class RestAPI extends Datasource { - - /** - * - * @param {Object} [readDefinition] An options object containing any custom settings that you want to apply to the read request. - * @param {Object} [writeDefinition] An options object containing any custom settings that you want to apply to the write request. - * @throws {TypeError} value is not a string - */ - constructor(readDefinition, writeDefinition) { - super(); - - const options = {} - - if (isObject(readDefinition)) options.read = readDefinition; - if (isObject(writeDefinition)) options.write = writeDefinition; - - this.setOptions(options); - - } - - /** - * @property {string} url=undefined Defines the resource that you wish to fetch. - * @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 {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 {Object} write.report - * @property {String} write.report.path Path to validations - * @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing. - * @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', - }, - acceptedStatus: [200, 201], - url: undefined, - mapping: { - transformer: undefined, - callbacks: [] - }, - report: { - path: undefined - } - }, - read: { - init: { - method: 'GET' - }, - 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 response; - - let init = self.getOption('read.init'); - if (!isObject(init)) init = {}; - - return fetch(self.getOption('read.url'), init).then(resp => { - response = resp; - - const acceptedStatus = self.getOption('read.acceptedStatus', [200]); - - if (acceptedStatus.indexOf(resp.status) === -1) { - throw Error('the data cannot be read (response ' + resp.status + ')') - } - - return resp.text() - }).then(body => { - - let obj; - - try { - obj = JSON.parse(body); - - } catch (e) { - - if (body.length > 100) { - body = body.substring(0, 97) + '...'; - } - - throw new Error('the response does not contain a valid json (actual: ' + body + ').'); - } - - let transformation = self.getOption('read.mapping.transformer'); - if (transformation !== undefined) { - const pipe = new Pipe(transformation); - obj = pipe.run(obj); - } - - self.set(obj); - return response; - }) - } - - /** - * @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' - } - } - - let obj = self.get(); - let transformation = self.getOption('write.mapping.transformer'); - if (transformation !== undefined) { - const pipe = new Pipe(transformation); - obj = pipe.run(obj); - } - - let sheathingObject = self.getOption('write.sheathing.object'); - let sheathingPath = self.getOption('write.sheathing.path'); - let reportPath = self.getOption('write.report.path'); - - if (sheathingObject && sheathingPath) { - const sub = obj; - obj = sheathingObject; - (new Pathfinder(obj)).setVia(sheathingPath, sub); - } - - init['body'] = JSON.stringify(obj); - - return fetch(self.getOption('write.url'), init).then(response => { - - const acceptedStatus = self.getOption('write.acceptedStatus', [200, 2001]); - - if (acceptedStatus.indexOf(response.status) === -1) { - - return response.text().then((body) => { - - let obj, validation; - try { - obj = JSON.parse(body); - validation = new Pathfinder(obj).getVia(reportPath) - - } catch (e) { - - if (body.length > 100) { - body = body.substring(0, 97) + '...'; - } - - throw new Error('the response does not contain a valid json (actual: ' + body + ').'); - } - - throw new WriteError('the data cannot be written (response ' + response.status + ')', response, validation) - - }) - - - } - - return response; - }); - } - - - /** - * @return {RestAPI} - */ - getClone() { - const self = this; - return new RestAPI(self[internalSymbol].getRealSubject()['options'].read, self[internalSymbol].getRealSubject()['options'].write); - } - -} - - -/** - * This callback can be passed to a datasource and is used to adapt data structures. - * - * @callback Monster.Data.Datasource~exampleCallback - * @param {*} value Value - * @param {string} key Key - * @memberOf Monster.Data - * @see Monster.Data.Datasource - */ -'use strict'; - -/** - * Namespace for datasources - * - * @namespace Monster.Data.Datasource - * @memberOf Monster.Data - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../../constants.mjs"; -import {getGlobalObject} from "../../../types/global.mjs"; -import {Datasource} from "../../datasource.mjs"; -import {Storage, storageObjectSymbol} from "../storage.mjs"; - -export {SessionStorage} - -/** - * The SessionStorage class provides a data source that uses the SessionStorage API on the client. - * - * ``` - * <script type="module"> - * import {SessionStorage} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/storage/sessionstorage.mjs'; - * new SessionStorage() - * </script> - * ``` - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource.Storage - * @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'); - } - - /** - * Create Clone - * - * @return {SessionStorage} - */ - getClone() { - const self = this; - return new SessionStorage(self[internalSymbol].getRealSubject()['options'].key); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../../constants.mjs"; -import {getGlobalObject} from "../../../types/global.mjs"; -import {Datasource} from "../../datasource.mjs"; -import {Storage, storageObjectSymbol} from "../storage.mjs"; - -export {LocalStorage} - -/** - * The LocalStorage Datasource provides a data store in the browser localStorage. - * - * ``` - * <script type="module"> - * import {LocalStorage} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/storage/localstorage.mjs'; - * new LocalStorage() - * </script> - * ``` - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource.Storage - * @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'); - } - - /** - * Create clone - * @return {LocalStorage} - */ - getClone() { - const self = this; - return new LocalStorage(self[internalSymbol].getRealSubject()['options'].key); - } - - -} -'use strict'; - -/** - * Namespace for storages - * - * @namespace Monster.Data.Datasource.Storage - * @memberOf Monster.Data.Datasource - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * Namespace for storages - * - * @namespace Monster.Data.Datasource.RestAPI - * @memberOf Monster.Data.Datasource - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../../constants.mjs"; - -export {WriteError} - -/** - * Error message for API requests with extension of request and validation. - * - * @since 1.24.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource.RestAPI - * @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 - }; - } - - /** - * @return {Response} - */ - getResponse() { - return this[internalSymbol]['response'] - } - - /** - * @return {Object} - */ - getValidation() { - return this[internalSymbol]['validation'] - } -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../constants.mjs"; -import {validateString} from "../../types/validate.mjs"; -import {Datasource} from "../datasource.mjs"; - -export {Storage, storageObjectSymbol} - -/** - * @private - * @type {symbol} - */ -const storageObjectSymbol = Symbol('storageObject'); - -/** - * The class represents a record. - * - * ``` - * <script type="module"> - * import {Storage} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/storage.mjs'; - * new Storage() - * </script> - * ``` - * - * @example - * - * import {Storage} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/storage.mjs'; - * - * new Datasource(); - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource - * @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)); - } - - /** - * @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") - } - - /** - * @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](); - - 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; - - 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)); - } - - resolve(); - }) - } - - /** - * @return {Storage} - */ - getClone() { - const self=this; - return new Storage(self[internalSymbol].getRealSubject()['options'].key); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {getGlobal} from '../types/global.mjs'; - -export {random} - -/** - * this function uses crypt and returns a random number. - * - * ``` - * <script type="module"> - * import {random} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/math/random.mjs'; - * random(1,10) - * // ↦ 5 - * </script> - * ``` - * - * @param {number} min starting value of the definition set (default is 0) - * @param {number} max end value of the definition set (default is 1000000000) - * @returns {number} - * @memberOf Monster.Math - * @throws {Error} missing crypt - * @throws {Error} we cannot generate numbers larger than 53 bits. - * @throws {Error} the distance is too small to create a random number. - - * @since 1.0.0 - * @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)); - -} - -/** - * @private - * @type {number} - */ -var MAX = 1000000000; - - -Math.log2 = Math.log2 || function (n) { - return Math.log(n) / Math.log(2); -}; - -/** - * - * @param {number} min - * @param {number} max - * @returns {number} - * @private - * @throws {Error} missing crypt - * @throws {Error} we cannot generate numbers larger than 53 bits. - * @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; - -} -'use strict'; - -/** - * Namespace for math. - * - * @namespace Monster.Math - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - -import {isObject} from "../types/is.mjs"; -import {AbstractConstraint} from "./abstract.mjs"; - -export {IsObject} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * ``` - * <script type="module"> - * import {IsObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/isobject.mjs'; - * console.log(new IsObject()) - * </script> - * ``` - * - * @example - * - * import {IsObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/isobject.mjs'; - * - * new IsObject() - * .isValid({}) - * .then(()=>console.log(true)); - * // ↦ true - * - * - * new IsObject() - * .isValid(99) - * .catch(e=>console.log(e)); - * // ↦ 99 - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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); - } - - return Promise.reject(value); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractConstraint} from "./abstract.mjs"; - -export {Invalid} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The invalid constraint allows an always invalid query to be performed. this constraint is mainly intended for testing. - * - * ``` - * <script type="module"> - * import {Invalid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/invalid.mjs'; - * new Invalid(); - * </script> - * ``` - * - * @example - * - * import {Invalid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/invalid.mjs'; - * - * new Invalid().isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ false - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractOperator} from "./abstractoperator.mjs"; - -export {AndOperator} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The AndOperator is used to link several contraints. The constraint is fulfilled if all constraints of the operators are fulfilled. - * - * ``` - * <script type="module"> - * import {AndOperator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/andoperator.mjs'; - * new AndOperator(); - * </script> - * ``` - * - * @example - * - * import {Valid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/valid.mjs'; - * import {Invalid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/invalid.mjs'; - * import {AndOperator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/andoperator.mjs'; - * - * new AndOperator( - * new Valid(), new Valid()).isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ true - * - * new AndOperator( - * new Invalid(), new Valid()).isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ false - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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)]); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractOperator} from "./abstractoperator.mjs"; - -export {OrOperator} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The OrOperator is used to link several constraints. The constraint is fulfilled if one of the constraints is fulfilled. - * - * ``` - * <script type="module"> - * import {OrOperator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraint/oroperator.mjs'; - * new OrOperator(); - * </script> - * ``` - * - * @example - * - * import {Valid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/valid.mjs'; - * import {Invalid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/invalid.mjs'; - * import {OrOperator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/oroperator.mjs'; - * - * new OrOperator( - * new Valid(), new Invalid()).isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ true - * - * new OrOperator( - * new Invalid(), new Invalid()).isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ false - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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; - - return new Promise(function (resolve, reject) { - let a, 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.operantB.isValid(value) - .then(function () { - resolve(); - }).catch(function () { - b = false; - /** b has already been evaluated and was not true */ - if (a === false) { - reject(); - } - }); - }); - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {Base} from '../types/base.mjs'; - -export {AbstractConstraint} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The abstract constraint defines the api for all constraints. mainly the method isValid() is defined. - * - * derived classes must implement the method isValid(). - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @summary The abstract constraint - */ -class AbstractConstraint extends Base { - - /** - * - */ - constructor() { - super(); - } - - /** - * this method must return a promise containing the result of the check. - * - * @param {*} value - * @returns {Promise} - */ - isValid(value) { - return Promise.reject(value); - } -} -'use strict'; - -/** - * Constraints are used to define conditions that must be met by the value of a variable so that the value can be transferred to the system. - * - * @namespace Monster.Constraints - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - -import {isArray} from "../types/is.mjs"; -import {AbstractConstraint} from "./abstract.mjs"; - -export {IsArray} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * ``` - * <script type="module"> - * import {IsArray} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/isarray.mjs'; - * console.log(new IsArray()) - * </script> - * ``` - * - * @example - * - * import {IsArray} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/isarray.mjs'; - * - * new IsArray() - * .isValid([]) - * .then(()=>console.log(true)); - * // ↦ true - * - * new IsArray() - * .isValid(99) - * .catch(e=>console.log(e)); - * // ↦ 99 - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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); - } - - return Promise.reject(value); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractConstraint} from "./abstract.mjs"; - -export {AbstractOperator} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * Operators allow you to link constraints together. for example, you can check whether a value is an object or an array. each operator has two operands that are linked together. - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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(); - - if (!(operantA instanceof AbstractConstraint) || !(operantB instanceof AbstractConstraint)) { - throw new TypeError("parameters must be from type AbstractConstraint") - } - - this.operantA = operantA; - this.operantB = operantB; - - } - - -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractConstraint} from "./abstract.mjs"; - -export {Valid} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The valid constraint allows an always valid query to be performed. this constraint is mainly intended for testing. - * - * ``` - * <script type="module"> - * import {Valid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/valid.mjs'; - * new Valid(); - * </script> - * ``` - * - * @example - * - * import {Valid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/valid.mjs'; - * - * new Valid().isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ true - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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); - } - -} -/** - * @license - * Copyright 2021 schukai GmbH - * SPDX-License-Identifier: AGPL-3.0-only or COMMERCIAL - * @author schukai GmbH - */ - - -/** - * Main namespace for Monster. - * - * @namespace Monster - * @author schukai GmbH - */ -'use strict'; - -import './constants.mjs'; -// find packages/monster/source/ -type f -name "*.mjs" -not -name "*namespace*" -not -iname "monster.mjs" -import './constraints/isobject.mjs'; -import './constraints/valid.mjs'; -import './constraints/invalid.mjs'; -import './constraints/abstractoperator.mjs'; -import './constraints/oroperator.mjs'; -import './constraints/andoperator.mjs'; -import './constraints/abstract.mjs'; -import './constraints/isarray.mjs'; -import './logging/logger.mjs'; -import './logging/handler.mjs'; -import './logging/logentry.mjs'; -import './logging/handler/console.mjs'; -import './text/formatter.mjs'; -import './dom/resource/script.mjs'; -import './dom/resource/data.mjs'; -import './dom/resource/link/stylesheet.mjs'; -import './dom/resource/link.mjs'; -import './dom/resource.mjs'; -import './dom/updater.mjs'; -import './dom/attributes.mjs'; -import './dom/template.mjs'; -import './dom/util.mjs'; -import './dom/ready.mjs'; -import './dom/resourcemanager.mjs'; -import './dom/locale.mjs'; -import './dom/customcontrol.mjs'; -import './dom/constants.mjs'; -import './dom/assembler.mjs'; -import './dom/theme.mjs'; -import './dom/worker/factory.mjs'; -import './dom/focusmanager.mjs'; -import './dom/events.mjs'; -import './dom/customelement.mjs'; -import './i18n/formatter.mjs'; -import './i18n/providers/fetch.mjs'; -import './i18n/translations.mjs'; -import './i18n/locale.mjs'; -import './i18n/provider.mjs'; -import './types/queue.mjs'; -import './types/binary.mjs'; -import './types/regex.mjs'; -import './types/observer.mjs'; -import './types/observerlist.mjs'; -import './types/basewithoptions.mjs'; -import './types/is.mjs'; -import './types/proxyobserver.mjs'; -import './types/uniquequeue.mjs'; -import './types/node.mjs'; -import './types/tokenlist.mjs'; -import './types/typeof.mjs'; -import './types/uuid.mjs'; -import './types/mediatype.mjs'; -import './types/dataurl.mjs'; -import './types/base.mjs'; -import './types/version.mjs'; -import './types/nodelist.mjs'; -import './types/id.mjs'; -import './types/randomid.mjs'; -import './types/noderecursiveiterator.mjs'; -import './types/validate.mjs'; -import './types/stack.mjs'; -import './util/deadmansswitch.mjs'; -import './util/comparator.mjs'; -import './util/trimspaces.mjs'; -import './util/clone.mjs'; -import './util/freeze.mjs'; -import './util/processing.mjs'; -import './constants.mjs'; -import './data/pathfinder.mjs'; -import './data/pipe.mjs'; -import './data/extend.mjs'; -import './data/diff.mjs'; -import './data/buildmap.mjs'; -import './data/datasource.mjs'; -import './data/buildtree.mjs'; -import './data/transformer.mjs'; -import './data/datasource/storage.mjs'; -import './data/datasource/restapi.mjs'; -import './data/datasource/storage/sessionstorage.mjs'; -import './data/datasource/storage/localstorage.mjs'; -import './data/datasource/restapi/writeerror.mjs'; -import './math/random.mjs'; - -export {Monster} - -/** - * This class has no other purpose than to exist. - * - * @since 2.0.0 - * @copyright schukai GmbH - * @memberOf Monster - */ -class Monster { - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -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'; - -export {Logger, ALL, TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF}; - -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const ALL = 255; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const TRACE = 64; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const DEBUG = 32; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const INFO = 16; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const WARN = 8; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const ERROR = 4; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const FATAL = 2; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const OFF = 0; - -/** - * The logger is a class that takes care of logging. - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/logging/logger.mjs'; - * new Logger() - * </script> - * ``` - * - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Logging - */ -class Logger extends Base { - - /** - * - */ - 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") - } - - 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") - } - - 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() { - triggerLog.apply(this, [TRACE, ...arguments]); - 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() { - triggerLog.apply(this, [DEBUG, ...arguments]); - 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() { - triggerLog.apply(this, [INFO, ...arguments]); - 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() { - triggerLog.apply(this, [WARN, ...arguments]); - 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() { - triggerLog.apply(this, [ERROR, ...arguments]); - 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() { - triggerLog.apply(this, [FATAL, ...arguments]); - return this; - }; - - - /** - * 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'; - - return 'unknown'; - }; - - /** - * 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; - - return 0; - }; - - -} - - -/** - * Log triggern - * - * @param {integer} loglevel - * @param {*} args - * @returns {Logger} - * @private - */ -function triggerLog(loglevel, ...args) { - var logger = this; - - for (let handler of logger.handler) { - handler.log(new LogEntry(loglevel, args)) - } - - return logger; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {Base} from '../types/base.mjs'; -import {validateInteger} from '../types/validate.mjs'; - -export {LogEntry} - -/** - * A log entry for the logger - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/logging/logentry.mjs'; - * console.log(new LogEntry()) - * </script> - * ``` - * - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Logging - */ - class LogEntry extends Base { - /** - * - * @param {Integer} loglevel - * @param {...*} args - */ - constructor(loglevel, ...args) { - super(); - validateInteger(loglevel); - - this.loglevel = loglevel - this.arguments = args - } - - /** - * - * @returns {integerr} - */ - getLogLevel() { - return this.loglevel - } - - /** - * - * @returns {array} - */ - getArguments() { - return this.arguments - } - -} -'use strict'; - -/** - * Namespace for logging. - * - * @namespace Monster.Logging - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @namespace Monster.Logging.Handler - * @memberOf Monster.Logging - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../../types/base.mjs'; -import {getGlobalObject} from "../../types/global.mjs"; -import {Handler} from '../handler.mjs'; -import {LogEntry} from "../logentry.mjs"; - -export {ConsoleHandler} - -/** - * You can create an object of the class simply by using the namespace `new Monster.Logging.Handler.ConsoleHandler()`. - * - * ``` - * <script type="module"> - * import {ConsoleHandler} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/logging/handler/console.mjs'; - * console.log(new ConsoleHandler()) - * </script> - * ``` - * - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Logging.Handler - */ - class ConsoleHandler extends Handler { - constructor() { - super(); - } - - - /** - * 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; - console.log(entry.toString()); - return true; - } - - return false; - } - -} - - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {validateInstance, validateInteger} from "../types/validate.mjs"; -import {LogEntry} from "./logentry.mjs"; -import {ALL, DEBUG, ERROR, FATAL, INFO, OFF, TRACE, WARN} from "./logger.mjs"; - -export {Handler} - -/** - * The log handler is the interface between the log entries and the log listeners. - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/logging/handler.mjs'; - * console.log(new Handler()) - * </script> - * ``` - * - * @since 1.5.0 - * @copyright schukai GmbH - * @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; - }; - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; -import {Pipe} from "../data/pipe.mjs"; - -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {isObject, isString} from "../types/is.mjs"; -import {validateArray, validateString} from "../types/validate.mjs"; -import {getMonsterVersion} from "../types/version.mjs"; - -export {Formatter} - -/** - * @private - * @type {symbol} - */ -const internalObjectSymbol = Symbol('internalObject'); - -/** - * @private - * @type {symbol} - */ -const watchdogSymbol = Symbol('watchdog'); - -/** - * @private - * @type {symbol} - */ -const markerOpenIndexSymbol = Symbol('markerOpenIndex'); - -/** - * @private - * @type {symbol} - */ -const markerCloseIndexSymbol = Symbol('markercloseIndex'); - -/** - * @private - * @type {symbol} - */ -const workingDataSymbol = Symbol('workingData'); - - -/** - * Messages can be formatted with the formatter. To do this, an object with the values must be passed to the formatter. The message can then contain placeholders. - * - * Look at the example below. The placeholders use the logic of Pipe. - * - * ``` - * <script type="module"> - * import {Formatter} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/text/formatter.mjs'; - * new Formatter() - * </script> - * ``` - * - * ## Marker in marker - * - * Markers can be nested. Here, the inner marker is resolved first `${subkey} ↦ 1 = ${mykey2}` and then the outer marker `${mykey2}`. - * - * ``` - * const text = '${mykey${subkey}}'; - * let obj = { - * mykey2: "1", - * subkey: "2" - * }; - * - * new Formatter(obj).format(text); - * // ↦ 1 - * ``` - * - * ## Callbacks - * - * The values in a formatter can be adjusted via the commands of the `Transformer` or the`Pipe`. - * There is also the possibility to use callbacks. - * - * const formatter = new Formatter({x: '1'}, { - * callbacks: { - * quote: (value) => { - * return '"' + value + '"' - * } - * } - * }); - * - * formatter.format('${x | call:quote}')) - * // ↦ "1" - * - * ## Marker with parameter - * - * A string can also bring its own values. These must then be separated from the key by a separator `::`. - * The values themselves must be specified in key/value pairs. The key must be separated from the value by a separator `=`. - * - * When using a pipe, you must pay attention to the separators. - * - * @example - * - * import {Formatter} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/text/formatter.mjs'; - * - * new Formatter({ - * a: { - * b: { - * c: "Hello" - * }, - * d: "world", - * } - * }).format("${a.b.c} ${a.d | ucfirst}!"); // with pipe - * - * // ↦ Hello World! - * - * @since 1.12.0 - * @copyright schukai GmbH - * @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); - } - -} - -/** - * @private - * @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; -} - -/** - * @private - * @since 1.12.0 - * @param 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") - return; - } - - 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(''); -} -'use strict'; - -/** - * Namespace for texts. - * - * @namespace Monster.Text - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {extend} from "../../../data/extend.mjs"; -import {Link} from "../link.mjs"; - -export {Stylesheet} - -/** - * This class is used by the resource manager to embed external resources. - * - * ``` - * <script type="module"> - * import {Style} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource/link/stylesheet.mjs'; - * new Stylesheet() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Resource - * @summary A Resource class - * @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' - }) - } - -} - -'use strict'; - -/** - * In this namespace you will find classes and methods for links - * - * @namespace Monster.DOM.Resource.Link - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * In this namespace you will find classes and methods for handling resources. - * - * @namespace Monster.DOM.Resource - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -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 -} from "../constants.mjs"; -import {KEY_DOCUMENT, KEY_QUERY, referenceSymbol, Resource} from "../resource.mjs"; - -export {Data} - -/** - * This class is used by the resource manager to embed data. - * - * ``` - * <script type="module"> - * import {Data} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource/data.mjs'; - * new Data() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Resource - * @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; - } - - /** - * @return {string} - */ - static getURLAttribute() { - return ATTRIBUTE_SRC - } - -} - -/** - * @private - * @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; -} - - -/** - * @private - * @return {Promise} - * 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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {extend} from "../../data/extend.mjs"; -import { - 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"; - -export {Link} - -/** - * This class is used by the resource manager to embed external resources. - * - * ``` - * <script type="module"> - * import {Link} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource/link.mjs'; - * new Link() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Resource - * @summary A Resource class - * @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 - }) - } - - /** - * - * @return {Monster.DOM.Resource.Link} - */ - create() { - createElement.call(this); - return this; - } - - /** - * @return {string} - */ - static getURLAttribute() { - return ATTRIBUTE_HREF - } - -} - -/** - * @private - * @return {Monster.DOM.Resource.Link} - */ -function createElement() { - const self = this; - - 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); - } - } - - return self; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {extend} from "../../data/extend.mjs"; -import { - 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"; - -export {Script} - -/** - * This class is used by the resource manager to embed scripts. - * - * ``` - * <script type="module"> - * import {Script} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource/script.mjs'; - * new Script() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Resource - * @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', - }) - } - - /** - * - * @return {Monster.DOM.Resource.Script} - */ - create() { - createElement.call(this); - return this; - } - - /** - * @return {string} - */ - static getURLAttribute() { - return ATTRIBUTE_SRC - } - -} - -/** - * @private - * @return {Monster.DOM.Resource.Script} - */ -function createElement() { - const self = this; - - 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); - } - } - - - return self; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -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 -} from "../dom/constants.mjs"; - -import {Base} from "../types/base.mjs"; -import {isArray, isInstance, isIterable} from "../types/is.mjs"; -import {Observer} from "../types/observer.mjs"; -import {ProxyObserver} from "../types/proxyobserver.mjs"; -import {validateArray, validateInstance} from "../types/validate.mjs"; -import {clone} from "../util/clone.mjs"; -import {trimSpaces} from "../util/trimspaces.mjs"; -import {findTargetElementFromEvent} from "./events.mjs"; -import {findDocumentTemplate} from "./template.mjs"; -import {getDocument} from "./util.mjs"; - -export {Updater} - -/** - * The updater class connects an object with the dom. In this way, structures and contents in the DOM can be programmatically adapted via attributes. - * - * For example, to include a string from an object, the attribute `data-monster-replace` can be used. - * a further explanation can be found under {@tutorial dom-based-templating-implementation}. - * - * Changes to attributes are made only when the direct values are changed. If you want to assign changes to other values - * as well, you have to insert the attribute `data-monster-select-this`. This should be done with care, as it can reduce performance. - * - * ``` - * <script type="module"> - * import {Updater} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/updater.mjs'; - * new Updater() - * </script> - * ``` - * - * @example - * - * import {Updater} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/updater.mjs'; - * - * // First we prepare the html document. - * // This is done here via script, but can also be inserted into the document as pure html. - * // To do this, simply insert the tag <h1 data-monster-replace="path:headline"></h1>. - * const body = document.querySelector('body'); - * const headline = document.createElement('h1'); - * headline.setAttribute('data-monster-replace','path:headline') - * body.appendChild(headline); - * - * // the data structure - * let obj = { - * headline: "Hello World", - * }; - * - * // Now comes the real magic. we pass the updater the parent HTMLElement - * // and the desired data structure. - * const updater = new Updater(body, obj); - * updater.run(); - * - * // Now you can change the data structure and the HTML will follow these changes. - * const subject = updater.getSubject(); - * subject['headline'] = "Hello World!" - * - * @since 1.8.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} the value is not iterable - * @throws {Error} pipes are not allowed when cloning a node. - * @throws {Error} no template was found with the specified key. - * @throws {Error} the maximum depth for the recursion is reached. - * @throws {TypeError} value is not a object - * @throws {TypeError} value is not an instance of HTMLElement - * @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; - } - -} - -/** - * @private - * @since 1.9.0 - * @return {function - * @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; - } - } -} - -/** - * @private - */ -const symbol = Symbol('EventHandler'); - -/** - * @private - * @return {function} - * @this Updater - * @throws {Error} the bind argument must start as a value with a path - */ -function getControlEventHandler() { - - const self = this; - - 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); - - if (element === undefined) { - return; - } - - retrieveAndSetValue.call(self, element); - - } - - return self[symbol]; - - -} - -/** - * @throws {Error} the bind argument must start as a value with a path - * @param {HTMLElement} element - * @return void - * @memberOf Monster.DOM - * @private - */ -function retrieveAndSetValue(element) { - - const self = this; - - const pathfinder = new Pathfinder(self[internalSymbol].subject.getSubject()); - - let path = element.getAttribute(ATTRIBUTE_UPDATER_BIND); - - if (path.indexOf('path:') !== 0) { - throw new Error('the bind argument must start as a value with a path'); - } - - path = path.substr(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); - } -} - -/** - * @since 1.27.0 - * @return void - * @private - */ -function retrieveFromBindings() { - const self = this; - - if (self[internalSymbol].element.matches('[' + ATTRIBUTE_UPDATER_BIND + ']')) { - retrieveAndSetValue.call(self, element) - } - - for (const [, element] of self[internalSymbol].element.querySelectorAll('[' + ATTRIBUTE_UPDATER_BIND + ']').entries()) { - retrieveAndSetValue.call(self, element) - } - -} - -/** - * @private - * @since 1.8.0 - * @param {object} change - * @return {void} - */ -function removeElement(change) { - const self = this; - - for (const [, element] of self[internalSymbol].element.querySelectorAll(':scope [' + ATTRIBUTE_UPDATER_REMOVE + ']').entries()) { - element.parentNode.removeChild(element); - } -} - -/** - * @private - * @since 1.8.0 - * @param {object} change - * @return {void} - * @throws {Error} the value is not iterable - * @throws {Error} pipes are not allowed when cloning a node. - * @throws {Error} no template was found with the specified key. - * @throws {Error} the maximum depth for the recursion is reached. - * @this Updater - */ -function insertElement(change) { - const self = this; - const subject = self[internalSymbol].subject.getRealSubject(); - const document = getDocument(); - - 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); - 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.'); - } - - } - - -} - -/** - * - * @private - * @since 1.8.0 - * @param {HTMLElement} container - * @param {string} key - * @param {string} ref - * @param {string} path - * @throws {Error} no template was found with the specified key. - */ -function appendNewDocumentFragment(container, key, ref, path) { - - 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); - } - - container.appendChild(node); - } -} - -/** - * @private - * @since 1.10.0 - * @param {HTMLElement} node - * @param {string} key - * @param {string} 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); - } - } -} - -/** - * @private - * @since 1.8.0 - * @param {object} change - * @return {void} - * @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); - } - } - } - - -} - -/** - * @private - * @since 1.8.0 - * @param {HTMLElement} container - * @param {array} parts - * @param {object} subject - * @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:"]'; - 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; - } - - } - - - } - -} - -/** - * @private - * @since 1.8.0 - * @param {string} path - * @param {object} change - * @return {void} - */ -function updateAttributes(change) { - const subject = this[internalSymbol].subject.getRealSubject(); - let p = clone(change?.['path']); - runUpdateAttributes.call(this, this[internalSymbol].element, p, subject); -} - -/** - * @private - * @param {HTMLElement} container - * @param {array} parts - * @param {object} subject - * @return {void} - * @this Updater - */ -function runUpdateAttributes(container, parts, subject) { - - const self = this; - - if (!isArray(parts)) return; - parts = clone(parts); - - let mem = new WeakSet; - - while (parts.length > 0) { - const current = parts.join('.'); - parts.pop(); - - let iterator = new Set; - - const query = '[' + ATTRIBUTE_UPDATER_SELECT_THIS + '], [' + ATTRIBUTE_UPDATER_ATTRIBUTES + '*="path:' + current + '"], [' + ATTRIBUTE_UPDATER_ATTRIBUTES + '^="static:"]'; - - const e = container.querySelectorAll(query); - - if (e.length > 0) { - iterator = new Set( - [...e] - ) - } - - if (container.matches(query)) { - iterator.add(container); - } - - for (const [element] of iterator.entries()) { - - if (mem.has(element)) return; - mem.add(element) - - 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)); - - let pipe = new Pipe(cmd); - - 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); - } - - - if (value === undefined) { - element.removeAttribute(name) - - } else if (element.getAttribute(name) !== value) { - element.setAttribute(name, value); - } - - handleInputControlAttributeUpdate.call(this, element, name, value); - - } - } - } - -} - -/** - * @private - * @param {HTMLElement|*} element - * @param {string} name - * @param {string|number|undefined} value - * @return {void} - * @this Updater - */ - -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); - } - } - -} -'use strict'; - -import {extend} from "../data/extend.mjs"; -/** - * @author schukai GmbH - */ - -import {ATTRIBUTE_VALUE} from "./constants.mjs"; -import {CustomElement, attributeObserverSymbol} from "./customelement.mjs"; - -export {CustomControl} - -/** - * @private - * @type {symbol} - */ -const attachedInternalSymbol = Symbol('attachedInternal'); - -/** - * To define a new HTML control we need the power of CustomElement - * - * IMPORTANT: after defining a `CustomElement`, the `registerCustomElement` method must be called - * with the new class name. only then will the tag defined via the `getTag` method be made known to the DOM. - * - * <img src="./images/customcontrol-class.png"> - * - * This control uses `attachInternals()` to integrate the control into a form. - * If the target environment does not support this method, the [polyfill](https://www.npmjs.com/package/element-internals-polyfill ) can be used. - * - * You can create the object via the function `document.createElement()`. - * - * ``` - * <script type="module"> - * import {CustomControl} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * document.createElement('monster-') - * </script> - * ``` - * - * @startuml customcontrol-class.png - * skinparam monochrome true - * skinparam shadowing false - * HTMLElement <|-- CustomElement - * CustomElement <|-- CustomControl - * @enduml - * - * @summary A base class for customcontrols based on CustomElement - * @see {@link https://www.npmjs.com/package/element-internals-polyfill} - * @see {@link https://github.com/WICG/webcomponents} - * @see {@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements} - * @since 1.14.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - */ -class CustomControl extends CustomElement { - - /** - * 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 ElementInternals is not supported and a polyfill is necessary - * @summary create new Instance - */ - constructor() { - super(); - - if (typeof this['attachInternals'] === 'function') { - /** - * currently only supported by chrome - * @property {Object} - * @private - */ - this[attachedInternalSymbol] = this.attachInternals(); - } - - initObserver.call(this); - - } - - /** - * This method determines which attributes are to be monitored by `attributeChangedCallback()`. - * - * @return {string[]} - * @since 1.15.0 - */ - static get observedAttributes() { - const list = super.observedAttributes; - list.push(ATTRIBUTE_VALUE); - return list; - } - - /** - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} - * @since 1.14.0 - * @return {boolean} - */ - static get formAssociated() { - return true; - } - - /** - * Derived classes can override and extend this method as follows. - * - * ``` - * get defaults() { - * return extends{}, super.defaults, { - * myValue:true - * }); - * } - * ``` - * - * @see {@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example} - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} - * @return {object} - * @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) - * - * @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 return the value of the control. - * - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @param {*} value - * @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 {@link https://developer.mozilla.org/en-US/docs/Web/API/ValidityState} - * @see {@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 {CustomStateSet} - * @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(); - } - -} - -/** - * @private - * @return {object} - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - * @this CustomControl - */ -function getInternal() { - const self = this; - - if (!(attachedInternalSymbol in this)) { - throw new Error('ElementInternals is not supported and a polyfill is necessary'); - } - - return this[attachedInternalSymbol]; -} - -/** - * @private - * @return {object} - * @this CustomControl - */ -function initObserver() { - const self = this; - - // value - self[attributeObserverSymbol]['value'] = () => { - self.setOption('value', self.getAttribute('value')); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {parseLocale} from "../i18n/locale.mjs"; - -import {getDocument} from "./util.mjs"; - -export {getLocaleOfDocument} - -/** - * @private - * @type {string} - */ -const DEFAULT_LANGUAGE = 'en'; - -/** - * With this function you can read the language version set by the document. - * For this the attribute `lang` in the html tag is read. If no attribute is set, `en` is used as default. - * - * ```html - * <html lang="en"> - * ``` - * - * You can call the function via the monster namespace `new Monster.DOM.getLocaleOfDocument()`. - * - * ``` - * <script type="module"> - * import {getLocaleOfDocument} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/locale.mjs'; - * new getLocaleOfDocument() - * </script> - * ``` - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {TypeError} value is not a string - * @throws {Error} unsupported locale - * @summary Tries to determine the locale used - */ -function getLocaleOfDocument() { - - 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) - } - } - - return parseLocale(DEFAULT_LANGUAGE); -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {getGlobalObject} from '../types/global.mjs'; -import {validateString} from "../types/validate.mjs"; -import {ATTRIBUTE_THEME_NAME, DEFAULT_THEME} from "./constants.mjs"; - -export {Theme, getDocumentTheme} - -/** - * The Theme class provides the functionality for the theme. - * - * ``` - * <script type="module"> - * import {Theme} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/theme.mjs'; - * console.log(new Theme()) - * </script> - * ``` - * - * @example - * - * import {getDocumentTheme} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/theme.mjs'; - * - * const theme = getDocumentTheme(); - * console.log(theme.getName()); - * // ↦ monster - * - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @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; - } - - /** - * - * @returns {string} - */ - getName() { - return this.name; - } - -} - -/** - * The theming used in the document can be defined via the html-tag. - * The theming is specified via the attribute `data-monster-theme-name`. - * - * As name for a theme all characters are valid, which are also allowed for a HTMLElement-ID. - * - * ``` - * <html data-monster-theme-name="my-theme"> - * ``` - * - * the default theme name is `monster`. - * - * @return {Theme} - * @memberOf Monster.DOM - * @since 1.7.0 - */ -function getDocumentTheme() { - 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; - } - } - - return new Theme(name); - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalStateSymbol, internalSymbol,} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {getGlobalObject} from "../types/global.mjs"; -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"; - -export {Resource, KEY_DOCUMENT, KEY_QUERY, referenceSymbol} - -/** - * @private - * @type {string} - */ -const KEY_DOCUMENT = 'document'; - -/** - * @private - * @type {string} - */ -const KEY_QUERY = 'query'; - -/** - * @private - * @type {string} - */ -const KEY_TIMEOUT = 'timeout'; - -/** - * @private - * @type {symbol} - */ -const referenceSymbol = Symbol('reference'); - -/** - * This class is the base class for all resources to be loaded. - * - * ``` - * <script type="module"> - * import {Resource} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource.mjs'; - * new Resource() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @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"); - } - -} - - -/** - * @private - * @return {Promise} - * 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') - } - - addEvents.call(self); - targetNode.appendChild(self[referenceSymbol]); - - return self; -} - -/** - * @private - * @return {addEvents} - */ -function addEvents() { - const self = this; - - 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', - }) - - 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); - - return self; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {extend} from "../data/extend.mjs"; -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {getGlobalObject} from "../types/global.mjs"; -import {isArray} from "../types/is.mjs"; -import {ATTRIBUTE_HREF, ATTRIBUTE_SRC} from "./constants.mjs"; -import {Resource} from "./resource.mjs"; -import {Data} from "./resource/data.mjs"; -import {Stylesheet} from "./resource/link/stylesheet.mjs"; -import {Script} from "./resource/script.mjs"; - -export {ResourceManager} - -/** - * The ResourceManager is a singleton that manages all resources. - * - * ``` - * <script type="module"> - * import {Resource} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resourcemanager.mjs'; - * new ResourceManager() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @summary A Resource class - */ - class ResourceManager extends BaseWithOptions { - - /** - * - * @param {Object} options - * throw {Error} unsupported document type - */ - constructor(options) { - super(options); - - if (!(this.getOption('document') instanceof Document)) { - throw new Error('unsupported document type') - } - - - } - - /** - * @property {string} baseurl - */ - getBaseURL() { - this.getOption('document')?.baseURL; - } - - /** - * - * @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 defaults() { - return Object.assign({}, super.defaults, { - 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); - } - - -} - -/** - * @private - * @param {string} method - * @return {Array} - */ -function runResourceMethod(method) { - const self = this; - - const result = []; - - 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') - } - - result.push(resource[method]()); - } - - } - - return result; -} - -/** - * - * @param {string} type - * @param {string|URL} url - * @param [Object|undefined} options - * @return {Monster.DOM.ResourceManager} - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {getGlobal} from "../types/global.mjs"; -import {validateString} from "../types/validate.mjs"; - -export {getDocument, getWindow, getDocumentFragmentFromString} - -/** - * this method fetches the document object - * - * ``` - * <script type="module"> - * import {getDocument} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/util.mjs'; - * console.log(getDocument()) - * </script> - * ``` - * - * in nodejs this functionality can be performed with [jsdom](https://www.npmjs.com/package/jsdom). - * - * ``` - * import {JSDOM} from "jsdom" - * if (typeof window !== "object") { - * const {window} = new JSDOM('', { - * url: 'http://example.com/', - * pretendToBeVisual: true - * }); - * - * [ - * 'self', - * 'document', - * 'Document', - * 'Node', - * 'Element', - * 'HTMLElement', - * 'DocumentFragment', - * 'DOMParser', - * 'XMLSerializer', - * 'NodeFilter', - * 'InputEvent', - * 'CustomEvent' - * ].forEach(key => (getGlobal()[key] = window[key])); - * } - * ``` - * - * @returns {object} - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} not supported environment - */ -function getDocument() { - let document = getGlobal()?.['document']; - if (typeof document !== 'object') { - throw new Error("not supported environment") - } - - return document; -} - -/** - * this method fetches the window object - * - * ``` - * <script type="module"> - * import {getWindow} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/util.mjs'; - * console.log(getWindow(null)) - * </script> - * ``` - * - * in nodejs this functionality can be performed with [jsdom](https://www.npmjs.com/package/jsdom). - * - * ``` - * import {JSDOM} from "jsdom" - * if (typeof window !== "object") { - * const {window} = new JSDOM('', { - * url: 'http://example.com/', - * pretendToBeVisual: true - * }); - * - * getGlobal()['window']=window; - * - * [ - * 'self', - * 'document', - * 'Document', - * 'Node', - * 'Element', - * 'HTMLElement', - * 'DocumentFragment', - * 'DOMParser', - * 'XMLSerializer', - * 'NodeFilter', - * 'InputEvent', - * 'CustomEvent' - * ].forEach(key => (getGlobal()[key] = window[key])); - * } - * ``` - * - * @returns {object} - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} not supported environment - */ -function getWindow() { - let window = getGlobal()?.['window']; - if (typeof window !== 'object') { - throw new Error("not supported environment") - } - - return window; -} - - -/** - * this method fetches the document object - * - * ``` - * <script type="module"> - * import {getDocumentFragmentFromString} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/util.mjs'; - * console.log(getDocumentFragmentFromString('<div></div>')) - * </script> - * ``` - * - * in nodejs this functionality can be performed with [jsdom](https://www.npmjs.com/package/jsdom). - * - * ``` - * import {JSDOM} from "jsdom" - * if (typeof window !== "object") { - * const {window} = new JSDOM('', { - * url: 'http://example.com/', - * pretendToBeVisual: true - * }); - * - * [ - * 'self', - * 'document', - * 'Document', - * 'Node', - * 'Element', - * 'HTMLElement', - * 'DocumentFragment', - * 'DOMParser', - * 'XMLSerializer', - * 'NodeFilter', - * 'InputEvent', - * 'CustomEvent' - * ].forEach(key => (getGlobal()[key] = window[key])); - * } - * ``` - * - * @returns {DocumentFragment} - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} not supported environment - * @throws {TypeError} value is not a string - */ -function getDocumentFragmentFromString(html) { - validateString(html); - - const document = getDocument(); - const template = document.createElement('template'); - template.innerHTML = html; - - return template.content; -} -'use strict'; - - -/** - * @author schukai GmbH - */ - -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, - -} - -/** - * default theme - * @memberOf Monster.DOM - * @type {string} - */ -const DEFAULT_THEME = 'monster'; - -/** - * @memberOf Monster.DOM - * @since 1.8.0 - * @type {string} - */ -const ATTRIBUTE_PREFIX = 'data-monster-'; - -/** - * This is the name of the attribute to pass options to a control - * - * @memberOf Monster.DOM - * @since 1.8.0 - * @type {string} - */ -const ATTRIBUTE_OPTIONS = ATTRIBUTE_PREFIX + 'options'; - -/** - * This is the name of the attribute to pass options to a control - * - * @memberOf Monster.DOM - * @since 1.30.0 - * @type {string} - */ -const ATTRIBUTE_OPTIONS_SELECTOR = ATTRIBUTE_PREFIX + 'options-selector'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_THEME_PREFIX = ATTRIBUTE_PREFIX + 'theme-'; - -/** - * @memberOf Monster.DOM - * @type {string} - */ -const ATTRIBUTE_THEME_NAME = ATTRIBUTE_THEME_PREFIX + 'name'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_ATTRIBUTES = ATTRIBUTE_PREFIX + 'attributes'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.27.1 - */ -const ATTRIBUTE_UPDATER_SELECT_THIS = ATTRIBUTE_PREFIX + 'select-this'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_REPLACE = ATTRIBUTE_PREFIX + 'replace'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_INSERT = ATTRIBUTE_PREFIX + 'insert'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_INSERT_REFERENCE = ATTRIBUTE_PREFIX + 'insert-reference'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_REMOVE = ATTRIBUTE_PREFIX + 'remove'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.9.0 - */ -const ATTRIBUTE_UPDATER_BIND = ATTRIBUTE_PREFIX + 'bind'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.27.0 - */ -const ATTRIBUTE_TEMPLATE_PREFIX = ATTRIBUTE_PREFIX + 'template-prefix'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.14.0 - */ -const ATTRIBUTE_ROLE = ATTRIBUTE_PREFIX + 'role'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.24.0 - */ -const ATTRIBUTE_DISABLED = 'disabled'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.24.0 - */ -const ATTRIBUTE_VALUE = 'value'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.9.0 - */ -const ATTRIBUTE_OBJECTLINK = ATTRIBUTE_PREFIX + 'objectlink'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.24.0 - */ -const ATTRIBUTE_ERRORMESSAGE = ATTRIBUTE_PREFIX + 'error'; - -/** - * @memberOf Monster.DOM - * @type {symbol} - * @since 1.24.0 - */ -const objectUpdaterLinkSymbol = Symbol('monsterUpdater'); - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const TAG_SCRIPT = 'script'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const TAG_STYLE = 'style'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const TAG_LINK = 'link'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ - -const ATTRIBUTE_ID = 'id'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ - -const ATTRIBUTE_CLASS = 'class'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_TITLE = 'title'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_SRC = 'src'; -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_HREF = 'href'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_TYPE = 'type'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_NONCE = 'nonce'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_TRANSLATE = 'translate'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_TABINDEX = 'tabindex'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_SPELLCHECK = 'spellcheck'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_SLOT = 'slot'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_PART = 'part'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_LANG = 'lang'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMTYPE = 'itemtype'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMSCOPE = 'itemscope'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMREF = 'itemref'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMID = 'itemid'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMPROP = 'itemprop'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_IS = 'is'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_INPUTMODE = 'inputmode'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ACCESSKEY = 'accesskey'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_AUTOCAPITALIZE = 'autocapitalize'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_AUTOFOCUS = 'autofocus'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_CONTENTEDITABLE = 'contenteditable'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_DIR = 'dir'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_DRAGGABLE = 'draggable'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ENTERKEYHINT = 'enterkeyhint'; -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_EXPORTPARTS = 'exportparts'; -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_HIDDEN = 'hidden'; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; -import {Pathfinder} from "../data/pathfinder.mjs"; - -import {parseDataURL} from "../types/dataurl.mjs"; -import {getGlobalObject} from "../types/global.mjs"; -import {isArray, isFunction, 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 {clone} from "../util/clone.mjs"; -import {addAttributeToken, addToObjectLink, getLinkedObjects, hasObjectLink} from "./attributes.mjs"; -import { - ATTRIBUTE_DISABLED, - ATTRIBUTE_ERRORMESSAGE, - ATTRIBUTE_OPTIONS, - ATTRIBUTE_OPTIONS_SELECTOR, - objectUpdaterLinkSymbol -} from "./constants.mjs"; -import {findDocumentTemplate, Template} from "./template.mjs"; -import {Updater} from "./updater.mjs"; - -export {CustomElement, initMethodSymbol, assembleMethodSymbol, attributeObserverSymbol, registerCustomElement, assignUpdaterToElement} - -/** - * @memberOf Monster.DOM - * @type {symbol} - */ -const initMethodSymbol = Symbol('initMethodSymbol'); - -/** - * @memberOf Monster.DOM - * @type {symbol} - */ -const assembleMethodSymbol = Symbol('assembleMethodSymbol'); - -/** - * this symbol holds the attribute observer callbacks. The key is the attribute name. - * @memberOf Monster.DOM - * @type {symbol} - */ -const attributeObserverSymbol = Symbol('attributeObserver'); - - -/** - * HTMLElement - * @external HTMLElement - * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement - * - * @startuml customelement-sequencediagram.png - * skinparam monochrome true - * skinparam shadowing false - * - * autonumber - * - * Script -> DOM: element = document.createElement('my-element') - * DOM -> CustomElement: constructor() - * CustomElement -> CustomElement: [initMethodSymbol]() - * - * CustomElement --> DOM: Element - * DOM --> Script : element - * - * - * Script -> DOM: document.querySelector('body').append(element) - * - * DOM -> CustomElement : connectedCallback() - * - * note right CustomElement: is only called at\nthe first connection - * CustomElement -> CustomElement : [assembleMethodSymbol]() - * - * ... ... - * - * autonumber - * - * Script -> DOM: document.querySelector('monster-confirm-button').parentNode.removeChild(element) - * DOM -> CustomElement: disconnectedCallback() - * - * - * @enduml - * - * @startuml customelement-class.png - * skinparam monochrome true - * skinparam shadowing false - * HTMLElement <|-- CustomElement - * @enduml - */ - - -/** - * To define a new HTML element we need the power of CustomElement - * - * IMPORTANT: after defining a `CustomElement`, the `registerCustomElement` method must be called - * with the new class name. only then will the tag defined via the `getTag` method be made known to the DOM. - * - * <img src="./images/customelement-class.png"> - * - * You can create the object via the function `document.createElement()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * document.createElement('monster-') - * </script> - * ``` - * - * ## Interaction - * - * <img src="./images/customelement-sequencediagram.png"> - * - * ## Styling - * - * For optimal display of custom-elements the pseudo-class :defined can be used. - * - * To prevent the custom elements from being displayed and flickering until the control is registered, it is recommended to create a css directive. - * - * In the simplest case, you can simply hide the control. - * - * ``` - * <style> - * - * my-custom-element:not(:defined) { - * display: none; - * } - * - * my-custom-element:defined { - * display: flex; - * } - * - * </style> - * ``` - * - * Alternatively you can also display a loader - * - * ``` - * my-custom-element:not(:defined) { - * display: flex; - * box-shadow: 0 4px 10px 0 rgba(33, 33, 33, 0.15); - * border-radius: 4px; - * height: 200px; - * position: relative; - * overflow: hidden; - * } - * - * my-custom-element:not(:defined)::before { - * content: ''; - * display: block; - * position: absolute; - * left: -150px; - * top: 0; - * height: 100%; - * width: 150px; - * background: linear-gradient(to right, transparent 0%, #E8E8E8 50%, transparent 100%); - * animation: load 1s cubic-bezier(0.4, 0.0, 0.2, 1) infinite; - * } - * - * @keyframes load { - * from { - * left: -150px; - * } - * to { - * left: 100%; - * } - * } - * - * my-custom-element:defined { - * display: flex; - * } - * ``` - * - * @example - * - * // In the example the the user can use his own template by creating a template in the DOM with the ID `my-custom-element`. - * // You can also specify a theme (for example `mytheme`), then it will search for the ID `my-custom-element-mytheme` and - * // if not available for the ID `my-custom-element`. - * - * class MyCustomElement extends CustomElement { - * - * static getTag() { - * return "my-custom-element" - * } - * - * } - * - * // ↦ <my-custom-element></my-custom-element> - * - * @see https://github.com/WICG/webcomponents - * @see https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @extends external:HTMLElement - * @summary A base class for HTML5 customcontrols - */ -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. - * - * @throws {Error} the options attribute does not contain a valid json definition. - * @since 1.7.0 - */ - constructor() { - super(); - this[internalSymbol] = new ProxyObserver({'options': extend({}, this.defaults)}); - this[attributeObserverSymbol] = {}; - initOptionObserver.call(this); - this[initMethodSymbol](); - } - - /** - * This method determines which attributes are to be monitored by `attributeChangedCallback()`. - * - * @return {string[]} - * @since 1.15.0 - */ - static get observedAttributes() { - return [ATTRIBUTE_OPTIONS, ATTRIBUTE_DISABLED]; - } - - /** - * Derived classes can override and extend this method as follows. - * - * ``` - * get defaults() { - * return Object.assign({}, super.defaults, { - * myValue:true - * }); - * } - * ``` - * - * To set the options via the html tag the attribute data-monster-options must be set. - * As value a JSON object with the desired values must be defined. - * - * Since 1.18.0 the JSON can be specified as a DataURI. - * - * ``` - * new Monster.Types.DataUrl(btoa(JSON.stringify({ - * shadowMode: 'open', - * delegatesFocus: true, - * templates: { - * main: undefined - * } - * })),'application/json',true).toString() - * ``` - * - * The attribute data-monster-options-selector can be used to access a script tag that contains additional configuration. - * - * As value a selector must be specified, which belongs to a script tag and contains the configuration as json. - * - * ``` - * <script id="id-for-this-config" type="application/json"> - * { - * "config-key": "config-value" - * } - * </script> - * ``` - * - * The individual configuration values can be found in the table. - * - * @property {boolean} disabled=false Object The Boolean disabled attribute, when present, makes the element not mutable, focusable, or even submitted with the form. - * @property {string} shadowMode=open `open` Elements of the shadow root are accessible from JavaScript outside the root, for example using. `close` Denies access to the node(s) of a closed shadow root from JavaScript outside it - * @property {Boolean} delegatesFocus=true A boolean that, when set to true, specifies behavior that 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 Templates - * @property {string} templates.main=undefined Main template - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow - * @since 1.8.0 - */ - get defaults() { - return { - ATTRIBUTE_DISABLED: this.getAttribute(ATTRIBUTE_DISABLED), - shadowMode: 'open', - delegatesFocus: true, - templates: { - main: undefined - } - }; - } - - /** - * There is no check on the name by this class. the developer is responsible for assigning an appropriate tag. - * if the name is not valid, registerCustomElement() will issue an error - * - * @link https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name - * @return {string} - * @throws {Error} the method getTag must be overwritten by the derived class. - * @since 1.7.0 - */ - static getTag() { - throw new Error("the method getTag must be overwritten by the derived class."); - } - - /** - * At this point a `CSSStyleSheet` object can be returned. If the environment does not - * support a constructor, then an object can also be built using the following detour. - * - * If `undefined` is returned then the shadowRoot does not get a stylesheet. - * - * ``` - * const doc = document.implementation.createHTMLDocument('title'); - * - * let style = doc.createElement("style"); - * style.innerHTML="p{color:red;}"; - * - * // WebKit Hack - * style.appendChild(document.createTextNode("")); - * // Add the <style> element to the page - * doc.head.appendChild(style); - * return doc.styleSheets[0]; - * ; - * ``` - * - * @return {CSSStyleSheet|CSSStyleSheet[]|string|undefined} - */ - 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; - } - - /** - * Is called once when the object is included in the DOM for the first time. - * - * @return {CustomElement} - * @since 1.8.0 - */ - [assembleMethodSymbol]() { - - const self = this; - let elements, nodeList; - - const AttributeOptions = getOptionsFromAttributes.call(self); - if (isObject(AttributeOptions) && Object.keys(AttributeOptions).length > 0) { - self.setOptions(AttributeOptions); - } - - const ScriptOptions = getOptionsFromScriptTag.call(self); - if (isObject(ScriptOptions) && Object.keys(ScriptOptions).length > 0) { - self.setOptions(ScriptOptions); - } - - - if (self.getOption('shadowMode', false) !== false) { - try { - initShadowRoot.call(self); - elements = self.shadowRoot.childNodes; - - } catch (e) { - - } - - try { - initCSSStylesheet.call(this); - } catch (e) { - addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString()); - } - } - - if (!(elements instanceof NodeList)) { - if (!(elements instanceof NodeList)) { - initHtmlContent.call(this); - elements = this.childNodes; - } - } - - try { - nodeList = new Set([ - ...elements, - ...getSlottedElements.call(self) - ]) - } catch (e) { - nodeList = elements - } - - assignUpdaterToElement.call(self, nodeList, clone(self[internalSymbol].getRealSubject()['options'])); - return self; - } - - /** - * Called every time the element is inserted into the DOM. Useful for running setup code, such as - * fetching resources or rendering. Generally, you should try to delay work until this time. - * - * @return {void} - * @since 1.7.0 - */ - connectedCallback() { - let self = this; - if (!hasObjectLink(self, objectUpdaterLinkSymbol)) { - 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; - - const callback = self[attributeObserverSymbol]?.[attrName]; - - if (isFunction(callback)) { - callback.call(self, newVal, oldVal); - } - - } - - /** - * - * @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); - - } - -} - -/** - * @private - * @param {String|undefined} query - * @param {String|undefined|null} name name of the slot (if the parameter is undefined, all slots are searched, if the parameter has the value null, all slots without a name are searched. if a string is specified, the slots with this name are searched.) - * @return {*} - * @this CustomElement - * @since 1.23.0 - * @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; -} - -/** - * @this CustomElement - * @private - * @param {Node} node - * @return {boolean} - */ -function containChildNode(node) { - const self = this; - - if (self.contains(node)) { - return true; - } - - for (const [, e] of Object.entries(self.childNodes)) { - if (e.contains(node)) { - return true; - } - - containChildNode.call(e, node); - } - - - return false; -} - -/** - * @since 1.15.0 - * @private - * @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, objectUpdaterLinkSymbol)) { - return; - } - // inform every element - const updaters = getLinkedObjects(self, objectUpdaterLinkSymbol); - - 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); - } - } - - -} - -/** - * @private - * @return {object} - * @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; - -} - -/** - * @private - * @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 {}; -} - -/** - * @private - * @param data - * @return {Object} - */ -function parseOptionsJSON(data) { - - const self = this, 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) { - - } - - try { - let obj = JSON.parse(data); - return validateObject(obj); - } catch (e) { - throw e; - } - - - return obj; -} - -/** - * @private - * @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) { - this.innerHTML = html; - } - - } - - return this; - -} - -/** - * @private - * @return {CustomElement} - * @memberOf Monster.DOM - * @this CustomElement - * @since 1.16.0 - * @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; - -} - -/** - * @private - * @return {CustomElement} - * @throws {Error} html is not set. - * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow - * @memberOf Monster.DOM - * @since 1.8.0 - */ -function initShadowRoot() { - - let template, 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; - } - - this.shadowRoot.innerHTML = html; - return this; -} - -/** - * This method registers a new element. The string returned by `CustomElement.getTag()` is used as the tag. - * - * @param {CustomElement} element - * @return {void} - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {DOMException} Failed to execute 'define' on 'CustomElementRegistry': is not a valid custom element name - */ -function registerCustomElement(element) { - validateFunction(element); - getGlobalObject('customElements').define(element.getTag(), element); -} - - -/** - * - * @param element - * @param object - * @return {Promise[]} - * @since 1.23.0 - * @memberOf Monster.DOM - */ -function assignUpdaterToElement(elements, object) { - - const updaters = new Set; - - if (elements instanceof NodeList) { - elements = new Set([ - ...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(this, objectUpdaterLinkSymbol, updaters); - } - - return result; -} -'use strict'; - -/** - * In this namespace you will find classes and methods for handling the DOM. - * - * @namespace Monster.DOM - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - - -import {getGlobalFunction} from "../types/global.mjs"; -import {TokenList} from "../types/tokenlist.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 -} - -/** - * Get the closest object link of a node - * - * if a node is specified without a object link, a recursive search upwards is performed until the corresponding - * object link is found, or undefined is returned. - * - * ``` - * <script type="module"> - * import {getUpdaterFromNode} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/updater.mjs'; - * console.log(findClosestObjectLink()) - * </script> - * ``` - * - * @param {HTMLElement} element - * @return {HTMLElement|undefined} - * @since 1.10.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {TypeError} value is not an instance of HTMLElement - */ -function findClosestObjectLink(element) { - return findClosestByAttribute(element, ATTRIBUTE_OBJECTLINK); -} - -/** - * Adds a class attribute to an element. - * - * ``` - * <script type="module"> - * import {addToObjectLink} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * addToObjectLink(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {Symbol} symbol - * @param {Object} object - * @return {boolean} - */ -function addToObjectLink(element, symbol, object) { - - validateInstance(element, HTMLElement); - validateSymbol(symbol) - - if (element?.[symbol] === undefined) { - element[symbol] = new Set; - } - - addAttributeToken(element, ATTRIBUTE_OBJECTLINK, symbol.toString()); - element[symbol].add(object); - return element; - -} - -/** - * Removes an object from an element - * - * ``` - * <script type="module"> - * import {removeObjectLink} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * removeObjectLink(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {Symbol} symbol - * @return {boolean} - */ -function removeObjectLink(element, symbol) { - - validateInstance(element, HTMLElement); - validateSymbol(symbol) - - if (element?.[symbol] === undefined) { - return element - } - - removeAttributeToken(element, ATTRIBUTE_OBJECTLINK, symbol.toString()); - delete element[symbol]; - return element; - -} - - -/** - * Checks if an element has an object link - * - * ``` - * <script type="module"> - * import {hasObjectLink} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * hasObjectLink(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {Symbol} 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()); - -} - -/** - * The ObjectLink can be used to attach objects to HTMLElements. The elements are kept in a set under a unique - * symbol and can be read via an iterator {@see {@link getLinkedObjects}}. - * - * In addition, elements with an objectLink receive the attribute `data-monster-objectlink`. - * - * With the method {@see {@link addToObjectLink}} the objects can be added. - * - * ``` - * <script type="module"> - * import {getLinkedObjects} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * getLinkedObjects(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {Symbol} symbol - * @return {Iterator} - * @throws {Error} there is no object link for symbol - */ -function getLinkedObjects(element, symbol) { - - validateInstance(element, HTMLElement); - validateSymbol(symbol) - - if (element?.[symbol] === undefined) { - throw new Error('there is no object link for ' + symbol.toString()); - } - - return element?.[symbol][Symbol.iterator](); - -} - - -/** - * With this method tokens in an attribute can be switched on or off. For example, classes can be switched on and off in the elements class attribute. - * - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {toggleAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * toggleAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} token - * @return {HTMLElement} - */ -function toggleAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token) - validateString(key) - - if (!element.hasAttribute(key)) { - element.setAttribute(key, token); - return element; - } - - element.setAttribute(key, new TokenList(element.getAttribute(key)).toggle(token).toString()); - - return element -} - -/** - * This method can be used to add a token to an attribute. Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {addAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * addAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} token - * @return {HTMLElement} - */ -function addAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token) - validateString(key) - - if (!element.hasAttribute(key)) { - element.setAttribute(key, token); - return element; - } - - element.setAttribute(key, new TokenList(element.getAttribute(key)).add(token).toString()); - - return element -} - -/** - * This function can be used to remove tokens from an attribute. - * - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {removeAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * removeAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} token - * @return {HTMLElement} - */ -function removeAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token) - validateString(key) - - if (!element.hasAttribute(key)) { - return element; - } - - element.setAttribute(key, new TokenList(element.getAttribute(key)).remove(token).toString()); - - return element -} - -/** - * This method can be used to determine whether an attribute has a token. - * - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {containsAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * containsAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} token - * @return {boolean} - */ -function containsAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token) - validateString(key) - - if (!element.hasAttribute(key)) { - return false; - } - - return new TokenList(element.getAttribute(key)).contains(token); - -} - -/** - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {replaceAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * replaceAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} from - * @param {string} to - * @return {HTMLElement} - */ -function replaceAttributeToken(element, key, from, to) { - validateInstance(element, HTMLElement); - validateString(from) - validateString(to) - validateString(key) - - if (!element.hasAttribute(key)) { - return element; - } - - element.setAttribute(key, new TokenList(element.getAttribute(key)).replace(from, to).toString()); - - return element -} - -/** - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {clearAttributeTokens} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * clearAttributeTokens(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @return {HTMLElement} - */ -function clearAttributeTokens(element, key) { - validateInstance(element, HTMLElement); - validateString(key) - - if (!element.hasAttribute(key)) { - return element; - } - - element.setAttribute(key, ""); - - return element -} - -/** - * This function searches, starting from an `HTMLElemement`, for the next element that has a certain attribute. - * - * ```html - * <div data-my-attribute="2" id="2"> - * <div id="1"></div> - * </div> - * ``` - * - * ```javascript - * // if no value is specified (undefined), then only the attribute is checked. - * findClosestByAttribute(document.getElementById('1'),'data-my-attribute'); // ↦ node with id 2 - * findClosestByAttribute(document.getElementById('2'),'data-my-attribute'); // ↦ node with id 2 - * - * // if a value is specified, for example an empty string, then the name and the value are checked. - * findClosestByAttribute(document.getElementById('1'),'data-my-attribute', ''); // ↦ undefined - * findClosestByAttribute(document.getElementById('1'),'data-my-attribute', '2'); // ↦ node with id 2 - * ``` - * - * ``` - * <script type="module"> - * import {findClosestByAttribute} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * findClosestByAttribute(); - * </script> - * ``` - * - * @since 1.14.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string|undefined} value - * @return {HTMLElement|undefined} - * @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; -} - -/** - * This function searches, starting from an `HTMLElemement`, for the next element that has a certain attribute. - * - * ```html - * <div class="myclass" id="2"> - * <div id="1"></div> - * </div> - * ``` - * - * ```javascript - * // if no value is specified (undefined), then only the attribute is checked. - * findClosestByClass(document.getElementById('1'),'myclass'); // ↦ node with id 2 - * findClosestByClass(document.getElementById('2'),'myclass'); // ↦ node with id 2 - * ``` - * - * ``` - * <script type="module"> - * import {findClosestByClass} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * findClosestByClass(); - * </script> - * ``` - * - * @since 1.27.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} className - * @return {HTMLElement|undefined} - * @summary find closest node - */ -function findClosestByClass(element, className) { - validateInstance(element, getGlobalFunction('HTMLElement')); - - if (element?.classList?.contains(validateString(className))) { - return element; - } - - let result = element.closest('.' + className); - if (result instanceof HTMLElement) { - return result; - } - - return undefined; -} -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {isArray, isObject} from "../types/is.mjs"; -import {validateInstance, validateString} from "../types/validate.mjs"; -import {getDocument} from "./util.mjs"; - -export {fireEvent, fireCustomEvent, findTargetElementFromEvent} - -/** - * The function sends an event - * - * ``` - * <script type="module"> - * import {fireEvent} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/events.mjs'; - * fireEvent() - * </script> - * ``` - * - * @param {HTMLElement|HTMLCollection|NodeList} element - * @param {string} type - * @return {void} - * @since 1.10.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {TypeError} value is not an instance of HTMLElement or HTMLCollection - * @summary Construct and send and event - */ -function fireEvent(element, type) { - - const document = getDocument(); - - if (element instanceof HTMLElement) { - - if (type === 'click') { - element.click(); - return; - } - - let event = new Event(validateString(type), { - bubbles: true, - cancelable: 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') - } - -} - -/** - * You can call the function via the monster namespace `new Monster.DOM.fireCustomEvent()`. - * - * ``` - * <script type="module"> - * import {fireCustomEvent} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/events.mjs'; - * fireCustomEvent() - * </script> - * ``` - * - * @param {HTMLElement|HTMLCollection|NodeList} element - * @param {string} type - * @return {void} - * @since 1.29.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {TypeError} value is not an instance of HTMLElement or HTMLCollection - * @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, - 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') - } - -} - -/** - * This function gets the path `Event.composedPath()` from an event and tries to find the next element - * up the tree `element.closest()` with the attribute and value. If no value, or a value that is undefined or null, - * is specified, only the attribute is searched. - * - * ``` - * <script type="module"> - * import {findTargetElementFromEvent} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/events.mjs'; - * findTargetElementFromEvent() - * </script> - * ``` - * - * @since 1.14.0 - * @param {Event} event - * @param {string} attributeName - * @param {string|null|undefined} attributeValue - * @throws {Error} unsupported event - * @memberOf Monster.DOM - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not an instance of HTMLElement - * @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; - -} -'use strict'; - - -/** - * @author schukai GmbH - */ - - -import {internalSymbol} from "../../constants.mjs"; -import {Base} from "../../types/base.mjs"; -import {getGlobal, getGlobalFunction} from "../../types/global.mjs"; -import {isFunction} from "../../types/is.mjs"; -import {validateInstance, validateString} from "../../types/validate.mjs"; - -export {Factory} - -/** - * A factory for creating worker instances. - * - * ``` - * <script type="module"> - * import {Factory} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/worker/factory.mjs'; - * console.log(new Factory()) - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Worker - * @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; - } - - -} -'use strict'; - -/** - * In this namespace you will find classes and methods for handling the DOM. - * - * @namespace Monster.DOM.Worker - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {getGlobalFunction, getGlobalObject} from '../types/global.mjs'; -import {validateInstance, validateString} from "../types/validate.mjs"; -import {ATTRIBUTE_TEMPLATE_PREFIX} from "./constants.mjs"; -import {getDocumentTheme} from "./theme.mjs"; - -export {Template} - -/** - * The template class provides methods for creating templates. - * - * ``` - * <script type="module"> - * import {Template} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/template.mjs'; - * new Template() - * </script> - * ``` - * - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @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; - } - - /** - * - * @returns {HTMLTemplateElement} - */ - getTemplateElement() { - return this.template; - } - - /** - * - * @return {DocumentFragment} - * @throws {TypeError} value is not an instance of - */ - createDocumentFragment() { - return this.template.content.cloneNode(true); - } - -} - -/** - * This method loads a template with the given ID and returns it. - * - * To do this, it first reads the theme of the document and looks for the `data-monster-theme-name` attribute in the HTML tag. - * - * ``` - * <html data-monster-theme-name="my-theme"> - * ``` - * - * If no theme was specified, the default theme is `monster`. - * - * Now it is looked if there is a template with the given ID and theme `id-theme` and if yes it is returned. - * If there is no template a search for a template with the given ID `id` is done. If this is also not found, an error is thrown. - * - * You can call the method via the monster namespace `Monster.DOM.findDocumentTemplate()`. - * - * ``` - * <script type="module"> - * import {findTemplate} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/template.mjs'; - * findDocumentTemplate() - * </script> - * ``` - * - * @example - * - * import { findDocumentTemplate } from "https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/template.mjs"; - * - * const template = document.createElement("template"); - * template.id = "myTemplate"; - * template.innerHTML = "<p>my default template</p>"; - * document.body.appendChild(template); - * - * const themedTemplate = document.createElement("template"); - * themedTemplate.id = "myTemplate-myTheme"; - * themedTemplate.innerHTML = "<p>my themed template</p>"; - * document.body.appendChild(themedTemplate); - * - * // loads the temple and since no theme is set the default template - * const template1 = findDocumentTemplate("myTemplate"); - * console.log(template1.createDocumentFragment()); - * // ↦ '<p>my default template</p>' - * - * // now we set our own theme - * document - * .querySelector("html") - * .setAttribute("data-monster-theme-name", "myTheme"); - * - * // now we don't get the default template, - * // but the template with the theme in the id - * const template2 = findDocumentTemplate("myTemplate"); - * console.log(template2.createDocumentFragment()); - * // ↦ '<p>my themed template</p>' - * - * @param {string} id - * @param {Node} currentNode - * @return {Monster.DOM.Template} - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} template id not found. - * @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.") -} - -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {getDocument, getWindow} from "./util.mjs"; - -export {domReady, windowReady} - -/** - * This variable is a promise that is fulfilled as soon as the dom is available. - * - * The DOMContentLoaded event is fired when the original HTML document is fully loaded and parsed - * without waiting for stylesheets, images, and subframes to finish loading. - * - * ``` - * <script type="module"> - * import {domReady} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/ready.mjs'; - * domReady().then(()=>{ - * // ... - * }) - * </script> - * ``` - * - * @since 1.31.0 - * @memberOf Monster.DOM - * @summary variable to check if dom is ready - * @type {Promise} - * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event - * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState - */ -const domReady = new Promise(resolve => { - - const document = getDocument(); - - if (document.readyState === "loading") { - document.addEventListener('DOMContentLoaded', resolve); - } else { - resolve(); - } -}); - - -/** - * This variable is a promise that is fulfilled as soon as the windows is available. - * - * The load event fires when the entire page is loaded, including all dependent resources such as stylesheets, - * assets, and images. Unlike DOMContentLoaded, which fires as soon as the DOM of the page is loaded, - * without waiting for the resources to finish loading. - * - * ``` - * <script type="module"> - * import {windowReady} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/ready.mjs'; - * windowReady().then(()=>{ - * // ... - * }) - * </script> - * ``` - * - * @since 1.31.0 - * @memberOf Monster.DOM - * @summary variable to check if window is ready - * @type {Promise} - * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event - * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState - */ -const windowReady = new Promise(resolve => { - - const document = getDocument(); - const window = getWindow(); - - if (document.readyState === 'complete') { - resolve(); - } else { - window.addEventListener('load', resolve); - } -}); -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {extend} from "../data/extend.mjs"; -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {getGlobalObject} from "../types/global.mjs"; -import {isArray} from "../types/is.mjs"; -import {Stack} from "../types/stack.mjs"; -import {validateInstance, validateString} from "../types/validate.mjs"; - -export {FocusManager} - -/** - * @private - * @type {string} - */ -const KEY_DOCUMENT = 'document'; - -/** - * @private - * @type {string} - */ -const KEY_CONTEXT = 'context'; - - -/** - * @private - * @type {Symbol} - */ -const stackSymbol = Symbol('stack'); - - -/** - * With the focusmanager the focus can be stored in a document, recalled and moved. - * - * ``` - * <script type="module"> - * import {FocusManager} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/focusmanager.mjs'; - * new FocusManager() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} unsupported locale - * @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(); - } - - /** - * @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; - } - - -} - - - - - - - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {getGlobalFunction} from "../types/global.mjs"; -import {ProxyObserver} from "../types/proxyobserver.mjs"; -import {validateInstance, validateString} from "../types/validate.mjs"; - -export {ATTRIBUTEPREFIX,Assembler} - -/** - * attribute prefix - * - * @type {string} - * @memberOf Monster.DOM - */ -const ATTRIBUTEPREFIX = "data-monster-"; - -/** - * Assembler class - * - * ``` - * <script type="module"> - * import {Assembler} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/assembler.mjs'; - * console.log(new Assembler()) - * </script> - * ``` - * - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @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 {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; - } - - /** - * - * @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; - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from "../types/base.mjs"; -import {isObject, isString} from "../types/is.mjs"; -import {validateInstance, validateInteger, validateObject, validateString} from "../types/validate.mjs"; -import {Locale, parseLocale} from "./locale.mjs"; - -export {Translations} - -/** - * With this class you can manage translations and access the keys. - * - * ``` - * <script type="module"> - * import {Translations} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/translations.mjs'; - * new Translations() - * </script> - * ``` - * - * @example - * - * import {Translations} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/translations.mjs'; - * import {parseLocale} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/locale.mjs'; - * - * const translation = new Translations(parseLocale('en-GB')); - * - * translation.assignTranslations({ - * text1: "click", - * text2: { - * 'one': 'click once', - * 'other': 'click n times' - * } - * }); - * - * console.log(translation.getText('text1')); - * // ↦ click - * - * console.log(translation.getPluralRuleText('text2',1)); - * // -> click once - * console.log(translation.getPluralRuleText('text2',2)); - * // -> click n times - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n - * @see https://datatracker.ietf.org/doc/html/rfc3066 - */ -class Translations extends Base { - - /** - * - * @param {Locale} locale - */ - constructor(locale) { - super(); - - if (isString(locale)) { - locale = parseLocale(locale); - } - - this.locale = validateInstance(locale, Locale); - this.storage = new Map(); - - } - - - /** - * 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 handlig 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]); - } - - 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); - - for (const [k, v] of Object.entries(translations)) { - this.setText(k, v); - } - - return this; - - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from "../types/base.mjs"; -import {validateString} from "../types/validate.mjs"; -import {clone} from "../util/clone.mjs"; - -export {Locale, parseLocale} - -/** - * @memberOf Monster.I18n - * @type {symbol} - */ -const propertiesSymbol = Symbol('properties'); - -/** - * @type {symbol} - * @memberOf Monster.I18n - */ -const localeStringSymbol = Symbol('localeString'); - -/** - * The Locale class is a base class for the language classes. - * - * ``` - * <script type="module"> - * import {Locale} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/locale.mjs'; - * new Locale() - * </script> - * ``` - * - * RFC - * - * ``` - * A Language-Tag consists of: - * langtag ; generated tag - * -or- private-use ; a private use tag - * - * langtag = (language - * ["-" script] - * ["-" region] - * *("-" variant) - * *("-" extension) - * ["-" privateuse]) - * - * language = "en", "ale", or a registered value - * - * script = "Latn", "Cyrl", "Hant" ISO 15924 codes - * - * region = "US", "CS", "FR" ISO 3166 codes - * "419", "019", or UN M.49 codes - * - * variant = "rozaj", "nedis", "1996", multiple subtags can be used in a tag - * - * extension = single letter followed by additional subtags; more than one extension - * may be used in a language tag - * - * private-use = "x-" followed by additional subtags, as many as are required - * Note that these can start a tag or appear at the end (but not - * in the middle) - * ``` - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n - * @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(); - - 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); - - if (s.length === 0) { - throw new Error('unsupported locale'); - } - - this[localeStringSymbol] = s.join('-'); - - } - - /** - * @return {string} - */ - get localeString() { - return this[localeStringSymbol]; - } - - /** - * @return {string|undefined} - */ - get language() { - return this[propertiesSymbol].language; - } - - /** - * @return {string|undefined} - */ - get region() { - return this[propertiesSymbol].region; - } - - /** - * @return {string|undefined} - */ - get script() { - return this[propertiesSymbol].script; - } - - /** - * @return {string|undefined} - */ - get variants() { - return this[propertiesSymbol].variants; - } - - /** - * @return {string|undefined} - */ - get extlang() { - return this[propertiesSymbol].extlang; - } - - /** - * @return {string|undefined} - */ - get privateUse() { - return this[propertiesSymbol].privateValue; - } - - - /** - * @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]) - } - - -} - -/** - * @typedef {Object} LocaleMap - * @property {string} language - * @property {string} script - * @property {string} region - * @property {string} variants - * @property {string} extlang - * @property {string} privateUse - * @memberOf Monster.I18n - */ - -/** - * Parse local according to rfc4646 standard - * - * Limitations: The regex cannot handle multiple variants or private. - * - * You can call the method via the monster namespace `Monster.I18n.createLocale()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * new Monster.I18n.createLocale() - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {createLocale} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/locale.mjs'; - * createLocale() - * </script> - * ``` - * - * RFC - * - * ``` - * The syntax of the language tag in ABNF [RFC4234] is: - * - * Language-Tag = langtag - * / privateuse ; private use tag - * / grandfathered ; grandfathered registrations - * - * langtag = (language - * ["-" script] - * ["-" region] - * *("-" variant) - * *("-" extension) - * ["-" privateuse]) - * - * language = (2*3ALPHA [ extlang ]) ; shortest ISO 639 code - * / 4ALPHA ; reserved for future use - * / 5*8ALPHA ; registered language subtag - * - * extlang = *3("-" 3ALPHA) ; reserved for future use - * - * script = 4ALPHA ; ISO 15924 code - * - * region = 2ALPHA ; ISO 3166 code - * / 3DIGIT ; UN M.49 code - * - * variant = 5*8alphanum ; registered variants - * / (DIGIT 3alphanum) - * - * extension = singleton 1*("-" (2*8alphanum)) - * - * singleton = %x41-57 / %x59-5A / %x61-77 / %x79-7A / DIGIT - * ; "a"-"w" / "y"-"z" / "A"-"W" / "Y"-"Z" / "0"-"9" - * ; Single letters: x/X is reserved for private use - * - * privateuse = ("x"/"X") 1*("-" (1*8alphanum)) - * - * grandfathered = 1*3ALPHA 1*2("-" (2*8alphanum)) - * ; grandfathered registration - * ; Note: i is the only singleton - * ; that starts a grandfathered tag - * - * alphanum = (ALPHA / DIGIT) ; letters and numbers - * - * Figure 1: Language Tag ABNF - * ``` - * - * @param {string} locale - * @returns {Locale} - * @since 1.14.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n - * @throws {TypeError} value is not a string - * @throws {Error} unsupported locale - */ -function parseLocale(locale) { - - locale = validateString(locale).replace(/_/g, "-"); - - let language, region, variants, parts, script, extlang, - regexRegular = "(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)", - 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)", - regexGrandfathered = "(" + regexIrregular + "|" + regexRegular + ")", - regexPrivateUse = "(x(-[A-Za-z0-9]{1,8})+)", - regexSingleton = "[0-9A-WY-Za-wy-z]", - regexExtension = "(" + regexSingleton + "(-[A-Za-z0-9]{2,8})+)", - regexVariant = "([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})", - regexRegion = "([A-Za-z]{2}|[0-9]{3})", - regexScript = "([A-Za-z]{4})", - regexExtlang = "([A-Za-z]{3}(-[A-Za-z]{3}){0,2})", - regexLanguage = "(([A-Za-z]{2,3}(-" + regexExtlang + ")?)|[A-Za-z]{4}|[A-Za-z]{5,8})", - regexLangtag = "(" + regexLanguage + "(-" + regexScript + ")?" + "(-" + regexRegion + ")?" + "(-" + regexVariant + ")*" + "(-" + regexExtension + ")*" + "(-" + regexPrivateUse + ")?" + ")", - regexLanguageTag = "^(" + regexGrandfathered + "|" + regexLangtag + "|" + regexPrivateUse + ")$", - regex = new RegExp(regexLanguageTag), match; - - - 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[6] !== undefined) { - language = match[6]; - - parts = language.split('-'); - if (parts.length > 1) { - language = parts[0]; - extlang = parts[1]; - } - - } - - if (match[14] !== undefined) { - region = match[14]; - } - - if (match[12] !== undefined) { - script = match[12]; - } - - if (match[16] !== undefined) { - variants = match[16]; - } - - return new Locale(language, region, script, variants, extlang); - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {internalSymbol} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; - -import {Formatter as TextFormatter} from "../text/formatter.mjs"; -import {validateInstance, validateString} from "../types/validate.mjs"; -import {Translations} from "./translations.mjs"; - -export {Formatter} - -/** - * @private - * @type {symbol} - */ -const internalTranslationSymbol = Symbol('internalTranslation') - -/** - * The Formatter extends the Text.Formatter with the possibility to replace the key by a translation. - * - * ``` - * <script type="module"> - * import {Formatter} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/formatter.mjs'; - * new Formatter() - * </script> - * ``` - * - * @example - * - * import {Formatter} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/formatter.mjs'; - * import {Translations} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/translations.mjs'; - * - * const translations = new Translations('en') - * .assignTranslations({ - * thekey: "${animal} has eaten the ${food}!" - * }); - * - * new Formatter({}, translations).format("thekey:animal=dog::food=cake") - * // ↦ dog has eaten the cake! - * - * @since 1.26.0 - * @copyright schukai GmbH - * @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); - } - - /** - * @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) - - 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(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(); - - - let assembledText = openMarker + 'static:' + translationKey + ' | call:i18n'; - if (parameter.length > 0) { - assembledText += '::' + parameter; - } - assembledText += closeMarker; - return super.format(assembledText); - } - - -} -'use strict'; - -/** - * In this namespace you will find classes and methods for handling locale and localized texts. - * - * @namespace Monster.I18n.Providers - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../constants.mjs"; -import {extend} from "../../data/extend.mjs"; -import {Formatter} from "../../text/formatter.mjs"; -import {getGlobalFunction} from "../../types/global.mjs"; -import {isInstance, isString} from "../../types/is.mjs"; -import {validateObject, validateString} from "../../types/validate.mjs"; -import {parseLocale} from "../locale.mjs"; -import {Provider} from "../provider.mjs"; -import {Translations} from "../translations.mjs"; - -export {Fetch} - -/** - * The fetch provider retrieves a JSON file from the given URL and returns a translation object. - * - * ``` - * <script type="module"> - * import {Fetch} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/providers/fetch.mjs'; - * new Fetch() - * </script> - * ``` - * - * @example <caption>das ist ein test</caption> - * - * import {Fetch} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/providers/fetch.mjs'; - * - * // fetch from API - * const translation = new Fetch('https://example.com/${language}.json').getTranslation('en-GB'); - * // ↦ https://example.com/en.json - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n.Providers - * @see {@link https://datatracker.ietf.org/doc/html/rfc3066} - * @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); - - if (isInstance(url, URL)) { - url = url.toString(); - } - - if (options === undefined) { - options = {}; - } - - validateString(url); - - /** - * @property {string} - */ - this.url = url; - - /** - * @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 { - 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 - } - } - - } - - /** - * - * @param {Locale|string} locale - * @return {Promise} - */ - getTranslations(locale) { - - if (isString(locale)) { - locale = parseLocale(locale); - } - - 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); - }); - - } - - -} - -'use strict'; - -/** - * In this namespace you will find classes and methods for handling locale and localized texts. - * - * @namespace Monster.I18n - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {Locale} from "./locale.mjs" -import {Translations} from "./translations.mjs" - -export {Provider} - -/** - * A provider makes a translation object available. - * - * ``` - * <script type="module"> - * import {Provider} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/provider.mjs'; - * new Provider() - * </script> - * ``` - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n - * @see {@link https://datatracker.ietf.org/doc/html/rfc3066} - */ -class Provider extends BaseWithOptions { - - /** - * @param {Locale|string} locale - * @return {Promise} - */ - getTranslations(locale) { - return new Promise((resolve, reject) => { - try { - resolve(new Translations(locale)); - } catch (e) { - reject(e); - } - - }); - } - -} -'use strict'; - -/** - * Property-Keys - * @author schukai GmbH - */ - -export { - internalSymbol, - internalStateSymbol -} - - -/** - * @private - * @type {symbol} - * @memberOf Monster - * @since 1.24.0 - */ -const internalSymbol = Symbol('internalData'); - -/** - * @private - * @type {symbol} - * @memberOf Monster - * @since 1.25.0 - */ -const internalStateSymbol = Symbol('state'); - -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {Base} from "./base.mjs"; -import {isString} from "./is.mjs"; -import {validateArray, validateString} from "./validate.mjs"; - -export {MediaType, parseMediaType} - -/** - * @private - * @type {symbol} - */ -const internal = Symbol('internal'); - -/** - * @typedef {Object} Parameter - * @property {string} key - * @property {string} value - * @memberOf Monster.Types - */ - - -/** - * You can create an object via the monster namespace `new Monster.Types.MediaType()`. - * - * ``` - * <script type="module"> - * import {MediaType} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/mediatype.mjs'; - * console.log(new MediaType()) - * </script> - * ``` - * - * @since 1.8.0 - * @copyright schukai GmbH - * @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); - } - - - } - - /** - * @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(';') : ''); - } - -} - -/** - * You can call the function via the monster namespace `Monster.Types.parseMediaType()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * console.log(Monster.Types.parseMediaType()) - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {parseMediaType} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/dataurl.mjs'; - * console.log(parseMediaType()) - * </script> - * ``` - * - * Specification: - * - * ``` - * dataurl := "data:" [ mediatype ] [ ";base64" ] "," data - * mediatype := [ type "/" subtype ] *( ";" parameter ) - * data := *urlchar - * parameter := attribute "=" value - * ``` - * - * @param {String} mediatype - * @return {Monster.Types.MediaType} - * @see https://datatracker.ietf.org/doc/html/rfc2045#section-5.1 - * @throws {TypeError} the mimetype can not be parsed - * @throws {TypeError} blank value is not allowed - * @throws {TypeError} malformed data url - * @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 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']; - - if (subtype === "" || type === "") { - throw new TypeError('blank value is not allowed'); - } - - return new MediaType(type, subtype, parseParameter(parameter)); - - -} - -/** - * @private - * @since 1.18.0 - * @param {String} parameter - * @return {Monster.Types.Parameter[]|undefined} - * @memberOf Monster.Types - */ -function parseParameter(parameter) { - - if (!isString(parameter)) { - return undefined; - } - - let result = []; - - parameter.split(';').forEach((entry) => { - - entry = entry.trim(); - if (entry === "") { - return; - } - - const kv = entry.split('=') - - 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 - }) - - - }) - - return result; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -export {typeOf} - -/** - * The built-in typeof method is known to have some historical weaknesses. This function tries to provide a better and more accurate result. - * - * ``` - * <script type="module"> - * import {typeOf} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/typeof.mjs'; - * console.log(typeOf()) - * </script> - * ``` - * - * @example - * - * import {typeOf} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/typeof.mjs'; - * - * console.log(typeOf(undefined)); // ↦ undefined - * console.log(typeOf("")); // ↦ string - * console.log(typeOf(5)); // ↦ number - * console.log(typeOf({})); // ↦ object - * console.log(typeOf([])); // ↦ array - * console.log(typeOf(new Map)); // ↦ map - * console.log(typeOf(true)); // ↦ boolean - * - * @param {*} value - * @return {string} - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @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(); - } - - const results = (/^(class|function)\s+(\w+)/).exec(value.constructor.toString()); - type = (results && results.length > 2) ? results[2] : ''; - } - - return type.toLowerCase(); -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {Observer} from "./observer.mjs"; -import {validateInstance} from "./validate.mjs"; - -export {ObserverList} - -/** - * With the help of the ObserverList class, observer can be managed. - * - * ``` - * <script type="module"> - * import {ObserverList} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/observerlist.mjs'; - * console.log(ObserverList()) - * console.log(ObserverList()) - * </script> - * ``` - * - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -class ObserverList extends Base { - - /** - * - */ - constructor() { - super(); - this.observers = []; - } - - /** - * - * @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; - }; - - /** - * - * @param {Observer} observer - * @return {ObserverList} - * @throws {TypeError} value is not an instance of Observer - */ - detach(observer) { - validateInstance(observer, Observer) - - var i = 0, l = this.observers.length; - for (; i < l; i++) { - if (this.observers[i] === observer) { - this.observers.splice(i, 1); - } - } - - return this; - }; - - /** - * - * @param {Observer} observer - * @return {boolean} - * @throws {TypeError} value is not an instance of Observer - */ - contains(observer) { - validateInstance(observer, Observer) - var i = 0, 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 = [] - - let i = 0, l = this.observers.length; - for (; i < l; i++) { - pomises.push(this.observers[i].update(subject)); - } - - return Promise.all(pomises); - }; - -} -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {random} from "../math/random.mjs"; -import {getGlobal} from "./global.mjs"; -import {ID} from "./id.mjs"; - -export {RandomID} - -/** - * @private - * @type {number} - */ -let internalCounter = 0; - -/** - * The `RandomID` class provides a unique ID for an item. - * - * ``` - * <script type="module"> - * import {RandomID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/randomid.mjs'; - * console.log(new RandomID()) - * </script> - * ``` - * - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary class to generate random numbers - */ -class RandomID extends ID { - - /** - * create new object - */ - constructor() { - super(); - - 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; - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -import {random} from "../math/random.mjs"; -import {isObject} from '../types/is.mjs'; -import {Base} from "./base.mjs"; -import {getGlobalObject} from "./global.mjs"; - -export {UUID} - -/** - * The UUID class makes it possible to get a unique UUID for an object. - * - * ``` - * <script type="module"> - * import {Base} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/uuid.mjs'; - * new UUID() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {Error} unsupported - */ - class UUID extends Base { - - /** - * - */ - constructor() { - super(); - - let uuid = createWithCrypto(); - - if (uuid === undefined) { - uuid = createWithRandom(); - } - - - if (uuid === undefined) { - throw new Error('unsupported') - } - - this[internalSymbol] = { - value: uuid - } - - } - - /** - * - * @return {string} - */ - toString() { - return this[internalSymbol]['value']; - } - - -} - -/** - * @private - * @return {string|undefined} - */ -function createWithRandom() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = random(0, 65000) * 16 | 0, - v = ((c === 'x') ? r : (r & 0x3 | 0x8)); - return v.toString(16)[0]; - }) -} - - -/** - * @private - * @return {string|undefined} - */ -function createWithCrypto() { - const crypt = getGlobalObject('crypto'); - if (!isObject(crypt)) return; - if (typeof crypt?.['randomUUID']) return; - return crypt.randomUUID(); -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {isObject} from './is.mjs'; -import {TokenList} from './tokenlist.mjs'; -import {UniqueQueue} from './uniquequeue.mjs'; - -export {Observer} - -/** - * An observer manages a callback function - * - * ``` - * import {Observer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/observer.mjs'; - * new Observer() - * ``` - * - * The update method is called with the subject object as this pointer. For this reason the callback should not - * be an arrow function, because it gets the this pointer of its own context. - * - * ``` - * new Observer(()=>{ - * // this is not subject - * }) - * - * new Observer(function() { - * // this is subject - * }) - * ``` - * - * Additional arguments can be passed to the callback. To do this, simply specify them. - * - * ``` - * Observer(function(a, b, c) { - * console.log(a, b, c); // ↦ "a", 2, true - * }, "a", 2, true) - * ``` - * - * The callback function must have as many parameters as arguments are given. - * - * @example - * - * import {Observer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/observer.mjs'; - * - * const observer = new Observer(function(a, b, c) { - * console.log(this, a, b, c); // ↦ "a", 2, true - * }, "a", 2, true); - * - * observer.update({value:true}).then(()=>{}); - * // ↦ {value: true} "a" 2 true - * - * @since 1.0.0 - * @copyright schukai GmbH - * @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(); - } - - /** - * - * @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) - - }); - - }; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {isIterable, isString} from '../types/is.mjs'; -import {validateFunction, validateString} from '../types/validate.mjs'; -import {Base} from './base.mjs'; - -export {TokenList} - -/** - * A tokenlist allows you to manage tokens (individual character strings such as css classes in an attribute string). - * - * The tokenlist offers various functions to manipulate values. For example, you can add, remove or replace a class in a CSS list. - * - * ``` - * <script type="module"> - * import {TokenList} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/tokenlist.mjs'; - * new TokenList("myclass row") - * </script> - * ``` - * - * This class implements the [iteration protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols). - * - * ``` - * typeof new TokenList("myclass row")[Symbol.iterator]; - * // ↦ "function" - * ``` - * - * @since 1.2.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -class TokenList extends Base { - - /** - * - * @param {array|string|iteratable} init - */ - constructor(init) { - super(); - this.tokens = new Set(); - - if (typeof init !== "undefined") { - this.add(init); - } - - } - - /** - * 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() - - return { - next: () => { - if (index < entries.length) { - return {value: entries?.[index++], done: false} - } else { - return {done: true} - } - } - } - } - - /** - * Returns true if it contains token, otherwise false - * - * ``` - * new TokenList("start middle end").contains('start')); // ↦ true - * new TokenList("start middle end").contains('end')); // ↦ true - * new TokenList("start middle end").contains('xyz')); // ↦ false - * new TokenList("start middle end").contains(['end','start','middle'])); // ↦ true - * new TokenList("start middle end").contains(['end','start','xyz'])); // ↦ false - * ``` - * - * @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; - } - - return false; - } - - /** - * add tokens - * - * ``` - * new TokenList().add("abc xyz").toString(); // ↦ "abc xyz" - * new TokenList().add(["abc","xyz"]).toString(); // ↦ "abc xyz" - * new TokenList().add(undefined); // ↦ add nothing - * ``` - * - * @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; - } - - /** - * remove all tokens - * - * @returns {TokenList} - */ - clear() { - this.tokens.clear(); - return this; - } - - /** - * Removes token - * - * ``` - * new TokenList("abc xyz").remove("xyz").toString(); // ↦ "abc" - * new TokenList("abc xyz").remove(["xyz"]).toString(); // ↦ "abc" - * new TokenList("abc xyz").remove(undefined); // ↦ remove nothing - * ``` - * - * @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; - } - - /** - * 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; - - a.splice(i, 1, newToken); - this.tokens = new Set(); - this.add(a); - - return this; - - - } - - /** - * Removes token from string. If token doesn't exist it's added. - * - * ``` - * new TokenList("abc def ghi").toggle("def xyz").toString(); // ↦ "abc ghi xyz" - * new TokenList("abc def ghi").toggle(["abc","xyz"]).toString(); // ↦ "def ghi xyz" - * new TokenList().toggle(undefined); // ↦ nothing - * ``` - * - * @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; - - } - - /** - * 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; - } - - /** - * returns the individual tokens separated by a blank character - * - * @returns {string} - */ - toString() { - return this.entries().join(' '); - } - -} - -/** - * @private - * @param token - * @returns {toggleValue} - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; - -export {Queue} - -/** - * You can create the instance via the monster namespace `new Monster.Types.Queue()`. - * - * ``` - * <script type="module"> - * import {Queue} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/queue.mjs'; - * new Queue() - * </script> - * ``` - * - * @example - * - * import {Queue} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/queue.mjs'; - * - * const queue = new Queue; - * - * queue.add(2); - * queue.add(true); - * queue.add("Hello"); - * queue.add(4.5); - * - * console.log(queue.poll()); - * // ↦ 2 - * console.log(queue.poll()); - * // ↦ true - * console.log(queue.poll()); - * // ↦ "Hello" - * console.log(queue.poll()); - * // ↦ 4.5 - * console.log(queue.poll()); - * // ↦ undefined - * - * - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary A Queue (Fifo) - */ -class Queue extends Base { - - /** - * - */ - constructor() { - super(); - this.data = []; - } - - - /** - * @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; - } - - 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; - } - - /** - * 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(); - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; - -export {Stack} - -/** - * You can call the method via the monster namespace `new Monster.Types.Queue()`. - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/stack.mjs'; - * console.log(new Stack()) - * </script> - * ``` - * - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -class Stack extends Base { - - /** - * - */ - constructor() { - super(); - this.data = []; - } - - - /** - * @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; - } - - 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; - } - - /** - * 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(); - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ -import {validateString} from "./validate.mjs"; - -export {toBinary, fromBinary} - -/** - * You can call the function via the monster namespace `Monster.Types.toBinary()`. - * - * ``` - * <script type="module"> - * import {toBinary} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/binary.mjs'; - * toBinary() - * </script> - * ``` - * - * @param {String} binary - * @return {String} - * @throws {TypeError} value is not a string - * @memberOf Monster.Types - * @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 charCodes = new Uint8Array(codeUnits.buffer); - let result = ''; - - for (let i = 0; i < charCodes.byteLength; i++) { - result += String.fromCharCode(charCodes[i]); - } - - return result; -} - -/** - * You can call the function via the monster namespace `Monster.Types.fromBinary()`. - * - * ``` - * <script type="module"> - * import {fromBinary} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/binary.mjs'; - * fromBinary() - * </script> - * ``` - * - * @param {String} binary - * @return {String} - * @throws {TypeError} value is not a string - * @memberOf Monster.Types - * @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; -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; -import {Pathfinder} from "../data/pathfinder.mjs"; - -import {Base} from "./base.mjs"; -import {validateObject} from "./validate.mjs"; - -export {BaseWithOptions} - -/** - * This is the base class with options from which some monster classes are derived. - * - * This class is actually only used as a base class. - * - * ```html - * <script type="module"> - * import {BaseWithOptions} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/basewithoptions.mjs'; - * new BaseWithOptions() - * </script> - * ``` - * - * Classes that require the possibility of options can be derived directly from this class. - * Derived classes almost always override the `defaul` getter with their own values. - * - * ```javascript - * class My extends BaseWithOptions { - * get defaults() { - * return Object.assign({}, super.defaults, { - * mykey: true - * }); - * } - * } - * ``` - * - * The class was formerly called Object. - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -class BaseWithOptions extends Base { - - /** - * - * @param {object} options - */ - constructor(options) { - super(); - - if (options === undefined) { - 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 {} - } - - /** - * 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) { - - } - - if (value === undefined) return defaultValue; - return value; - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {validateString} from "./validate.mjs"; - -export {escapeString} - -/** - * This function prefixes all special characters that may appear in a regex with a slash. - * - * ``` - * <script type="module"> - * import {escapeString} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * escapeString() - * </script> - * ``` - * - * @param {string} value - * @return {string} - * @since 1.26.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a string - */ -function escapeString(value) { - return validateString(value) - .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') - .replace(/-/g, '\\x2d'); -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {isArray, isObject, isPrimitive} from "./is.mjs"; -import {Observer} from "./observer.mjs"; -import {ObserverList} from "./observerlist.mjs"; -import {validateObject} from "./validate.mjs"; -import {extend} from "../data/extend.mjs"; - -export {ProxyObserver} - -/** - * An observer manages a callback function - * - * ``` - * <script type="module"> - * import {ProxyObserver} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/proxyobserver.mjs'; - * new ProxyObserver() - * </script> - * ``` - * - * With the ProxyObserver you can attach observer for observation. with each change at the object to be observed an update takes place. - * - * this also applies to nested objects. - * - * @example - * - * import {ProxyObserver} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/proxyobserver.mjs'; - * import {Observer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/observer.mjs'; - * import {isObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * - * const o = new Observer(function () { - * if (isObject(this) && this instanceof ProxyObserver) { - * // do something (this ist ProxyObserver) - * const subject = this.getSubject(); - * console.log(subject); - * } - * }); - * - * let realSubject = { - * a: { - * b: { - * c: true - * }, - * d: 9 - * } - * } - * - * const p = new ProxyObserver(realSubject); - * p.attachObserver(o); - * const s = p.getSubject(); - * s.a.b.c = false; - * - * @since 1.0.0 - * @copyright schukai GmbH - * @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; - } - - /** - * 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} - */ - getSubject() { - return this.subject - } - - /** - * @since 1.24.0 - * @param {Object} obj - * @return {Monster.Types.ProxyObserver} - */ - setSubject(obj) { - - let i, 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 proxied object - * - * @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) - } - -} - -/** - * - * @returns {{defineProperty: (function(*=, *=, *=): *), setPrototypeOf: (function(*, *=): boolean), set: (function(*, *, *, *): boolean), get: ((function(*=, *=, *=): (undefined))|*), deleteProperty: ((function(*, *): (boolean))|*)}} - * @private - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {isArray, isInstance} from "./is.mjs"; -import {Node} from "./node.mjs"; -import {validateInstance} from "./validate.mjs"; - -export {NodeList} - -/** - * You can create the instance via the monster namespace `new Monster.Types.NodeList()`. - * - * ``` - * <script type="module"> - * import {NodeList} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/nodelist.mjs'; - * new NodeList() - * </script> - * ``` - * - * @since 1.26.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @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'); - } - } - - /** - * - * @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)); - return false; - } - - /** - * @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; - } -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; - -export {Version, getMonsterVersion} - -/** - * The version object contains a sematic version number - * - * ``` - * <script type="module"> - * import {Version} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/version.mjs'; - * console.log(new Version('1.2.3')) // ↦ 1.2.3 - * console.log(new Version('1')) // ↦ 1.0.0 - * </script> - * ``` - * - * @example - * - * import {Version} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/version.mjs'; - * - * new Version('1.0.0') // ↦ 1.0.0 - * new Version(1) // ↦ 1.0.0 - * new Version(1, 0, 0) // ↦ 1.0.0 - * new Version('1.2.3', 4, 5) // ↦ 1.4.5 - * - * @since 1.0.0 - * @author schukai GmbH - * @copyright schukai GmbH - * @memberOf Monster.Types - * @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"); - } - - } - - /** - * - * @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 - * then 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; - -/** - * Version of monster - * - * You can call the method via the monster namespace `Monster.getVersion()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * console.log(Monster.getVersion()) - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {getMonsterVersion} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/version.mjs'; - * console.log(getMonsterVersion()) - * </script> - * ``` - * - * @returns {Monster.Types.Version} - * @since 1.0.0 - * @copyright schukai GmbH - * @author schukai GmbH - * @memberOf Monster - */ -function getMonsterVersion() { - if (monsterVersion instanceof Version) { - return monsterVersion; - } - /**#@+ dont touch, replaced by make with package.json version */ - monsterVersion = new Version('1.31.0') - /**#@-*/ - - return monsterVersion; - -} -'use strict'; - -/** - * Namespace for types. - * - * @namespace Monster.Types - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {}; - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {isPrimitive} from "./is.mjs"; -import {NodeList} from './nodelist.mjs'; -import {validateInstance} from './validate.mjs'; - -export {Node} - -/** - * @private - * @type {symbol} - */ -const internalValueSymbol = Symbol('internalData'); - -/** - * @private - * @type {symbol} - */ -const treeStructureSymbol = Symbol('treeStructure'); - - -/** - * You can create the instance via the monster namespace `new Monster.Types.Node()`. - * - * ``` - * <script type="module"> - * import {Node} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/node.mjs'; - * new Node() - * </script> - * ``` - * - * @since 1.26.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary A Node Class - * @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 - } - - } - - /** - * @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, - 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"); - } - -} - -/** - * @private - * @param {Node} node - * @param {int} operand - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; - -import {Base} from './base.mjs'; -import {isInstance} from "./is.mjs"; -import {Node} from "./node.mjs"; -import {NodeList} from "./nodelist.mjs"; -import {validateInstance} from "./validate.mjs"; - -export {NodeRecursiveIterator} - -/** - * @private - * @type {symbol} - */ -const isNodeListSymbol = Symbol('isNodeList'); - -/** - * You can create the instance via the monster namespace `new Monster.Types.NodeRecursiveIterator()`. - * - * ``` - * <script type="module"> - * import {NodeRecursiveIterator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/noderecursiveiterator.mjs'; - * new NodeRecursiveIterator() - * </script> - * ``` - * - * @example - * import {Node} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/node.mjs'; - * import {NodeRecursiveIterator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/noderecursiveiterator.mjs'; - * - * const node =new Node('1') - * - * // 1 - * // 2 - * // ├ 2.1 - * // ├ 2.2 - * // └ 2.3 - * // 3 - * // 4 - * // ├ 4.1 - * // └ 4.2 - * node.appendChild( - * (new Node('2')) - * .appendChild(new Node('2.1')) - * .appendChild(new Node('2.2')) - * .appendChild(new Node('2.3'))) - * .appendChild(new Node('3')) - * .appendChild(new Node('4') - * .appendChild(new Node('4.1')) - * .appendChild(new Node('4.2'))); - * - * const iterator = new NodeRecursiveIterator(node); - * - * const result = []; - * for (const n of iterator) { - * result.push(n.value); - * } - * - * // ↦ ['1', '2', '2.1', '2.2', '2.3', '3', '4', '4.1', '4.2'] - * - * @since 1.26.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary An iterator to run recursively through a tree of nodes - */ - class NodeRecursiveIterator extends Base { - - /** - * @param {Node} [data] - */ - constructor(node) { - super(); - - this[isNodeListSymbol] = false; - - // 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); - } - - /** - * @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]; - } - - if (this[internalSymbol].hasChildNodes()) { - let childNodes = this[internalSymbol].childNodes; - - for (let node of childNodes) { - yield* new NodeRecursiveIterator(node); - } - } - - return; - } - - /** - * @param {function} callback - * @return {Monster.Types.NodeRecursiveIterator} - */ - forEach(callback) { - for (const node of this) { - callback(node); - } - return this; - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {validateFunction, validateObject, validateString} from "./validate.mjs"; - -export {getGlobal, getGlobalObject, getGlobalFunction} - -/** - * @type {objec} - * @private - */ -let globalReference; - -/** - * @private - * @throws {Error} unsupported environment. - */ -(function () { - - if (typeof globalThis === 'object') { - globalReference = globalThis; - 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 - }); - - if (typeof __monster__ === 'object') { - __monster__.globalThis = __monster__; - delete Object.prototype.__monster__; - - globalReference = globalThis; - return; - } - - try { - globalReference = Function('return this')(); - } catch (e) { - - } - - throw new Error("unsupported environment.") - - -}()); - -/** - * Return globalThis - * - * If globalThis is not available, it will be polyfilled - * - * @since 1.6.0 - * @memberOf Monster.Types - * @returns {objec} globalThis - */ -function getGlobal() { - return globalReference; -} - -/** - * Return global object or throw Error - * - * You can call the method via the monster namespace `Monster.Types.getGlobalObject()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.30.0/dist/monster.mjs'; - * Monster.Types.getGlobalObject('document') - * // ↦ { } - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {getGlobalObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.30.0/dist/modules/types/global.mjs'; - * getGlobalObject('document') - * // ↦ { } - * </script> - * ``` - * - * @since 1.6.0 - * @memberOf Monster.Types - * @param {string} name - * @returns {objec} - * @throws {Error} the object is not defined - * @throws {TypeError} value is not a object - * @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; -} - -/** - * Return global function or throw Error - * - * You can call the method via the monster namespace `Monster.Types.getGlobalFunction()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.30.0/dist/monster.mjs'; - * console.log(Monster.Types.getGlobalFunction('parseInt')) // ↦ f parseInt() { } - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {getGlobalFunction} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.30.0/dist/modules/types/global.mjs'; - * console.log(getGlobalFunction('parseInt')) // ↦ f parseInt() { } - * </script> - * ``` - * - * @since 1.6.0 - * @memberOf Monster.Types - * @param {string} name - * @return {objec} - * @throws {TypeError} value is not a function - * @throws {Error} the function is not defined - * @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; -} - - - - -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {Base} from "./base.mjs"; -import {isString} from "./is.mjs"; -import {MediaType, parseMediaType} from "./mediatype.mjs"; -import {validateBoolean, validateInstance, validateString} from "./validate.mjs"; - -export {DataUrl, parseDataURL} - -/** - * @private - * @type {symbol} - */ -const internal = Symbol('internal'); - - -/** - * You can create an object via the monster namespace `new Monster.Types.DataUrl()`. - * - * ``` - * <script type="module"> - * import {DataUrl} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/dataurl.mjs'; - * new DataUrl() - * </script> - * ``` - * - * @since 1.8.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs - * @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) - } - - - } - - 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; - } - -} - -/** - * You can call the function via the monster namespace `Monster.Types.parseDataURL()`. - * - * ``` - * <script type="module"> - * import {parseDataURL} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/dataurl.mjs'; - * parseDataURL(...) - * </script> - * ``` - * - * Specification: - * - * ``` - * dataurl := "data:" [ mediatype ] [ ";base64" ] "," data - * mediatype := [ type "/" subtype ] *( ";" parameter ) - * data := *urlchar - * parameter := attribute "=" value - * ``` - * - * @param {String} dataurl - * @return {Monster.Types.DataUrl} - * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs - * @see https://datatracker.ietf.org/doc/html/rfc2397 - * @throws {TypeError} incorrect or missing data protocol - * @throws {TypeError} malformed data url - * @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); - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import { - isArray, - isBoolean, - isFunction, - isInstance, - isInteger, - isIterable, - isObject, - isPrimitive, - isString, - isSymbol -} from './is.mjs'; - -export { - validateIterable, - validatePrimitive, - validateBoolean, - validateString, - validateObject, - validateInstance, - validateArray, - validateSymbol, - validateFunction, - validateInteger -} - -/** - * This method checks if the type matches the primitive type. this function is identical to isPrimitive() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateIterable} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateIterable('2')) // ↦ TypeError - * console.log(validateIterable([])) // ↦ value - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.2.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a primitive - * @see {@link isPrimitive} - * @see {@link Monster.Types.isPrimitive} - * @see {@link Monster.Types#isPrimitive} - */ -function validateIterable(value) { - if (!isIterable(value)) { - throw new TypeError('value is not iterable') - } - return value -} - -/** - * This method checks if the type matches the primitive type. this function is identical to isPrimitive() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validatePrimitive} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validatePrimitive('2')) // ↦ value - * console.log(validatePrimitive([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a primitive - * @see {@link isPrimitive} - * @see {@link Monster.Types.isPrimitive} - * @see {@link Monster.Types#isPrimitive} - */ -function validatePrimitive(value) { - if (!isPrimitive(value)) { - throw new TypeError('value is not a primitive') - } - return value -} - -/** - * This method checks if the type matches the boolean type. this function is identical to isBoolean() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateBoolean} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateBoolean(false)) // ↦ value - * console.log(validateBoolean('2')) // ↦ TypeError - * console.log(validateBoolean([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - - * @throws {TypeError} value is not primitive - */ -function validateBoolean(value) { - if (!isBoolean(value)) { - throw new TypeError('value is not a boolean') - } - return value -} - -/** - * This method checks if the type matches the string type. this function is identical to isString() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateString} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateString('2')) // ↦ value - * console.log(validateString([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a string - */ -function validateString(value) { - if (!isString(value)) { - throw new TypeError('value is not a string') - } - return value -} - - -/** - * This method checks if the type matches the object type. this function is identical to isObject() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateObject({})) // ↦ value - * console.log(validateObject('2')) // ↦ TypeError - * console.log(validateObject([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a object - */ -function validateObject(value) { - if (!isObject(value)) { - throw new TypeError('value is not a object') - } - return value -} - -/** - * This method checks if the type matches the object instance. - * - * ``` - * <script type="module"> - * import {validateInstance} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateInstance({}, Object)) // ↦ value - * console.log(validateInstance('2', Object)) // ↦ TypeError - * console.log(validateInstance([], Object)) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @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 (n) { - n = " " + n; - } - - throw new TypeError('value is not an instance of' + n) - } - return value -} - -/** - * This method checks if the type matches the array type. this function is identical to isArray() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateArray} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateArray('2')) // ↦ TypeError - * console.log(validateArray([])) // ↦ value - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not an array - */ -function validateArray(value) { - if (!isArray(value)) { - throw new TypeError('value is not an array') - } - return value -} - -/** - * This method checks if the type matches the symbol type. this function is identical to isSymbol() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateSymbol} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateSymbol('2')) // ↦ TypeError - * console.log(validateSymbol()) // ↦ value - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not an symbol - */ -function validateSymbol(value) { - if (!isSymbol(value)) { - throw new TypeError('value is not an symbol') - } - return value -} - -/** - * This method checks if the type matches the function type. this function is identical to isFunction() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateFunction} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateFunction(()=>{})) // ↦ value - * console.log(validateFunction('2')) // ↦ TypeError - * console.log(validateFunction([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a function - */ -function validateFunction(value) { - if (!isFunction(value)) { - throw new TypeError('value is not a function') - } - return value -} - -/** - * This method checks if the type is an integer. this function is identical to isInteger() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateFunction} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateInteger(true)) // ↦ TypeError - * console.log(validateInteger('2')) // ↦ TypeError - * console.log(validateInteger(2)) // ↦ value - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not an integer - */ -function validateInteger(value) { - if (!isInteger(value)) { - throw new TypeError('value is not an integer') - } - return value -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Queue} from "./queue.mjs"; -import {validateObject} from "./validate.mjs"; - -export {UniqueQueue} - -/** - * A UniqueQueue is a queue that contains items only once. - * - * ``` - * <script type="module"> - * import {UniqueQueue} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/uniquequeue.mjs'; - * new UniqueQueue() - * </script> - * ``` - * - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary A queue for unique values - */ - class UniqueQueue extends Queue { - - /** - * - */ - constructor() { - super(); - this.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); - - if (!this.unique.has(value)) { - this.unique.add(value); - super.add(value); - } - - return this; - } - - /** - * remove all entries - * - * @returns {Queue} - */ - clear() { - super.clear(); - this.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.unique.delete(value); - return value; - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -export {Base} - -/** - * This is the base class from which all monster classes are derived. - * - * ``` - * <script type="module"> - * import {Base} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/base.mjs'; - * new Base() - * </script> - * ``` - * - * The class was formerly called Object. - * - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ - class Base extends Object { - - /** - * - * @returns {string} - */ - toString() { - return JSON.stringify(this); - }; - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -export {isIterable, isPrimitive, isSymbol, isBoolean, isString, isObject, isInstance, isArray, isFunction, isInteger} - - -/** - * With this function you can check if a value is iterable. - * - * This method is used in the library to have consistent names. - * - * You can call the method via the monster namespace `Monster.Types.isPrimitive()`. - * - * ``` - * <script type="module"> - * import {isIterable} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isIterable(null) // ↦ false - * isIterable('hello') // ↦ true - * isIterable([]) // ↦ true - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.2.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isIterable(value) { - if (value === undefined) return false; - if (value === null) return false; - return typeof value?.[Symbol.iterator] === 'function'; -} - - -/** - * Checks whether the value passed is a primitive (string, number, boolean, NaN, undefined, null or symbol) - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isPrimitive} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isPrimitive('2')) // ↦ true - * isPrimitive([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isPrimitive(value) { - var type; - - if (value === undefined || value === null) { - return true; - } - - type = typeof value; - - if (type === 'string' || type === 'number' || type === 'boolean' || type === 'symbol') { - return true; - } - - return false; -} - -/** - * Checks whether the value passed is a symbol - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isSymbol} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isSymbol(Symbol('a'))) // ↦ true - * isSymbol([]) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isSymbol(value) { - return ('symbol' === typeof value) ? true : false; -} - -/** - * Checks whether the value passed is a boolean. - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isBoolean} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isBoolean('2')) // ↦ false - * isBoolean([])) // ↦ false - * isBoolean(2>4)) // ↦ true - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isBoolean(value) { - - if (value === true || value === false) { - return true; - } - - return false; -} - -/** - * Checks whether the value passed is a string - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isString} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isString('2')) // ↦ true - * isString([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isString(value) { - if (value === undefined || typeof value !== 'string') { - return false; - } - return true; -} - -/** - * Checks whether the value passed is a object - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isObject('2')) // ↦ false - * isObject([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isObject(value) { - - if (isArray(value)) return false; - if (isPrimitive(value)) return false; - - if (typeof value === 'object') { - return true; - } - - return false; -} - -/** - * Checks whether the value passed is a object and instance of instance. - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isInstance} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isInstance('2')) // ↦ false - * isInstance([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @param {*} instance - * @returns {boolean} - * @since 1.5.0 - * @copyright schukai GmbH - * @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; -} - -/** - * Checks whether the value passed is a array - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isArray} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isArray('2')) // ↦ false - * isArray([])) // ↦ true - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @see https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray - */ -function isArray(value) { - return Array.isArray(value); -} - -/** - * Checks whether the value passed is a function - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isFunction} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isFunction(()=>{}) // ↦ true - * isFunction('2')) // ↦ false - * isFunction([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isFunction(value) { - if (isArray(value)) return false; - if (isPrimitive(value)) return false; - - if (typeof value === 'function') { - return true; - } - - return false; - -} - -/** - * Checks whether the value passed is an integer. - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isInteger} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isInteger(()=>{}) // ↦ true - * isInteger('2')) // ↦ false - * isInteger(2)) // ↦ true - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isInteger(value) { - return Number.isInteger(value); -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {validateString} from "./validate.mjs"; - -export {ID} - -/** - * @private - * @type {Map<string, integer>} - */ -let internalCounter = new Map; - -/** - * With the id class, sequences of ids can be created. for this purpose, an internal counter is incremented for each prefix. - * thus, the first id with the prefix `myid` will be `myid1` and the second id `myid2`. - * The ids are the same for every call, for example on a web page. - * - * So the ids can also be used for navigation. you just have to take care that the order stays the same. - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/id.mjs'; - * console.log(new ID()) - * </script> - * ``` - * - * As of version 1.6.0 there is the new RandomID. this ID class is continuous from now on. - * - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary Automatic generation of ids - */ - class ID extends Base { - - /** - * create new id with prefix - * - * @param {string} prefix - */ - constructor(prefix) { - super(); - - if (prefix === undefined) { - prefix = "id"; - } - - validateString(prefix); - - if (!internalCounter.has(prefix)) { - internalCounter.set(prefix, 1); - } - - let count = internalCounter.get(prefix); - this.id = prefix + count; - - internalCounter.set(prefix, ++count); - } - - /** - * @return {string} - */ - toString() { - return this.id; - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {ID} from "../types/id.mjs"; -import {isObject} from "../types/is.mjs"; -import {validateString} from "../types/validate.mjs"; - -export {trimSpaces} - -/** - * This special trim function allows to trim spaces that have been protected by a special escape character. - * - * ``` - * <script type="module"> - * import {trimSpaces} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/trimspaces.mjs'; - * trimSpaces(' hello \\ ') - * </script> - * ``` - * - * Hint: One stroke is escaped by the javascript interpreter, the second stroke escapes the stroke. - * - * ```text - * a\ b ↦ a b - * a\\ b ↦ a\ b - * ``` - * - * @since 1.24.0 - * @memberOf Monster.Util - * @copyright schukai GmbH - * @param {string} value - * @return {string} - * @throws {TypeError} value is not a string - */ - function trimSpaces(value) { - - validateString(value); - - let placeholder = new Map; - const regex = /((?<pattern>\\(?<char>.)){1})/mig; - - // 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; - } - - 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); - } - - } - - value = value.trim(); - placeholder.forEach((v, k) => { - value = value.replace(k, '\\' + v) - }) - - return value; - -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; - -import {Base} from "../types/base.mjs"; -import {isInteger} from "../types/is.mjs"; -import {validateFunction, validateInteger} from "../types/validate.mjs"; - -export {DeadMansSwitch} - -/** - * The dead man's switch allows to set a timer which can be reset again and again within a defined period of time. - * - * ``` - * <script type="module"> - * import {DeadMansSwitch} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/deadmansswitch.mjs'; - * new DeadMansSwitch(); - * </script> - * ``` - * - * @example - * import {DeadMansSwitch} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/deadmansswitch.mjs'; - * - * const deadmansswitch = new DeadMansSwitch(100, ()=>{ - * console.log('yeah!') - * // ↦ "yeah!" - * }) - * - * deadmansswitch.touch(); // from here wait again 100 ms - * deadmansswitch.touch(200); // from here wait 200 ms - * - * @copyright schukai GmbH - * @since 1.29.0 - * @memberOf Monster.Util - * @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(); - - init.call(this, validateInteger(delay), validateFunction(callback)); - } - - /** - * - * @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') - } - - clearTimeout(this[internalSymbol]['timer']); - - initCallback.call(this); - - return this; - } -} - -/** - * @private - */ -function initCallback() { - - const self = this; - - self[internalSymbol]['timer'] = setTimeout(() => { - self[internalSymbol]['isAlreadyRun'] = true; - self[internalSymbol]['callback'](); - }, self[internalSymbol]['delay']) -} - -/** - * @private - * @param {integer} delay - * @param {function} callback - */ -function init(delay, callback) { - const self = this; - - self[internalSymbol] = { - callback, - delay, - isAlreadyRun: false, - timer: undefined - }; - - initCallback.call(self); - -} - - - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {validateObject} from '../types/validate.mjs'; - -export {deepFreeze} - -/** - * Deep freeze a object - * - * ``` - * <script type="module"> - * import {deepFreeze} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/freeze.mjs'; - * deepFreeze({}) - * </script> - * ``` - * - * @param {object} object object to be freeze - * @since 1.0.0 - * @returns {object} - * @memberOf Monster.Util - * @copyright schukai GmbH - * @throws {TypeError} value is not a object - */ - function deepFreeze(object) { - - validateObject(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]; - - object[name] = (value && typeof value === "object") ? - deepFreeze(value) : value; - } - - return Object.freeze(object); -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {isFunction} from '../types/is.mjs'; - -export {Comparator} - -/** - * The comparator allows a comparison function to be abstracted. - * - * ``` - * <script type="module"> - * import {Comparator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/comparator.mjs'; - * console.log(new Comparator()) - * </script> - * ``` - * - * The following are some examples of the application of the class. - * - * ``` - * new Comparator().lessThanOrEqual(2, 5) // ↦ true - * new Comparator().greaterThan(4, 2) // ↦ true - * new Comparator().equal(4, 4) // ↦ true - * new Comparator().equal(4, 5) // ↦ false - * ``` - * - * You can also pass your own comparison function, and thus define the comparison function. - * - * ``` - * new Comparator(function (a, b) { - * if (a.v === b.v) return 0; - * return a.v < b.v ? -1 : 1; - * }).equal({v: 2}, {v: 2}); // ↦ true - * ``` - * - * @example - * - * import {Comparator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/comparator.mjs'; - * - * console.log(new Comparator().lessThanOrEqual(2, 5)) - * // ↦ true - * console.log(new Comparator().greaterThan(4, 2)) - * // ↦ true - * console.log(new Comparator().equal(4, 4)) - * // ↦ true - * console.log(new Comparator().equal(4, 5)) - * // ↦ false - * - * @since 1.3.0 - * @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(); - - 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") - } - - 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; - } - - /** - * 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 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 b - * - * @param {*} a - * @param {*} b - * - * @return {boolean} - */ - lessThan(a, b) { - return this.compare(a, b) < 0; - } - - -} - - -/** - * This is the description for the callback function used by the operator - * - * ``` - * new Comparator(function (a, b) { - * if (a.v === b.v) return 0; - * return a.v < b.v ? -1 : 1; - * }).equal({v: 2}, {v: 2}); // ↦ true - * ``` - * - * @callback Monster.Util~exampleCallback - * @param {*} a - * @param {*} b - * @return {integer} -1, 0 or 1 - * @memberOf Monster.Util - * @see Monster.Util.Comparator - */ - -'use strict'; - -/** - * Namespace for utilities. - * - * @namespace Monster.Util - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - - -import {getGlobal} from '../types/global.mjs'; -import {isArray, isFunction, isObject, isPrimitive} from '../types/is.mjs'; -import {typeOf} from "../types/typeof.mjs"; -import {validateObject} from "../types/validate.mjs"; - -export {clone} - -/** - * With this function, objects can be cloned. - * The entire object tree is run through. - * - * Proxy, Element, HTMLDocument and DocumentFragment instances are not cloned. - * Global objects such as windows are also not cloned, - * - * If an object has a method `getClone()`, this method is used to create the clone. - * - * ``` - * <script type="module"> - * import {clone} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/clone.mjs'; - * clone({}) - * </script> - * ``` - * - * @param {*} obj object to be cloned - * @returns {*} - * @since 1.0.0 - * @memberOf Monster.Util - * @copyright schukai GmbH - * @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."); -} - -/** - * - * @param {object} obj - * @returns {object} - * @private - */ -function cloneObject(obj) { - - validateObject(obj); - - const constructor = obj?.['constructor']; - - /** Object has clone method */ - if(typeOf(constructor)==='function') { - const prototype = constructor?.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; -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -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} - -/** - * @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) - }; - } - - /** - * @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); - - - }) - - } -} - -/** - * This class allows to execute several functions in order. - * - * 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 - * - * The result of `run()` is a promise. - * - * ``` - * <script type="module"> - * import {Processing} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/processing.mjs'; - * new Processing(); - * </script> - * ``` - * - * @example - * import {Processing} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/processing.mjs'; - * - * let startTime = +new Date(); - * - * new Processing((url)=>{ - * return fetch(url) - * },(response)=>{ - * // do something with the response - * console.log(response.status, +new Date()-startTime) - * },200,()=>{ - * // this function is called 200 seconds after fetch is received. - * console.log('finished', +new Date()-startTime) - * return 'done' - * }).run('https://monsterjs.org/assets/world.json').then(r=>{ - * console.log(r) - * // ↦ "done" - * }) - * - * @copyright schukai GmbH - * @since 1.21.0 - * @memberOf Monster.Util - * @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 - * - * @param {int} timeout Timeout - * @param {function} callback Callback - * @throw {TypeError} the arguments must be either integer or functions - */ - constructor() { - super(); - - this[internalSymbol] = { - queue: new Queue - }; - - let time = 0 - - for (const [, arg] of Object.entries(arguments)) { - 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; - } - - - /** - * Executes the defined functions in order. - * - * @param {*} data - * @return {Promise} - */ - run(data) { - const self = this; - if (this[internalSymbol].queue.isEmpty()) { - return Promise.resolve(data); - } - - return this[internalSymbol].queue.poll().run(data).then((result) => { - return self.run(result); - }); - - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {isArray, isObject} from "../types/is.mjs"; -import {typeOf} from "../types/typeof.mjs"; - -export {extend} - -/** - * Extend copies all enumerable own properties from one or - * more source objects to a target object. It returns the modified target object. - * - * ``` - * <script type="module"> - * import {extend} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/extend.mjs'; - * extend(a, b) - * </script> - * ``` - * - * @param {object} target - * @param {object} - * @return {object} - * @since 1.10.0 - * @copyright schukai GmbH - * @memberOf Monster.Data - * @throws {Error} unsupported argument - * @throws {Error} type mismatch - */ -function extend() { - let o, i; - - for (i = 0; i < arguments.length; i++) { - let a = arguments[i]; - - if (!(isObject(a) || isArray(a))) { - throw new Error('unsupported argument ' + JSON.stringify(a)); - } - - if (o === undefined) { - o = a; - continue; - } - - for (let k in a) { - - let v = a?.[k]; - - 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) + ")"); - } - } - - o[k] = extend(o[k], v); - - } else { - o[k] = v; - } - - } - } - - return o; -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {Base} from '../types/base.mjs'; -import {validateString} from '../types/validate.mjs'; -import {Transformer} from './transformer.mjs'; - -export {Pipe} - -const DELIMITER = '|'; - -/** - * The pipe class makes it possible to combine several processing steps. - * - * ``` - * <script type="module"> - * import {Pipe} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pipe.mjs'; - * new Pipe() - * </script> - * ``` - * - * A pipe consists of commands whose input and output are connected with the pipe symbol `|`. - * - * With the Pipe, processing steps can be combined. Here, the value of an object is accessed via the pathfinder (path command). - * the word is then converted to uppercase letters and a prefix Hello is added. the two backslash safe the space char. - * - * @example - * import {Pipe} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pipe.mjs'; - * - * let obj = { - * a: { - * b: { - * c: { - * d: "world" - * } - * } - * } - * } - * - * console.log(new Pipe('path:a.b.c.d | toupper | prefix:Hello\\ ').run(obj)); - * // ↦ Hello WORLD - * - * @since 1.5.0 - * @copyright schukai GmbH - * @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); - - 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); - } - - return this; - } - - /** - * run a pipe - * - * @param {*} value - * @returns {*} - */ - run(value) { - return this.pipe.reduce((accumulator, transformer, currentIndex, array) => { - return transformer.run(accumulator); - }, value); - } -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {isFunction, isObject, isString} from "../types/is.mjs"; -import {validateString} from "../types/validate.mjs"; -import {clone} from "../util/clone.mjs"; -import {DELIMITER, Pathfinder, WILDCARD} from "./pathfinder.mjs"; - -export {buildMap, PARENT, assembleParts} - -/** - * @type {string} - * @memberOf Monster.Data - */ -const PARENT = '^'; - - -/** - * With the help of the function `buildMap()`, maps can be easily created from data objects. - * - * Either a simple definition `a.b.c` or a template `${a.b.c}` can be specified as the path. - * Key and value can be either a definition or a template. The key does not have to be defined. - * - * ``` - * <script type="module"> - * import {buildMap} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/buildmap.mjs'; - * console.log(buildMap()) - * </script> - * ``` - * - * The templates determine the appearance of the keys and the value of the map. Either a single value `id` can be taken or a composite key `${id} ${name}` can be used. - * - * If you want to access values of the parent data set, you have to use the `^` character `${id} ${^.name}`. - * - * @example - * - * import {buildMap} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/buildmap.mjs'; - * // a typical data structure as reported by an api - * - * let map; - * let obj = { - * "data": [ - * { - * "id": 10, - * "name": "Cassandra", - * "address": { - * "street": "493-4105 Vulputate Street", - * "city": "Saumur", - * "zip": "52628" - * } - * }, - * { - * "id": 20, - * "name": "Holly", - * "address": { - * "street": "1762 Eget Rd.", - * "city": "Schwalbach", - * "zip": "952340" - * } - * }, - * { - * "id": 30, - * "name": "Guy", - * "address": { - * "street": "957-388 Sollicitudin Avenue", - * "city": "Panchià", - * "zip": "420729" - * } - * } - * ] - * }; - * - * // The function is passed this data structure and with the help of the selector `'data.*'` the data to be considered are selected. - * // The key is given by a simple definition `'id'` and the value is given by a template `'${name} (${address.zip} ${address.city})'`. - * map = buildMap(obj, 'data.*', '${name} (${address.zip} ${address.city})', 'id'); - * console.log(map); - * - * // ↦ Map(3) { - * // '10' => 'Cassandra (52628 Saumur)', - * // '20' => 'Holly (952340 Schwalbach)', - * // '30' => 'Guy (420729 Panchià)' - * // } - * - * // If no key is specified, the key from the selection, here the array index, is taken. - * map = buildMap(obj, 'data.*', '${name} (${address.zip} ${address.city})'); - * console.log(map); - * - * // ↦ Map(3) { - * // '0' => 'Cassandra (52628 Saumur)', - * // '1' => 'Holly (952340 Schwalbach)', - * // '2' => 'Guy (420729 Panchià)' - * // } - * - * // a filter (function(value, key) {}) can be specified to accept only defined entries. - * map = buildMap(obj, 'data.*', '${name} (${address.zip} ${address.city})', 'id', function (value, key) { - * return (value['id'] >= 20) ? true : false - * }); - * console.log(map); - * - * // ↦ Map(2) { - * // 20 => 'Holly (952340 Schwalbach)', - * // 30 => 'Guy (420729 Panchià)' - * // } - * - * @param {*} subject - * @param {string|Monster.Data~exampleSelectorCallback} selector - * @param {string} [valueTemplate] - * @param {string} [keyTemplate] - * @param {Monster.Data~exampleFilterCallback} [filter] - * @return {*} - * @memberOf Monster.Data - * @throws {TypeError} value is neither a string nor a function - * @throws {TypeError} the selector callback must 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); - }); - -} - - -/** - * @private - * @param {*} subject - * @param {string|Monster.Data~exampleSelectorCallback} selector - * @param {Monster.Data~exampleFilterCallback} [filter] - * @param {function} callback - * @return {Map} - * @throws {TypeError} selector is neither a string nor a function - */ -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; -} - -/** - * @private - * @param subject - * @param selector - * @param key - * @param parentMap - * @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 = "", 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; - -} - - -/** - * With the help of this filter callback, values can be filtered out. Only if the filter function returns true, the value is taken for the map. - * - * @callback Monster.Data~exampleFilterCallback - * @param {*} value Value - * @param {string} key Key - * @memberOf Monster.Data - * @see {@link Monster.Data.buildMap} - */ - -/** - * Alternatively to a string selector a callback can be specified. this must return a map. - * - * @example - * import {buildMap} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/buildmap.mjs'; - * - * let obj = { - * "data": [ - * { - * "id": 10, - * "name": "Cassandra", - * "enrichment": { - * variants: [ - * { - * sku: 1, label: "XXS", price: [ - * {vk: '12.12 €'}, - * {vk: '12.12 €'} - * ] - * }, - * { - * sku: 2, label: "XS", price: [ - * {vk: '22.12 €'}, - * {vk: '22.12 €'} - * ] - * }, - * { - * sku: 3, label: "S", price: [ - * {vk: '32.12 €'}, - * {vk: '32.12 €'} - * ] - * }, - * { - * sku: 4, label: "L", price: [ - * {vk: '42.12 €'}, - * {vk: '42.12 €'} - * ] - * } - * ] - * - * } - * }, - * { - * "id": 20, - * "name": "Yessey!", - * "enrichment": { - * variants: [ - * { - * sku: 1, label: "XXS", price: [ - * {vk: '12.12 €'}, - * {vk: '12.12 €'} - * ] - * }, - * { - * sku: 2, label: "XS", price: [ - * {vk: '22.12 €'}, - * {vk: '22.12 €'} - * ] - * }, - * { - * sku: 3, label: "S", price: [ - * {vk: '32.12 €'}, - * {vk: '32.12 €'} - * ] - * }, - * { - * sku: 4, label: "L", price: [ - * {vk: '42.12 €'}, - * {vk: '42.12 €'} - * ] - * } - * ] - * - * } - * } - * ] - * }; - * - * let callback = function (subject) { - * let m = new Map; - * - * for (const [i, b] of Object.entries(subject.data)) { - * - * let key1 = i; - * - * for (const [j, c] of Object.entries(b.enrichment.variants)) { - * let key2 = j; - * - * for (const [k, d] of Object.entries(c.price)) { - * - * let key3 = k; - * - * d.name = b.name; - * d.label = c.label; - * d.id = [key1, key2, key3].join('.'); - * - * m.set(d.id, d); - * } - * - * } - * } - * return m; - * } - * - * let map = buildMap(obj, callback, '${name} ${vk}', '${id}') - * - * // ↦ Map(3) { - * // "0.0.0":"Cassandra 12.12 €", - * // "0.0.1":"Cassandra 12.12 €", - * // "0.1.0":"Cassandra 22.12 €", - * // "0.1.1":"Cassandra 22.12 €", - * // "0.2.0":"Cassandra 32.12 €", - * // "0.2.1":"Cassandra 32.12 €", - * // "0.3.0":"Cassandra 42.12 €", - * // "0.3.1":"Cassandra 42.12 €", - * // "1.0.0":"Yessey! 12.12 €", - * // "1.0.1":"Yessey! 12.12 €", - * // "1.1.0":"Yessey! 22.12 €", - * // "1.1.1":"Yessey! 22.12 €", - * // "1.2.0":"Yessey! 32.12 €", - * // "1.2.1":"Yessey! 32.12 €", - * // "1.3.0":"Yessey! 42.12 €", - * // "1.3.1":"Yessey! 42.12 €" - * // } - * - * @callback Monster.Data~exampleSelectorCallback - * @param {*} subject subject - * @return Map - * @since 1.17.0 - * @memberOf Monster.Data - * @see {@link Monster.Data.buildMap} - */ - -/** - * @private - * @param {*} subject - * @param {string|undefined} definition - * @param {*} defaultValue - * @return {*} - */ -function build(subject, definition, defaultValue) { - 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)]; - - let finder = new Pathfinder(subject); - - if (array.length === 0) { - return finder.getVia(definition); - } - - array.forEach((a) => { - let groups = a?.['groups']; - let placeholder = groups?.['placeholder'] - if (placeholder === undefined) return; - - let path = groups?.['path'] - - let v = finder.getVia(path); - if (v === undefined) v = defaultValue; - - definition = definition.replaceAll(placeholder, v); - - - }) - - return definition; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; - -import {Base} from "../types/base.mjs"; -import {parseDataURL} from "../types/dataurl.mjs"; -import {isString} from "../types/is.mjs"; -import {ProxyObserver} from "../types/proxyobserver.mjs"; -import {validateObject} from "../types/validate.mjs"; -import {extend} from "./extend.mjs"; -import {Pathfinder} from "./pathfinder.mjs"; - -export {Datasource} - -/** - * @private - * @type {symbol} - * @memberOf Monster.Data - * @since 1.24.0 - */ -const internalDataSymbol = Symbol('internalData'); - -/** - * The datasource class is the basis for dealing with different data sources. - * It provides a unified interface for accessing data - * - * ``` - * <script type="module"> - * import {Datasource} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource.mjs'; - * new Datasource() - * </script> - * ``` - * - * @example - * - * import {Datasource} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource.mjs' - * - * class MyDatasource extends Datasource { - * - * } - * - * const ds = new MyDatasource(); - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data - * @summary The datasource class encapsulates the access to data objects. - */ -class Datasource extends Base { - - /** - * - */ - 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; - } - -} - -/** - * @private - * @param data - * @return {Object} - * @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 {}; -} -'use strict'; - -/** - * In this namespace you will find classes and methods for handling data. - * - * @namespace Monster.Data - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - - -import {isArray, isObject} from "../types/is.mjs"; -import {Node} from "../types/node.mjs"; -import {NodeList} from "../types/nodelist.mjs"; -import {assembleParts} from "./buildmap.mjs"; -import {extend} from "./extend.mjs"; - -export {buildTree} - -/** - * @private - * @type {symbol} - */ -const parentSymbol = Symbol('parent'); - -/** - * @private - * @type {symbol} - */ -const rootSymbol = Symbol('root'); - -/** - * @typedef {Object} buildTreeOptions - * @property {array} options.rootReferences=[null, undefined] defines the values for elements without parents - * @property {Monster.Data~exampleFilterCallback} options.filter filtering of the values - * @memberOf Monster.Data - */ - -/** - * With the help of the function `buildTree()`, nodes can be easily created from data objects. - * - * ``` - * <script type="module"> - * import {buildTree} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/buildtree.mjs'; - * buildTree() - * </script> - * ``` - * - * @param {*} subject - * @param {string|Monster.Data~exampleSelectorCallback} selector - * @param {string} idKey - * @param {string} parentIDKey - * @param {buildTreeOptions} [options] - * @return {*} - * @memberOf Monster.Data - * @throws {TypeError} value is neither a string nor a function - * @throws {TypeError} the selector callback must return a map - * @throws {Error} the object has no value for the specified id - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {getGlobal, getGlobalObject} from "../types/global.mjs"; -import {ID} from '../types/id.mjs'; -import {isArray, isObject, isString} from '../types/is.mjs'; -import { - validateFunction, - validateInteger, - validateObject, - validatePrimitive, - validateString -} from '../types/validate.mjs'; -import {clone} from "../util/clone.mjs"; -import {Pathfinder} from "./pathfinder.mjs"; - -export {Transformer} - -/** - * The transformer class is a swiss army knife for manipulating values. especially in combination with the pipe, processing chains can be built up. - * - * ``` - * <script type="module"> - * import {Transformer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/transformer.mjs'; - * new Transformer() - * </script> - * ``` - * - * A simple example is the conversion of all characters to lowercase. for this purpose the command `tolower` must be used. - * - * ``` - * let t = new Transformer('tolower').run('ABC'); // ↦ abc - * ``` - * - * **all commands** - * - * in the following table all commands, parameters and existing aliases are described. - * - * | command | parameter | alias | description | - * |:-------------|:---------------------------|:------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| - * | to-base64 | | base64, btob | Converts the value to base64 | - * | from-base64 | | atob | Converts the value from base64 | - * | call | function:param1:param2:... | | Calling a callback function. The function can be defined in three places: either globally, in the context `addCallback` or in the passed object | - * | default | value:type | ?? | If the value is undefined the first argument is returned, otherwise the value. The third optional parameter specifies the desired type. If no type is specified, string is used. Valid types are bool, string, int, float, undefined and object. An object default value must be specified as a base64 encoded json string. (since 1.12.0) | - * | debug | | | the passed value is output (console) and returned | - * | empty | | | Return empty String "" | - * | first-key | default | | Can be applied to objects and returns the value of the first key. All keys of the object are fetched and sorted. (since 1.23.0) | - * | fromjson | | | Type conversion from a JSON string (since 1.12.0) | - * | if | statement1:statement2 | ? | Is the ternary operator, the first parameter is the valid statement, the second is the false part. To use the current value in the queue, you can set the value keyword. On the other hand, if you want to have the static string "value", you have to put one backslash \\ in front of it and write value. the follow values are true: 'on', true, 'true'. If you want to have a space, you also have to write \\ in front of the space. | - * | index | key:default | property, key | Fetches a value from an object, an array, a map or a set | - * | last-key | default | | Can be applied to objects and returns the value of the last key. All keys of the object are fetched and sorted. (since 1.23.0) | - * | length | | count | Length of the string or entries of an array or object | - * | nop | | | Do nothing | - * | nth-key | index:default | | Can be applied to objects and returns the value of the nth key. All keys of the object are fetched and sorted. (since 1.23.0) | - * | nth-last-key | index:default | | Can be applied to objects and returns the value of the nth key from behind. All keys of the object are fetched and sorted. (since 1.23.0) | - * | path | path | | The access to an object is done via a Pathfinder object | - * | path-exists | path | | Check if the specified path is available in the value (since 1.24.0) | - * | plaintext | | plain | All HTML tags are removed (*) | - * | prefix | text | | Adds a prefix | - * | rawurlencode | | | URL coding | - * | static | | none | The Arguments value is used and passed to the value. Special characters \ <space> and : can be quotet by a preceding \. | - * | substring | start:length | | Returns a substring | - * | suffix | text | | Adds a suffix | - * | tointeger | | | Type conversion to an integer value | - * | tojson | | | Type conversion to a JSON string (since 1.8.0) | - * | tolower | | strtolower, tolowercase | The input value is converted to lowercase letters | - * | tostring | | | Type conversion to a string. | - * | toupper | | strtoupper, touppercase | The input value is converted to uppercase letters | - * | trim | | | Remove spaces at the beginning and end | - * | ucfirst | | | First character large | - * | ucwords | | | Any word beginning large | - * | undefined | | | Return undefined | - * | uniqid | | | Creates a string with a unique value (**) - * - * (*) for this functionality the extension [jsdom](https://www.npmjs.com/package/jsdom) must be loaded in the nodejs context. - * - * ``` - * // polyfill - * if (typeof window !== "object") { - * const {window} = new JSDOM('', { - * url: 'http://example.com/', - * pretendToBeVisual: true - * }); - * - * [ - * 'self', - * 'document', - * 'Node', - * 'Element', - * 'HTMLElement', - * 'DocumentFragment', - * 'DOMParser', - * 'XMLSerializer', - * 'NodeFilter', - * 'InputEvent', - * 'CustomEvent' - * ].forEach(key => (global[key] = window[key])); - * } - * ``` - * - * (**) for this command the crypt library is necessary in the nodejs context. - * - * ``` - * import * as Crypto from "@peculiar/webcrypto"; - * global['crypto'] = new Crypto.Crypto(); - * ``` - * - * @example - * - * import {Transformer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/transformer.mjs'; - * - * const transformer = new Transformer("tolower") - * - * console.log(transformer.run("HELLO")) - * // ↦ hello - * - * console.log(transformer.run("WORLD")) - * // ↦ world - * - * @since 1.5.0 - * @copyright schukai GmbH - * @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} command - * @returns {array} - * @private - */ -function disassemble(command) { - - validateString(command); - - let placeholder = new Map; - const regex = /((?<pattern>\\(?<char>.)){1})/mig; - - // 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 -} - -/** - * tries to make a string out of value and if this succeeds to return it back - * - * @param {*} value - * @returns {string} - * @private - */ -function convertToString(value) { - - if (isObject(value) && value.hasOwnProperty('toString')) { - value = value.toString(); - } - - validateString(value) - return value; -} - -/** - * - * @param {*} value - * @returns {*} - * @private - * @throws {Error} unknown command - * @throws {TypeError} unsupported type - * @throws {Error} type not supported - * @throws {Error} missing key parameter - */ -function transform(value) { - - const console = getGlobalObject('console'); - - let args = clone(this.args); - let key, defaultValue; - - switch (this.command) { - - case 'static': - return this.args.join(':'); - - case 'tolower': - case 'strtolower': - case 'tolowercase': - validateString(value) - return value.toLowerCase(); - - 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 'tojson': - return JSON.stringify(value); - - 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 (falseStatement === 'value') { - falseStatement = value; - } - if (falseStatement === '\\value') { - falseStatement = 'value'; - } - - 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 '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") - - - default: - throw new Error("unknown command " + this.command) - } - - return value; -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {isArray, isInteger, isObject, isPrimitive} from '../types/is.mjs'; -import {Stack} from "../types/stack.mjs"; -import {validateInteger, validateString} from '../types/validate.mjs'; - -export {Pathfinder, DELIMITER, WILDCARD} - -/** - * path separator - * - * @private - * @type {string} - */ -const DELIMITER = '.'; - -/** - * @private - * @type {string} - */ -const WILDCARD = '*'; - -/** - * Pathfinder is a class to find a path to an object. - * - * ``` - * <script type="module"> - * import {Pathfinder} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pathfinder.mjs'; - * console.log(new Pathfinder()) - * </script> - * ``` - * - * With the help of the pathfinder, values can be read and written from an object construct. - * - * ``` - * new Pathfinder({ - * a: { - * b: { - * f: [ - * { - * g: false, - * } - * ], - * } - * } - * }).getVia("a.b.f.0.g"); // ↦ false - * ``` - * - * if a value is not present or has the wrong type, a corresponding exception is thrown. - * - * ``` - * new Pathfinder({}).getVia("a.b.f.0.g"); // ↦ Error - * ``` - * - * The `Pathfinder.exists()` method can be used to check whether access to the path is possible. - * - * ``` - * new Pathfinder({}).exists("a.b.f.0.g"); // ↦ false - * ``` - * - * pathfinder can also be used to build object structures. to do this, the `Pathfinder.setVia()` method must be used. - * - * ``` - * obj = {}; - * new Pathfinder(obj).setVia('a.b.0.c', true); // ↦ {a:{b:[{c:true}]}} - * ``` - * - * @example - * - * import {Pathfinder} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pathfinder.mjs'; - * - * let value = new Pathfinder({ - * a: { - * b: { - * f: [ - * { - * g: false, - * } - * ], - * } - * } - * }).getVia("a.b.f.0.g"); - * - * console.log(value); - * // ↦ false - * - * try { - * new Pathfinder({}).getVia("a.b.f.0.g"); - * } catch(e) { - * console.log(e.toString()); - * // ↦ Error: the journey is not at its end (b.f.0.g) - * } - * - * @example - * - * import {Pathfinder} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pathfinder.mjs'; - * - * let p = new Pathfinder({ - * a: { - * x: [ - * {c: 1}, {c: 2} - * ], - * y: true - * }, - * b: { - * x: [ - * {c: 1, d: false}, {c: 2} - * ], - * y: true - * }, - * }); - * - * let r = p.getVia("*.x.*.c"); - * console.log(r); - * - * @since 1.4.0 - * @copyright schukai GmbH - * @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} 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, validateString(path)); - } - - /** - * - * @param {string} 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) { - validateString(path); - setValueViaPath.call(this, this.object, path, value); - return this; - } - - /** - * Delete Via Path - * - * @param {string} 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) { - validateString(path); - deleteValueViaPath.call(this, this.object, path); - return this; - } - - /** - * - * @param {string} 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) { - validateString(path); - try { - getValueViaPath.call(this, this.object, path, true); - return true; - } catch (e) { - - } - - return false; - } - -} - - -/** - * - * @param {*} subject - * @param {string} path - * @param {string} check - * @return {Map} - * @throws {TypeError} unsupported type - * @throws {Error} the journey is not at its end - * @throws {Error} unsupported action for this data type - * @private - */ -function iterate(subject, path, check) { - - const result = new Map; - - 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; - - -} - -/** - * - * @param {*} subject - * @param [string} path - * @param [boolean} check - * @returns {*} - * @throws {TypeError} unsupported type - * @throws {Error} the journey is not at its end - * @throws {Error} unsupported action for this data type - * @private - */ -function getValueViaPath(subject, path, check) { - - if (path === "") { - return subject; - } - - let 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) - -} - -/** - * - * @param object - * @param path - * @param value - * @returns {void} - * @throws {TypeError} unsupported type - * @throws {TypeError} unsupported type - * @throws {Error} the journey is not at its end - * @throws {Error} unsupported action for this data type - * @private - */ -function setValueViaPath(object, path, value) { - - validateString(path); - - let 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, object, 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, object, current, obj); - } - - let anchor = getValueViaPath.call(this, object, subpath); - - if (!isObject(object) && !isArray(object)) { - throw TypeError("unsupported type: " + typeof object); - } - - 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); - } - - -} - -/** - * @private - * @param {object} object - * @param {string} key - * @param {*} value - */ -function assignProperty(object, key, value) { - - if (!object.hasOwnProperty(key)) { - object[key] = value; - return; - } - - if (value === undefined) { - delete object[key]; - } - - object[key] = value; - -} - -/** - * - * @param object - * @param path - * @returns {void} - * @throws {TypeError} unsupported type - * @throws {TypeError} unsupported type - * @throws {Error} the journey is not at its end - * @throws {Error} unsupported action for this data type - * @since 1.6.0 - * @private - */ -function deleteValueViaPath(object, path) { - - const parts = path.split(DELIMITER) - let last = parts.pop(); - const subpath = parts.join(DELIMITER); - - const anchor = getValueViaPath.call(this, object, 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]; - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {isArray, isObject} from "../types/is.mjs"; -import {typeOf} from "../types/typeof.mjs"; - -export {diff} - -/** - * With the diff function you can perform the change of one object to another. The result shows the changes of the second object to the first object. - * - * The operator `add` means that something has been added to the second object. `delete` means that something has been deleted from the second object compared to the first object. - * - * ``` - * <script type="module"> - * import {Diff} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/diff.mjs'; - * Diff(a, b) - * </script> - * ``` - * - * @example - * - * import {Diff} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/diff.mjs'; - * - * // given are two objects x and y. - * - * let x = { - * a: 1, - * b: "Hello!" - * } - * - * let y = { - * a: 2, - * c: true - * } - * - * // These two objects can be compared with each other. - * - * console.log(Diff(x, y)); - * - * // the result is then the following - * - * // - * // [ - * // { - * // operator: 'update', - * // path: [ 'a' ], - * // first: { value: 1, type: 'number' }, - * // second: { value: 2, type: 'number' } - * // }, - * // { - * // operator: 'delete', - * // path: [ 'b' ], - * // first: { value: 'Hello!', type: 'string' } - * // }, - * // { - * // operator: 'add', - * // path: [ 'c' ], - * // second: { value: true, type: 'boolean' } - * // } - * // ] - * - * @param {*} first - * @param {*} second - * @return {array} - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.Data - */ -function diff(first, second) { - return doDiff(first, second) -} - -/** - * @private - * @param a - * @param b - * @param type - * @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))); -} - -/** - * @private - * @param a - * @param b - * @param path - * @param diff - * @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; - -} - -/** - * - * @param {*} a - * @param {*} b - * @param {string} operator - * @param {array} path - * @return {{path: array, operator: string}} - * @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; -} - -/** - * @private - * @param {*} a - * @param {*} b - * @return {boolean} - */ -function isNotEqual(a, b) { - - if (typeof a !== typeof b) { - return true; - } - - if (a instanceof Date && b instanceof Date) { - return a.getTime() !== b.getTime(); - } - - return a !== b; -} - -/** - * @private - * @param {*} a - * @param {*} 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; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../constants.mjs"; -import {isObject} from "../../types/is.mjs"; -import {Datasource} from "../datasource.mjs"; -import {Pathfinder} from "../pathfinder.mjs"; -import {Pipe} from "../pipe.mjs"; -import {WriteError} from "./restapi/writeerror.mjs"; - -export {RestAPI} - -/** - * The RestAPI is a class that enables a REST API server. - * - * ``` - * <script type="module"> - * import {RestAPI} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/restapi.mjs'; - * new RestAPI() - * </script> - * ``` - * - * @example - * - * import {RestAPI} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/restapi.mjs'; - * - * const ds = new RestAPI({ - * url: 'https://httpbin.org/get' - * },{ - * url: 'https://httpbin.org/post' - * }); - * - * ds.set({flag:true}) - * ds.write().then(()=>console.log('done')); - * ds.read().then(()=>console.log('done')); - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource - * @summary The LocalStorage class encapsulates the access to data objects. - */ -class RestAPI extends Datasource { - - /** - * - * @param {Object} [readDefinition] An options object containing any custom settings that you want to apply to the read request. - * @param {Object} [writeDefinition] An options object containing any custom settings that you want to apply to the write request. - * @throws {TypeError} value is not a string - */ - constructor(readDefinition, writeDefinition) { - super(); - - const options = {} - - if (isObject(readDefinition)) options.read = readDefinition; - if (isObject(writeDefinition)) options.write = writeDefinition; - - this.setOptions(options); - - } - - /** - * @property {string} url=undefined Defines the resource that you wish to fetch. - * @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 {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 {Object} write.report - * @property {String} write.report.path Path to validations - * @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing. - * @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', - }, - acceptedStatus: [200, 201], - url: undefined, - mapping: { - transformer: undefined, - callbacks: [] - }, - report: { - path: undefined - } - }, - read: { - init: { - method: 'GET' - }, - 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 response; - - let init = self.getOption('read.init'); - if (!isObject(init)) init = {}; - - return fetch(self.getOption('read.url'), init).then(resp => { - response = resp; - - const acceptedStatus = self.getOption('read.acceptedStatus', [200]); - - if (acceptedStatus.indexOf(resp.status) === -1) { - throw Error('the data cannot be read (response ' + resp.status + ')') - } - - return resp.text() - }).then(body => { - - let obj; - - try { - obj = JSON.parse(body); - - } catch (e) { - - if (body.length > 100) { - body = body.substring(0, 97) + '...'; - } - - throw new Error('the response does not contain a valid json (actual: ' + body + ').'); - } - - let transformation = self.getOption('read.mapping.transformer'); - if (transformation !== undefined) { - const pipe = new Pipe(transformation); - obj = pipe.run(obj); - } - - self.set(obj); - return response; - }) - } - - /** - * @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' - } - } - - let obj = self.get(); - let transformation = self.getOption('write.mapping.transformer'); - if (transformation !== undefined) { - const pipe = new Pipe(transformation); - obj = pipe.run(obj); - } - - let sheathingObject = self.getOption('write.sheathing.object'); - let sheathingPath = self.getOption('write.sheathing.path'); - let reportPath = self.getOption('write.report.path'); - - if (sheathingObject && sheathingPath) { - const sub = obj; - obj = sheathingObject; - (new Pathfinder(obj)).setVia(sheathingPath, sub); - } - - init['body'] = JSON.stringify(obj); - - return fetch(self.getOption('write.url'), init).then(response => { - - const acceptedStatus = self.getOption('write.acceptedStatus', [200, 2001]); - - if (acceptedStatus.indexOf(response.status) === -1) { - - return response.text().then((body) => { - - let obj, validation; - try { - obj = JSON.parse(body); - validation = new Pathfinder(obj).getVia(reportPath) - - } catch (e) { - - if (body.length > 100) { - body = body.substring(0, 97) + '...'; - } - - throw new Error('the response does not contain a valid json (actual: ' + body + ').'); - } - - throw new WriteError('the data cannot be written (response ' + response.status + ')', response, validation) - - }) - - - } - - return response; - }); - } - - - /** - * @return {RestAPI} - */ - getClone() { - const self = this; - return new RestAPI(self[internalSymbol].getRealSubject()['options'].read, self[internalSymbol].getRealSubject()['options'].write); - } - -} - - -/** - * This callback can be passed to a datasource and is used to adapt data structures. - * - * @callback Monster.Data.Datasource~exampleCallback - * @param {*} value Value - * @param {string} key Key - * @memberOf Monster.Data - * @see Monster.Data.Datasource - */ -'use strict'; - -/** - * Namespace for datasources - * - * @namespace Monster.Data.Datasource - * @memberOf Monster.Data - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../../constants.mjs"; -import {getGlobalObject} from "../../../types/global.mjs"; -import {Datasource} from "../../datasource.mjs"; -import {Storage, storageObjectSymbol} from "../storage.mjs"; - -export {SessionStorage} - -/** - * The SessionStorage class provides a data source that uses the SessionStorage API on the client. - * - * ``` - * <script type="module"> - * import {SessionStorage} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/storage/sessionstorage.mjs'; - * new SessionStorage() - * </script> - * ``` - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource.Storage - * @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'); - } - - /** - * Create Clone - * - * @return {SessionStorage} - */ - getClone() { - const self = this; - return new SessionStorage(self[internalSymbol].getRealSubject()['options'].key); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../../constants.mjs"; -import {getGlobalObject} from "../../../types/global.mjs"; -import {Datasource} from "../../datasource.mjs"; -import {Storage, storageObjectSymbol} from "../storage.mjs"; - -export {LocalStorage} - -/** - * The LocalStorage Datasource provides a data store in the browser localStorage. - * - * ``` - * <script type="module"> - * import {LocalStorage} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/storage/localstorage.mjs'; - * new LocalStorage() - * </script> - * ``` - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource.Storage - * @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'); - } - - /** - * Create clone - * @return {LocalStorage} - */ - getClone() { - const self = this; - return new LocalStorage(self[internalSymbol].getRealSubject()['options'].key); - } - - -} -'use strict'; - -/** - * Namespace for storages - * - * @namespace Monster.Data.Datasource.Storage - * @memberOf Monster.Data.Datasource - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * Namespace for storages - * - * @namespace Monster.Data.Datasource.RestAPI - * @memberOf Monster.Data.Datasource - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../../constants.mjs"; - -export {WriteError} - -/** - * Error message for API requests with extension of request and validation. - * - * @since 1.24.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource.RestAPI - * @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 - }; - } - - /** - * @return {Response} - */ - getResponse() { - return this[internalSymbol]['response'] - } - - /** - * @return {Object} - */ - getValidation() { - return this[internalSymbol]['validation'] - } -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../constants.mjs"; -import {validateString} from "../../types/validate.mjs"; -import {Datasource} from "../datasource.mjs"; - -export {Storage, storageObjectSymbol} - -/** - * @private - * @type {symbol} - */ -const storageObjectSymbol = Symbol('storageObject'); - -/** - * The class represents a record. - * - * ``` - * <script type="module"> - * import {Storage} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/storage.mjs'; - * new Storage() - * </script> - * ``` - * - * @example - * - * import {Storage} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/storage.mjs'; - * - * new Datasource(); - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource - * @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)); - } - - /** - * @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") - } - - /** - * @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](); - - 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; - - 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)); - } - - resolve(); - }) - } - - /** - * @return {Storage} - */ - getClone() { - const self=this; - return new Storage(self[internalSymbol].getRealSubject()['options'].key); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {getGlobal} from '../types/global.mjs'; - -export {random} - -/** - * this function uses crypt and returns a random number. - * - * ``` - * <script type="module"> - * import {random} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/math/random.mjs'; - * random(1,10) - * // ↦ 5 - * </script> - * ``` - * - * @param {number} min starting value of the definition set (default is 0) - * @param {number} max end value of the definition set (default is 1000000000) - * @returns {number} - * @memberOf Monster.Math - * @throws {Error} missing crypt - * @throws {Error} we cannot generate numbers larger than 53 bits. - * @throws {Error} the distance is too small to create a random number. - - * @since 1.0.0 - * @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)); - -} - -/** - * @private - * @type {number} - */ -var MAX = 1000000000; - - -Math.log2 = Math.log2 || function (n) { - return Math.log(n) / Math.log(2); -}; - -/** - * - * @param {number} min - * @param {number} max - * @returns {number} - * @private - * @throws {Error} missing crypt - * @throws {Error} we cannot generate numbers larger than 53 bits. - * @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; - -} -'use strict'; - -/** - * Namespace for math. - * - * @namespace Monster.Math - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - -import {isObject} from "../types/is.mjs"; -import {AbstractConstraint} from "./abstract.mjs"; - -export {IsObject} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * ``` - * <script type="module"> - * import {IsObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/isobject.mjs'; - * console.log(new IsObject()) - * </script> - * ``` - * - * @example - * - * import {IsObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/isobject.mjs'; - * - * new IsObject() - * .isValid({}) - * .then(()=>console.log(true)); - * // ↦ true - * - * - * new IsObject() - * .isValid(99) - * .catch(e=>console.log(e)); - * // ↦ 99 - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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); - } - - return Promise.reject(value); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractConstraint} from "./abstract.mjs"; - -export {Invalid} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The invalid constraint allows an always invalid query to be performed. this constraint is mainly intended for testing. - * - * ``` - * <script type="module"> - * import {Invalid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/invalid.mjs'; - * new Invalid(); - * </script> - * ``` - * - * @example - * - * import {Invalid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/invalid.mjs'; - * - * new Invalid().isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ false - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractOperator} from "./abstractoperator.mjs"; - -export {AndOperator} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The AndOperator is used to link several contraints. The constraint is fulfilled if all constraints of the operators are fulfilled. - * - * ``` - * <script type="module"> - * import {AndOperator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/andoperator.mjs'; - * new AndOperator(); - * </script> - * ``` - * - * @example - * - * import {Valid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/valid.mjs'; - * import {Invalid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/invalid.mjs'; - * import {AndOperator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/andoperator.mjs'; - * - * new AndOperator( - * new Valid(), new Valid()).isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ true - * - * new AndOperator( - * new Invalid(), new Valid()).isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ false - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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)]); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractOperator} from "./abstractoperator.mjs"; - -export {OrOperator} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The OrOperator is used to link several constraints. The constraint is fulfilled if one of the constraints is fulfilled. - * - * ``` - * <script type="module"> - * import {OrOperator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraint/oroperator.mjs'; - * new OrOperator(); - * </script> - * ``` - * - * @example - * - * import {Valid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/valid.mjs'; - * import {Invalid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/invalid.mjs'; - * import {OrOperator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/oroperator.mjs'; - * - * new OrOperator( - * new Valid(), new Invalid()).isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ true - * - * new OrOperator( - * new Invalid(), new Invalid()).isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ false - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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; - - return new Promise(function (resolve, reject) { - let a, 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.operantB.isValid(value) - .then(function () { - resolve(); - }).catch(function () { - b = false; - /** b has already been evaluated and was not true */ - if (a === false) { - reject(); - } - }); - }); - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {Base} from '../types/base.mjs'; - -export {AbstractConstraint} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The abstract constraint defines the api for all constraints. mainly the method isValid() is defined. - * - * derived classes must implement the method isValid(). - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @summary The abstract constraint - */ -class AbstractConstraint extends Base { - - /** - * - */ - constructor() { - super(); - } - - /** - * this method must return a promise containing the result of the check. - * - * @param {*} value - * @returns {Promise} - */ - isValid(value) { - return Promise.reject(value); - } -} -'use strict'; - -/** - * Constraints are used to define conditions that must be met by the value of a variable so that the value can be transferred to the system. - * - * @namespace Monster.Constraints - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - -import {isArray} from "../types/is.mjs"; -import {AbstractConstraint} from "./abstract.mjs"; - -export {IsArray} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * ``` - * <script type="module"> - * import {IsArray} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/isarray.mjs'; - * console.log(new IsArray()) - * </script> - * ``` - * - * @example - * - * import {IsArray} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/isarray.mjs'; - * - * new IsArray() - * .isValid([]) - * .then(()=>console.log(true)); - * // ↦ true - * - * new IsArray() - * .isValid(99) - * .catch(e=>console.log(e)); - * // ↦ 99 - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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); - } - - return Promise.reject(value); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractConstraint} from "./abstract.mjs"; - -export {AbstractOperator} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * Operators allow you to link constraints together. for example, you can check whether a value is an object or an array. each operator has two operands that are linked together. - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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(); - - if (!(operantA instanceof AbstractConstraint) || !(operantB instanceof AbstractConstraint)) { - throw new TypeError("parameters must be from type AbstractConstraint") - } - - this.operantA = operantA; - this.operantB = operantB; - - } - - -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {AbstractConstraint} from "./abstract.mjs"; - -export {Valid} - -/** - * Constraints are used to define conditions that must be met by the value of a variable. - * - * The uniform API of the constraints allows chains to be formed. - * - * The valid constraint allows an always valid query to be performed. this constraint is mainly intended for testing. - * - * ``` - * <script type="module"> - * import {Valid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/valid.mjs'; - * new Valid(); - * </script> - * ``` - * - * @example - * - * import {Valid} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/constraints/valid.mjs'; - * - * new Valid().isValid() - * .then(()=>console.log(true)) - * .catch(()=>console.log(false)); - * // ↦ true - * - * @since 1.3.0 - * @copyright schukai GmbH - * @memberOf Monster.Constraints - * @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); - } - -} -/** - * @license - * Copyright 2021 schukai GmbH - * SPDX-License-Identifier: AGPL-3.0-only or COMMERCIAL - * @author schukai GmbH - */ - - -/** - * Main namespace for Monster. - * - * @namespace Monster - * @author schukai GmbH - */ -'use strict'; - -import './constants.mjs'; -// find packages/monster/source/ -type f -name "*.mjs" -not -name "*namespace*" -not -iname "monster.mjs" -import './constraints/isobject.mjs'; -import './constraints/valid.mjs'; -import './constraints/invalid.mjs'; -import './constraints/abstractoperator.mjs'; -import './constraints/oroperator.mjs'; -import './constraints/andoperator.mjs'; -import './constraints/abstract.mjs'; -import './constraints/isarray.mjs'; -import './logging/logger.mjs'; -import './logging/handler.mjs'; -import './logging/logentry.mjs'; -import './logging/handler/console.mjs'; -import './text/formatter.mjs'; -import './dom/resource/script.mjs'; -import './dom/resource/data.mjs'; -import './dom/resource/link/stylesheet.mjs'; -import './dom/resource/link.mjs'; -import './dom/resource.mjs'; -import './dom/updater.mjs'; -import './dom/attributes.mjs'; -import './dom/template.mjs'; -import './dom/util.mjs'; -import './dom/ready.mjs'; -import './dom/resourcemanager.mjs'; -import './dom/locale.mjs'; -import './dom/customcontrol.mjs'; -import './dom/constants.mjs'; -import './dom/assembler.mjs'; -import './dom/theme.mjs'; -import './dom/worker/factory.mjs'; -import './dom/focusmanager.mjs'; -import './dom/events.mjs'; -import './dom/customelement.mjs'; -import './i18n/formatter.mjs'; -import './i18n/providers/fetch.mjs'; -import './i18n/translations.mjs'; -import './i18n/locale.mjs'; -import './i18n/provider.mjs'; -import './types/queue.mjs'; -import './types/binary.mjs'; -import './types/regex.mjs'; -import './types/observer.mjs'; -import './types/observerlist.mjs'; -import './types/basewithoptions.mjs'; -import './types/is.mjs'; -import './types/proxyobserver.mjs'; -import './types/uniquequeue.mjs'; -import './types/node.mjs'; -import './types/tokenlist.mjs'; -import './types/typeof.mjs'; -import './types/uuid.mjs'; -import './types/mediatype.mjs'; -import './types/dataurl.mjs'; -import './types/base.mjs'; -import './types/version.mjs'; -import './types/nodelist.mjs'; -import './types/id.mjs'; -import './types/randomid.mjs'; -import './types/noderecursiveiterator.mjs'; -import './types/validate.mjs'; -import './types/stack.mjs'; -import './util/deadmansswitch.mjs'; -import './util/comparator.mjs'; -import './util/trimspaces.mjs'; -import './util/clone.mjs'; -import './util/freeze.mjs'; -import './util/processing.mjs'; -import './constants.mjs'; -import './data/pathfinder.mjs'; -import './data/pipe.mjs'; -import './data/extend.mjs'; -import './data/diff.mjs'; -import './data/buildmap.mjs'; -import './data/datasource.mjs'; -import './data/buildtree.mjs'; -import './data/transformer.mjs'; -import './data/datasource/storage.mjs'; -import './data/datasource/restapi.mjs'; -import './data/datasource/storage/sessionstorage.mjs'; -import './data/datasource/storage/localstorage.mjs'; -import './data/datasource/restapi/writeerror.mjs'; -import './math/random.mjs'; - -export {Monster} - -/** - * This class has no other purpose than to exist. - * - * @since 2.0.0 - * @copyright schukai GmbH - * @memberOf Monster - */ -class Monster { - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -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'; - -export {Logger, ALL, TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF}; - -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const ALL = 255; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const TRACE = 64; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const DEBUG = 32; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const INFO = 16; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const WARN = 8; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const ERROR = 4; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const FATAL = 2; -/** - * ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF - * @type {number} - * @memberOf Monster.Logging - */ -const OFF = 0; - -/** - * The logger is a class that takes care of logging. - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/logging/logger.mjs'; - * new Logger() - * </script> - * ``` - * - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Logging - */ -class Logger extends Base { - - /** - * - */ - 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") - } - - 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") - } - - 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() { - triggerLog.apply(this, [TRACE, ...arguments]); - 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() { - triggerLog.apply(this, [DEBUG, ...arguments]); - 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() { - triggerLog.apply(this, [INFO, ...arguments]); - 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() { - triggerLog.apply(this, [WARN, ...arguments]); - 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() { - triggerLog.apply(this, [ERROR, ...arguments]); - 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() { - triggerLog.apply(this, [FATAL, ...arguments]); - return this; - }; - - - /** - * 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'; - - return 'unknown'; - }; - - /** - * 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; - - return 0; - }; - - -} - - -/** - * Log triggern - * - * @param {integer} loglevel - * @param {*} args - * @returns {Logger} - * @private - */ -function triggerLog(loglevel, ...args) { - var logger = this; - - for (let handler of logger.handler) { - handler.log(new LogEntry(loglevel, args)) - } - - return logger; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {Base} from '../types/base.mjs'; -import {validateInteger} from '../types/validate.mjs'; - -export {LogEntry} - -/** - * A log entry for the logger - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/logging/logentry.mjs'; - * console.log(new LogEntry()) - * </script> - * ``` - * - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Logging - */ - class LogEntry extends Base { - /** - * - * @param {Integer} loglevel - * @param {...*} args - */ - constructor(loglevel, ...args) { - super(); - validateInteger(loglevel); - - this.loglevel = loglevel - this.arguments = args - } - - /** - * - * @returns {integerr} - */ - getLogLevel() { - return this.loglevel - } - - /** - * - * @returns {array} - */ - getArguments() { - return this.arguments - } - -} -'use strict'; - -/** - * Namespace for logging. - * - * @namespace Monster.Logging - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @namespace Monster.Logging.Handler - * @memberOf Monster.Logging - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../../types/base.mjs'; -import {getGlobalObject} from "../../types/global.mjs"; -import {Handler} from '../handler.mjs'; -import {LogEntry} from "../logentry.mjs"; - -export {ConsoleHandler} - -/** - * You can create an object of the class simply by using the namespace `new Monster.Logging.Handler.ConsoleHandler()`. - * - * ``` - * <script type="module"> - * import {ConsoleHandler} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/logging/handler/console.mjs'; - * console.log(new ConsoleHandler()) - * </script> - * ``` - * - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Logging.Handler - */ - class ConsoleHandler extends Handler { - constructor() { - super(); - } - - - /** - * 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; - console.log(entry.toString()); - return true; - } - - return false; - } - -} - - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {validateInstance, validateInteger} from "../types/validate.mjs"; -import {LogEntry} from "./logentry.mjs"; -import {ALL, DEBUG, ERROR, FATAL, INFO, OFF, TRACE, WARN} from "./logger.mjs"; - -export {Handler} - -/** - * The log handler is the interface between the log entries and the log listeners. - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/logging/handler.mjs'; - * console.log(new Handler()) - * </script> - * ``` - * - * @since 1.5.0 - * @copyright schukai GmbH - * @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; - }; - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; -import {Pipe} from "../data/pipe.mjs"; - -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {isObject, isString} from "../types/is.mjs"; -import {validateArray, validateString} from "../types/validate.mjs"; -import {getMonsterVersion} from "../types/version.mjs"; - -export {Formatter} - -/** - * @private - * @type {symbol} - */ -const internalObjectSymbol = Symbol('internalObject'); - -/** - * @private - * @type {symbol} - */ -const watchdogSymbol = Symbol('watchdog'); - -/** - * @private - * @type {symbol} - */ -const markerOpenIndexSymbol = Symbol('markerOpenIndex'); - -/** - * @private - * @type {symbol} - */ -const markerCloseIndexSymbol = Symbol('markercloseIndex'); - -/** - * @private - * @type {symbol} - */ -const workingDataSymbol = Symbol('workingData'); - - -/** - * Messages can be formatted with the formatter. To do this, an object with the values must be passed to the formatter. The message can then contain placeholders. - * - * Look at the example below. The placeholders use the logic of Pipe. - * - * ``` - * <script type="module"> - * import {Formatter} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/text/formatter.mjs'; - * new Formatter() - * </script> - * ``` - * - * ## Marker in marker - * - * Markers can be nested. Here, the inner marker is resolved first `${subkey} ↦ 1 = ${mykey2}` and then the outer marker `${mykey2}`. - * - * ``` - * const text = '${mykey${subkey}}'; - * let obj = { - * mykey2: "1", - * subkey: "2" - * }; - * - * new Formatter(obj).format(text); - * // ↦ 1 - * ``` - * - * ## Callbacks - * - * The values in a formatter can be adjusted via the commands of the `Transformer` or the`Pipe`. - * There is also the possibility to use callbacks. - * - * const formatter = new Formatter({x: '1'}, { - * callbacks: { - * quote: (value) => { - * return '"' + value + '"' - * } - * } - * }); - * - * formatter.format('${x | call:quote}')) - * // ↦ "1" - * - * ## Marker with parameter - * - * A string can also bring its own values. These must then be separated from the key by a separator `::`. - * The values themselves must be specified in key/value pairs. The key must be separated from the value by a separator `=`. - * - * When using a pipe, you must pay attention to the separators. - * - * @example - * - * import {Formatter} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/text/formatter.mjs'; - * - * new Formatter({ - * a: { - * b: { - * c: "Hello" - * }, - * d: "world", - * } - * }).format("${a.b.c} ${a.d | ucfirst}!"); // with pipe - * - * // ↦ Hello World! - * - * @since 1.12.0 - * @copyright schukai GmbH - * @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); - } - -} - -/** - * @private - * @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; -} - -/** - * @private - * @since 1.12.0 - * @param 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") - return; - } - - 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(''); -} -'use strict'; - -/** - * Namespace for texts. - * - * @namespace Monster.Text - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {extend} from "../../../data/extend.mjs"; -import {Link} from "../link.mjs"; - -export {Stylesheet} - -/** - * This class is used by the resource manager to embed external resources. - * - * ``` - * <script type="module"> - * import {Style} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource/link/stylesheet.mjs'; - * new Stylesheet() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Resource - * @summary A Resource class - * @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' - }) - } - -} - -'use strict'; - -/** - * In this namespace you will find classes and methods for links - * - * @namespace Monster.DOM.Resource.Link - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * In this namespace you will find classes and methods for handling resources. - * - * @namespace Monster.DOM.Resource - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -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 -} from "../constants.mjs"; -import {KEY_DOCUMENT, KEY_QUERY, referenceSymbol, Resource} from "../resource.mjs"; - -export {Data} - -/** - * This class is used by the resource manager to embed data. - * - * ``` - * <script type="module"> - * import {Data} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource/data.mjs'; - * new Data() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Resource - * @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; - } - - /** - * @return {string} - */ - static getURLAttribute() { - return ATTRIBUTE_SRC - } - -} - -/** - * @private - * @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; -} - - -/** - * @private - * @return {Promise} - * 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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {extend} from "../../data/extend.mjs"; -import { - 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"; - -export {Link} - -/** - * This class is used by the resource manager to embed external resources. - * - * ``` - * <script type="module"> - * import {Link} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource/link.mjs'; - * new Link() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Resource - * @summary A Resource class - * @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 - }) - } - - /** - * - * @return {Monster.DOM.Resource.Link} - */ - create() { - createElement.call(this); - return this; - } - - /** - * @return {string} - */ - static getURLAttribute() { - return ATTRIBUTE_HREF - } - -} - -/** - * @private - * @return {Monster.DOM.Resource.Link} - */ -function createElement() { - const self = this; - - 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); - } - } - - return self; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {extend} from "../../data/extend.mjs"; -import { - 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"; - -export {Script} - -/** - * This class is used by the resource manager to embed scripts. - * - * ``` - * <script type="module"> - * import {Script} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource/script.mjs'; - * new Script() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Resource - * @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', - }) - } - - /** - * - * @return {Monster.DOM.Resource.Script} - */ - create() { - createElement.call(this); - return this; - } - - /** - * @return {string} - */ - static getURLAttribute() { - return ATTRIBUTE_SRC - } - -} - -/** - * @private - * @return {Monster.DOM.Resource.Script} - */ -function createElement() { - const self = this; - - 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); - } - } - - - return self; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -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 -} from "../dom/constants.mjs"; - -import {Base} from "../types/base.mjs"; -import {isArray, isInstance, isIterable} from "../types/is.mjs"; -import {Observer} from "../types/observer.mjs"; -import {ProxyObserver} from "../types/proxyobserver.mjs"; -import {validateArray, validateInstance} from "../types/validate.mjs"; -import {clone} from "../util/clone.mjs"; -import {trimSpaces} from "../util/trimspaces.mjs"; -import {findTargetElementFromEvent} from "./events.mjs"; -import {findDocumentTemplate} from "./template.mjs"; -import {getDocument} from "./util.mjs"; - -export {Updater} - -/** - * The updater class connects an object with the dom. In this way, structures and contents in the DOM can be programmatically adapted via attributes. - * - * For example, to include a string from an object, the attribute `data-monster-replace` can be used. - * a further explanation can be found under {@tutorial dom-based-templating-implementation}. - * - * Changes to attributes are made only when the direct values are changed. If you want to assign changes to other values - * as well, you have to insert the attribute `data-monster-select-this`. This should be done with care, as it can reduce performance. - * - * ``` - * <script type="module"> - * import {Updater} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/updater.mjs'; - * new Updater() - * </script> - * ``` - * - * @example - * - * import {Updater} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/updater.mjs'; - * - * // First we prepare the html document. - * // This is done here via script, but can also be inserted into the document as pure html. - * // To do this, simply insert the tag <h1 data-monster-replace="path:headline"></h1>. - * const body = document.querySelector('body'); - * const headline = document.createElement('h1'); - * headline.setAttribute('data-monster-replace','path:headline') - * body.appendChild(headline); - * - * // the data structure - * let obj = { - * headline: "Hello World", - * }; - * - * // Now comes the real magic. we pass the updater the parent HTMLElement - * // and the desired data structure. - * const updater = new Updater(body, obj); - * updater.run(); - * - * // Now you can change the data structure and the HTML will follow these changes. - * const subject = updater.getSubject(); - * subject['headline'] = "Hello World!" - * - * @since 1.8.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} the value is not iterable - * @throws {Error} pipes are not allowed when cloning a node. - * @throws {Error} no template was found with the specified key. - * @throws {Error} the maximum depth for the recursion is reached. - * @throws {TypeError} value is not a object - * @throws {TypeError} value is not an instance of HTMLElement - * @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; - } - -} - -/** - * @private - * @since 1.9.0 - * @return {function - * @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; - } - } -} - -/** - * @private - */ -const symbol = Symbol('EventHandler'); - -/** - * @private - * @return {function} - * @this Updater - * @throws {Error} the bind argument must start as a value with a path - */ -function getControlEventHandler() { - - const self = this; - - 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); - - if (element === undefined) { - return; - } - - retrieveAndSetValue.call(self, element); - - } - - return self[symbol]; - - -} - -/** - * @throws {Error} the bind argument must start as a value with a path - * @param {HTMLElement} element - * @return void - * @memberOf Monster.DOM - * @private - */ -function retrieveAndSetValue(element) { - - const self = this; - - const pathfinder = new Pathfinder(self[internalSymbol].subject.getSubject()); - - let path = element.getAttribute(ATTRIBUTE_UPDATER_BIND); - - if (path.indexOf('path:') !== 0) { - throw new Error('the bind argument must start as a value with a path'); - } - - path = path.substr(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); - } -} - -/** - * @since 1.27.0 - * @return void - * @private - */ -function retrieveFromBindings() { - const self = this; - - if (self[internalSymbol].element.matches('[' + ATTRIBUTE_UPDATER_BIND + ']')) { - retrieveAndSetValue.call(self, element) - } - - for (const [, element] of self[internalSymbol].element.querySelectorAll('[' + ATTRIBUTE_UPDATER_BIND + ']').entries()) { - retrieveAndSetValue.call(self, element) - } - -} - -/** - * @private - * @since 1.8.0 - * @param {object} change - * @return {void} - */ -function removeElement(change) { - const self = this; - - for (const [, element] of self[internalSymbol].element.querySelectorAll(':scope [' + ATTRIBUTE_UPDATER_REMOVE + ']').entries()) { - element.parentNode.removeChild(element); - } -} - -/** - * @private - * @since 1.8.0 - * @param {object} change - * @return {void} - * @throws {Error} the value is not iterable - * @throws {Error} pipes are not allowed when cloning a node. - * @throws {Error} no template was found with the specified key. - * @throws {Error} the maximum depth for the recursion is reached. - * @this Updater - */ -function insertElement(change) { - const self = this; - const subject = self[internalSymbol].subject.getRealSubject(); - const document = getDocument(); - - 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); - 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.'); - } - - } - - -} - -/** - * - * @private - * @since 1.8.0 - * @param {HTMLElement} container - * @param {string} key - * @param {string} ref - * @param {string} path - * @throws {Error} no template was found with the specified key. - */ -function appendNewDocumentFragment(container, key, ref, path) { - - 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); - } - - container.appendChild(node); - } -} - -/** - * @private - * @since 1.10.0 - * @param {HTMLElement} node - * @param {string} key - * @param {string} 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); - } - } -} - -/** - * @private - * @since 1.8.0 - * @param {object} change - * @return {void} - * @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); - } - } - } - - -} - -/** - * @private - * @since 1.8.0 - * @param {HTMLElement} container - * @param {array} parts - * @param {object} subject - * @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:"]'; - 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; - } - - } - - - } - -} - -/** - * @private - * @since 1.8.0 - * @param {string} path - * @param {object} change - * @return {void} - */ -function updateAttributes(change) { - const subject = this[internalSymbol].subject.getRealSubject(); - let p = clone(change?.['path']); - runUpdateAttributes.call(this, this[internalSymbol].element, p, subject); -} - -/** - * @private - * @param {HTMLElement} container - * @param {array} parts - * @param {object} subject - * @return {void} - * @this Updater - */ -function runUpdateAttributes(container, parts, subject) { - - const self = this; - - if (!isArray(parts)) return; - parts = clone(parts); - - let mem = new WeakSet; - - while (parts.length > 0) { - const current = parts.join('.'); - parts.pop(); - - let iterator = new Set; - - const query = '[' + ATTRIBUTE_UPDATER_SELECT_THIS + '], [' + ATTRIBUTE_UPDATER_ATTRIBUTES + '*="path:' + current + '"], [' + ATTRIBUTE_UPDATER_ATTRIBUTES + '^="static:"]'; - - const e = container.querySelectorAll(query); - - if (e.length > 0) { - iterator = new Set( - [...e] - ) - } - - if (container.matches(query)) { - iterator.add(container); - } - - for (const [element] of iterator.entries()) { - - if (mem.has(element)) return; - mem.add(element) - - 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)); - - let pipe = new Pipe(cmd); - - 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); - } - - - if (value === undefined) { - element.removeAttribute(name) - - } else if (element.getAttribute(name) !== value) { - element.setAttribute(name, value); - } - - handleInputControlAttributeUpdate.call(this, element, name, value); - - } - } - } - -} - -/** - * @private - * @param {HTMLElement|*} element - * @param {string} name - * @param {string|number|undefined} value - * @return {void} - * @this Updater - */ - -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); - } - } - -} -'use strict'; - -import {extend} from "../data/extend.mjs"; -/** - * @author schukai GmbH - */ - -import {ATTRIBUTE_VALUE} from "./constants.mjs"; -import {CustomElement, attributeObserverSymbol} from "./customelement.mjs"; - -export {CustomControl} - -/** - * @private - * @type {symbol} - */ -const attachedInternalSymbol = Symbol('attachedInternal'); - -/** - * To define a new HTML control we need the power of CustomElement - * - * IMPORTANT: after defining a `CustomElement`, the `registerCustomElement` method must be called - * with the new class name. only then will the tag defined via the `getTag` method be made known to the DOM. - * - * <img src="./images/customcontrol-class.png"> - * - * This control uses `attachInternals()` to integrate the control into a form. - * If the target environment does not support this method, the [polyfill](https://www.npmjs.com/package/element-internals-polyfill ) can be used. - * - * You can create the object via the function `document.createElement()`. - * - * ``` - * <script type="module"> - * import {CustomControl} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * document.createElement('monster-') - * </script> - * ``` - * - * @startuml customcontrol-class.png - * skinparam monochrome true - * skinparam shadowing false - * HTMLElement <|-- CustomElement - * CustomElement <|-- CustomControl - * @enduml - * - * @summary A base class for customcontrols based on CustomElement - * @see {@link https://www.npmjs.com/package/element-internals-polyfill} - * @see {@link https://github.com/WICG/webcomponents} - * @see {@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements} - * @since 1.14.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - */ -class CustomControl extends CustomElement { - - /** - * 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 ElementInternals is not supported and a polyfill is necessary - * @summary create new Instance - */ - constructor() { - super(); - - if (typeof this['attachInternals'] === 'function') { - /** - * currently only supported by chrome - * @property {Object} - * @private - */ - this[attachedInternalSymbol] = this.attachInternals(); - } - - initObserver.call(this); - - } - - /** - * This method determines which attributes are to be monitored by `attributeChangedCallback()`. - * - * @return {string[]} - * @since 1.15.0 - */ - static get observedAttributes() { - const list = super.observedAttributes; - list.push(ATTRIBUTE_VALUE); - return list; - } - - /** - * - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} - * @since 1.14.0 - * @return {boolean} - */ - static get formAssociated() { - return true; - } - - /** - * Derived classes can override and extend this method as follows. - * - * ``` - * get defaults() { - * return extends{}, super.defaults, { - * myValue:true - * }); - * } - * ``` - * - * @see {@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example} - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} - * @return {object} - * @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) - * - * @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 return the value of the control. - * - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @param {*} value - * @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 {@link https://developer.mozilla.org/en-US/docs/Web/API/ValidityState} - * @see {@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 {CustomStateSet} - * @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(); - } - -} - -/** - * @private - * @return {object} - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - * @this CustomControl - */ -function getInternal() { - const self = this; - - if (!(attachedInternalSymbol in this)) { - throw new Error('ElementInternals is not supported and a polyfill is necessary'); - } - - return this[attachedInternalSymbol]; -} - -/** - * @private - * @return {object} - * @this CustomControl - */ -function initObserver() { - const self = this; - - // value - self[attributeObserverSymbol]['value'] = () => { - self.setOption('value', self.getAttribute('value')); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {parseLocale} from "../i18n/locale.mjs"; - -import {getDocument} from "./util.mjs"; - -export {getLocaleOfDocument} - -/** - * @private - * @type {string} - */ -const DEFAULT_LANGUAGE = 'en'; - -/** - * With this function you can read the language version set by the document. - * For this the attribute `lang` in the html tag is read. If no attribute is set, `en` is used as default. - * - * ```html - * <html lang="en"> - * ``` - * - * You can call the function via the monster namespace `new Monster.DOM.getLocaleOfDocument()`. - * - * ``` - * <script type="module"> - * import {getLocaleOfDocument} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/locale.mjs'; - * new getLocaleOfDocument() - * </script> - * ``` - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {TypeError} value is not a string - * @throws {Error} unsupported locale - * @summary Tries to determine the locale used - */ -function getLocaleOfDocument() { - - 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) - } - } - - return parseLocale(DEFAULT_LANGUAGE); -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {getGlobalObject} from '../types/global.mjs'; -import {validateString} from "../types/validate.mjs"; -import {ATTRIBUTE_THEME_NAME, DEFAULT_THEME} from "./constants.mjs"; - -export {Theme, getDocumentTheme} - -/** - * The Theme class provides the functionality for the theme. - * - * ``` - * <script type="module"> - * import {Theme} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/theme.mjs'; - * console.log(new Theme()) - * </script> - * ``` - * - * @example - * - * import {getDocumentTheme} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/theme.mjs'; - * - * const theme = getDocumentTheme(); - * console.log(theme.getName()); - * // ↦ monster - * - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @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; - } - - /** - * - * @returns {string} - */ - getName() { - return this.name; - } - -} - -/** - * The theming used in the document can be defined via the html-tag. - * The theming is specified via the attribute `data-monster-theme-name`. - * - * As name for a theme all characters are valid, which are also allowed for a HTMLElement-ID. - * - * ``` - * <html data-monster-theme-name="my-theme"> - * ``` - * - * the default theme name is `monster`. - * - * @return {Theme} - * @memberOf Monster.DOM - * @since 1.7.0 - */ -function getDocumentTheme() { - 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; - } - } - - return new Theme(name); - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalStateSymbol, internalSymbol,} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {getGlobalObject} from "../types/global.mjs"; -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"; - -export {Resource, KEY_DOCUMENT, KEY_QUERY, referenceSymbol} - -/** - * @private - * @type {string} - */ -const KEY_DOCUMENT = 'document'; - -/** - * @private - * @type {string} - */ -const KEY_QUERY = 'query'; - -/** - * @private - * @type {string} - */ -const KEY_TIMEOUT = 'timeout'; - -/** - * @private - * @type {symbol} - */ -const referenceSymbol = Symbol('reference'); - -/** - * This class is the base class for all resources to be loaded. - * - * ``` - * <script type="module"> - * import {Resource} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resource.mjs'; - * new Resource() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @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"); - } - -} - - -/** - * @private - * @return {Promise} - * 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') - } - - addEvents.call(self); - targetNode.appendChild(self[referenceSymbol]); - - return self; -} - -/** - * @private - * @return {addEvents} - */ -function addEvents() { - const self = this; - - 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', - }) - - 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); - - return self; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {extend} from "../data/extend.mjs"; -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {getGlobalObject} from "../types/global.mjs"; -import {isArray} from "../types/is.mjs"; -import {ATTRIBUTE_HREF, ATTRIBUTE_SRC} from "./constants.mjs"; -import {Resource} from "./resource.mjs"; -import {Data} from "./resource/data.mjs"; -import {Stylesheet} from "./resource/link/stylesheet.mjs"; -import {Script} from "./resource/script.mjs"; - -export {ResourceManager} - -/** - * The ResourceManager is a singleton that manages all resources. - * - * ``` - * <script type="module"> - * import {Resource} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/resourcemanager.mjs'; - * new ResourceManager() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @summary A Resource class - */ - class ResourceManager extends BaseWithOptions { - - /** - * - * @param {Object} options - * throw {Error} unsupported document type - */ - constructor(options) { - super(options); - - if (!(this.getOption('document') instanceof Document)) { - throw new Error('unsupported document type') - } - - - } - - /** - * @property {string} baseurl - */ - getBaseURL() { - this.getOption('document')?.baseURL; - } - - /** - * - * @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 defaults() { - return Object.assign({}, super.defaults, { - 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); - } - - -} - -/** - * @private - * @param {string} method - * @return {Array} - */ -function runResourceMethod(method) { - const self = this; - - const result = []; - - 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') - } - - result.push(resource[method]()); - } - - } - - return result; -} - -/** - * - * @param {string} type - * @param {string|URL} url - * @param [Object|undefined} options - * @return {Monster.DOM.ResourceManager} - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {getGlobal} from "../types/global.mjs"; -import {validateString} from "../types/validate.mjs"; - -export {getDocument, getWindow, getDocumentFragmentFromString} - -/** - * this method fetches the document object - * - * ``` - * <script type="module"> - * import {getDocument} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/util.mjs'; - * console.log(getDocument()) - * </script> - * ``` - * - * in nodejs this functionality can be performed with [jsdom](https://www.npmjs.com/package/jsdom). - * - * ``` - * import {JSDOM} from "jsdom" - * if (typeof window !== "object") { - * const {window} = new JSDOM('', { - * url: 'http://example.com/', - * pretendToBeVisual: true - * }); - * - * [ - * 'self', - * 'document', - * 'Document', - * 'Node', - * 'Element', - * 'HTMLElement', - * 'DocumentFragment', - * 'DOMParser', - * 'XMLSerializer', - * 'NodeFilter', - * 'InputEvent', - * 'CustomEvent' - * ].forEach(key => (getGlobal()[key] = window[key])); - * } - * ``` - * - * @returns {object} - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} not supported environment - */ -function getDocument() { - let document = getGlobal()?.['document']; - if (typeof document !== 'object') { - throw new Error("not supported environment") - } - - return document; -} - -/** - * this method fetches the window object - * - * ``` - * <script type="module"> - * import {getWindow} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/util.mjs'; - * console.log(getWindow(null)) - * </script> - * ``` - * - * in nodejs this functionality can be performed with [jsdom](https://www.npmjs.com/package/jsdom). - * - * ``` - * import {JSDOM} from "jsdom" - * if (typeof window !== "object") { - * const {window} = new JSDOM('', { - * url: 'http://example.com/', - * pretendToBeVisual: true - * }); - * - * getGlobal()['window']=window; - * - * [ - * 'self', - * 'document', - * 'Document', - * 'Node', - * 'Element', - * 'HTMLElement', - * 'DocumentFragment', - * 'DOMParser', - * 'XMLSerializer', - * 'NodeFilter', - * 'InputEvent', - * 'CustomEvent' - * ].forEach(key => (getGlobal()[key] = window[key])); - * } - * ``` - * - * @returns {object} - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} not supported environment - */ -function getWindow() { - let window = getGlobal()?.['window']; - if (typeof window !== 'object') { - throw new Error("not supported environment") - } - - return window; -} - - -/** - * this method fetches the document object - * - * ``` - * <script type="module"> - * import {getDocumentFragmentFromString} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/util.mjs'; - * console.log(getDocumentFragmentFromString('<div></div>')) - * </script> - * ``` - * - * in nodejs this functionality can be performed with [jsdom](https://www.npmjs.com/package/jsdom). - * - * ``` - * import {JSDOM} from "jsdom" - * if (typeof window !== "object") { - * const {window} = new JSDOM('', { - * url: 'http://example.com/', - * pretendToBeVisual: true - * }); - * - * [ - * 'self', - * 'document', - * 'Document', - * 'Node', - * 'Element', - * 'HTMLElement', - * 'DocumentFragment', - * 'DOMParser', - * 'XMLSerializer', - * 'NodeFilter', - * 'InputEvent', - * 'CustomEvent' - * ].forEach(key => (getGlobal()[key] = window[key])); - * } - * ``` - * - * @returns {DocumentFragment} - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} not supported environment - * @throws {TypeError} value is not a string - */ -function getDocumentFragmentFromString(html) { - validateString(html); - - const document = getDocument(); - const template = document.createElement('template'); - template.innerHTML = html; - - return template.content; -} -'use strict'; - - -/** - * @author schukai GmbH - */ - -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, - -} - -/** - * default theme - * @memberOf Monster.DOM - * @type {string} - */ -const DEFAULT_THEME = 'monster'; - -/** - * @memberOf Monster.DOM - * @since 1.8.0 - * @type {string} - */ -const ATTRIBUTE_PREFIX = 'data-monster-'; - -/** - * This is the name of the attribute to pass options to a control - * - * @memberOf Monster.DOM - * @since 1.8.0 - * @type {string} - */ -const ATTRIBUTE_OPTIONS = ATTRIBUTE_PREFIX + 'options'; - -/** - * This is the name of the attribute to pass options to a control - * - * @memberOf Monster.DOM - * @since 1.30.0 - * @type {string} - */ -const ATTRIBUTE_OPTIONS_SELECTOR = ATTRIBUTE_PREFIX + 'options-selector'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_THEME_PREFIX = ATTRIBUTE_PREFIX + 'theme-'; - -/** - * @memberOf Monster.DOM - * @type {string} - */ -const ATTRIBUTE_THEME_NAME = ATTRIBUTE_THEME_PREFIX + 'name'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_ATTRIBUTES = ATTRIBUTE_PREFIX + 'attributes'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.27.1 - */ -const ATTRIBUTE_UPDATER_SELECT_THIS = ATTRIBUTE_PREFIX + 'select-this'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_REPLACE = ATTRIBUTE_PREFIX + 'replace'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_INSERT = ATTRIBUTE_PREFIX + 'insert'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_INSERT_REFERENCE = ATTRIBUTE_PREFIX + 'insert-reference'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.8.0 - */ -const ATTRIBUTE_UPDATER_REMOVE = ATTRIBUTE_PREFIX + 'remove'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.9.0 - */ -const ATTRIBUTE_UPDATER_BIND = ATTRIBUTE_PREFIX + 'bind'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.27.0 - */ -const ATTRIBUTE_TEMPLATE_PREFIX = ATTRIBUTE_PREFIX + 'template-prefix'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.14.0 - */ -const ATTRIBUTE_ROLE = ATTRIBUTE_PREFIX + 'role'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.24.0 - */ -const ATTRIBUTE_DISABLED = 'disabled'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.24.0 - */ -const ATTRIBUTE_VALUE = 'value'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.9.0 - */ -const ATTRIBUTE_OBJECTLINK = ATTRIBUTE_PREFIX + 'objectlink'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.24.0 - */ -const ATTRIBUTE_ERRORMESSAGE = ATTRIBUTE_PREFIX + 'error'; - -/** - * @memberOf Monster.DOM - * @type {symbol} - * @since 1.24.0 - */ -const objectUpdaterLinkSymbol = Symbol('monsterUpdater'); - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const TAG_SCRIPT = 'script'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const TAG_STYLE = 'style'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const TAG_LINK = 'link'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ - -const ATTRIBUTE_ID = 'id'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ - -const ATTRIBUTE_CLASS = 'class'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_TITLE = 'title'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_SRC = 'src'; -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_HREF = 'href'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_TYPE = 'type'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_NONCE = 'nonce'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_TRANSLATE = 'translate'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_TABINDEX = 'tabindex'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_SPELLCHECK = 'spellcheck'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_SLOT = 'slot'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_PART = 'part'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_LANG = 'lang'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMTYPE = 'itemtype'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMSCOPE = 'itemscope'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMREF = 'itemref'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMID = 'itemid'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ITEMPROP = 'itemprop'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_IS = 'is'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_INPUTMODE = 'inputmode'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ACCESSKEY = 'accesskey'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_AUTOCAPITALIZE = 'autocapitalize'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_AUTOFOCUS = 'autofocus'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_CONTENTEDITABLE = 'contenteditable'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_DIR = 'dir'; - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_DRAGGABLE = 'draggable'; - - -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_ENTERKEYHINT = 'enterkeyhint'; -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_EXPORTPARTS = 'exportparts'; -/** - * @memberOf Monster.DOM - * @type {string} - * @since 1.25.0 - */ -const ATTRIBUTE_HIDDEN = 'hidden'; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; -import {Pathfinder} from "../data/pathfinder.mjs"; - -import {parseDataURL} from "../types/dataurl.mjs"; -import {getGlobalObject} from "../types/global.mjs"; -import {isArray, isFunction, 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 {clone} from "../util/clone.mjs"; -import {addAttributeToken, addToObjectLink, getLinkedObjects, hasObjectLink} from "./attributes.mjs"; -import { - ATTRIBUTE_DISABLED, - ATTRIBUTE_ERRORMESSAGE, - ATTRIBUTE_OPTIONS, - ATTRIBUTE_OPTIONS_SELECTOR, - objectUpdaterLinkSymbol -} from "./constants.mjs"; -import {findDocumentTemplate, Template} from "./template.mjs"; -import {Updater} from "./updater.mjs"; - -export {CustomElement, initMethodSymbol, assembleMethodSymbol, attributeObserverSymbol, registerCustomElement, assignUpdaterToElement} - -/** - * @memberOf Monster.DOM - * @type {symbol} - */ -const initMethodSymbol = Symbol('initMethodSymbol'); - -/** - * @memberOf Monster.DOM - * @type {symbol} - */ -const assembleMethodSymbol = Symbol('assembleMethodSymbol'); - -/** - * this symbol holds the attribute observer callbacks. The key is the attribute name. - * @memberOf Monster.DOM - * @type {symbol} - */ -const attributeObserverSymbol = Symbol('attributeObserver'); - - -/** - * HTMLElement - * @external HTMLElement - * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement - * - * @startuml customelement-sequencediagram.png - * skinparam monochrome true - * skinparam shadowing false - * - * autonumber - * - * Script -> DOM: element = document.createElement('my-element') - * DOM -> CustomElement: constructor() - * CustomElement -> CustomElement: [initMethodSymbol]() - * - * CustomElement --> DOM: Element - * DOM --> Script : element - * - * - * Script -> DOM: document.querySelector('body').append(element) - * - * DOM -> CustomElement : connectedCallback() - * - * note right CustomElement: is only called at\nthe first connection - * CustomElement -> CustomElement : [assembleMethodSymbol]() - * - * ... ... - * - * autonumber - * - * Script -> DOM: document.querySelector('monster-confirm-button').parentNode.removeChild(element) - * DOM -> CustomElement: disconnectedCallback() - * - * - * @enduml - * - * @startuml customelement-class.png - * skinparam monochrome true - * skinparam shadowing false - * HTMLElement <|-- CustomElement - * @enduml - */ - - -/** - * To define a new HTML element we need the power of CustomElement - * - * IMPORTANT: after defining a `CustomElement`, the `registerCustomElement` method must be called - * with the new class name. only then will the tag defined via the `getTag` method be made known to the DOM. - * - * <img src="./images/customelement-class.png"> - * - * You can create the object via the function `document.createElement()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * document.createElement('monster-') - * </script> - * ``` - * - * ## Interaction - * - * <img src="./images/customelement-sequencediagram.png"> - * - * ## Styling - * - * For optimal display of custom-elements the pseudo-class :defined can be used. - * - * To prevent the custom elements from being displayed and flickering until the control is registered, it is recommended to create a css directive. - * - * In the simplest case, you can simply hide the control. - * - * ``` - * <style> - * - * my-custom-element:not(:defined) { - * display: none; - * } - * - * my-custom-element:defined { - * display: flex; - * } - * - * </style> - * ``` - * - * Alternatively you can also display a loader - * - * ``` - * my-custom-element:not(:defined) { - * display: flex; - * box-shadow: 0 4px 10px 0 rgba(33, 33, 33, 0.15); - * border-radius: 4px; - * height: 200px; - * position: relative; - * overflow: hidden; - * } - * - * my-custom-element:not(:defined)::before { - * content: ''; - * display: block; - * position: absolute; - * left: -150px; - * top: 0; - * height: 100%; - * width: 150px; - * background: linear-gradient(to right, transparent 0%, #E8E8E8 50%, transparent 100%); - * animation: load 1s cubic-bezier(0.4, 0.0, 0.2, 1) infinite; - * } - * - * @keyframes load { - * from { - * left: -150px; - * } - * to { - * left: 100%; - * } - * } - * - * my-custom-element:defined { - * display: flex; - * } - * ``` - * - * @example - * - * // In the example the the user can use his own template by creating a template in the DOM with the ID `my-custom-element`. - * // You can also specify a theme (for example `mytheme`), then it will search for the ID `my-custom-element-mytheme` and - * // if not available for the ID `my-custom-element`. - * - * class MyCustomElement extends CustomElement { - * - * static getTag() { - * return "my-custom-element" - * } - * - * } - * - * // ↦ <my-custom-element></my-custom-element> - * - * @see https://github.com/WICG/webcomponents - * @see https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @extends external:HTMLElement - * @summary A base class for HTML5 customcontrols - */ -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. - * - * @throws {Error} the options attribute does not contain a valid json definition. - * @since 1.7.0 - */ - constructor() { - super(); - this[internalSymbol] = new ProxyObserver({'options': extend({}, this.defaults)}); - this[attributeObserverSymbol] = {}; - initOptionObserver.call(this); - this[initMethodSymbol](); - } - - /** - * This method determines which attributes are to be monitored by `attributeChangedCallback()`. - * - * @return {string[]} - * @since 1.15.0 - */ - static get observedAttributes() { - return [ATTRIBUTE_OPTIONS, ATTRIBUTE_DISABLED]; - } - - /** - * Derived classes can override and extend this method as follows. - * - * ``` - * get defaults() { - * return Object.assign({}, super.defaults, { - * myValue:true - * }); - * } - * ``` - * - * To set the options via the html tag the attribute data-monster-options must be set. - * As value a JSON object with the desired values must be defined. - * - * Since 1.18.0 the JSON can be specified as a DataURI. - * - * ``` - * new Monster.Types.DataUrl(btoa(JSON.stringify({ - * shadowMode: 'open', - * delegatesFocus: true, - * templates: { - * main: undefined - * } - * })),'application/json',true).toString() - * ``` - * - * The attribute data-monster-options-selector can be used to access a script tag that contains additional configuration. - * - * As value a selector must be specified, which belongs to a script tag and contains the configuration as json. - * - * ``` - * <script id="id-for-this-config" type="application/json"> - * { - * "config-key": "config-value" - * } - * </script> - * ``` - * - * The individual configuration values can be found in the table. - * - * @property {boolean} disabled=false Object The Boolean disabled attribute, when present, makes the element not mutable, focusable, or even submitted with the form. - * @property {string} shadowMode=open `open` Elements of the shadow root are accessible from JavaScript outside the root, for example using. `close` Denies access to the node(s) of a closed shadow root from JavaScript outside it - * @property {Boolean} delegatesFocus=true A boolean that, when set to true, specifies behavior that 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 Templates - * @property {string} templates.main=undefined Main template - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow - * @since 1.8.0 - */ - get defaults() { - return { - ATTRIBUTE_DISABLED: this.getAttribute(ATTRIBUTE_DISABLED), - shadowMode: 'open', - delegatesFocus: true, - templates: { - main: undefined - } - }; - } - - /** - * There is no check on the name by this class. the developer is responsible for assigning an appropriate tag. - * if the name is not valid, registerCustomElement() will issue an error - * - * @link https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name - * @return {string} - * @throws {Error} the method getTag must be overwritten by the derived class. - * @since 1.7.0 - */ - static getTag() { - throw new Error("the method getTag must be overwritten by the derived class."); - } - - /** - * At this point a `CSSStyleSheet` object can be returned. If the environment does not - * support a constructor, then an object can also be built using the following detour. - * - * If `undefined` is returned then the shadowRoot does not get a stylesheet. - * - * ``` - * const doc = document.implementation.createHTMLDocument('title'); - * - * let style = doc.createElement("style"); - * style.innerHTML="p{color:red;}"; - * - * // WebKit Hack - * style.appendChild(document.createTextNode("")); - * // Add the <style> element to the page - * doc.head.appendChild(style); - * return doc.styleSheets[0]; - * ; - * ``` - * - * @return {CSSStyleSheet|CSSStyleSheet[]|string|undefined} - */ - 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; - } - - /** - * Is called once when the object is included in the DOM for the first time. - * - * @return {CustomElement} - * @since 1.8.0 - */ - [assembleMethodSymbol]() { - - const self = this; - let elements, nodeList; - - const AttributeOptions = getOptionsFromAttributes.call(self); - if (isObject(AttributeOptions) && Object.keys(AttributeOptions).length > 0) { - self.setOptions(AttributeOptions); - } - - const ScriptOptions = getOptionsFromScriptTag.call(self); - if (isObject(ScriptOptions) && Object.keys(ScriptOptions).length > 0) { - self.setOptions(ScriptOptions); - } - - - if (self.getOption('shadowMode', false) !== false) { - try { - initShadowRoot.call(self); - elements = self.shadowRoot.childNodes; - - } catch (e) { - - } - - try { - initCSSStylesheet.call(this); - } catch (e) { - addAttributeToken(self, ATTRIBUTE_ERRORMESSAGE, e.toString()); - } - } - - if (!(elements instanceof NodeList)) { - if (!(elements instanceof NodeList)) { - initHtmlContent.call(this); - elements = this.childNodes; - } - } - - try { - nodeList = new Set([ - ...elements, - ...getSlottedElements.call(self) - ]) - } catch (e) { - nodeList = elements - } - - assignUpdaterToElement.call(self, nodeList, clone(self[internalSymbol].getRealSubject()['options'])); - return self; - } - - /** - * Called every time the element is inserted into the DOM. Useful for running setup code, such as - * fetching resources or rendering. Generally, you should try to delay work until this time. - * - * @return {void} - * @since 1.7.0 - */ - connectedCallback() { - let self = this; - if (!hasObjectLink(self, objectUpdaterLinkSymbol)) { - 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; - - const callback = self[attributeObserverSymbol]?.[attrName]; - - if (isFunction(callback)) { - callback.call(self, newVal, oldVal); - } - - } - - /** - * - * @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); - - } - -} - -/** - * @private - * @param {String|undefined} query - * @param {String|undefined|null} name name of the slot (if the parameter is undefined, all slots are searched, if the parameter has the value null, all slots without a name are searched. if a string is specified, the slots with this name are searched.) - * @return {*} - * @this CustomElement - * @since 1.23.0 - * @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; -} - -/** - * @this CustomElement - * @private - * @param {Node} node - * @return {boolean} - */ -function containChildNode(node) { - const self = this; - - if (self.contains(node)) { - return true; - } - - for (const [, e] of Object.entries(self.childNodes)) { - if (e.contains(node)) { - return true; - } - - containChildNode.call(e, node); - } - - - return false; -} - -/** - * @since 1.15.0 - * @private - * @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, objectUpdaterLinkSymbol)) { - return; - } - // inform every element - const updaters = getLinkedObjects(self, objectUpdaterLinkSymbol); - - 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); - } - } - - -} - -/** - * @private - * @return {object} - * @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; - -} - -/** - * @private - * @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 {}; -} - -/** - * @private - * @param data - * @return {Object} - */ -function parseOptionsJSON(data) { - - const self = this, 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) { - - } - - try { - let obj = JSON.parse(data); - return validateObject(obj); - } catch (e) { - throw e; - } - - - return obj; -} - -/** - * @private - * @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) { - this.innerHTML = html; - } - - } - - return this; - -} - -/** - * @private - * @return {CustomElement} - * @memberOf Monster.DOM - * @this CustomElement - * @since 1.16.0 - * @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; - -} - -/** - * @private - * @return {CustomElement} - * @throws {Error} html is not set. - * @see https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow - * @memberOf Monster.DOM - * @since 1.8.0 - */ -function initShadowRoot() { - - let template, 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; - } - - this.shadowRoot.innerHTML = html; - return this; -} - -/** - * This method registers a new element. The string returned by `CustomElement.getTag()` is used as the tag. - * - * @param {CustomElement} element - * @return {void} - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {DOMException} Failed to execute 'define' on 'CustomElementRegistry': is not a valid custom element name - */ -function registerCustomElement(element) { - validateFunction(element); - getGlobalObject('customElements').define(element.getTag(), element); -} - - -/** - * - * @param element - * @param object - * @return {Promise[]} - * @since 1.23.0 - * @memberOf Monster.DOM - */ -function assignUpdaterToElement(elements, object) { - - const updaters = new Set; - - if (elements instanceof NodeList) { - elements = new Set([ - ...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(this, objectUpdaterLinkSymbol, updaters); - } - - return result; -} -'use strict'; - -/** - * In this namespace you will find classes and methods for handling the DOM. - * - * @namespace Monster.DOM - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - - -import {getGlobalFunction} from "../types/global.mjs"; -import {TokenList} from "../types/tokenlist.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 -} - -/** - * Get the closest object link of a node - * - * if a node is specified without a object link, a recursive search upwards is performed until the corresponding - * object link is found, or undefined is returned. - * - * ``` - * <script type="module"> - * import {getUpdaterFromNode} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/updater.mjs'; - * console.log(findClosestObjectLink()) - * </script> - * ``` - * - * @param {HTMLElement} element - * @return {HTMLElement|undefined} - * @since 1.10.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {TypeError} value is not an instance of HTMLElement - */ -function findClosestObjectLink(element) { - return findClosestByAttribute(element, ATTRIBUTE_OBJECTLINK); -} - -/** - * Adds a class attribute to an element. - * - * ``` - * <script type="module"> - * import {addToObjectLink} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * addToObjectLink(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {Symbol} symbol - * @param {Object} object - * @return {boolean} - */ -function addToObjectLink(element, symbol, object) { - - validateInstance(element, HTMLElement); - validateSymbol(symbol) - - if (element?.[symbol] === undefined) { - element[symbol] = new Set; - } - - addAttributeToken(element, ATTRIBUTE_OBJECTLINK, symbol.toString()); - element[symbol].add(object); - return element; - -} - -/** - * Removes an object from an element - * - * ``` - * <script type="module"> - * import {removeObjectLink} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * removeObjectLink(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {Symbol} symbol - * @return {boolean} - */ -function removeObjectLink(element, symbol) { - - validateInstance(element, HTMLElement); - validateSymbol(symbol) - - if (element?.[symbol] === undefined) { - return element - } - - removeAttributeToken(element, ATTRIBUTE_OBJECTLINK, symbol.toString()); - delete element[symbol]; - return element; - -} - - -/** - * Checks if an element has an object link - * - * ``` - * <script type="module"> - * import {hasObjectLink} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * hasObjectLink(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {Symbol} 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()); - -} - -/** - * The ObjectLink can be used to attach objects to HTMLElements. The elements are kept in a set under a unique - * symbol and can be read via an iterator {@see {@link getLinkedObjects}}. - * - * In addition, elements with an objectLink receive the attribute `data-monster-objectlink`. - * - * With the method {@see {@link addToObjectLink}} the objects can be added. - * - * ``` - * <script type="module"> - * import {getLinkedObjects} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * getLinkedObjects(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {Symbol} symbol - * @return {Iterator} - * @throws {Error} there is no object link for symbol - */ -function getLinkedObjects(element, symbol) { - - validateInstance(element, HTMLElement); - validateSymbol(symbol) - - if (element?.[symbol] === undefined) { - throw new Error('there is no object link for ' + symbol.toString()); - } - - return element?.[symbol][Symbol.iterator](); - -} - - -/** - * With this method tokens in an attribute can be switched on or off. For example, classes can be switched on and off in the elements class attribute. - * - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {toggleAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * toggleAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} token - * @return {HTMLElement} - */ -function toggleAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token) - validateString(key) - - if (!element.hasAttribute(key)) { - element.setAttribute(key, token); - return element; - } - - element.setAttribute(key, new TokenList(element.getAttribute(key)).toggle(token).toString()); - - return element -} - -/** - * This method can be used to add a token to an attribute. Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {addAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * addAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} token - * @return {HTMLElement} - */ -function addAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token) - validateString(key) - - if (!element.hasAttribute(key)) { - element.setAttribute(key, token); - return element; - } - - element.setAttribute(key, new TokenList(element.getAttribute(key)).add(token).toString()); - - return element -} - -/** - * This function can be used to remove tokens from an attribute. - * - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {removeAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * removeAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} token - * @return {HTMLElement} - */ -function removeAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token) - validateString(key) - - if (!element.hasAttribute(key)) { - return element; - } - - element.setAttribute(key, new TokenList(element.getAttribute(key)).remove(token).toString()); - - return element -} - -/** - * This method can be used to determine whether an attribute has a token. - * - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {containsAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * containsAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} token - * @return {boolean} - */ -function containsAttributeToken(element, key, token) { - validateInstance(element, HTMLElement); - validateString(token) - validateString(key) - - if (!element.hasAttribute(key)) { - return false; - } - - return new TokenList(element.getAttribute(key)).contains(token); - -} - -/** - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {replaceAttributeToken} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * replaceAttributeToken(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string} from - * @param {string} to - * @return {HTMLElement} - */ -function replaceAttributeToken(element, key, from, to) { - validateInstance(element, HTMLElement); - validateString(from) - validateString(to) - validateString(key) - - if (!element.hasAttribute(key)) { - return element; - } - - element.setAttribute(key, new TokenList(element.getAttribute(key)).replace(from, to).toString()); - - return element -} - -/** - * Tokens are always separated by a space. - * - * ``` - * <script type="module"> - * import {clearAttributeTokens} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * clearAttributeTokens(); - * </script> - * ``` - * - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @return {HTMLElement} - */ -function clearAttributeTokens(element, key) { - validateInstance(element, HTMLElement); - validateString(key) - - if (!element.hasAttribute(key)) { - return element; - } - - element.setAttribute(key, ""); - - return element -} - -/** - * This function searches, starting from an `HTMLElemement`, for the next element that has a certain attribute. - * - * ```html - * <div data-my-attribute="2" id="2"> - * <div id="1"></div> - * </div> - * ``` - * - * ```javascript - * // if no value is specified (undefined), then only the attribute is checked. - * findClosestByAttribute(document.getElementById('1'),'data-my-attribute'); // ↦ node with id 2 - * findClosestByAttribute(document.getElementById('2'),'data-my-attribute'); // ↦ node with id 2 - * - * // if a value is specified, for example an empty string, then the name and the value are checked. - * findClosestByAttribute(document.getElementById('1'),'data-my-attribute', ''); // ↦ undefined - * findClosestByAttribute(document.getElementById('1'),'data-my-attribute', '2'); // ↦ node with id 2 - * ``` - * - * ``` - * <script type="module"> - * import {findClosestByAttribute} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * findClosestByAttribute(); - * </script> - * ``` - * - * @since 1.14.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} key - * @param {string|undefined} value - * @return {HTMLElement|undefined} - * @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; -} - -/** - * This function searches, starting from an `HTMLElemement`, for the next element that has a certain attribute. - * - * ```html - * <div class="myclass" id="2"> - * <div id="1"></div> - * </div> - * ``` - * - * ```javascript - * // if no value is specified (undefined), then only the attribute is checked. - * findClosestByClass(document.getElementById('1'),'myclass'); // ↦ node with id 2 - * findClosestByClass(document.getElementById('2'),'myclass'); // ↦ node with id 2 - * ``` - * - * ``` - * <script type="module"> - * import {findClosestByClass} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/attributes.mjs'; - * findClosestByClass(); - * </script> - * ``` - * - * @since 1.27.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @param {HTMLElement} element - * @param {string} className - * @return {HTMLElement|undefined} - * @summary find closest node - */ -function findClosestByClass(element, className) { - validateInstance(element, getGlobalFunction('HTMLElement')); - - if (element?.classList?.contains(validateString(className))) { - return element; - } - - let result = element.closest('.' + className); - if (result instanceof HTMLElement) { - return result; - } - - return undefined; -} -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {isArray, isObject} from "../types/is.mjs"; -import {validateInstance, validateString} from "../types/validate.mjs"; -import {getDocument} from "./util.mjs"; - -export {fireEvent, fireCustomEvent, findTargetElementFromEvent} - -/** - * The function sends an event - * - * ``` - * <script type="module"> - * import {fireEvent} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/events.mjs'; - * fireEvent() - * </script> - * ``` - * - * @param {HTMLElement|HTMLCollection|NodeList} element - * @param {string} type - * @return {void} - * @since 1.10.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {TypeError} value is not an instance of HTMLElement or HTMLCollection - * @summary Construct and send and event - */ -function fireEvent(element, type) { - - const document = getDocument(); - - if (element instanceof HTMLElement) { - - if (type === 'click') { - element.click(); - return; - } - - let event = new Event(validateString(type), { - bubbles: true, - cancelable: 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') - } - -} - -/** - * You can call the function via the monster namespace `new Monster.DOM.fireCustomEvent()`. - * - * ``` - * <script type="module"> - * import {fireCustomEvent} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/events.mjs'; - * fireCustomEvent() - * </script> - * ``` - * - * @param {HTMLElement|HTMLCollection|NodeList} element - * @param {string} type - * @return {void} - * @since 1.29.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {TypeError} value is not an instance of HTMLElement or HTMLCollection - * @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, - 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') - } - -} - -/** - * This function gets the path `Event.composedPath()` from an event and tries to find the next element - * up the tree `element.closest()` with the attribute and value. If no value, or a value that is undefined or null, - * is specified, only the attribute is searched. - * - * ``` - * <script type="module"> - * import {findTargetElementFromEvent} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/events.mjs'; - * findTargetElementFromEvent() - * </script> - * ``` - * - * @since 1.14.0 - * @param {Event} event - * @param {string} attributeName - * @param {string|null|undefined} attributeValue - * @throws {Error} unsupported event - * @memberOf Monster.DOM - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not an instance of HTMLElement - * @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; - -} -'use strict'; - - -/** - * @author schukai GmbH - */ - - -import {internalSymbol} from "../../constants.mjs"; -import {Base} from "../../types/base.mjs"; -import {getGlobal, getGlobalFunction} from "../../types/global.mjs"; -import {isFunction} from "../../types/is.mjs"; -import {validateInstance, validateString} from "../../types/validate.mjs"; - -export {Factory} - -/** - * A factory for creating worker instances. - * - * ``` - * <script type="module"> - * import {Factory} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/worker/factory.mjs'; - * console.log(new Factory()) - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM.Worker - * @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; - } - - -} -'use strict'; - -/** - * In this namespace you will find classes and methods for handling the DOM. - * - * @namespace Monster.DOM.Worker - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {getGlobalFunction, getGlobalObject} from '../types/global.mjs'; -import {validateInstance, validateString} from "../types/validate.mjs"; -import {ATTRIBUTE_TEMPLATE_PREFIX} from "./constants.mjs"; -import {getDocumentTheme} from "./theme.mjs"; - -export {Template} - -/** - * The template class provides methods for creating templates. - * - * ``` - * <script type="module"> - * import {Template} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/template.mjs'; - * new Template() - * </script> - * ``` - * - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @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; - } - - /** - * - * @returns {HTMLTemplateElement} - */ - getTemplateElement() { - return this.template; - } - - /** - * - * @return {DocumentFragment} - * @throws {TypeError} value is not an instance of - */ - createDocumentFragment() { - return this.template.content.cloneNode(true); - } - -} - -/** - * This method loads a template with the given ID and returns it. - * - * To do this, it first reads the theme of the document and looks for the `data-monster-theme-name` attribute in the HTML tag. - * - * ``` - * <html data-monster-theme-name="my-theme"> - * ``` - * - * If no theme was specified, the default theme is `monster`. - * - * Now it is looked if there is a template with the given ID and theme `id-theme` and if yes it is returned. - * If there is no template a search for a template with the given ID `id` is done. If this is also not found, an error is thrown. - * - * You can call the method via the monster namespace `Monster.DOM.findDocumentTemplate()`. - * - * ``` - * <script type="module"> - * import {findTemplate} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/template.mjs'; - * findDocumentTemplate() - * </script> - * ``` - * - * @example - * - * import { findDocumentTemplate } from "https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/template.mjs"; - * - * const template = document.createElement("template"); - * template.id = "myTemplate"; - * template.innerHTML = "<p>my default template</p>"; - * document.body.appendChild(template); - * - * const themedTemplate = document.createElement("template"); - * themedTemplate.id = "myTemplate-myTheme"; - * themedTemplate.innerHTML = "<p>my themed template</p>"; - * document.body.appendChild(themedTemplate); - * - * // loads the temple and since no theme is set the default template - * const template1 = findDocumentTemplate("myTemplate"); - * console.log(template1.createDocumentFragment()); - * // ↦ '<p>my default template</p>' - * - * // now we set our own theme - * document - * .querySelector("html") - * .setAttribute("data-monster-theme-name", "myTheme"); - * - * // now we don't get the default template, - * // but the template with the theme in the id - * const template2 = findDocumentTemplate("myTemplate"); - * console.log(template2.createDocumentFragment()); - * // ↦ '<p>my themed template</p>' - * - * @param {string} id - * @param {Node} currentNode - * @return {Monster.DOM.Template} - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} template id not found. - * @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.") -} - -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {getDocument, getWindow} from "./util.mjs"; - -export {domReady, windowReady} - -/** - * This variable is a promise that is fulfilled as soon as the dom is available. - * - * The DOMContentLoaded event is fired when the original HTML document is fully loaded and parsed - * without waiting for stylesheets, images, and subframes to finish loading. - * - * ``` - * <script type="module"> - * import {domReady} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/ready.mjs'; - * domReady().then(()=>{ - * // ... - * }) - * </script> - * ``` - * - * @since 1.31.0 - * @memberOf Monster.DOM - * @summary variable to check if dom is ready - * @type {Promise} - * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event - * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState - */ -const domReady = new Promise(resolve => { - - const document = getDocument(); - - if (document.readyState === "loading") { - document.addEventListener('DOMContentLoaded', resolve); - } else { - resolve(); - } -}); - - -/** - * This variable is a promise that is fulfilled as soon as the windows is available. - * - * The load event fires when the entire page is loaded, including all dependent resources such as stylesheets, - * assets, and images. Unlike DOMContentLoaded, which fires as soon as the DOM of the page is loaded, - * without waiting for the resources to finish loading. - * - * ``` - * <script type="module"> - * import {windowReady} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/ready.mjs'; - * windowReady().then(()=>{ - * // ... - * }) - * </script> - * ``` - * - * @since 1.31.0 - * @memberOf Monster.DOM - * @summary variable to check if window is ready - * @type {Promise} - * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event - * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState - */ -const windowReady = new Promise(resolve => { - - const document = getDocument(); - const window = getWindow(); - - if (document.readyState === 'complete') { - resolve(); - } else { - window.addEventListener('load', resolve); - } -}); -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {extend} from "../data/extend.mjs"; -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {getGlobalObject} from "../types/global.mjs"; -import {isArray} from "../types/is.mjs"; -import {Stack} from "../types/stack.mjs"; -import {validateInstance, validateString} from "../types/validate.mjs"; - -export {FocusManager} - -/** - * @private - * @type {string} - */ -const KEY_DOCUMENT = 'document'; - -/** - * @private - * @type {string} - */ -const KEY_CONTEXT = 'context'; - - -/** - * @private - * @type {Symbol} - */ -const stackSymbol = Symbol('stack'); - - -/** - * With the focusmanager the focus can be stored in a document, recalled and moved. - * - * ``` - * <script type="module"> - * import {FocusManager} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/focusmanager.mjs'; - * new FocusManager() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @throws {Error} unsupported locale - * @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(); - } - - /** - * @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; - } - - -} - - - - - - - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {getGlobalFunction} from "../types/global.mjs"; -import {ProxyObserver} from "../types/proxyobserver.mjs"; -import {validateInstance, validateString} from "../types/validate.mjs"; - -export {ATTRIBUTEPREFIX,Assembler} - -/** - * attribute prefix - * - * @type {string} - * @memberOf Monster.DOM - */ -const ATTRIBUTEPREFIX = "data-monster-"; - -/** - * Assembler class - * - * ``` - * <script type="module"> - * import {Assembler} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/dom/assembler.mjs'; - * console.log(new Assembler()) - * </script> - * ``` - * - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.DOM - * @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 {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; - } - - /** - * - * @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; - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from "../types/base.mjs"; -import {isObject, isString} from "../types/is.mjs"; -import {validateInstance, validateInteger, validateObject, validateString} from "../types/validate.mjs"; -import {Locale, parseLocale} from "./locale.mjs"; - -export {Translations} - -/** - * With this class you can manage translations and access the keys. - * - * ``` - * <script type="module"> - * import {Translations} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/translations.mjs'; - * new Translations() - * </script> - * ``` - * - * @example - * - * import {Translations} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/translations.mjs'; - * import {parseLocale} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/locale.mjs'; - * - * const translation = new Translations(parseLocale('en-GB')); - * - * translation.assignTranslations({ - * text1: "click", - * text2: { - * 'one': 'click once', - * 'other': 'click n times' - * } - * }); - * - * console.log(translation.getText('text1')); - * // ↦ click - * - * console.log(translation.getPluralRuleText('text2',1)); - * // -> click once - * console.log(translation.getPluralRuleText('text2',2)); - * // -> click n times - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n - * @see https://datatracker.ietf.org/doc/html/rfc3066 - */ -class Translations extends Base { - - /** - * - * @param {Locale} locale - */ - constructor(locale) { - super(); - - if (isString(locale)) { - locale = parseLocale(locale); - } - - this.locale = validateInstance(locale, Locale); - this.storage = new Map(); - - } - - - /** - * 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 handlig 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]); - } - - 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); - - for (const [k, v] of Object.entries(translations)) { - this.setText(k, v); - } - - return this; - - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from "../types/base.mjs"; -import {validateString} from "../types/validate.mjs"; -import {clone} from "../util/clone.mjs"; - -export {Locale, parseLocale} - -/** - * @memberOf Monster.I18n - * @type {symbol} - */ -const propertiesSymbol = Symbol('properties'); - -/** - * @type {symbol} - * @memberOf Monster.I18n - */ -const localeStringSymbol = Symbol('localeString'); - -/** - * The Locale class is a base class for the language classes. - * - * ``` - * <script type="module"> - * import {Locale} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/locale.mjs'; - * new Locale() - * </script> - * ``` - * - * RFC - * - * ``` - * A Language-Tag consists of: - * langtag ; generated tag - * -or- private-use ; a private use tag - * - * langtag = (language - * ["-" script] - * ["-" region] - * *("-" variant) - * *("-" extension) - * ["-" privateuse]) - * - * language = "en", "ale", or a registered value - * - * script = "Latn", "Cyrl", "Hant" ISO 15924 codes - * - * region = "US", "CS", "FR" ISO 3166 codes - * "419", "019", or UN M.49 codes - * - * variant = "rozaj", "nedis", "1996", multiple subtags can be used in a tag - * - * extension = single letter followed by additional subtags; more than one extension - * may be used in a language tag - * - * private-use = "x-" followed by additional subtags, as many as are required - * Note that these can start a tag or appear at the end (but not - * in the middle) - * ``` - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n - * @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(); - - 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); - - if (s.length === 0) { - throw new Error('unsupported locale'); - } - - this[localeStringSymbol] = s.join('-'); - - } - - /** - * @return {string} - */ - get localeString() { - return this[localeStringSymbol]; - } - - /** - * @return {string|undefined} - */ - get language() { - return this[propertiesSymbol].language; - } - - /** - * @return {string|undefined} - */ - get region() { - return this[propertiesSymbol].region; - } - - /** - * @return {string|undefined} - */ - get script() { - return this[propertiesSymbol].script; - } - - /** - * @return {string|undefined} - */ - get variants() { - return this[propertiesSymbol].variants; - } - - /** - * @return {string|undefined} - */ - get extlang() { - return this[propertiesSymbol].extlang; - } - - /** - * @return {string|undefined} - */ - get privateUse() { - return this[propertiesSymbol].privateValue; - } - - - /** - * @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]) - } - - -} - -/** - * @typedef {Object} LocaleMap - * @property {string} language - * @property {string} script - * @property {string} region - * @property {string} variants - * @property {string} extlang - * @property {string} privateUse - * @memberOf Monster.I18n - */ - -/** - * Parse local according to rfc4646 standard - * - * Limitations: The regex cannot handle multiple variants or private. - * - * You can call the method via the monster namespace `Monster.I18n.createLocale()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * new Monster.I18n.createLocale() - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {createLocale} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/locale.mjs'; - * createLocale() - * </script> - * ``` - * - * RFC - * - * ``` - * The syntax of the language tag in ABNF [RFC4234] is: - * - * Language-Tag = langtag - * / privateuse ; private use tag - * / grandfathered ; grandfathered registrations - * - * langtag = (language - * ["-" script] - * ["-" region] - * *("-" variant) - * *("-" extension) - * ["-" privateuse]) - * - * language = (2*3ALPHA [ extlang ]) ; shortest ISO 639 code - * / 4ALPHA ; reserved for future use - * / 5*8ALPHA ; registered language subtag - * - * extlang = *3("-" 3ALPHA) ; reserved for future use - * - * script = 4ALPHA ; ISO 15924 code - * - * region = 2ALPHA ; ISO 3166 code - * / 3DIGIT ; UN M.49 code - * - * variant = 5*8alphanum ; registered variants - * / (DIGIT 3alphanum) - * - * extension = singleton 1*("-" (2*8alphanum)) - * - * singleton = %x41-57 / %x59-5A / %x61-77 / %x79-7A / DIGIT - * ; "a"-"w" / "y"-"z" / "A"-"W" / "Y"-"Z" / "0"-"9" - * ; Single letters: x/X is reserved for private use - * - * privateuse = ("x"/"X") 1*("-" (1*8alphanum)) - * - * grandfathered = 1*3ALPHA 1*2("-" (2*8alphanum)) - * ; grandfathered registration - * ; Note: i is the only singleton - * ; that starts a grandfathered tag - * - * alphanum = (ALPHA / DIGIT) ; letters and numbers - * - * Figure 1: Language Tag ABNF - * ``` - * - * @param {string} locale - * @returns {Locale} - * @since 1.14.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n - * @throws {TypeError} value is not a string - * @throws {Error} unsupported locale - */ -function parseLocale(locale) { - - locale = validateString(locale).replace(/_/g, "-"); - - let language, region, variants, parts, script, extlang, - regexRegular = "(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)", - 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)", - regexGrandfathered = "(" + regexIrregular + "|" + regexRegular + ")", - regexPrivateUse = "(x(-[A-Za-z0-9]{1,8})+)", - regexSingleton = "[0-9A-WY-Za-wy-z]", - regexExtension = "(" + regexSingleton + "(-[A-Za-z0-9]{2,8})+)", - regexVariant = "([A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})", - regexRegion = "([A-Za-z]{2}|[0-9]{3})", - regexScript = "([A-Za-z]{4})", - regexExtlang = "([A-Za-z]{3}(-[A-Za-z]{3}){0,2})", - regexLanguage = "(([A-Za-z]{2,3}(-" + regexExtlang + ")?)|[A-Za-z]{4}|[A-Za-z]{5,8})", - regexLangtag = "(" + regexLanguage + "(-" + regexScript + ")?" + "(-" + regexRegion + ")?" + "(-" + regexVariant + ")*" + "(-" + regexExtension + ")*" + "(-" + regexPrivateUse + ")?" + ")", - regexLanguageTag = "^(" + regexGrandfathered + "|" + regexLangtag + "|" + regexPrivateUse + ")$", - regex = new RegExp(regexLanguageTag), match; - - - 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[6] !== undefined) { - language = match[6]; - - parts = language.split('-'); - if (parts.length > 1) { - language = parts[0]; - extlang = parts[1]; - } - - } - - if (match[14] !== undefined) { - region = match[14]; - } - - if (match[12] !== undefined) { - script = match[12]; - } - - if (match[16] !== undefined) { - variants = match[16]; - } - - return new Locale(language, region, script, variants, extlang); - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {internalSymbol} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; - -import {Formatter as TextFormatter} from "../text/formatter.mjs"; -import {validateInstance, validateString} from "../types/validate.mjs"; -import {Translations} from "./translations.mjs"; - -export {Formatter} - -/** - * @private - * @type {symbol} - */ -const internalTranslationSymbol = Symbol('internalTranslation') - -/** - * The Formatter extends the Text.Formatter with the possibility to replace the key by a translation. - * - * ``` - * <script type="module"> - * import {Formatter} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/formatter.mjs'; - * new Formatter() - * </script> - * ``` - * - * @example - * - * import {Formatter} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/formatter.mjs'; - * import {Translations} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/translations.mjs'; - * - * const translations = new Translations('en') - * .assignTranslations({ - * thekey: "${animal} has eaten the ${food}!" - * }); - * - * new Formatter({}, translations).format("thekey:animal=dog::food=cake") - * // ↦ dog has eaten the cake! - * - * @since 1.26.0 - * @copyright schukai GmbH - * @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); - } - - /** - * @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) - - 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(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(); - - - let assembledText = openMarker + 'static:' + translationKey + ' | call:i18n'; - if (parameter.length > 0) { - assembledText += '::' + parameter; - } - assembledText += closeMarker; - return super.format(assembledText); - } - - -} -'use strict'; - -/** - * In this namespace you will find classes and methods for handling locale and localized texts. - * - * @namespace Monster.I18n.Providers - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../constants.mjs"; -import {extend} from "../../data/extend.mjs"; -import {Formatter} from "../../text/formatter.mjs"; -import {getGlobalFunction} from "../../types/global.mjs"; -import {isInstance, isString} from "../../types/is.mjs"; -import {validateObject, validateString} from "../../types/validate.mjs"; -import {parseLocale} from "../locale.mjs"; -import {Provider} from "../provider.mjs"; -import {Translations} from "../translations.mjs"; - -export {Fetch} - -/** - * The fetch provider retrieves a JSON file from the given URL and returns a translation object. - * - * ``` - * <script type="module"> - * import {Fetch} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/providers/fetch.mjs'; - * new Fetch() - * </script> - * ``` - * - * @example <caption>das ist ein test</caption> - * - * import {Fetch} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/providers/fetch.mjs'; - * - * // fetch from API - * const translation = new Fetch('https://example.com/${language}.json').getTranslation('en-GB'); - * // ↦ https://example.com/en.json - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n.Providers - * @see {@link https://datatracker.ietf.org/doc/html/rfc3066} - * @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); - - if (isInstance(url, URL)) { - url = url.toString(); - } - - if (options === undefined) { - options = {}; - } - - validateString(url); - - /** - * @property {string} - */ - this.url = url; - - /** - * @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 { - 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 - } - } - - } - - /** - * - * @param {Locale|string} locale - * @return {Promise} - */ - getTranslations(locale) { - - if (isString(locale)) { - locale = parseLocale(locale); - } - - 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); - }); - - } - - -} - -'use strict'; - -/** - * In this namespace you will find classes and methods for handling locale and localized texts. - * - * @namespace Monster.I18n - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - -import {BaseWithOptions} from "../types/basewithoptions.mjs"; -import {Locale} from "./locale.mjs" -import {Translations} from "./translations.mjs" - -export {Provider} - -/** - * A provider makes a translation object available. - * - * ``` - * <script type="module"> - * import {Provider} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/i18n/provider.mjs'; - * new Provider() - * </script> - * ``` - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.I18n - * @see {@link https://datatracker.ietf.org/doc/html/rfc3066} - */ -class Provider extends BaseWithOptions { - - /** - * @param {Locale|string} locale - * @return {Promise} - */ - getTranslations(locale) { - return new Promise((resolve, reject) => { - try { - resolve(new Translations(locale)); - } catch (e) { - reject(e); - } - - }); - } - -} -'use strict'; - -/** - * Property-Keys - * @author schukai GmbH - */ - -export { - internalSymbol, - internalStateSymbol -} - - -/** - * @private - * @type {symbol} - * @memberOf Monster - * @since 1.24.0 - */ -const internalSymbol = Symbol('internalData'); - -/** - * @private - * @type {symbol} - * @memberOf Monster - * @since 1.25.0 - */ -const internalStateSymbol = Symbol('state'); - -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {Base} from "./base.mjs"; -import {isString} from "./is.mjs"; -import {validateArray, validateString} from "./validate.mjs"; - -export {MediaType, parseMediaType} - -/** - * @private - * @type {symbol} - */ -const internal = Symbol('internal'); - -/** - * @typedef {Object} Parameter - * @property {string} key - * @property {string} value - * @memberOf Monster.Types - */ - - -/** - * You can create an object via the monster namespace `new Monster.Types.MediaType()`. - * - * ``` - * <script type="module"> - * import {MediaType} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/mediatype.mjs'; - * console.log(new MediaType()) - * </script> - * ``` - * - * @since 1.8.0 - * @copyright schukai GmbH - * @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); - } - - - } - - /** - * @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(';') : ''); - } - -} - -/** - * You can call the function via the monster namespace `Monster.Types.parseMediaType()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * console.log(Monster.Types.parseMediaType()) - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {parseMediaType} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/dataurl.mjs'; - * console.log(parseMediaType()) - * </script> - * ``` - * - * Specification: - * - * ``` - * dataurl := "data:" [ mediatype ] [ ";base64" ] "," data - * mediatype := [ type "/" subtype ] *( ";" parameter ) - * data := *urlchar - * parameter := attribute "=" value - * ``` - * - * @param {String} mediatype - * @return {Monster.Types.MediaType} - * @see https://datatracker.ietf.org/doc/html/rfc2045#section-5.1 - * @throws {TypeError} the mimetype can not be parsed - * @throws {TypeError} blank value is not allowed - * @throws {TypeError} malformed data url - * @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 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']; - - if (subtype === "" || type === "") { - throw new TypeError('blank value is not allowed'); - } - - return new MediaType(type, subtype, parseParameter(parameter)); - - -} - -/** - * @private - * @since 1.18.0 - * @param {String} parameter - * @return {Monster.Types.Parameter[]|undefined} - * @memberOf Monster.Types - */ -function parseParameter(parameter) { - - if (!isString(parameter)) { - return undefined; - } - - let result = []; - - parameter.split(';').forEach((entry) => { - - entry = entry.trim(); - if (entry === "") { - return; - } - - const kv = entry.split('=') - - 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 - }) - - - }) - - return result; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -export {typeOf} - -/** - * The built-in typeof method is known to have some historical weaknesses. This function tries to provide a better and more accurate result. - * - * ``` - * <script type="module"> - * import {typeOf} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/typeof.mjs'; - * console.log(typeOf()) - * </script> - * ``` - * - * @example - * - * import {typeOf} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/typeof.mjs'; - * - * console.log(typeOf(undefined)); // ↦ undefined - * console.log(typeOf("")); // ↦ string - * console.log(typeOf(5)); // ↦ number - * console.log(typeOf({})); // ↦ object - * console.log(typeOf([])); // ↦ array - * console.log(typeOf(new Map)); // ↦ map - * console.log(typeOf(true)); // ↦ boolean - * - * @param {*} value - * @return {string} - * @since 1.7.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @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(); - } - - const results = (/^(class|function)\s+(\w+)/).exec(value.constructor.toString()); - type = (results && results.length > 2) ? results[2] : ''; - } - - return type.toLowerCase(); -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {Observer} from "./observer.mjs"; -import {validateInstance} from "./validate.mjs"; - -export {ObserverList} - -/** - * With the help of the ObserverList class, observer can be managed. - * - * ``` - * <script type="module"> - * import {ObserverList} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/observerlist.mjs'; - * console.log(ObserverList()) - * console.log(ObserverList()) - * </script> - * ``` - * - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -class ObserverList extends Base { - - /** - * - */ - constructor() { - super(); - this.observers = []; - } - - /** - * - * @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; - }; - - /** - * - * @param {Observer} observer - * @return {ObserverList} - * @throws {TypeError} value is not an instance of Observer - */ - detach(observer) { - validateInstance(observer, Observer) - - var i = 0, l = this.observers.length; - for (; i < l; i++) { - if (this.observers[i] === observer) { - this.observers.splice(i, 1); - } - } - - return this; - }; - - /** - * - * @param {Observer} observer - * @return {boolean} - * @throws {TypeError} value is not an instance of Observer - */ - contains(observer) { - validateInstance(observer, Observer) - var i = 0, 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 = [] - - let i = 0, l = this.observers.length; - for (; i < l; i++) { - pomises.push(this.observers[i].update(subject)); - } - - return Promise.all(pomises); - }; - -} -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {random} from "../math/random.mjs"; -import {getGlobal} from "./global.mjs"; -import {ID} from "./id.mjs"; - -export {RandomID} - -/** - * @private - * @type {number} - */ -let internalCounter = 0; - -/** - * The `RandomID` class provides a unique ID for an item. - * - * ``` - * <script type="module"> - * import {RandomID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/randomid.mjs'; - * console.log(new RandomID()) - * </script> - * ``` - * - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary class to generate random numbers - */ -class RandomID extends ID { - - /** - * create new object - */ - constructor() { - super(); - - 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; - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -import {random} from "../math/random.mjs"; -import {isObject} from '../types/is.mjs'; -import {Base} from "./base.mjs"; -import {getGlobalObject} from "./global.mjs"; - -export {UUID} - -/** - * The UUID class makes it possible to get a unique UUID for an object. - * - * ``` - * <script type="module"> - * import {Base} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/uuid.mjs'; - * new UUID() - * </script> - * ``` - * - * @since 1.25.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {Error} unsupported - */ - class UUID extends Base { - - /** - * - */ - constructor() { - super(); - - let uuid = createWithCrypto(); - - if (uuid === undefined) { - uuid = createWithRandom(); - } - - - if (uuid === undefined) { - throw new Error('unsupported') - } - - this[internalSymbol] = { - value: uuid - } - - } - - /** - * - * @return {string} - */ - toString() { - return this[internalSymbol]['value']; - } - - -} - -/** - * @private - * @return {string|undefined} - */ -function createWithRandom() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = random(0, 65000) * 16 | 0, - v = ((c === 'x') ? r : (r & 0x3 | 0x8)); - return v.toString(16)[0]; - }) -} - - -/** - * @private - * @return {string|undefined} - */ -function createWithCrypto() { - const crypt = getGlobalObject('crypto'); - if (!isObject(crypt)) return; - if (typeof crypt?.['randomUUID']) return; - return crypt.randomUUID(); -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {isObject} from './is.mjs'; -import {TokenList} from './tokenlist.mjs'; -import {UniqueQueue} from './uniquequeue.mjs'; - -export {Observer} - -/** - * An observer manages a callback function - * - * ``` - * import {Observer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/observer.mjs'; - * new Observer() - * ``` - * - * The update method is called with the subject object as this pointer. For this reason the callback should not - * be an arrow function, because it gets the this pointer of its own context. - * - * ``` - * new Observer(()=>{ - * // this is not subject - * }) - * - * new Observer(function() { - * // this is subject - * }) - * ``` - * - * Additional arguments can be passed to the callback. To do this, simply specify them. - * - * ``` - * Observer(function(a, b, c) { - * console.log(a, b, c); // ↦ "a", 2, true - * }, "a", 2, true) - * ``` - * - * The callback function must have as many parameters as arguments are given. - * - * @example - * - * import {Observer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/observer.mjs'; - * - * const observer = new Observer(function(a, b, c) { - * console.log(this, a, b, c); // ↦ "a", 2, true - * }, "a", 2, true); - * - * observer.update({value:true}).then(()=>{}); - * // ↦ {value: true} "a" 2 true - * - * @since 1.0.0 - * @copyright schukai GmbH - * @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(); - } - - /** - * - * @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) - - }); - - }; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {isIterable, isString} from '../types/is.mjs'; -import {validateFunction, validateString} from '../types/validate.mjs'; -import {Base} from './base.mjs'; - -export {TokenList} - -/** - * A tokenlist allows you to manage tokens (individual character strings such as css classes in an attribute string). - * - * The tokenlist offers various functions to manipulate values. For example, you can add, remove or replace a class in a CSS list. - * - * ``` - * <script type="module"> - * import {TokenList} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/tokenlist.mjs'; - * new TokenList("myclass row") - * </script> - * ``` - * - * This class implements the [iteration protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols). - * - * ``` - * typeof new TokenList("myclass row")[Symbol.iterator]; - * // ↦ "function" - * ``` - * - * @since 1.2.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -class TokenList extends Base { - - /** - * - * @param {array|string|iteratable} init - */ - constructor(init) { - super(); - this.tokens = new Set(); - - if (typeof init !== "undefined") { - this.add(init); - } - - } - - /** - * 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() - - return { - next: () => { - if (index < entries.length) { - return {value: entries?.[index++], done: false} - } else { - return {done: true} - } - } - } - } - - /** - * Returns true if it contains token, otherwise false - * - * ``` - * new TokenList("start middle end").contains('start')); // ↦ true - * new TokenList("start middle end").contains('end')); // ↦ true - * new TokenList("start middle end").contains('xyz')); // ↦ false - * new TokenList("start middle end").contains(['end','start','middle'])); // ↦ true - * new TokenList("start middle end").contains(['end','start','xyz'])); // ↦ false - * ``` - * - * @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; - } - - return false; - } - - /** - * add tokens - * - * ``` - * new TokenList().add("abc xyz").toString(); // ↦ "abc xyz" - * new TokenList().add(["abc","xyz"]).toString(); // ↦ "abc xyz" - * new TokenList().add(undefined); // ↦ add nothing - * ``` - * - * @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; - } - - /** - * remove all tokens - * - * @returns {TokenList} - */ - clear() { - this.tokens.clear(); - return this; - } - - /** - * Removes token - * - * ``` - * new TokenList("abc xyz").remove("xyz").toString(); // ↦ "abc" - * new TokenList("abc xyz").remove(["xyz"]).toString(); // ↦ "abc" - * new TokenList("abc xyz").remove(undefined); // ↦ remove nothing - * ``` - * - * @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; - } - - /** - * 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; - - a.splice(i, 1, newToken); - this.tokens = new Set(); - this.add(a); - - return this; - - - } - - /** - * Removes token from string. If token doesn't exist it's added. - * - * ``` - * new TokenList("abc def ghi").toggle("def xyz").toString(); // ↦ "abc ghi xyz" - * new TokenList("abc def ghi").toggle(["abc","xyz"]).toString(); // ↦ "def ghi xyz" - * new TokenList().toggle(undefined); // ↦ nothing - * ``` - * - * @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; - - } - - /** - * 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; - } - - /** - * returns the individual tokens separated by a blank character - * - * @returns {string} - */ - toString() { - return this.entries().join(' '); - } - -} - -/** - * @private - * @param token - * @returns {toggleValue} - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; - -export {Queue} - -/** - * You can create the instance via the monster namespace `new Monster.Types.Queue()`. - * - * ``` - * <script type="module"> - * import {Queue} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/queue.mjs'; - * new Queue() - * </script> - * ``` - * - * @example - * - * import {Queue} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/queue.mjs'; - * - * const queue = new Queue; - * - * queue.add(2); - * queue.add(true); - * queue.add("Hello"); - * queue.add(4.5); - * - * console.log(queue.poll()); - * // ↦ 2 - * console.log(queue.poll()); - * // ↦ true - * console.log(queue.poll()); - * // ↦ "Hello" - * console.log(queue.poll()); - * // ↦ 4.5 - * console.log(queue.poll()); - * // ↦ undefined - * - * - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary A Queue (Fifo) - */ -class Queue extends Base { - - /** - * - */ - constructor() { - super(); - this.data = []; - } - - - /** - * @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; - } - - 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; - } - - /** - * 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(); - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; - -export {Stack} - -/** - * You can call the method via the monster namespace `new Monster.Types.Queue()`. - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/stack.mjs'; - * console.log(new Stack()) - * </script> - * ``` - * - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -class Stack extends Base { - - /** - * - */ - constructor() { - super(); - this.data = []; - } - - - /** - * @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; - } - - 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; - } - - /** - * 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(); - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ -import {validateString} from "./validate.mjs"; - -export {toBinary, fromBinary} - -/** - * You can call the function via the monster namespace `Monster.Types.toBinary()`. - * - * ``` - * <script type="module"> - * import {toBinary} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/binary.mjs'; - * toBinary() - * </script> - * ``` - * - * @param {String} binary - * @return {String} - * @throws {TypeError} value is not a string - * @memberOf Monster.Types - * @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 charCodes = new Uint8Array(codeUnits.buffer); - let result = ''; - - for (let i = 0; i < charCodes.byteLength; i++) { - result += String.fromCharCode(charCodes[i]); - } - - return result; -} - -/** - * You can call the function via the monster namespace `Monster.Types.fromBinary()`. - * - * ``` - * <script type="module"> - * import {fromBinary} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/binary.mjs'; - * fromBinary() - * </script> - * ``` - * - * @param {String} binary - * @return {String} - * @throws {TypeError} value is not a string - * @memberOf Monster.Types - * @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; -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; -import {extend} from "../data/extend.mjs"; -import {Pathfinder} from "../data/pathfinder.mjs"; - -import {Base} from "./base.mjs"; -import {validateObject} from "./validate.mjs"; - -export {BaseWithOptions} - -/** - * This is the base class with options from which some monster classes are derived. - * - * This class is actually only used as a base class. - * - * ```html - * <script type="module"> - * import {BaseWithOptions} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/basewithoptions.mjs'; - * new BaseWithOptions() - * </script> - * ``` - * - * Classes that require the possibility of options can be derived directly from this class. - * Derived classes almost always override the `defaul` getter with their own values. - * - * ```javascript - * class My extends BaseWithOptions { - * get defaults() { - * return Object.assign({}, super.defaults, { - * mykey: true - * }); - * } - * } - * ``` - * - * The class was formerly called Object. - * - * @since 1.13.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -class BaseWithOptions extends Base { - - /** - * - * @param {object} options - */ - constructor(options) { - super(); - - if (options === undefined) { - 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 {} - } - - /** - * 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) { - - } - - if (value === undefined) return defaultValue; - return value; - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {validateString} from "./validate.mjs"; - -export {escapeString} - -/** - * This function prefixes all special characters that may appear in a regex with a slash. - * - * ``` - * <script type="module"> - * import {escapeString} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * escapeString() - * </script> - * ``` - * - * @param {string} value - * @return {string} - * @since 1.26.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a string - */ -function escapeString(value) { - return validateString(value) - .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') - .replace(/-/g, '\\x2d'); -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {isArray, isObject, isPrimitive} from "./is.mjs"; -import {Observer} from "./observer.mjs"; -import {ObserverList} from "./observerlist.mjs"; -import {validateObject} from "./validate.mjs"; -import {extend} from "../data/extend.mjs"; - -export {ProxyObserver} - -/** - * An observer manages a callback function - * - * ``` - * <script type="module"> - * import {ProxyObserver} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/proxyobserver.mjs'; - * new ProxyObserver() - * </script> - * ``` - * - * With the ProxyObserver you can attach observer for observation. with each change at the object to be observed an update takes place. - * - * this also applies to nested objects. - * - * @example - * - * import {ProxyObserver} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/proxyobserver.mjs'; - * import {Observer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/observer.mjs'; - * import {isObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * - * const o = new Observer(function () { - * if (isObject(this) && this instanceof ProxyObserver) { - * // do something (this ist ProxyObserver) - * const subject = this.getSubject(); - * console.log(subject); - * } - * }); - * - * let realSubject = { - * a: { - * b: { - * c: true - * }, - * d: 9 - * } - * } - * - * const p = new ProxyObserver(realSubject); - * p.attachObserver(o); - * const s = p.getSubject(); - * s.a.b.c = false; - * - * @since 1.0.0 - * @copyright schukai GmbH - * @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; - } - - /** - * 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} - */ - getSubject() { - return this.subject - } - - /** - * @since 1.24.0 - * @param {Object} obj - * @return {Monster.Types.ProxyObserver} - */ - setSubject(obj) { - - let i, 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 proxied object - * - * @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) - } - -} - -/** - * - * @returns {{defineProperty: (function(*=, *=, *=): *), setPrototypeOf: (function(*, *=): boolean), set: (function(*, *, *, *): boolean), get: ((function(*=, *=, *=): (undefined))|*), deleteProperty: ((function(*, *): (boolean))|*)}} - * @private - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {isArray, isInstance} from "./is.mjs"; -import {Node} from "./node.mjs"; -import {validateInstance} from "./validate.mjs"; - -export {NodeList} - -/** - * You can create the instance via the monster namespace `new Monster.Types.NodeList()`. - * - * ``` - * <script type="module"> - * import {NodeList} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/nodelist.mjs'; - * new NodeList() - * </script> - * ``` - * - * @since 1.26.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @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'); - } - } - - /** - * - * @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)); - return false; - } - - /** - * @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; - } -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; - -export {Version, getMonsterVersion} - -/** - * The version object contains a sematic version number - * - * ``` - * <script type="module"> - * import {Version} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/version.mjs'; - * console.log(new Version('1.2.3')) // ↦ 1.2.3 - * console.log(new Version('1')) // ↦ 1.0.0 - * </script> - * ``` - * - * @example - * - * import {Version} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/version.mjs'; - * - * new Version('1.0.0') // ↦ 1.0.0 - * new Version(1) // ↦ 1.0.0 - * new Version(1, 0, 0) // ↦ 1.0.0 - * new Version('1.2.3', 4, 5) // ↦ 1.4.5 - * - * @since 1.0.0 - * @author schukai GmbH - * @copyright schukai GmbH - * @memberOf Monster.Types - * @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"); - } - - } - - /** - * - * @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 - * then 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; - -/** - * Version of monster - * - * You can call the method via the monster namespace `Monster.getVersion()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source//monster.mjs'; - * console.log(Monster.getVersion()) - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {getMonsterVersion} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/version.mjs'; - * console.log(getMonsterVersion()) - * </script> - * ``` - * - * @returns {Monster.Types.Version} - * @since 1.0.0 - * @copyright schukai GmbH - * @author schukai GmbH - * @memberOf Monster - */ -function getMonsterVersion() { - if (monsterVersion instanceof Version) { - return monsterVersion; - } - /**#@+ dont touch, replaced by make with package.json version */ - monsterVersion = new Version('1.31.0') - /**#@-*/ - - return monsterVersion; - -} -'use strict'; - -/** - * Namespace for types. - * - * @namespace Monster.Types - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {}; - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {isPrimitive} from "./is.mjs"; -import {NodeList} from './nodelist.mjs'; -import {validateInstance} from './validate.mjs'; - -export {Node} - -/** - * @private - * @type {symbol} - */ -const internalValueSymbol = Symbol('internalData'); - -/** - * @private - * @type {symbol} - */ -const treeStructureSymbol = Symbol('treeStructure'); - - -/** - * You can create the instance via the monster namespace `new Monster.Types.Node()`. - * - * ``` - * <script type="module"> - * import {Node} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/node.mjs'; - * new Node() - * </script> - * ``` - * - * @since 1.26.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary A Node Class - * @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 - } - - } - - /** - * @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, - 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"); - } - -} - -/** - * @private - * @param {Node} node - * @param {int} operand - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; - -import {Base} from './base.mjs'; -import {isInstance} from "./is.mjs"; -import {Node} from "./node.mjs"; -import {NodeList} from "./nodelist.mjs"; -import {validateInstance} from "./validate.mjs"; - -export {NodeRecursiveIterator} - -/** - * @private - * @type {symbol} - */ -const isNodeListSymbol = Symbol('isNodeList'); - -/** - * You can create the instance via the monster namespace `new Monster.Types.NodeRecursiveIterator()`. - * - * ``` - * <script type="module"> - * import {NodeRecursiveIterator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/noderecursiveiterator.mjs'; - * new NodeRecursiveIterator() - * </script> - * ``` - * - * @example - * import {Node} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/node.mjs'; - * import {NodeRecursiveIterator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/noderecursiveiterator.mjs'; - * - * const node =new Node('1') - * - * // 1 - * // 2 - * // ├ 2.1 - * // ├ 2.2 - * // └ 2.3 - * // 3 - * // 4 - * // ├ 4.1 - * // └ 4.2 - * node.appendChild( - * (new Node('2')) - * .appendChild(new Node('2.1')) - * .appendChild(new Node('2.2')) - * .appendChild(new Node('2.3'))) - * .appendChild(new Node('3')) - * .appendChild(new Node('4') - * .appendChild(new Node('4.1')) - * .appendChild(new Node('4.2'))); - * - * const iterator = new NodeRecursiveIterator(node); - * - * const result = []; - * for (const n of iterator) { - * result.push(n.value); - * } - * - * // ↦ ['1', '2', '2.1', '2.2', '2.3', '3', '4', '4.1', '4.2'] - * - * @since 1.26.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary An iterator to run recursively through a tree of nodes - */ - class NodeRecursiveIterator extends Base { - - /** - * @param {Node} [data] - */ - constructor(node) { - super(); - - this[isNodeListSymbol] = false; - - // 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); - } - - /** - * @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]; - } - - if (this[internalSymbol].hasChildNodes()) { - let childNodes = this[internalSymbol].childNodes; - - for (let node of childNodes) { - yield* new NodeRecursiveIterator(node); - } - } - - return; - } - - /** - * @param {function} callback - * @return {Monster.Types.NodeRecursiveIterator} - */ - forEach(callback) { - for (const node of this) { - callback(node); - } - return this; - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {validateFunction, validateObject, validateString} from "./validate.mjs"; - -export {getGlobal, getGlobalObject, getGlobalFunction} - -/** - * @type {objec} - * @private - */ -let globalReference; - -/** - * @private - * @throws {Error} unsupported environment. - */ -(function () { - - if (typeof globalThis === 'object') { - globalReference = globalThis; - 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 - }); - - if (typeof __monster__ === 'object') { - __monster__.globalThis = __monster__; - delete Object.prototype.__monster__; - - globalReference = globalThis; - return; - } - - try { - globalReference = Function('return this')(); - } catch (e) { - - } - - throw new Error("unsupported environment.") - - -}()); - -/** - * Return globalThis - * - * If globalThis is not available, it will be polyfilled - * - * @since 1.6.0 - * @memberOf Monster.Types - * @returns {objec} globalThis - */ -function getGlobal() { - return globalReference; -} - -/** - * Return global object or throw Error - * - * You can call the method via the monster namespace `Monster.Types.getGlobalObject()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.30.0/dist/monster.mjs'; - * Monster.Types.getGlobalObject('document') - * // ↦ { } - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {getGlobalObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.30.0/dist/modules/types/global.mjs'; - * getGlobalObject('document') - * // ↦ { } - * </script> - * ``` - * - * @since 1.6.0 - * @memberOf Monster.Types - * @param {string} name - * @returns {objec} - * @throws {Error} the object is not defined - * @throws {TypeError} value is not a object - * @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; -} - -/** - * Return global function or throw Error - * - * You can call the method via the monster namespace `Monster.Types.getGlobalFunction()`. - * - * ``` - * <script type="module"> - * import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.30.0/dist/monster.mjs'; - * console.log(Monster.Types.getGlobalFunction('parseInt')) // ↦ f parseInt() { } - * </script> - * ``` - * - * Alternatively, you can also integrate this function individually. - * - * ``` - * <script type="module"> - * import {getGlobalFunction} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.30.0/dist/modules/types/global.mjs'; - * console.log(getGlobalFunction('parseInt')) // ↦ f parseInt() { } - * </script> - * ``` - * - * @since 1.6.0 - * @memberOf Monster.Types - * @param {string} name - * @return {objec} - * @throws {TypeError} value is not a function - * @throws {Error} the function is not defined - * @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; -} - - - - -'use strict'; - - -/** - * @author schukai GmbH - */ - -import {Base} from "./base.mjs"; -import {isString} from "./is.mjs"; -import {MediaType, parseMediaType} from "./mediatype.mjs"; -import {validateBoolean, validateInstance, validateString} from "./validate.mjs"; - -export {DataUrl, parseDataURL} - -/** - * @private - * @type {symbol} - */ -const internal = Symbol('internal'); - - -/** - * You can create an object via the monster namespace `new Monster.Types.DataUrl()`. - * - * ``` - * <script type="module"> - * import {DataUrl} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/dataurl.mjs'; - * new DataUrl() - * </script> - * ``` - * - * @since 1.8.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs - * @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) - } - - - } - - 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; - } - -} - -/** - * You can call the function via the monster namespace `Monster.Types.parseDataURL()`. - * - * ``` - * <script type="module"> - * import {parseDataURL} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/dataurl.mjs'; - * parseDataURL(...) - * </script> - * ``` - * - * Specification: - * - * ``` - * dataurl := "data:" [ mediatype ] [ ";base64" ] "," data - * mediatype := [ type "/" subtype ] *( ";" parameter ) - * data := *urlchar - * parameter := attribute "=" value - * ``` - * - * @param {String} dataurl - * @return {Monster.Types.DataUrl} - * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs - * @see https://datatracker.ietf.org/doc/html/rfc2397 - * @throws {TypeError} incorrect or missing data protocol - * @throws {TypeError} malformed data url - * @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); - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import { - isArray, - isBoolean, - isFunction, - isInstance, - isInteger, - isIterable, - isObject, - isPrimitive, - isString, - isSymbol -} from './is.mjs'; - -export { - validateIterable, - validatePrimitive, - validateBoolean, - validateString, - validateObject, - validateInstance, - validateArray, - validateSymbol, - validateFunction, - validateInteger -} - -/** - * This method checks if the type matches the primitive type. this function is identical to isPrimitive() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateIterable} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateIterable('2')) // ↦ TypeError - * console.log(validateIterable([])) // ↦ value - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.2.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a primitive - * @see {@link isPrimitive} - * @see {@link Monster.Types.isPrimitive} - * @see {@link Monster.Types#isPrimitive} - */ -function validateIterable(value) { - if (!isIterable(value)) { - throw new TypeError('value is not iterable') - } - return value -} - -/** - * This method checks if the type matches the primitive type. this function is identical to isPrimitive() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validatePrimitive} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validatePrimitive('2')) // ↦ value - * console.log(validatePrimitive([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a primitive - * @see {@link isPrimitive} - * @see {@link Monster.Types.isPrimitive} - * @see {@link Monster.Types#isPrimitive} - */ -function validatePrimitive(value) { - if (!isPrimitive(value)) { - throw new TypeError('value is not a primitive') - } - return value -} - -/** - * This method checks if the type matches the boolean type. this function is identical to isBoolean() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateBoolean} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateBoolean(false)) // ↦ value - * console.log(validateBoolean('2')) // ↦ TypeError - * console.log(validateBoolean([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - - * @throws {TypeError} value is not primitive - */ -function validateBoolean(value) { - if (!isBoolean(value)) { - throw new TypeError('value is not a boolean') - } - return value -} - -/** - * This method checks if the type matches the string type. this function is identical to isString() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateString} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateString('2')) // ↦ value - * console.log(validateString([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a string - */ -function validateString(value) { - if (!isString(value)) { - throw new TypeError('value is not a string') - } - return value -} - - -/** - * This method checks if the type matches the object type. this function is identical to isObject() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateObject({})) // ↦ value - * console.log(validateObject('2')) // ↦ TypeError - * console.log(validateObject([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a object - */ -function validateObject(value) { - if (!isObject(value)) { - throw new TypeError('value is not a object') - } - return value -} - -/** - * This method checks if the type matches the object instance. - * - * ``` - * <script type="module"> - * import {validateInstance} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateInstance({}, Object)) // ↦ value - * console.log(validateInstance('2', Object)) // ↦ TypeError - * console.log(validateInstance([], Object)) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @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 (n) { - n = " " + n; - } - - throw new TypeError('value is not an instance of' + n) - } - return value -} - -/** - * This method checks if the type matches the array type. this function is identical to isArray() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateArray} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateArray('2')) // ↦ TypeError - * console.log(validateArray([])) // ↦ value - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not an array - */ -function validateArray(value) { - if (!isArray(value)) { - throw new TypeError('value is not an array') - } - return value -} - -/** - * This method checks if the type matches the symbol type. this function is identical to isSymbol() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateSymbol} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateSymbol('2')) // ↦ TypeError - * console.log(validateSymbol()) // ↦ value - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not an symbol - */ -function validateSymbol(value) { - if (!isSymbol(value)) { - throw new TypeError('value is not an symbol') - } - return value -} - -/** - * This method checks if the type matches the function type. this function is identical to isFunction() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateFunction} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateFunction(()=>{})) // ↦ value - * console.log(validateFunction('2')) // ↦ TypeError - * console.log(validateFunction([])) // ↦ TypeError - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not a function - */ -function validateFunction(value) { - if (!isFunction(value)) { - throw new TypeError('value is not a function') - } - return value -} - -/** - * This method checks if the type is an integer. this function is identical to isInteger() except that a TypeError is thrown. - * - * ``` - * <script type="module"> - * import {validateFunction} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/validate.mjs'; - * console.log(validateInteger(true)) // ↦ TypeError - * console.log(validateInteger('2')) // ↦ TypeError - * console.log(validateInteger(2)) // ↦ value - * </script> - * ``` - * - * @param {*} value - * @return {*} - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @throws {TypeError} value is not an integer - */ -function validateInteger(value) { - if (!isInteger(value)) { - throw new TypeError('value is not an integer') - } - return value -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Queue} from "./queue.mjs"; -import {validateObject} from "./validate.mjs"; - -export {UniqueQueue} - -/** - * A UniqueQueue is a queue that contains items only once. - * - * ``` - * <script type="module"> - * import {UniqueQueue} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/uniquequeue.mjs'; - * new UniqueQueue() - * </script> - * ``` - * - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary A queue for unique values - */ - class UniqueQueue extends Queue { - - /** - * - */ - constructor() { - super(); - this.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); - - if (!this.unique.has(value)) { - this.unique.add(value); - super.add(value); - } - - return this; - } - - /** - * remove all entries - * - * @returns {Queue} - */ - clear() { - super.clear(); - this.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.unique.delete(value); - return value; - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -export {Base} - -/** - * This is the base class from which all monster classes are derived. - * - * ``` - * <script type="module"> - * import {Base} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/base.mjs'; - * new Base() - * </script> - * ``` - * - * The class was formerly called Object. - * - * @since 1.5.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ - class Base extends Object { - - /** - * - * @returns {string} - */ - toString() { - return JSON.stringify(this); - }; - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -export {isIterable, isPrimitive, isSymbol, isBoolean, isString, isObject, isInstance, isArray, isFunction, isInteger} - - -/** - * With this function you can check if a value is iterable. - * - * This method is used in the library to have consistent names. - * - * You can call the method via the monster namespace `Monster.Types.isPrimitive()`. - * - * ``` - * <script type="module"> - * import {isIterable} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isIterable(null) // ↦ false - * isIterable('hello') // ↦ true - * isIterable([]) // ↦ true - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.2.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isIterable(value) { - if (value === undefined) return false; - if (value === null) return false; - return typeof value?.[Symbol.iterator] === 'function'; -} - - -/** - * Checks whether the value passed is a primitive (string, number, boolean, NaN, undefined, null or symbol) - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isPrimitive} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isPrimitive('2')) // ↦ true - * isPrimitive([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isPrimitive(value) { - var type; - - if (value === undefined || value === null) { - return true; - } - - type = typeof value; - - if (type === 'string' || type === 'number' || type === 'boolean' || type === 'symbol') { - return true; - } - - return false; -} - -/** - * Checks whether the value passed is a symbol - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isSymbol} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isSymbol(Symbol('a'))) // ↦ true - * isSymbol([]) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.9.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isSymbol(value) { - return ('symbol' === typeof value) ? true : false; -} - -/** - * Checks whether the value passed is a boolean. - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isBoolean} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isBoolean('2')) // ↦ false - * isBoolean([])) // ↦ false - * isBoolean(2>4)) // ↦ true - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isBoolean(value) { - - if (value === true || value === false) { - return true; - } - - return false; -} - -/** - * Checks whether the value passed is a string - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isString} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isString('2')) // ↦ true - * isString([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isString(value) { - if (value === undefined || typeof value !== 'string') { - return false; - } - return true; -} - -/** - * Checks whether the value passed is a object - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isObject} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isObject('2')) // ↦ false - * isObject([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isObject(value) { - - if (isArray(value)) return false; - if (isPrimitive(value)) return false; - - if (typeof value === 'object') { - return true; - } - - return false; -} - -/** - * Checks whether the value passed is a object and instance of instance. - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isInstance} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isInstance('2')) // ↦ false - * isInstance([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @param {*} instance - * @returns {boolean} - * @since 1.5.0 - * @copyright schukai GmbH - * @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; -} - -/** - * Checks whether the value passed is a array - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isArray} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isArray('2')) // ↦ false - * isArray([])) // ↦ true - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @see https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray - */ -function isArray(value) { - return Array.isArray(value); -} - -/** - * Checks whether the value passed is a function - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isFunction} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isFunction(()=>{}) // ↦ true - * isFunction('2')) // ↦ false - * isFunction([])) // ↦ false - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isFunction(value) { - if (isArray(value)) return false; - if (isPrimitive(value)) return false; - - if (typeof value === 'function') { - return true; - } - - return false; - -} - -/** - * Checks whether the value passed is an integer. - * - * This method is used in the library to have consistent names. - * - * ``` - * <script type="module"> - * import {isInteger} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/is.mjs'; - * isInteger(()=>{}) // ↦ true - * isInteger('2')) // ↦ false - * isInteger(2)) // ↦ true - * </script> - * ``` - * - * @param {*} value - * @returns {boolean} - * @since 1.4.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - */ -function isInteger(value) { - return Number.isInteger(value); -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from './base.mjs'; -import {validateString} from "./validate.mjs"; - -export {ID} - -/** - * @private - * @type {Map<string, integer>} - */ -let internalCounter = new Map; - -/** - * With the id class, sequences of ids can be created. for this purpose, an internal counter is incremented for each prefix. - * thus, the first id with the prefix `myid` will be `myid1` and the second id `myid2`. - * The ids are the same for every call, for example on a web page. - * - * So the ids can also be used for navigation. you just have to take care that the order stays the same. - * - * ``` - * <script type="module"> - * import {ID} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/types/id.mjs'; - * console.log(new ID()) - * </script> - * ``` - * - * As of version 1.6.0 there is the new RandomID. this ID class is continuous from now on. - * - * @since 1.0.0 - * @copyright schukai GmbH - * @memberOf Monster.Types - * @summary Automatic generation of ids - */ - class ID extends Base { - - /** - * create new id with prefix - * - * @param {string} prefix - */ - constructor(prefix) { - super(); - - if (prefix === undefined) { - prefix = "id"; - } - - validateString(prefix); - - if (!internalCounter.has(prefix)) { - internalCounter.set(prefix, 1); - } - - let count = internalCounter.get(prefix); - this.id = prefix + count; - - internalCounter.set(prefix, ++count); - } - - /** - * @return {string} - */ - toString() { - return this.id; - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {ID} from "../types/id.mjs"; -import {isObject} from "../types/is.mjs"; -import {validateString} from "../types/validate.mjs"; - -export {trimSpaces} - -/** - * This special trim function allows to trim spaces that have been protected by a special escape character. - * - * ``` - * <script type="module"> - * import {trimSpaces} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/trimspaces.mjs'; - * trimSpaces(' hello \\ ') - * </script> - * ``` - * - * Hint: One stroke is escaped by the javascript interpreter, the second stroke escapes the stroke. - * - * ```text - * a\ b ↦ a b - * a\\ b ↦ a\ b - * ``` - * - * @since 1.24.0 - * @memberOf Monster.Util - * @copyright schukai GmbH - * @param {string} value - * @return {string} - * @throws {TypeError} value is not a string - */ - function trimSpaces(value) { - - validateString(value); - - let placeholder = new Map; - const regex = /((?<pattern>\\(?<char>.)){1})/mig; - - // 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; - } - - 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); - } - - } - - value = value.trim(); - placeholder.forEach((v, k) => { - value = value.replace(k, '\\' + v) - }) - - return value; - -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; - -import {Base} from "../types/base.mjs"; -import {isInteger} from "../types/is.mjs"; -import {validateFunction, validateInteger} from "../types/validate.mjs"; - -export {DeadMansSwitch} - -/** - * The dead man's switch allows to set a timer which can be reset again and again within a defined period of time. - * - * ``` - * <script type="module"> - * import {DeadMansSwitch} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/deadmansswitch.mjs'; - * new DeadMansSwitch(); - * </script> - * ``` - * - * @example - * import {DeadMansSwitch} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/deadmansswitch.mjs'; - * - * const deadmansswitch = new DeadMansSwitch(100, ()=>{ - * console.log('yeah!') - * // ↦ "yeah!" - * }) - * - * deadmansswitch.touch(); // from here wait again 100 ms - * deadmansswitch.touch(200); // from here wait 200 ms - * - * @copyright schukai GmbH - * @since 1.29.0 - * @memberOf Monster.Util - * @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(); - - init.call(this, validateInteger(delay), validateFunction(callback)); - } - - /** - * - * @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') - } - - clearTimeout(this[internalSymbol]['timer']); - - initCallback.call(this); - - return this; - } -} - -/** - * @private - */ -function initCallback() { - - const self = this; - - self[internalSymbol]['timer'] = setTimeout(() => { - self[internalSymbol]['isAlreadyRun'] = true; - self[internalSymbol]['callback'](); - }, self[internalSymbol]['delay']) -} - -/** - * @private - * @param {integer} delay - * @param {function} callback - */ -function init(delay, callback) { - const self = this; - - self[internalSymbol] = { - callback, - delay, - isAlreadyRun: false, - timer: undefined - }; - - initCallback.call(self); - -} - - - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {validateObject} from '../types/validate.mjs'; - -export {deepFreeze} - -/** - * Deep freeze a object - * - * ``` - * <script type="module"> - * import {deepFreeze} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/freeze.mjs'; - * deepFreeze({}) - * </script> - * ``` - * - * @param {object} object object to be freeze - * @since 1.0.0 - * @returns {object} - * @memberOf Monster.Util - * @copyright schukai GmbH - * @throws {TypeError} value is not a object - */ - function deepFreeze(object) { - - validateObject(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]; - - object[name] = (value && typeof value === "object") ? - deepFreeze(value) : value; - } - - return Object.freeze(object); -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {isFunction} from '../types/is.mjs'; - -export {Comparator} - -/** - * The comparator allows a comparison function to be abstracted. - * - * ``` - * <script type="module"> - * import {Comparator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/comparator.mjs'; - * console.log(new Comparator()) - * </script> - * ``` - * - * The following are some examples of the application of the class. - * - * ``` - * new Comparator().lessThanOrEqual(2, 5) // ↦ true - * new Comparator().greaterThan(4, 2) // ↦ true - * new Comparator().equal(4, 4) // ↦ true - * new Comparator().equal(4, 5) // ↦ false - * ``` - * - * You can also pass your own comparison function, and thus define the comparison function. - * - * ``` - * new Comparator(function (a, b) { - * if (a.v === b.v) return 0; - * return a.v < b.v ? -1 : 1; - * }).equal({v: 2}, {v: 2}); // ↦ true - * ``` - * - * @example - * - * import {Comparator} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/comparator.mjs'; - * - * console.log(new Comparator().lessThanOrEqual(2, 5)) - * // ↦ true - * console.log(new Comparator().greaterThan(4, 2)) - * // ↦ true - * console.log(new Comparator().equal(4, 4)) - * // ↦ true - * console.log(new Comparator().equal(4, 5)) - * // ↦ false - * - * @since 1.3.0 - * @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(); - - 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") - } - - 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; - } - - /** - * 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 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 b - * - * @param {*} a - * @param {*} b - * - * @return {boolean} - */ - lessThan(a, b) { - return this.compare(a, b) < 0; - } - - -} - - -/** - * This is the description for the callback function used by the operator - * - * ``` - * new Comparator(function (a, b) { - * if (a.v === b.v) return 0; - * return a.v < b.v ? -1 : 1; - * }).equal({v: 2}, {v: 2}); // ↦ true - * ``` - * - * @callback Monster.Util~exampleCallback - * @param {*} a - * @param {*} b - * @return {integer} -1, 0 or 1 - * @memberOf Monster.Util - * @see Monster.Util.Comparator - */ - -'use strict'; - -/** - * Namespace for utilities. - * - * @namespace Monster.Util - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - - -import {getGlobal} from '../types/global.mjs'; -import {isArray, isFunction, isObject, isPrimitive} from '../types/is.mjs'; -import {typeOf} from "../types/typeof.mjs"; -import {validateObject} from "../types/validate.mjs"; - -export {clone} - -/** - * With this function, objects can be cloned. - * The entire object tree is run through. - * - * Proxy, Element, HTMLDocument and DocumentFragment instances are not cloned. - * Global objects such as windows are also not cloned, - * - * If an object has a method `getClone()`, this method is used to create the clone. - * - * ``` - * <script type="module"> - * import {clone} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/clone.mjs'; - * clone({}) - * </script> - * ``` - * - * @param {*} obj object to be cloned - * @returns {*} - * @since 1.0.0 - * @memberOf Monster.Util - * @copyright schukai GmbH - * @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."); -} - -/** - * - * @param {object} obj - * @returns {object} - * @private - */ -function cloneObject(obj) { - - validateObject(obj); - - const constructor = obj?.['constructor']; - - /** Object has clone method */ - if(typeOf(constructor)==='function') { - const prototype = constructor?.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; -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -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} - -/** - * @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) - }; - } - - /** - * @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); - - - }) - - } -} - -/** - * This class allows to execute several functions in order. - * - * 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 - * - * The result of `run()` is a promise. - * - * ``` - * <script type="module"> - * import {Processing} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/processing.mjs'; - * new Processing(); - * </script> - * ``` - * - * @example - * import {Processing} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/util/processing.mjs'; - * - * let startTime = +new Date(); - * - * new Processing((url)=>{ - * return fetch(url) - * },(response)=>{ - * // do something with the response - * console.log(response.status, +new Date()-startTime) - * },200,()=>{ - * // this function is called 200 seconds after fetch is received. - * console.log('finished', +new Date()-startTime) - * return 'done' - * }).run('https://monsterjs.org/assets/world.json').then(r=>{ - * console.log(r) - * // ↦ "done" - * }) - * - * @copyright schukai GmbH - * @since 1.21.0 - * @memberOf Monster.Util - * @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 - * - * @param {int} timeout Timeout - * @param {function} callback Callback - * @throw {TypeError} the arguments must be either integer or functions - */ - constructor() { - super(); - - this[internalSymbol] = { - queue: new Queue - }; - - let time = 0 - - for (const [, arg] of Object.entries(arguments)) { - 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; - } - - - /** - * Executes the defined functions in order. - * - * @param {*} data - * @return {Promise} - */ - run(data) { - const self = this; - if (this[internalSymbol].queue.isEmpty()) { - return Promise.resolve(data); - } - - return this[internalSymbol].queue.poll().run(data).then((result) => { - return self.run(result); - }); - - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {isArray, isObject} from "../types/is.mjs"; -import {typeOf} from "../types/typeof.mjs"; - -export {extend} - -/** - * Extend copies all enumerable own properties from one or - * more source objects to a target object. It returns the modified target object. - * - * ``` - * <script type="module"> - * import {extend} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/extend.mjs'; - * extend(a, b) - * </script> - * ``` - * - * @param {object} target - * @param {object} - * @return {object} - * @since 1.10.0 - * @copyright schukai GmbH - * @memberOf Monster.Data - * @throws {Error} unsupported argument - * @throws {Error} type mismatch - */ -function extend() { - let o, i; - - for (i = 0; i < arguments.length; i++) { - let a = arguments[i]; - - if (!(isObject(a) || isArray(a))) { - throw new Error('unsupported argument ' + JSON.stringify(a)); - } - - if (o === undefined) { - o = a; - continue; - } - - for (let k in a) { - - let v = a?.[k]; - - 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) + ")"); - } - } - - o[k] = extend(o[k], v); - - } else { - o[k] = v; - } - - } - } - - return o; -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {Base} from '../types/base.mjs'; -import {validateString} from '../types/validate.mjs'; -import {Transformer} from './transformer.mjs'; - -export {Pipe} - -const DELIMITER = '|'; - -/** - * The pipe class makes it possible to combine several processing steps. - * - * ``` - * <script type="module"> - * import {Pipe} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pipe.mjs'; - * new Pipe() - * </script> - * ``` - * - * A pipe consists of commands whose input and output are connected with the pipe symbol `|`. - * - * With the Pipe, processing steps can be combined. Here, the value of an object is accessed via the pathfinder (path command). - * the word is then converted to uppercase letters and a prefix Hello is added. the two backslash safe the space char. - * - * @example - * import {Pipe} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pipe.mjs'; - * - * let obj = { - * a: { - * b: { - * c: { - * d: "world" - * } - * } - * } - * } - * - * console.log(new Pipe('path:a.b.c.d | toupper | prefix:Hello\\ ').run(obj)); - * // ↦ Hello WORLD - * - * @since 1.5.0 - * @copyright schukai GmbH - * @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); - - 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); - } - - return this; - } - - /** - * run a pipe - * - * @param {*} value - * @returns {*} - */ - run(value) { - return this.pipe.reduce((accumulator, transformer, currentIndex, array) => { - return transformer.run(accumulator); - }, value); - } -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {isFunction, isObject, isString} from "../types/is.mjs"; -import {validateString} from "../types/validate.mjs"; -import {clone} from "../util/clone.mjs"; -import {DELIMITER, Pathfinder, WILDCARD} from "./pathfinder.mjs"; - -export {buildMap, PARENT, assembleParts} - -/** - * @type {string} - * @memberOf Monster.Data - */ -const PARENT = '^'; - - -/** - * With the help of the function `buildMap()`, maps can be easily created from data objects. - * - * Either a simple definition `a.b.c` or a template `${a.b.c}` can be specified as the path. - * Key and value can be either a definition or a template. The key does not have to be defined. - * - * ``` - * <script type="module"> - * import {buildMap} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/buildmap.mjs'; - * console.log(buildMap()) - * </script> - * ``` - * - * The templates determine the appearance of the keys and the value of the map. Either a single value `id` can be taken or a composite key `${id} ${name}` can be used. - * - * If you want to access values of the parent data set, you have to use the `^` character `${id} ${^.name}`. - * - * @example - * - * import {buildMap} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/buildmap.mjs'; - * // a typical data structure as reported by an api - * - * let map; - * let obj = { - * "data": [ - * { - * "id": 10, - * "name": "Cassandra", - * "address": { - * "street": "493-4105 Vulputate Street", - * "city": "Saumur", - * "zip": "52628" - * } - * }, - * { - * "id": 20, - * "name": "Holly", - * "address": { - * "street": "1762 Eget Rd.", - * "city": "Schwalbach", - * "zip": "952340" - * } - * }, - * { - * "id": 30, - * "name": "Guy", - * "address": { - * "street": "957-388 Sollicitudin Avenue", - * "city": "Panchià", - * "zip": "420729" - * } - * } - * ] - * }; - * - * // The function is passed this data structure and with the help of the selector `'data.*'` the data to be considered are selected. - * // The key is given by a simple definition `'id'` and the value is given by a template `'${name} (${address.zip} ${address.city})'`. - * map = buildMap(obj, 'data.*', '${name} (${address.zip} ${address.city})', 'id'); - * console.log(map); - * - * // ↦ Map(3) { - * // '10' => 'Cassandra (52628 Saumur)', - * // '20' => 'Holly (952340 Schwalbach)', - * // '30' => 'Guy (420729 Panchià)' - * // } - * - * // If no key is specified, the key from the selection, here the array index, is taken. - * map = buildMap(obj, 'data.*', '${name} (${address.zip} ${address.city})'); - * console.log(map); - * - * // ↦ Map(3) { - * // '0' => 'Cassandra (52628 Saumur)', - * // '1' => 'Holly (952340 Schwalbach)', - * // '2' => 'Guy (420729 Panchià)' - * // } - * - * // a filter (function(value, key) {}) can be specified to accept only defined entries. - * map = buildMap(obj, 'data.*', '${name} (${address.zip} ${address.city})', 'id', function (value, key) { - * return (value['id'] >= 20) ? true : false - * }); - * console.log(map); - * - * // ↦ Map(2) { - * // 20 => 'Holly (952340 Schwalbach)', - * // 30 => 'Guy (420729 Panchià)' - * // } - * - * @param {*} subject - * @param {string|Monster.Data~exampleSelectorCallback} selector - * @param {string} [valueTemplate] - * @param {string} [keyTemplate] - * @param {Monster.Data~exampleFilterCallback} [filter] - * @return {*} - * @memberOf Monster.Data - * @throws {TypeError} value is neither a string nor a function - * @throws {TypeError} the selector callback must 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); - }); - -} - - -/** - * @private - * @param {*} subject - * @param {string|Monster.Data~exampleSelectorCallback} selector - * @param {Monster.Data~exampleFilterCallback} [filter] - * @param {function} callback - * @return {Map} - * @throws {TypeError} selector is neither a string nor a function - */ -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; -} - -/** - * @private - * @param subject - * @param selector - * @param key - * @param parentMap - * @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 = "", 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; - -} - - -/** - * With the help of this filter callback, values can be filtered out. Only if the filter function returns true, the value is taken for the map. - * - * @callback Monster.Data~exampleFilterCallback - * @param {*} value Value - * @param {string} key Key - * @memberOf Monster.Data - * @see {@link Monster.Data.buildMap} - */ - -/** - * Alternatively to a string selector a callback can be specified. this must return a map. - * - * @example - * import {buildMap} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/buildmap.mjs'; - * - * let obj = { - * "data": [ - * { - * "id": 10, - * "name": "Cassandra", - * "enrichment": { - * variants: [ - * { - * sku: 1, label: "XXS", price: [ - * {vk: '12.12 €'}, - * {vk: '12.12 €'} - * ] - * }, - * { - * sku: 2, label: "XS", price: [ - * {vk: '22.12 €'}, - * {vk: '22.12 €'} - * ] - * }, - * { - * sku: 3, label: "S", price: [ - * {vk: '32.12 €'}, - * {vk: '32.12 €'} - * ] - * }, - * { - * sku: 4, label: "L", price: [ - * {vk: '42.12 €'}, - * {vk: '42.12 €'} - * ] - * } - * ] - * - * } - * }, - * { - * "id": 20, - * "name": "Yessey!", - * "enrichment": { - * variants: [ - * { - * sku: 1, label: "XXS", price: [ - * {vk: '12.12 €'}, - * {vk: '12.12 €'} - * ] - * }, - * { - * sku: 2, label: "XS", price: [ - * {vk: '22.12 €'}, - * {vk: '22.12 €'} - * ] - * }, - * { - * sku: 3, label: "S", price: [ - * {vk: '32.12 €'}, - * {vk: '32.12 €'} - * ] - * }, - * { - * sku: 4, label: "L", price: [ - * {vk: '42.12 €'}, - * {vk: '42.12 €'} - * ] - * } - * ] - * - * } - * } - * ] - * }; - * - * let callback = function (subject) { - * let m = new Map; - * - * for (const [i, b] of Object.entries(subject.data)) { - * - * let key1 = i; - * - * for (const [j, c] of Object.entries(b.enrichment.variants)) { - * let key2 = j; - * - * for (const [k, d] of Object.entries(c.price)) { - * - * let key3 = k; - * - * d.name = b.name; - * d.label = c.label; - * d.id = [key1, key2, key3].join('.'); - * - * m.set(d.id, d); - * } - * - * } - * } - * return m; - * } - * - * let map = buildMap(obj, callback, '${name} ${vk}', '${id}') - * - * // ↦ Map(3) { - * // "0.0.0":"Cassandra 12.12 €", - * // "0.0.1":"Cassandra 12.12 €", - * // "0.1.0":"Cassandra 22.12 €", - * // "0.1.1":"Cassandra 22.12 €", - * // "0.2.0":"Cassandra 32.12 €", - * // "0.2.1":"Cassandra 32.12 €", - * // "0.3.0":"Cassandra 42.12 €", - * // "0.3.1":"Cassandra 42.12 €", - * // "1.0.0":"Yessey! 12.12 €", - * // "1.0.1":"Yessey! 12.12 €", - * // "1.1.0":"Yessey! 22.12 €", - * // "1.1.1":"Yessey! 22.12 €", - * // "1.2.0":"Yessey! 32.12 €", - * // "1.2.1":"Yessey! 32.12 €", - * // "1.3.0":"Yessey! 42.12 €", - * // "1.3.1":"Yessey! 42.12 €" - * // } - * - * @callback Monster.Data~exampleSelectorCallback - * @param {*} subject subject - * @return Map - * @since 1.17.0 - * @memberOf Monster.Data - * @see {@link Monster.Data.buildMap} - */ - -/** - * @private - * @param {*} subject - * @param {string|undefined} definition - * @param {*} defaultValue - * @return {*} - */ -function build(subject, definition, defaultValue) { - 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)]; - - let finder = new Pathfinder(subject); - - if (array.length === 0) { - return finder.getVia(definition); - } - - array.forEach((a) => { - let groups = a?.['groups']; - let placeholder = groups?.['placeholder'] - if (placeholder === undefined) return; - - let path = groups?.['path'] - - let v = finder.getVia(path); - if (v === undefined) v = defaultValue; - - definition = definition.replaceAll(placeholder, v); - - - }) - - return definition; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../constants.mjs"; - -import {Base} from "../types/base.mjs"; -import {parseDataURL} from "../types/dataurl.mjs"; -import {isString} from "../types/is.mjs"; -import {ProxyObserver} from "../types/proxyobserver.mjs"; -import {validateObject} from "../types/validate.mjs"; -import {extend} from "./extend.mjs"; -import {Pathfinder} from "./pathfinder.mjs"; - -export {Datasource} - -/** - * @private - * @type {symbol} - * @memberOf Monster.Data - * @since 1.24.0 - */ -const internalDataSymbol = Symbol('internalData'); - -/** - * The datasource class is the basis for dealing with different data sources. - * It provides a unified interface for accessing data - * - * ``` - * <script type="module"> - * import {Datasource} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource.mjs'; - * new Datasource() - * </script> - * ``` - * - * @example - * - * import {Datasource} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource.mjs' - * - * class MyDatasource extends Datasource { - * - * } - * - * const ds = new MyDatasource(); - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data - * @summary The datasource class encapsulates the access to data objects. - */ -class Datasource extends Base { - - /** - * - */ - 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; - } - -} - -/** - * @private - * @param data - * @return {Object} - * @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 {}; -} -'use strict'; - -/** - * In this namespace you will find classes and methods for handling data. - * - * @namespace Monster.Data - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - - -import {isArray, isObject} from "../types/is.mjs"; -import {Node} from "../types/node.mjs"; -import {NodeList} from "../types/nodelist.mjs"; -import {assembleParts} from "./buildmap.mjs"; -import {extend} from "./extend.mjs"; - -export {buildTree} - -/** - * @private - * @type {symbol} - */ -const parentSymbol = Symbol('parent'); - -/** - * @private - * @type {symbol} - */ -const rootSymbol = Symbol('root'); - -/** - * @typedef {Object} buildTreeOptions - * @property {array} options.rootReferences=[null, undefined] defines the values for elements without parents - * @property {Monster.Data~exampleFilterCallback} options.filter filtering of the values - * @memberOf Monster.Data - */ - -/** - * With the help of the function `buildTree()`, nodes can be easily created from data objects. - * - * ``` - * <script type="module"> - * import {buildTree} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/buildtree.mjs'; - * buildTree() - * </script> - * ``` - * - * @param {*} subject - * @param {string|Monster.Data~exampleSelectorCallback} selector - * @param {string} idKey - * @param {string} parentIDKey - * @param {buildTreeOptions} [options] - * @return {*} - * @memberOf Monster.Data - * @throws {TypeError} value is neither a string nor a function - * @throws {TypeError} the selector callback must return a map - * @throws {Error} the object has no value for the specified id - * @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; -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {getGlobal, getGlobalObject} from "../types/global.mjs"; -import {ID} from '../types/id.mjs'; -import {isArray, isObject, isString} from '../types/is.mjs'; -import { - validateFunction, - validateInteger, - validateObject, - validatePrimitive, - validateString -} from '../types/validate.mjs'; -import {clone} from "../util/clone.mjs"; -import {Pathfinder} from "./pathfinder.mjs"; - -export {Transformer} - -/** - * The transformer class is a swiss army knife for manipulating values. especially in combination with the pipe, processing chains can be built up. - * - * ``` - * <script type="module"> - * import {Transformer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/transformer.mjs'; - * new Transformer() - * </script> - * ``` - * - * A simple example is the conversion of all characters to lowercase. for this purpose the command `tolower` must be used. - * - * ``` - * let t = new Transformer('tolower').run('ABC'); // ↦ abc - * ``` - * - * **all commands** - * - * in the following table all commands, parameters and existing aliases are described. - * - * | command | parameter | alias | description | - * |:-------------|:---------------------------|:------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| - * | to-base64 | | base64, btob | Converts the value to base64 | - * | from-base64 | | atob | Converts the value from base64 | - * | call | function:param1:param2:... | | Calling a callback function. The function can be defined in three places: either globally, in the context `addCallback` or in the passed object | - * | default | value:type | ?? | If the value is undefined the first argument is returned, otherwise the value. The third optional parameter specifies the desired type. If no type is specified, string is used. Valid types are bool, string, int, float, undefined and object. An object default value must be specified as a base64 encoded json string. (since 1.12.0) | - * | debug | | | the passed value is output (console) and returned | - * | empty | | | Return empty String "" | - * | first-key | default | | Can be applied to objects and returns the value of the first key. All keys of the object are fetched and sorted. (since 1.23.0) | - * | fromjson | | | Type conversion from a JSON string (since 1.12.0) | - * | if | statement1:statement2 | ? | Is the ternary operator, the first parameter is the valid statement, the second is the false part. To use the current value in the queue, you can set the value keyword. On the other hand, if you want to have the static string "value", you have to put one backslash \\ in front of it and write value. the follow values are true: 'on', true, 'true'. If you want to have a space, you also have to write \\ in front of the space. | - * | index | key:default | property, key | Fetches a value from an object, an array, a map or a set | - * | last-key | default | | Can be applied to objects and returns the value of the last key. All keys of the object are fetched and sorted. (since 1.23.0) | - * | length | | count | Length of the string or entries of an array or object | - * | nop | | | Do nothing | - * | nth-key | index:default | | Can be applied to objects and returns the value of the nth key. All keys of the object are fetched and sorted. (since 1.23.0) | - * | nth-last-key | index:default | | Can be applied to objects and returns the value of the nth key from behind. All keys of the object are fetched and sorted. (since 1.23.0) | - * | path | path | | The access to an object is done via a Pathfinder object | - * | path-exists | path | | Check if the specified path is available in the value (since 1.24.0) | - * | plaintext | | plain | All HTML tags are removed (*) | - * | prefix | text | | Adds a prefix | - * | rawurlencode | | | URL coding | - * | static | | none | The Arguments value is used and passed to the value. Special characters \ <space> and : can be quotet by a preceding \. | - * | substring | start:length | | Returns a substring | - * | suffix | text | | Adds a suffix | - * | tointeger | | | Type conversion to an integer value | - * | tojson | | | Type conversion to a JSON string (since 1.8.0) | - * | tolower | | strtolower, tolowercase | The input value is converted to lowercase letters | - * | tostring | | | Type conversion to a string. | - * | toupper | | strtoupper, touppercase | The input value is converted to uppercase letters | - * | trim | | | Remove spaces at the beginning and end | - * | ucfirst | | | First character large | - * | ucwords | | | Any word beginning large | - * | undefined | | | Return undefined | - * | uniqid | | | Creates a string with a unique value (**) - * - * (*) for this functionality the extension [jsdom](https://www.npmjs.com/package/jsdom) must be loaded in the nodejs context. - * - * ``` - * // polyfill - * if (typeof window !== "object") { - * const {window} = new JSDOM('', { - * url: 'http://example.com/', - * pretendToBeVisual: true - * }); - * - * [ - * 'self', - * 'document', - * 'Node', - * 'Element', - * 'HTMLElement', - * 'DocumentFragment', - * 'DOMParser', - * 'XMLSerializer', - * 'NodeFilter', - * 'InputEvent', - * 'CustomEvent' - * ].forEach(key => (global[key] = window[key])); - * } - * ``` - * - * (**) for this command the crypt library is necessary in the nodejs context. - * - * ``` - * import * as Crypto from "@peculiar/webcrypto"; - * global['crypto'] = new Crypto.Crypto(); - * ``` - * - * @example - * - * import {Transformer} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/transformer.mjs'; - * - * const transformer = new Transformer("tolower") - * - * console.log(transformer.run("HELLO")) - * // ↦ hello - * - * console.log(transformer.run("WORLD")) - * // ↦ world - * - * @since 1.5.0 - * @copyright schukai GmbH - * @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} command - * @returns {array} - * @private - */ -function disassemble(command) { - - validateString(command); - - let placeholder = new Map; - const regex = /((?<pattern>\\(?<char>.)){1})/mig; - - // 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 -} - -/** - * tries to make a string out of value and if this succeeds to return it back - * - * @param {*} value - * @returns {string} - * @private - */ -function convertToString(value) { - - if (isObject(value) && value.hasOwnProperty('toString')) { - value = value.toString(); - } - - validateString(value) - return value; -} - -/** - * - * @param {*} value - * @returns {*} - * @private - * @throws {Error} unknown command - * @throws {TypeError} unsupported type - * @throws {Error} type not supported - * @throws {Error} missing key parameter - */ -function transform(value) { - - const console = getGlobalObject('console'); - - let args = clone(this.args); - let key, defaultValue; - - switch (this.command) { - - case 'static': - return this.args.join(':'); - - case 'tolower': - case 'strtolower': - case 'tolowercase': - validateString(value) - return value.toLowerCase(); - - 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 'tojson': - return JSON.stringify(value); - - 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 (falseStatement === 'value') { - falseStatement = value; - } - if (falseStatement === '\\value') { - falseStatement = 'value'; - } - - 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 '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") - - - default: - throw new Error("unknown command " + this.command) - } - - return value; -} - -'use strict'; - -/** - * @author schukai GmbH - */ - -import {Base} from '../types/base.mjs'; -import {isArray, isInteger, isObject, isPrimitive} from '../types/is.mjs'; -import {Stack} from "../types/stack.mjs"; -import {validateInteger, validateString} from '../types/validate.mjs'; - -export {Pathfinder, DELIMITER, WILDCARD} - -/** - * path separator - * - * @private - * @type {string} - */ -const DELIMITER = '.'; - -/** - * @private - * @type {string} - */ -const WILDCARD = '*'; - -/** - * Pathfinder is a class to find a path to an object. - * - * ``` - * <script type="module"> - * import {Pathfinder} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pathfinder.mjs'; - * console.log(new Pathfinder()) - * </script> - * ``` - * - * With the help of the pathfinder, values can be read and written from an object construct. - * - * ``` - * new Pathfinder({ - * a: { - * b: { - * f: [ - * { - * g: false, - * } - * ], - * } - * } - * }).getVia("a.b.f.0.g"); // ↦ false - * ``` - * - * if a value is not present or has the wrong type, a corresponding exception is thrown. - * - * ``` - * new Pathfinder({}).getVia("a.b.f.0.g"); // ↦ Error - * ``` - * - * The `Pathfinder.exists()` method can be used to check whether access to the path is possible. - * - * ``` - * new Pathfinder({}).exists("a.b.f.0.g"); // ↦ false - * ``` - * - * pathfinder can also be used to build object structures. to do this, the `Pathfinder.setVia()` method must be used. - * - * ``` - * obj = {}; - * new Pathfinder(obj).setVia('a.b.0.c', true); // ↦ {a:{b:[{c:true}]}} - * ``` - * - * @example - * - * import {Pathfinder} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pathfinder.mjs'; - * - * let value = new Pathfinder({ - * a: { - * b: { - * f: [ - * { - * g: false, - * } - * ], - * } - * } - * }).getVia("a.b.f.0.g"); - * - * console.log(value); - * // ↦ false - * - * try { - * new Pathfinder({}).getVia("a.b.f.0.g"); - * } catch(e) { - * console.log(e.toString()); - * // ↦ Error: the journey is not at its end (b.f.0.g) - * } - * - * @example - * - * import {Pathfinder} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/pathfinder.mjs'; - * - * let p = new Pathfinder({ - * a: { - * x: [ - * {c: 1}, {c: 2} - * ], - * y: true - * }, - * b: { - * x: [ - * {c: 1, d: false}, {c: 2} - * ], - * y: true - * }, - * }); - * - * let r = p.getVia("*.x.*.c"); - * console.log(r); - * - * @since 1.4.0 - * @copyright schukai GmbH - * @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} 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, validateString(path)); - } - - /** - * - * @param {string} 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) { - validateString(path); - setValueViaPath.call(this, this.object, path, value); - return this; - } - - /** - * Delete Via Path - * - * @param {string} 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) { - validateString(path); - deleteValueViaPath.call(this, this.object, path); - return this; - } - - /** - * - * @param {string} 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) { - validateString(path); - try { - getValueViaPath.call(this, this.object, path, true); - return true; - } catch (e) { - - } - - return false; - } - -} - - -/** - * - * @param {*} subject - * @param {string} path - * @param {string} check - * @return {Map} - * @throws {TypeError} unsupported type - * @throws {Error} the journey is not at its end - * @throws {Error} unsupported action for this data type - * @private - */ -function iterate(subject, path, check) { - - const result = new Map; - - 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; - - -} - -/** - * - * @param {*} subject - * @param [string} path - * @param [boolean} check - * @returns {*} - * @throws {TypeError} unsupported type - * @throws {Error} the journey is not at its end - * @throws {Error} unsupported action for this data type - * @private - */ -function getValueViaPath(subject, path, check) { - - if (path === "") { - return subject; - } - - let 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) - -} - -/** - * - * @param object - * @param path - * @param value - * @returns {void} - * @throws {TypeError} unsupported type - * @throws {TypeError} unsupported type - * @throws {Error} the journey is not at its end - * @throws {Error} unsupported action for this data type - * @private - */ -function setValueViaPath(object, path, value) { - - validateString(path); - - let 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, object, 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, object, current, obj); - } - - let anchor = getValueViaPath.call(this, object, subpath); - - if (!isObject(object) && !isArray(object)) { - throw TypeError("unsupported type: " + typeof object); - } - - 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); - } - - -} - -/** - * @private - * @param {object} object - * @param {string} key - * @param {*} value - */ -function assignProperty(object, key, value) { - - if (!object.hasOwnProperty(key)) { - object[key] = value; - return; - } - - if (value === undefined) { - delete object[key]; - } - - object[key] = value; - -} - -/** - * - * @param object - * @param path - * @returns {void} - * @throws {TypeError} unsupported type - * @throws {TypeError} unsupported type - * @throws {Error} the journey is not at its end - * @throws {Error} unsupported action for this data type - * @since 1.6.0 - * @private - */ -function deleteValueViaPath(object, path) { - - const parts = path.split(DELIMITER) - let last = parts.pop(); - const subpath = parts.join(DELIMITER); - - const anchor = getValueViaPath.call(this, object, 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]; - } - - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {isArray, isObject} from "../types/is.mjs"; -import {typeOf} from "../types/typeof.mjs"; - -export {diff} - -/** - * With the diff function you can perform the change of one object to another. The result shows the changes of the second object to the first object. - * - * The operator `add` means that something has been added to the second object. `delete` means that something has been deleted from the second object compared to the first object. - * - * ``` - * <script type="module"> - * import {Diff} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/diff.mjs'; - * Diff(a, b) - * </script> - * ``` - * - * @example - * - * import {Diff} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/diff.mjs'; - * - * // given are two objects x and y. - * - * let x = { - * a: 1, - * b: "Hello!" - * } - * - * let y = { - * a: 2, - * c: true - * } - * - * // These two objects can be compared with each other. - * - * console.log(Diff(x, y)); - * - * // the result is then the following - * - * // - * // [ - * // { - * // operator: 'update', - * // path: [ 'a' ], - * // first: { value: 1, type: 'number' }, - * // second: { value: 2, type: 'number' } - * // }, - * // { - * // operator: 'delete', - * // path: [ 'b' ], - * // first: { value: 'Hello!', type: 'string' } - * // }, - * // { - * // operator: 'add', - * // path: [ 'c' ], - * // second: { value: true, type: 'boolean' } - * // } - * // ] - * - * @param {*} first - * @param {*} second - * @return {array} - * @since 1.6.0 - * @copyright schukai GmbH - * @memberOf Monster.Data - */ -function diff(first, second) { - return doDiff(first, second) -} - -/** - * @private - * @param a - * @param b - * @param type - * @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))); -} - -/** - * @private - * @param a - * @param b - * @param path - * @param diff - * @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; - -} - -/** - * - * @param {*} a - * @param {*} b - * @param {string} operator - * @param {array} path - * @return {{path: array, operator: string}} - * @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; -} - -/** - * @private - * @param {*} a - * @param {*} b - * @return {boolean} - */ -function isNotEqual(a, b) { - - if (typeof a !== typeof b) { - return true; - } - - if (a instanceof Date && b instanceof Date) { - return a.getTime() !== b.getTime(); - } - - return a !== b; -} - -/** - * @private - * @param {*} a - * @param {*} 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; - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../constants.mjs"; -import {isObject} from "../../types/is.mjs"; -import {Datasource} from "../datasource.mjs"; -import {Pathfinder} from "../pathfinder.mjs"; -import {Pipe} from "../pipe.mjs"; -import {WriteError} from "./restapi/writeerror.mjs"; - -export {RestAPI} - -/** - * The RestAPI is a class that enables a REST API server. - * - * ``` - * <script type="module"> - * import {RestAPI} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/restapi.mjs'; - * new RestAPI() - * </script> - * ``` - * - * @example - * - * import {RestAPI} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/restapi.mjs'; - * - * const ds = new RestAPI({ - * url: 'https://httpbin.org/get' - * },{ - * url: 'https://httpbin.org/post' - * }); - * - * ds.set({flag:true}) - * ds.write().then(()=>console.log('done')); - * ds.read().then(()=>console.log('done')); - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource - * @summary The LocalStorage class encapsulates the access to data objects. - */ -class RestAPI extends Datasource { - - /** - * - * @param {Object} [readDefinition] An options object containing any custom settings that you want to apply to the read request. - * @param {Object} [writeDefinition] An options object containing any custom settings that you want to apply to the write request. - * @throws {TypeError} value is not a string - */ - constructor(readDefinition, writeDefinition) { - super(); - - const options = {} - - if (isObject(readDefinition)) options.read = readDefinition; - if (isObject(writeDefinition)) options.write = writeDefinition; - - this.setOptions(options); - - } - - /** - * @property {string} url=undefined Defines the resource that you wish to fetch. - * @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 {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 {Object} write.report - * @property {String} write.report.path Path to validations - * @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing. - * @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', - }, - acceptedStatus: [200, 201], - url: undefined, - mapping: { - transformer: undefined, - callbacks: [] - }, - report: { - path: undefined - } - }, - read: { - init: { - method: 'GET' - }, - 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 response; - - let init = self.getOption('read.init'); - if (!isObject(init)) init = {}; - - return fetch(self.getOption('read.url'), init).then(resp => { - response = resp; - - const acceptedStatus = self.getOption('read.acceptedStatus', [200]); - - if (acceptedStatus.indexOf(resp.status) === -1) { - throw Error('the data cannot be read (response ' + resp.status + ')') - } - - return resp.text() - }).then(body => { - - let obj; - - try { - obj = JSON.parse(body); - - } catch (e) { - - if (body.length > 100) { - body = body.substring(0, 97) + '...'; - } - - throw new Error('the response does not contain a valid json (actual: ' + body + ').'); - } - - let transformation = self.getOption('read.mapping.transformer'); - if (transformation !== undefined) { - const pipe = new Pipe(transformation); - obj = pipe.run(obj); - } - - self.set(obj); - return response; - }) - } - - /** - * @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' - } - } - - let obj = self.get(); - let transformation = self.getOption('write.mapping.transformer'); - if (transformation !== undefined) { - const pipe = new Pipe(transformation); - obj = pipe.run(obj); - } - - let sheathingObject = self.getOption('write.sheathing.object'); - let sheathingPath = self.getOption('write.sheathing.path'); - let reportPath = self.getOption('write.report.path'); - - if (sheathingObject && sheathingPath) { - const sub = obj; - obj = sheathingObject; - (new Pathfinder(obj)).setVia(sheathingPath, sub); - } - - init['body'] = JSON.stringify(obj); - - return fetch(self.getOption('write.url'), init).then(response => { - - const acceptedStatus = self.getOption('write.acceptedStatus', [200, 2001]); - - if (acceptedStatus.indexOf(response.status) === -1) { - - return response.text().then((body) => { - - let obj, validation; - try { - obj = JSON.parse(body); - validation = new Pathfinder(obj).getVia(reportPath) - - } catch (e) { - - if (body.length > 100) { - body = body.substring(0, 97) + '...'; - } - - throw new Error('the response does not contain a valid json (actual: ' + body + ').'); - } - - throw new WriteError('the data cannot be written (response ' + response.status + ')', response, validation) - - }) - - - } - - return response; - }); - } - - - /** - * @return {RestAPI} - */ - getClone() { - const self = this; - return new RestAPI(self[internalSymbol].getRealSubject()['options'].read, self[internalSymbol].getRealSubject()['options'].write); - } - -} - - -/** - * This callback can be passed to a datasource and is used to adapt data structures. - * - * @callback Monster.Data.Datasource~exampleCallback - * @param {*} value Value - * @param {string} key Key - * @memberOf Monster.Data - * @see Monster.Data.Datasource - */ -'use strict'; - -/** - * Namespace for datasources - * - * @namespace Monster.Data.Datasource - * @memberOf Monster.Data - * @author schukai GmbH - */ -const ns = {};'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../../constants.mjs"; -import {getGlobalObject} from "../../../types/global.mjs"; -import {Datasource} from "../../datasource.mjs"; -import {Storage, storageObjectSymbol} from "../storage.mjs"; - -export {SessionStorage} - -/** - * The SessionStorage class provides a data source that uses the SessionStorage API on the client. - * - * ``` - * <script type="module"> - * import {SessionStorage} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/storage/sessionstorage.mjs'; - * new SessionStorage() - * </script> - * ``` - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource.Storage - * @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'); - } - - /** - * Create Clone - * - * @return {SessionStorage} - */ - getClone() { - const self = this; - return new SessionStorage(self[internalSymbol].getRealSubject()['options'].key); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../../constants.mjs"; -import {getGlobalObject} from "../../../types/global.mjs"; -import {Datasource} from "../../datasource.mjs"; -import {Storage, storageObjectSymbol} from "../storage.mjs"; - -export {LocalStorage} - -/** - * The LocalStorage Datasource provides a data store in the browser localStorage. - * - * ``` - * <script type="module"> - * import {LocalStorage} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/storage/localstorage.mjs'; - * new LocalStorage() - * </script> - * ``` - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource.Storage - * @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'); - } - - /** - * Create clone - * @return {LocalStorage} - */ - getClone() { - const self = this; - return new LocalStorage(self[internalSymbol].getRealSubject()['options'].key); - } - - -} -'use strict'; - -/** - * Namespace for storages - * - * @namespace Monster.Data.Datasource.Storage - * @memberOf Monster.Data.Datasource - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * Namespace for storages - * - * @namespace Monster.Data.Datasource.RestAPI - * @memberOf Monster.Data.Datasource - * @author schukai GmbH - */ -const ns = {}; -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../../constants.mjs"; - -export {WriteError} - -/** - * Error message for API requests with extension of request and validation. - * - * @since 1.24.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource.RestAPI - * @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 - }; - } - - /** - * @return {Response} - */ - getResponse() { - return this[internalSymbol]['response'] - } - - /** - * @return {Object} - */ - getValidation() { - return this[internalSymbol]['validation'] - } -} -'use strict'; - -/** - * @author schukai GmbH - */ - -import {internalSymbol} from "../../constants.mjs"; -import {validateString} from "../../types/validate.mjs"; -import {Datasource} from "../datasource.mjs"; - -export {Storage, storageObjectSymbol} - -/** - * @private - * @type {symbol} - */ -const storageObjectSymbol = Symbol('storageObject'); - -/** - * The class represents a record. - * - * ``` - * <script type="module"> - * import {Storage} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/storage.mjs'; - * new Storage() - * </script> - * ``` - * - * @example - * - * import {Storage} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/data/datasource/storage.mjs'; - * - * new Datasource(); - * - * @since 1.22.0 - * @copyright schukai GmbH - * @memberOf Monster.Data.Datasource - * @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)); - } - - /** - * @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") - } - - /** - * @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](); - - 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; - - 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)); - } - - resolve(); - }) - } - - /** - * @return {Storage} - */ - getClone() { - const self=this; - return new Storage(self[internalSymbol].getRealSubject()['options'].key); - } - -} -'use strict'; - -/** - * @author schukai GmbH - */ - - -import {getGlobal} from '../types/global.mjs'; - -export {random} - -/** - * this function uses crypt and returns a random number. - * - * ``` - * <script type="module"> - * import {random} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@latest/source/math/random.mjs'; - * random(1,10) - * // ↦ 5 - * </script> - * ``` - * - * @param {number} min starting value of the definition set (default is 0) - * @param {number} max end value of the definition set (default is 1000000000) - * @returns {number} - * @memberOf Monster.Math - * @throws {Error} missing crypt - * @throws {Error} we cannot generate numbers larger than 53 bits. - * @throws {Error} the distance is too small to create a random number. - - * @since 1.0.0 - * @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)); - -} - -/** - * @private - * @type {number} - */ -var MAX = 1000000000; - - -Math.log2 = Math.log2 || function (n) { - return Math.log(n) / Math.log(2); -}; - -/** - * - * @param {number} min - * @param {number} max - * @returns {number} - * @private - * @throws {Error} missing crypt - * @throws {Error} we cannot generate numbers larger than 53 bits. - * @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; - -} -'use strict'; - -/** - * Namespace for math. - * - * @namespace Monster.Math - * @memberOf Monster - * @author schukai GmbH - */ -const ns = {}; \ No newline at end of file diff --git a/development/package.json b/development/package.json index 1814a63fa1dfc6ecc789c8fe0b598f130b73088e..1aa38cb4a12320c9cff0aad6f5c0aea9852f88f4 100644 --- a/development/package.json +++ b/development/package.json @@ -1,6 +1,6 @@ { "name": "monster", - "version": "0.1.17", + "version": "0.1.18", "description": "monster", "repository": { "type": "git", diff --git a/release.json b/release.json index 466566d6986bb0f81791c820510a5b9917baf5cb..0bea138f6c32e41d798596f3a9f3f999bfa301bd 100644 --- a/release.json +++ b/release.json @@ -1 +1 @@ -{"version":"0.1.18"} +{"version":"0.1.19"}