refactor: derive Ebox styles from immutable package schemas
This commit is contained in:
parent
8b28f278bd
commit
31046ad8c9
@ -79,6 +79,7 @@ Caller-owned Source Tree
|
||||
- A failed candidate leaves the previous buffer, runtime identity, and report intact.
|
||||
- Keys are local to siblings; visible strings are never used as identity.
|
||||
- Ebox owns logical node-to-subject adaptation and id/class/type candidate indexes; ECSS's public structured matcher is the only selector truth source. Subjects expose built-in id/key plus explicit `:selector-attributes`, never visible content, layout state, or runtime containers.
|
||||
- `ebox-style--property-definitions` is the single Ebox author-property source. Load time derives Ebox's read-only lookup index and one immutable ECSS package schema from it; the surface schema is composed once, and node construction or updates never re-register or copy the whole schema domain.
|
||||
- `owner-rerender` is broader than `span-patch`, which is broader than `paint-patch`.
|
||||
- Buffer coordinates belong to the generation that produced them and must be refreshed after mutation.
|
||||
- Grid uses the normal measurement and rendering pipeline. Native reflow may reject an ineligible tree and must fall back to Elisp without changing correctness.
|
||||
|
||||
@ -79,6 +79,7 @@ active 合同还覆盖 `Makefile`、`.github/workflows/ci.yml`、`tests/ebox-cor
|
||||
- 候选失败时必须保留之前的 buffer、runtime identity 和报告。
|
||||
- Key 只在兄弟节点中有效;不能用可见字符串作为 identity。
|
||||
- Ebox 只拥有逻辑 node-to-subject 适配与 id/class/type 候选索引;ECSS 的公共结构化 matcher 是唯一 selector 真相源。subject 只暴露内建 id/key 和显式 `:selector-attributes`,绝不暴露可见 content、布局状态或 runtime 容器。
|
||||
- `ebox-style--property-definitions` 是 Ebox 作者属性的唯一来源:加载时一次生成 Ebox 自己的只读查询索引与一个不可变 ECSS package schema;surface schema 只组合一次,node 构造和更新不重复注册或复制整份 schema。
|
||||
- `owner-rerender` 范围大于 `span-patch`,`span-patch` 大于 `paint-patch`。
|
||||
- Buffer 坐标属于生成它的 generation,变更后必须重新获取。
|
||||
- Grid 使用普通测量与渲染流水线;native reflow 可以拒绝不适合的树并回退到 Elisp,正确性不变。
|
||||
|
||||
@ -155,7 +155,7 @@ and author TAG."
|
||||
(error "%s rejected properties: %s"
|
||||
context (error-message-string err)))))))
|
||||
(cl-loop for (id _value) on declarations by #'cddr
|
||||
for property = (or (ebox-style-property id)
|
||||
for property = (or (ebox-style--property id)
|
||||
(error "Missing canonical Ebox property: %S"
|
||||
id))
|
||||
unless (funcall predicate property)
|
||||
|
||||
@ -6401,7 +6401,7 @@ by impact classification; the declaration container itself is not geometry."
|
||||
(memq key ebox-tree--excluded-source-keys)
|
||||
(memq key '(:props :raw-props
|
||||
:ebox-style-declarations))
|
||||
(ebox-style-property key))
|
||||
(ebox-style--property key))
|
||||
(push key seen)
|
||||
(let ((old-entry (plist-member old key))
|
||||
(new-entry (plist-member new key)))
|
||||
@ -6683,7 +6683,7 @@ derive render changes from the computed fragment style signatures."
|
||||
(ordinary-keys
|
||||
(cl-remove-if
|
||||
(lambda (key)
|
||||
(or (ebox-style-property key)
|
||||
(or (ebox-style--property key)
|
||||
(memq key ebox-tree-metadata-source-keys)))
|
||||
source-keys))
|
||||
(style-keys
|
||||
|
||||
198
ebox-style.el
198
ebox-style.el
@ -24,17 +24,12 @@
|
||||
"ebox-flex" (value allowed-keywords))
|
||||
(declare-function ebox--nonnegative-horizontal-size-pixels
|
||||
"ebox-layout" (value &optional default))
|
||||
(declare-function ecss-schema-set-create "ecss" ())
|
||||
(declare-function ecss-stylesheet-create "ecss" ())
|
||||
|
||||
(defvar ebox--preferred-size-keywords)
|
||||
(defvar ebox--min-size-keywords)
|
||||
(defvar ebox--max-size-keywords)
|
||||
|
||||
(defun ebox-style--create-schema-set ()
|
||||
"Return one private ECSS schema set for Ebox style state."
|
||||
(ecss-schema-set-create))
|
||||
|
||||
(defun ebox-style--create-stylesheet ()
|
||||
"Return one private ECSS stylesheet for Ebox style state."
|
||||
(ecss-stylesheet-create))
|
||||
@ -226,24 +221,37 @@
|
||||
(:name :grid-row-span :id ebox/grid-row-span :initial nil
|
||||
:contexts (item) :group geometry :dirty-kind geometry :signature layout
|
||||
:validator positive-integer)
|
||||
(:name :padding :id ebox/padding :shorthand padding)
|
||||
(:name :padding-inline :id ebox/padding-inline :shorthand padding-inline)
|
||||
(:name :padding-block :id ebox/padding-block :shorthand padding-block)
|
||||
(:name :margin :id ebox/margin :shorthand margin)
|
||||
(:name :margin-inline :id ebox/margin-inline :shorthand margin-inline)
|
||||
(:name :margin-block :id ebox/margin-block :shorthand margin-block)
|
||||
(:name :border :id ebox/border :shorthand border)
|
||||
(:name :border-top :id ebox/border-top :shorthand border-top)
|
||||
(:name :border-right :id ebox/border-right :shorthand border-right)
|
||||
(:name :border-bottom :id ebox/border-bottom :shorthand border-bottom)
|
||||
(:name :border-left :id ebox/border-left :shorthand border-left)
|
||||
(:name :padding :id ebox/padding :shorthand padding
|
||||
:impacts (geometry) :projections (layout))
|
||||
(:name :padding-inline :id ebox/padding-inline :shorthand padding-inline
|
||||
:impacts (geometry) :projections (layout))
|
||||
(:name :padding-block :id ebox/padding-block :shorthand padding-block
|
||||
:impacts (geometry) :projections (layout))
|
||||
(:name :margin :id ebox/margin :shorthand margin
|
||||
:impacts (geometry) :projections (layout))
|
||||
(:name :margin-inline :id ebox/margin-inline :shorthand margin-inline
|
||||
:impacts (geometry) :projections (layout))
|
||||
(:name :margin-block :id ebox/margin-block :shorthand margin-block
|
||||
:impacts (geometry) :projections (layout))
|
||||
(:name :border :id ebox/border :shorthand border
|
||||
:impacts (geometry structure paint) :projections (layout paint))
|
||||
(:name :border-top :id ebox/border-top :shorthand border-top
|
||||
:impacts (geometry structure paint) :projections (layout paint))
|
||||
(:name :border-right :id ebox/border-right :shorthand border-right
|
||||
:impacts (geometry structure paint) :projections (layout paint))
|
||||
(:name :border-bottom :id ebox/border-bottom :shorthand border-bottom
|
||||
:impacts (geometry structure paint) :projections (layout paint))
|
||||
(:name :border-left :id ebox/border-left :shorthand border-left
|
||||
:impacts (geometry structure paint) :projections (layout paint))
|
||||
(:name :border-top-p :id ebox/border-top-p
|
||||
:shorthand border-top-present :validator boolean
|
||||
:exclusive-outputs t
|
||||
:impacts (geometry structure) :projections (layout)
|
||||
:group structure :dirty-kind structure :signature layout)
|
||||
(:name :border-bottom-p :id ebox/border-bottom-p
|
||||
:shorthand border-bottom-present :validator boolean
|
||||
:exclusive-outputs t
|
||||
:impacts (geometry structure) :projections (layout)
|
||||
:group structure :dirty-kind structure :signature layout)
|
||||
(:name :border-width :id ebox/border-width :shorthand border-width
|
||||
:group geometry :dirty-kind geometry :signature layout)
|
||||
@ -259,16 +267,8 @@
|
||||
:group geometry :dirty-kind geometry :signature layout))
|
||||
"Canonical Ebox property schemas and public aliases.")
|
||||
|
||||
(defvar ebox-style--property-table nil
|
||||
"Hash table from canonical property names and aliases to metadata.")
|
||||
|
||||
(defvar ebox-style-schemas (ebox-style--create-schema-set)
|
||||
"ECSS property schemas for the isolated Ebox style domain.")
|
||||
|
||||
(defvar ebox-style--registered-schemas nil
|
||||
"Schema-set object whose Ebox properties are already registered.
|
||||
`ecss-schema-set-property' returns a detached schema copy, so probing it on
|
||||
every `ebox-create' needlessly copies the entire property metadata domain.")
|
||||
(defvar ebox-style-schemas)
|
||||
(defvar ebox-style--property-index)
|
||||
|
||||
(defconst ebox-style--declaration-cache-max-entries 512
|
||||
"Maximum canonical declaration compilations retained by Ebox.")
|
||||
@ -277,44 +277,27 @@ every `ebox-create' needlessly copies the entire property metadata domain.")
|
||||
(make-hash-table :test 'equal)
|
||||
"Bounded memo table for canonical Ebox declaration compilation.")
|
||||
|
||||
(defun ebox-style--register-property-name (table name property role)
|
||||
"Register NAME for PROPERTY ROLE in TABLE, rejecting collisions."
|
||||
(when-let* ((existing (gethash name table)))
|
||||
(unless (eq existing property)
|
||||
(error "Ebox property %S %s collides with canonical property %S"
|
||||
name role (plist-get existing :name))))
|
||||
(puthash name property table))
|
||||
|
||||
(defun ebox-style--ensure-property-table ()
|
||||
"Return the canonical style property registry table."
|
||||
(or ebox-style--property-table
|
||||
(let ((table (make-hash-table :test 'eq)))
|
||||
(dolist (property ebox-style--property-definitions)
|
||||
(ebox-style--register-property-name
|
||||
table (plist-get property :name) property "name")
|
||||
(ebox-style--register-property-name
|
||||
table (plist-get property :id) property "ID")
|
||||
(dolist (alias (plist-get property :aliases))
|
||||
(ebox-style--register-property-name table alias property "alias")))
|
||||
(setq ebox-style--property-table table))))
|
||||
(defun ebox-style--property (name)
|
||||
"Return registry-owned style metadata for internal NAME lookup."
|
||||
(gethash name ebox-style--property-index))
|
||||
|
||||
(defun ebox-style-property (name)
|
||||
"Return registered style metadata for canonical property or alias NAME."
|
||||
(gethash name (ebox-style--ensure-property-table)))
|
||||
"Return detached style metadata for canonical property or alias NAME."
|
||||
(ecss-schema-set-property-metadata ebox-style-schemas name))
|
||||
|
||||
(defun ebox-style-canonical-name (name)
|
||||
"Return canonical CSS-like longhand property name for NAME."
|
||||
(when-let* ((property (ebox-style-property name)))
|
||||
(when-let* ((property (ebox-style--property name)))
|
||||
(plist-get property :name)))
|
||||
|
||||
(defun ebox-style-property-accepted-p (tag name)
|
||||
"Return non-nil when author form TAG accepts property NAME."
|
||||
(when-let* ((property (ebox-style-property name)))
|
||||
(when-let* ((property (ebox-style--property name)))
|
||||
(ebox-style--form-property-allowed-p tag property)))
|
||||
|
||||
(defun ebox-style-schema-id (name)
|
||||
"Return namespaced ECSS schema id for Ebox property NAME."
|
||||
(when-let* ((property (ebox-style-property name)))
|
||||
(when-let* ((property (ebox-style--property name)))
|
||||
(plist-get property :id)))
|
||||
|
||||
(defun ebox-style--put (plist key value)
|
||||
@ -694,6 +677,50 @@ generated wrappers that already contain exact line breaks."
|
||||
(setq options (plist-put options :shorthand shorthand)))
|
||||
options))
|
||||
|
||||
(defun ebox-style--schema-impacts (property)
|
||||
"Return immutable impact classes for PROPERTY metadata."
|
||||
(or (plist-get property :impacts)
|
||||
(and (eq (plist-get property :group) 'typography)
|
||||
'(geometry paint))
|
||||
(list (plist-get property :dirty-kind))))
|
||||
|
||||
(defun ebox-style--schema-projections (property)
|
||||
"Return immutable projection owners for PROPERTY metadata."
|
||||
(or (plist-get property :projections)
|
||||
(delq nil (list (plist-get property :signature)))))
|
||||
|
||||
(defun ebox-style--schema-definition (property)
|
||||
"Return one ECSS package definition for Ebox PROPERTY metadata."
|
||||
(append
|
||||
(list :id (plist-get property :id)
|
||||
:aliases
|
||||
(cons (plist-get property :name)
|
||||
(copy-sequence (plist-get property :aliases)))
|
||||
:impacts (ebox-style--schema-impacts property)
|
||||
:projections (ebox-style--schema-projections property)
|
||||
:metadata property)
|
||||
(ebox-style--schema-options property)))
|
||||
|
||||
(defconst ebox-style--schema-package
|
||||
(ecss-schema-package-create
|
||||
'ebox
|
||||
(mapcar #'ebox-style--schema-definition
|
||||
ebox-style--property-definitions))
|
||||
"Immutable Ebox property package registered with ECSS.")
|
||||
|
||||
(defconst ebox-style--property-index
|
||||
(let ((table (make-hash-table :test #'eq)))
|
||||
(dolist (property ebox-style--property-definitions table)
|
||||
(puthash (plist-get property :id) property table)
|
||||
(puthash (plist-get property :name) property table)
|
||||
(dolist (alias (plist-get property :aliases))
|
||||
(puthash alias property table))))
|
||||
"Ebox-owned immutable lookup index over property definitions.")
|
||||
|
||||
(defconst ebox-style-schemas
|
||||
(ecss-schema-set-compose ebox-style--schema-package)
|
||||
"Immutable Ebox-only surface schema set.")
|
||||
|
||||
(defvar ebox-style--closed-computed-cache (make-hash-table :test #'equal)
|
||||
"Bounded cache of selector-free, dependency-free computed styles.")
|
||||
|
||||
@ -711,26 +738,6 @@ generated wrappers that already contain exact line breaks."
|
||||
(cl-loop for (_property value) on declarations by #'cddr
|
||||
never (tp-computed-p value)))
|
||||
|
||||
(defun ebox-style-register-properties ()
|
||||
"Register the complete namespaced Ebox property domain with ECSS."
|
||||
(dolist (property ebox-style--property-definitions)
|
||||
(apply #'ecss-schema-set-define
|
||||
ebox-style-schemas (plist-get property :id)
|
||||
(ebox-style--schema-options property)))
|
||||
(clrhash ebox-style--closed-computed-cache)
|
||||
(clrhash ebox-style--closed-inheritance-cache)
|
||||
(setq ebox-style--registered-schemas ebox-style-schemas))
|
||||
|
||||
(defun ebox-style--ensure-properties ()
|
||||
"Ensure Ebox property schemas exist in the isolated ECSS schema set."
|
||||
(unless (eq ebox-style--registered-schemas ebox-style-schemas)
|
||||
;; Only the first call for a new schema-set needs to inspect/register the
|
||||
;; domain. Avoid the detached `ecss-schema-set-property' accessor on the
|
||||
;; hot `ebox-create' path; it recursively copies schema metadata.
|
||||
(if (ecss-schema-set-property ebox-style-schemas 'ebox/color)
|
||||
(setq ebox-style--registered-schemas ebox-style-schemas)
|
||||
(ebox-style-register-properties))))
|
||||
|
||||
(defvar ebox-style-stylesheet (ebox-style--create-stylesheet)
|
||||
"Isolated ECSS stylesheet containing Ebox layout and paint rules.")
|
||||
|
||||
@ -782,6 +789,12 @@ author declaration that references the slot."
|
||||
right (cddr right)))
|
||||
(and equal-p (null left) (null right))))
|
||||
|
||||
(defconst ebox-style--inherited-properties
|
||||
(cl-loop for property in ebox-style--property-definitions
|
||||
when (plist-get property :inherits)
|
||||
collect (plist-get property :id))
|
||||
"Canonical ECSS properties inherited according to the shared schema.")
|
||||
|
||||
(defun ebox-style--inherited-declarations-changed-p (old new)
|
||||
"Return non-nil when OLD and NEW change descendant style inputs.
|
||||
The inherited property domain comes only from the canonical schema. ECSS
|
||||
@ -813,7 +826,7 @@ consume them even though Ebox does not interpret their names."
|
||||
"Reject duplicate canonical properties in author PLIST."
|
||||
(let ((seen (make-hash-table :test 'eq)))
|
||||
(cl-loop for (name _value) on plist by #'cddr
|
||||
for property = (ebox-style-property name)
|
||||
for property = (ebox-style--property name)
|
||||
for id = (and property (plist-get property :id))
|
||||
when id
|
||||
do (when-let* ((previous (gethash id seen)))
|
||||
@ -825,7 +838,7 @@ consume them even though Ebox does not interpret their names."
|
||||
|
||||
(defun ebox-style--author-property-output-ids (name value)
|
||||
"Return canonical output IDs produced by author NAME and VALUE."
|
||||
(when-let* ((property (ebox-style-property name)))
|
||||
(when-let* ((property (ebox-style--property name)))
|
||||
(if-let* ((shorthand-name (plist-get property :shorthand))
|
||||
(expander (ebox-style--shorthand shorthand-name)))
|
||||
(cl-loop for (id _output) on (funcall expander value) by #'cddr
|
||||
@ -836,7 +849,7 @@ consume them even though Ebox does not interpret their names."
|
||||
"Reject author properties whose canonical outputs overlap in PLIST."
|
||||
(let (entries)
|
||||
(cl-loop for (name value) on plist by #'cddr
|
||||
for property = (ebox-style-property name)
|
||||
for property = (ebox-style--property name)
|
||||
when property
|
||||
do (push (list :name name :value value :property property)
|
||||
entries))
|
||||
@ -899,17 +912,25 @@ not affect declaration compilation and are intentionally excluded."
|
||||
(defun ebox-style--validate-declaration-values (declarations)
|
||||
"Return DECLARATIONS after static Ebox-owned value validation."
|
||||
(cl-loop for (property value) on declarations by #'cddr
|
||||
for schema = (ebox-style-property property)
|
||||
for schema = (ebox-style--property property)
|
||||
for validator = (and schema (plist-get schema :validator))
|
||||
when (ebox-style--static-invalid-declaration-p validator value)
|
||||
do (error "ebox: invalid value for %S: %S"
|
||||
(plist-get schema :name) value))
|
||||
declarations)
|
||||
|
||||
(defun ebox-style--merge-ordered-author-declarations (declarations)
|
||||
"Expand ordered Ebox DECLARATIONS as distinct normalization groups.
|
||||
Ebox currently preserves CSS shorthand/longhand declaration order, while its
|
||||
own author validator rejects exact aliases and explicitly exclusive outputs."
|
||||
(apply #'ecss-merge-declarations
|
||||
ebox-style-schemas
|
||||
(cl-loop for (property value) on declarations by #'cddr
|
||||
collect (list property value))))
|
||||
|
||||
(defun ebox-style-compile-declarations (plist &optional strict)
|
||||
"Compile Ebox PLIST aliases to ECSS schema declarations.
|
||||
When STRICT is non-nil, reject properties outside the Ebox style domain."
|
||||
(ebox-style--ensure-properties)
|
||||
(unless (ebox-style--valid-plist-p plist)
|
||||
(user-error "Ebox style declarations must be an even property list"))
|
||||
(ebox-style--validate-no-duplicate-properties plist)
|
||||
@ -931,7 +952,7 @@ When STRICT is non-nil, reject properties outside the Ebox style domain."
|
||||
property)))
|
||||
(let ((compiled
|
||||
(ebox-style--validate-declaration-values
|
||||
(ecss-merge-declarations ebox-style-schemas canonical))))
|
||||
(ebox-style--merge-ordered-author-declarations canonical))))
|
||||
(when (>= (hash-table-count ebox-style--declaration-cache)
|
||||
ebox-style--declaration-cache-max-entries)
|
||||
(clrhash ebox-style--declaration-cache))
|
||||
@ -963,7 +984,7 @@ When STRICT is non-nil, reject properties outside the Ebox style domain."
|
||||
declarations))
|
||||
(let ((seen (make-hash-table :test #'eq)))
|
||||
(cl-loop for (id _value) on declarations by #'cddr
|
||||
for property = (ebox-style-property id)
|
||||
for property = (ebox-style--property id)
|
||||
unless (and property (eq id (plist-get property :id)))
|
||||
do (error "Ebox declaration is not a canonical property ID: %S"
|
||||
id)
|
||||
@ -984,7 +1005,7 @@ When STRICT is non-nil, reject properties outside the Ebox style domain."
|
||||
(defun ebox-style-declaration-properties (declarations predicate)
|
||||
"Return canonical author properties in DECLARATIONS matching PREDICATE."
|
||||
(cl-loop for (id value) on declarations by #'cddr
|
||||
for property = (or (ebox-style-property id)
|
||||
for property = (or (ebox-style--property id)
|
||||
(error "Unknown canonical Ebox property: %S" id))
|
||||
when (funcall predicate property)
|
||||
append (list (plist-get property :name) value)))
|
||||
@ -993,20 +1014,18 @@ When STRICT is non-nil, reject properties outside the Ebox style domain."
|
||||
(declarations predicate)
|
||||
"Return non-nil when DECLARATIONS include facts outside PREDICATE."
|
||||
(cl-loop for (id _value) on declarations by #'cddr
|
||||
for property = (or (ebox-style-property id)
|
||||
for property = (or (ebox-style--property id)
|
||||
(error "Unknown canonical Ebox property: %S" id))
|
||||
thereis (not (funcall predicate property))))
|
||||
|
||||
(defun ebox-style-merge-declarations (base overrides)
|
||||
"Merge canonical BASE and OVERRIDES through ECSS property schemas."
|
||||
(ebox-style--ensure-properties)
|
||||
(ecss-merge-declarations ebox-style-schemas base overrides))
|
||||
|
||||
(cl-defun ebox-style-add-rule
|
||||
(selector declarations &key (origin 'author) layer scope)
|
||||
"Add SELECTOR rule with Ebox DECLARATIONS to the isolated stylesheet.
|
||||
ORIGIN, LAYER, and SCOPE use ECSS cascade semantics."
|
||||
(ebox-style--ensure-properties)
|
||||
(ecss-stylesheet-add-rule
|
||||
ebox-style-stylesheet ebox-style-schemas (ebox-style--selector selector)
|
||||
(ebox-style-compile-declarations declarations t)
|
||||
@ -1015,7 +1034,6 @@ ORIGIN, LAYER, and SCOPE use ECSS cascade semantics."
|
||||
|
||||
(defun ebox-style-compute-subject (subject declarations &optional parent-style)
|
||||
"Compute SUBJECT style from DECLARATIONS and optional PARENT-STYLE via ECSS."
|
||||
(ebox-style--ensure-properties)
|
||||
(let* ((closed-p
|
||||
(and (not (ebox-style-cascade-active-p))
|
||||
(ebox-style--closed-declarations-p declarations)))
|
||||
@ -1085,12 +1103,6 @@ winner is required so parent inherited geometry/paint cannot be stale."
|
||||
(plist-member old 'ebox/color)
|
||||
(plist-member new 'ebox/color)))
|
||||
|
||||
(defconst ebox-style--inherited-properties
|
||||
(cl-loop for property in ebox-style--property-definitions
|
||||
when (plist-get property :inherits)
|
||||
collect (plist-get property :id))
|
||||
"Canonical ECSS properties inherited according to the shared schema.")
|
||||
|
||||
(defun ebox-style--inherited-style-signature (style)
|
||||
"Return the inherited-value fingerprint of computed STYLE, or nil.
|
||||
The fingerprint deliberately contains only values that can flow from a
|
||||
@ -1326,7 +1338,7 @@ same canonical property id, so callers never inspect backend plist fields."
|
||||
(defun ebox-style--remove-style-properties (plist)
|
||||
"Return PLIST without Ebox style or ECSS custom properties."
|
||||
(cl-loop for (property value) on plist by #'cddr
|
||||
unless (or (ebox-style-property property)
|
||||
unless (or (ebox-style--property property)
|
||||
(ebox-style--custom-property-p property))
|
||||
append (list property value)))
|
||||
|
||||
@ -1636,7 +1648,7 @@ until their private encoding is removed."
|
||||
|
||||
(defun ebox-style-dirty-kind (name)
|
||||
"Return dirty kind for canonical property or alias NAME."
|
||||
(when-let* ((property (ebox-style-property name)))
|
||||
(when-let* ((property (ebox-style--property name)))
|
||||
(plist-get property :dirty-kind)))
|
||||
|
||||
(defun ebox-style-signature (computed-style groups)
|
||||
@ -1645,7 +1657,7 @@ until their private encoding is removed."
|
||||
(while computed-style
|
||||
(let* ((property-name (pop computed-style))
|
||||
(value (pop computed-style))
|
||||
(property (ebox-style-property property-name))
|
||||
(property (ebox-style--property property-name))
|
||||
(group (and property (plist-get property :signature))))
|
||||
(when (and property
|
||||
(or (null groups)
|
||||
@ -1662,8 +1674,6 @@ until their private encoding is removed."
|
||||
(list (car entry) (cdr entry)))
|
||||
entries))))
|
||||
|
||||
(ebox-style-register-properties)
|
||||
|
||||
(defconst ebox-style-ebox-property-rules
|
||||
'((:padding :expand (:padding-top :padding-right
|
||||
:padding-bottom :padding-left)
|
||||
|
||||
2
ebox.el
2
ebox.el
@ -3992,7 +3992,7 @@ cannot be changed through the unpublished batch root."
|
||||
(ebox-get box target-key))
|
||||
value))
|
||||
(and declarations-changed
|
||||
(ebox-style-property key)))
|
||||
(ebox-style--property key)))
|
||||
do (if (eq target-key :content)
|
||||
(if-let* ((text
|
||||
(ebox--region-update-direct-text-node box)))
|
||||
|
||||
@ -508,17 +508,24 @@
|
||||
:removed-node-ids
|
||||
(ebox--runtime-node-ids removed-node))))))))
|
||||
|
||||
(ert-deftest ebox-style-schema-registration-is-not-per-node-copy ()
|
||||
"Repeated node construction must not copy the whole ECSS schema domain."
|
||||
(let ((calls 0)
|
||||
(original (symbol-function 'ecss-schema-set-property)))
|
||||
(cl-letf (((symbol-function 'ecss-schema-set-property)
|
||||
(ert-deftest ebox-style-schema-composition-is-not-per-node-work ()
|
||||
"Repeated node construction must not rebuild the immutable schema domain."
|
||||
(let ((package-calls 0)
|
||||
(compose-calls 0)
|
||||
(original-package (symbol-function 'ecss-schema-package-create))
|
||||
(original-compose (symbol-function 'ecss-schema-set-compose)))
|
||||
(cl-letf (((symbol-function 'ecss-schema-package-create)
|
||||
(lambda (&rest arguments)
|
||||
(cl-incf calls)
|
||||
(apply original arguments))))
|
||||
(cl-incf package-calls)
|
||||
(apply original-package arguments)))
|
||||
((symbol-function 'ecss-schema-set-compose)
|
||||
(lambda (&rest arguments)
|
||||
(cl-incf compose-calls)
|
||||
(apply original-compose arguments))))
|
||||
(dotimes (_ 24)
|
||||
(ebox-test-box :content "schema-hot-path" :color "#111111")))
|
||||
(should (= calls 0))))
|
||||
(should (= package-calls 0))
|
||||
(should (= compose-calls 0))))
|
||||
|
||||
(ert-deftest ebox-style-declaration-compilation-is-memoized ()
|
||||
"Repeated equivalent style declarations compile through ECSS once."
|
||||
|
||||
@ -454,6 +454,43 @@
|
||||
:font-height)
|
||||
literal))))
|
||||
|
||||
(ert-deftest ebox-ecss-computed-fact-retains-owner-impact-and-provenance ()
|
||||
"The Ebox package schema should remain attached to one computed fact."
|
||||
(let* ((style
|
||||
(ecss-compute-style
|
||||
ebox-style-schemas (ecss-subject-create :type "box")
|
||||
:declarations (ebox-style-compile-declarations '(:width (120)) t)
|
||||
:provenance t))
|
||||
(fact (ecss-computed-style-property-fact style 'ebox/width)))
|
||||
(should (eq 'ebox/width (ecss-computed-property-fact-property fact)))
|
||||
(should (eq 'ebox (ecss-computed-property-fact-owner fact)))
|
||||
(should (equal '(geometry)
|
||||
(ecss-computed-property-fact-impacts fact)))
|
||||
(should (equal '(layout)
|
||||
(ecss-computed-property-fact-projections fact)))
|
||||
(should (equal (ecss-computed-property-fact-value fact)
|
||||
(ecss-computed-style-value style 'ebox/width)))
|
||||
(should (eq 'declaration
|
||||
(plist-get (ecss-computed-property-fact-provenance fact)
|
||||
:source)))))
|
||||
|
||||
(ert-deftest ebox-ecss-multi-impact-fact-shares-one-computed-value ()
|
||||
"Typography geometry and paint consumers should share one value identity."
|
||||
(let* ((style
|
||||
(ecss-compute-style
|
||||
ebox-style-schemas (ecss-subject-create :type "box")
|
||||
:declarations
|
||||
(ebox-style-compile-declarations '(:font-weight bold) t)))
|
||||
(fact
|
||||
(ecss-computed-style-property-fact style 'ebox/font-weight))
|
||||
(geometry-value (ecss-computed-property-fact-value fact))
|
||||
(paint-value (ecss-computed-property-fact-value fact)))
|
||||
(should (equal '(geometry paint)
|
||||
(ecss-computed-property-fact-impacts fact)))
|
||||
(should (eq geometry-value paint-value))
|
||||
(should (equal geometry-value
|
||||
(ecss-computed-style-value style 'ebox/font-weight)))))
|
||||
|
||||
(ert-deftest ebox-style-context-reads-each-ecss-snapshot-once ()
|
||||
"Style projection should not repeatedly copy one computed ECSS snapshot."
|
||||
(let* ((style
|
||||
@ -2158,7 +2195,7 @@
|
||||
"A color-only change should produce paint dirty, not geometry dirty."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((box (ebox-test-box :content "Paint" :width 80
|
||||
:border t :border-color "red"))
|
||||
:border '(1 solid "red")))
|
||||
(region-id (car (ebox-region-ids box))))
|
||||
(ebox-test--with-rendered-buffer box
|
||||
(let ((old (ebox--current-layout-snapshots (current-buffer))))
|
||||
@ -2225,7 +2262,7 @@
|
||||
"Paint-only dirty entries should produce paint-patch ops."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((box (ebox-test-box :content "Paint" :width 80
|
||||
:border t :border-color "red"))
|
||||
:border '(1 solid "red")))
|
||||
(region-id (car (ebox-region-ids box))))
|
||||
(ebox-test--with-rendered-buffer box
|
||||
(let ((full-rerenders
|
||||
@ -4255,7 +4292,7 @@
|
||||
"Update reports should show dirty and patch-set summaries."
|
||||
(ebox-test--reset-runtime-state)
|
||||
(let* ((box (ebox-test-box :content "Report" :width 80
|
||||
:border t :border-color "red"))
|
||||
:border '(1 solid "red")))
|
||||
(region-id (car (ebox-region-ids box))))
|
||||
(ebox-test--with-rendered-buffer box
|
||||
(ebox-test--region-update region-id :border-color "blue")
|
||||
@ -4569,14 +4606,18 @@
|
||||
(box :id left :content "Left" :width (120)
|
||||
:padding (0 (8)) :border "#D97757")
|
||||
(box :content "Body" :width (120)
|
||||
:padding (0 (8)) :border "#D97757"
|
||||
:border-top none))
|
||||
:padding (0 (8))
|
||||
:border-right "#D97757"
|
||||
:border-bottom "#D97757"
|
||||
:border-left "#D97757"))
|
||||
(column
|
||||
(box :id right :content "Right" :width (120)
|
||||
:padding (0 (8)) :border "#5E7F6A")
|
||||
(box :content "Body" :width (120)
|
||||
:padding (0 (8)) :border "#5E7F6A"
|
||||
:border-top none)))))
|
||||
:padding (0 (8))
|
||||
:border-right "#5E7F6A"
|
||||
:border-bottom "#5E7F6A"
|
||||
:border-left "#5E7F6A")))))
|
||||
(left-id (plist-get
|
||||
(car (ebox-selector-query-all layout "#left"))
|
||||
:region-id)))
|
||||
@ -4606,18 +4647,26 @@
|
||||
:padding (0 (12)) :border "#D97757" :bgcolor "#FFF8F2"
|
||||
(column
|
||||
(box :id left :content "Left" :width (120)
|
||||
:padding (0 (8)) :border "#D97757"
|
||||
:border-bottom none)
|
||||
:padding (0 (8))
|
||||
:border-top "#D97757"
|
||||
:border-right "#D97757"
|
||||
:border-left "#D97757")
|
||||
(box :content "Body" :width (120)
|
||||
:padding (0 (8)) :border "#D97757"
|
||||
:border-top none))
|
||||
:padding (0 (8))
|
||||
:border-right "#D97757"
|
||||
:border-bottom "#D97757"
|
||||
:border-left "#D97757"))
|
||||
(column
|
||||
(box :content "Right" :width (120)
|
||||
:padding (0 (8)) :border "#5E7F6A"
|
||||
:border-bottom none)
|
||||
:padding (0 (8))
|
||||
:border-top "#5E7F6A"
|
||||
:border-right "#5E7F6A"
|
||||
:border-left "#5E7F6A")
|
||||
(box :content "Body" :width (120)
|
||||
:padding (0 (8)) :border "#5E7F6A"
|
||||
:border-top none)))))
|
||||
:padding (0 (8))
|
||||
:border-right "#5E7F6A"
|
||||
:border-bottom "#5E7F6A"
|
||||
:border-left "#5E7F6A")))))
|
||||
(left-id (plist-get
|
||||
(car (ebox-selector-query-all layout "#left"))
|
||||
:region-id)))
|
||||
@ -4650,18 +4699,26 @@
|
||||
:padding (0 (12)) :border "#D97757" :bgcolor "#FFF8F2"
|
||||
(column
|
||||
(box :id left :content "Left" :width (120)
|
||||
:padding (0 (8)) :border "#D97757"
|
||||
:border-bottom none)
|
||||
:padding (0 (8))
|
||||
:border-top "#D97757"
|
||||
:border-right "#D97757"
|
||||
:border-left "#D97757")
|
||||
(box :content "Body" :width (120)
|
||||
:padding (0 (8)) :border "#D97757"
|
||||
:border-top none))
|
||||
:padding (0 (8))
|
||||
:border-right "#D97757"
|
||||
:border-bottom "#D97757"
|
||||
:border-left "#D97757"))
|
||||
(column
|
||||
(box :content "Right" :width (120)
|
||||
:padding (0 (8)) :border "#5E7F6A"
|
||||
:border-bottom none)
|
||||
:padding (0 (8))
|
||||
:border-top "#5E7F6A"
|
||||
:border-right "#5E7F6A"
|
||||
:border-left "#5E7F6A")
|
||||
(box :content "Body" :width (120)
|
||||
:padding (0 (8)) :border "#5E7F6A"
|
||||
:border-top none)))))
|
||||
:padding (0 (8))
|
||||
:border-right "#5E7F6A"
|
||||
:border-bottom "#5E7F6A"
|
||||
:border-left "#5E7F6A")))))
|
||||
(left-id (plist-get
|
||||
(car (ebox-selector-query-all layout "#left"))
|
||||
:region-id)))
|
||||
@ -5335,11 +5392,7 @@
|
||||
:height 4 :min-height 3 :max-height 6
|
||||
:padding-top 1 :padding-bottom 1)
|
||||
(ebox-test--region-update region-id
|
||||
:margin (list 0 '(8) 0 '(8))
|
||||
:margin-inline '((8) (8))
|
||||
:margin-block '(0 0)
|
||||
:margin-top 0 :margin-right '(8)
|
||||
:margin-bottom 0 :margin-left '(8))
|
||||
:margin-inline '((8) (8)))
|
||||
(let ((report (ebox-test--buffer-update-report)))
|
||||
(should (eq (plist-get report :strategy) 'span-patch))
|
||||
(should (eq (plist-get report :owner-type) 'box))
|
||||
@ -5755,11 +5808,7 @@
|
||||
(region-id (plist-get handle :region-id))
|
||||
(node-id (plist-get handle :node-id)))
|
||||
(ebox-test--region-update region-id
|
||||
:margin (list 0 '(8) 0 '(8))
|
||||
:margin-inline '((8) (8))
|
||||
:margin-block '(0 0)
|
||||
:margin-top 0 :margin-right '(8)
|
||||
:margin-bottom 0 :margin-left '(8))
|
||||
:margin-inline '((8) (8)))
|
||||
(let* ((report (ebox-test--buffer-update-report))
|
||||
(snapshot (ebox--ensure-layout-snapshot-spans
|
||||
(current-buffer) node-id))
|
||||
@ -7109,8 +7158,11 @@
|
||||
`(flex :id "flex-window-wrapper" :width (240)
|
||||
:flex-flow (row wrap)
|
||||
:row-gap 1 :column-gap 0
|
||||
:padding (0 (8)) :border "#D97757"
|
||||
:border-top none :bgcolor "#FFF8F2"
|
||||
:padding (0 (8))
|
||||
:border-right "#D97757"
|
||||
:border-bottom "#D97757"
|
||||
:border-left "#D97757"
|
||||
:bgcolor "#FFF8F2"
|
||||
,@(cl-loop for idx below 8
|
||||
collect
|
||||
`(box :content ,(format "wrapper %02d" idx)
|
||||
@ -7156,9 +7208,8 @@
|
||||
(let* ((box (ebox-test-box :width 120
|
||||
:padding-left 8
|
||||
:padding-right 8
|
||||
:border-left 1
|
||||
:border-right 1
|
||||
:border-color "#D97757"
|
||||
:border-left '(1 solid "#D97757")
|
||||
:border-right '(1 solid "#D97757")
|
||||
:bgcolor "#FFF8F2"))
|
||||
(source (propertize "child" 'ebox-content 77
|
||||
'ebox-content-owner 77
|
||||
@ -7244,8 +7295,11 @@
|
||||
`(flex :id "flex-prefix-wrapper" :width (240)
|
||||
:flex-flow (row wrap)
|
||||
:row-gap 1 :column-gap 0
|
||||
:padding (0 (8)) :border "#D97757"
|
||||
:border-top none :bgcolor "#FFF8F2"
|
||||
:padding (0 (8))
|
||||
:border-right "#D97757"
|
||||
:border-bottom "#D97757"
|
||||
:border-left "#D97757"
|
||||
:bgcolor "#FFF8F2"
|
||||
,@(cl-loop for idx below 8
|
||||
collect
|
||||
`(box :content ,(format "extend %02d" idx)
|
||||
@ -7277,8 +7331,11 @@
|
||||
(ebox-test-build
|
||||
`(flex :width (240) :flex-flow (row wrap)
|
||||
:row-gap 1 :column-gap 0
|
||||
:padding (0 (8)) :border "#D97757"
|
||||
:border-top none :bgcolor "#FFF8F2"
|
||||
:padding (0 (8))
|
||||
:border-right "#D97757"
|
||||
:border-bottom "#D97757"
|
||||
:border-left "#D97757"
|
||||
:bgcolor "#FFF8F2"
|
||||
,@(cl-loop for idx below 16
|
||||
collect
|
||||
`(box :content ,(format "piece %02d" idx)
|
||||
@ -7308,8 +7365,9 @@
|
||||
:row-gap 1 :column-gap 0
|
||||
:padding (0 (8) 2 (8))
|
||||
:margin-bottom 2
|
||||
:border "#D97757"
|
||||
:border-top none
|
||||
:border-right "#D97757"
|
||||
:border-bottom "#D97757"
|
||||
:border-left "#D97757"
|
||||
:bgcolor "#FFF8F2"
|
||||
,@(cl-loop for idx below 2
|
||||
collect
|
||||
@ -7353,8 +7411,9 @@
|
||||
:flex-flow (row wrap)
|
||||
:row-gap 1 :column-gap 0
|
||||
:padding (0 (8))
|
||||
:border "#D97757"
|
||||
:border-top none
|
||||
:border-right "#D97757"
|
||||
:border-bottom "#D97757"
|
||||
:border-left "#D97757"
|
||||
:bgcolor "#FFF8F2"
|
||||
,@(cl-loop for idx below 4
|
||||
collect
|
||||
@ -7390,8 +7449,9 @@
|
||||
:flex-flow (row wrap)
|
||||
:row-gap 1 :column-gap 0
|
||||
:padding (0 (8))
|
||||
:border "#D97757"
|
||||
:border-top none
|
||||
:border-right "#D97757"
|
||||
:border-bottom "#D97757"
|
||||
:border-left "#D97757"
|
||||
,@(cl-loop for idx below 4
|
||||
collect
|
||||
`(box :content ,(format "atomic %02d" idx)
|
||||
@ -9251,8 +9311,10 @@
|
||||
(flex :id "signature-flex" :width (viewport)
|
||||
:flex-flow (row wrap)
|
||||
:row-gap 1 :column-gap (8)
|
||||
:padding (0 (12)) :border "#D97757"
|
||||
:border-top none
|
||||
:padding (0 (12))
|
||||
:border-right "#D97757"
|
||||
:border-bottom "#D97757"
|
||||
:border-left "#D97757"
|
||||
,@(cl-loop for idx below 24
|
||||
collect
|
||||
`(box :content ,(format "signature %02d" idx)
|
||||
@ -12674,8 +12736,8 @@ explicit visible-window handoff may admit its full-content publication."
|
||||
"A hidden box renders blank with identical text, lengths, and metadata."
|
||||
(let* ((make-box
|
||||
(lambda (visibility)
|
||||
(ebox-test-box :content "Hello" :width (list 90) :border t
|
||||
:border-color "#AA3311" :bgcolor "#223344"
|
||||
(ebox-test-box :content "Hello" :width (list 90)
|
||||
:border '(1 solid "#AA3311") :bgcolor "#223344"
|
||||
:color "#FFFFFF" :padding '(0 1)
|
||||
:visibility visibility)))
|
||||
(visible (ebox-render (funcall make-box 'visible)))
|
||||
|
||||
@ -95,13 +95,49 @@
|
||||
'(:background-color "red" :bgcolor "blue") t)
|
||||
:type 'error))
|
||||
|
||||
(ert-deftest ebox-style-preserves-ordered-shorthand-longhand-normalization ()
|
||||
"Ebox author pairs remain ordered normalization groups before cutover."
|
||||
(let ((border-then-top
|
||||
(ebox-style-compile-declarations
|
||||
'(:border "red" :border-top none) t))
|
||||
(top-then-border
|
||||
(ebox-style-compile-declarations
|
||||
'(:border-top none :border "red") t)))
|
||||
(should (eq 'none
|
||||
(plist-get border-then-top 'ebox/border-top-style)))
|
||||
(should (equal "red"
|
||||
(plist-get border-then-top 'ebox/border-right-color)))
|
||||
(should (eq 'solid
|
||||
(plist-get top-then-border 'ebox/border-top-style)))
|
||||
(should (equal "red"
|
||||
(plist-get top-then-border 'ebox/border-top-color)))))
|
||||
|
||||
(ert-deftest ebox-style-property-registry-rejects-alias-collisions ()
|
||||
"An alias should never shadow another canonical property."
|
||||
(let ((ebox-style--property-table nil)
|
||||
(ebox-style--property-definitions
|
||||
'((:name :first :id ebox/first :aliases (:second))
|
||||
(:name :second :id ebox/second))))
|
||||
(should-error (ebox-style--ensure-property-table) :type 'error)))
|
||||
(should-error
|
||||
(ecss-schema-package-create
|
||||
'ebox
|
||||
(mapcar
|
||||
#'ebox-style--schema-definition
|
||||
'((:name :first :id ebox/first :aliases (:second)
|
||||
:group paint :dirty-kind paint :signature paint)
|
||||
(:name :second :id ebox/second
|
||||
:group paint :dirty-kind paint :signature paint))))
|
||||
:type 'ecss-invalid-property-schema)
|
||||
(should (eq 'ebox/background-color
|
||||
(ecss-schema-set-canonical-id ebox-style-schemas :bgcolor)))
|
||||
(should (eq :background-color
|
||||
(plist-get
|
||||
(ecss-schema-set-property-metadata
|
||||
ebox-style-schemas :bgcolor)
|
||||
:name)))
|
||||
(let ((metadata (ebox-style-property :bgcolor)))
|
||||
(setf (plist-get metadata :name) :damaged)
|
||||
(setcar (plist-get metadata :contexts) 'damaged)
|
||||
(should (eq :background-color
|
||||
(plist-get (ebox-style-property :bgcolor) :name)))
|
||||
(should (equal '(box)
|
||||
(plist-get (ebox-style-property :bgcolor) :contexts)))))
|
||||
|
||||
(ert-deftest ebox-style-border-present-is-a-typed-shorthand-not-an-alias ()
|
||||
"The concise border boolean should expand to canonical width and style."
|
||||
|
||||
@ -428,7 +428,9 @@
|
||||
:padding (0 (10)) :border "#5E7F6A"
|
||||
:text-align center "wrap")
|
||||
(flex :id "body" :max-width (293) :padding (0 (8))
|
||||
:border "#5E7F6A" :border-top none
|
||||
:border-right "#5E7F6A"
|
||||
:border-bottom "#5E7F6A"
|
||||
:border-left "#5E7F6A"
|
||||
:flex-wrap wrap :column-gap (8)
|
||||
(box :flex-shrink 0 :width (118) "A 118")
|
||||
(box :flex-shrink 0 :width (118) "B 118")
|
||||
@ -459,14 +461,19 @@
|
||||
(layout
|
||||
(ebox-build
|
||||
'(flex :flex-flow (row wrap) :row-gap 1 :column-gap (12)
|
||||
:padding (0 (12)) :border "#6F6A95" :border-top none
|
||||
:padding (0 (12))
|
||||
:border-right "#6F6A95"
|
||||
:border-bottom "#6F6A95"
|
||||
:border-left "#6F6A95"
|
||||
:bgcolor "#FBF9FF"
|
||||
(column
|
||||
(box :max-width (440)
|
||||
:padding (0 (10)) :border "#6F6A95"
|
||||
:text-align center ":flex-flow (row wrap)")
|
||||
(flex :max-width (440) :padding (0 (8))
|
||||
:border "#6F6A95" :border-top none
|
||||
:border-right "#6F6A95"
|
||||
:border-bottom "#6F6A95"
|
||||
:border-left "#6F6A95"
|
||||
:flex-flow (row wrap) :gap (1 (12))
|
||||
(box :width (170) "A")
|
||||
(box :width (170) "B")
|
||||
@ -476,7 +483,9 @@
|
||||
:padding (0 (10)) :border "#6F6A95"
|
||||
:text-align center ":flex-flow (column wrap)")
|
||||
(flex :max-width (440) :padding (0 (8))
|
||||
:border "#6F6A95" :border-top none
|
||||
:border-right "#6F6A95"
|
||||
:border-bottom "#6F6A95"
|
||||
:border-left "#6F6A95"
|
||||
:flex-flow (column wrap) :gap (1 (12))
|
||||
(box :width (140) "A")
|
||||
(box :width (140) "B")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user