diff --git a/docs/api.en.md b/docs/api.en.md index 83349df..0c8aad9 100644 --- a/docs/api.en.md +++ b/docs/api.en.md @@ -49,6 +49,7 @@ Do not pass both `:stylesheet` and `:rules`. `:rules` accepts the rule list retu - `(ecss-computed-style-custom-properties STYLE)` returns a deterministic custom-property plist. - `(ecss-computed-style-active-properties STYLE)` returns property IDs supplied by valid declarations or active inheritance, preserving explicit nil versus absence. An invalid winner falling back to the initial value is not presented as an active declaration. - `(ecss-computed-style-specified-properties STYLE)` returns longhand property IDs that have cascade winners, independent of whether provenance was requested. +- `(ecss-computed-style-copy-with-values STYLE VALUES &key active-properties specified-properties)` returns an immutable-style copy for a consumer that has already proved selector/cascade facts unchanged and only needs to replace computed values. - `(ecss-computed-style-provenance STYLE)` returns winner, origin, importance, layer, specificity, scope/source/declaration order, validity, and fallback facts. - `(ecss-computed-style-diagnostics STYLE)` returns deterministic diagnostics such as custom-property cycles. - `(ecss-computed-style-value STYLE PROPERTY &optional FALLBACK)` reads one value. diff --git a/docs/api.zh.md b/docs/api.zh.md index f9f431d..f33a45d 100644 --- a/docs/api.zh.md +++ b/docs/api.zh.md @@ -49,6 +49,7 @@ ORIGIN 是 `ua`、`user`、`author`、`animation` 或 `transition`。Inline decl - `(ecss-computed-style-custom-properties STYLE)` 返回 deterministic custom-property plist。 - `(ecss-computed-style-active-properties STYLE)` 返回由有效 declaration 或 active inheritance 实际提供的 property ids,保留 explicit nil 与 absence 的区别;invalid winner 回退到 initial 时不伪装成 active declaration。 - `(ecss-computed-style-specified-properties STYLE)` 返回存在 cascade winner 的长手 property ids,独立于是否请求 provenance。 +- `(ecss-computed-style-copy-with-values STYLE VALUES &key active-properties specified-properties)` 在 selector/cascade 事实已证明不变、只需替换 computed values 时返回新的 immutable-style copy。 - `(ecss-computed-style-provenance STYLE)` 返回 winner、origin、important、layer、specificity、scope/source/declaration order、valid/fallback 等事实。 - `(ecss-computed-style-diagnostics STYLE)` 返回 custom-property cycle 等 deterministic diagnostics。 - `(ecss-computed-style-value STYLE PROPERTY &optional FALLBACK)` 读取单值。 diff --git a/ecss-cascade.el b/ecss-cascade.el index 35bee7e..8845317 100644 --- a/ecss-cascade.el +++ b/ecss-cascade.el @@ -801,7 +801,7 @@ STACK detects cycles and DIAGNOSTICS records them." (defun ecss--resolve-property-value (value schema parent-style custom-tables diagnostics) - "Resolve VALUE for SCHEMA using PARENT-STYLE, CUSTOM, and DIAGNOSTICS." + "Resolve VALUE for SCHEMA using PARENT-STYLE, CUSTOM-TABLES, and DIAGNOSTICS." (cond ((and (ecss--wide-p value) (memq (ecss--wide-kind value) '(initial inherit unset))) (ecss--wide-default value schema parent-style)) @@ -826,10 +826,10 @@ STACK detects cycles and DIAGNOSTICS records them." (defun ecss--compute-candidate-value (candidate schema source parent-style custom-tables diagnostics) "Resolve CANDIDATE for SCHEMA and computation context. -SOURCE is the already resolved candidate value. PARENT-STYLE and CUSTOM +SOURCE is the already resolved candidate value. PARENT-STYLE and CUSTOM-TABLES supply inherited values, and DIAGNOSTICS records variable failures." - (let* ((property (ecss-property-schema-id schema)) - (resolved (ecss--resolve-property-value + (ignore candidate) + (let* ((resolved (ecss--resolve-property-value source schema parent-style custom-tables diagnostics))) (cons (if (eq (cdr resolved) 'declaration) (ecss--normalize-property-value schema (car resolved)) @@ -845,8 +845,8 @@ supply inherited values, and DIAGNOSTICS records variable failures." (defun ecss--resolve-property-candidates (schema candidates parent-style custom-tables subject resolver diagnostics) "Resolve SCHEMA from ordered CANDIDATES. -PARENT-STYLE and CUSTOM supply inherited values. SUBJECT and RESOLVER handle -explicit sources, and DIAGNOSTICS records variable failures." +PARENT-STYLE and CUSTOM-TABLES supply inherited values. SUBJECT and RESOLVER +handle explicit sources, and DIAGNOSTICS records variable failures." (let ((remaining candidates) winner resolved) (while (and remaining (null winner)) (let* ((candidate (pop remaining)) @@ -885,8 +885,9 @@ explicit sources, and DIAGNOSTICS records variable failures." (schemas table parent-style custom-tables subject resolver diagnostics provenance-p) "Compute SCHEMAS property values from TABLE and context. -PARENT-STYLE and CUSTOM-TABLES supply inherited values. SUBJECT and RESOLVER handle -explicit sources. DIAGNOSTICS records failures; PROVENANCE-P retains facts." +PARENT-STYLE and CUSTOM-TABLES supply inherited values. SUBJECT and RESOLVER +handle explicit sources. DIAGNOSTICS records failures; PROVENANCE-P retains +facts." (let (values active specified provenance) (dolist (property (ecss--schema-set-order schemas)) (let ((schema (ecss--schema-for schemas property))) @@ -980,6 +981,29 @@ non-nil, is the only function allowed to evaluate caller-owned value sources; (append (cadr raw-facts) property-facts)) :diagnostics (nreverse (car diagnostics)))))))) +;;;###autoload +(cl-defun ecss-computed-style-copy-with-values + (style values &key active-properties specified-properties) + "Return STYLE copied with replacement VALUES and optional metadata. + +This is a narrow immutable-style transformation for consumers that have +already proved selector/cascade facts unchanged and only need to propagate a +safe computed-value delta. Omitted ACTIVE-PROPERTIES and +SPECIFIED-PROPERTIES retain STYLE's metadata; all mutable values are copied at +the public boundary." + (unless (ecss-computed-style-p style) + (signal 'wrong-type-argument (list 'ecss-computed-style-p style))) + (let ((copy (copy-ecss-computed-style style))) + (setf (ecss--computed-style-values copy) + (ecss--copy-boundary-data values)) + (when active-properties + (setf (ecss--computed-style-active-properties copy) + (ecss--copy-boundary-data active-properties))) + (when specified-properties + (setf (ecss--computed-style-specified-properties copy) + (ecss--copy-boundary-data specified-properties))) + copy)) + (defun ecss--computed-style-check (style) "Return STYLE or signal when it is not a computed style." (unless (ecss-computed-style-p style) diff --git a/tests/ecss-cascade-tests.el b/tests/ecss-cascade-tests.el index 0a30ff8..1aef3f3 100644 --- a/tests/ecss-cascade-tests.el +++ b/tests/ecss-cascade-tests.el @@ -38,6 +38,27 @@ "Return PROPERTY from computed STYLE." (ecss-computed-style-value style property :absent)) +(ert-deftest ecss-cascade-test-computed-style-copy-with-values-is-defensive () + "Copy computed STYLE while replacing values and preserving metadata." + (let* ((schemas (ecss-cascade-test--schemas)) + (subject (ecss-cascade-test--subject)) + (style (ecss-compute-style schemas subject + :declarations '(app/color "red"))) + (copy (ecss-computed-style-copy-with-values + style '(app/color "blue") + :active-properties '(app/color) + :specified-properties '(app/color))) + (values (ecss-computed-style-values copy))) + (should-not (eq style copy)) + (should (equal "blue" (plist-get values 'app/color))) + (should (equal '(app/color) + (ecss-computed-style-active-properties copy))) + (should (equal '(app/color) + (ecss-computed-style-specified-properties copy))) + (setf (plist-get values 'app/color) "mutated") + (should (equal "blue" + (ecss-computed-style-value copy 'app/color))))) + (ert-deftest ecss-cascade-test-schema-registration-is-atomic () (let ((schemas (ecss-schema-set-create))) (ecss-schema-set-define schemas 'app/width :initial 4 :validator #'integerp)