Switch to svelte store contract
This commit is contained in:
parent
688cbae9ba
commit
2445617e8b
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
|
// 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
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue