'use strict';
/**
* @author schukai GmbH
*/
import {Monster} from '../namespace.js';
import {Base} from './base.js';
import {isString, isIterable} from '../types/is.js';
import {validateString, validateFunction} from '../types/validate.js';
/**
* 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.
*
* you can call the method via the monster namespace `new Monster.Types.TokenList()`.
*
* ```
* <script type="module">
* import {Monster} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.6.0/dist/modules/types/tokenlist.js';
* console.log(new Monster.Types.TokenList("myclass row"))
* console.log(new Monster.Types.TokenList("myclass row"))
* </script>
* ```
*
* Alternatively, you can also integrate this function individually.
*
* ```
* <script type="module">
* import {TokenList} from 'https://cdn.jsdelivr.net/npm/@schukai/monster@1.6.0/dist/modules/types/tokenlist.js';
* console.log(new TokenList("myclass row"))
* console.log(new TokenList("myclass row"))
* </script>
* ```
*
* This class implements the [iteration protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols).
*
* ```
* console.log(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");
}
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");
}
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;
}
Monster.assignToNamespace('Monster.Types', TokenList);
export {Monster, TokenList}