🐛 Add :stroke-image support to plugins API (#10683)

This commit is contained in:
Elena Torró 2026-07-14 10:26:19 +02:00 committed by GitHub
parent a006a12ab6
commit c11d3aaaa7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 60 additions and 5 deletions

View File

@ -206,11 +206,13 @@
;; strokeCapStart?: StrokeCap;
;; strokeCapEnd?: StrokeCap;
;; strokeColorGradient?: Gradient;
;; strokeImage?: ImageData;
;; }
(defn format-stroke
[{:keys [stroke-color stroke-color-ref-file stroke-color-ref-id
stroke-opacity stroke-style stroke-width stroke-alignment
stroke-cap-start stroke-cap-end stroke-color-gradient] :as stroke}]
stroke-cap-start stroke-cap-end stroke-color-gradient
stroke-image] :as stroke}]
(when (some? stroke)
(obj/without-empty
@ -223,7 +225,8 @@
:strokeAlignment (format-key stroke-alignment)
:strokeCapStart (format-key stroke-cap-start)
:strokeCapEnd (format-key stroke-cap-end)
:strokeColorGradient (format-gradient stroke-color-gradient)})))
:strokeColorGradient (format-gradient stroke-color-gradient)
:strokeImage (format-image stroke-image)})))
;; export interface Blur {

View File

@ -214,6 +214,7 @@
;; strokeCapStart?: StrokeCap;
;; strokeCapEnd?: StrokeCap;
;; strokeColorGradient?: Gradient;
;; strokeImage?: ImageData;
;; }
(defn parse-stroke
[^js stroke]
@ -228,7 +229,8 @@
:stroke-alignment (-> (obj/get stroke "strokeAlignment") parse-keyword)
:stroke-cap-start (-> (obj/get stroke "strokeCapStart") parse-keyword)
:stroke-cap-end (-> (obj/get stroke "strokeCapEnd") parse-keyword)
:stroke-color-gradient (-> (obj/get stroke "strokeColorGradient") parse-gradient)})))
:stroke-color-gradient (-> (obj/get stroke "strokeColorGradient") parse-gradient)
:stroke-image (-> (obj/get stroke "strokeImage") parse-image-data)})))
(defn parse-strokes
[^js strokes]

View File

@ -18,7 +18,7 @@
:strokeColor
{:get (fn [] (:stroke-color @state))
:set (fn [v]
(swap! state #(-> % (assoc :stroke-color v) (dissoc :stroke-color-gradient)))
(swap! state #(-> % (assoc :stroke-color v) (dissoc :stroke-color-gradient :stroke-image)))
(on-change!))}
:strokeColorRefFile
@ -62,7 +62,13 @@
(on-change!))]
(gradients/gradient-proxy gradient-state gradient-change!))))
:set (fn [v]
(swap! state #(-> % (assoc :stroke-color-gradient (parser/parse-gradient v)) (dissoc :stroke-color)))
(swap! state #(-> % (assoc :stroke-color-gradient (parser/parse-gradient v)) (dissoc :stroke-color :stroke-image)))
(on-change!))}
:strokeImage
{:get (fn [] (format/format-image (:stroke-image @state)))
:set (fn [v]
(swap! state #(-> % (assoc :stroke-image (parser/parse-image-data v)) (dissoc :stroke-color :stroke-color-gradient)))
(on-change!))})))
(defn format-strokes

View File

@ -467,6 +467,7 @@
"strokeColorGradient",
"strokeColorRefFile",
"strokeColorRefId",
"strokeImage",
"strokeOpacity",
"strokeStyle",
"strokeWidth"
@ -7791,6 +7792,12 @@
"kind": "getset",
"type": "Gradient",
"array": false
},
"strokeImage": {
"decl": "Stroke",
"kind": "getset",
"type": "ImageData",
"array": false
}
},
"SvgRaw": {

View File

@ -61,6 +61,39 @@ describe.skipIfMocked('Media', () => {
expect(fill.fillImage).toBeDefined();
});
test('an uploaded image can be used as a stroke', async (ctx) => {
const image = await ctx.penpot.uploadMediaData(
'plugin-stroke',
PNG_1X1,
'image/png',
);
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
rect.strokes = [{ strokeWidth: 2, strokeImage: image }];
const strokes = rect.strokes;
expect(strokes).toHaveLength(1);
expect(strokes[0].strokeImage).toBeDefined();
});
test('Stroke.strokeImage can be set on a stroke and clears the solid color', async (ctx) => {
const image = await ctx.penpot.uploadMediaData(
'plugin-stroke-set',
PNG_1X1,
'image/png',
);
const rect = ctx.penpot.createRectangle();
ctx.board.appendChild(rect);
rect.strokes = [{ strokeColor: '#ff0000', strokeWidth: 2 }];
// Set strokeImage directly on the stroke (covers Stroke.strokeImage (set)).
const stroke = rect.strokes[0];
stroke.strokeImage = image;
expect(stroke.strokeImage).toBeDefined();
// An image stroke replaces the solid color.
expect(stroke.strokeColor).toBeFalsy();
});
test('uploadMediaUrl resolves to image data', async (ctx) => {
// Needs the backend to fetch an external URL, which may be unavailable in
// the headless runner; treat a rejection as an environment limitation.

View File

@ -4180,6 +4180,10 @@ export interface Stroke {
* The optional gradient stroke defined by a Gradient object.
*/
strokeColorGradient?: Gradient;
/**
* The optional image stroke defined by an ImageData object.
*/
strokeImage?: ImageData;
}
/**