Fix bug in special fields selection
This commit is contained in:
parent
fd10a49a43
commit
e2ec8312af
1 changed files with 42 additions and 20 deletions
60
render.js
60
render.js
|
@ -1,13 +1,19 @@
|
||||||
// Keep a referee alive until a referrer is collected
|
// Keep a referee alive until a referrer is collected
|
||||||
const weakReferences = new WeakMap()
|
const weakReferences = new WeakMap()
|
||||||
|
|
||||||
|
/** Keeps the referenced value alive until the referrer is collected
|
||||||
|
* @param {Object} referrer
|
||||||
|
* @param {Object} reference
|
||||||
|
*/
|
||||||
const untilDeathDoThemPart = (referrer, reference) => {
|
const untilDeathDoThemPart = (referrer, reference) => {
|
||||||
if (!weakReferences.has(referrer)) weakReferences.set(referrer, new Set())
|
if (!weakReferences.has(referrer)) weakReferences.set(referrer, new Set())
|
||||||
weakReferences.get(referrer).add(reference)
|
weakReferences.get(referrer).add(reference)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like AbortController, but resets after each abort
|
/** Like AbortController, but resets after each abort */
|
||||||
class MultiAbortController {
|
class MultiAbortController {
|
||||||
#controller = new AbortController()
|
#controller = new AbortController()
|
||||||
|
/** @return {AbortSignal} */
|
||||||
get signal() { return this.#controller.signal }
|
get signal() { return this.#controller.signal }
|
||||||
abort() { this.#controller.abort(); this.#controller = new AbortController() }
|
abort() { this.#controller.abort(); this.#controller = new AbortController() }
|
||||||
}
|
}
|
||||||
|
@ -48,11 +54,7 @@ const getCustom = args => args.reduce(
|
||||||
, undefined
|
, undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/** @typedef {EventTarget & {value: any}} Observable */
|
||||||
* @typedef Observable
|
|
||||||
* @type {EventTarget|object}
|
|
||||||
* @property {any} value
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** Cancelable event triggered when a reactive element gets replaced with something else */
|
/** Cancelable event triggered when a reactive element gets replaced with something else */
|
||||||
export class BeforeReplaceEvent extends Event {
|
export class BeforeReplaceEvent extends Event {
|
||||||
|
@ -95,17 +97,17 @@ export class Ref {
|
||||||
/** @type {WeakMap<Text|Element,Text|Element>} */
|
/** @type {WeakMap<Text|Element,Text|Element>} */
|
||||||
static #map = new WeakMap()
|
static #map = new WeakMap()
|
||||||
|
|
||||||
/** @type {Element} */
|
/** @type {Element|Text} */
|
||||||
#element
|
#element
|
||||||
|
|
||||||
/** @param {Element} element */
|
/** @param {Element|Text} element */
|
||||||
constructor(element) {
|
constructor(element) {
|
||||||
this.#element = element
|
this.#element = element
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return {Element} */
|
/** @return {Element|Text} */
|
||||||
deref() {
|
deref() {
|
||||||
const next = this.constructor.newer(this.#element)
|
const next = Ref.newer(this.#element)
|
||||||
if (next) {
|
if (next) {
|
||||||
this.#element = next
|
this.#element = next
|
||||||
return this.deref()
|
return this.deref()
|
||||||
|
@ -114,14 +116,14 @@ export class Ref {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {Element} element */
|
/** @param {Element|Text} element */
|
||||||
static newer(element) {
|
static newer(element) {
|
||||||
return this.#map.get(element)
|
return this.#map.get(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Element} previous
|
* @param {Element|Text} previous
|
||||||
* @param {Element} next
|
* @param {Element|Text} next
|
||||||
*/
|
*/
|
||||||
static replace(previous, next) {
|
static replace(previous, next) {
|
||||||
if (this.newer(previous))
|
if (this.newer(previous))
|
||||||
|
@ -134,20 +136,25 @@ export class Ref {
|
||||||
export class Renderer {
|
export class Renderer {
|
||||||
static proxy() {
|
static proxy() {
|
||||||
return new Proxy(new this(), {
|
return new Proxy(new this(), {
|
||||||
get: (renderer, prop) => (...args) => renderer.node(prop, args),
|
/** @param {string} prop */
|
||||||
|
get: (renderer, prop) => /** @param {any[]} args */ (...args) => renderer.node(prop, args),
|
||||||
has: (renderer, prop) => renderer.nodeSupported(prop),
|
has: (renderer, prop) => renderer.nodeSupported(prop),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {string} name */
|
||||||
node(name, ...args) {
|
node(name, ...args) {
|
||||||
throw "Attempting to use an abstract Renderer"
|
throw "Attempting to use an abstract Renderer"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {string|symbol} name */
|
||||||
nodeSupported(name) {
|
nodeSupported(name) {
|
||||||
|
if (typeof(name) != "string") return false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Turns an attribute value into a string */
|
/** Turns an attribute value into a string */
|
||||||
|
/** @param {any} value */
|
||||||
static serialiseAttributeValue(value) {
|
static serialiseAttributeValue(value) {
|
||||||
if (typeof value == "string" || typeof value == "number")
|
if (typeof value == "string" || typeof value == "number")
|
||||||
return value
|
return value
|
||||||
|
@ -165,7 +172,7 @@ export class DomRenderer extends Renderer {
|
||||||
static specialAttributes = Object.freeze({})
|
static specialAttributes = Object.freeze({})
|
||||||
|
|
||||||
/** Processes a list of arguments for an HTML Node
|
/** Processes a list of arguments for an HTML Node
|
||||||
* @param {Element} element
|
* @param {Element|ShadowRoot} element
|
||||||
* @param {Array} args
|
* @param {Array} args
|
||||||
*/
|
*/
|
||||||
static apply(element, ...args) {
|
static apply(element, ...args) {
|
||||||
|
@ -180,7 +187,10 @@ export class DomRenderer extends Renderer {
|
||||||
this.apply(element, arg(element) || empty)
|
this.apply(element, arg(element) || empty)
|
||||||
else if (arg && typeof(arg)=="object")
|
else if (arg && typeof(arg)=="object")
|
||||||
for (const key in arg)
|
for (const key in arg)
|
||||||
|
if (element instanceof Element)
|
||||||
this.setAttribute(element, key, arg[key])
|
this.setAttribute(element, key, arg[key])
|
||||||
|
else
|
||||||
|
throw `Attempting to set attributes on a non-element (${element.constructor.name})`
|
||||||
else
|
else
|
||||||
console.warn(`An argument of type ${typeof arg} has been ignored`, element)
|
console.warn(`An argument of type ${typeof arg} has been ignored`, element)
|
||||||
}
|
}
|
||||||
|
@ -343,7 +353,7 @@ export class DomRenderer extends Renderer {
|
||||||
const special = this.specialAttributes[attribute]
|
const special = this.specialAttributes[attribute]
|
||||||
if (special?.filter == undefined)
|
if (special?.filter == undefined)
|
||||||
return special
|
return special
|
||||||
if (special.filter.call(element))
|
if (special.filter(element))
|
||||||
return special
|
return special
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
@ -369,7 +379,7 @@ export class DomRenderer extends Renderer {
|
||||||
const fragment = new DocumentFragment()
|
const fragment = new DocumentFragment()
|
||||||
for (const key in items) {
|
for (const key in items) {
|
||||||
fragment.append(document.createTextNode(literals[key]))
|
fragment.append(document.createTextNode(literals[key]))
|
||||||
fragment.append(toElement(items[key]))
|
fragment.append(this.toElement(items[key]))
|
||||||
}
|
}
|
||||||
fragment.append(document.createTextNode(literals[literals.length - 1]))
|
fragment.append(document.createTextNode(literals[literals.length - 1]))
|
||||||
return fragment
|
return fragment
|
||||||
|
@ -390,22 +400,30 @@ export class DomHtmlRenderer extends DomRenderer {
|
||||||
/** @type {Object<string,SpecialAttributeDescriptor>} */
|
/** @type {Object<string,SpecialAttributeDescriptor>} */
|
||||||
static specialAttributes = {
|
static specialAttributes = {
|
||||||
value: {
|
value: {
|
||||||
|
/** @param {HTMLInputElement} element */
|
||||||
get(element) { return element.value },
|
get(element) { return element.value },
|
||||||
|
/** @param {HTMLInputElement} element */
|
||||||
set(element, value) {
|
set(element, value) {
|
||||||
element.setAttribute("value", value)
|
element.setAttribute("value", value)
|
||||||
element.value = value
|
element.value = value
|
||||||
},
|
},
|
||||||
|
/** @param {HTMLInputElement} element */
|
||||||
subscribe(element, callback) {
|
subscribe(element, callback) {
|
||||||
element.addEventListener("input", () => {
|
element.addEventListener("input", () => {
|
||||||
callback(this.get(element))
|
callback(this.get(element))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
filter(element) { return element.nodeName.toLowerCase() == "input" }
|
/** @param {HTMLElement} element */
|
||||||
|
filter(element) {
|
||||||
|
return element.nodeName.toLowerCase() == "input"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
|
/** @param {HTMLElement} element */
|
||||||
set(element, value) { DomRenderer.insertStyles(element.style, value) }
|
set(element, value) { DomRenderer.insertStyles(element.style, value) }
|
||||||
},
|
},
|
||||||
dataset: {
|
dataset: {
|
||||||
|
/** @param {HTMLElement} element */
|
||||||
set(element, value) {
|
set(element, value) {
|
||||||
for (const [attribute2, value2] of Object.entries(value)) {
|
for (const [attribute2, value2] of Object.entries(value)) {
|
||||||
element.dataset[attribute2] = DomRenderer.serialiseAttributeValue(value2)
|
element.dataset[attribute2] = DomRenderer.serialiseAttributeValue(value2)
|
||||||
|
@ -413,8 +431,12 @@ export class DomHtmlRenderer extends DomRenderer {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
shadowRoot: {
|
shadowRoot: {
|
||||||
|
/** @param {HTMLElement} element */
|
||||||
set(element, value) {
|
set(element, value) {
|
||||||
apply((element.shadowRoot || element.attachShadow({ mode: "open" })), value)
|
DomRenderer.apply(
|
||||||
|
(element.shadowRoot || element.attachShadow({ mode: "open" })),
|
||||||
|
value
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue