@schukai/monster
Version:
Monster is a simple library for creating fast, robust and lightweight websites.
270 lines (244 loc) • 6.31 kB
JavaScript
/**
* Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
* Node module: @schukai/monster
*
* This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
* The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
*
* For those who do not wish to adhere to the AGPLv3, a commercial license is available.
* Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
* For more information about purchasing a commercial license, please contact Volker Schukai.
*
* SPDX-License-Identifier: AGPL-3.0
*/
import { isIterable, isString } from "./is.mjs";
import { validateFunction, validateString } from "./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.
*
* This class implements the [iteration protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols).
*
* @license AGPLv3
* @since 1.2.0
* @copyright Volker Schukai
*/
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
*
* @return {Symbol.iterator}
*/
getIterator() {
return this[Symbol.iterator]();
}
/**
* Iterator
*
* @return {{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;
const 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
*
* @externalExample ../../example/types/tokenlist-2.mjs
* @param {array|string|iteratable} value
* @return {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 (const token of value) {
validateString(token);
if (this.tokens.has(token.trim()) === false) return false;
counter++;
}
return counter > 0 ? true : false;
}
return false;
}
/**
* Add tokens
*
* @externalExample ../../example/types/tokenlist-3.mjs
* @param {array|string|iteratable} value
* @return {TokenList}
* @throws {TypeError} unsupported value
*/
add(value) {
if (isString(value)) {
value.split(" ").forEach((token) => {
this.tokens.add(token.trim());
});
} else if (isIterable(value)) {
for (const 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
*
* @return {TokenList}
*/
clear() {
this.tokens.clear();
return this;
}
/**
* Removes token
*
* @externalExample ../../example/types/tokenlist-4.mjs
* @param {array|string|iteratable} value
* @return {TokenList}
* @throws {TypeError} unsupported value
*/
remove(value) {
if (isString(value)) {
value.split(" ").forEach((token) => {
this.tokens.delete(token.trim());
});
} else if (isIterable(value)) {
for (const 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
* @return {TokenList}
*/
replace(token, newToken) {
validateString(token);
validateString(newToken);
if (!this.contains(token)) {
return this;
}
const a = Array.from(this.tokens);
const 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.
*
* @externalExample ../../example/types/tokenlist-5.mjs
* @param {array|string|iteratable} value
* @return {boolean}
* @throws {TypeError} unsupported value
*/
toggle(value) {
if (isString(value)) {
value.split(" ").forEach((token) => {
toggleValue.call(this, token);
});
} else if (isIterable(value)) {
for (const 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
*
* @return {array}
*/
entries() {
return Array.from(this.tokens);
}
/**
* executes the provided function with each value of the set
*
* @param {function} callback
* @return {TokenList}
*/
forEach(callback) {
validateFunction(callback);
this.tokens.forEach(callback);
return this;
}
/**
* returns the individual tokens separated by a blank character
*
* @return {string}
*/
toString() {
return this.entries().join(" ");
}
}
/**
* @private
* @param token
* @return {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;
}