(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (factory((global.TinySVG = {}))); }(this, (function (exports) { 'use strict'; function ensureImported(element, target) { if (element.ownerDocument !== target.ownerDocument) { try { // may fail on webkit return target.ownerDocument.importNode(element, true); } catch (e) { // ignore } } return element; } /** * appendTo utility */ /** * Append a node to a target element and return the appended node. * * @param {SVGElement} element * @param {SVGElement} target * * @return {SVGElement} the appended node */ function appendTo(element, target) { return target.appendChild(ensureImported(element, target)); } /** * append utility */ /** * Append a node to an element * * @param {SVGElement} element * @param {SVGElement} node * * @return {SVGElement} the element */ function append(target, node) { appendTo(node, target); return target; } /** * attribute accessor utility */ var LENGTH_ATTR = 2; var CSS_PROPERTIES = { 'alignment-baseline': 1, 'baseline-shift': 1, 'clip': 1, 'clip-path': 1, 'clip-rule': 1, 'color': 1, 'color-interpolation': 1, 'color-interpolation-filters': 1, 'color-profile': 1, 'color-rendering': 1, 'cursor': 1, 'direction': 1, 'display': 1, 'dominant-baseline': 1, 'enable-background': 1, 'fill': 1, 'fill-opacity': 1, 'fill-rule': 1, 'filter': 1, 'flood-color': 1, 'flood-opacity': 1, 'font': 1, 'font-family': 1, 'font-size': LENGTH_ATTR, 'font-size-adjust': 1, 'font-stretch': 1, 'font-style': 1, 'font-variant': 1, 'font-weight': 1, 'glyph-orientation-horizontal': 1, 'glyph-orientation-vertical': 1, 'image-rendering': 1, 'kerning': 1, 'letter-spacing': 1, 'lighting-color': 1, 'marker': 1, 'marker-end': 1, 'marker-mid': 1, 'marker-start': 1, 'mask': 1, 'opacity': 1, 'overflow': 1, 'pointer-events': 1, 'shape-rendering': 1, 'stop-color': 1, 'stop-opacity': 1, 'stroke': 1, 'stroke-dasharray': 1, 'stroke-dashoffset': 1, 'stroke-linecap': 1, 'stroke-linejoin': 1, 'stroke-miterlimit': 1, 'stroke-opacity': 1, 'stroke-width': LENGTH_ATTR, 'text-anchor': 1, 'text-decoration': 1, 'text-rendering': 1, 'unicode-bidi': 1, 'visibility': 1, 'word-spacing': 1, 'writing-mode': 1 }; function getAttribute(node, name) { if (CSS_PROPERTIES[name]) { return node.style[name]; } else { return node.getAttributeNS(null, name); } } function setAttribute(node, name, value) { var hyphenated = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); var type = CSS_PROPERTIES[hyphenated]; if (type) { // append pixel unit, unless present if (type === LENGTH_ATTR && typeof value === 'number') { value = String(value) + 'px'; } node.style[hyphenated] = value; } else { node.setAttributeNS(null, name, value); } } function setAttributes(node, attrs) { var names = Object.keys(attrs), i, name; for (i = 0, name; (name = names[i]); i++) { setAttribute(node, name, attrs[name]); } } /** * Gets or sets raw attributes on a node. * * @param {SVGElement} node * @param {Object} [attrs] * @param {String} [name] * @param {String} [value] * * @return {String} */ function attr(node, name, value) { if (typeof name === 'string') { if (value !== undefined) { setAttribute(node, name, value); } else { return getAttribute(node, name); } } else { setAttributes(node, name); } return node; } /** * Clear utility */ function index(arr, obj) { if (arr.indexOf) { return arr.indexOf(obj); } for (var i = 0; i < arr.length; ++i) { if (arr[i] === obj) { return i; } } return -1; } var re = /\s+/; var toString = Object.prototype.toString; function defined(o) { return typeof o !== 'undefined'; } /** * Wrap `el` in a `ClassList`. * * @param {Element} el * @return {ClassList} * @api public */ function classes(el) { return new ClassList(el); } function ClassList(el) { if (!el || !el.nodeType) { throw new Error('A DOM element reference is required'); } this.el = el; this.list = el.classList; } /** * Add class `name` if not already present. * * @param {String} name * @return {ClassList} * @api public */ ClassList.prototype.add = function(name) { // classList if (this.list) { this.list.add(name); return this; } // fallback var arr = this.array(); var i = index(arr, name); if (!~i) { arr.push(name); } if (defined(this.el.className.baseVal)) { this.el.className.baseVal = arr.join(' '); } else { this.el.className = arr.join(' '); } return this; }; /** * Remove class `name` when present, or * pass a regular expression to remove * any which match. * * @param {String|RegExp} name * @return {ClassList} * @api public */ ClassList.prototype.remove = function(name) { if ('[object RegExp]' === toString.call(name)) { return this.removeMatching(name); } // classList if (this.list) { this.list.remove(name); return this; } // fallback var arr = this.array(); var i = index(arr, name); if (~i) { arr.splice(i, 1); } this.el.className.baseVal = arr.join(' '); return this; }; /** * Remove all classes matching `re`. * * @param {RegExp} re * @return {ClassList} * @api private */ ClassList.prototype.removeMatching = function(re) { var arr = this.array(); for (var i = 0; i < arr.length; i++) { if (re.test(arr[i])) { this.remove(arr[i]); } } return this; }; /** * Toggle class `name`, can force state via `force`. * * For browsers that support classList, but do not support `force` yet, * the mistake will be detected and corrected. * * @param {String} name * @param {Boolean} force * @return {ClassList} * @api public */ ClassList.prototype.toggle = function(name, force) { // classList if (this.list) { if (defined(force)) { if (force !== this.list.toggle(name, force)) { this.list.toggle(name); // toggle again to correct } } else { this.list.toggle(name); } return this; } // fallback if (defined(force)) { if (!force) { this.remove(name); } else { this.add(name); } } else { if (this.has(name)) { this.remove(name); } else { this.add(name); } } return this; }; /** * Return an array of classes. * * @return {Array} * @api public */ ClassList.prototype.array = function() { var className = this.el.getAttribute('class') || ''; var str = className.replace(/^\s+|\s+$/g, ''); var arr = str.split(re); if ('' === arr[0]) { arr.shift(); } return arr; }; /** * Check if class `name` is present. * * @param {String} name * @return {ClassList} * @api public */ ClassList.prototype.has = ClassList.prototype.contains = function(name) { return ( this.list ? this.list.contains(name) : !! ~index(this.array(), name) ); }; function remove(element) { var parent = element.parentNode; if (parent) { parent.removeChild(element); } return element; } /** * Clear utility */ /** * Removes all children from the given element * * @param {DOMElement} element * @return {DOMElement} the element (for chaining) */ function clear(element) { var child; while ((child = element.firstChild)) { remove(child); } return element; } function clone(element) { return element.cloneNode(true); } var ns = { svg: 'http://www.w3.org/2000/svg' }; /** * DOM parsing utility */ var SVG_START = '' + svg + ''; unwrap = true; } var parsed = parseDocument(svg); if (!unwrap) { return parsed; } var fragment = document.createDocumentFragment(); var parent = parsed.firstChild; while (parent.firstChild) { fragment.appendChild(parent.firstChild); } return fragment; } function parseDocument(svg) { var parser; // parse parser = new DOMParser(); parser.async = false; return parser.parseFromString(svg, 'text/xml'); } /** * Create utility for SVG elements */ /** * Create a specific type from name or SVG markup. * * @param {String} name the name or markup of the element * @param {Object} [attrs] attributes to set on the element * * @returns {SVGElement} */ function create(name, attrs) { var element; if (name.charAt(0) === '<') { element = parse(name).firstChild; element = document.importNode(element, true); } else { element = document.createElementNS(ns.svg, name); } if (attrs) { attr(element, attrs); } return element; } /** * Events handling utility */ function on(node, event, listener, useCapture) { node.addEventListener(event, listener, useCapture); } function off(node, event, listener, useCapture) { node.removeEventListener(event, listener, useCapture); } /** * Geometry helpers */ // fake node used to instantiate svg geometry elements var node = create('svg'); function extend(object, props) { var i, k, keys = Object.keys(props); for (i = 0; (k = keys[i]); i++) { object[k] = props[k]; } return object; } function createPoint(x, y) { var point = node.createSVGPoint(); switch (arguments.length) { case 0: return point; case 2: x = { x: x, y: y }; break; } return extend(point, x); } /** * Create matrix via args. * * @example * * createMatrix({ a: 1, b: 1 }); * createMatrix(); * createMatrix(1, 2, 0, 0, 30, 20); * * @return {SVGMatrix} */ function createMatrix(a, b, c, d, e, f) { var matrix = node.createSVGMatrix(); switch (arguments.length) { case 0: return matrix; case 1: return extend(matrix, a); case 6: return extend(matrix, { a: a, b: b, c: c, d: d, e: e, f: f }); } } function createTransform(matrix) { if (matrix) { return node.createSVGTransformFromMatrix(matrix); } else { return node.createSVGTransform(); } } /** * Serialization util */ var TEXT_ENTITIES = /([&<>]{1})/g; var ATTR_ENTITIES = /([\n\r"]{1})/g; var ENTITY_REPLACEMENT = { '&': '&', '<': '<', '>': '>', '"': '\'' }; function escape(str, pattern) { function replaceFn(match, entity) { return ENTITY_REPLACEMENT[entity] || entity; } return str.replace(pattern, replaceFn); } function serialize(node, output) { var i, len, attrMap, attrNode, childNodes; switch (node.nodeType) { // TEXT case 3: // replace special XML characters output.push(escape(node.textContent, TEXT_ENTITIES)); break; // ELEMENT case 1: output.push('<', node.tagName); if (node.hasAttributes()) { attrMap = node.attributes; for (i = 0, len = attrMap.length; i < len; ++i) { attrNode = attrMap.item(i); output.push(' ', attrNode.name, '="', escape(attrNode.value, ATTR_ENTITIES), '"'); } } if (node.hasChildNodes()) { output.push('>'); childNodes = node.childNodes; for (i = 0, len = childNodes.length; i < len; ++i) { serialize(childNodes.item(i), output); } output.push(''); } else { output.push('/>'); } break; // COMMENT case 8: output.push(''); break; // CDATA case 4: output.push(''); break; default: throw new Error('unable to handle node ' + node.nodeType); } return output; } /** * innerHTML like functionality for SVG elements. * based on innerSVG (https://code.google.com/p/innersvg) */ function set(element, svg) { var parsed = parse(svg); // clear element contents clear(element); if (!svg) { return; } if (!isFragment(parsed)) { // extract from parsed document parsed = parsed.documentElement; } var nodes = slice(parsed.childNodes); // import + append each node for (var i = 0; i < nodes.length; i++) { appendTo(nodes[i], element); } } function get(element) { var child = element.firstChild, output = []; while (child) { serialize(child, output); child = child.nextSibling; } return output.join(''); } function isFragment(node) { return node.nodeName === '#document-fragment'; } function innerSVG(element, svg) { if (svg !== undefined) { try { set(element, svg); } catch (e) { throw new Error('error parsing SVG: ' + e.message); } return element; } else { return get(element); } } function slice(arr) { return Array.prototype.slice.call(arr); } /** * Selection utilities */ function select(node, selector) { return node.querySelector(selector); } function selectAll(node, selector) { var nodes = node.querySelectorAll(selector); return [].map.call(nodes, function(element) { return element; }); } /** * prependTo utility */ /** * Prepend a node to a target element and return the prepended node. * * @param {SVGElement} node * @param {SVGElement} target * * @return {SVGElement} the prepended node */ function prependTo(node, target) { return target.insertBefore(ensureImported(node, target), target.firstChild || null); } /** * prepend utility */ /** * Prepend a node to a target element * * @param {SVGElement} target * @param {SVGElement} node * * @return {SVGElement} the target element */ function prepend(target, node) { prependTo(node, target); return target; } /** * Replace utility */ function replace(element, replacement) { element.parentNode.replaceChild(ensureImported(replacement, element), element); return replacement; } /** * transform accessor utility */ function wrapMatrix(transformList, transform) { if (transform instanceof SVGMatrix) { return transformList.createSVGTransformFromMatrix(transform); } return transform; } function setTransforms(transformList, transforms) { var i, t; transformList.clear(); for (i = 0; (t = transforms[i]); i++) { transformList.appendItem(wrapMatrix(transformList, t)); } } /** * Get or set the transforms on the given node. * * @param {SVGElement} node * @param {SVGTransform|SVGMatrix|Array} [transforms] * * @return {SVGTransform} the consolidated transform */ function transform(node, transforms) { var transformList = node.transform.baseVal; if (transforms) { if (!Array.isArray(transforms)) { transforms = [ transforms ]; } setTransforms(transformList, transforms); } return transformList.consolidate(); } exports.append = append; exports.appendTo = appendTo; exports.attr = attr; exports.classes = classes; exports.clear = clear; exports.clone = clone; exports.create = create; exports.innerSVG = innerSVG; exports.prepend = prepend; exports.prependTo = prependTo; exports.remove = remove; exports.replace = replace; exports.transform = transform; exports.on = on; exports.off = off; exports.createPoint = createPoint; exports.createMatrix = createMatrix; exports.createTransform = createTransform; exports.select = select; exports.selectAll = selectAll; Object.defineProperty(exports, '__esModule', { value: true }); })));