From 0c54b3aae08043f99e428673abf13885346fb300 Mon Sep 17 00:00:00 2001 From: Julian P <5290648+Juarrow@users.noreply.github.com> Date: Sun, 5 Dec 2021 00:15:57 +0100 Subject: [PATCH] feat(icon): expose 'inner-html' and 'svg' shadow parts Also - add usage examples to index.html - update icon readme --- src/components/icon/icon.tsx | 66 +++++++++++++++++++++++++++++++---- src/components/icon/readme.md | 8 +++++ src/index.html | 12 +++++++ 3 files changed, 79 insertions(+), 7 deletions(-) diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx index 11dfc12da..3117dd3f0 100755 --- a/src/components/icon/icon.tsx +++ b/src/components/icon/icon.tsx @@ -12,6 +12,7 @@ export class Icon { private io?: IntersectionObserver; private iconName: string | null = null; private inheritedAttributes: { [k: string]: any } = {}; + private svgContentInjected: boolean = false; @Element() el!: HTMLElement; @@ -80,7 +81,7 @@ export class Icon { * @default true */ @Prop() sanitize = true; - + componentWillLoad() { this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']); } @@ -122,13 +123,48 @@ export class Icon { cb(); } } - + private hasAriaHidden = () => { const { el } = this; - + return el.hasAttribute('aria-hidden') && el.getAttribute('aria-hidden') === 'true'; } + private createSvgElement(): SVGElement | null { + if (this.svgContent) { + const template = document.createElement("template"); + template.innerHTML = this.svgContent; + + // Extract the first element from the template. + // It should be our . + const node = template.content.firstChild; + + if (node) { + if (node.nodeType !== Node.ELEMENT_NODE) { + return null; + } else { + if ((node as Element).tagName === 'svg') { + const svg = node as SVGElement; + + // Add the shadow part + svg.setAttribute('part', 'svg'); + + return svg; + } + } + } + } + + return null; + } + + private injectSvgElement(svgElement: SVGElement) { + const el = (this.el.shadowRoot as ShadowRoot).querySelector('.icon-inner') + if (el) { + el.appendChild(svgElement); + } + } + @Watch('name') @Watch('src') @Watch('icon') @@ -186,13 +222,29 @@ export class Icon { {...inheritedAttributes} > {Build.isBrowser && this.svgContent ? ( -
+
+
) : (
)} ); } + + componentDidRender() { + /** + * If it has not been done already, create & inject the element + * into `div.icon-inner`. + */ + if (!this.svgContentInjected) { + const svgElement = this.createSvgElement(); + if (svgElement) { + this.injectSvgElement(svgElement); + + this.svgContentInjected = true; + } + } + } } const getIonMode = () => @@ -201,8 +253,8 @@ const getIonMode = () => const createColorClasses = (color: string | undefined) => { return color ? { - 'ion-color': true, - [`ion-color-${color}`]: true, - } + 'ion-color': true, + [`ion-color-${color}`]: true, + } : null; }; diff --git a/src/components/icon/readme.md b/src/components/icon/readme.md index b4140b45b..56e1e88ad 100644 --- a/src/components/icon/readme.md +++ b/src/components/icon/readme.md @@ -22,6 +22,14 @@ | `src` | `src` | Specifies the exact `src` of an SVG file to use. | `string \| undefined` | `undefined` | +## Shadow Parts + +| Part | Description | +| -------------- | ----------- | +| `"icon-inner"` | The container that wraps the SVG element | +| `"svg"` | The svg element | + + ---------------------------------------------- *Built with [StencilJS](https://stenciljs.com/)* diff --git a/src/index.html b/src/index.html index 93d55537f..f3fbcf6f0 100644 --- a/src/index.html +++ b/src/index.html @@ -130,6 +130,10 @@

Sanitized (shouldn't show)

Not Sanitized (should show)

+

Accessing ::part()'s

+ + +

Cheatsheet

@@ -156,6 +160,14 @@

Not Sanitized (should show)

stroke: red; fill: blue; } + + #part-svg::part(svg) { + fill: rebeccapurple; + } + + #part-icon-inner::part(icon-inner) { + fill: aliceblue; + }