7.4 KiB
etaf-ui
etaf-ui is the official ETAF Component catalog. It is a normal optional package above etaf; it does not add a second Control or Widget model. Button, Checkbox, Label, Panel, and DataGrid are all ordinary Components with the same View, props, slots, events, and Data contracts.
(require 'etaf-ui)
(let ((done (etaf-ref nil)))
(etaf-view
(panel :title "Account"
(slot :name 'header
(button :label "Save" :ref 'save-button
:on-press (lambda () (message "saved"))))
(label :text "Preferences")
(checkbox :checked (etaf-value done) :label "Done"
:ref 'done-checkbox
:on-change (lambda (next)
(setf (etaf-value done) next))))))
In .etaf files use the short registered tags (panel, label, button,
checkbox, and data-grid) for structure. The companion .el file owns
reactive values, Actions, Behaviors, and callbacks; keep the etaf-view tree
made from those same short tags. Canonical etaf-* names remain the Elisp
definition and API names.
Button and Checkbox are controlled Components. etaf-button accepts :label,
:on-press, :disabled, :ref, and the small presentation set
:class, :color, :bgcolor, :border, :padding, :font-weight, :tab-index,
and :aria-label, plus :use for a public Behavior list. An enabled button
defaults to role button and tab index 0; a disabled button keeps its label
and disabled styling but has no press handler, Behavior, or tab stop.
etaf-checkbox accepts :checked, :label,
:on-change, :disabled, :ref, and the same presentation props. It exposes
role checkbox, a stable :ref, and tab index 0 while enabled. Its
:on-change receives the next boolean value; disabled checkboxes have no
handler or tab stop.
etaf-number-input is a small controlled input primitive for values edited
through Emacs's native minibuffer. It accepts :value, :label, :on-change,
:min, :max, :ref, and :disabled; the callback receives the validated
integer while the caller remains the owner of the ref.
The catalog uses one semantic Theme vocabulary for color roles. The main
tokens are :ui-fg, :ui-bg, :ui-border, :ui-muted-fg,
:ui-disabled-*, :ui-button-primary-*, :ui-button-secondary-*,
:ui-button-ghost-*, :ui-checkbox-*, :ui-table-*,
:ui-data-grid-error-fg, :ui-pagination-muted-fg, and :ui-panel-*.
Applications provide these through ETAF's
etaf-theme-provide; legacy :ui-button-color-style aliases remain accepted
for compatibility. etaf-ui does not depend on TP or on a renderer-specific
palette registry. If an application wants TP's light/dark palette data, the
optional etaf-theme-tp adapter converts it into the same ETAF Theme plist
before the UI catalog sees it.
Component :styles declarations own default appearance. A non-nil
presentation prop supplied by the caller wins; an omitted or nil prop leaves
the inherited Theme/default style available. Button has the deliberately small
primary, secondary, and ghost visual vocabulary; it is a visual variant
set, not a second widget taxonomy.
Enabled buttons and checkboxes expose the same interaction affordances: a hand
pointer, native mouse-face hover feedback, a readable help-echo, a numeric
tab stop, and a semantic role. Hover ends when the pointer leaves; activation
does not create retained visual state. Disabled controls retain their text and disabled appearance but expose
no callback, pointer activation, or tab stop. The Runtime owns composition of
an explicit :on-* callback with a Behavior callback; the UI package only
declares the control and its visual state.
Spacing is owned by the parent layout, not by Button. Put adjacent controls in
a row/flex with an explicit horizontal :gap; this keeps each control's
mouse-face and hit range semantically separate.
etaf-label accepts :text, :class, :color, :bgcolor, :font-weight,
:width, :border, :padding, and :ref.
etaf-panel accepts :title, the same
surface presentation props, and projects the named header slot plus the
default slot.
The public Component business props are derived from the definitions and kept separate from forwarded Host attributes:
| Component | Business props |
|---|---|
etaf-label |
:text, :variant |
etaf-button |
:label, :on-press, :disabled, :ref, :class, :color, :bgcolor, :border, :padding, :font-weight, :tab-index, :aria-label, :use, :variant |
etaf-checkbox |
:checked, :label, :on-change, :disabled |
etaf-panel |
:title, :variant |
etaf-number-input |
:value, :label, :on-change, :disabled, :min, :max |
etaf-table |
:columns, :rows, :row-key, :row-ref, :on-row-press, :row-selected-p |
etaf-data-grid |
:controller, :columns, :row-key, :on-row-press, :row-ref, :row-selected-p, :loading-label, :error-label, :empty-label |
etaf-pagination |
:controller, :previous-ref, :next-ref, :class, :color, :bgcolor, :border, :padding, :aria-label |
Every public catalog Component mounts exactly one Host root. All valid Host
attributes that are not consumed as business props—such as :ref, :class,
and :aria-label—are forwarded to that root. This forwarding rule does not
make those attributes business props of every Component.
DataGrid accepts column descriptors, a function-valued :row-key, and the
optional :loading-label, :error-label, :empty-label, or function-valued
:row-selected-p. When :on-row-press is supplied, every row is an
interactive button with role button and tab index 0; the callback receives
the row. Without :on-row-press, rows have no callback or tab stop.
:row-ref is optional for DataGrid. When it is omitted on an interactive
grid, DataGrid derives a stable internal Host reference from the row identity
owned by the controller. Fallback refs are uninterned and scoped to each
retained DataGrid instance, so separate grids may share row identities without
colliding. When :row-ref is supplied, the callback owns that identity and
must return a non-nil stable Host reference for every row. :row-key
remains a required non-nil stable scalar identity for retained rows. DataGrid
retains one action closure per row key, so selection or Data Range updates do
not recreate handlers for unchanged rows.
An integer column :width is readable character capacity, not a raw pixel
value. DataGrid passes Ebox's native character unit through and reserves one
native character between non-final tracks.
etaf-pagination is a controlled Data Component. It accepts a Data controller
plus stable :previous-ref and :next-ref values, renders readable ←/→
controls and a Page N / M summary, and disables the boundary action while a
page is loading or already at the first/last page. It owns no page state and
uses the same Button interaction contract.
Keyboard focus uses ETAF's public runtime ports: call
(etaf-focus-next runtime) to move through visible Hosts with a numeric
non-negative :tab-index, and dispatch through
(etaf-dispatch-event runtime ref 'press) when an action must be invoked by
code. Disabled controls and non-interactive DataGrid rows are omitted from
that tab order.
Run make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs from this directory.