UNPKG

vuetify

Version:

Vue Material Component Framework

255 lines (218 loc) 6.22 kB
import './VIcon.sass' // Mixins import BindsAttrs from '../../mixins/binds-attrs' import Colorable from '../../mixins/colorable' import Sizeable from '../../mixins/sizeable' import Themeable from '../../mixins/themeable' // Util import { convertToUnit, keys, remapInternalIcon } from '../../util/helpers' // Types import Vue, { CreateElement, VNode, VNodeChildren, VNodeData } from 'vue' import mixins from '../../util/mixins' import { VuetifyIcon, VuetifyIconComponent } from 'vuetify/types/services/icons' enum SIZE_MAP { xSmall = '12px', small = '16px', default = '24px', medium = '28px', large = '36px', xLarge = '40px' } function isFontAwesome5 (iconType: string): boolean { return ['fas', 'far', 'fal', 'fab', 'fad'].some(val => iconType.includes(val)) } function isSvgPath (icon: string): boolean { return (/^[mzlhvcsqta]\s*[-+.0-9][^mlhvzcsqta]+/i.test(icon) && /[\dz]$/i.test(icon) && icon.length > 4) } const VIcon = mixins( BindsAttrs, Colorable, Sizeable, Themeable /* @vue/component */ ).extend({ name: 'v-icon', props: { dense: Boolean, disabled: Boolean, left: Boolean, right: Boolean, size: [Number, String], tag: { type: String, required: false, default: 'i', }, }, computed: { medium () { return false }, hasClickListener (): boolean { return Boolean( this.listeners$.click || this.listeners$['!click'] ) }, }, methods: { getIcon (): VuetifyIcon { let iconName = '' if (this.$slots.default) iconName = this.$slots.default[0].text!.trim() return remapInternalIcon(this, iconName) }, getSize (): string | undefined { const sizes = { xSmall: this.xSmall, small: this.small, medium: this.medium, large: this.large, xLarge: this.xLarge, } const explicitSize = keys(sizes).find(key => sizes[key]) return ( (explicitSize && SIZE_MAP[explicitSize]) || convertToUnit(this.size) ) }, // Component data for both font icon and SVG wrapper span getDefaultData (): VNodeData { return { staticClass: 'v-icon notranslate', class: { 'v-icon--disabled': this.disabled, 'v-icon--left': this.left, 'v-icon--link': this.hasClickListener, 'v-icon--right': this.right, 'v-icon--dense': this.dense, }, attrs: { 'aria-hidden': !this.hasClickListener, disabled: this.hasClickListener && this.disabled, type: this.hasClickListener ? 'button' : undefined, ...this.attrs$, }, on: this.listeners$, } }, getSvgWrapperData () { const fontSize = this.getSize() const wrapperData = { ...this.getDefaultData(), style: fontSize ? { fontSize, height: fontSize, width: fontSize, } : undefined, } this.applyColors(wrapperData) return wrapperData }, applyColors (data: VNodeData): void { data.class = { ...data.class, ...this.themeClasses } this.setTextColor(this.color, data) }, renderFontIcon (icon: string, h: CreateElement): VNode { const newChildren: VNodeChildren = [] const data = this.getDefaultData() let iconType = 'material-icons' // Material Icon delimiter is _ // https://material.io/icons/ const delimiterIndex = icon.indexOf('-') const isMaterialIcon = delimiterIndex <= -1 if (isMaterialIcon) { // Material icon uses ligatures. newChildren.push(icon) } else { iconType = icon.slice(0, delimiterIndex) if (isFontAwesome5(iconType)) iconType = '' } data.class[iconType] = true data.class[icon] = !isMaterialIcon const fontSize = this.getSize() if (fontSize) data.style = { fontSize } this.applyColors(data) return h(this.hasClickListener ? 'button' : this.tag, data, newChildren) }, renderSvgIcon (icon: string, h: CreateElement): VNode { const svgData: VNodeData = { class: 'v-icon__svg', attrs: { xmlns: 'http://www.w3.org/2000/svg', viewBox: '0 0 24 24', role: 'img', 'aria-hidden': true, }, } const size = this.getSize() if (size) { svgData.style = { fontSize: size, height: size, width: size, } } return h(this.hasClickListener ? 'button' : 'span', this.getSvgWrapperData(), [ h('svg', svgData, [ h('path', { attrs: { d: icon, }, }), ]), ]) }, renderSvgIconComponent ( icon: VuetifyIconComponent, h: CreateElement ): VNode { const data: VNodeData = { class: { 'v-icon__component': true, }, } const size = this.getSize() if (size) { data.style = { fontSize: size, height: size, width: size, } } this.applyColors(data) const component = icon.component data.props = icon.props data.nativeOn = data.on return h(this.hasClickListener ? 'button' : 'span', this.getSvgWrapperData(), [ h(component, data), ]) }, }, render (h: CreateElement): VNode { const icon = this.getIcon() if (typeof icon === 'string') { if (isSvgPath(icon)) { return this.renderSvgIcon(icon, h) } return this.renderFontIcon(icon, h) } return this.renderSvgIconComponent(icon, h) }, }) export default Vue.extend({ name: 'v-icon', $_wrapperFor: VIcon, functional: true, render (h, { data, children }): VNode { let iconName = '' // Support usage of v-text and v-html if (data.domProps) { iconName = data.domProps.textContent || data.domProps.innerHTML || iconName // Remove nodes so it doesn't // overwrite our changes delete data.domProps.textContent delete data.domProps.innerHTML } return h(VIcon, data, iconName ? [iconName] : children) }, })