buildComponentLibrary gives a component props and a render. There is no hook that runs when the element leaves the DOM, and the name is reserved, so a component cannot release anything it acquired:
resources/elements/weight-chart.stx: method "disconnectedCallback" is reservedconnectedCallback is reserved the same way. An instance property is not an alternative — the browser reads custom-element reactions off the prototype at define() time, so this.disconnectedCallback = ... assigned during a render is never called.
What that rules out
Work that starts from a render can be made to work: the fetch, caching on this, requestUpdate() when it lands, and a queueMicrotask for the few things that need the written markup. I converted one element that way and it matches the hand-written original exactly.
The next one cannot be written at all. It holds:
disconnectedCallback() {
if (this._observer) this._observer.disconnect() // content changes
if (this._resize) this._resize.disconnect() // redraw at a new width
if (this._themeObserver) this._themeObserver.disconnect() // follows the page's dark class
this._release() // document + window listeners
}and, inside the chart it draws:
document.addEventListener('pointerdown', outside, { passive: true })
window.addEventListener('scroll', hide, { passive: true })Installed from a render, every one of those outlives the element. The document-level listener is the sharp end: it holds the element and its whole shadow tree alive after removal, so an element created per page view in a SPA leaks without bound. There is no way to write it correctly, and a compiled element that leaks is worse than the hand-written one it would replace — so that conversion is where I stopped.
Suggestion
Unreserve the two lifecycle names and call them around the runtime's own, which is what a subclass would do:
connectedCallback() { super.connectedCallback(); /* component body */ }
disconnectedCallback() { /* component body */; super.disconnectedCallback() }Or, if the point of reserving them is that the runtime owns the sequence, an explicit pair the runtime invokes — onConnected / onDisconnected — would do the same job without letting a component break the base class.
A @connected / @disconnected block in the .stx file would be nicer still, since it keeps the component's lifecycle in the component rather than in a build config.
Related: stacksjs/stx#2009, which is about the same build's defineProps handling.