Skip to content

Instantly share code, notes, and snippets.

@jonathantneal
Last active August 21, 2021 14:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonathantneal/4812bae99b12bb677e9bf27e99977aee to your computer and use it in GitHub Desktop.
Save jonathantneal/4812bae99b12bb677e9bf27e99977aee to your computer and use it in GitHub Desktop.
Tokens — manage space-separated tokens for any attribute — 990 bytes / 498 bytes gzipped
/**
* The **Tokens** interface represents a set of space-separated tokens.
*
* **Usage**:
* ```js
* let classTokens = new Tokens(document.body, 'class')
* classTokens.has('js') // boolean
* classTokens.add('js').has('js') // true
* classTokens.remove('js').has('js') // false
* ```
*
* **Usage with `toggle`**:
* ```js
* classTokens.toggle('js') // boolean
* classTokens.toggle('js', true) // true
* classTokens.toggle('js', false) // false
* ```
**/
export class Tokens {
/**
* The **at()** method returns the token at the given index.
*
* **Example**:
* ```js
* let classTokens = new Tokens(document.body, 'class')
*
* classTokens.at(0) // first token
* classTokens.at(-1) // last token
* ```
**/
at(index: number): string | undefined
/**
* The **add()** method adds the given tokens and returns the current token set.
*
* **Example**:
* ```js
* let classTokens = new Tokens(document.body, 'class')
*
* classTokens.add('peace')
* ```
**/
add(...tokens: string[]): this
/**
* The **has()** method returns whether the current token set has the given token.
*
* **Example**:
* ```js
* let classTokens = new Tokens(document.body, 'class')
*
* classTokens.has('peace')
* ```
**/
has(token: string): boolean
/**
* The **remove()** method removes the given token and returns the current token set.
*
* **Example**:
* ```js
* let classTokens = new Tokens(document.body, 'class')
*
* classTokens.remove('worry')
* ```
**/
remove(...tokens: string[]): this
/**
* The **toggle()** method toggles the given token and returns whether the current token set has it.
*
* **Example**:
* ```js
* let classTokens = new Tokens(document.body, 'class')
*
* classTokens.toggle('smile') // adds or removes "smile"
*
* classTokens.toggle('smile', false) // removes "smile"
* classTokens.toggle('smile', true) // adds "smile"
* ```
**/
toggle(token: string, force?: boolean): boolean
/**
* The **size()** property returns the number of tokens in the current token set.
*
* **Example**:
* ```js
* let classTokens = new Tokens(document.body, 'class')
*
* classTokens.size // number of tokens
* ```
**/
size: boolean
}
/**
* The **tokens** method returns a set of **Tokens** for the given element and attribute.
*
* **Usage**:
* ```js
* Element.prototype._tokensFor = tokens
*
* let classTokens = document.body._tokensFor('class')
* classTokens.has('js') // boolean
* classTokens.add('js').has('js') // true
* classTokens.remove('js').has('js') // false
* ```
*
* **Usage with `toggle`**:
* ```js
* classTokens.toggle('js') // boolean
* classTokens.toggle('js', true) // true
* classTokens.toggle('js', false) // false
* ```
**/
export function tokens(this: Element, name: string): Tokens
let map = new WeakMap()
let getSet = (/** @type {Tokens} */ self, /** @type {boolean} */ make) => new Set(getAttr(self, make).value.split(/[\u0009\u000A\u000C\u000D\u0020]+/).filter(Boolean))
let getAttr = (/** @type {Tokens} */ self, /** @type {boolean} */ make, /** @type {{ node: Element, name: string }} */ data = map.get(self)) => /** @type {Attr} */ ((make ? data.node.attributes.setNamedItem : Object).call(data.node.attributes, data.node.attributes[data.name] || document.createAttribute(data.name)) || data.node.attributes[data.name])
export class Tokens {
constructor(/** @type {Element} */ node, /** @type {string} */ name) {
map.set(this, { node, name })
}
at(/** @type {number} */ index) {
let set = [ ...getSet(this) ]
return set[index >= 0 ? index : set.length + index]
}
add(/** @type {string[]} */ ...tokens) {
let set = getSet(this, true)
for (let token of tokens) {
token = String(token).trim()
if (token) set.add(token)
}
getAttr(this).value = [ ...set ].join(' ')
return this
}
has(/** @type {string} */ token) {
return getSet(this).has(String(token).trim())
}
remove(/** @type {string[]} */ ...tokens) {
let set = getSet(this, true)
for (let token of tokens) {
set.delete(String(token).trim())
}
getAttr(this).value = [ ...set ].join(' ')
return this
}
toggle(/** @type {string} */ token, /** @type {boolean} */ force = null) {
let set = getSet(this, true)
token = String(token).trim()
force = force === null ? !set.has(token) : Boolean(force)
set[exists ? 'add' : 'delete'](token)
getAttr(this).value = [ ...set ].join(' ')
return force
}
get size() {
return getSet(this).size
}
[Symbol.iterator]() {
return getSet(this)[Symbol.iterator]()
}
[Symbol.toPrimitive]() {
return getAttr(this).value
}
}
export function tokens(/** @type {string} */ name) {
return new Tokens(this, name)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment