Harlock.Menu (harlock v0.6.0)

Copy Markdown View Source

Key-event helper for the Harlock.Elements.menu/1 widget.

A menu is a vertical list of labels with one highlighted item. The widget is a dumb renderer — the app holds the highlighted id in its model, exactly as Harlock.Tabs holds the active tab.

Bindings:

  • Up / Down — move the highlight (wrapping at both ends)
  • Home / End — first / last item
  • Enter — commit the highlighted item
  • anything else — :noop

Two events, not one

Moving the highlight and choosing an item are different things, so apply_key/3 distinguishes them: arrows return {:select, id} and Enter returns :submit. An app that only cares about the final choice ignores the first and acts on the second; one that previews as you move — a file picker showing contents, say — acts on both.

Auto-routing

When a menu element carries a :focusable id the runtime calls apply_key/3 for you and delivers {:harlock_select, focus_id, id} as the highlight moves and {:harlock_submit, focus_id} on Enter:

menu(focusable: :actions, items: [{:save, "Save"}, {:quit, "Quit"}],
     active: m.action)

def update({:harlock_select, :actions, id}, m), do: %{m | action: id}
def update({:harlock_submit, :actions}, m), do: run(m.action, m)

Both tuples already exist — tabs produces the first and text_input the second — so a menu adds no new message shapes to learn. Calling apply_key/3 directly from update/2 stays supported for menus without a :focusable id, or with handle_keys: false.

A menu longer than its region clips rather than scrolling, the same as list/2. Wrap it in a viewport/1 when it can outgrow the space.

Summary

Functions

Map a {:key, key, mods} event to a highlight change or a commit.

Types

event()

@type event() :: {:select, id()} | :submit | :noop

id()

@type id() :: any()

item()

@type item() :: {id(), String.t()}

Functions

apply_key(arg1, active, items)

@spec apply_key({:key, any(), [atom()]}, id(), [item()]) :: event()

Map a {:key, key, mods} event to a highlight change or a commit.

Returns :noop when the highlight would not move, so an unchanged key falls through to the app's update/2 instead of arriving as a no-op message.