esc

Settings pages and widgets

An add-on describes its settings page and its widgets as data. Ledge draws them with its own controls, and tells the add-on when someone uses one.

4The add-on's page in Settings, and the Widgets page of the open notch"settings-page" · LedgeKit 0.5.0

A settings page

The page is sections of rows. Ledge puts it under the add-on's header and its Turn on switch, in the same cards as Ledge's own settings, with the add-on's capabilities and details below it.

let page = SettingsPage([
    SettingsSection("general", title: "Moon", footer: "Phases are worked out on this Mac.", rows: [
        .toggle("show", "Show the moon", isOn: true),
        .picker("units", "Distance", options: [.init("km", "Kilometres"), .init("mi", "Miles")], selected: "km"),
        .slider("size", "Size", range: 10...40, step: 1, value: 24, unit: "pt"),
        .textField("city", "City", text: "Pune", placeholder: "Find a city"),
        .button("refresh", "Refresh now"),
        .value("phase", "Tonight", "Waxing gibbous"),
    ])
])
AddOnSession.show(page)
RowDrawsSends
.toggleA switch.toggled(Bool)
.pickerA pop-up menu, up to 50 options.picked(id)
.sliderA slider with its value beside it, ticks when there's a step.changed(Double)
.textFieldA text field. Sends when the user presses Return or leaves it.edited(String)
.buttonA button; destructive ones are red. With confirm:, Ledge asks first (0.13.0).pressed, only once confirmed
.linkAn Open button for an https, http or mailto linkNothing; Ledge opens it
.valueRead-only text on the rightNothing
.noteA paragraphNothing
.accountLedge's own sign-in for an account it manages. See accountsNothing; Ledge does the sign-in

When someone uses a control

Ledge sends a SurfaceEvent to onEvent with the row's id and what happened. Store the choice, then show the page again with the new value:

handlers.onEvent = { event in
    guard event.surface == Surface.settingsPage else { return }
    if event.control == "show", case .toggled(let on) = event.action {
        try? await LedgeHost.set(on, forKey: "show")
        AddOnSession.show(page(showing: on))
    }
}

Each show replaces the page. When only values change, Ledge updates the controls in place, so a text field someone is typing in isn't reset. Keep row ids the same between sends.

Asking first, and being found

A button that deletes or resets something should ask first. Give it a confirm, and Ledge shows its own alert and sends pressed only when the user agrees. Say what happens and whether it can be undone. Give the page keywords, and Ledge's Settings search finds it by them as well as by your add-on's name.

SettingsPage([
    SettingsSection("notes", rows: [
        .button("trashAll", "Move all notes to the Trash", destructive: true,
                confirm: .init("Move all 12 notes to the Trash?",
                               message: "You can put them back from the Trash in Finder.",
                               confirmTitle: "Move to Trash")),
    ]),
], keywords: ["markdown", "writing"])

Widgets

A widget is a tile on the Widgets page of the open notch, beside Ledge's own. It's a small tree of stacks, text, symbols, progress and buttons. Declare "widget" in the manifest.

let moon = Widget("moon", title: "Moon", symbol: "moon.fill", content:
    .vstack(spacing: 6, children: [
        .hstack(spacing: 8, children: [.symbol("moon.fill", size: 22), .text("Waxing gibbous", style: .title)]),
        .progress(0.78, label: "78% lit"),
        .button(id: "next", title: "Next full moon", symbol: "calendar"),
    ]))
AddOnSession.show(widgets: [moon])

Text styles are title, body, caption, secondary and monospaced. A widget button sends .pressed with the widget's id in event.widget.

Limits

  • A page has at most 200 rows. Row ids are unique within the page.
  • Up to 8 widgets, each at most 100 parts, nested at most 6 deep.
  • Any text is at most 1,000 characters.