Fix bug in special fields selection

This commit is contained in:
Talia 2024-07-27 16:03:24 +02:00
parent fd10a49a43
commit e2ec8312af
Signed by: darkwiiplayer
GPG Key ID: 7808674088232B3E
1 changed files with 42 additions and 20 deletions

View File

@ -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)
this.setAttribute(element, key, arg[key]) if (element instanceof Element)
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
)
} }
} }
} }