fix: make component catalog package reload-safe
This commit is contained in:
parent
4d0d63bbb3
commit
6a1d816292
@ -52,8 +52,7 @@
|
||||
:color (plist-get theme color-key)
|
||||
:font-weight (when (memq variant '(strong heading)) 'bold))))
|
||||
|
||||
;;;###autoload
|
||||
(etaf-define-component etaf-label (&key text variant)
|
||||
(etaf-ui--define-component etaf-label (&key text variant)
|
||||
"Render TEXT as a semantic Label."
|
||||
:view
|
||||
(text
|
||||
@ -99,8 +98,7 @@
|
||||
(funcall callback)))))
|
||||
state))
|
||||
|
||||
;;;###autoload
|
||||
(etaf-define-component etaf-button
|
||||
(etaf-ui--define-component etaf-button
|
||||
(&key label on-press disabled ref class color bgcolor border padding
|
||||
font-weight tab-index aria-label use variant)
|
||||
"Render a standard pressable Button with retained callback identity.
|
||||
@ -154,8 +152,7 @@ across parent Component rerenders."
|
||||
:bgcolor (plist-get theme bg)
|
||||
:border (etaf-ui--style-border (plist-get theme border-key)))))
|
||||
|
||||
;;;###autoload
|
||||
(etaf-define-component etaf-checkbox
|
||||
(etaf-ui--define-component etaf-checkbox
|
||||
(&key checked label on-change disabled)
|
||||
"Render a controlled Checkbox whose next value is sent to ON-CHANGE."
|
||||
:view
|
||||
@ -187,8 +184,7 @@ across parent Component rerenders."
|
||||
("&.enabled" :padding (0 1))
|
||||
(".etaf-checkbox-mark" :font-weight bold :width 1)))
|
||||
|
||||
;;;###autoload
|
||||
(etaf-define-component etaf-panel (&key title variant)
|
||||
(etaf-ui--define-component etaf-panel (&key title variant)
|
||||
"Render a titled Panel with named header and default slots."
|
||||
:render
|
||||
(let ((theme (etaf-ui--style-tokens
|
||||
@ -216,8 +212,7 @@ across parent Component rerenders."
|
||||
("&" :padding (1 2))
|
||||
(".etaf-panel-title" :font-weight bold)))
|
||||
|
||||
;;;###autoload
|
||||
(etaf-define-component etaf-number-input
|
||||
(etaf-ui--define-component etaf-number-input
|
||||
(&key value label on-change disabled min max)
|
||||
"Render a controlled minibuffer-backed NumberInput using Button."
|
||||
:render
|
||||
|
||||
@ -240,7 +240,7 @@ LOADING-LABEL, ERROR-LABEL, and EMPTY-LABEL override the state text."
|
||||
(when (eq kind 'error) "etaf-data-grid-error")
|
||||
(and theme (plist-get theme :ui-data-grid-error-fg)))))
|
||||
|
||||
(etaf-define-component etaf-ui--data-grid-body-item
|
||||
(etaf-ui--define-component etaf-ui--data-grid-body-item
|
||||
(&key controller entry columns row-ref on-row-press row-selected-p
|
||||
grid-state)
|
||||
"Render one retained keyed DataGrid ENTRY with a cached row action."
|
||||
@ -252,8 +252,7 @@ LOADING-LABEL, ERROR-LABEL, and EMPTY-LABEL override the state text."
|
||||
(etaf-ui--style-tokens
|
||||
:ui-table-border :ui-table-selected-fg :ui-table-selected-bg))))
|
||||
|
||||
;;;###autoload
|
||||
(etaf-define-component etaf-data-grid
|
||||
(etaf-ui--define-component etaf-data-grid
|
||||
(&key controller columns row-key on-row-press row-ref row-selected-p
|
||||
loading-label error-label empty-label)
|
||||
"Render DATA CONTROLLER state through the public Component DSL.
|
||||
@ -297,8 +296,7 @@ actions; failed candidates cannot mutate committed handler inputs."
|
||||
:grid-state (etaf-state)))
|
||||
(slot :name 'footer)))
|
||||
|
||||
;;;###autoload
|
||||
(etaf-define-component etaf-pagination
|
||||
(etaf-ui--define-component etaf-pagination
|
||||
(&key controller previous-ref next-ref class color bgcolor border padding
|
||||
aria-label)
|
||||
"Render a controlled pager for DATA CONTROLLER with retained controls."
|
||||
|
||||
@ -12,6 +12,29 @@
|
||||
(require 'cl-lib)
|
||||
(require 'etaf)
|
||||
|
||||
(defvar etaf-ui--component-definition-signatures
|
||||
(make-hash-table :test #'eq)
|
||||
"Exact catalog definitions already installed in the current process.")
|
||||
|
||||
(defmacro etaf-ui--define-component (name arguments &rest clauses)
|
||||
"Define catalog Component NAME once for exact ARGUMENTS and CLAUSES.
|
||||
|
||||
`package.el' may reload dependency files after byte compilation. Identical
|
||||
catalog definitions are idempotent across that reload, while a changed
|
||||
definition still reaches `etaf-define-component' and therefore requires the
|
||||
public `etaf-component-redefine-run' boundary."
|
||||
(declare (indent 2) (debug defun))
|
||||
(let ((signature
|
||||
(secure-hash 'sha256
|
||||
(prin1-to-string (list name arguments clauses)))))
|
||||
`(unless (equal
|
||||
(gethash ',name etaf-ui--component-definition-signatures)
|
||||
,signature)
|
||||
(prog1
|
||||
(etaf-define-component ,name ,arguments ,@clauses)
|
||||
(puthash ',name ,signature
|
||||
etaf-ui--component-definition-signatures)))))
|
||||
|
||||
(defconst etaf-ui--style-palette
|
||||
'(:ui-fg "#252A2E"
|
||||
:ui-bg "#FFFDF8"
|
||||
|
||||
@ -202,7 +202,7 @@ ROW-REF, ON-ROW-PRESS, and ROW-SELECTED-P define optional interaction."
|
||||
(lambda () (funcall callback row-value))))
|
||||
(etaf-ui--table-row-children row columns))))
|
||||
|
||||
(etaf-define-component etaf-ui--table-header (&key columns)
|
||||
(etaf-ui--define-component etaf-ui--table-header (&key columns)
|
||||
"Render one retained Table header."
|
||||
:render (etaf-ui--table-header columns)
|
||||
:styles
|
||||
@ -210,7 +210,7 @@ ROW-REF, ON-ROW-PRESS, and ROW-SELECTED-P define optional interaction."
|
||||
(".etaf-table-header" :font-weight bold :padding (0 1))
|
||||
(".etaf-table-header-cell" :font-weight bold)))
|
||||
|
||||
(etaf-define-component etaf-ui--table-row
|
||||
(etaf-ui--define-component etaf-ui--table-row
|
||||
(&key row identity columns row-ref on-row-press row-selected-p)
|
||||
"Render one retained Table ROW with stable IDENTITY."
|
||||
:render
|
||||
@ -220,8 +220,7 @@ ROW-REF, ON-ROW-PRESS, and ROW-SELECTED-P define optional interaction."
|
||||
(styles
|
||||
(".etaf-table-row" :padding (0 1))))
|
||||
|
||||
;;;###autoload
|
||||
(etaf-define-component etaf-table
|
||||
(etaf-ui--define-component etaf-table
|
||||
(&key columns rows row-key row-ref on-row-press row-selected-p)
|
||||
"Render ordinary ROWS as a presentational Table.
|
||||
|
||||
|
||||
@ -106,7 +106,9 @@ it is not a claim that every attribute is a declared business prop.")
|
||||
"\\`etaf-ui-.*\\.el\\'"))
|
||||
(dolist (entry (etaf-ui-m0a--read-top-level-forms file))
|
||||
(let ((form (plist-get entry :form)))
|
||||
(when (and (eq (car-safe form) 'etaf-define-component)
|
||||
(when (and (memq (car-safe form)
|
||||
'(etaf-define-component
|
||||
etaf-ui--define-component))
|
||||
(symbolp (cadr form))
|
||||
(not (string-prefix-p "etaf-ui--"
|
||||
(symbol-name (cadr form)))))
|
||||
|
||||
@ -30,6 +30,24 @@ Use Chinese punctuation when CHINESE-P is non-nil."
|
||||
(plist-get component :observed-business-props)
|
||||
(if chinese-p "、" ", "))))
|
||||
|
||||
(ert-deftest etaf-ui-package-autoloads-never-register-components ()
|
||||
"Keep package activation from executing Component registration forms."
|
||||
(dolist (file (directory-files default-directory t "\\.el\\'"))
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^;;;###autoload[[:space:]]*$" nil t)
|
||||
(forward-comment (point-max))
|
||||
(should-not (eq (car-safe (read (current-buffer)))
|
||||
'etaf-define-component))))))
|
||||
|
||||
(ert-deftest etaf-ui-package-reload-keeps-identical-catalog-definitions ()
|
||||
"Allow package.el to reload exact catalog files without duplicate owners."
|
||||
(let ((before (etaf-ui-m0a-component-inventory)))
|
||||
(dolist (file '("etaf-ui-basic.el" "etaf-ui-table.el" "etaf-ui-data.el"))
|
||||
(load-file (expand-file-name file default-directory)))
|
||||
(should (equal before (etaf-ui-m0a-component-inventory)))))
|
||||
|
||||
(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))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user