Switch to svelte store contract

This commit is contained in:
Talia 2024-01-24 14:50:51 +01:00
parent 688cbae9ba
commit 2445617e8b

View file

@ -1,12 +1,16 @@
// 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()
const untilDeathDoThemPart = (referrer, reference) => { const untilDeathDoThemPart = (referrer, reference) => {
if (!weakReferences.has(referrer)) { if (!weakReferences.has(referrer)) weakReferences.set(referrer, new Set())
weakReferences.set(referrer, new Set())
}
weakReferences.get(referrer).add(reference) weakReferences.get(referrer).add(reference)
} }
class MultiAbortController {
#controller = new AbortController()
get signal() { return this.#controller.signal }
abort() { this.#controller.abort(); this.#controller = new AbortController() }
}
export const empty = Symbol("Explicit empty argument for Skooma") export const empty = Symbol("Explicit empty argument for Skooma")
const keyToPropName = key => key.replace(/^[A-Z]/, a => "-"+a).replace(/[A-Z]/g, a => '-'+a.toLowerCase()) const keyToPropName = key => key.replace(/^[A-Z]/, a => "-"+a).replace(/[A-Z]/g, a => '-'+a.toLowerCase())
@ -38,55 +42,56 @@ const getCustom = args => args.reduce(
,undefined ,undefined
) )
export const isReactive = object => !(object instanceof HTMLElement) export const isReactive = object => object
&& (object instanceof EventTarget) && typeof object == "object"
&& ("value" in object) && !(object instanceof HTMLElement)
&& object.subscribe
const toChild = arg => { const toChild = arg => {
if (typeof arg == "string" || typeof arg == "number") { if (typeof arg == "string" || typeof arg == "number")
return document.createTextNode(arg) return document.createTextNode(arg)
} else if (arg instanceof HTMLElement) { else if (arg instanceof HTMLElement)
return arg return arg
} else if (isReactive(arg)) { else if (isReactive(arg))
return reactiveChild(arg) return reactiveChild(arg)
} else { else
return document.createComment("Placeholder for reactive content") return document.createComment("Placeholder for reactive content")
} }
}
const reactiveChild = reactive => { const reactiveChild = reactive => {
const ref = new WeakRef(toChild(reactive.value)) let ref
reactive.addEventListener("change", () => { const abort = reactive.subscribe(value => {
const value = ref.deref() if (ref && !ref.deref()) return abort()
if (value) const child = toChild(value)
value.replaceWith(reactiveChild(reactive)) if (ref) ref.deref().replaceWith(child)
}, {once: true}) untilDeathDoThemPart(child, reactive)
untilDeathDoThemPart(ref.deref(), reactive) ref = new WeakRef(child)
})
return ref.deref() return ref.deref()
} }
const specialAttributes = { const specialAttributes = {
value: { value: {
get: element => element.value, get() { return this.value },
set: (element, value) => { set(value) {
element.setAttribute("value", value) this.setAttribute("value", value)
element.value = value this.value = value
}, },
hook: (element, callback) => { element.addEventListener("input", callback) } hook(callback) { this.addEventListener("input", callback) }
}, },
style: { style: {
set: (element, value) => { insertStyles(element.style, value) } set(value) { insertStyles(this.style, value) }
}, },
dataset: { dataset: {
set: (element, value) => { set(value) {
for (const [attribute2, value2] of Object.entries(value)) { for (const [attribute2, value2] of Object.entries(value)) {
element.dataset[attribute2] = parseAttribute(value2) this.dataset[attribute2] = parseAttribute(value2)
} }
} }
}, },
shadowRoot: { shadowRoot: {
set: (element, value) => { set(value) {
parseArgs((element.shadowRoot || element.attachShadow({mode: "open"})), value) parseArgs((this.shadowRoot || this.attachShadow({mode: "open"})), value)
} }
} }
} }
@ -98,7 +103,7 @@ const setAttribute = (element, attribute, value, cleanupSignal) => {
else if (typeof value === "function") else if (typeof value === "function")
element.addEventListener(attribute.replace(/^on[A-Z]/, x => x.charAt(x.length-1).toLowerCase()), value, {signal: cleanupSignal}) element.addEventListener(attribute.replace(/^on[A-Z]/, x => x.charAt(x.length-1).toLowerCase()), value, {signal: cleanupSignal})
else if (special) { else if (special) {
special.set(element, value) special.set.call(element, value)
} }
else if (value === true) else if (value === true)
{if (!element.hasAttribute(attribute)) element.setAttribute(attribute, '')} {if (!element.hasAttribute(attribute)) element.setAttribute(attribute, '')}
@ -109,26 +114,19 @@ const setAttribute = (element, attribute, value, cleanupSignal) => {
} }
} }
const setReactiveAttribute = (element, attribute, reactive, abortController) => { const setReactiveAttribute = (element, attribute, reactive) => {
untilDeathDoThemPart(element, reactive) untilDeathDoThemPart(element, reactive)
const multiAbort = new MultiAbortController()
if (abortController) abortController.abort() let old
abortController = new AbortController() reactive.subscribe(value => {
old = value
const ref = new WeakRef(element) multiAbort.abort()
setAttribute(element, attribute, reactive.value, abortController.signal) setAttribute(element, attribute, value, multiAbort.signal)
})
reactive.addEventListener("change", () => { if (special?.hook && reactive.set) {
const element = ref.deref() special.hook.call(element, () => {
if (element) const value = special.get.call(element, attribute)
setReactiveAttribute(element, attribute, reactive, abortController) if (value != old) reactive.set() = value
}, {once: true})
const special = specialAttributes[attribute]
if (special?.hook) {
special.hook(element, () => {
const value = special.get(element, attribute)
if (value != reactive.value) reactive.value = value
}) })
} }
} }