Skip to content

Commit 0c54b3a

Browse files
committed
feat(icon): expose 'inner-html' and 'svg' shadow parts
Also - add usage examples to index.html - update icon readme
1 parent 19dd664 commit 0c54b3a

File tree

3 files changed

+79
-7
lines changed

3 files changed

+79
-7
lines changed

src/components/icon/icon.tsx

+59-7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export class Icon {
1212
private io?: IntersectionObserver;
1313
private iconName: string | null = null;
1414
private inheritedAttributes: { [k: string]: any } = {};
15+
private svgContentInjected: boolean = false;
1516

1617
@Element() el!: HTMLElement;
1718

@@ -80,7 +81,7 @@ export class Icon {
8081
* @default true
8182
*/
8283
@Prop() sanitize = true;
83-
84+
8485
componentWillLoad() {
8586
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
8687
}
@@ -122,13 +123,48 @@ export class Icon {
122123
cb();
123124
}
124125
}
125-
126+
126127
private hasAriaHidden = () => {
127128
const { el } = this;
128-
129+
129130
return el.hasAttribute('aria-hidden') && el.getAttribute('aria-hidden') === 'true';
130131
}
131132

133+
private createSvgElement(): SVGElement | null {
134+
if (this.svgContent) {
135+
const template = document.createElement("template");
136+
template.innerHTML = this.svgContent;
137+
138+
// Extract the first element from the template.
139+
// It should be our <svg>.
140+
const node = template.content.firstChild;
141+
142+
if (node) {
143+
if (node.nodeType !== Node.ELEMENT_NODE) {
144+
return null;
145+
} else {
146+
if ((node as Element).tagName === 'svg') {
147+
const svg = node as SVGElement;
148+
149+
// Add the shadow part
150+
svg.setAttribute('part', 'svg');
151+
152+
return svg;
153+
}
154+
}
155+
}
156+
}
157+
158+
return null;
159+
}
160+
161+
private injectSvgElement(svgElement: SVGElement) {
162+
const el = (this.el.shadowRoot as ShadowRoot).querySelector('.icon-inner')
163+
if (el) {
164+
el.appendChild(svgElement);
165+
}
166+
}
167+
132168
@Watch('name')
133169
@Watch('src')
134170
@Watch('icon')
@@ -186,13 +222,29 @@ export class Icon {
186222
{...inheritedAttributes}
187223
>
188224
{Build.isBrowser && this.svgContent ? (
189-
<div class="icon-inner" innerHTML={this.svgContent}></div>
225+
<div class="icon-inner" part="icon-inner" >
226+
</div>
190227
) : (
191228
<div class="icon-inner"></div>
192229
)}
193230
</Host>
194231
);
195232
}
233+
234+
componentDidRender() {
235+
/**
236+
* If it has not been done already, create & inject the <SVG> element
237+
* into `div.icon-inner`.
238+
*/
239+
if (!this.svgContentInjected) {
240+
const svgElement = this.createSvgElement();
241+
if (svgElement) {
242+
this.injectSvgElement(svgElement);
243+
244+
this.svgContentInjected = true;
245+
}
246+
}
247+
}
196248
}
197249

198250
const getIonMode = () =>
@@ -201,8 +253,8 @@ const getIonMode = () =>
201253
const createColorClasses = (color: string | undefined) => {
202254
return color
203255
? {
204-
'ion-color': true,
205-
[`ion-color-${color}`]: true,
206-
}
256+
'ion-color': true,
257+
[`ion-color-${color}`]: true,
258+
}
207259
: null;
208260
};

src/components/icon/readme.md

+8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@
2222
| `src` | `src` | Specifies the exact `src` of an SVG file to use. | `string \| undefined` | `undefined` |
2323

2424

25+
## Shadow Parts
26+
27+
| Part | Description |
28+
| -------------- | ----------- |
29+
| `"icon-inner"` | The container that wraps the SVG element |
30+
| `"svg"` | The svg element |
31+
32+
2533
----------------------------------------------
2634

2735
*Built with [StencilJS](https://stenciljs.com/)*

src/index.html

+12
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ <h2>Sanitized (shouldn't show)</h2>
130130
<h2>Not Sanitized (should show)</h2>
131131
<ion-icon sanitize="false" src="./assets/no-sanitize.svg"></ion-icon>
132132

133+
<h2>Accessing ::part()'s</h2>
134+
<ion-icon id="part-svg" src="./assets/bug_report.svg"></ion-icon>
135+
<ion-icon id="part-icon-inner" src="./assets/chat.svg"></ion-icon>
136+
133137
<p>
134138
<a href="./cheatsheet.html">Cheatsheet</a>
135139
</p>
@@ -156,6 +160,14 @@ <h2>Not Sanitized (should show)</h2>
156160
stroke: red;
157161
fill: blue;
158162
}
163+
164+
#part-svg::part(svg) {
165+
fill: rebeccapurple;
166+
}
167+
168+
#part-icon-inner::part(icon-inner) {
169+
fill: aliceblue;
170+
}
159171
</style>
160172
</body>
161173
</html>

0 commit comments

Comments
 (0)