suyiScreen/node_modules/vue-awesome/components/Icon.vue

367 lines
7.5 KiB
Vue

<script>
import nanoid from 'nanoid/non-secure'
let icons = {}
function warn (msg, vm) {
if (!vm) {
console.error(msg)
return
}
vm.constructor.super.util.warn(msg, vm)
}
export default {
name: 'fa-icon',
props: {
name: {
type: String,
validator (val) {
if (val && !(val in icons)) {
warn(
`Invalid prop: prop "name" is referring to an unregistered icon "${val}".\n` +
`Please make sure you have imported this icon before using it.`,
this
)
return false
}
return true
}
},
title: String,
scale: [Number, String],
spin: Boolean,
inverse: Boolean,
pulse: Boolean,
flip: {
validator (val) {
return val === 'horizontal' || val === 'vertical' || val === 'both'
}
},
label: String,
tabindex: [Number, String]
},
data () {
return {
id: getId('va-'),
x: false,
y: false,
childrenWidth: 0,
childrenHeight: 0,
outerScale: 1
}
},
computed: {
normalizedScale () {
let scale = this.scale
scale = typeof scale === 'undefined' ? 1 : Number(scale)
if (isNaN(scale) || scale <= 0) {
warn(
`Invalid prop: prop "scale" should be a number over 0.`,
this
)
return this.outerScale
}
return scale * this.outerScale
},
klass () {
let classes = {
'fa-icon': true,
'fa-spin': this.spin,
'fa-flip-horizontal': this.flip === 'horizontal',
'fa-flip-vertical': this.flip === 'vertical',
'fa-flip-both': this.flip === 'both',
'fa-inverse': this.inverse,
'fa-pulse': this.pulse
}
if (this.classes) {
Object.keys(this.classes).forEach(c => {
if (this.classes[c]) {
classes[c] = true
}
})
}
return classes
},
icon () {
if (this.name) {
return icons[this.name]
}
return null
},
box () {
if (this.icon) {
return `0 0 ${this.icon.width} ${this.icon.height}`
}
return `0 0 ${this.width} ${this.height}`
},
ratio () {
if (!this.icon) {
return 1
}
let { width, height } = this.icon
return Math.max(width, height) / 16
},
width () {
return (
this.childrenWidth ||
(this.icon && (this.icon.width / this.ratio) * this.normalizedScale) ||
0
)
},
height () {
return (
this.childrenHeight ||
(this.icon && (this.icon.height / this.ratio) * this.normalizedScale) ||
0
)
},
style () {
if (this.normalizedScale === 1) {
return false
}
return {
fontSize: this.normalizedScale + 'em'
}
},
raw () {
// generate unique id for each icon's SVG element with ID
if (!this.icon || !this.icon.raw) {
return null
}
let raw = this.icon.raw
let ids = {}
raw = raw.replace(
/\s(?:xml:)?id=(["']?)([^"')\s]+)\1/g,
(match, quote, id) => {
let uniqueId = getId('vat-')
ids[id] = uniqueId
return ` id="${uniqueId}"`
}
)
raw = raw.replace(
/#(?:([^'")\s]+)|xpointer\(id\((['"]?)([^')]+)\2\)\))/g,
(match, rawId, _, pointerId) => {
let id = rawId || pointerId
if (!id || !ids[id]) {
return match
}
return `#${ids[id]}`
}
)
return raw
},
focusable () {
let { tabindex } = this
if (tabindex == null) {
return 'false'
}
let index =
typeof tabindex === 'string' ? parseInt(tabindex, 10) : tabindex
if (index >= 0) {
return null
}
return 'false'
}
},
mounted () {
this.updateStack()
},
updated () {
this.updateStack()
},
methods: {
updateStack () {
if (!this.name && this.name !== null && this.$children.length === 0) {
warn(`Invalid prop: prop "name" is required.`, this)
return
}
if (this.icon) {
return
}
let width = 0
let height = 0
this.$children.forEach(child => {
child.outerScale = this.normalizedScale
width = Math.max(width, child.width)
height = Math.max(height, child.height)
})
this.childrenWidth = width
this.childrenHeight = height
this.$children.forEach(child => {
child.x = (width - child.width) / 2
child.y = (height - child.height) / 2
})
}
},
render (h) {
if (this.name === null) {
return h()
}
let options = {
class: this.klass,
style: this.style,
attrs: {
role: this.$attrs.role || (this.label || this.title ? 'img' : null),
'aria-label': this.label || null,
'aria-hidden': !(this.label || this.title),
tabindex: this.tabindex,
x: this.x,
y: this.y,
width: this.width,
height: this.height,
viewBox: this.box,
focusable: this.focusable
},
on: this.$listeners
}
let titleId = this.id
if (this.title) {
options.attrs['aria-labelledby'] = titleId
}
if (this.raw) {
let html = `<g>${this.raw}</g>`
if (this.title) {
html = `<title id="${titleId}">${escapeHTML(this.title)}</title>${html}`
}
options.domProps = { innerHTML: html }
}
let content = this.title
? [h('title', { attrs: { id: titleId } }, this.title)]
: []
return h(
'svg',
options,
this.raw
? null
: content.concat(
[
h(
'g',
this.$slots.default || (this.icon ? [
...this.icon.paths.map((path, i) =>
h('path', {
attrs: path,
key: `path-${i}`
})
),
...this.icon.polygons.map((polygon, i) =>
h('polygon', {
attrs: polygon,
key: `polygon-${i}`
})
)
] : [])
)]
)
)
},
register (data) {
for (let name in data) {
let icon = data[name]
let { paths = [], d, polygons = [], points } = icon
if (d) {
paths.push({ d })
}
if (points) {
polygons.push({ points })
}
icons[name] = assign({}, icon, {
paths,
polygons
})
}
},
icons
}
function assign (obj, ...sources) {
sources.forEach(source => {
for (let key in source) {
if (source.hasOwnProperty(key)) {
obj[key] = source[key]
}
}
})
return obj
}
function getId (prefix = '') {
return prefix + nanoid(7)
}
const ESCAPE_MAP = {
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
'&': '&amp;'
}
function escapeHTML (html) {
return html.replace(/[<>"&]/g, c => ESCAPE_MAP[c] || c)
}
</script>
<style>
.fa-icon {
display: inline-block;
fill: currentColor;
overflow: visible;
}
.fa-icon > g {
transform-origin: 50% 50%;
}
.fa-flip-horizontal {
transform: scale(-1, 1);
}
.fa-flip-vertical {
transform: scale(1, -1);
}
.fa-flip-both {
transform: scale(-1, -1);
}
.fa-spin > g {
animation: fa-spin 1s 0s infinite linear;
}
.fa-pulse > g {
animation: fa-spin 1s infinite steps(8);
}
.fa-inverse {
color: #fff;
}
@keyframes fa-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>