Repository navigation
Conversation
Listens to an event on any EventTarget for as long as the component is connected, and stops when it disconnects. It avoids two bugs that pull in opposite directions. Subscribing to the handler itself and never re-subscribing keeps the closure from the render that connected the component, so a handler that reads state keeps answering with the state it was bound with. Re-subscribing whenever the handler changes fixes that, but costs an add and a remove on the DOM every render, because a lambda written in a template is a new object each time. So the listener is added once and reads the handler through a ref that every render refreshes. The test covers both: with the ref refresh removed it fails with "1" where "2" was expected (a stale closure), and with the removal taken out of the disposable it fails with 3 calls where 2 were expected (a leaked listener). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
customEvent<'T> "name" declares a custom event's name and the type of its detail in one value. EventTarget.dispatchCustom raises it on any target, document included, so components that hold no reference to each other can talk. A Hook.useEventListener overload hands the handler the detail already typed. Without it, the sender boxes a value into detail and each listener casts it back on its own, and nothing ties those casts to the sender: change the payload and every listener keeps compiling while reading a shape that is no longer there. Here the one unchecked cast is inside the hook, next to the descriptor that pairs the name with the type. Sending a payload of the wrong type is a compile error. customEvent is a function rather than the union case itself because type arguments cannot be applied to a union case: CustomEventOf<T> "name" is error FS0717. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds typed custom events. One value states an event's name and the type of its
detail, and both ends take their types from it:Why
With
dispatchCustomEvent, the sender boxes a value intodetailand each listener casts it back with(e :?> CustomEvent).detail :?> (string * int). Nothing ties those casts to what the sender sent. Change the payload and every listener keeps compiling, then reads a shape that isn't there at runtime.Here the payload type lives in one place. Sending
("apples", "3")toitemAddedis a compile error (expected to have type 'int' but here has type 'string'). The one unchecked cast left is inside the hook, next to the descriptor that pairs the name with the type.What's added
CustomEventOf<'T>(inDomHelpers): an erased single-case union, so at runtime it's just the name string.customEvent<'T> name: makes one. It's a function, not the union case itself, because F# can't apply type arguments to a union case (CustomEventOf<string> "x"iserror FS0717: Unexpected type arguments).EventTarget.dispatchCustom(ev, detail, ?bubbles, ?composed, ?cancelable): works on anyEventTarget, not only a LitElement'shost, so components that don't hold a reference to each other can talk overdocument. The defaults matchdispatchCustomEvent: alltrue.Hook.useEventListener(target, ev: CustomEventOf<'T>, handler: 'T -> unit): an overload of the hook from feat: add Hook.useEventListener #84 that hands the handler the typeddetail.Docs: a "Typed custom events" section under Events in
docsrc/docs/web-components.md. Its example compiles.Tests
useEventListener carries a typed detail both waysinHookTest.fssends a tuple throughdocumentand reads it back in a hook component.dotnet build Fable.Lit.slnandnpm testpass (39 tests).🤖 Generated with Claude Code