Skip to content

feat: add Lit.renderPart and Lit.trackConnection - #87

Open
OnurGumus wants to merge 1 commit into
fable-hub:mainfrom
OnurGumus:feat/track-connection
Open

OnurGumus wants to merge 1 commit into
fable-hub:mainfrom
OnurGumus:feat/track-connection

Conversation

@OnurGumus

Copy link
Copy Markdown
Contributor

Gives markup rendered into a plain element a way to hear that it left the page.

A LitElement gets disconnectedCallback, and a HookComponent disposes what Hook.useEffectOnce returned. But a template rendered with Lit.render into a plain <div> hears nothing when other code removes that div, so timers, sockets and subscriptions started inside it keep running. lit tells directives when it clears a part, but it never watches the document.

What's added

Lit.renderPart el template: renders like Lit.render and returns lit's root part, which the binding used to throw away. part.setConnected false reaches every AsyncDirective in the tree. Lit.render still returns unit, so existing call sites don't change.

Lit.trackConnection "my-island": turns the tag into a custom element whose only behaviour is to forward its connected and disconnected callbacks to the root part inside it. That's the same mechanism LitElement uses. Directives pause when the element leaves the document and resume when it returns, so a moved element keeps what it had.

Lit.trackConnection("my-island", fun el -> ...): also runs your own setup while the element is in the document:

let subscription =
    Lit.trackConnection("my-island", fun el ->
        let id = JS.setInterval (fun () -> refresh el) 1000
        Hook.createDisposable (fun () -> JS.clearInterval id))
  • The handler is called when an element with the tag joins the document, and what it returns is disposed when that element leaves. Setup and teardown are in one place.
  • The call returns an IDisposable that stops the handler and disposes whatever is still set up. The forwarding and any other handlers carry on (a custom element can't be undefined).
  • Elements already in the document when a handler subscribes are reported to it straight away. This matters for server-rendered pages: defining the tag upgrades those elements on the spot, before the handler is registered, so without it the handler would never hear about them.

Docs: a "Leaving the page" section in docsrc/docs/templates.md. Its examples compile.

One signature change

LitBindings.render now returns ChildPart instead of unit, because that's what lit's render returns. Code that calls the raw binding as a statement gets the usual "implicitly ignored" warning. Lit.render is unchanged.

Tests

Five in a new test/DisconnectTest.fs:

  • renderPart's part reaches the directives in the tree;
  • lit tells directives when it clears a part, but not when the DOM is removed under it (this pins down the gap this PR fills);
  • a tracked host forwards leaving and returning, the handler runs set up, tear down, set up, and disposing stops the handler without stopping the forwarding;
  • an element in the page before the handler subscribed is reported;
  • a second handler on an already-tracked tag hears about elements already there.

Mutation check: removing the forwarding fails the third test, and removing the report of elements already present fails the last two. dotnet build Fable.Lit.sln and npm test pass (42 tests).

🤖 Generated with Claude Code

A LitElement hears when it leaves the document, and a HookComponent
disposes what useEffectOnce returned. A template rendered with Lit.render
into a plain element hears nothing when other code removes that element,
so timers and subscriptions started inside it keep running. lit itself
only tells directives about parts it clears; it never watches the
document.

Lit.renderPart returns lit's root part, which the binding used to throw
away. part.setConnected false reaches every AsyncDirective in the tree.
Lit.render still returns unit, so existing call sites are unchanged.

Lit.trackConnection tag turns the tag into a custom element whose only
behaviour is to forward its connected and disconnected callbacks to the
root part inside it: the same mechanism LitElement uses. Directives pause
when it leaves the document and resume when it comes back, so a moved
element keeps what it had.

The overload with a handler runs setup when an element with the tag joins
the document and disposes what it returned when the element leaves. It
returns an IDisposable that stops that handler, so it can sit in a hook.
Elements already in the document when a handler subscribes are reported to
it straight away: defining a tag upgrades elements already in the page on
the spot, before the handler is registered, so server-rendered elements
would otherwise never be reported.

Five tests in DisconnectTest.fs. Removing the forwarding fails one;
removing the report of elements already present fails two.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JordanMarr JordanMarr added the after-2.0 Considered after the 2.0 maintenance release (see #90) label Oct 19, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

after-2.0 Considered after the 2.0 maintenance release (see #90)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants