Primitives / Events
The entries
keyboardEvents(), pointerEvents(), scrollEvents(), and
activeElementEvents() are entries, not bundles: the parent owns whatever
key, cursor, scroll, or focus state it keeps. Lift one with
Subscription.persistentEntry, mapping into the parent's Message:
import { Stream } from 'effect'
import * as Subscription from 'foldkit/subscription'
import { keyboardEvents, matchHotkey } from 'foldkit-primitives/events'
const subscriptions = Subscription.make<EditorModel, EditorMessage>()(() => ({
keys: Subscription.persistentEntry(
keyboardEvents({ preventDefault: press => matchHotkey('ctrl+s', press) }).pipe(
Stream.filter(event => event._tag === 'Pressed' && matchHotkey('ctrl+s', event)),
Stream.map(() => EditorMessage.SaveRequested()),
),
),
}))They report presses (with repeat and modifiers) and releases, moves { x, y },
scroll positions (in the capture phase, so a container's scroll reports too),
and focus as { tag, id }: an element crosses as its tag and id, never as a
live node. Without a window each stream is empty rather than throwing.
matchHotkey('ctrl+shift+k', press) answers whether a press is that chord, so
update stays a table of chords. Matching is exact, with Mac aliases, and an
auto-repeat never matches.
keyboardEvents({ preventDefault }) cancels the default of the presses the
predicate picks (the arrows scrolling a game's page, a browser's own ctrl+s),
decided inside the listener, because a default can only be cancelled while the
event dispatches. The predicate reads the press, not the DOM event.
When the observed target depends on state, scope the entry through subscription dependencies and it restreams on change.
Failure
Without a window every stream is empty and every init a safe default.