uikit
Version:
UIkit is a lightweight and modular front-end framework for developing fast and powerful web interfaces.
238 lines (169 loc) • 5.25 kB
JavaScript
import {$, $$, after, ajax, append, attr, includes, isVisible, isVoidElement, memoize, noop, Promise, remove, removeAttr, startsWith, toFloat} from 'uikit-util';
export default {
args: 'src',
props: {
id: Boolean,
icon: String,
src: String,
style: String,
width: Number,
height: Number,
ratio: Number,
class: String,
strokeAnimation: Boolean,
focusable: Boolean, // IE 11
attributes: 'list'
},
data: {
ratio: 1,
include: ['style', 'class', 'focusable'],
class: '',
strokeAnimation: false
},
beforeConnect() {
this.class += ' uk-svg';
},
connected() {
if (!this.icon && includes(this.src, '#')) {
[this.src, this.icon] = this.src.split('#');
}
this.svg = this.getSvg().then(el => {
if (this._connected) {
const svg = insertSVG(el, this.$el);
if (this.svgEl && svg !== this.svgEl) {
remove(this.svgEl);
}
this.applyAttributes(svg, el);
this.$emit();
return this.svgEl = svg;
}
}, noop);
},
disconnected() {
this.svg.then(svg => {
if (!this._connected) {
if (isVoidElement(this.$el)) {
this.$el.hidden = false;
}
remove(svg);
this.svgEl = null;
}
});
this.svg = null;
},
update: {
read() {
return !!(this.strokeAnimation && this.svgEl && isVisible(this.svgEl));
},
write() {
applyAnimation(this.svgEl);
},
type: ['resize']
},
methods: {
getSvg() {
return loadSVG(this.src).then(svg =>
parseSVG(svg, this.icon) || Promise.reject('SVG not found.')
);
},
applyAttributes(el, ref) {
for (const prop in this.$options.props) {
if (includes(this.include, prop) && (prop in this)) {
attr(el, prop, this[prop]);
}
}
for (const attribute in this.attributes) {
const [prop, value] = this.attributes[attribute].split(':', 2);
attr(el, prop, value);
}
if (!this.id) {
removeAttr(el, 'id');
}
const props = ['width', 'height'];
let dimensions = props.map(prop => this[prop]);
if (!dimensions.some(val => val)) {
dimensions = props.map(prop => attr(ref, prop));
}
const viewBox = attr(ref, 'viewBox');
if (viewBox && !dimensions.some(val => val)) {
dimensions = viewBox.split(' ').slice(2);
}
dimensions.forEach((val, i) =>
attr(el, props[i], toFloat(val) * this.ratio || null)
);
}
}
};
const loadSVG = memoize(src =>
new Promise((resolve, reject) => {
if (!src) {
reject();
return;
}
if (startsWith(src, 'data:')) {
resolve(decodeURIComponent(src.split(',')[1]));
} else {
ajax(src).then(
xhr => resolve(xhr.response),
() => reject('SVG not found.')
);
}
})
);
function parseSVG(svg, icon) {
if (icon && includes(svg, '<symbol')) {
svg = parseSymbols(svg, icon) || svg;
}
svg = $(svg.substr(svg.indexOf('<svg')));
return svg && svg.hasChildNodes() && svg;
}
const symbolRe = /<symbol([^]*?id=(['"])(.+?)\2[^]*?<\/)symbol>/g;
const symbols = {};
function parseSymbols(svg, icon) {
if (!symbols[svg]) {
symbols[svg] = {};
symbolRe.lastIndex = 0;
let match;
while ((match = symbolRe.exec(svg))) {
symbols[svg][match[3]] = `<svg xmlns="http://www.w3.org/2000/svg"${match[1]}svg>`;
}
}
return symbols[svg][icon];
}
function applyAnimation(el) {
const length = getMaxPathLength(el);
if (length) {
el.style.setProperty('--uk-animation-stroke', length);
}
}
export function getMaxPathLength(el) {
return Math.ceil(Math.max(0, ...$$('[stroke]', el).map(stroke => {
try {
return stroke.getTotalLength();
} catch (e) {
return 0;
}
})));
}
function insertSVG(el, root) {
if (isVoidElement(root) || root.tagName === 'CANVAS') {
root.hidden = true;
const next = root.nextElementSibling;
return equals(el, next)
? next
: after(root, el);
}
const last = root.lastElementChild;
return equals(el, last)
? last
: append(root, el);
}
function equals(el, other) {
return isSVG(el) && isSVG(other) && innerHTML(el) === innerHTML(other);
}
function isSVG(el) {
return el && el.tagName === 'svg';
}
function innerHTML(el) {
return (el.innerHTML || (new XMLSerializer()).serializeToString(el).replace(/<svg.*?>(.*?)<\/svg>/g, '$1')).replace(/\s/g, '');
}