Stabilize themed DataGrid and pagination UI
This commit is contained in:
parent
e2f8fd812c
commit
7ee8d65679
16
README.md
16
README.md
@ -36,6 +36,22 @@ role `checkbox`, a stable `:ref`, and tab index `0` while enabled. Its
|
|||||||
`:on-change` receives the next boolean value; disabled checkboxes have no
|
`:on-change` receives the next boolean value; disabled checkboxes have no
|
||||||
handler or tab stop.
|
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-grid-*`, 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
|
Component `:styles` declarations own default appearance. A non-nil
|
||||||
presentation prop supplied by the caller wins; an omitted or nil prop leaves
|
presentation prop supplied by the caller wins; an omitted or nil prop leaves
|
||||||
the inherited Theme/default style available. Button has the deliberately small
|
the inherited Theme/default style available. Button has the deliberately small
|
||||||
|
|||||||
@ -33,6 +33,19 @@ Button 和 Checkbox 都是受控组件。`etaf-button` 支持 `:label`、
|
|||||||
`checkbox` role、稳定的 `:ref` 和 `tab-index 0`;`:on-change` 接收下一个
|
`checkbox` role、稳定的 `:ref` 和 `tab-index 0`;`:on-change` 接收下一个
|
||||||
布尔值。禁用 checkbox 没有回调或 tab stop。
|
布尔值。禁用 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-grid-*` 和 `: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
|
Component 的 `:styles` 声明负责默认外观。调用者提供的非 nil presentation
|
||||||
属性会覆盖默认值;省略或传 nil 时,继承的 Theme/默认样式仍可生效。Button
|
属性会覆盖默认值;省略或传 nil 时,继承的 Theme/默认样式仍可生效。Button
|
||||||
只提供刻意收敛的 `primary`、`secondary`、`ghost` 三种视觉 variant;这是
|
只提供刻意收敛的 `primary`、`secondary`、`ghost` 三种视觉 variant;这是
|
||||||
|
|||||||
379
etaf-ui.el
379
etaf-ui.el
@ -24,6 +24,7 @@
|
|||||||
(declare-function etaf-data-total "etaf-data" (controller))
|
(declare-function etaf-data-total "etaf-data" (controller))
|
||||||
(declare-function etaf-data-previous-page "etaf-data" (controller))
|
(declare-function etaf-data-previous-page "etaf-data" (controller))
|
||||||
(declare-function etaf-data-next-page "etaf-data" (controller))
|
(declare-function etaf-data-next-page "etaf-data" (controller))
|
||||||
|
(declare-function etaf-theme-defaults "etaf-context" (&optional default))
|
||||||
(declare-function text "etaf-view" (&rest arguments))
|
(declare-function text "etaf-view" (&rest arguments))
|
||||||
(declare-function row "etaf-view" (&rest arguments))
|
(declare-function row "etaf-view" (&rest arguments))
|
||||||
(declare-function column "etaf-view" (&rest arguments))
|
(declare-function column "etaf-view" (&rest arguments))
|
||||||
@ -31,6 +32,110 @@
|
|||||||
(declare-function expr "etaf-view" (&rest arguments))
|
(declare-function expr "etaf-view" (&rest arguments))
|
||||||
(declare-function slot "etaf-view" (&rest arguments))
|
(declare-function slot "etaf-view" (&rest arguments))
|
||||||
|
|
||||||
|
(defconst etaf-ui--default-theme-palette
|
||||||
|
'(:ui-fg "#252A2E"
|
||||||
|
:ui-bg "#FFFDF8"
|
||||||
|
:ui-border "#687386"
|
||||||
|
:ui-muted-fg "#526174"
|
||||||
|
:ui-danger-fg "#FF6B6B"
|
||||||
|
:ui-success-fg "#2F6B43"
|
||||||
|
:ui-disabled-fg "#687386"
|
||||||
|
:ui-disabled-bg "#E5E7EB"
|
||||||
|
:ui-disabled-border "#9CA3AF"
|
||||||
|
:ui-button-primary-fg "#FFFFFF"
|
||||||
|
:ui-button-primary-bg "#2F6B43"
|
||||||
|
:ui-button-primary-border "#2F6B43"
|
||||||
|
:ui-button-secondary-fg "#142235"
|
||||||
|
:ui-button-secondary-bg "#D9EEEA"
|
||||||
|
:ui-button-secondary-border "#2E8B83"
|
||||||
|
:ui-button-ghost-fg "#142235"
|
||||||
|
:ui-button-ghost-bg "#FFFDF8"
|
||||||
|
:ui-button-ghost-border "#C8C1B6"
|
||||||
|
:ui-checkbox-enabled-fg "#252A2E"
|
||||||
|
:ui-checkbox-enabled-bg "#DCEBDD"
|
||||||
|
:ui-checkbox-enabled-border "#6D8A73"
|
||||||
|
:ui-checkbox-disabled-fg "#6B7280"
|
||||||
|
:ui-checkbox-disabled-bg "#EEEAE2"
|
||||||
|
:ui-checkbox-disabled-border "#9CA3AF"
|
||||||
|
:ui-grid-border "#687386"
|
||||||
|
:ui-grid-selected-fg "#2F6B43"
|
||||||
|
:ui-grid-selected-bg "#DCEBDD"
|
||||||
|
:ui-grid-error-fg "#FF6B6B"
|
||||||
|
:ui-pagination-muted-fg "#526174"
|
||||||
|
:ui-panel-fg "#252A2E"
|
||||||
|
:ui-panel-bg "#FFFDF8"
|
||||||
|
:ui-panel-border "#687386")
|
||||||
|
"Default semantic UI palette, centralized outside Component definitions.
|
||||||
|
|
||||||
|
Applications normally override these tokens through ETAF Theme. Keeping the
|
||||||
|
fallback palette here gives the catalog a useful standalone appearance while
|
||||||
|
ensuring every Component reads one shared semantic vocabulary.")
|
||||||
|
|
||||||
|
(defconst etaf-ui--legacy-theme-aliases
|
||||||
|
'((:ui-fg :color)
|
||||||
|
(:ui-bg :bgcolor)
|
||||||
|
(:ui-border :border)
|
||||||
|
(:ui-button-primary-fg :ui-button-color)
|
||||||
|
(:ui-button-primary-bg :ui-button-bgcolor)
|
||||||
|
(:ui-button-primary-border :ui-button-border)
|
||||||
|
(:ui-button-secondary-fg :ui-button-secondary-color)
|
||||||
|
(:ui-button-secondary-bg :ui-button-secondary-bgcolor)
|
||||||
|
(:ui-button-secondary-border :ui-button-secondary-border)
|
||||||
|
(:ui-button-ghost-fg :ui-button-ghost-color)
|
||||||
|
(:ui-button-ghost-bg :ui-button-ghost-bgcolor)
|
||||||
|
(:ui-button-ghost-border :ui-button-ghost-border)
|
||||||
|
(:ui-disabled-fg :ui-button-disabled-color)
|
||||||
|
(:ui-disabled-bg :ui-button-disabled-bgcolor)
|
||||||
|
(:ui-disabled-border :ui-button-disabled-border))
|
||||||
|
"Compatibility aliases for the first ETAF UI Theme token spelling.")
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun etaf-ui-theme-values (&rest requested)
|
||||||
|
"Return merged semantic UI Theme values for REQUESTED tokens.
|
||||||
|
|
||||||
|
Inherited application tokens win, legacy aliases remain readable, and the
|
||||||
|
central catalog palette fills only omitted values. This is the boundary
|
||||||
|
between generic ETAF Theme Context and etaf-ui's product-independent visual
|
||||||
|
semantics; individual Components do not own separate color systems. When
|
||||||
|
REQUESTED is nil, return the complete catalog token map."
|
||||||
|
(let* ((inherited (etaf-theme-defaults))
|
||||||
|
(defaults etaf-ui--default-theme-palette)
|
||||||
|
;; Callers that request a subset only need that semantic subset.
|
||||||
|
;; Keeping the full inherited plist is useful for the no-argument
|
||||||
|
;; catalog query, but copying it for every Button/Panel/DataGrid
|
||||||
|
;; render needlessly scales Theme work with application token count.
|
||||||
|
(result (unless requested (copy-sequence inherited)))
|
||||||
|
(keys (or requested
|
||||||
|
(cl-loop for (key _spec) on defaults by #'cddr
|
||||||
|
collect key))))
|
||||||
|
(dolist (key keys)
|
||||||
|
(let ((found
|
||||||
|
(cond
|
||||||
|
((plist-member inherited key)
|
||||||
|
(cons t (plist-get inherited key)))
|
||||||
|
(t
|
||||||
|
(cl-loop for alias in etaf-ui--legacy-theme-aliases
|
||||||
|
when (and (eq (car alias) key)
|
||||||
|
(plist-member inherited (cadr alias)))
|
||||||
|
return
|
||||||
|
(cons t (plist-get inherited (cadr alias))))))))
|
||||||
|
(setq result
|
||||||
|
(plist-put result key
|
||||||
|
(if found (cdr found) (plist-get defaults key)))))
|
||||||
|
)
|
||||||
|
result))
|
||||||
|
|
||||||
|
(defun etaf-ui--theme-border (value)
|
||||||
|
"Return Ebox border VALUE, preserving complete caller-owned specs.
|
||||||
|
|
||||||
|
Semantic Theme border tokens conventionally contain a color string. The
|
||||||
|
catalog turns a hex color into its one-cell border shape; an existing border
|
||||||
|
plist or a legacy caller-owned string remains untouched for compatibility."
|
||||||
|
(if (and (stringp value)
|
||||||
|
(string-match-p "\\`#[[:xdigit:]]+\\'" value))
|
||||||
|
(list (list 1) 'solid value)
|
||||||
|
value))
|
||||||
|
|
||||||
(defun etaf-ui--class-value (base state custom)
|
(defun etaf-ui--class-value (base state custom)
|
||||||
"Return BASE and STATE classes with optional CUSTOM classes."
|
"Return BASE and STATE classes with optional CUSTOM classes."
|
||||||
(let ((custom (cond
|
(let ((custom (cond
|
||||||
@ -56,6 +161,26 @@ the optional text properties and keep the semantic role/event contract."
|
|||||||
'mouse-face 'highlight
|
'mouse-face 'highlight
|
||||||
'help-echo (format "%s · RET or mouse-1" (or label "Activate")))))
|
'help-echo (format "%s · RET or mouse-1" (or label "Activate")))))
|
||||||
|
|
||||||
|
(defun etaf-ui--button-variant-values (variant disabled)
|
||||||
|
"Return themed presentation defaults for Button VARIANT and DISABLED."
|
||||||
|
(let* ((fg (cond (disabled :ui-disabled-fg)
|
||||||
|
((eq variant 'secondary) :ui-button-secondary-fg)
|
||||||
|
((eq variant 'ghost) :ui-button-ghost-fg)
|
||||||
|
(t :ui-button-primary-fg)))
|
||||||
|
(bg (cond (disabled :ui-disabled-bg)
|
||||||
|
((eq variant 'secondary) :ui-button-secondary-bg)
|
||||||
|
((eq variant 'ghost) :ui-button-ghost-bg)
|
||||||
|
(t :ui-button-primary-bg)))
|
||||||
|
(border (cond (disabled :ui-disabled-border)
|
||||||
|
((eq variant 'secondary) :ui-button-secondary-border)
|
||||||
|
((eq variant 'ghost) :ui-button-ghost-border)
|
||||||
|
(t :ui-button-primary-border)))
|
||||||
|
(theme (etaf-ui-theme-values fg bg border)))
|
||||||
|
(list :color (plist-get theme fg)
|
||||||
|
:bgcolor (plist-get theme bg)
|
||||||
|
:border (etaf-ui--theme-border (plist-get theme border))
|
||||||
|
:face (if (or disabled (eq variant 'ghost)) 'normal 'bold))))
|
||||||
|
|
||||||
(defun etaf-ui--button-view
|
(defun etaf-ui--button-view
|
||||||
(label on-press disabled ref class color bgcolor border padding face
|
(label on-press disabled ref class color bgcolor border padding face
|
||||||
tab-index aria-label use)
|
tab-index aria-label use)
|
||||||
@ -88,6 +213,18 @@ TAB-INDEX, and ARIA-LABEL provide Host identity and presentation."
|
|||||||
:surface-properties surface
|
:surface-properties surface
|
||||||
:use (unless disabled use) (expr :value label))))))
|
:use (unless disabled use) (expr :value label))))))
|
||||||
|
|
||||||
|
(defun etaf-ui--checkbox-variant-values (theme disabled)
|
||||||
|
"Return semantic Theme values from THEME for DISABLED Checkbox state."
|
||||||
|
(let ((prefix (if disabled "disabled" "enabled")))
|
||||||
|
(list :color (plist-get theme
|
||||||
|
(intern (format ":ui-checkbox-%s-fg" prefix)))
|
||||||
|
:bgcolor (plist-get theme
|
||||||
|
(intern (format ":ui-checkbox-%s-bg" prefix)))
|
||||||
|
:border
|
||||||
|
(etaf-ui--theme-border
|
||||||
|
(plist-get theme
|
||||||
|
(intern (format ":ui-checkbox-%s-border" prefix)))))))
|
||||||
|
|
||||||
(defun etaf-ui--column-value (column key)
|
(defun etaf-ui--column-value (column key)
|
||||||
"Return KEY from COLUMN, accepting a plist or alist descriptor."
|
"Return KEY from COLUMN, accepting a plist or alist descriptor."
|
||||||
(if (and (listp column) (keywordp (car column)))
|
(if (and (listp column) (keywordp (car column)))
|
||||||
@ -142,10 +279,12 @@ too small; the original ROW remains intact for selection and callbacks."
|
|||||||
(etaf-ui--column-value column :key))
|
(etaf-ui--column-value column :key))
|
||||||
column)))))
|
column)))))
|
||||||
|
|
||||||
(defun etaf-ui--grid-header (columns)
|
(defun etaf-ui--grid-header (columns theme)
|
||||||
"Return a View header row for COLUMNS."
|
"Return a View header row for COLUMNS using semantic THEME colors."
|
||||||
(etaf-view
|
(etaf-view
|
||||||
(row :class "etaf-data-grid-header"
|
(row :class "etaf-data-grid-header"
|
||||||
|
:border (etaf-ui--theme-border
|
||||||
|
(plist-get theme :ui-grid-border))
|
||||||
(expr :value
|
(expr :value
|
||||||
(cl-loop for column in columns
|
(cl-loop for column in columns
|
||||||
for tail on columns
|
for tail on columns
|
||||||
@ -186,8 +325,8 @@ too small; the original ROW remains intact for selection and callbacks."
|
|||||||
|
|
||||||
(defun etaf-ui--grid-row
|
(defun etaf-ui--grid-row
|
||||||
(row columns row-key row-ref on-row-press selected-key row-selected-p
|
(row columns row-key row-ref on-row-press selected-key row-selected-p
|
||||||
row-actions)
|
row-actions theme)
|
||||||
"Return a View row for ROW and COLUMNS with the DataGrid contract.
|
"Return a View row for ROW and COLUMNS using THEME and the DataGrid contract.
|
||||||
|
|
||||||
ROW-KEY returns identity; ROW-REF returns the interactive reference;
|
ROW-KEY returns identity; ROW-REF returns the interactive reference;
|
||||||
ON-ROW-PRESS, SELECTED-KEY, and ROW-SELECTED-P control state. ROW-ACTIONS
|
ON-ROW-PRESS, SELECTED-KEY, and ROW-SELECTED-P control state. ROW-ACTIONS
|
||||||
@ -212,29 +351,35 @@ owns stable keyed callbacks across Range reevaluation."
|
|||||||
:ref host-ref
|
:ref host-ref
|
||||||
:role (when interactive-p 'button)
|
:role (when interactive-p 'button)
|
||||||
:tab-index (when interactive-p 0)
|
:tab-index (when interactive-p 0)
|
||||||
:surface-properties
|
:border-bottom-p t
|
||||||
(when interactive-p
|
:border-bottom-color (plist-get theme :ui-grid-border)
|
||||||
(etaf-ui--interactive-surface-properties
|
|
||||||
(format "Row %s" key) nil))
|
|
||||||
:on-press (when interactive-p
|
:on-press (when interactive-p
|
||||||
(etaf-ui--grid-row-action
|
(etaf-ui--grid-row-action
|
||||||
row-actions key row on-row-press))
|
row-actions key row on-row-press))
|
||||||
|
:bgcolor (when selected-p
|
||||||
|
(plist-get theme :ui-grid-selected-bg))
|
||||||
(expr :value (etaf-ui--grid-cells row columns))))))
|
(expr :value (etaf-ui--grid-cells row columns))))))
|
||||||
|
|
||||||
(defun etaf-ui--grid-rows
|
(defun etaf-ui--grid-rows
|
||||||
(items columns row-key row-ref on-row-press selected-key row-selected-p
|
(items columns row-key row-ref on-row-press selected-key row-selected-p
|
||||||
row-actions)
|
row-actions &optional theme)
|
||||||
"Return keyed item Views and prune ROW-ACTIONS outside current ITEMS.
|
"Return keyed item Views and prune ROW-ACTIONS outside current ITEMS.
|
||||||
COLUMNS and ROW-KEY describe cells and identity. ROW-REF, ON-ROW-PRESS,
|
COLUMNS and ROW-KEY describe cells and identity. ROW-REF, ON-ROW-PRESS,
|
||||||
SELECTED-KEY, and ROW-SELECTED-P provide interaction state."
|
SELECTED-KEY, and ROW-SELECTED-P provide interaction state."
|
||||||
(let ((seen (make-hash-table :test #'equal)))
|
(let* ((theme (or theme
|
||||||
|
;; A Range owns the data rows, so read Theme once here to
|
||||||
|
;; keep palette changes reactive without doing it per row.
|
||||||
|
(etaf-ui-theme-values :ui-grid-border
|
||||||
|
:ui-grid-selected-fg
|
||||||
|
:ui-grid-selected-bg)))
|
||||||
|
(seen (make-hash-table :test #'equal)))
|
||||||
(prog1
|
(prog1
|
||||||
(mapcar
|
(mapcar
|
||||||
(lambda (item)
|
(lambda (item)
|
||||||
(puthash (funcall row-key item) t seen)
|
(puthash (funcall row-key item) t seen)
|
||||||
(etaf-ui--grid-row
|
(etaf-ui--grid-row
|
||||||
item columns row-key row-ref on-row-press selected-key
|
item columns row-key row-ref on-row-press selected-key
|
||||||
row-selected-p row-actions))
|
row-selected-p row-actions theme))
|
||||||
items)
|
items)
|
||||||
(maphash (lambda (key _entry)
|
(maphash (lambda (key _entry)
|
||||||
(unless (gethash key seen)
|
(unless (gethash key seen)
|
||||||
@ -261,22 +406,7 @@ SELECTED-KEY, and ROW-SELECTED-P provide interaction state."
|
|||||||
current-press-p press-p)
|
current-press-p press-p)
|
||||||
(let* ((variant (and (not disabled) (etaf-current-prop :variant)))
|
(let* ((variant (and (not disabled) (etaf-current-prop :variant)))
|
||||||
(variant-values
|
(variant-values
|
||||||
(cond
|
(etaf-ui--button-variant-values variant disabled)))
|
||||||
(disabled
|
|
||||||
'(:color "#687386" :bgcolor "#E5E7EB"
|
|
||||||
:border ((1) solid "#9CA3AF") :face normal))
|
|
||||||
((eq variant 'secondary)
|
|
||||||
'(:color "#142235" :bgcolor "#D9EEEA"
|
|
||||||
:border ((1) solid "#2E8B83") :face bold))
|
|
||||||
((eq variant 'ghost)
|
|
||||||
'(:color "#142235" :bgcolor "#FFFDF8"
|
|
||||||
:border ((1) solid "#C8C1B6") :face normal))
|
|
||||||
;; The enabled catalog defaults used to come from state
|
|
||||||
;; selectors alone. Resolve them as props too so an
|
|
||||||
;; explicit theme can override the same state boundary.
|
|
||||||
(t
|
|
||||||
'(:color "#FFFFFF" :bgcolor "#2F6B43"
|
|
||||||
:border ((1) solid "#2F6B43") :face bold)))))
|
|
||||||
(etaf-ui--button-view
|
(etaf-ui--button-view
|
||||||
label (and press-p press) disabled (etaf-current-prop :ref)
|
label (and press-p press) disabled (etaf-current-prop :ref)
|
||||||
(etaf-current-prop :class)
|
(etaf-current-prop :class)
|
||||||
@ -313,6 +443,42 @@ contract; callers can still override presentation with the ordinary props."
|
|||||||
:setup
|
:setup
|
||||||
(etaf-ui--button-setup))
|
(etaf-ui--button-setup))
|
||||||
|
|
||||||
|
(etaf-define-component etaf-number-input
|
||||||
|
(&key value label on-change ref disabled min max aria-label)
|
||||||
|
"Render a controlled minibuffer-backed numeric input.
|
||||||
|
|
||||||
|
VALUE is displayed as a Button. Activating it reads a number through
|
||||||
|
Emacs's native minibuffer, validates optional MIN and MAX bounds, and calls
|
||||||
|
ON-CHANGE with the accepted integer. The Component owns prompting and
|
||||||
|
validation; the caller owns the value and subsequent state write."
|
||||||
|
:setup
|
||||||
|
(let ((callback (etaf-current-prop :on-change)))
|
||||||
|
(lambda ()
|
||||||
|
(let* ((value (etaf-current-prop :value))
|
||||||
|
(label (or (etaf-current-prop :label) "Value"))
|
||||||
|
(min-value (etaf-current-prop :min))
|
||||||
|
(max-value (etaf-current-prop :max))
|
||||||
|
(disabled (etaf-current-prop :disabled))
|
||||||
|
(ref (etaf-current-prop :ref))
|
||||||
|
(aria-label (etaf-current-prop :aria-label)))
|
||||||
|
(etaf-view
|
||||||
|
(button
|
||||||
|
:label (format "%s %s ✎" label (or value "—"))
|
||||||
|
:ref ref :disabled disabled
|
||||||
|
:aria-label (or aria-label label)
|
||||||
|
:variant 'ghost
|
||||||
|
:on-press
|
||||||
|
(unless disabled
|
||||||
|
(lambda ()
|
||||||
|
(let ((next (read-number
|
||||||
|
(format "%s: " label) (or value 0))))
|
||||||
|
(unless (and (integerp next)
|
||||||
|
(or (null min-value) (>= next min-value))
|
||||||
|
(or (null max-value) (<= next max-value)))
|
||||||
|
(user-error "%s must be an integer from %s to %s"
|
||||||
|
label (or min-value "—") (or max-value "—")))
|
||||||
|
(when callback (funcall callback next)))))))))))
|
||||||
|
|
||||||
(defun etaf-ui--checkbox-view
|
(defun etaf-ui--checkbox-view
|
||||||
(checked label on-change ref disabled class color bgcolor border padding
|
(checked label on-change ref disabled class color bgcolor border padding
|
||||||
face tab-index aria-label)
|
face tab-index aria-label)
|
||||||
@ -343,10 +509,10 @@ caller, so the Component works with local refs or Data-backed forms."
|
|||||||
:styles
|
:styles
|
||||||
(styles
|
(styles
|
||||||
("&" :width max-content)
|
("&" :width max-content)
|
||||||
("&.disabled" :color "#6B7280" :bgcolor "#EEEAE2"
|
;; Color, background, and border are resolved through the shared semantic
|
||||||
:border ((1) solid "#9CA3AF") :padding (0 1))
|
;; Theme map in :setup; styles keep only geometry defaults.
|
||||||
("&.enabled" :color "#252A2E" :bgcolor "#DCEBDD"
|
("&.disabled" :padding (0 1))
|
||||||
:border ((1) solid "#6D8A73") :padding (0 1))
|
("&.enabled" :padding (0 1))
|
||||||
(".etaf-checkbox-mark" :face bold :width 1))
|
(".etaf-checkbox-mark" :face bold :width 1))
|
||||||
:setup
|
:setup
|
||||||
(let* ((current-checked nil)
|
(let* ((current-checked nil)
|
||||||
@ -356,19 +522,34 @@ caller, so the Component works with local refs or Data-backed forms."
|
|||||||
(when current-callback
|
(when current-callback
|
||||||
(funcall current-callback (not current-checked))))))
|
(funcall current-callback (not current-checked))))))
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(setq current-checked (etaf-current-prop :checked)
|
(let* ((theme (etaf-ui-theme-values :ui-fg :ui-bg
|
||||||
|
:ui-checkbox-enabled-fg
|
||||||
|
:ui-checkbox-enabled-bg
|
||||||
|
:ui-checkbox-enabled-border
|
||||||
|
:ui-checkbox-disabled-fg
|
||||||
|
:ui-checkbox-disabled-bg
|
||||||
|
:ui-checkbox-disabled-border))
|
||||||
|
(variant-values
|
||||||
|
(etaf-ui--checkbox-variant-values
|
||||||
|
theme (etaf-current-prop :disabled))))
|
||||||
|
(setq current-checked (etaf-current-prop :checked)
|
||||||
current-callback
|
current-callback
|
||||||
(when (and (not (etaf-current-prop :disabled))
|
(when (and (not (etaf-current-prop :disabled))
|
||||||
(etaf-current-prop :on-change))
|
(etaf-current-prop :on-change))
|
||||||
(etaf-current-prop :on-change)))
|
(etaf-current-prop :on-change)))
|
||||||
(etaf-ui--checkbox-view
|
(etaf-ui--checkbox-view
|
||||||
current-checked (etaf-current-prop :label)
|
current-checked (etaf-current-prop :label)
|
||||||
(and current-callback press)
|
(and current-callback press)
|
||||||
(etaf-current-prop :ref) (etaf-current-prop :disabled)
|
(etaf-current-prop :ref) (etaf-current-prop :disabled)
|
||||||
(etaf-current-prop :class) (etaf-current-prop :color)
|
(etaf-current-prop :class)
|
||||||
(etaf-current-prop :bgcolor) (etaf-current-prop :border)
|
(or (etaf-current-prop :color)
|
||||||
(etaf-current-prop :padding) (etaf-current-prop :face)
|
(plist-get variant-values :color))
|
||||||
(etaf-current-prop :tab-index) (etaf-current-prop :aria-label)))))
|
(or (etaf-current-prop :bgcolor)
|
||||||
|
(plist-get variant-values :bgcolor))
|
||||||
|
(or (etaf-current-prop :border)
|
||||||
|
(plist-get variant-values :border))
|
||||||
|
(etaf-current-prop :padding) (etaf-current-prop :face)
|
||||||
|
(etaf-current-prop :tab-index) (etaf-current-prop :aria-label))))))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(etaf-define-component etaf-label
|
(etaf-define-component etaf-label
|
||||||
@ -381,29 +562,39 @@ caller, so the Component works with local refs or Data-backed forms."
|
|||||||
(text :class class :face face :color color :bgcolor bgcolor :border border
|
(text :class class :face face :color color :bgcolor bgcolor :border border
|
||||||
:padding padding :ref ref :width width (expr :value text)))))
|
:padding padding :ref ref :width width (expr :value text)))))
|
||||||
|
|
||||||
|
(defun etaf-ui--panel-view (title class color bgcolor border padding ref)
|
||||||
|
"Render a themed Panel View.
|
||||||
|
Arguments are TITLE, CLASS, COLOR, BGCOLOR, BORDER, PADDING, and REF."
|
||||||
|
(let ((theme (etaf-ui-theme-values :ui-panel-fg :ui-panel-bg
|
||||||
|
:ui-panel-border)))
|
||||||
|
(etaf-view
|
||||||
|
(column
|
||||||
|
:class (etaf-ui--class-value "etaf-panel" nil class)
|
||||||
|
:color (or color (plist-get theme :ui-panel-fg))
|
||||||
|
:bgcolor (or bgcolor (plist-get theme :ui-panel-bg))
|
||||||
|
:border (or border
|
||||||
|
(etaf-ui--theme-border
|
||||||
|
(plist-get theme :ui-panel-border)))
|
||||||
|
:padding padding :ref ref
|
||||||
|
(expr
|
||||||
|
:value
|
||||||
|
(when title
|
||||||
|
(etaf-view (text :class "etaf-panel-title"
|
||||||
|
(expr :value title)))))
|
||||||
|
(slot :name 'header)
|
||||||
|
(slot)))))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(etaf-define-component etaf-panel
|
(etaf-define-component etaf-panel
|
||||||
(&key title class color bgcolor border padding ref)
|
(&key title class color bgcolor border padding ref)
|
||||||
"Render a titled panel with header and default slot projections."
|
"Render a titled panel with header and default slot projections."
|
||||||
:styles
|
:styles
|
||||||
(styles
|
(styles
|
||||||
("&" :padding (1 2) :border ((1) solid "#687386")
|
("&" :padding (1 2))
|
||||||
:color "#252A2E" :bgcolor "#FFFDF8")
|
|
||||||
(".etaf-panel-title" :face bold))
|
(".etaf-panel-title" :face bold))
|
||||||
:view
|
:view
|
||||||
(expr
|
(expr :value
|
||||||
:value
|
(etaf-ui--panel-view title class color bgcolor border padding ref)))
|
||||||
(etaf-view
|
|
||||||
(column
|
|
||||||
:class (etaf-ui--class-value "etaf-panel" nil class)
|
|
||||||
:color color :bgcolor bgcolor :border border :padding padding :ref ref
|
|
||||||
(expr
|
|
||||||
:value
|
|
||||||
(when title
|
|
||||||
(etaf-view (text :class "etaf-panel-title"
|
|
||||||
(expr :value title)))))
|
|
||||||
(slot :name 'header)
|
|
||||||
(slot)))))
|
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(etaf-define-component etaf-data-grid
|
(etaf-define-component etaf-data-grid
|
||||||
@ -418,32 +609,43 @@ errors, pagination, mutation, and selection; this Component only projects
|
|||||||
those values into ordinary Hosts. Interactive rows require ROW-REF to return
|
those values into ordinary Hosts. Interactive rows require ROW-REF to return
|
||||||
a stable Host reference."
|
a stable Host reference."
|
||||||
:styles
|
:styles
|
||||||
(styles
|
(styles
|
||||||
(".etaf-data-grid-header" :face bold :padding (0 1)
|
(".etaf-data-grid-header" :face bold :padding (0 1))
|
||||||
:border ((1) solid "#687386"))
|
|
||||||
(".etaf-data-grid-header-cell" :face bold)
|
(".etaf-data-grid-header-cell" :face bold)
|
||||||
(".etaf-data-grid-row" :padding (0 1)
|
(".etaf-data-grid-row" :padding (0 1))
|
||||||
:border ((1) solid "#687386"))
|
;; Selection color and error color are dynamic semantic props below, so
|
||||||
(".selected" :color "#2F6B43"
|
;; this Component style scope contains geometry only.
|
||||||
:border ((1) solid "#73A982"))
|
)
|
||||||
(".etaf-data-grid-error" :color "#FF6B6B"))
|
|
||||||
:setup
|
:setup
|
||||||
(let ((row-actions (make-hash-table :test #'equal)))
|
(let ((row-actions (make-hash-table :test #'equal)))
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(let ((controller (etaf-current-prop :controller))
|
(let* ((controller (etaf-current-prop :controller))
|
||||||
(columns (etaf-current-prop :columns))
|
(columns (etaf-current-prop :columns))
|
||||||
(row-key (etaf-current-prop :row-key))
|
(row-key (etaf-current-prop :row-key))
|
||||||
(row-ref (etaf-current-prop :row-ref))
|
(row-ref (etaf-current-prop :row-ref))
|
||||||
(on-row-press (etaf-current-prop :on-row-press))
|
(on-row-press (etaf-current-prop :on-row-press))
|
||||||
(selected-key (etaf-current-prop :selected-key))
|
(selected-key (etaf-current-prop :selected-key))
|
||||||
(row-selected-p (etaf-current-prop :row-selected-p))
|
(row-selected-p (etaf-current-prop :row-selected-p))
|
||||||
(loading-label (etaf-current-prop :loading-label))
|
(loading-label (etaf-current-prop :loading-label))
|
||||||
(error-label (etaf-current-prop :error-label))
|
(error-label (etaf-current-prop :error-label))
|
||||||
(empty-label (etaf-current-prop :empty-label)))
|
(empty-label (etaf-current-prop :empty-label))
|
||||||
|
;; Resolve the shared semantic UI Theme once at the DataGrid
|
||||||
|
;; boundary. Rows reuse this immutable snapshot instead of
|
||||||
|
;; allocating or resolving border values independently during
|
||||||
|
;; every retained Range update.
|
||||||
|
(raw-theme (etaf-ui-theme-values :ui-fg
|
||||||
|
:ui-grid-border
|
||||||
|
:ui-grid-selected-bg))
|
||||||
|
(theme (plist-put (copy-sequence raw-theme)
|
||||||
|
:ui-grid-border
|
||||||
|
(etaf-ui--theme-border
|
||||||
|
(plist-get raw-theme :ui-grid-border))))
|
||||||
|
(theme-color (plist-get theme :ui-fg)))
|
||||||
(etaf-view
|
(etaf-view
|
||||||
(column
|
(column
|
||||||
:class "etaf-data-grid"
|
:class "etaf-data-grid" :color theme-color
|
||||||
(expr :value (etaf-ui--grid-header columns))
|
(expr :value (etaf-ui--grid-header
|
||||||
|
columns theme))
|
||||||
(column
|
(column
|
||||||
:class "etaf-data-grid-body"
|
:class "etaf-data-grid-body"
|
||||||
(expr
|
(expr
|
||||||
@ -466,12 +668,18 @@ a stable Host reference."
|
|||||||
((eq status 'error)
|
((eq status 'error)
|
||||||
(etaf-view
|
(etaf-view
|
||||||
(text :class "etaf-data-grid-error"
|
(text :class "etaf-data-grid-error"
|
||||||
|
:color (plist-get (etaf-ui-theme-values
|
||||||
|
:ui-grid-error-fg)
|
||||||
|
:ui-grid-error-fg)
|
||||||
(expr :value
|
(expr :value
|
||||||
(or error-label "Unable to load data.")))))
|
(or error-label "Unable to load data.")))))
|
||||||
((null items)
|
((null items)
|
||||||
(etaf-view
|
(etaf-view
|
||||||
(text (expr :value (or empty-label "No data.")))))
|
(text (expr :value (or empty-label "No data.")))))
|
||||||
(t
|
(t
|
||||||
|
;; Read Theme inside this Range's evaluator so retained row
|
||||||
|
;; Hosts follow palette changes without requiring the parent
|
||||||
|
;; DataGrid Component to be rebuilt.
|
||||||
(etaf-ui--grid-rows
|
(etaf-ui--grid-rows
|
||||||
items columns row-key row-ref on-row-press selected-key
|
items columns row-key row-ref on-row-press selected-key
|
||||||
row-selected-p row-actions))))))
|
row-selected-p row-actions))))))
|
||||||
@ -491,8 +699,7 @@ readable in both GUI and text review."
|
|||||||
:styles
|
:styles
|
||||||
(styles
|
(styles
|
||||||
("&" :width stretch)
|
("&" :width stretch)
|
||||||
(".etaf-pagination-label" :face bold)
|
(".etaf-pagination-label" :face bold))
|
||||||
(".etaf-pagination-summary" :color "#526174"))
|
|
||||||
:setup
|
:setup
|
||||||
(let* ((current-controller nil)
|
(let* ((current-controller nil)
|
||||||
(previous
|
(previous
|
||||||
@ -514,8 +721,13 @@ readable in both GUI and text review."
|
|||||||
(last-item (min total (* page page-size)))
|
(last-item (min total (* page page-size)))
|
||||||
(previous-disabled (or (eq status 'loading) (<= page 1)))
|
(previous-disabled (or (eq status 'loading) (<= page 1)))
|
||||||
(next-disabled (or (eq status 'loading) (>= page pages)))
|
(next-disabled (or (eq status 'loading) (>= page pages)))
|
||||||
(parent-color (etaf-current-prop :color))
|
(theme (etaf-ui-theme-values :ui-fg :ui-bg
|
||||||
(parent-bgcolor (etaf-current-prop :bgcolor))
|
:ui-disabled-fg
|
||||||
|
:ui-pagination-muted-fg))
|
||||||
|
(parent-color (or (etaf-current-prop :color)
|
||||||
|
(plist-get theme :ui-fg)))
|
||||||
|
(parent-bgcolor (or (etaf-current-prop :bgcolor)
|
||||||
|
(plist-get theme :ui-bg)))
|
||||||
(arrow-border '((0) solid "transparent")))
|
(arrow-border '((0) solid "transparent")))
|
||||||
(setq current-controller controller-value)
|
(setq current-controller controller-value)
|
||||||
(etaf-view
|
(etaf-view
|
||||||
@ -539,7 +751,9 @@ readable in both GUI and text review."
|
|||||||
:disabled previous-disabled
|
:disabled previous-disabled
|
||||||
:padding '(0 0)
|
:padding '(0 0)
|
||||||
:border arrow-border
|
:border arrow-border
|
||||||
:color (if previous-disabled "#9CA3AF" parent-color)
|
:color (if previous-disabled
|
||||||
|
(plist-get theme :ui-disabled-fg)
|
||||||
|
parent-color)
|
||||||
:bgcolor parent-bgcolor
|
:bgcolor parent-bgcolor
|
||||||
:face 'bold
|
:face 'bold
|
||||||
:on-press (unless previous-disabled previous)))
|
:on-press (unless previous-disabled previous)))
|
||||||
@ -547,6 +761,7 @@ readable in both GUI and text review."
|
|||||||
(text :class "etaf-pagination-label" :text-align 'center
|
(text :class "etaf-pagination-label" :text-align 'center
|
||||||
(expr :value (format "Page %d / %d" page pages)))
|
(expr :value (format "Page %d / %d" page pages)))
|
||||||
(text :class "etaf-pagination-summary" :text-align 'center
|
(text :class "etaf-pagination-summary" :text-align 'center
|
||||||
|
:color (plist-get theme :ui-pagination-muted-fg)
|
||||||
(expr :value (format "%d–%d of %d"
|
(expr :value (format "%d–%d of %d"
|
||||||
first-item last-item total))))
|
first-item last-item total))))
|
||||||
(column :width 'max-content
|
(column :width 'max-content
|
||||||
@ -556,7 +771,9 @@ readable in both GUI and text review."
|
|||||||
:disabled next-disabled
|
:disabled next-disabled
|
||||||
:padding '(0 0)
|
:padding '(0 0)
|
||||||
:border arrow-border
|
:border arrow-border
|
||||||
:color (if next-disabled "#9CA3AF" parent-color)
|
:color (if next-disabled
|
||||||
|
(plist-get theme :ui-disabled-fg)
|
||||||
|
parent-color)
|
||||||
:bgcolor parent-bgcolor
|
:bgcolor parent-bgcolor
|
||||||
:face 'bold
|
:face 'bold
|
||||||
:on-press (unless next-disabled next)))))))))
|
:on-press (unless next-disabled next)))))))))
|
||||||
|
|||||||
@ -19,7 +19,17 @@
|
|||||||
(let (found)
|
(let (found)
|
||||||
(maphash
|
(maphash
|
||||||
(lambda (_ref props)
|
(lambda (_ref props)
|
||||||
(when (equal (plist-get props :key) key)
|
(when (equal (plist-get props :key) key)
|
||||||
|
(setq found props)))
|
||||||
|
(etaf-runtime-host-props (etaf-runtime-for-buffer buffer-name)))
|
||||||
|
found))
|
||||||
|
|
||||||
|
(defun etaf-ui-test--props-with-class (buffer-name class)
|
||||||
|
"Return mounted Host properties with CLASS in BUFFER-NAME."
|
||||||
|
(let (found)
|
||||||
|
(maphash
|
||||||
|
(lambda (_ref props)
|
||||||
|
(when (equal (plist-get props :class) class)
|
||||||
(setq found props)))
|
(setq found props)))
|
||||||
(etaf-runtime-host-props (etaf-runtime-for-buffer buffer-name)))
|
(etaf-runtime-host-props (etaf-runtime-for-buffer buffer-name)))
|
||||||
found))
|
found))
|
||||||
@ -60,6 +70,38 @@
|
|||||||
:color nil :bgcolor nil)
|
:color nil :bgcolor nil)
|
||||||
(panel :title "Styled panel" :ref 'styled-panel))))))
|
(panel :title "Styled panel" :ref 'styled-panel))))))
|
||||||
|
|
||||||
|
(etaf-define-component etaf-ui-test-token-theme-fixture ()
|
||||||
|
"Provide explicit UI Button tokens through ETAF Theme."
|
||||||
|
:setup
|
||||||
|
(progn
|
||||||
|
(etaf-theme-provide
|
||||||
|
'(:ui-button-color "token-fg"
|
||||||
|
:ui-button-bgcolor "token-bg"
|
||||||
|
:ui-button-border "token-border"
|
||||||
|
:ui-button-secondary-color "secondary-fg"
|
||||||
|
:ui-button-secondary-bgcolor "secondary-bg"
|
||||||
|
:ui-button-secondary-border "secondary-border"))
|
||||||
|
(lambda ()
|
||||||
|
(etaf-view
|
||||||
|
(row
|
||||||
|
(button :label "Token" :ref 'token-button)
|
||||||
|
(button :label "Secondary" :ref 'token-secondary
|
||||||
|
:variant 'secondary))))))
|
||||||
|
|
||||||
|
(etaf-define-component etaf-ui-test-grid-theme-fixture
|
||||||
|
(&key controller theme)
|
||||||
|
"Provide a reactive Theme around one DataGrid for palette tests."
|
||||||
|
:setup
|
||||||
|
(progn
|
||||||
|
(etaf-theme-provide (etaf-current-prop :theme))
|
||||||
|
(lambda ()
|
||||||
|
(etaf-view
|
||||||
|
(data-grid
|
||||||
|
:controller (etaf-current-prop :controller)
|
||||||
|
:columns '((:key :id :label "ID") (:key :name :label "Name"))
|
||||||
|
:row-key (lambda (row) (plist-get row :id))
|
||||||
|
:selected-key 1)))))
|
||||||
|
|
||||||
(ert-deftest etaf-ui-button-use-behavior-dispatches-through-host ()
|
(ert-deftest etaf-ui-button-use-behavior-dispatches-through-host ()
|
||||||
"Install Button `:use' Behavior and dispatch its merged callback."
|
"Install Button `:use' Behavior and dispatch its merged callback."
|
||||||
(let ((buffer-name " *etaf-ui-button-use-test*"))
|
(let ((buffer-name " *etaf-ui-button-use-test*"))
|
||||||
@ -106,6 +148,49 @@
|
|||||||
(when-let ((buffer (get-buffer buffer-name)))
|
(when-let ((buffer (get-buffer buffer-name)))
|
||||||
(kill-buffer buffer)))))
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
|
(ert-deftest etaf-ui-button-reads-variant-tokens-from-theme ()
|
||||||
|
"Resolve Button presentation defaults from inherited Theme tokens."
|
||||||
|
(let ((buffer-name " *etaf-ui-button-token-theme-test*"))
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(etaf-mount buffer-name
|
||||||
|
(etaf-view (ui-test-token-theme-fixture)))
|
||||||
|
(let ((token (etaf-ui-test--props buffer-name 'token-button))
|
||||||
|
(secondary (etaf-ui-test--props buffer-name 'token-secondary)))
|
||||||
|
(should (equal "token-fg" (plist-get token :color)))
|
||||||
|
(should (equal "token-bg" (plist-get token :bgcolor)))
|
||||||
|
(should (equal "token-border" (plist-get token :border)))
|
||||||
|
(should (equal "secondary-fg" (plist-get secondary :color)))
|
||||||
|
(should (equal "secondary-bg" (plist-get secondary :bgcolor)))
|
||||||
|
(should (equal "secondary-border"
|
||||||
|
(plist-get secondary :border)))))
|
||||||
|
(when-let ((runtime (etaf-runtime-for-buffer buffer-name)))
|
||||||
|
(etaf-unmount runtime))
|
||||||
|
(when-let ((buffer (get-buffer buffer-name)))
|
||||||
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
|
(ert-deftest etaf-ui-number-input-validates-and-emits-value ()
|
||||||
|
"Prompt-backed number input owns bounds and emits the accepted value."
|
||||||
|
(let ((buffer-name " *etaf-ui-number-input-test*")
|
||||||
|
(value 3))
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(etaf-mount
|
||||||
|
buffer-name
|
||||||
|
(etaf-view
|
||||||
|
(number-input :label "Rows" :value value :ref 'rows
|
||||||
|
:min 1 :max 10
|
||||||
|
:on-change (lambda (next) (setq value next)))))
|
||||||
|
(cl-letf (((symbol-function 'read-number)
|
||||||
|
(lambda (&rest _args) 7)))
|
||||||
|
(etaf-dispatch-event (etaf-runtime-for-buffer buffer-name)
|
||||||
|
'rows 'press))
|
||||||
|
(should (= 7 value)))
|
||||||
|
(when-let ((runtime (etaf-runtime-for-buffer buffer-name)))
|
||||||
|
(etaf-unmount runtime))
|
||||||
|
(when-let ((buffer (get-buffer buffer-name)))
|
||||||
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
(ert-deftest etaf-ui-button-dispatches-and-exposes-enabled-props ()
|
(ert-deftest etaf-ui-button-dispatches-and-exposes-enabled-props ()
|
||||||
"Render an enabled button with semantic and presentation properties."
|
"Render an enabled button with semantic and presentation properties."
|
||||||
(let ((buffer-name " *etaf-ui-button-test*")
|
(let ((buffer-name " *etaf-ui-button-test*")
|
||||||
@ -409,6 +494,48 @@
|
|||||||
(when-let ((buffer (get-buffer buffer-name)))
|
(when-let ((buffer (get-buffer buffer-name)))
|
||||||
(kill-buffer buffer)))))
|
(kill-buffer buffer)))))
|
||||||
|
|
||||||
|
(ert-deftest etaf-ui-data-grid-follows-inherited-theme-color ()
|
||||||
|
"Re-render DataGrid rows when only inherited Theme color changes."
|
||||||
|
(let* ((theme (etaf-ref '(:color "light-ink"
|
||||||
|
:ui-grid-border "light-grid-border"
|
||||||
|
:ui-grid-selected-fg "light-selected"
|
||||||
|
:ui-grid-selected-bg "light-selected-bg")))
|
||||||
|
(source (etaf-data-memory-source
|
||||||
|
'((:id 1 :name "Ada") (:id 2 :name "Grace")) :id-key :id))
|
||||||
|
(controller (etaf-data-controller source :auto-load t))
|
||||||
|
(buffer-name " *etaf-ui-grid-theme-test*"))
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(etaf-mount
|
||||||
|
buffer-name
|
||||||
|
(etaf-view
|
||||||
|
(ui-test-grid-theme-fixture :controller controller :theme theme)))
|
||||||
|
(should (equal "light-selected-bg"
|
||||||
|
(plist-get (etaf-ui-test--props-with-key buffer-name 1)
|
||||||
|
:bgcolor)))
|
||||||
|
(should (equal "light-grid-border"
|
||||||
|
(plist-get (etaf-ui-test--props-with-key
|
||||||
|
buffer-name 1)
|
||||||
|
:border-bottom-color)))
|
||||||
|
(should (plist-get (etaf-ui-test--props-with-key buffer-name 1)
|
||||||
|
:border-bottom-p))
|
||||||
|
(setf (etaf-value theme) '(:color "dark-ink"
|
||||||
|
:ui-grid-border "dark-grid-border"
|
||||||
|
:ui-grid-selected-fg "dark-selected"
|
||||||
|
:ui-grid-selected-bg "dark-selected-bg"))
|
||||||
|
(should (equal "dark-selected-bg"
|
||||||
|
(plist-get (etaf-ui-test--props-with-key buffer-name 1)
|
||||||
|
:bgcolor)))
|
||||||
|
(should (equal "dark-grid-border"
|
||||||
|
(plist-get (etaf-ui-test--props-with-key
|
||||||
|
buffer-name 1)
|
||||||
|
:border-bottom-color))))
|
||||||
|
(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)))))
|
||||||
|
|
||||||
(ert-deftest etaf-ui-pagination-is-readable-and-boundary-safe ()
|
(ert-deftest etaf-ui-pagination-is-readable-and-boundary-safe ()
|
||||||
"Pagination renders Unicode controls, stable refs, and page boundaries."
|
"Pagination renders Unicode controls, stable refs, and page boundaries."
|
||||||
(let* ((source (etaf-data-memory-source
|
(let* ((source (etaf-data-memory-source
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user