Compare commits

..

No commits in common. "e52bfd8e13f690b4a7ea8880a3a7d4cb7e9b17d0" and "e7a34fa9707958590f62a92072ac818917544d6f" have entirely different histories.

5 changed files with 23 additions and 160 deletions

View file

@ -1,63 +1,25 @@
<script type="importmap">
{
"imports": {
"nyooom/render": "https://cdn.jsdelivr.net/npm/nyooom/render.js",
"controller-registry": "./src/controller-registry.js"
}
}
</script>
<script type="module"> <script type="module">
// Import nyooom to easily generate HTML nodes import controllers from "./src/controller-registry.js"
import {html} from "nyooom/render" import {html} from "https://cdn.jsdelivr.net/npm/nyooom/render.js"
// The actual library Object.defineProperty(HTMLElement.prototype, "controllers", {
import controllers from "controller-registry" get() { return controllers.list(this) }
// Define a basic word filter controller for input elements
controllers.define("filter", (element, detach) => {
element.addEventListener("input", event => {
if (element.value.toLowerCase() === "scunthorpe") {
element.value = ""
alert("Evil word detected, deleting input permanently!")
}
}, detach) // Alternatively: {signal: detach.signal}
}) })
// Define a more complex filter for form elements to enable or disable filters controllers.define("red", async (element, disconnected) => {
controllers.define("optional-filter", async (element, detach) => { const before = element.style.color
const checkBox = html.div(html.label( element.style.color = "red"
html.input({ element.addEventListener("click", () => element.controllers.remove("red"), disconnected)
type: "checkbox", await disconnected
checked: true, element.style.color = before
input: ({target: {checked}}) => { })
element
.querySelectorAll("input:not([type='checkbox'])") controllers.define("asterisk", async (element, disconnected) => {
.forEach(input => controllers.list(input).toggle("filter", checked)) const asterisk = html.span("*", {controller: ["red"]})
} element.append(asterisk)
}), await disconnected
html.span("Enable filter") asterisk.remove()
))
element.append(checkBox)
// Detach is a promise, so we can just pause the function until
// it's time for cleanup to happen
await detach
checkBox.remove()
}) })
</script> </script>
<style> <h1 controller="asterisk">Hello, World!</h1>
[controller~="filter"] {
outline: 3px solid #82f6;
border-radius: .2em;
}
</style>
<form controller="optional-filter">
<div>
<label>
<span>Where are you from?</span>
<input controller="filter"></input>
</label>
</div>
</form>

View file

@ -1,7 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"allowJs": true,
"checkJs": true
}
}

View file

@ -1,13 +0,0 @@
{
"name": "controller-registry",
"exports": {
".": "./src/controller-registry.js",
},
"type": "module",
"license": "MIT",
"version": "0.0.1",
"url": "https://git.but.gay/darkwiiplayer/controller-registry",
"scripts": {
"definitions": "tsc src/*.js --declaration --allowJs --emitDeclarationOnly"
}
}

View file

@ -1,48 +0,0 @@
export class ControllerList {
/**
* @param {HTMLElement} element
* @param {String} attribute
* */
constructor(element: HTMLElement, attribute?: string);
/** @param {String} name */
contains(name: string): any;
/** @param {String} name */
add(name: string): void;
/** @param {String} name */
remove(name: string): void;
/**
* @param {String} name
* @param {String} replacement
*/
replace(name: string, replacement: string): boolean;
/**
* @param {String} name
* @param {Boolean} force
*/
toggle(name: string, force: boolean): void;
#private;
}
export class ControllerRegistry {
/** @typedef {HTMLElement} Root */
/**
* @param {Root} root
* @param {String} attribute
*/
constructor(root: HTMLElement, attribute?: string);
/**
* @param {Root} root
*/
upgrade(root: HTMLElement): void;
/**
* @param {String} name
* @param {Function} callback
*/
define(name: string, callback: Function): void;
get(name: any): any;
list(element: any): any;
getName(controller: any): void;
whenDefined(name: any): void;
#private;
}
declare const _default: ControllerRegistry;
export default _default;

View file

@ -22,25 +22,18 @@ export class ControllerList {
this.#element.setAttribute(this.#attribute, [...set].join(" ")) this.#element.setAttribute(this.#attribute, [...set].join(" "))
} }
/** @param {String} name */
contains(name) { contains(name) {
return this.#set.has(name) return this.#set.has(name)
} }
/** @param {String} name */
add(name) { add(name) {
this.toggle(name, true) this.toggle(name, true)
} }
/** @param {String} name */
remove(name) { remove(name) {
this.toggle(name, false) this.toggle(name, false)
} }
/**
* @param {String} name
* @param {String} replacement
*/
replace(name, replacement) { replace(name, replacement) {
const set = this.#set const set = this.#set
if (set.has(name)) { if (set.has(name)) {
@ -53,10 +46,6 @@ export class ControllerList {
} }
} }
/**
* @param {String} name
* @param {Boolean} force
*/
toggle(name, force) { toggle(name, force) {
const set = this.#set const set = this.#set
if (force === true) { if (force === true) {
@ -94,42 +83,29 @@ export class ControllerRegistry {
/** @type {WeakMap<HTMLElement,Map<String,Object>>} */ /** @type {WeakMap<HTMLElement,Map<String,Object>>} */
#attached = new WeakMap() #attached = new WeakMap()
/** @type {Map<String,Set<HTMLElement>>} */ /** @type {Map<String,Set<HTMLElement>} */
#waiting = new Map() #waiting = new Map()
/** @type {Map<String,(element: HTMLElement, signal: AbortSignal)=>void>} */ /** @type {Map<Strong,(HTMLElement, AbortSignal)=>void>} */
#defined = new Map() #defined = new Map()
#attribute #attribute
/** @typedef {HTMLElement} Root */
/**
* @param {Root} root
* @param {String} attribute
*/
constructor(root, attribute="controller") { constructor(root, attribute="controller") {
this.#attribute = attribute this.#attribute = attribute
this.#observer.observe(root, {subtree: true, childList: true, attributes: true, attributeFilter: [attribute], attributeOldValue: false}) this.#observer.observe(root, {subtree: true, childList: true, attributes: true, attributeFilter: [attribute], attributeOldValue: false})
this.upgrade(root) this.upgrade(root)
} }
/**
* @param {Root} root
*/
upgrade(root) { upgrade(root) {
root.querySelectorAll(`[${this.#attribute}]`).forEach(element => this.#update(element)) root.querySelectorAll(`[${this.#attribute}]`).forEach(element => this.#update(element))
} }
/**
* @param {String} name
* @param {Function} callback
*/
define(name, callback) { define(name, callback) {
if (("function" == typeof callback) && callback.prototype) { if (("function" == typeof callback) && callback.prototype) {
callback = async (element, disconnected) => { callback = async (element, disconnected) => {
const {proxy, revoke} = Proxy.revocable(element, {}) const {proxy, revoke} = Proxy.reevocable(element)
const controller = new callback(proxy, disconnected) const controller = new callback(proxy)
await disconnected await disconnected
revoke() revoke()
if ("detach" in controller) controller.detach(element) if ("detach" in controller) controller.detach(element)
@ -150,15 +126,8 @@ export class ControllerRegistry {
return this.#defined.get(name) return this.#defined.get(name)
} }
#listMap = new WeakMap()
list(element) { list(element) {
if (this.#listMap.has(element)) { return new ControllerList(element, this.#attribute)
return this.#listMap.get(element)
} else {
const list = new ControllerList(element, this.#attribute)
this.#listMap.set(element, list)
return list
}
} }
getName(controller) { getName(controller) {