docs: align etaf-ui DataGrid public contract
This commit is contained in:
parent
7f465fc6d7
commit
c1c3015fa7
48
README.md
48
README.md
@ -44,8 +44,9 @@ 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-grid-*`, and
|
||||
`:ui-panel-*`. Applications provide these through ETAF's
|
||||
`: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
|
||||
@ -76,16 +77,41 @@ mouse-face and hit range semantically separate.
|
||||
surface presentation props, and projects the named `header` slot plus the
|
||||
default slot.
|
||||
|
||||
<!-- M0b1: business-props -->
|
||||
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` |
|
||||
|
||||
<!-- M0b1: single-host-root -->
|
||||
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`, `:selected-key`, or
|
||||
function-valued `:row-selected-p`. When `:on-row-press` is supplied, every
|
||||
row is an interactive button: `:row-ref` is required, must return a non-nil
|
||||
stable Host reference for each row, and the row receives role `button` and
|
||||
tab index `0`. `:on-row-press` receives the row. Without `:on-row-press`, rows
|
||||
have no callback or tab stop. `: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.
|
||||
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.
|
||||
|
||||
<!-- M0b1: row-ref-optional -->
|
||||
`: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. When it 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.
|
||||
|
||||
@ -40,7 +40,8 @@ Emacs 原生 minibuffer。它支持 `:value`、`:label`、`:on-change`、`:min`
|
||||
目录统一使用语义化的 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-grid-*` 和 `:ui-panel-*`。应用通过 ETAF 的
|
||||
`: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 数据,应先通过可选的
|
||||
@ -66,14 +67,38 @@ Component 的 `:styles` 声明负责默认外观。调用者提供的非 nil pre
|
||||
`etaf-panel` 支持 `:title`、相同的
|
||||
surface presentation 属性,并投影命名的 `header` slot 和默认 slot。
|
||||
|
||||
<!-- M0b1: business-props -->
|
||||
公共 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` |
|
||||
|
||||
<!-- M0b1: single-host-root -->
|
||||
目录中的每个公共 Component 都只挂载一个 Host root。未被作为 business prop
|
||||
消费的所有合法 Host 属性(例如 `:ref`、`:class` 和 `:aria-label`)都会透传到
|
||||
该 root;透传规则并不意味着这些属性都是每个 Component 的 business prop。
|
||||
|
||||
DataGrid 支持列描述、函数型 `:row-key`,以及可选的
|
||||
`:loading-label`、`:error-label`、`:empty-label`、`:selected-key` 或函数型
|
||||
`:row-selected-p`。提供 `:on-row-press` 后,每一行都是可交互 button:必须
|
||||
提供 `:row-ref`,它对每一行返回非 nil 的稳定 Host ref;行会获得
|
||||
`button` role 和 `tab-index 0`,且回调接收该行。没有 `:on-row-press` 时,
|
||||
行没有回调或 tab stop。`:row-key` 仍然是 retained row 必须具备的非 nil
|
||||
稳定标量 identity。DataGrid 按 row key 保留唯一 action closure,因此 selection
|
||||
或 Data Range 更新不会为未变化的行重建 handler。
|
||||
`:loading-label`、`:error-label`、`:empty-label` 或函数型
|
||||
`:row-selected-p`。提供 `:on-row-press` 后,每一行都是带 `button` role 和
|
||||
`tab-index 0` 的可交互 button,且回调接收该行;没有 `:on-row-press` 时,行
|
||||
没有回调或 tab stop。
|
||||
|
||||
<!-- M0b1: row-ref-optional -->
|
||||
DataGrid 的 `:row-ref` 是可选项。交互式 grid 省略它时,DataGrid 会根据
|
||||
controller 持有的 row identity 派生稳定的内部 Host ref;显式提供它时,
|
||||
该回调拥有 identity,并且必须为每一行返回非 nil 的稳定 Host ref。
|
||||
`:row-key` 仍然是 retained row 必须具备的非 nil 稳定标量 identity。
|
||||
DataGrid 按 row key 保留唯一 action closure,因此 selection 或 Data Range
|
||||
更新不会为未变化的行重建 handler。
|
||||
整数列 `:width` 表示可读字符容量,不是裸像素;DataGrid 原样使用 Ebox 原生字符单位,
|
||||
并在非末列之间保留一个原生字符间距。
|
||||
|
||||
|
||||
108
tests/etaf-ui-m0b1-doc-contract-tests.el
Normal file
108
tests/etaf-ui-m0b1-doc-contract-tests.el
Normal file
@ -0,0 +1,108 @@
|
||||
;;; etaf-ui-m0b1-doc-contract-tests.el --- M0b1 UI documentation contracts -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Code:
|
||||
(require 'ert)
|
||||
(require 'seq)
|
||||
(require 'etaf-ui)
|
||||
(declare-function etaf-ui-m0a-component-inventory
|
||||
"../scripts/etaf-ui-m0a-inventory")
|
||||
(load-file (expand-file-name "scripts/etaf-ui-m0a-inventory.el"
|
||||
default-directory))
|
||||
|
||||
(defconst etaf-ui-m0b1-doc--readmes
|
||||
'("README.md" "README.zh-CN.md")
|
||||
"README files governed by the M0b1 etaf-ui documentation contract.")
|
||||
|
||||
(defun etaf-ui-m0b1-doc--contents (file)
|
||||
"Return FILE contents from the package root."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents (expand-file-name file default-directory))
|
||||
(buffer-string)))
|
||||
|
||||
(defun etaf-ui-m0b1-doc--component-table-row (component chinese-p)
|
||||
"Return COMPONENT's exact README table row.
|
||||
|
||||
Use Chinese punctuation when CHINESE-P is non-nil."
|
||||
(format "| `%s` | %s |"
|
||||
(plist-get component :name)
|
||||
(mapconcat
|
||||
(lambda (prop) (format "`:%s`" prop))
|
||||
(plist-get component :observed-business-props)
|
||||
(if chinese-p "、" ", "))))
|
||||
|
||||
(ert-deftest etaf-ui-m0b1-readmes-match-runtime-component-manifest ()
|
||||
"Keep the README contract aligned with the runtime-derived inventory."
|
||||
(let* ((components (etaf-ui-m0a-component-inventory))
|
||||
(grid (seq-find (lambda (entry)
|
||||
(eq (plist-get entry :name) 'etaf-data-grid))
|
||||
components)))
|
||||
(should (= 8 (length components)))
|
||||
(should
|
||||
(equal '(controller columns row-key on-row-press row-ref row-selected-p
|
||||
loading-label error-label empty-label)
|
||||
(plist-get grid :observed-business-props)))
|
||||
(dolist (file etaf-ui-m0b1-doc--readmes)
|
||||
(let ((contents (etaf-ui-m0b1-doc--contents file))
|
||||
(chinese-p (string-match-p "zh-CN" file)))
|
||||
(should-not (string-match-p ":selected-key" contents))
|
||||
(dolist (component components)
|
||||
(should
|
||||
(string-match-p
|
||||
(regexp-quote
|
||||
(etaf-ui-m0b1-doc--component-table-row component chinese-p))
|
||||
contents))
|
||||
(should (eq 'all-valid-host-attrs
|
||||
(plist-get component :forwarded-host-attrs)))
|
||||
(should (eq 'single-host-root
|
||||
(plist-get component :root-guarantee))))))))
|
||||
|
||||
(ert-deftest etaf-ui-m0b1-readmes-document-forwarding-and-row-ref-fallback ()
|
||||
"Document the single-root forwarding and optional row-ref contracts."
|
||||
(dolist (file etaf-ui-m0b1-doc--readmes)
|
||||
(let ((contents (etaf-ui-m0b1-doc--contents file)))
|
||||
(should (string-match-p "M0b1: single-host-root" contents))
|
||||
(should (string-match-p "M0b1: row-ref-optional" contents))
|
||||
(should (string-match-p "M0b1: business-props" contents)))))
|
||||
|
||||
(ert-deftest etaf-ui-m0b1-data-grid-row-ref-current-contract ()
|
||||
"Use a controller fallback when row-ref is nil and honor an explicit ref."
|
||||
(dolist (explicit '(nil explicit))
|
||||
(let* ((source (etaf-data-memory-source '((:id 7 :name "Ada"))
|
||||
:id-key :id))
|
||||
(controller (etaf-data-controller source :auto-load t))
|
||||
(buffer-name (generate-new-buffer-name " *etaf-ui-m0b1-row-ref*"))
|
||||
pressed)
|
||||
(unwind-protect
|
||||
(progn
|
||||
(etaf-mount
|
||||
buffer-name
|
||||
(etaf-view
|
||||
(etaf-data-grid
|
||||
:controller controller
|
||||
:columns '((:key :id :label "ID"))
|
||||
:row-key (lambda (row) (plist-get row :id))
|
||||
:row-ref (when explicit
|
||||
(lambda (row)
|
||||
(intern (format "documented-row-%s"
|
||||
(plist-get row :id)))))
|
||||
:on-row-press (lambda (row) (setq pressed row)))))
|
||||
(let ((runtime (etaf-runtime-for-buffer buffer-name))
|
||||
host-ref)
|
||||
(maphash
|
||||
(lambda (ref props)
|
||||
(when (equal 7 (plist-get props :key))
|
||||
(setq host-ref ref)))
|
||||
(etaf-runtime-host-props runtime))
|
||||
(should host-ref)
|
||||
(when explicit
|
||||
(should (eq 'documented-row-7 host-ref)))
|
||||
(etaf-dispatch-event runtime host-ref 'press)
|
||||
(should (equal 7 (plist-get pressed :id)))))
|
||||
(when-let* ((runtime (etaf-runtime-for-buffer buffer-name)))
|
||||
(etaf-unmount runtime))
|
||||
(etaf-data-stop controller)
|
||||
(when-let* ((buffer (get-buffer buffer-name)))
|
||||
(kill-buffer buffer))))))
|
||||
|
||||
(provide 'etaf-ui-m0b1-doc-contract-tests)
|
||||
;;; etaf-ui-m0b1-doc-contract-tests.el ends here
|
||||
Loading…
Reference in New Issue
Block a user