etaf-ui/README.zh-CN.md
2026-08-31 15:17:30 +08:00

7.0 KiB
Raw Blame History

etaf-ui

etaf-ui 是 ETAF 的官方 Component 目录,是建立在 etaf 之上的可选独立包。它不增加第二套 Control 或 Widget 模型Button、Checkbox、Label、Panel、DataGrid 都是使用同一套 View、属性、slot、事件和 Data 契约的普通 Component。

(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))))))

.etaf 文件的结构使用已注册的短标签(panellabelbuttoncheckboxdata-grid)。配套 .el 文件负责响应式值、Action、Behavior 和回调;其中 etaf-view 也使用同一组短标签。Elisp 定义和 API 文档仍使用 规范的 etaf-* 名称。

Button 和 Checkbox 都是受控组件。etaf-button 支持 :label:on-press:disabled:ref,以及最小 presentation 属性 :class:color:bgcolor:border:padding:font-weight:tab-index:aria-label 和公共 Behavior 列表 :use。启用按钮默认使用 button role 和 tab-index 0;禁用按钮仍显示标签和禁用样式,但没有 press 回调、Behavior也不进入 tab 顺序。etaf-checkbox 支持 :checked:label:on-change:disabled:ref 及相同的 presentation 属性。启用时它提供 checkbox role、稳定的 :reftab-index 0:on-change 接收下一个 布尔值。禁用 checkbox 没有回调或 tab stop。

etaf-number-input 是一个受控的轻量输入组件;需要编辑数值时,它使用 Emacs 原生 minibuffer。它支持 :value:label:on-change:min:max:ref:disabled回调接收校验后的整数ref 仍由调用方持有。

目录统一使用语义化的 Theme 色彩 vocabulary主要 token 包括 :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:ui-panel-*。应用通过 ETAF 的 etaf-theme-provide 提供这些 token旧的 :ui-button-color 一类 alias 仍兼容。etaf-ui 不依赖 TP也不直接依赖 renderer-specific palette registry如果应用想使用 TP 的亮/暗 palette 数据,应先通过可选的 etaf-theme-tp adapter 转成同一份 ETAF Theme plist再交给 UI 目录。

Component 的 :styles 声明负责默认外观。调用者提供的非 nil presentation 属性会覆盖默认值;省略或传 nil 时,继承的 Theme/默认样式仍可生效。Button 只提供刻意收敛的 primarysecondaryghost 三种视觉 variant这是 视觉状态集合,不是第二套 widget 分类体系。

启用的 button 和 checkbox 共享同一套交互反馈:小手指针、原生 mouse-face 悬停反馈、可读的 help-echo、数字 tab stop 和语义 role。指针离开后 hover 自动结束,点击不会产生 retained 视觉状态。禁用控件保留文字和禁用样式,但 没有回调、指针激活或 tab stop。 显式 :on-* 回调和 Behavior 回调的组合由 Runtime 负责UI 包只声明控件和 视觉状态。

间距由父布局负责,而不是由 Button 偷塞。相邻控件应放在带明确横向 :gaprow/flex 中,这样每个控件的 mouse-face 和命中范围才保持语义独立。

etaf-label 支持 :text:class:color:bgcolor:font-weight:width:border:padding:refetaf-panel 支持 :title、相同的 surface presentation 属性,并投影命名的 header slot 和默认 slot。

公共 Component 的 business props 由实际定义导出,并与透传的 Host 属性分开:

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

目录中的每个公共 Component 都只挂载一个 Host root。未被作为 business prop 消费的所有合法 Host 属性(例如 :ref:class:aria-label)都会透传到 该 root透传规则并不意味着这些属性都是每个 Component 的 business prop。

DataGrid 支持列描述、函数型 :row-key,以及可选的 :loading-label:error-label:empty-label 或函数型 :row-selected-p。提供 :on-row-press 后,每一行都是带 button role 和 tab-index 0 的可交互 button且回调接收该行没有 :on-row-press 时,行 没有回调或 tab stop。

DataGrid 的 :row-ref 是可选项。交互式 grid 省略它时DataGrid 会根据 controller 持有的 row identity 派生稳定的内部 Host ref。fallback ref 不会 进入全局 symbol table并按每个 retained DataGrid instance 隔离,因此多个 grid 可以安全复用相同行 identity显式提供它时该回调拥有 identity并且必须为 每一行返回非 nil 的稳定 Host ref。 :row-key 仍然是 retained row 必须具备的非 nil 稳定标量 identity。 DataGrid 按 row key 保留唯一 action closure因此 selection 或 Data Range 更新不会为未变化的行重建 handler。 整数列 :width 表示可读字符容量不是裸像素DataGrid 原样使用 Ebox 原生字符单位, 并在非末列之间保留一个原生字符间距。

etaf-pagination 是受控 Data Component。它接收 Data controller 以及稳定的 :previous-ref:next-ref,显示易读的 / 控件和 Page N / M 摘要; 加载中或已经位于首/末页时会禁用对应动作。它不拥有页码状态,并复用 Button 的交互契约。

键盘焦点使用 ETAF 的公共 Runtime 端口:用 (etaf-focus-next runtime) 在带数值且非负 :tab-index 的可见 Host 之间 移动;需要代码触发动作时使用 (etaf-dispatch-event runtime ref 'press)。禁用控件和不可交互的 DataGrid 行不会进入 tab 顺序。

在该目录运行 make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs