Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Events

Perry widgets support native event handlers for user interaction. Every snippet below is excerpted from docs/examples/ui/events/snippets.ts — CI compiles and runs it on every PR, so the API drawn here is the API the runtime exposes.

Event handlers are registered as free functions that take the widget handle as the first argument. The widget handle itself is opaque (number at the type level); perry’s API is function-first throughout.

onClick

const greet = Button("Click me", () => {
    log.set("Button clicked")
})

// Or attach a click handler to a non-button widget after creation:
const label = Text("Clickable text")
widgetSetOnClick(label, () => {
    log.set("Text clicked")
})

onHover

Triggered when the cursor enters the widget.

const hoverBtn = Button("Hover me", () => {})
widgetSetOnHover(hoverBtn, () => {
    log.set("hovered")
})

Note: Hover events are available on macOS, Windows, Linux, and Web. iOS and Android use touch interactions instead. The callback fires once on enter; if you need a “left” event you’ll have to track it yourself.

onDoubleClick

const dbl = Text("Double-click me")
widgetSetOnDoubleClick(dbl, () => {
    log.set("double-clicked!")
})

Keyboard Shortcuts

Register in-app keyboard shortcuts (active when the app is focused):

// Cmd+N on macOS, Ctrl+N on other platforms (modifier 1 = Cmd/Ctrl).
addKeyboardShortcut("n", 1, () => {
    log.set("New document")
})

// Cmd+Shift+S — modifiers add: 1 (Cmd/Ctrl) + 2 (Shift) = 3.
addKeyboardShortcut("s", 3, () => {
    log.set("Save as...")
})

Modifier bits: 1 = Cmd (macOS) / Ctrl (Windows/Linux), 2 = Shift, 4 = Option (macOS) / Alt (others), 8 = Control (macOS only). Combine by adding — 3 = Cmd+Shift, 5 = Cmd+Option, etc.

Keyboard shortcuts are also available on menu items:

const fileMenu = menuCreate()
menuAddItem(fileMenu, "New", () => log.set("file/new"))
menuAddItem(fileMenu, "Save As", () => log.set("file/saveAs"))

Global Hotkeys

Register a hotkey that fires system-wide, even when the app is in the background:

// System-wide: fires even when the app is in the background.
// macOS: real Carbon RegisterEventHotKey. Other platforms: no-op.
registerGlobalHotkey("F5", 0, () => {
    log.set("Global F5 hotkey fired")
})

// Cmd+Shift+G (modifiers: 1=Cmd + 2=Shift = 3)
registerGlobalHotkey("g", 3, () => {
    log.set("Global Cmd+Shift+G fired")
})

Platform support: macOS uses Carbon RegisterEventHotKey (real implementation). Linux, Windows, iOS, tvOS, visionOS, watchOS, and Android log the registration and no-op — global hotkeys on those platforms require OS-level portal / hook APIs that vary per OS.

Clipboard

// Copy to clipboard
clipboardWrite("Hello, clipboard!")

// Read from clipboard
const text = clipboardRead()
log.set(`clipboard length: ${text.length}`)

Complete Example

// demonstrates: click + hover + double-click + keyboard shortcut all wired to
// a single State-backed status label
// docs: docs/src/ui/events.md
// platforms: macos, linux, windows
// targets: ios-simulator, web, wasm

import {
    App,
    Text,
    Button,
    VStack,
    State,
    Spacer,
    addKeyboardShortcut,
    widgetSetOnHover,
    widgetSetOnDoubleClick,
} from "perry/ui"

const lastEvent = State("No events yet")

// Cmd+R (modifiers: 1 = Cmd/Ctrl).
addKeyboardShortcut("r", 1, () => {
    lastEvent.set("Keyboard: Cmd+R")
})

const hoverBtn = Button("Hover me", () => {})
widgetSetOnHover(hoverBtn, () => {
    lastEvent.set("Hover fired")
})

const dblLabel = Text("Double-click me")
widgetSetOnDoubleClick(dblLabel, () => {
    lastEvent.set("Double-clicked!")
})

App({
    title: "Events Demo",
    width: 400,
    height: 300,
    body: VStack(16, [
        Text(`Last event: ${lastEvent.value}`),
        Spacer(),
        Button("Click me", () => {
            lastEvent.set("Button clicked")
        }),
        hoverBtn,
        dblLabel,
    ]),
})

Next Steps