Raxol. UI. Harness. OverlayPanel
(Raxol v2.6.1)
View Source
Pure, read-only footer-region content panel: a fixed-height scrollable
block of pre-formatted lines, driven entirely by
Raxol.UI.Harness.InputEvent-normalized events. No process, no I/O --
state is a plain map, and every function here is (t(), ...) -> t() | result, exactly Raxol.UI.Harness.OverlayPicker's style.
OverlayPicker vs. this module
OverlayPicker is filterable and selects -- a query row, ranked
matches, {:picked, item} on Enter. This module is its read-only
sibling: no query row, no selection, no {:picked, _} outcome ever.
It exists for content a footer panel needs to show, not choose from --
the worktracks/memory/plan read-models Raxol.Harness.PanelProjection
folds. Same host contract (fixed claimed height, exact-row-count
rendering, InputEvent-normalized handle_key/2), different shape of
interaction: scroll instead of filter-and-pick.
Fixed claimed height -- no per-keystroke footer re-pin
height/1 is 1 + max_visible, fixed at construction from :max_visible
alone -- it never tracks length(lines) the way a naive "shrink to fit"
panel would. This mirrors OverlayPicker's own fixed-height rationale
exactly: the footer viewport this panel is hosted inside grows its
DECSTBM split ONCE, at open, to fit the claimed height. If height instead
tracked content length, a put_lines/2 call mid-interaction (a footer
repaint pulling a fresh PanelProjection.render_lines/2 result) could
change the claimed row count and force a mid-interaction re-pin -- a
visible flicker. A panel that claims its full potential height up front,
then pads unused rows with blank lines, costs a few empty footer rows in
exchange for zero re-pinning churn.
State shape
%{
kind: :worktracks | :memory | :plan,
title: String.t(),
lines: [String.t()],
offset: non_neg_integer(),
max_visible: pos_integer()
}Rendering: text leaves only, exactly height/1 of them
render/1 returns a %{type: :column, children: [...]} view map whose
children are ALWAYS exactly height(t) %{type: :text} leaves: one
title row, then max_visible content rows (padded with blank leaves when
there are fewer lines than that). This is the same row-accounting
contract OverlayPicker establishes for its host's footer row budget --
load-bearing here for the identical reason: a caller (the harness
surface's footer composer) sizes the scroll-region split from height/1
alone and must never have to guess the actual child count. The title row
shows a "title (first-last/total)" range indicator only when content
overflows max_visible; otherwise it is the plain title. No width
truncation happens here -- Raxol.Harness.Surface.ViewText is the one
trust boundary that owns display-width truncation and control-byte
sanitization, so this module hands it full, untruncated line content
(already clamped upstream by Raxol.Harness.PanelProjection's own
hostile-content discipline).
Summary
Functions
The default :max_visible content-row cap new/1 uses. Exposed so hosts
clamp against THIS value rather than re-encoding the literal -- one
source of truth, mirroring OverlayPicker.default_max_visible/0.
Handles one normalized InputEvent.t(). Returns {:continue, t()} for
everything except :escape, which returns :dismissed (defensive -- in
the assembled harness surface, the Keymap's :overlay guard captures ESC
before it ever reaches this function, exactly as documented on
OverlayPicker.handle_key/2). Read-only: printable characters, Enter,
and paste are all inert no-ops. NEVER returns {:picked, _}.
The rows this panel claims -- fixed at construction from :max_visible
alone, never the live length(lines) (see moduledoc, "no per-keystroke
footer re-pin").
Builds a fresh panel.
Replaces the panel's content (e.g. a fresh
PanelProjection.render_lines/2 result on repaint), clamping a
now-stale offset into the new content's valid scroll range.
Renders the fixed-height view map (see moduledoc, "Rendering"): one title
row followed by exactly max_visible content-row leaves.
Types
@type t() :: %{ kind: atom(), title: String.t(), lines: [String.t()], offset: non_neg_integer(), max_visible: pos_integer() }
Functions
@spec default_max_visible() :: pos_integer()
The default :max_visible content-row cap new/1 uses. Exposed so hosts
clamp against THIS value rather than re-encoding the literal -- one
source of truth, mirroring OverlayPicker.default_max_visible/0.
@spec handle_key(t(), Raxol.UI.Harness.InputEvent.t()) :: {:continue, t()} | :dismissed
Handles one normalized InputEvent.t(). Returns {:continue, t()} for
everything except :escape, which returns :dismissed (defensive -- in
the assembled harness surface, the Keymap's :overlay guard captures ESC
before it ever reaches this function, exactly as documented on
OverlayPicker.handle_key/2). Read-only: printable characters, Enter,
and paste are all inert no-ops. NEVER returns {:picked, _}.
@spec height(t()) :: pos_integer()
The rows this panel claims -- fixed at construction from :max_visible
alone, never the live length(lines) (see moduledoc, "no per-keystroke
footer re-pin").
Builds a fresh panel.
Options
:kind(required) --:worktracks,:memory, or:plan; used only to derive the default title.:title(default derived from:kind-- "Worktracks"/"Memory"/"Plan"):max_visible(default8) -- the cap on content rows (excluding the title row)height/1claims.:lines(default[]) -- initial content, typicallyRaxol.Harness.PanelProjection.render_lines/2's output.
Replaces the panel's content (e.g. a fresh
PanelProjection.render_lines/2 result on repaint), clamping a
now-stale offset into the new content's valid scroll range.
Renders the fixed-height view map (see moduledoc, "Rendering"): one title
row followed by exactly max_visible content-row leaves.