@@ -105,13 +105,32 @@ export function svg(name, size = 16, className = '') {
105
105
106
106
const document = parser . parseFromString ( svgs [ name ] , 'image/svg+xml' ) ;
107
107
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
+ }
111
112
if ( className ) svgNode . classList . add ( ...className . split ( / \s + / ) . filter ( Boolean ) ) ;
112
113
return serializer . serializeToString ( svgNode ) ;
113
114
}
114
115
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
+
115
134
export const SvgIcon = {
116
135
name : 'SvgIcon' ,
117
136
props : {
@@ -120,6 +139,32 @@ export const SvgIcon = {
120
139
className : { type : String , default : '' } ,
121
140
} ,
122
141
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
+ } ) ;
124
169
} ,
125
170
} ;
0 commit comments