🐛 Fix inner shadow covering the shape in the SVG renderer (#12141)

* 🐛 Fix inner shadow covering the shape in the SVG renderer

The svg filter components moved to the `*` syntax (#9448), but
filter-entry* still built their props as a raw JS object with
kebab-case keys. Raw JS props skip the kebab to camel conversion, so
the components read nil for filterId and filterIn, and no filter
primitive got its result or in2. With the chain broken, an inner
shadow was blended over the empty flood instead of the shape, hiding
the fill (and a board's children) under the shadow color. Exports
using these filters broke the same way. Pass the props in camelCase.

Closes #11967

AI-assisted-by: claude-opus-5-5

* ♻️ Build filter entry props with mf/props

Use mf/props instead of a raw JS object so the props keep the
kebab-case keys used across the codebase. The macro converts them to
camelCase at compile time, so there is no runtime cost, and the
comment about the manual conversion is no longer needed.

AI-assisted-by: claude-opus-5-5
This commit is contained in:
Eva Marco 2026-10-07 15:45:10 +02:00 committed by GitHub
parent f62f4024be
commit dcf0b197eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 52 additions and 3 deletions

View File

@ -116,9 +116,9 @@
:result filter-id}])
(mf/defc filter-entry* [{:keys [entry]}]
(let [props #js {:filter-id (:id entry)
:filter-in (:filter-in entry)
:params (:params entry)}]
(let [props (mf/props {:filter-id (:id entry)
:filter-in (:filter-in entry)
:params (:params entry)})]
(case (:type entry)
:drop-shadow [:> drop-shadow-filter* props]
:inner-shadow [:> inner-shadow-filter* props]

View File

@ -124,6 +124,7 @@
[frontend-tests.ui.routes-test]
[frontend-tests.ui.settings-password-schema-test]
[frontend-tests.ui.settings-shortcuts-test]
[frontend-tests.ui.shape-filters-test]
[frontend-tests.ui.shortcuts-labels-test]
[frontend-tests.ui.sidebar-scroll-test]
[frontend-tests.ui.stroke-menu-test]
@ -273,6 +274,7 @@
'frontend-tests.text-editor-paste-guard-test
'frontend-tests.ui.settings-password-schema-test
'frontend-tests.ui.settings-shortcuts-test
'frontend-tests.ui.shape-filters-test
'frontend-tests.ui.shortcuts-labels-test
'frontend-tests.ui.sidebar-scroll-test
'frontend-tests.ui.stroke-menu-test

View File

@ -0,0 +1,47 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns frontend-tests.ui.shape-filters-test
(:require
["react-dom/server" :as rds]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[app.main.ui.shapes.filters :as filters]
[cljs.test :as t :include-macros true]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn- render-filter
[shadow-style]
(let [shape (-> (cts/setup-shape {:type :rect :x 0 :y 0 :width 100 :height 100})
(assoc :fills [{:fill-color "#4488ff" :fill-opacity 1}]
:shadow [{:id (uuid/next)
:style shadow-style
:offset-x 4 :offset-y 4 :blur 4 :spread 0
:hidden false
:color {:color "#000000" :opacity 0.5}}]))]
(rds/renderToStaticMarkup
(mf/element filters/filters* #js {:filterId "filter" :shape shape}))))
(defn- tags
[markup tag]
(re-seq (re-pattern (str "<" tag "[^>]*>")) markup))
;; Each filter primitive must name its result and read the previous one, or
;; the chain breaks: an inner shadow then covers the shape fill with the
;; shadow color (#11967).
(t/deftest inner-shadow-filter-chain-is-wired
(let [markup (render-filter :inner-shadow)
blends (tags markup "feBlend")]
(t/is (str/includes? (first (tags markup "feFlood")) "result="))
(t/is (= 2 (count blends)))
(t/is (every? #(str/includes? % "in2=") blends))
(t/is (every? #(str/includes? % "result=") blends))))
(t/deftest drop-shadow-filter-chain-is-wired
(let [blends (tags (render-filter :drop-shadow) "feBlend")]
(t/is (every? #(and (str/includes? % "in2=") (str/includes? % "result=")) blends))))