Compare commits
No commits in common. "e52bfd8e13f690b4a7ea8880a3a7d4cb7e9b17d0" and "e7a34fa9707958590f62a92072ac818917544d6f" have entirely different histories.
e52bfd8e13
...
e7a34fa970
5 changed files with 23 additions and 160 deletions
74
example.html
74
example.html
|
@ -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>
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "esnext",
|
|
||||||
"allowJs": true,
|
|
||||||
"checkJs": true
|
|
||||||
}
|
|
||||||
}
|
|
13
package.json
13
package.json
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
48
src/controller-registry.d.ts
vendored
48
src/controller-registry.d.ts
vendored
|
@ -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;
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue