Primitives / foldkit-primitives

The other three ways in

An entry, lifted into your Subscriptions and mapped to your Message. The parent keeps whatever it wants of the stream:

ts
import { Stream } from 'effect'
import * as Subscription from 'foldkit/subscription'
import { keyboardEvents } from 'foldkit-primitives/events'

const subscriptions = Subscription.make<GameModel, GameMessage>()(() => ({
  keys: Subscription.persistentEntry(
    keyboardEvents({ preventDefault: press => press.key.startsWith('Arrow') }).pipe(
      Stream.filter(event => event._tag === 'Pressed'),
      Stream.map(({ key }) => GameMessage.PressedKey({ key })),
    ),
  ),
}))

A Mount, attached to the element it observes. No ref to thread: the Mount receives its element.

ts
import { Resize } from 'foldkit-primitives/observers'

const panel = (model: PanelModel, h: HtmlBuilder<PanelMessage>) =>
  h.div([h.OnMount(Resize())], [`${model.width} × ${model.height}`])

A Command, returned from update, whose result comes back as a Message your union wraps:

ts
import { mapMessage } from 'foldkit/command'
import { copyText } from 'foldkit-primitives/dom'

const copy = (model: NoteModel): Update.Return<NoteModel, NoteMessage> => ({
  model,
  commands: [mapMessage(copyText(model.text), message => NoteMessage.Clipboard({ message }))],
})