Change skooma to use svelte store contract
Since States are now also valid svelte stores, skooma now uses that API to decide whether something is an observable state and to interact with it.
This commit is contained in:
parent
e293594edb
commit
b25a3013d2
1 changed files with 46 additions and 48 deletions
94
skooma.js
94
skooma.js
|
@ -1,12 +1,16 @@
|
|||
// Keep a referee alive until a referrer is collected
|
||||
const weakReferences = new WeakMap()
|
||||
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)
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
export const isReactive = object => !(object instanceof HTMLElement)
|
||||
&& (object instanceof EventTarget)
|
||||
&& ("value" in object)
|
||||
export const isReactive = object => object
|
||||
&& typeof object == "object"
|
||||
&& !(object instanceof HTMLElement)
|
||||
&& object.subscribe
|
||||
|
||||
const toChild = arg => {
|
||||
if (typeof arg == "string" || typeof arg == "number") {
|
||||
if (typeof arg == "string" || typeof arg == "number")
|
||||
return document.createTextNode(arg)
|
||||
} else if (arg instanceof HTMLElement) {
|
||||
else if (arg instanceof HTMLElement)
|
||||
return arg
|
||||
} else if (isReactive(arg)) {
|
||||
else if (isReactive(arg))
|
||||
return reactiveChild(arg)
|
||||
} else {
|
||||
else
|
||||
return document.createComment("Placeholder for reactive content")
|
||||
}
|
||||
}
|
||||
|
||||
const reactiveChild = reactive => {
|
||||
const ref = new WeakRef(toChild(reactive.value))
|
||||
reactive.addEventListener("change", () => {
|
||||
const value = ref.deref()
|
||||
if (value)
|
||||
value.replaceWith(reactiveChild(reactive))
|
||||
}, {once: true})
|
||||
untilDeathDoThemPart(ref.deref(), reactive)
|
||||
let ref
|
||||
const abort = reactive.subscribe(value => {
|
||||
if (ref && !ref.deref()) return abort()
|
||||
const child = toChild(value)
|
||||
if (ref) ref.deref().replaceWith(child)
|
||||
untilDeathDoThemPart(child, reactive)
|
||||
ref = new WeakRef(child)
|
||||
})
|
||||
return ref.deref()
|
||||
}
|
||||
|
||||
const specialAttributes = {
|
||||
value: {
|
||||
get: element => element.value,
|
||||
set: (element, value) => {
|
||||
element.setAttribute("value", value)
|
||||
element.value = value
|
||||
get() { return this.value },
|
||||
set(value) {
|
||||
this.setAttribute("value", value)
|
||||
this.value = value
|
||||
},
|
||||
hook: (element, callback) => { element.addEventListener("input", callback) }
|
||||
hook(callback) { this.addEventListener("input", callback) }
|
||||
},
|
||||
style: {
|
||||
set: (element, value) => { insertStyles(element.style, value) }
|
||||
set(value) { insertStyles(this.style, value) }
|
||||
},
|
||||
dataset: {
|
||||
set: (element, value) => {
|
||||
set(value) {
|
||||
for (const [attribute2, value2] of Object.entries(value)) {
|
||||
element.dataset[attribute2] = parseAttribute(value2)
|
||||
this.dataset[attribute2] = parseAttribute(value2)
|
||||
}
|
||||
}
|
||||
},
|
||||
shadowRoot: {
|
||||
set: (element, value) => {
|
||||
parseArgs((element.shadowRoot || element.attachShadow({mode: "open"})), value)
|
||||
set(value) {
|
||||
parseArgs((this.shadowRoot || this.attachShadow({mode: "open"})), value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +103,7 @@ const setAttribute = (element, attribute, value, cleanupSignal) => {
|
|||
else if (typeof value === "function")
|
||||
element.addEventListener(attribute.replace(/^on[A-Z]/, x => x.charAt(x.length-1).toLowerCase()), value, {signal: cleanupSignal})
|
||||
else if (special) {
|
||||
special.set(element, value)
|
||||
special.set.call(element, value)
|
||||
}
|
||||
else if (value === true)
|
||||
{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)
|
||||
|
||||
if (abortController) abortController.abort()
|
||||
abortController = new AbortController()
|
||||
|
||||
const ref = new WeakRef(element)
|
||||
setAttribute(element, attribute, reactive.value, abortController.signal)
|
||||
|
||||
reactive.addEventListener("change", () => {
|
||||
const element = ref.deref()
|
||||
if (element)
|
||||
setReactiveAttribute(element, attribute, reactive, abortController)
|
||||
}, {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
|
||||
const multiAbort = new MultiAbortController()
|
||||
let old
|
||||
reactive.subscribe(value => {
|
||||
old = value
|
||||
multiAbort.abort()
|
||||
setAttribute(element, attribute, value, multiAbort.signal)
|
||||
})
|
||||
if (special?.hook && reactive.set) {
|
||||
special.hook.call(element, () => {
|
||||
const value = special.get.call(element, attribute)
|
||||
if (value != old) reactive.set() = value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue