@@ -107,7 +107,6 @@ export function svg(name, size = 16, className = '') {
107107 const svgNode = document . firstChild ;
108108 if ( size !== 16 ) svgNode . setAttribute ( 'width' , String ( size ) ) ;
109109 if ( size !== 16 ) svgNode . setAttribute ( 'height' , String ( size ) ) ;
110- // filter array to remove empty string
111110 if ( className ) svgNode . classList . add ( ...className . split ( / \s + / ) . filter ( Boolean ) ) ;
112111 return serializer . serializeToString ( svgNode ) ;
113112}
@@ -120,6 +119,45 @@ export const SvgIcon = {
120119 className : { type : String , default : '' } ,
121120 } ,
122121 render ( ) {
123- return h ( 'span' , { innerHTML : svg ( this . name , this . size , this . className ) } ) ;
122+ const svgStr = svgs [ this . name ] ;
123+ if ( ! svgStr ) throw new Error ( `Unknown SVG icon: ${ this . name } ` ) ;
124+
125+ // parse the SVG string to 2 parts
126+ // * svgInnerHtml: the inner part of the SVG, will be used as the content of the <svg> VNode
127+ // * svgOuter: the outer part of the SVG, including attributes
128+ // the builtin SVG contents are clean, so it's safe to use `indexOf` to split the content:
129+ // eg: <svg outer-attributes>${svgInnerHtml}</svg>
130+ const p1 = svgStr . indexOf ( '>' ) , p2 = svgStr . lastIndexOf ( '<' ) ;
131+ if ( p1 === - 1 || p2 === - 1 ) throw new Error ( `Invalid SVG icon: ${ this . name } ` ) ;
132+ const svgInnerHtml = svgStr . slice ( p1 + 1 , p2 ) ;
133+ const svgOuterHtml = svgStr . slice ( 0 , p1 + 1 ) + svgStr . slice ( p2 ) ;
134+ const svgDoc = parser . parseFromString ( svgOuterHtml , 'image/svg+xml' ) ;
135+ const svgOuter = svgDoc . firstChild ;
136+
137+ // https://vuejs.org/guide/extras/render-function.html#creating-vnodes
138+ // the `^` is used for attr, set SVG attributes like 'width', `aria-hidden`, `viewBox`, etc
139+ const attrs = { } ;
140+ for ( const attr of svgOuter . attributes ) {
141+ if ( attr . name === 'class' ) continue ;
142+ attrs [ `^${ attr . name } ` ] = attr . value ;
143+ }
144+ attrs [ `^width` ] = this . size ;
145+ attrs [ `^height` ] = this . size ;
146+
147+ // make the <SvgIcon class="foo" class-name="bar"> classes work together
148+ const classes = [ ] ;
149+ for ( const cls of svgOuter . classList ) {
150+ classes . push ( cls ) ;
151+ }
152+ if ( this . className ) {
153+ classes . push ( ...this . className . split ( / \s + / ) . filter ( Boolean ) ) ;
154+ }
155+
156+ // create VNode
157+ return h ( 'svg' , {
158+ ...attrs ,
159+ class : classes ,
160+ innerHTML : svgInnerHtml ,
161+ } ) ;
124162 } ,
125163} ;
0 commit comments