Skip to content

Commit a7fd3cf

Browse files
committed
feat(): support svg icon with content
1 parent 846ca27 commit a7fd3cf

File tree

2 files changed

+76
-48
lines changed

2 files changed

+76
-48
lines changed

bricks/icons/src/shared/SvgCache.ts

Lines changed: 61 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,65 @@ interface ResolveIconOptions {
1414
replaceSource?(source: string): string;
1515
}
1616

17+
export function constructSvgElement(
18+
content: string,
19+
retryable: false,
20+
options?: ResolveIconOptions
21+
): SVGSVGElement | null;
22+
export function constructSvgElement(
23+
content: string,
24+
retryable: true,
25+
options?: ResolveIconOptions
26+
): SVGResult;
27+
export function constructSvgElement(
28+
content: string,
29+
retryable: boolean,
30+
options?: ResolveIconOptions
31+
): SVGResult | null {
32+
const div = document.createElement("div");
33+
div.innerHTML = content;
34+
35+
const svg = div.firstElementChild;
36+
if (svg?.tagName?.toLowerCase() !== "svg")
37+
return retryable ? CACHEABLE_ERROR : null;
38+
39+
if (!parser) parser = new DOMParser();
40+
const doc = parser.parseFromString(svg.outerHTML, "text/html");
41+
42+
const svgEl = doc.body.querySelector("svg");
43+
if (!svgEl) return retryable ? CACHEABLE_ERROR : null;
44+
45+
const titles = svgEl.querySelectorAll("title");
46+
for (const title of titles) {
47+
title.remove();
48+
}
49+
50+
if (options?.currentColor) {
51+
const colorProps = [
52+
"color",
53+
"fill",
54+
"stroke",
55+
"stop-color",
56+
"flood-color",
57+
"lighting-color",
58+
];
59+
for (const prop of colorProps) {
60+
const elements = svgEl.querySelectorAll(
61+
`[${prop}]:not([${prop}="none"])`
62+
);
63+
for (const e of elements) {
64+
if (!belongToMask(e, svgEl)) {
65+
e.setAttribute(prop, "currentColor");
66+
}
67+
}
68+
}
69+
}
70+
71+
svgEl.setAttribute("width", "1em");
72+
svgEl.setAttribute("height", "1em");
73+
return document.adoptNode(svgEl);
74+
}
75+
1776
/** Given a URL, this function returns the resulting SVG element or an appropriate error symbol. */
1877
async function resolveIcon(
1978
url: string,
@@ -29,47 +88,8 @@ async function resolveIcon(
2988
}
3089

3190
try {
32-
const div = document.createElement("div");
33-
div.innerHTML = await fileData.text();
34-
35-
const svg = div.firstElementChild;
36-
if (svg?.tagName?.toLowerCase() !== "svg") return CACHEABLE_ERROR;
37-
38-
if (!parser) parser = new DOMParser();
39-
const doc = parser.parseFromString(svg.outerHTML, "text/html");
40-
41-
const svgEl = doc.body.querySelector("svg");
42-
if (!svgEl) return CACHEABLE_ERROR;
43-
44-
const titles = svgEl.querySelectorAll("title");
45-
for (const title of titles) {
46-
title.remove();
47-
}
48-
49-
if (options?.currentColor) {
50-
const colorProps = [
51-
"color",
52-
"fill",
53-
"stroke",
54-
"stop-color",
55-
"flood-color",
56-
"lighting-color",
57-
];
58-
for (const prop of colorProps) {
59-
const elements = svgEl.querySelectorAll(
60-
`[${prop}]:not([${prop}="none"])`
61-
);
62-
for (const e of elements) {
63-
if (!belongToMask(e, svgEl)) {
64-
e.setAttribute(prop, "currentColor");
65-
}
66-
}
67-
}
68-
}
69-
70-
svgEl.setAttribute("width", "1em");
71-
svgEl.setAttribute("height", "1em");
72-
return document.adoptNode(svgEl);
91+
const content = await fileData.text();
92+
return constructSvgElement(content, true, options);
7393
} catch {
7494
return CACHEABLE_ERROR;
7595
}

bricks/icons/src/svg-icon/index.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
type EventEmitter,
66
} from "@next-core/element";
77
import { wrapLocalBrick } from "@next-core/react-element";
8-
import { getIcon } from "../shared/SvgCache.js";
8+
import { constructSvgElement, getIcon } from "../shared/SvgCache.js";
99
import { getImageUrl } from "../shared/getImageUrl.js";
1010
import type { IconEvents, IconEventsMapping } from "../shared/interfaces.js";
1111
import sharedStyleText from "../shared/icons.shadow.css";
@@ -14,6 +14,7 @@ const { defineElement, property, event } = createDecorators();
1414

1515
export interface SvgIconProps {
1616
imgSrc?: string;
17+
svgContent?: string;
1718
noPublicRoot?: boolean;
1819
}
1920

@@ -23,6 +24,8 @@ class SvgIcon extends NextElement implements SvgIconProps {
2324
/** 图标地址 */
2425
@property() accessor imgSrc: string | undefined;
2526

27+
@property() accessor svgContent: string | undefined;
28+
2629
@property({
2730
type: Boolean,
2831
})
@@ -60,13 +63,18 @@ class SvgIcon extends NextElement implements SvgIconProps {
6063
if (!this.isConnected || !this.shadowRoot) {
6164
return;
6265
}
63-
const url = getImageUrl(this.imgSrc, this.noPublicRoot);
64-
65-
const svg = await getIcon(url, { currentColor: true });
66-
if (url !== getImageUrl(this.imgSrc, this.noPublicRoot)) {
67-
// The icon has changed during `await getIcon(...)`
68-
return;
66+
let svg: SVGElement | null = null;
67+
if (this.svgContent) {
68+
svg = constructSvgElement(this.svgContent, false, { currentColor: true });
69+
} else {
70+
const url = getImageUrl(this.imgSrc, this.noPublicRoot);
71+
svg = await getIcon(url, { currentColor: true });
72+
if (url !== getImageUrl(this.imgSrc, this.noPublicRoot)) {
73+
// The icon has changed during `await getIcon(...)`
74+
return;
75+
}
6976
}
77+
7078
// Currently React can't render mixed React Component and DOM nodes which are siblings,
7179
// so we manually construct the DOM.
7280
const nodes: Node[] = [];

0 commit comments

Comments
 (0)