@@ -105,13 +105,32 @@ export function svg(name, size = 16, className = '') {
105105
106106 const document = parser . parseFromString ( svgs [ name ] , 'image/svg+xml' ) ;
107107 const svgNode = document . firstChild ;
108- if ( size !== 16 ) svgNode . setAttribute ( 'width' , String ( size ) ) ;
109- if ( size !== 16 ) svgNode . setAttribute ( 'height' , String ( size ) ) ;
110- // filter array to remove empty string
108+ if ( size !== 16 ) {
109+ svgNode . setAttribute ( 'width' , String ( size ) ) ;
110+ svgNode . setAttribute ( 'height' , String ( size ) ) ;
111+ }
111112 if ( className ) svgNode . classList . add ( ...className . split ( / \s + / ) . filter ( Boolean ) ) ;
112113 return serializer . serializeToString ( svgNode ) ;
113114}
114115
116+ export function svgParseOuterInner ( name ) {
117+ const svgStr = svgs [ name ] ;
118+ if ( ! svgStr ) throw new Error ( `Unknown SVG icon: ${ name } ` ) ;
119+
120+ // parse the SVG string to 2 parts
121+ // * svgInnerHtml: the inner part of the SVG, will be used as the content of the <svg> VNode
122+ // * svgOuter: the outer part of the SVG, including attributes
123+ // the builtin SVG contents are clean, so it's safe to use `indexOf` to split the content:
124+ // eg: <svg outer-attributes>${svgInnerHtml}</svg>
125+ const p1 = svgStr . indexOf ( '>' ) , p2 = svgStr . lastIndexOf ( '<' ) ;
126+ if ( p1 === - 1 || p2 === - 1 ) throw new Error ( `Invalid SVG icon: ${ name } ` ) ;
127+ const svgInnerHtml = svgStr . slice ( p1 + 1 , p2 ) ;
128+ const svgOuterHtml = svgStr . slice ( 0 , p1 + 1 ) + svgStr . slice ( p2 ) ;
129+ const svgDoc = parser . parseFromString ( svgOuterHtml , 'image/svg+xml' ) ;
130+ const svgOuter = svgDoc . firstChild ;
131+ return { svgOuter, svgInnerHtml} ;
132+ }
133+
115134export const SvgIcon = {
116135 name : 'SvgIcon' ,
117136 props : {
@@ -120,6 +139,32 @@ export const SvgIcon = {
120139 className : { type : String , default : '' } ,
121140 } ,
122141 render ( ) {
123- return h ( 'span' , { innerHTML : svg ( this . name , this . size , this . className ) } ) ;
142+ const { svgOuter, svgInnerHtml} = svgParseOuterInner ( this . name ) ;
143+ // https://vuejs.org/guide/extras/render-function.html#creating-vnodes
144+ // the `^` is used for attr, set SVG attributes like 'width', `aria-hidden`, `viewBox`, etc
145+ const attrs = { } ;
146+ for ( const attr of svgOuter . attributes ) {
147+ if ( attr . name === 'class' ) continue ;
148+ attrs [ `^${ attr . name } ` ] = attr . value ;
149+ }
150+ attrs [ `^width` ] = this . size ;
151+ attrs [ `^height` ] = this . size ;
152+
153+ // make the <SvgIcon class="foo" class-name="bar"> classes work together
154+ const classes = [ ] ;
155+ for ( const cls of svgOuter . classList ) {
156+ classes . push ( cls ) ;
157+ }
158+ // TODO: drop the `className/class-name` prop in the future, only use "class" prop
159+ if ( this . className ) {
160+ classes . push ( ...this . className . split ( / \s + / ) . filter ( Boolean ) ) ;
161+ }
162+
163+ // create VNode
164+ return h ( 'svg' , {
165+ ...attrs ,
166+ class : classes ,
167+ innerHTML : svgInnerHtml ,
168+ } ) ;
124169 } ,
125170} ;
0 commit comments