index.esm.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  1. function ensureImported(element, target) {
  2. if (element.ownerDocument !== target.ownerDocument) {
  3. try {
  4. // may fail on webkit
  5. return target.ownerDocument.importNode(element, true);
  6. } catch (e) {
  7. // ignore
  8. }
  9. }
  10. return element;
  11. }
  12. /**
  13. * appendTo utility
  14. */
  15. /**
  16. * Append a node to a target element and return the appended node.
  17. *
  18. * @param {SVGElement} element
  19. * @param {SVGElement} target
  20. *
  21. * @return {SVGElement} the appended node
  22. */
  23. function appendTo(element, target) {
  24. return target.appendChild(ensureImported(element, target));
  25. }
  26. /**
  27. * append utility
  28. */
  29. /**
  30. * Append a node to an element
  31. *
  32. * @param {SVGElement} element
  33. * @param {SVGElement} node
  34. *
  35. * @return {SVGElement} the element
  36. */
  37. function append(target, node) {
  38. appendTo(node, target);
  39. return target;
  40. }
  41. /**
  42. * attribute accessor utility
  43. */
  44. var LENGTH_ATTR = 2;
  45. var CSS_PROPERTIES = {
  46. 'alignment-baseline': 1,
  47. 'baseline-shift': 1,
  48. 'clip': 1,
  49. 'clip-path': 1,
  50. 'clip-rule': 1,
  51. 'color': 1,
  52. 'color-interpolation': 1,
  53. 'color-interpolation-filters': 1,
  54. 'color-profile': 1,
  55. 'color-rendering': 1,
  56. 'cursor': 1,
  57. 'direction': 1,
  58. 'display': 1,
  59. 'dominant-baseline': 1,
  60. 'enable-background': 1,
  61. 'fill': 1,
  62. 'fill-opacity': 1,
  63. 'fill-rule': 1,
  64. 'filter': 1,
  65. 'flood-color': 1,
  66. 'flood-opacity': 1,
  67. 'font': 1,
  68. 'font-family': 1,
  69. 'font-size': LENGTH_ATTR,
  70. 'font-size-adjust': 1,
  71. 'font-stretch': 1,
  72. 'font-style': 1,
  73. 'font-variant': 1,
  74. 'font-weight': 1,
  75. 'glyph-orientation-horizontal': 1,
  76. 'glyph-orientation-vertical': 1,
  77. 'image-rendering': 1,
  78. 'kerning': 1,
  79. 'letter-spacing': 1,
  80. 'lighting-color': 1,
  81. 'marker': 1,
  82. 'marker-end': 1,
  83. 'marker-mid': 1,
  84. 'marker-start': 1,
  85. 'mask': 1,
  86. 'opacity': 1,
  87. 'overflow': 1,
  88. 'pointer-events': 1,
  89. 'shape-rendering': 1,
  90. 'stop-color': 1,
  91. 'stop-opacity': 1,
  92. 'stroke': 1,
  93. 'stroke-dasharray': 1,
  94. 'stroke-dashoffset': 1,
  95. 'stroke-linecap': 1,
  96. 'stroke-linejoin': 1,
  97. 'stroke-miterlimit': 1,
  98. 'stroke-opacity': 1,
  99. 'stroke-width': LENGTH_ATTR,
  100. 'text-anchor': 1,
  101. 'text-decoration': 1,
  102. 'text-rendering': 1,
  103. 'unicode-bidi': 1,
  104. 'visibility': 1,
  105. 'word-spacing': 1,
  106. 'writing-mode': 1
  107. };
  108. function getAttribute(node, name) {
  109. if (CSS_PROPERTIES[name]) {
  110. return node.style[name];
  111. } else {
  112. return node.getAttributeNS(null, name);
  113. }
  114. }
  115. function setAttribute(node, name, value) {
  116. var hyphenated = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
  117. var type = CSS_PROPERTIES[hyphenated];
  118. if (type) {
  119. // append pixel unit, unless present
  120. if (type === LENGTH_ATTR && typeof value === 'number') {
  121. value = String(value) + 'px';
  122. }
  123. node.style[hyphenated] = value;
  124. } else {
  125. node.setAttributeNS(null, name, value);
  126. }
  127. }
  128. function setAttributes(node, attrs) {
  129. var names = Object.keys(attrs), i, name;
  130. for (i = 0, name; (name = names[i]); i++) {
  131. setAttribute(node, name, attrs[name]);
  132. }
  133. }
  134. /**
  135. * Gets or sets raw attributes on a node.
  136. *
  137. * @param {SVGElement} node
  138. * @param {Object} [attrs]
  139. * @param {String} [name]
  140. * @param {String} [value]
  141. *
  142. * @return {String}
  143. */
  144. function attr(node, name, value) {
  145. if (typeof name === 'string') {
  146. if (value !== undefined) {
  147. setAttribute(node, name, value);
  148. } else {
  149. return getAttribute(node, name);
  150. }
  151. } else {
  152. setAttributes(node, name);
  153. }
  154. return node;
  155. }
  156. /**
  157. * Taken from https://github.com/component/classes
  158. *
  159. * Without the component bits.
  160. */
  161. /**
  162. * toString reference.
  163. */
  164. const toString = Object.prototype.toString;
  165. /**
  166. * Wrap `el` in a `ClassList`.
  167. *
  168. * @param {Element} el
  169. * @return {ClassList}
  170. * @api public
  171. */
  172. function classes(el) {
  173. return new ClassList(el);
  174. }
  175. function ClassList(el) {
  176. if (!el || !el.nodeType) {
  177. throw new Error('A DOM element reference is required');
  178. }
  179. this.el = el;
  180. this.list = el.classList;
  181. }
  182. /**
  183. * Add class `name` if not already present.
  184. *
  185. * @param {String} name
  186. * @return {ClassList}
  187. * @api public
  188. */
  189. ClassList.prototype.add = function(name) {
  190. this.list.add(name);
  191. return this;
  192. };
  193. /**
  194. * Remove class `name` when present, or
  195. * pass a regular expression to remove
  196. * any which match.
  197. *
  198. * @param {String|RegExp} name
  199. * @return {ClassList}
  200. * @api public
  201. */
  202. ClassList.prototype.remove = function(name) {
  203. if ('[object RegExp]' == toString.call(name)) {
  204. return this.removeMatching(name);
  205. }
  206. this.list.remove(name);
  207. return this;
  208. };
  209. /**
  210. * Remove all classes matching `re`.
  211. *
  212. * @param {RegExp} re
  213. * @return {ClassList}
  214. * @api private
  215. */
  216. ClassList.prototype.removeMatching = function(re) {
  217. const arr = this.array();
  218. for (let i = 0; i < arr.length; i++) {
  219. if (re.test(arr[i])) {
  220. this.remove(arr[i]);
  221. }
  222. }
  223. return this;
  224. };
  225. /**
  226. * Toggle class `name`, can force state via `force`.
  227. *
  228. * For browsers that support classList, but do not support `force` yet,
  229. * the mistake will be detected and corrected.
  230. *
  231. * @param {String} name
  232. * @param {Boolean} force
  233. * @return {ClassList}
  234. * @api public
  235. */
  236. ClassList.prototype.toggle = function(name, force) {
  237. if ('undefined' !== typeof force) {
  238. if (force !== this.list.toggle(name, force)) {
  239. this.list.toggle(name); // toggle again to correct
  240. }
  241. } else {
  242. this.list.toggle(name);
  243. }
  244. return this;
  245. };
  246. /**
  247. * Return an array of classes.
  248. *
  249. * @return {Array}
  250. * @api public
  251. */
  252. ClassList.prototype.array = function() {
  253. return Array.from(this.list);
  254. };
  255. /**
  256. * Check if class `name` is present.
  257. *
  258. * @param {String} name
  259. * @return {ClassList}
  260. * @api public
  261. */
  262. ClassList.prototype.has =
  263. ClassList.prototype.contains = function(name) {
  264. return this.list.contains(name);
  265. };
  266. function remove(element) {
  267. var parent = element.parentNode;
  268. if (parent) {
  269. parent.removeChild(element);
  270. }
  271. return element;
  272. }
  273. /**
  274. * Clear utility
  275. */
  276. /**
  277. * Removes all children from the given element
  278. *
  279. * @param {DOMElement} element
  280. * @return {DOMElement} the element (for chaining)
  281. */
  282. function clear(element) {
  283. var child;
  284. while ((child = element.firstChild)) {
  285. remove(child);
  286. }
  287. return element;
  288. }
  289. function clone(element) {
  290. return element.cloneNode(true);
  291. }
  292. var ns = {
  293. svg: 'http://www.w3.org/2000/svg'
  294. };
  295. /**
  296. * DOM parsing utility
  297. */
  298. var SVG_START = '<svg xmlns="' + ns.svg + '"';
  299. function parse(svg) {
  300. var unwrap = false;
  301. // ensure we import a valid svg document
  302. if (svg.substring(0, 4) === '<svg') {
  303. if (svg.indexOf(ns.svg) === -1) {
  304. svg = SVG_START + svg.substring(4);
  305. }
  306. } else {
  307. // namespace svg
  308. svg = SVG_START + '>' + svg + '</svg>';
  309. unwrap = true;
  310. }
  311. var parsed = parseDocument(svg);
  312. if (!unwrap) {
  313. return parsed;
  314. }
  315. var fragment = document.createDocumentFragment();
  316. var parent = parsed.firstChild;
  317. while (parent.firstChild) {
  318. fragment.appendChild(parent.firstChild);
  319. }
  320. return fragment;
  321. }
  322. function parseDocument(svg) {
  323. var parser;
  324. // parse
  325. parser = new DOMParser();
  326. parser.async = false;
  327. return parser.parseFromString(svg, 'text/xml');
  328. }
  329. /**
  330. * Create utility for SVG elements
  331. */
  332. /**
  333. * Create a specific type from name or SVG markup.
  334. *
  335. * @param {String} name the name or markup of the element
  336. * @param {Object} [attrs] attributes to set on the element
  337. *
  338. * @returns {SVGElement}
  339. */
  340. function create(name, attrs) {
  341. var element;
  342. if (name.charAt(0) === '<') {
  343. element = parse(name).firstChild;
  344. element = document.importNode(element, true);
  345. } else {
  346. element = document.createElementNS(ns.svg, name);
  347. }
  348. if (attrs) {
  349. attr(element, attrs);
  350. }
  351. return element;
  352. }
  353. /**
  354. * Events handling utility
  355. */
  356. function on(node, event, listener, useCapture) {
  357. node.addEventListener(event, listener, useCapture);
  358. }
  359. function off(node, event, listener, useCapture) {
  360. node.removeEventListener(event, listener, useCapture);
  361. }
  362. /**
  363. * Geometry helpers
  364. */
  365. // fake node used to instantiate svg geometry elements
  366. var node = null;
  367. function getNode() {
  368. if (node === null) {
  369. node = create('svg');
  370. }
  371. return node;
  372. }
  373. function extend(object, props) {
  374. var i, k, keys = Object.keys(props);
  375. for (i = 0; (k = keys[i]); i++) {
  376. object[k] = props[k];
  377. }
  378. return object;
  379. }
  380. function createPoint(x, y) {
  381. var point = getNode().createSVGPoint();
  382. switch (arguments.length) {
  383. case 0:
  384. return point;
  385. case 2:
  386. x = {
  387. x: x,
  388. y: y
  389. };
  390. break;
  391. }
  392. return extend(point, x);
  393. }
  394. /**
  395. * Create matrix via args.
  396. *
  397. * @example
  398. *
  399. * createMatrix({ a: 1, b: 1 });
  400. * createMatrix();
  401. * createMatrix(1, 2, 0, 0, 30, 20);
  402. *
  403. * @return {SVGMatrix}
  404. */
  405. function createMatrix(a, b, c, d, e, f) {
  406. var matrix = getNode().createSVGMatrix();
  407. switch (arguments.length) {
  408. case 0:
  409. return matrix;
  410. case 1:
  411. return extend(matrix, a);
  412. case 6:
  413. return extend(matrix, {
  414. a: a,
  415. b: b,
  416. c: c,
  417. d: d,
  418. e: e,
  419. f: f
  420. });
  421. }
  422. }
  423. function createTransform(matrix) {
  424. if (matrix) {
  425. return getNode().createSVGTransformFromMatrix(matrix);
  426. } else {
  427. return getNode().createSVGTransform();
  428. }
  429. }
  430. /**
  431. * Serialization util
  432. */
  433. var TEXT_ENTITIES = /([&<>]{1})/g;
  434. var ATTR_ENTITIES = /([\n\r"]{1})/g;
  435. var ENTITY_REPLACEMENT = {
  436. '&': '&amp;',
  437. '<': '&lt;',
  438. '>': '&gt;',
  439. '"': '\''
  440. };
  441. function escape(str, pattern) {
  442. function replaceFn(match, entity) {
  443. return ENTITY_REPLACEMENT[entity] || entity;
  444. }
  445. return str.replace(pattern, replaceFn);
  446. }
  447. function serialize(node, output) {
  448. var i, len, attrMap, attrNode, childNodes;
  449. switch (node.nodeType) {
  450. // TEXT
  451. case 3:
  452. // replace special XML characters
  453. output.push(escape(node.textContent, TEXT_ENTITIES));
  454. break;
  455. // ELEMENT
  456. case 1:
  457. output.push('<', node.tagName);
  458. if (node.hasAttributes()) {
  459. attrMap = node.attributes;
  460. for (i = 0, len = attrMap.length; i < len; ++i) {
  461. attrNode = attrMap.item(i);
  462. output.push(' ', attrNode.name, '="', escape(attrNode.value, ATTR_ENTITIES), '"');
  463. }
  464. }
  465. if (node.hasChildNodes()) {
  466. output.push('>');
  467. childNodes = node.childNodes;
  468. for (i = 0, len = childNodes.length; i < len; ++i) {
  469. serialize(childNodes.item(i), output);
  470. }
  471. output.push('</', node.tagName, '>');
  472. } else {
  473. output.push('/>');
  474. }
  475. break;
  476. // COMMENT
  477. case 8:
  478. output.push('<!--', escape(node.nodeValue, TEXT_ENTITIES), '-->');
  479. break;
  480. // CDATA
  481. case 4:
  482. output.push('<![CDATA[', node.nodeValue, ']]>');
  483. break;
  484. default:
  485. throw new Error('unable to handle node ' + node.nodeType);
  486. }
  487. return output;
  488. }
  489. /**
  490. * innerHTML like functionality for SVG elements.
  491. * based on innerSVG (https://code.google.com/p/innersvg)
  492. */
  493. function set(element, svg) {
  494. var parsed = parse(svg);
  495. // clear element contents
  496. clear(element);
  497. if (!svg) {
  498. return;
  499. }
  500. if (!isFragment(parsed)) {
  501. // extract <svg> from parsed document
  502. parsed = parsed.documentElement;
  503. }
  504. var nodes = slice(parsed.childNodes);
  505. // import + append each node
  506. for (var i = 0; i < nodes.length; i++) {
  507. appendTo(nodes[i], element);
  508. }
  509. }
  510. function get(element) {
  511. var child = element.firstChild,
  512. output = [];
  513. while (child) {
  514. serialize(child, output);
  515. child = child.nextSibling;
  516. }
  517. return output.join('');
  518. }
  519. function isFragment(node) {
  520. return node.nodeName === '#document-fragment';
  521. }
  522. function innerSVG(element, svg) {
  523. if (svg !== undefined) {
  524. try {
  525. set(element, svg);
  526. } catch (e) {
  527. throw new Error('error parsing SVG: ' + e.message);
  528. }
  529. return element;
  530. } else {
  531. return get(element);
  532. }
  533. }
  534. function slice(arr) {
  535. return Array.prototype.slice.call(arr);
  536. }
  537. /**
  538. * Selection utilities
  539. */
  540. function select(node, selector) {
  541. return node.querySelector(selector);
  542. }
  543. function selectAll(node, selector) {
  544. var nodes = node.querySelectorAll(selector);
  545. return [].map.call(nodes, function(element) {
  546. return element;
  547. });
  548. }
  549. /**
  550. * prependTo utility
  551. */
  552. /**
  553. * Prepend a node to a target element and return the prepended node.
  554. *
  555. * @param {SVGElement} node
  556. * @param {SVGElement} target
  557. *
  558. * @return {SVGElement} the prepended node
  559. */
  560. function prependTo(node, target) {
  561. return target.insertBefore(ensureImported(node, target), target.firstChild || null);
  562. }
  563. /**
  564. * prepend utility
  565. */
  566. /**
  567. * Prepend a node to a target element
  568. *
  569. * @param {SVGElement} target
  570. * @param {SVGElement} node
  571. *
  572. * @return {SVGElement} the target element
  573. */
  574. function prepend(target, node) {
  575. prependTo(node, target);
  576. return target;
  577. }
  578. /**
  579. * Replace utility
  580. */
  581. function replace(element, replacement) {
  582. element.parentNode.replaceChild(ensureImported(replacement, element), element);
  583. return replacement;
  584. }
  585. /**
  586. * transform accessor utility
  587. */
  588. function wrapMatrix(transformList, transform) {
  589. if (transform instanceof SVGMatrix) {
  590. return transformList.createSVGTransformFromMatrix(transform);
  591. }
  592. return transform;
  593. }
  594. function setTransforms(transformList, transforms) {
  595. var i, t;
  596. transformList.clear();
  597. for (i = 0; (t = transforms[i]); i++) {
  598. transformList.appendItem(wrapMatrix(transformList, t));
  599. }
  600. }
  601. /**
  602. * Get or set the transforms on the given node.
  603. *
  604. * @param {SVGElement} node
  605. * @param {SVGTransform|SVGMatrix|Array<SVGTransform|SVGMatrix>} [transforms]
  606. *
  607. * @return {SVGTransform} the consolidated transform
  608. */
  609. function transform(node, transforms) {
  610. var transformList = node.transform.baseVal;
  611. if (transforms) {
  612. if (!Array.isArray(transforms)) {
  613. transforms = [ transforms ];
  614. }
  615. setTransforms(transformList, transforms);
  616. }
  617. return transformList.consolidate();
  618. }
  619. export { append, appendTo, attr, classes, clear, clone, create, createMatrix, createPoint, createTransform, innerSVG, off, on, prepend, prependTo, remove, replace, select, selectAll, transform };