[
  {
    "path": ".config/karma.conf.cjs",
    "content": "// Karma configuration\nconst karmaCommon = require('./karma.conf.common.cjs')\n\nlet chromeBin = 'ChromeHeadless'\nif (process.platform === 'linux') {\n  // We need to choose either Chrome or Chromium.\n  // Canary is not available on linux.\n  // If we do not find Chromium then we can deduce that\n  // either Chrome is installed or there is no Chrome variant at all,\n  // in which case karma-chrome-launcher will output an error.\n  // If `which` finds nothing it will throw an error.\n  const { execSync } = require('child_process')\n\n  try {\n    if (execSync('which chromium-browser')) chromeBin = 'ChromiumHeadless'\n  } catch (e) {}\n}\n\nmodule.exports = function (config) {\n  config.set(\n    Object.assign(karmaCommon(config), {\n      files: [\n        'spec/RAFPlugin.js',\n        {\n          pattern: 'spec/fixtures/fixture.css',\n          included: false,\n          served: true\n        },\n        {\n          pattern: 'spec/fixtures/pixel.png',\n          included: false,\n          served: true\n        },\n        {\n          pattern: 'src/**/*.js',\n          included: false,\n          served: true,\n          type: 'modules'\n        },\n        {\n          pattern: 'spec/helpers.js',\n          included: false,\n          served: true,\n          type: 'module'\n        },\n        {\n          pattern: 'spec/setupBrowser.js',\n          included: true,\n          type: 'module'\n        },\n        {\n          pattern: 'spec/spec/*/**/*.js',\n          included: true,\n          type: 'module'\n        }\n      ],\n\n      // preprocess matching files before serving them to the browser\n      // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor\n      preprocessors: {\n        'src/**/*.js': ['coverage']\n      },\n\n      // test results reporter to use\n      // possible values: 'dots', 'progress'\n      // available reporters: https://npmjs.org/browse/keyword/karma-reporter\n      reporters: ['progress', 'coverage'],\n      coverageReporter: {\n        // Specify a reporter type.\n        type: 'lcov',\n        dir: 'coverage/',\n        subdir: function (browser) {\n          // normalization process to keep a consistent browser name accross different OS\n          return browser.toLowerCase().split(/[ /-]/)[0] // output the results into: './coverage/firefox/'\n        },\n        instrumenterOptions: {\n          istanbul: {\n            esModules: true\n          }\n        }\n      },\n\n      // start these browsers\n      // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher\n      browsers: [chromeBin, 'FirefoxHeadless']\n    })\n  )\n}\n"
  },
  {
    "path": ".config/karma.conf.common.cjs",
    "content": "// Karma shared configuration\n\nconst os = require('os')\nconst cpuCount = os.cpus().length\n\nmodule.exports = function (config) {\n  return {\n    // base path that will be used to resolve all patterns (eg. files, exclude)\n    basePath: '../',\n\n    // frameworks to use\n    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter\n    frameworks: ['jasmine'],\n\n    // list of files / patterns to load in the browser\n    files: [\n      '.config/pretest.js',\n      'spec/RAFPlugin.js',\n      {\n        pattern: 'spec/fixtures/fixture.css',\n        included: false,\n        served: true\n      },\n      {\n        pattern: 'spec/fixtures/fixture.svg',\n        included: false,\n        served: true\n      },\n      {\n        pattern: 'spec/fixtures/pixel.png',\n        included: false,\n        served: true\n      },\n      'dist/svg.js',\n      'spec/spec/*.js'\n    ],\n\n    proxies: {\n      '/fixtures/': '/base/spec/fixtures/',\n      '/spec/': '/base/spec/'\n    },\n\n    // web server port\n    port: 9876,\n\n    // enable / disable colors in the output (reporters and logs)\n    colors: true,\n\n    // level of logging\n    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG\n    logLevel: config.LOG_INFO,\n\n    // enable / disable watching file and executing tests whenever any file changes\n    autoWatch: false,\n\n    // Continuous Integration mode\n    // if true, Karma captures browsers, runs the tests and exits\n    singleRun: true,\n\n    // Concurrency level\n    // how many browser should be started simultaneous\n    concurrency: cpuCount || Infinity,\n\n    // list of files to exclude\n    exclude: []\n  }\n}\n"
  },
  {
    "path": ".config/karma.conf.saucelabs.cjs",
    "content": "// Karma configuration\n// https://wiki.saucelabs.com/display/DOCS/Platform+Configurator\n\n// TODO: remove dotenv after local test\n// require('dotenv').config()\n\nconst karmaCommon = require('./karma.conf.common.cjs')\n\nconst SauceLabsLaunchers = {\n  /** Real mobile devices are not available\n   *  Your account does not have access to Android devices.\n   *  Please contact sales@saucelabs.com to add this feature to your account. */\n  /* sl_android_chrome: {\n    base: 'SauceLabs',\n    appiumVersion: '1.5.3',\n    deviceName: 'Samsung Galaxy S7 Device',\n    deviceOrientation: 'portrait',\n    browserName: 'Chrome',\n    platformVersion: '6.0',\n    platformName: 'Android'\n  }, */\n  /* sl_android: {\n    base: 'SauceLabs',\n    browserName: 'Android',\n    deviceName: 'Android Emulator',\n    deviceOrientation: 'portrait'\n  }, */\n  SL_firefox_latest: {\n    base: 'SauceLabs',\n    browserName: 'firefox',\n    version: 'latest'\n  },\n  SL_chrome_latest: {\n    base: 'SauceLabs',\n    browserName: 'chrome',\n    version: 'latest'\n  },\n  SL_InternetExplorer: {\n    base: 'SauceLabs',\n    browserName: 'internet explorer',\n    version: '11.0'\n  } /*\n  sl_windows_edge: {\n    base: 'SauceLabs',\n    browserName: 'MicrosoftEdge',\n    version: 'latest',\n    platform: 'Windows 10'\n  },\n  sl_macos_safari: {\n    base: 'SauceLabs',\n    browserName: 'safari',\n    platform: 'macOS 10.13',\n    version: '12.0',\n    recordVideo: true,\n    recordScreenshots: true,\n    screenResolution: '1024x768'\n  } */ /*,\n  sl_macos_iphone: {\n    base: 'SauceLabs',\n    browserName: 'Safari',\n    deviceName: 'iPhone SE Simulator',\n    deviceOrientation: 'portrait',\n    platformVersion: '10.2',\n    platformName: 'iOS'\n  }\n  'SL_Chrome': {\n    base: 'SauceLabs',\n    browserName: 'chrome',\n    version: '48.0',\n    platform: 'Linux'\n  },\n  'SL_Firefox': {\n    base: 'SauceLabs',\n    browserName: 'firefox',\n    version: '50.0',\n    platform: 'Windows 10'\n  },\n  'SL_Safari': {\n    base: 'SauceLabs',\n    browserName: 'safari',\n    platform: 'OS X 10.11',\n    version: '10.0'\n  } */\n}\n\nmodule.exports = function (config) {\n  if (!process.env.SAUCE_USERNAME || !process.env.SAUCE_ACCESS_KEY) {\n    console.error(\n      'SAUCE_USERNAME and SAUCE_ACCESS_KEY must be provided as environment variables.'\n    )\n    console.warn('Aborting Sauce Labs test')\n    process.exit(1)\n  }\n  const settings = Object.assign(karmaCommon(config), {\n    // Concurrency level\n    // how many browser should be started simultaneous\n    // Saucelabs allow up to 5 concurrent sessions on the free open source tier.\n    concurrency: 5,\n\n    // this specifies which plugins karma should load\n    // by default all karma plugins, starting with `karma-` will load\n    // so if you are really puzzled why something isn't working, then comment\n    // out plugins: [] - it's here to make karma load faster\n    // get possible karma plugins by `ls node_modules | grep 'karma-*'`\n    plugins: ['karma-jasmine', 'karma-sauce-launcher'],\n\n    // logLevel: config.LOG_DEBUG,\n\n    // test results reporter to use\n    // possible values: 'dots', 'progress'\n    // available reporters: https://npmjs.org/browse/keyword/karma-reporter\n    reporters: ['dots', 'saucelabs'],\n\n    customLaunchers: SauceLabsLaunchers,\n\n    // start these browsers\n    browsers: Object.keys(SauceLabsLaunchers),\n    sauceLabs: {\n      testName: 'SVG.js Unit Tests'\n      // connectOptions: {\n      //   noSslBumpDomains: \"all\"\n      // },\n      // connectOptions: {\n      //   port: 5757,\n      //   logfile: 'sauce_connect.log'\n      // },\n    }\n\n    // The number of disconnections tolerated.\n    // browserDisconnectTolerance: 0, // well, sometimes it helps to just restart\n    // // How long does Karma wait for a browser to reconnect (in ms).\n    // browserDisconnectTimeout: 10 * 60 * 1000,\n    // // How long will Karma wait for a message from a browser before disconnecting from it (in ms). ~ macOS 10.12 needs more than 7 minutes\n    // browserNoActivityTimeout: 20 * 60 * 1000,\n    // // Timeout for capturing a browser (in ms).  On newer versions of iOS simulator (10.0+), the start up time could be between 3 - 6 minutes.\n    // captureTimeout: 12 * 60 * 1000, // this is useful if saucelabs takes a long time to boot a vm\n\n    // // Required to make Safari on Sauce Labs play nice.\n    // // hostname: 'karmalocal.dev'\n  })\n\n  console.log(settings)\n  config.set(settings)\n}\n"
  },
  {
    "path": ".config/polyfillListIE.js",
    "content": "/* global SVGElement */\n/* eslint no-new-object: \"off\" */\n\nimport CustomEventPolyfill from '@target/custom-event-polyfill/src/index.js6'\nimport children from '../src/polyfills/children.js'\n\n/* IE 11 has no innerHTML on SVGElement */\nimport '../src/polyfills/innerHTML.js'\n\n/* IE 11 has no correct CustomEvent implementation */\nCustomEventPolyfill()\n\n/* IE 11 has no children on SVGElement */\ntry {\n  if (!SVGElement.prototype.children) {\n    Object.defineProperty(SVGElement.prototype, 'children', {\n      get: function () {\n        return children(this)\n      }\n    })\n  }\n} catch (e) {}\n\n/* IE 11 cannot handle getPrototypeOf(not_obj) */\ntry {\n  delete Object.getPrototypeOf('test')\n} catch (e) {\n  var old = Object.getPrototypeOf\n  Object.getPrototypeOf = function (o) {\n    if (typeof o !== 'object') o = new Object(o)\n    return old.call(this, o)\n  }\n}\n"
  },
  {
    "path": ".config/pretest.js",
    "content": "/* global XMLHttpRequest */\n'use strict'\n\nfunction get(uri) {\n  var xhr = new XMLHttpRequest()\n  xhr.open('GET', uri, false)\n  xhr.send()\n  if (xhr.status !== 200) {\n    console.error('SVG.js fixture could not be loaded. Tests will fail.')\n  }\n  return xhr.responseText\n}\n\nfunction main() {\n  var style = document.createElement('style')\n  document.head.appendChild(style)\n  style.sheet.insertRule(get('/fixtures/fixture.css'), 0)\n\n  document.body.innerHTML = get('/fixtures/fixture.svg')\n}\n\nmain()\n"
  },
  {
    "path": ".config/rollup.config.js",
    "content": "import pkg from '../package.json' with { type: 'json' }\nimport babel from '@rollup/plugin-babel'\nimport resolve from '@rollup/plugin-node-resolve'\nimport commonjs from '@rollup/plugin-commonjs'\nimport filesize from 'rollup-plugin-filesize'\nimport terser from '@rollup/plugin-terser'\n\nconst buildDate = Date()\n\nconst headerLong = `/*!\n* ${pkg.name} - ${pkg.description}\n* @version ${pkg.version}\n* ${pkg.homepage}\n*\n* @copyright ${pkg.author}\n* @license ${pkg.license}\n*\n* BUILT: ${buildDate}\n*/;`\n\nconst headerShort = `/*! ${pkg.name} v${pkg.version} ${pkg.license}*/;`\n\nconst getBabelConfig = (node = false) => {\n  let targets = pkg.browserslist\n  const plugins = [\n    [\n      '@babel/transform-runtime',\n      {\n        version: '^7.24.7',\n        regenerator: false,\n        useESModules: true\n      }\n    ],\n    [\n      'polyfill-corejs3',\n      {\n        method: 'usage-pure'\n      }\n    ]\n  ]\n\n  if (node) {\n    targets = 'maintained node versions'\n  }\n\n  return babel({\n    include: 'src/**',\n    babelHelpers: 'runtime',\n    babelrc: false,\n    targets: targets,\n    presets: [\n      [\n        '@babel/preset-env',\n        {\n          modules: false,\n          // useBuildins and plugin-transform-runtime are mutually exclusive\n          // https://github.com/babel/babel/issues/10271#issuecomment-528379505\n          // use babel-polyfills when released\n          useBuiltIns: false,\n          bugfixes: true,\n          loose: true\n        }\n      ]\n    ],\n    plugins\n  })\n}\n\n// When few of these get mangled nothing works anymore\n// We loose literally nothing by let these unmangled\nconst classes = [\n  'A',\n  'ClipPath',\n  'Defs',\n  'Element',\n  'G',\n  'Image',\n  'Marker',\n  'Path',\n  'Polygon',\n  'Rect',\n  'Stop',\n  'Svg',\n  'Text',\n  'Tspan',\n  'Circle',\n  'Container',\n  'Dom',\n  'Ellipse',\n  'Gradient',\n  'Line',\n  'Mask',\n  'Pattern',\n  'Polyline',\n  'Shape',\n  'Style',\n  'Symbol',\n  'TextPath',\n  'Use'\n]\n\nconst config = (node, min, esm = false) => ({\n  input: node || esm ? './src/main.js' : './src/svg.js',\n  output: {\n    file: esm\n      ? './dist/svg.esm.js'\n      : node\n        ? './dist/svg.node.cjs'\n        : min\n          ? './dist/svg.min.js'\n          : './dist/svg.js',\n    format: esm ? 'esm' : node ? 'cjs' : 'iife',\n    name: 'SVG',\n    sourcemap: true,\n    banner: headerLong,\n    // remove Object.freeze\n    freeze: false\n  },\n  treeshake: {\n    // property getter have no sideeffects\n    propertyReadSideEffects: false\n  },\n  plugins: [\n    resolve({ browser: !node }),\n    commonjs(),\n    getBabelConfig(node),\n    filesize(),\n    !min\n      ? {}\n      : terser({\n          mangle: {\n            reserved: classes\n          },\n          output: {\n            preamble: headerShort\n          }\n        })\n  ]\n})\n\n// [node, minified, esm]\nconst modes = [[false], [false, true], [true], [false, false, true]]\n\nexport default modes.map((m) => config(...m))\n"
  },
  {
    "path": ".config/rollup.polyfills.js",
    "content": "import resolve from '@rollup/plugin-node-resolve'\nimport commonjs from '@rollup/plugin-commonjs'\nimport filesize from 'rollup-plugin-filesize'\n\n// We dont need babel. All polyfills are compatible\nconst config = (ie) => ({\n  input: './.config/polyfillListIE.js',\n  output: {\n    file: 'dist/polyfillsIE.js',\n    format: 'iife'\n  },\n  plugins: [\n    resolve({ browser: true }),\n    commonjs(),\n    //terser(),\n    filesize()\n  ]\n})\n\nexport default [true].map(config)\n"
  },
  {
    "path": ".config/rollup.tests.js",
    "content": "import * as pkg from '../package.json'\nimport babel from '@rollup/plugin-babel'\nimport multiEntry from '@rollup/plugin-multi-entry'\nimport resolve from '@rollup/plugin-node-resolve'\nimport commonjs from '@rollup/plugin-commonjs'\n\nconst getBabelConfig = (targets) =>\n  babel({\n    include: ['src/**', 'spec/**/*'],\n    babelHelpers: 'runtime',\n    babelrc: false,\n    presets: [\n      [\n        '@babel/preset-env',\n        {\n          modules: false,\n          targets: targets || pkg.browserslist,\n          // useBuildins and plugin-transform-runtime are mutually exclusive\n          // https://github.com/babel/babel/issues/10271#issuecomment-528379505\n          // use babel-polyfills when released\n          useBuiltIns: false,\n          // corejs: 3,\n          bugfixes: true\n        }\n      ]\n    ],\n    plugins: [\n      [\n        '@babel/plugin-transform-runtime',\n        {\n          corejs: 3,\n          helpers: true,\n          useESModules: true,\n          version: '^7.9.6',\n          regenerator: false\n        }\n      ]\n    ]\n  })\n\nexport default {\n  input: ['spec/setupBrowser.js', 'spec/spec/*/*.js'],\n  output: {\n    file: 'spec/es5TestBundle.js',\n    name: 'SVGTests',\n    format: 'iife'\n  },\n  plugins: [\n    resolve({ browser: true }),\n    commonjs(),\n    getBabelConfig(),\n    multiEntry()\n  ],\n  external: ['@babel/runtime', '@babel/runtime-corejs3']\n}\n"
  },
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"parserOptions\": {\n    \"ecmaVersion\": \"latest\",\n    \"sourceType\": \"module\"\n  },\n  \"env\": {\n    \"browser\": true,\n    \"es6\": true\n  },\n  \"extends\": [\"eslint:recommended\", \"prettier\"],\n  \"rules\": {\n    \"padded-blocks\": \"off\",\n    \"no-unused-vars\": [\n      \"error\",\n      {\n        \"vars\": \"all\",\n        \"args\": \"after-used\",\n        \"ignoreRestSiblings\": true,\n        \"varsIgnorePattern\": \"^_\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing\n\nWhen contributing to this repository, please first discuss the change you wish to make on gitter, or with an issue to increase your chances of getting your pull request merged into the main code base.\n\n## Pull Request Process\n\nWhen you want to make contributions to the project, the process is pretty simple:\n\n1. Discuss in an issue or on gitter what you'd like to change\n2. Fork the repository to make your own local copy\n3. Make a branch in the format of <issue-number>-<friendly-name>. So for example if I made an issue to change the default color, and it was issue 385 (random) on the repo, the branch would be called `385-change-default-color`\n4. Make the changes to the src and perhaps make a playground by duplicating the playgrounds we already have.\n   - When you're done making changes, run `npm run build` to build the code and run the linter\n5. If applicable - please write new tests, we like to keep our code well tesvted 🎉. Run the tests by running `npm test`\n6. Push the code and make a pull request on the main svg.js repo\n7. Enjoy our endless love and gratitude ❤️\n\nSeriously, we love pull requests! So go wild!\n\n## Code of Conduct\n\nWe only have a few simple rules, because we know you wouldn't want to read a whole code of conduct guide now would you? 🤡\n\n- don't say anything you wouldn't want said to you\n- If you think you can help, then we'd love it if you did! 😃\n- Respect everybody\n- NEVER be rude\n\nIf the contributors feel like you're doing anything rude, we have the right to delete/report your posts. So please just treat everybody nicely, and we can all be great friends!\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [fuzzyma]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: Bug Report\nabout: 🐞 Report a bug that you found\n---\n\n# Bug report\n\n> **For support questions, please use [stackoverflow](https://stackoverflow.com/questions/tagged/svg.js) with the tag svg.js or head to our chat over at [gitter](https://gitter.im/svgdotjs/svg.js)**.\n\n## Fiddle\n\nModify [this fiddle](https://jsfiddle.net/Fuzzy/s06mfv5u/) to demonstrate the problem clearly, just fork it and paste the resulting fiddle in your issue. Please make sure this is a **minimal example**, containing only the minimum necessary code to help us troubleshoot your problem.\n\n## Explanation\n\n- What is the behaviour you expect?\n- What is happening instead?\n- What error message are you getting?\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.md",
    "content": "---\nname: Feature Request\nabout: 🎂 Ask nicely for something you reaaaaaaaally want\n---\n\n# Feature request\n\n> **For support questions, please use [stackoverflow](https://stackoverflow.com/questions/tagged/svg.js) with the tag svg.js or head to our chat over at [gitter](https://gitter.im/svgdotjs/svg.js)**.\n\nIf you want to make a feature request, here are some guidelines to make a good one:\n\n- Add example code and usage for feature requests to see how a user would use it\n- Tell us the benefits (everything is allowed)\n- Make a simple use case like the one below. Obviously your feature request shouldn't be so silly. But make it clear to the maintainers what you want added and how you plan to use it 😃\n\n## **Example** Drawing [Smiley the Meme](http://i0.kym-cdn.com/entries/icons/original/000/000/107/smily.jpg)\n\nIt would be cool if SVG.js could be used to easily draw smiley the meme, it would make my life so much easier when I want to have memes in my svg.\n\n### Benefits\n\n- Drawing memes would be quick and easy\n- Memes are funny\n\nI think the syntax to achieve this should be:\n\n```js\nlet meme = draw.meme({ radius: 300, cx: 50, cy: 80, lookAt: [30, 50] })\n```\n\nThen the user could easily change where the smiley is looking with:\n\n```js\nmeme.lookAt(50, 40)\n// OR\nmeme.lookAt([30, 20])\n// OR\nmeme.lookAt(new SVG.Point(30, 20))\n```\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/other.md",
    "content": "---\nname: Other Issue\nabout: 🍺 Something else...\n---\n\n> **For support questions, please use [stackoverflow](https://stackoverflow.com/questions/tagged/svg.js) with the tag svg.js or head to our chat over at [gitter](https://gitter.im/svgdotjs/svg.js), if you have a bug report or feature request, use those templates**.\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n.idea\n.importjs.js\ntest/\nnode_modules/\n.vscode/\ncoverage/\nspec/es5TestBundle.js\n.env\ndist\nindex.html\nindex.js\ntodo.md"
  },
  {
    "path": ".prettierignore",
    "content": ".DS_Store\n.idea\n.importjs.js\ntest/\nnode_modules/\n.vscode/\ncoverage/\nspec/es5TestBundle.js\n.env\ndist\npackage-lock.json\n"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"trailingComma\": \"none\",\n  \"tabWidth\": 2,\n  \"semi\": false,\n  \"singleQuote\": true\n}\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - 'stable'\nservices:\n  - xvfb\naddons:\n  firefox: latest\n  chrome: stable\ninstall:\n  - npm install\n  - npm run build\nscript:\n  - npm test\n  - cat coverage/firefox/lcov.info | node_modules/.bin/coveralls\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\n\nThe document follows the conventions described in [“Keep a CHANGELOG”](http://keepachangelog.com).\n\n====\n\n## [3.2.5]\n\n### Fixed\n\n- fixed import of svg strings with leading comment (#1339)\n\n## [3.2.4]\n\n### Fixed\n\n- fixed dmove for nested svgs (https://github.com/svgdotjs/svg.draggable.js/issues/127)\n\n## [3.2.3]\n\n### Fixed\n\n- fixed import map for father (#1317)\n\n## [3.2.2]\n\n### Fixed\n\n- fixed import map\n\n## [3.2.1]\n\n### Fixed\n\n- skip descriptive elements on rebuild and `toParent()` (#1304)\n- allow 0 as animation duration and delay (#1225)\n- allow nodes that are not imported yet (#1252)\n- only apply color conversion to attributes that can take a color (#1241)\n- support css vars (#1230)\n- fix import of leading, dont write data to dom if not neccessary\n- discontinue use of svgjs:data in favor of data-svg\n- allow + as delemiter in paths (#1165)\n- added `amove()` methods to runner (#1131)\n- fix `css()`, dont throw when screenCtm fails (#968)\n- several type fixes\n\n### Added\n\n- add terminate method to timeline so memory can be freed (#1295)\n- add more events to sugar (#1217)\n\n## [3.2.0]\n\n### Fixed\n\n- improve performance of `point()` by not creating intermediate objects (#1251)\n- fixed references by using single quotes instead of double quotes which lead to errors (#1277)\n- fixed a few spelling errors in comments (#1277)\n- fix several typings (#1253, #1280, #1300)\n\n### Added\n\n- added second parameter `assignNewId` to `clone()` to allow cloning with the same id (#1161)\n\n## [3.1.2]\n\n### Fixed\n\n- fixed several type issues (#1249, #1233, #1231, #1223, #1215)\n- fixed `css()` returning camelCased properties even though they were specified in kebap-case\n- fixed `ObjectBag` loosing information when calling `valueOf()` (Numbers lost its unit)\n- fixed `parents()` (#1235)\n- fixed `nodeOrNew()` to work in object tags as well (#1219)\n\n## [3.1.1]\n\n### Fixed\n\n- fixed typings for tcs 4.2.4 (#1204, #1206, #1203)\n\n## [3.1.0]\n\n### Fixed\n\n- fixed `zoom()` method of runner which was passed a wrong parameter\n- fixed positioning methods of `TSpan` to position them by its bounding box\n- fixed `flip()` method which flips correctly by center by default now and accepts correct arguments\n- fixed a case in `rbox()` where not always all values of the box were updated\n- fixed `getOrigin()` function used by `transform()` so that all origin popssibilities specified in the docs are working (#1085)\n- fixed positioning of text by its baseline when using `amove()`\n- fixed tons of typings in the svg.d.ts file and relaxed type requirements for `put()` and `parent()`\n- fixed adopter when adopting an svg/html string. It had still its wrapper as parentNode attached\n- fixed `put()` which correctly creates an svgjs object from the passed element now before returning\n- fixed `parent()` which correctly returns a Dom instance when parent is the document or document-fragment\n- fixed `add()` which correctly removes namespaces of non-root svg elements now when added to another svg element (#1086)\n- fixed `isRoot()` which correctly returns false, if the element is in a document-fragment\n- fixed `replace()` which works without a parent now, too\n- fixed `defs()` which correctly returns `null` when called on a detached node that is not a root node\n- fixed `reference()` which correctly returns `null` instead of throwing when specifying an attribute which holds a number\n- fixed `flatten()` which correctly flattens now but doesn't accept parameters anymore (makes no sense)\n- fixed `ungroup()` which now inserts the elements at the correct position in the correct order and has position as second argument now\n- fixed `position` for `transform()` to also allow a position of 0\n- fixed `bbox()` of `PathArray` and `PointArray` which returns an instance of `Box` now\n- fixed bug in creation of PointArray which had still references to source arrays in it\n- fixed `PID` controller and makeSetterGetter function\n- fixed `Queue.push` which didnt let you push queue items\n- fixed `Timeline.reverse()` which did exactly the opposite of what you would expect when passing `true/false`\n- fixed cancelAnimationFrame-mock for tests\n- fixed animate when=after to be really \"now\" when no runner is on the timeline\n- fixed animate attr which is also retargetable now\n- fixed internals of ObjectBag which can hold other Morphable values now\n- fixed animate transform which didnt change its origin on retarget for declarative animations\n- fixed path parsing (#1145)\n- fixed `clone()` to return the correct instance (#1154)\n\n### Added\n\n- added second Parameter to `SVG(el, isHTML)` which allows to explicitely create elements in the HTML namespace (#1058)\n- added `unlink()` and `linker()` to hyperlinked elements to remove or access the underling `<a>` element\n- added `wrap()` method to `Dom` which lets you wrap an element by another one\n- added `orient()` method to `Marker`\n- added `options` parameter to `dispatch()` and `fire()` to allow for more special needs\n- added `newLine()` constructor to `Text` to create a tspan marked as new line (#1088)\n- added `Fragment` as a wrapper for document-fragment\n- added position argument for `toParent()`\n- added position argument for `toRoot()`\n- added attr syntax for `data()` method\n- added index and array parameter when passing a function to `List.each()` so that it mostly behaves like map\n- added possibility to pass a transform object to `PointArray.transform()` similar to Point\n- added `with-last` as `when` to `animate` and `schedule` to let an animation start with the start of the last one in the timeline\n- added lots of tests in es6 format\n- added geometry and positioning methods to `A` (#1110)\n\n### Deleted\n\n- deleted undocumented `Matrix.compose()` method which did the same as `new Matrix()` or `Matrix.transform()`\n- deleted undocumented `Path.morph()` and `Path.at()` which was replaced with Morphables in v3\n\n## [3.0.16] - 2019-11-12\n\n### Fixed\n\n- fixed build of polyfills which was broken because of core-js update\n\n## [3.0.15] - 2019-11-08\n\n### Fixed\n\n- allow object input of `when` and `delay` to `animate()`\n\n### Added\n\n- added missing dmove function to runner\n\n## [3.0.14] - 2019-10-31\n\n### Fixed\n\n- hide parser from screen readers (#1023)\n\n### Added\n\n- added transpiled esm bundle for webpack und co and faster import\n\n## [3.0.13] - 2019-06-12\n\n### Fixed\n\n- fixed a bug in Timeline.finish() (#964)\n- fixed registration of classes with custom bundler\n- fixed transform getter (e.g. `el.transform('scale')`)\n- fixed typings (#1004)\n\n## [3.0.12] - 2019-02-19\n\n### Fixed\n\n- fixed package.json which points to correct file for webpack now (browser keyword)\n- fixed typescript types\n\n### Added\n\n- added `ForeignObject` to the core\n\n## [3.0.11] - 2019-01-22\n\n### Fixed\n\n- fixed move commands (x, y, move) of text so that it moves text always by the upper left edge.\n- fixed center commands (cx, cy, center) of text so that it moves text always by the center.\n\n## [3.0.10] - 2019-01-14\n\n### Fixed\n\n- fixed `textPath()`, `path().text()` and `text().path()`\n- fixed `root()` method\n- fixed default values returned by `attr`. Can be missleading if present.\n\n### Added\n\n- added `findOne()` for better performance\n\n## [3.0.9] - 2019-01-14\n\n### Fixed\n\n- renamed `unit()` to `convert()` due to name collision\n\n## [3.0.8] - 2019-01-13\n\n### Fixed\n\n- added back `to()` as `unit()` of `SVG.Number` which was removed accidentally\n\n## [3.0.7] - 2019-01-13\n\n### Fixed\n\n- fixed a bug in `isNulledBox()` and `domContains()`\n- performance changes:\n  - replace `getElementsByTagName` with `querySelector`\n  - make Color check in `attr` more restrictive to prevent expensive `toString`\n\n## [3.0.6] - 2019-01-12\n\n### Fixed\n\n- fixed group move and size commands\n- default font size is not set anymore because it mostly goes against what the user wants\n- fix bug in `font()` which set wrong values\n\n### Added\n\n- `PointArray.transform()` (#945)\n\n## [3.0.5] - 2018-12-12\n\n### Fixed\n\n- fixed `parser` which didnt have all required css rules and not focusable=false\n- group `x(), y(), width(), height(), dx(), dy()` now correctly change the bbox of the group by moving/resizing all children\n- fixed timeline which fired `finished` too early\n- fixed `Animator.frame()`. The passed callback gets the current time now (same as RAF)\n- allow `loop(true)` which is the same as `loop()`\n\n## [3.0.4] - 2018-12-07\n\n### Fixed\n\n- fixed `zoom` which was added correctly and is animatable now\n- fixed `Runner` which merges transformations on the correct frame and in the correct way now\n- fixed condition on which transforms get deleted from an element when animating\n- fixed `Timeline` which executes Runner in the correct order now\n- fixed `Svg` which correctly deletes the defs reference on `clear()`\n\n## [3.0.3] - 2018-12-05\n\n### Fixed\n\n- fixed `Runner` which correctly retains transformations when it is still on a timeline\n- fixed `plot()` method of Runner\n- fixed `timeline()` so that one can set the timeline of an element now\n- fixed `G` and added missing `width/height`\n\n## [3.0.2] - 2018-12-03\n\n### Fixed\n\n- fixed `List` which still didn't have all method names it should have\n- fixed `Runner` which correctly handle retargeted controlled animations now\n- fixed `Runner` so that it is able to be persisted correctly\n- fixed `Color` which correctly handles empty strings now\n- fixed `attr` which correctly handles Objects of other kind now\n- fixed `Morphable` which correctly calculates the done flag now\n\n## [3.0.1] - 2018-12-03\n\n### Fixed\n\n- fixed `insertBefore`, `insertAfter` and `flip` correctly returning `this`\n- fixed `List` which didn't have all method names it should have\n\n## [3.0.0] - 2018-12-01\n\n### Added\n\n- added `text()` method to `SVG.Path` to create a textPath from this path (#705)\n- added `SVG.HTMLNode` which is the object wrapped around html nodes to put something in them\n- added `dispatch()` method on `SVG.Element` which returns the dispatched event for event cancelation (#550)\n- added `isRoot()` on `SVG.Doc` (#809)\n- added a linter during the npm build process\n- added `beziere()` and `steps()` to generate easing functions\n- added `insertAfter()` and `insertBefore`\n- added `SVG.Style` which can be created with `style()` or `fontface()` (#517)\n- added `EventTarget` which is a baseclass to get event abilities (#641)\n- added `Dom` which is a baseclass to get dom abilities\n- added `round()` which lets you round attribues from a node\n- added `ax(), ay(), amove()` to change texts x and y values directly (#787)\n- added possibility to pass attributes into a constructor like: `new SVG.Rect({width:100})`\n- added possibility to pass in additional attribues to element creators e.g. `canvas.rect({x:100})` or `canvas.rect(100, 100, {x:100})` (#796)\n- added `SVG.List` (#645)\n- added `words()` and `element()` to `Dom` because of (#935)\n- added lab, lch, hsl and cmyk color spaces (#790)\n- added `random()` method on `SVG.Color` to create random colors of different kinds (#939)\n\n### Removed\n\n- removed `SVG.Array.split()` function\n- removed workaround for browser bug with stroke-width\n- removed polyfills\n- removed `SVG.Set` in favour of `SVG.List`\n- removed feature to set style with css string (e.g. \"fill:none;display:block;\")\n- removed `loaded()` and `error()` method on `SVG.Image` (#706)\n- removed sub-pixel offset fix\n- removed `SVG.Nested` (#809)\n- removed `show()` from `SVG.A` to avoid name clash (#802)\n- removed `size()` from `SVG.Text` to avoid name clash (#799)\n- removed `native()` function\n- removed `Bare` in favour of `Dom` (#935)\n- removed `bower` support because it is deprecated\n\n### Changed\n\n- gradients now have there corresponding node as type and not only radial/linear\n- `SVG.Path.pointAt()` correctly returns an `SVG.Point` now\n- replaced static reference to `masker` in `SVG.Mask` with the `masker()` method\n- replaced static reference to `clipper` in `SVG.ClipPath` with the `clipper()` method\n- replaced static reference to `targets` in `SVG.Mask` and `SVG.ClipPath` with the `targets()` method\n- moved all regexes to `SVG.regex`\n- new constructor signature for `SVG.Image` and `load()`: `container.image(src, callback) / image.load(src, callback)` (#706)\n- changed `style()` to `css()`. Now accepts array as input and returns object when no argument given (#517)\n- ids are not generated upon creation anymore. Instead they are generated when requested (#559)\n- `SVG.extend()` now expects exactly one module or an array of modules\n- `SVG.Text.path()` now returns an instance of SVG.TextPath (#705)\n- `SVG.Text.path()` does not move all contents to the textPath (#705)\n- `SVG.TextPath` now inherits from `SVG.Text` and can be manipulated the same way (#705)\n- `SVG.Text.textPath()` returns the first textPaths in the text element (#705)\n- renamed `SVG.Stop` constructor `at()` on `SVG.Gradient` to `stop()` (#707)\n- renamed `fill()` method on `SVG.Gradient` and `SVG.Pattern` to `url()` (#708)\n- renamed `previous()` method to `prev()`\n- changed `childNodes` to `children` (same for `firstChild`, `lastChild`, ...) (#710) - changed it back because of performance drop\n- moved `defs()` method from `SVG.Parent` to `SVG.Element`\n- `SVG()` can be called with css selector, node or svg string, now. Without an argument it creates a new `SVG.Doc()` (#646)\n- `add()`, `put()`, `addTo()`, `putIn()` now excepts all arguments accepted by `SVG()`\n- all `SVG.*` objects now can have a node as parameter when constructing\n- `SVG()` does not set a default size anymore\n- default constructor now has an optional `node` argument which is used to consruct the object e.g. `new SVG.Rect(rectNode)`\n- SVG.Elements constructor now tries to import svgjs:data from the node\n- `SVG.on()` calls the listener in the context of the passed object. el.on always uses the svg.js object as context\n- `SVG.on()/off()` and `el.on()/off()` now accepts multiple comma or space separated events e.g. \"mousedown, foo bar\" (#727)\n- Matrices now apply transformations like `scale`, `translate`, etc... by left multiplying them to simplify transformations\n- The way `transform()` works is now completely different. See the docs for more as soon as they are updated\n- merged `SVG.Doc` and `SVG.Nested`, added `isRoot()` on `SVG.Doc()` (#809)\n- The fx module was completely reworked to be faster and less error prone. For more information on how to use it refer to the docs\n- The whole lib is now splitted into es6 modules (#875)\n- `Element.svg()` now can can replace the current node, can export the children of a node and can take an export modifier to change/replace the exported nodes\n- `ungroup()` now breaks off one container and not more\n- `clone()` does not add the clone to the dom anymore\n- `attr()` excepts array now to get multiple values at once\n- `SVG.Text.rebuild()` now takes every font-size into account (#512)\n- `fill()` and `stroke()` return the fill and stroke attribute when called as getter (#789)\n- `parents()` now gives back all parents until the passed one or document\n- `Image` callback passes normal `load` event instead of custom object (#931)\n- renamed `Doc` to `Svg` and `doc()` to `root` (and `toDoc()/toRoot()`) (#932)\n\n## [2.7.1] - 2018-11-30\n\n### Fixed\n\n- CustomEvent-polyfill was not used (needed in IE) (#938)\n\n## [2.7.0] - 2018-11-13\n\n### Fixed\n\n- fixed calling `parent()` on `documentFragment`s children (#927)\n- parser is not focusable anymore (#908)\n- `SVG.Element.click(null)` correctly unbinds the event (#878)\n- fix memory leak (#905)\n\n### Added\n\n- `SVG.Set` now accepts another Set as input (#893)\n- `on()/off()` accepts multiple event names as input (backport from 3.0)\n\n## [2.6.6] - 2018-08-30\n\n### Added\n\n- added global reference to support 'window' in bundlers (#767)\n\n## [2.6.5] - 2018-05-26\n\n### Fixed\n\n- fixed `element.parent()` which sometimes failed when used on detached documents (#759)\n- fixed `SVG.Text.y()` which didnt work correctly with `SVG.Number` (#778)\n- fixed `SVG.Doc.clone()` which throwed an error (#782)\n- fixed `SVG.Mask.clone()` which throwed an error (#782)\n- fixed `SVG.PointArray` having a reference to outside array in some cases (#803)\n- fixed `reference()` which failed when trying to use a reference which does not exist in the attribuets (#840)\n- fixed `animate().attr()` method which doenst work for `d` attribute of paths (#847)\n- fixed problems with `CustomEvent` polyfill in IE11 (#852)\n\n### Added\n\n- added possibility to pass an array of point objects to SVG.PointArray (#803)\n\n## [2.6.4] - 2018-02-07\n\n### Fixed\n\n- fixed memory leak when creating images (#805)\n\n## [2.6.3] - 2017-07-21\n\n### Fixed\n\n- fixed error in parent method when hitting document node (#720)\n\n## [2.6.2] - 2017-06-05\n\n### Added\n\n- added `width()` and `height()` methods to `SVG.FX`\n- added the intended functionality to call animate functions with multiple parameter (#671)\n\n### Changed\n\n- updated Jasmine from 2.5.2 to 2.6.0\n- removed the typeof check in the initialisation of SVG.Matrix\n\n### Fixed\n\n- fixed `SVG.FX.once` so that it add its callback on the last situation instead of the current one\n- fixed `SVG.FX.step` so that the animation doesn't stop if an afterAll callback call animate (#677)\n\n## [2.6.1] - 2017-04-25\n\n### Fixed\n\n- fixed a bug in path parser which made it stop parsing when hitting z command (#665)\n\n## [2.6.1] - 2017-04-25\n\n### Fixed\n\n- fixed a bug in path parser which made it stop parsing when hitting z command (#665)\n\n## [2.6.0] - 2017-04-21\n\n### Added\n\n- added `options` object to `SVG.on()` and `el.on()` (#661)\n\n### Changed\n\n- back to sloppy mode because of problems with plugins (#660)\n\n## [2.5.3] - 2017-04-15\n\n### Added\n\n- added gitter badge in readme\n\n### Fixed\n\n- fixed svg.js.d.ts (#644 #648)\n- fixed bug in `el.flip()` which causes an error when calling flip without any argument\n\n### Removed\n\n- component.json (#652)\n\n## [2.5.2] - 2017-04-11\n\n### Changed\n\n- SVG.js is now running in strict mode\n\n### Fixed\n\n- `clear()` does not remove the parser in svg documents anymore\n- `len` not declared in FX module, making it a global variable (9737e8a)\n- `bbox` not declared in SVG.Box.transform in the Box module (131df0f)\n- `namespace` not declared in the Event module (e89c97e)\n\n## [2.5.1] - 2017-03-27\n\n### Changed\n\n- make svgjs ready to be used on the server\n\n### Fixed\n\n- fixed `SVG.PathArray.parse` that did not correctly parsed flat arrays\n- prevented unnecessary parsing of point or path strings\n\n## [2.5.0] - 2017-03-10\n\n### Added\n\n- added a plot and array method to `SVG.TextPath` (#582)\n- added `clone()` method to `SVG.Array/PointArray/PathArray` (#590)\n- added `font()` method to `SVG.Tspan`\n- added `SVG.Box()`\n- added `transform()` method to boxes\n- added `event()` to `SVG.Element` to retrieve the event that was fired last on the element (#550)\n\n### Changed\n\n- changed CHANGELOG to follow the conventions described in [“Keep a CHANGELOG”](http://keepachangelog.com) (#578)\n- make the method plot a getter when no parameter is passed for `SVG.Polyline`,`SVG.Polygon`, `SVG.Line`, `SVG.Path` (related #547)\n- allow `SVG.PointArray` to be passed flat array\n- change the regexp `SVG.PointArray` use to parse string to allow more flexibility in the way spaces and commas can be used\n- allow `plot` to be called with 4 parameters when animating an `SVG.Line`\n- relative value for `SVG.Number` are now calculated in its `morph` method (related #547)\n- clean up the implementation of the `initAnimation` method of the FX module (#547, #552, #584)\n- deprecated `.tbox()`. `.tbox()` now map to `.rbox()`. If you are using `.tbox()`, you can substitute it with `.rbox()` (#594, #602)\n- all boxes now accept 4 values or an object on creation\n- `el.rbox()` now always returns the right boxes in screen coordinates and has an additional paramater to transform the box into other coordinate systems\n- `font()` method can now be used like `attr()` method (#620)\n- events are now cancelable by default (#550)\n\n### Fixed\n\n- fixed a bug in the plain morphing part of `SVG.MorphObj` that is in the FX module\n- fixed bug which produces an error when removing an event from a node which was formerly removed with a global `off()` (#518)\n- fixed a bug in `size()` for poly elements when their height/width is zero (#505)\n- viewbox now also accepts strings and arrays as constructor arguments\n- `SVG.Array` now accepts a comma seperated string and returns array of numbers instead of strings\n- `SVG.Matrix` now accepts an array as input\n- `SVG.Element.matrix()` now accepts also 6 values\n- `dx()/dy()` now accepts percentage values, too but only if the value on the element is already percentage\n- `flip()` now flips on both axis when no parameter is passed\n- fixed bug with `documentElement.contains()` in IE\n- fixed offset produced by svg parser (#553)\n- fixed a bug with clone which didnt copy over dom data (#621)\n\n## [2.4.0] - 2017-01-14\n\n### Added\n\n- added support for basic path animations (#561)\n\n## [2.3.7] - 2017-01-14\n\n### Added\n\n- added code coverage https://coveralls.io/github/svgdotjs/svg.js (3e614d4)\n- added `npm run test:quick` which aim at being fast rather than correct - great for git hooks (981ce24)\n\n### Changed\n\n- moved project to [svgdotjs](https://github.com/svgdotjs)\n- made matrixify work with transformation chain separated by commas (#543)\n- updated dev dependencies; request and gulp-chmod - `npm run build` now requires nodejs 4.x+\n\n### Fixed\n\n- fixed `SVG.Matrix.skew()` (#545)\n- fixed broken animations, if using polyfills for es6/7 proposals (#504)\n- fixed and improved `SVG.FX.dequeue()` (#546)\n- fixed an error in `SVG.FX.step`, if custom properties is added to `Array.prototype` (#549)\n\n## [2.3.6] - 2016-10-21\n\n### Changed\n\n- make SVG.FX.loop modify the last situation instead of the current one (#532)\n\n### Fixed\n\n- fixed leading and trailing space in SVG.PointArray would return NaN for some points (695f26a) (#529)\n- fixed test of `SVG.FX.afterAll` (#534)\n- fixed `SVG.FX.speed()` (#536)\n\n## [2.3.5] - 2016-10-13\n\n### Added\n\n- added automated unit tests via [Travis](https://travis-ci.org/svgdotjs/svg.js) (#527)\n- added `npm run build` to build a new version of SVG.js without requiring gulp to be globally installed\n\n### Changed\n\n- calling `fill()`, `stroke()` without an argument is now a nop\n- Polygon now accepts comma less points to achieve parity with Adobe Illustrator (#529)\n- updated dependencies\n\n## [2.3.4] - 2016-08-04\n\n### Changed\n\n- reworked parent module for speed improvemenents\n- reworked `filterSVGElements` utility to use a for loop instead of the native filter function\n\n## [2.3.3] - 2016-08-02\n\n### Added\n\n- add error callback on image loading (#508)\n\n### Fixed\n\n- fixed bug when getting bbox of text elements which are not in the dom (#514)\n- fixed bug when getting bbox of element which is hidden with css (#516)\n\n## [2.3.2] - 2016-06-21\n\n### Added\n\n- added specs for `SVG.ViewBox`\n- added `parent` parameter for `clone()`\n- added spec for mentioned issue\n\n### Fixed\n\n- fixed string parsing in viewbox (#483)\n- fixed bbox when element is not in the dom (#480)\n- fixed line constructor which doesn't work with Array as input (#487)\n- fixed problem in IE with `document.contains` (#490) related to (#480)\n- fixed `undo` when undoing transformations (#494)\n\n## [2.3.1] - 2016-05-05\n\n### Added\n\n- added typings for svg.js (#470)\n\n### Fixed\n\n- fixed `SVG.morph()` (#473)\n- fixed parser error (#471)\n- fixed bug in `SVG.Color` with new fx\n- fixed `radius()` for circles when animating and other related code (#477)\n- fixed bug where `stop(true)` throws an error when element is not animated (#475)\n- fixed bug in `add()` when altering svgs with whitespaces\n- fixed bug in `SVG.Doc().create` where size was set to 100% even if size was already specified\n- fixed bug in `parse()` from `SVG.PathArray` which does not correctly handled `S` and `T` (#485)\n\n## [2.3.0] - 2016-03-30\n\n### Added\n\n- added `SVG.Point` which serves as Wrapper to the native `SVGPoint` (#437)\n- added `element.point(x,y)` which transforms a point from screen coordinates to the elements space (#403)\n- added `element.is()` which helps to check for the object instance faster (instanceof check)\n- added more fx specs\n\n### Changed\n\n- textpath now is a parent element, the lines method of text will return the tspans inside the textpath (#450)\n- fx module rewritten to support animation chaining and several other stuff (see docs)\n\n### Fixed\n\n- fixed `svgjs:data` attribute which was not set properly in all browsers (#428)\n- fixed `isNumber` and `numberAndUnit` regex (#405)\n- fixed error where a parent node is not found when loading an image but the canvas was cleared (#447)\n- fixed absolute transformation animations (not perfect but better)\n- fixed event listeners which didnt work correctly when identic funtions used\n\n## [2.2.5] - 2015-12-29\n\n### Added\n\n- added check for existence of node (#431)\n\n### Changed\n\n- `group.move()` now allows string numbers as input (#433)\n- `matrixify()` will not apply the calculated matrix to the node anymore\n\n## [2.2.4] - 2015-12-12\n\n### Fixed\n\n- fixed `transform()` which returns the matrix values (a-f) now, too (#423)\n- double newlines (\\n\\n) are correctly handled as blank line from `text()`\n- fixed use of scrollX vs pageXOffset in `rbox()` (#425)\n- fixed target array in mask and clip which was removed instead of reinitialized (#429)\n\n## [2.2.3] - 2015-11-30\n\n### Fixed\n\n- fixed null check in image (see 2.2.2)\n- fixed bug related to the new path parser (see 2.2.2)\n- fixed amd loader (#412)\n\n## [2.2.2] - 2015-11-28\n\n### Added\n\n- added null check in image onload callback (#415)\n\n### Changed\n\n- documentation rework (#407) [thanks @snowyplover]\n\n### Fixed\n\n- fixed leading point bug in path parsing (#416)\n\n## [2.2.1] - 2015-11-18\n\n### Added\n\n- added workaround for `SvgPathSeg` which is removed in Chrome 48 (#409)\n- added `gbox()` to group to get bbox with translation included (#405)\n\n### Fixed\n\n- fixed dom data which was not cleaned up properly (#398)\n\n## [2.2.0] - 2015-11-06\n\n### Added\n\n- added `ungroup()/flatten()` (#238), `toParent()` and `toDoc()`\n- added UMD-Wrapper with possibility to pass custom window object (#352)\n- added `morph()` method for paths via plugin [svg.pathmorphing.js](https://github.com/Fuzzyma/svg.pathmorphing.js)\n- added support for css selectors within the `parent()` method\n- added `parents()` method to get an array of all parenting elements\n\n### Changed\n\n- svgjs now saves crucial data in the dom before export and restores them when element is adopted\n\n### Fixed\n\n- fixed pattern and gradient animation (#385)\n- fixed mask animation in Firefox (#287)\n- fixed return value of `text()` after import/clone (#393)\n\n## [2.1.1] - 2015-10-03\n\n### Added\n\n- added custom context binding to event callback (default is the element the event is bound to)\n\n## [2.1.0] - 2015-09-20\n\n### Added\n\n- added transform to pattern and gradients (#383)\n\n### Fixed\n\n- fixed clone of textnodes (#369)\n- fixed transformlists in IE (#372)\n- fixed typo that leads to broken gradients (#370)\n- fixed animate radius for circles (#367)\n\n## [2.0.2] - 2015-06-22\n\n### Fixed\n\n- Fixed zoom consideration in circle and ellipse\n\n## [2.0.1] - 2015-06-21\n\n### Added\n\n- added possibility to remove all events from a certain namespace\n\n### Fixed\n\n- fixed bug with `doc()` which always should return root svg\n- fixed bug in `SVG.FX` when animating with `plot()`\n\n### Removed\n\n- removed target reference from use which caused bugs in `dmove()` and `use()` with external file\n- removed scale consideration in `move()` duo to incompatibilities with other move-functions e.g. in `SVG.PointArray`\n\n## [2.0.0] - 2015-06-11\n\n### Added\n\n- implemented an SVG adoption system to be able to manipulate existing SVG's not created with svg.js\n- added polyfill for IE9 and IE10 custom events [thanks @Fuzzyma]\n- added DOM query selector with the `select()` method globally or on parent elements\n- added the intentionally neglected `SVG.Circle` element\n- added `rx()` and `ry()` to `SVG.Rect`, `SVG.Circle`, `SVG.Ellispe` and `SVG.FX`\n- added support to clone manually built text elements\n- added `svg.wiml.js` plugin to plugins list\n- added `ctm()` method to for matrix-centric transformations\n- added `morph()` method to `SVG.Matrix`\n- added support for new matrix system to `SVG.FX`\n- added `native()` method to elements and matrix to get to the native api\n- added `untransform()` method to remove all transformations\n- added raw svg import functionality with the `svg()` method\n- added coding style description to README\n- added reverse functionality for animations\n- documented the `situation` object in `SVG.FX`\n- added distinction between relative and absolute matrix transformations\n- implemented the `element()` method using the `SVG.Bare` class to create elements that are not described by SVG.js\n- added `w` and `h` properties as shorthand for `width` and `height` to `SVG.BBox`\n- added `SVG.TBox` to get a bounding box that is affected by transformation values\n- added event-based or complete detaching of event listeners in `off()` method\n\n### Changed\n\n- changed `parent` reference on elements to `parent()` method\n- using `CustomEvent` instead of `Event` to be able to fire events with a `detail` object [thanks @Fuzzyma]\n- renamed `SVG.TSpan` class to `SVG.Tspan` to play nice with the adoption system\n- completely reworked `clone()` method to use the adoption system\n- completely reworked transformations to be chainable and more true to their nature\n- changed `lines` reference to `lines()` on `SVG.Text`\n- changed `track` reference to `track()` on `SVG.Text`\n- changed `textPath` reference to `textPath()` on `SVG.Text`\n- changed `array` reference to `array()` method on `SVG.Polyline`, `SVG.Polygon` and `SVG.Path`\n- reworked sup-pixel offset implementation to be more compact\n- switched from Ruby's `rake` to Node's `gulp` for building [thanks to Alex Ewerlöf]\n- changed `to()` method to `at()` method in `SVG.FX`\n- renamed `SVG.SetFX` to `SVG.FX.Set`\n- reworked `SVG.Number` to return new instances with calculations rather than itself\n- reworked animatable matrix rotations\n- removed `SVG.Symbol` but kept the `symbol()` method using the new `element()` method\n\n### Fixed\n\n- fixed bug in `radius()` method when `y` value equals `0`\n- fixed a bug where events are not detached properly\n\n## [1.0.0-rc.9] - 2014-06-17\n\n### Added\n\n- added `SVG.Marker`\n- added `SVG.Symbol`\n- added `first()` and `last()` methods to `SVG.Set`\n- added `length()` method to `SVG.Text` and `SVG.TSpan` to calculate total text length\n- added `reference()` method to get referenced elements from a given attribute value\n\n### Changed\n\n- `SVG.get()` will now also fetch elements with a `xlink:href=\"#elementId\"` or `url(#elementId)` value given\n\n### Fixed\n\n- fixed infinite loop in viewbox when element has a percentage width / height [thanks @shabegger]\n\n## [1.0.0-rc.8] - 2014-06-12\n\n### Fixed\n\n- fixed bug in `SVG.off`\n- fixed offset by window scroll position in `rbox()` [thanks @bryhoyt]\n\n## [1.0.0-rc.7] - 2014-06-11\n\n### Added\n\n- added `classes()`, `hasClass()`, `addClass()`, `removeClass()` and `toggleClass()` [thanks @pklingem]\n\n### Changed\n\n- binding events listeners to svg.js instance\n- calling `after()` when calling `stop(true)` (fulfill flag) [thanks @vird]\n- text element fires `rebuild` event whenever the `rebuild()` method is called\n\n### Fixed\n\n- fixed a bug where `Element#style()` would not save empty values in IE11 [thanks @Shtong]\n- fixed `SVG is not defined error` [thanks @anvaka]\n- fixed a bug in `move()`on text elements with a string based value\n- fix for `text()` method on text element when acting as getter [thanks @Lochemage]\n- fix in `style()` method with a css string [thanks @TobiasHeckel]\n\n## [1.0.0-rc.6] - 2014-03-03\n\n### Added\n\n- added `leading()` method to `SVG.FX`\n- added `reverse()` method to `SVG.Array` (and thereby also to `SVG.PointArray` and `SVG.PathArray`)\n- added `fulfill` option to `stop()` method in `SVG.FX` to finalise animations\n- added more output values to `bbox()` and `rbox()` methods\n\n### Changed\n\n- fine-tuned text element positioning\n- calling `at()` method directly on morphable svg.js instances in `SVG.FX` module\n- moved most `_private` methods to local named functions\n- moved helpers to a separate file\n\n### Fixed\n\n- fixed a bug in text `dy()` method\n\n### Removed\n\n- removed internal representation for `style`\n\n## [1.0.0-rc.5] - 2014-02-14\n\n### Added\n\n- added `plain()` method to `SVG.Text` element to add plain text content, without tspans\n- added `plain()` method to parent elements to create a text element without tspans\n- added `build()` to enable/disable build mode\n\n### Changed\n\n- updated `SVG.TSpan` to accept nested tspan elements, not unlike the `text()` method in `SVG.Text`\n- removed the `relative()` method in favour of `dx()`, `dy()` and `dmove()`\n- switched form objects to arrays in `SVG.PathArray` for compatibility with other libraries and better performance on parsing and rendering (up-to 48% faster than 1.0.0-rc.4)\n- refined docs on element-specific methods and `SVG.PathArray` structure\n- reworked `leading()` implementation to be more font-size \"aware\"\n- refactored the `attr` method on `SVG.Element`\n- applied Helvetica as default font\n- building `SVG.FX` class with `SVG.invent()` function\n\n### Removed\n\n- removed verbose style application to tspans\n\n## [1.0.0-rc.4] - 2014-02-04\n\n### Added\n\n- automatic pattern creation by passing an image url or instance as `fill` attribute on elements\n- added `loaded()` method to image tag\n- added `pointAt()` method to `SVG.Path`, wrapping the native `getPointAtLength()`\n\n### Changed\n\n- switched to `MAJOR`.`MINOR`.`PATCH` versioning format to play nice with package managers\n- made svg.pattern.js part of the core library\n- moved `length()` method to sugar module\n\n### Fixed\n\n- fix in `animate('=').to()`\n- fix for arcs in patharray `toString()` method [thanks @dotnetCarpenter]\n\n## [v1.0rc3] - 2014-02-03\n\n### Added\n\n- added the `SVG.invent` function to ease invention of new elements\n- added second values for `animate('2s')`\n- added `length()` mehtod to path, wrapping the native `getTotalLength()`\n\n### Changed\n\n- using `SVG.invent` to generate core shapes as well for leaner code\n\n### Fixed\n\n- fix for html-less documents\n- fix for arcs in patharray `toString()` method\n\n## [v1.0rc2] - 2014-02-01\n\n### Added\n\n- added `index()` method to `SVG.Parent` and `SVG.Set`\n- added `morph()` and `at()` methods to `SVG.Number` for unit morphing\n\n### Changed\n\n- modified `cx()` and `cy()` methods on elements with native `x`, `y`, `width` and `height` attributes for better performance\n\n## [v1.0rc1] - 2014-01-31\n\n### Added\n\n- added `SVG.PathArray` for real path transformations\n- added `bbox()` method to `SVG.Set`\n- added `relative()` method for moves relative to the current position\n- added `morph()` and `at()` methods to `SVG.Color` for color morphing\n\n### Changed\n\n- enabled proportional resizing on `size()` method with `null` for either `width` or `height` values\n- moved data module to separate file\n- `data()` method now accepts object for for multiple key / value assignments\n\n### Removed\n\n- removed `unbiased` system for paths\n\n## [v0.38] - 2014-01-28\n\n### Added\n\n- added `loop()` method to `SVG.FX`\n\n### Changed\n\n- switched from `setInterval` to `requestAnimFrame` for animations\n\n## [v0.37] - 2014-01-26\n\n### Added\n\n- added `get()` to `SVG.Set`\n\n### Changed\n\n- moved `SVG.PointArray` to a separate file\n\n## [v0.36] - 2014-01-25\n\n### Added\n\n- added `linkTo()`, `addTo()` and `putIn()` methods on `SVG.Element`\n\n### Changed\n\n- provided more detailed documentation on parent elements\n\n### Fixed\n\n## [v0.35] - 2014-01-23\n\n### Added\n\n- added `SVG.A` element with the `link()`\n\n## [v0.34] - 2014-01-23\n\n### Added\n\n- added `pause()` and `play()` to `SVG.FX`\n\n### Changed\n\n- storing animation values in `situation` object\n\n## [v0.33] - 2014-01-22\n\n### Added\n\n- added `has()` method to `SVG.Set`\n- added `width()` and `height()` as setter and getter methods on all shapes\n- added `replace()` method to elements\n- added `radius()` method to `SVG.Rect` and `SVG.Ellipse`\n- added reference to parent node in defs\n\n### Changed\n\n- moved sub-pixel offset fix to be an optional method (e.g. `SVG('drawing').fixSubPixelOffset()`)\n- merged plotable.js and path.js\n\n## [v0.32]\n\n### Added\n\n- added library to [cdnjs](http://cdnjs.com)\n\n<!-- Headings above link to the releases listed here -->\n\n[3.2.5]: https://github.com/svgdotjs/svg.js/releases/tag/3.2.5\n[3.2.4]: https://github.com/svgdotjs/svg.js/releases/tag/3.2.4\n[3.2.3]: https://github.com/svgdotjs/svg.js/releases/tag/3.2.3\n[3.2.2]: https://github.com/svgdotjs/svg.js/releases/tag/3.2.2\n[3.2.1]: https://github.com/svgdotjs/svg.js/releases/tag/3.2.1\n[3.2.0]: https://github.com/svgdotjs/svg.js/releases/tag/3.2.0\n[3.1.2]: https://github.com/svgdotjs/svg.js/releases/tag/3.1.2\n[3.1.1]: https://github.com/svgdotjs/svg.js/releases/tag/3.1.1\n[3.1.0]: https://github.com/svgdotjs/svg.js/releases/tag/3.1.0\n[3.0.16]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.16\n[3.0.15]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.15\n[3.0.14]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.14\n[3.0.13]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.13\n[3.0.12]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.12\n[3.0.11]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.11\n[3.0.10]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.10\n[3.0.9]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.9\n[3.0.8]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.8\n[3.0.7]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.7\n[3.0.6]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.6\n[3.0.5]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.5\n[3.0.4]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.4\n[3.0.3]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.3\n[3.0.2]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.2\n[3.0.1]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.1\n[3.0.0]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.0\n[2.7.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.7.1\n[2.7.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.7.0\n[2.6.6]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.6\n[2.6.5]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.5\n[2.6.4]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.4\n[2.6.3]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.3\n[2.6.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.2\n[2.6.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.1\n[2.6.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.0\n[2.5.3]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.3\n[2.5.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.2\n[2.5.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.1\n[2.5.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.0\n[2.4.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.4.0\n[2.3.7]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.7\n[2.3.6]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.6\n[2.3.5]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.5\n[2.3.4]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.4\n[2.3.3]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.3\n[2.3.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.2\n[2.3.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.1\n[2.3.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.0\n[2.2.5]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.5\n[2.2.4]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.4\n[2.2.3]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.3\n[2.2.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.2\n[2.2.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.1\n[2.2.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.0\n[2.1.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.1.1\n[2.1.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.1.0\n[2.0.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.0.2\n[2.0.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.0.1\n[2.0.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.0.0\n[1.0.0-rc.9]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.9\n[1.0.0-rc.8]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.8\n[1.0.0-rc.7]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.7\n[1.0.0-rc.6]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.6\n[1.0.0-rc.5]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.5\n[1.0.0-rc.4]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.4\n[v1.0rc3]: https://github.com/svgdotjs/svg.js/releases/tag/1.0rc3\n[v1.0rc2]: https://github.com/svgdotjs/svg.js/releases/tag/1.0rc2\n[v1.0rc1]: https://github.com/svgdotjs/svg.js/releases/tag/1.0rc1\n[v0.38]: https://github.com/svgdotjs/svg.js/releases/tag/0.38\n[v0.37]: https://github.com/svgdotjs/svg.js/releases/tag/0.37\n[v0.36]: https://github.com/svgdotjs/svg.js/releases/tag/0.36\n[v0.35]: https://github.com/svgdotjs/svg.js/releases/tag/0.35\n[v0.34]: https://github.com/svgdotjs/svg.js/releases/tag/0.34\n[v0.33]: https://github.com/svgdotjs/svg.js/releases/tag/0.33\n[v0.32]: https://github.com/svgdotjs/svg.js/releases/tag/0.32\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright (c) 2012-2018 Wout Fierens\nhttps://svgdotjs.github.io/\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# SVG.js\n\n[![Build Status](https://travis-ci.org/svgdotjs/svg.js.svg?branch=master)](https://travis-ci.org/svgdotjs/svg.js)\n[![Coverage Status](https://coveralls.io/repos/github/svgdotjs/svg.js/badge.svg?branch=master)](https://coveralls.io/github/svgdotjs/svg.js?branch=master)\n[![Cdnjs](https://img.shields.io/cdnjs/v/svg.js.svg)](https://cdnjs.com/libraries/svg.js)\n[![jsdelivr](https://badgen.net/jsdelivr/v/npm/@svgdotjs/svg.js)](https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js)\n[![Join the chat at https://gitter.im/svgdotjs/svg.js](https://badges.gitter.im/svgdotjs/svg.js.svg)](https://gitter.im/svgdotjs/svg.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)\n[![Twitter](https://img.shields.io/badge/Twitter-@svg__js-green.svg)](https://twitter.com/svg_js)\n\n**A lightweight library for manipulating and animating SVG, without any dependencies.**\n\nSVG.js is licensed under the terms of the MIT License.\n\n## Installation\n\n#### Npm:\n\n`npm install @svgdotjs/svg.js`\n\n#### Yarn:\n\n`yarn add @svgdotjs/svg.js`\n\n#### CDNs:\n\n[https://cdnjs.com/libraries/svg.js](https://cdnjs.com/libraries/svg.js)  \n[https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js](https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js)  \n[https://unpkg.com/@svgdotjs/svg.js](https://unpkg.com/@svgdotjs/svg.js)\n\n## Documentation\n\nCheck [svgjs.dev](https://svgjs.dev/docs/3.0/) to learn more.\n\n[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=ulima.ums%40googlemail.com&lc=US&item_name=SVG.JS&currency_code=EUR&bn=PP-DonationsBF%3Abtn_donate_74x21.png%3ANonHostedGuest) or [![Sponsor](https://img.shields.io/badge/Sponsor-svg.js-green.svg)](https://github.com/sponsors/Fuzzyma)\n"
  },
  {
    "path": "bench/runner.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <title>SVG.js benchmarker</title>\n    <style>\n      @import url('https://fonts.googleapis.com/css?family=Inconsolata');\n      body {\n        font-family: 'Inconsolata', 'Menlo', monospace;\n        font-weight: 300;\n        color: #999;\n        font-size: 14px;\n      }\n      svg {\n        width: 2px;\n        height: 2px;\n        overflow: hidden;\n        position: fixed;\n        right: 0;\n      }\n      span.name {\n        color: #b7cd3e;\n      }\n      span.ms {\n        color: #ff0066;\n      }\n      h1 {\n        font-size: 1.2em;\n      }\n      .test {\n        text-indent: 1em;\n      }\n      .skipped {\n        color: #fbcb72;\n      }\n    </style>\n  </head>\n  <body>\n    <div id=\"draw\"></div>\n    <svg\n      id=\"native\"\n      width=\"100\"\n      height=\"100\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      version=\"1.1\"\n      xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n      xmlns:svgjs=\"http://svgjs.dev/svgjs\"\n    ></svg>\n    <script src=\"../dist/svg.js\"></script>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js\"></script>\n    <script src=\"svg.bench.js\"></script>\n    <!--<script src=\"tests/10000-each.js\"></script> -->\n    <script src=\"tests/10000-rects.js\"></script>\n    <!-- <script src=\"tests/10000-circles.js\"></script>\n  <script src=\"tests/10000-paths.js\"></script>\n  <script src=\"tests/10000-boxes.js\"></script>\n  <script src=\"tests/10000-pointArray-bbox.js\"></script>\n  <script src=\"tests/10000-accesses.js\"></script>\n  <script src=\"tests/10000-transform.js\"></script> -->\n    <script>\n      SVG.bench.run()\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "bench/svg.bench.js",
    "content": "/* global Snap */\n\n;(function () {\n  SVG.bench = {\n    // Initalize test store\n    _chain: [],\n    _before: function () {},\n    _after: function () {},\n    draw: SVG().addTo('#draw'),\n    snap: Snap(100, 100),\n    raw: document.getElementById('native'),\n\n    // Add descriptor\n    describe: function (name, closure) {\n      this._chain.push({\n        name: name,\n        run: closure\n      })\n\n      return this\n    },\n\n    // Add test\n    test: function (name, run) {\n      // run test\n      var start = new Date().getTime()\n      run()\n      this.write(name, new Date().getTime() - start)\n\n      // clear everything\n      this.clear()\n    },\n\n    // Skip test\n    skip: function (name, run) {\n      this.write(name, false)\n    },\n\n    // Run tests\n    run: function () {\n      this.pad()\n\n      for (var h, i = 0, il = this._chain.length; i < il; i++) {\n        h = document.createElement('h1')\n        h.innerHTML = this._chain[i].name\n        this.pad().appendChild(h)\n        this._chain[i].run(this)\n      }\n    },\n\n    // Write result\n    write: function (name, ms) {\n      var test = document.createElement('div')\n\n      if (typeof ms === 'number') {\n        test.className = 'test'\n        test.innerHTML =\n          '<span class=\"name\">' +\n          name +\n          '</span> completed in <span class=\"ms\">' +\n          ms +\n          'ms</span>'\n      } else {\n        test.className = 'test skipped'\n        test.innerHTML = name + ' (skipped)'\n      }\n\n      this.pad().appendChild(test)\n\n      return this\n    },\n\n    // Reference writable element\n    pad: function () {\n      var pad = document.getElementById('pad')\n\n      if (!pad) {\n        pad = document.createElement('div')\n        document.getElementsByTagName('body')[0].appendChild(pad)\n      }\n\n      return pad\n    },\n\n    // Clear canvasses\n    clear: function () {\n      while (this.raw.hasChildNodes()) {\n        this.raw.removeChild(this.raw.lastChild)\n      }\n      this.draw.clear()\n      this.snap.clear()\n    }\n  }\n})()\n"
  },
  {
    "path": "bench/tests/10000-accesses.js",
    "content": "SVG.bench.describe(\n  'Access a dom attributes vs dom properties vs object properties',\n  function (bench) {\n    bench.test('using an object', function () {\n      var sum = 0\n      var obj = { x: '30' }\n      for (var i = 0; i < 1000000; i++) {\n        sum += obj.x * i\n      }\n      console.log(sum)\n    })\n\n    bench.test('figure out what the overhead is', function () {\n      var obj = bench.draw.rect(100, 100).move(0, 0)\n    })\n\n    bench.test('using dom attributes', function () {\n      var sum = 0\n      var obj = bench.draw.rect(100, 100).move(0, 0)\n      var node = obj.node\n      for (var i = 0; i < 1000000; i++) {\n        sum += node.getAttribute('x') * i\n      }\n      console.log(sum, node.getAttribute('x'))\n    })\n\n    bench.test('using dom properties', function () {\n      var sum = 0\n      var obj = bench.draw.rect(100, 100).move(0, 0)\n      var node = obj.node\n      for (var i = 0; i < 1000000; i++) {\n        sum += node.x.baseVal * i\n      }\n      console.log(sum, node.x)\n    })\n  }\n)\n"
  },
  {
    "path": "bench/tests/10000-boxes.js",
    "content": "SVG.bench.describe('Generate 100000 bbox', function (bench) {\n  var rect = bench.draw.rect(100, 100)\n\n  bench.test('using SVG.js v3.0.0', function () {\n    for (var i = 0; i < 100000; i++) rect.bbox()\n  })\n  //bench.test('using vanilla js', function() {\n  //  var node = rect.node\n  //  for (var i = 0; i < 10000; i++) {\n  //    node.getBBox()\n  //  }\n  //})\n  //bench.test('using Snap.svg v0.5.1', function() {\n  //  for (var i = 0; i < 10000; i++)\n  //    bench.snap.rect(50, 50, 100, 100)\n  //})\n})\n\nSVG.bench.describe('Generate 100000 rbox', function (bench) {\n  var rect = bench.draw.rect(100, 100)\n\n  bench.test('using SVG.js v3.0.0', function () {\n    for (var i = 0; i < 100000; i++) rect.bbox()\n  })\n  //bench.test('using vanilla js', function() {\n  //  var node = rect.node\n  //  for (var i = 0; i < 10000; i++) {\n  //    node.getBoundingClientRect()\n  //  }\n  //})\n  //bench.test('using Snap.svg v0.5.1', function() {\n  //  for (var i = 0; i < 10000; i++)\n  //    bench.snap.rect(50, 50, 100, 100)\n  //})\n})\nSVG.bench.describe('Generate 100000 viewbox', function (bench) {\n  var nested = bench.draw.nested().viewbox(10, 10, 100, 100)\n\n  bench.test('using SVG.js v3.0.0', function () {\n    for (var i = 0; i < 100000; i++) nested.viewbox()\n  })\n  //bench.test('using vanilla js', function() {\n  //  var node = rect.node\n  //  for (var i = 0; i < 10000; i++) {\n  //    node.getAttribute('viewBox')\n  //  }\n  //})\n  //bench.test('using Snap.svg v0.5.1', function() {\n  //  for (var i = 0; i < 10000; i++)\n  //    bench.snap.rect(50, 50, 100, 100)\n  //})\n})\n"
  },
  {
    "path": "bench/tests/10000-circles.js",
    "content": "SVG.bench.describe('Generate 10000 circles', function (bench) {\n  bench.test('using SVG.js v2.5.3', function () {\n    for (var i = 0; i < 10000; i++) bench.draw.circle(100, 100)\n  })\n  bench.test('using vanilla js', function () {\n    for (var i = 0; i < 10000; i++) {\n      var circle = document.createElementNS(SVG.ns, 'circle')\n      circle.setAttributeNS(null, 'rx', 50)\n      circle.setAttributeNS(null, 'ry', 50)\n      bench.raw.appendChild(circle)\n    }\n  })\n  bench.test('using Snap.svg v0.5.1', function () {\n    for (var i = 0; i < 10000; i++) bench.snap.circle(50, 50, 100, 100)\n  })\n})\n\nSVG.bench.describe('Generate 10000 circles with fill', function (bench) {\n  bench.test('using SVG.js v2.5.3', function () {\n    for (var i = 0; i < 10000; i++) bench.draw.circle(100, 100).fill('#f06')\n  })\n  bench.test('using vanilla js', function () {\n    for (var i = 0; i < 10000; i++) {\n      var circle = document.createElementNS(SVG.ns, 'circle')\n      circle.setAttributeNS(null, 'rx', 50)\n      circle.setAttributeNS(null, 'ry', 50)\n      circle.setAttributeNS(null, 'fill', '#f06')\n      bench.raw.appendChild(circle)\n    }\n  })\n  bench.test('using Snap.svg v0.5.1', function () {\n    for (var i = 0; i < 10000; i++)\n      bench.snap.circle(50, 50, 100, 100).attr('fill', '#f06')\n  })\n})\n"
  },
  {
    "path": "bench/tests/10000-each.js",
    "content": "SVG.bench.describe('each() vs forEach()', function (bench) {\n  // preparation\n  var list = []\n\n  for (var i = 99; i >= 0; i--) list.push(bench.draw.rect(100, 50))\n\n  var set = new SVG.Set(list)\n\n  bench.test('10000 x each()', function () {\n    for (var i = 0; i < 10000; i++) {\n      set.each(function () {\n        this.fill('#f06')\n      })\n    }\n  })\n\n  bench.test('10000 x forEach()', function () {\n    for (var i = 0; i < 10000; i++) {\n      list.forEach(function (e) {\n        e.fill('#f06')\n      })\n    }\n  })\n})\n"
  },
  {
    "path": "bench/tests/10000-pathArray-bbox.js",
    "content": "SVG.bench.describe('Generate 10000 pathArrays bbox', function (bench) {\n  var data =\n    'M97.499,75.211l5.652,-4.874c-1.235,-3.156 -2.115,-6.44 -2.623,-9.791l-8.313,-1.582c-0.345,-3.501 -0.345,-7.027 0,-10.527l8.313,-1.582c0.508,-3.351 1.388,-6.635 2.623,-9.791l-6.408,-5.526c1.452,-3.204 3.215,-6.258 5.263,-9.117l7.99,2.787c2.116,-2.648 4.52,-5.052 7.168,-7.168l-2.787,-7.99c2.86,-2.049 5.913,-3.812 9.117,-5.263l5.526,6.408c3.156,-1.236 6.44,-2.115 9.791,-2.624l1.582,-8.312c3.501,-0.345 7.027,-0.345 10.527,0l1.582,8.312c3.351,0.509 6.635,1.388 9.791,2.624l5.526,-6.408c3.204,1.451 6.258,3.214 9.117,5.263l-2.787,7.99c2.648,2.116 5.052,4.52 7.168,7.168l7.99,-2.787c2.049,2.859 3.812,5.913 5.263,9.117l-6.408,5.526c1.236,3.156 2.115,6.44 2.624,9.791l8.312,1.582c0.345,3.5 0.345,7.026 0,10.527l-8.312,1.582c-0.509,3.351 -1.388,6.635 -2.624,9.791l6.408,5.526c-1.451,3.204 -3.214,6.257 -5.263,9.117l-7.99,-2.787c-2.116,2.648 -4.52,5.052 -7.168,7.168l2.787,7.99c-2.859,2.048 -5.913,3.811 -9.117,5.263l-5.526,-6.408c-3.156,1.235 -6.44,2.115 -9.791,2.624l-1.444,7.589l0.16,0.015l1.582,8.313c3.351,0.508 6.635,1.388 9.791,2.624l5.526,-6.409c3.204,1.452 6.258,3.215 9.117,5.264l-2.787,7.99c2.648,2.116 5.052,4.52 7.168,7.167l7.99,-2.786c2.049,2.859 3.812,5.913 5.263,9.117l-6.408,5.526c1.235,3.156 2.115,6.44 2.624,9.791l8.312,1.582c0.345,3.5 0.345,7.026 0,10.527l-8.312,1.581c-0.509,3.351 -1.389,6.635 -2.624,9.792l6.408,5.526c-1.451,3.204 -3.214,6.257 -5.263,9.116l-7.99,-2.786c-2.116,2.648 -4.52,5.052 -7.168,7.168l2.787,7.989c-2.859,2.049 -5.913,3.812 -9.117,5.264l-5.526,-6.408c-3.156,1.235 -6.44,2.115 -9.791,2.623l-1.582,8.313c-3.5,0.345 -7.026,0.345 -10.527,0l-1.582,-8.313c-3.351,-0.508 -6.635,-1.388 -9.791,-2.623l-5.526,6.408c-3.204,-1.452 -6.258,-3.215 -9.117,-5.264l2.787,-7.989c-2.648,-2.116 -5.052,-4.52 -7.168,-7.168l-7.99,2.786c-2.048,-2.859 -3.811,-5.912 -5.263,-9.116l6.408,-5.526c-1.235,-3.157 -2.115,-6.441 -2.624,-9.792l-8.312,-1.581c-0.345,-3.501 -0.345,-7.027 0,-10.527l8.312,-1.582c0.509,-3.351 1.389,-6.635 2.624,-9.791l-6.408,-5.526c0.034,-0.076 0.068,-0.151 0.103,-0.226l-7.783,-2.714c-2.116,2.648 -4.52,5.052 -7.168,7.167l2.787,7.99c-2.86,2.049 -5.913,3.812 -9.117,5.264l-5.526,-6.408c-3.156,1.235 -6.44,2.115 -9.791,2.623l-1.582,8.313c-3.501,0.345 -7.027,0.345 -10.527,0l-1.582,-8.313c-3.351,-0.508 -6.635,-1.388 -9.791,-2.623l-5.526,6.408c-3.204,-1.452 -6.258,-3.215 -9.117,-5.264l2.787,-7.99c-2.648,-2.115 -5.052,-4.519 -7.168,-7.167l-7.99,2.786c-2.049,-2.859 -3.812,-5.913 -5.263,-9.116l6.408,-5.527c-1.236,-3.156 -2.115,-6.44 -2.624,-9.791l-8.312,-1.581c-0.345,-3.501 -0.345,-7.027 0,-10.528l8.312,-1.581c0.509,-3.351 1.388,-6.635 2.624,-9.791l-6.408,-5.527c1.451,-3.204 3.214,-6.257 5.263,-9.116l7.99,2.786c2.116,-2.648 4.52,-5.052 7.168,-7.167l-2.787,-7.99c2.859,-2.049 5.913,-3.812 9.117,-5.264l5.526,6.408c3.156,-1.235 6.44,-2.115 9.791,-2.623l1.582,-8.313c3.5,-0.345 7.026,-0.345 10.527,0l1.582,8.313c3.351,0.508 6.635,1.388 9.791,2.623l5.526,-6.408c3.204,1.452 6.257,3.215 9.117,5.264l-2.787,7.99c2.648,2.115 5.052,4.519 7.168,7.167l7.99,-2.786c0.049,0.069 0.099,0.139 0.148,0.209Zm48.456,73.925c5.927,0 10.74,4.813 10.74,10.74c0,5.928 -4.813,10.74 -10.74,10.74c-5.928,0 -10.741,-4.812 -10.741,-10.74c0,-5.927 4.813,-10.74 10.741,-10.74Zm-5.402,-41.978l-0.16,-0.016l-1.582,-8.312c-3.351,-0.509 -6.635,-1.389 -9.791,-2.624l-5.526,6.408c-3.204,-1.452 -6.257,-3.215 -9.117,-5.263l2.787,-7.99c-2.648,-2.116 -5.052,-4.52 -7.168,-7.168l-7.99,2.787c-0.049,-0.07 -0.099,-0.139 -0.148,-0.209l-5.652,4.874c1.235,3.156 2.115,6.44 2.624,9.791l8.312,1.581c0.345,3.501 0.345,7.027 0,10.528l-8.312,1.581c-0.509,3.351 -1.389,6.635 -2.624,9.791l6.408,5.527c-0.034,0.075 -0.068,0.15 -0.103,0.225l7.783,2.714c2.116,-2.647 4.52,-5.051 7.168,-7.167l-2.787,-7.99c2.859,-2.049 5.913,-3.812 9.117,-5.264l5.526,6.409c3.156,-1.236 6.44,-2.116 9.791,-2.624l1.444,-7.589Zm-86.853,-11.617c5.928,0 10.74,4.812 10.74,10.74c0,5.928 -4.812,10.74 -10.74,10.74c-5.927,0 -10.74,-4.812 -10.74,-10.74c0,-5.928 4.813,-10.74 10.74,-10.74Zm91.957,-52.581c5.927,0 10.74,4.813 10.74,10.74c0,5.928 -4.813,10.74 -10.74,10.74c-5.928,0 -10.74,-4.812 -10.74,-10.74c0,-5.927 4.812,-10.74 10.74,-10.74Z'\n\n  var data2 =\n    'M0.48858732019046247.35239897640279355L0.5168962223329727.32957811442929225C0.5107105368860513.31480120831760355.5063029229643583.2994249853547438.5037585276550173.2837350574775992L0.46212160205156927.2763278757701558C0.46039361704817827.25993562345804494.46039361704817827.2434263170734397.46212160205156927.22703874692422862L0.5037585276550173.2196315652167852C0.5063029229643582.20394163733964055.5107105368860513.1885654143767808.5168962223329727.17378850826509218L0.4848007791395535.14791487608093778C0.492073342110347.13291322615006.5009035959102843.11861390065414837.5111613155825881.1052275969236928L0.5511804465306873.11827678492536459C0.5617787545514856.10587841756676146.5738195544012016.09462249795570335.5870824653837506.0847150412597803L0.5731233517476615.047304559690581297C0.5874480969931638.037710807908943156.6027395121101284.02945615471664055.6187872337068381.022662336349067613L0.6464650456741969.052665636210823215C0.6622723519660869.046878482866701814.678720765737496.04276286167779995.6955047592052157.040379640761814675L0.7034284469598956.0014615027388882582C0.7209637382551767 -0.0001538434615339766.7386242458550512 -0.0001538434615339766.7561545284981485.0014615027388882582L0.7640782162528285.040379640761814675C0.7808622097205482.04276286167779995.7973106234919571.046878482866701814.8131179297838472.052665636210823215L0.8407957417512061.022662336349067617C0.8568434633479157.02945615471664055.872139887117064.037710807908943156.8864596237103826.04730455969058131L0.8725005100742934.0847150412597803C0.8857634210568424.09462249795570335.8978042209065583.10587841756676146.9084025289273567.11827678492536459L0.948421659875456.1052275969236928C0.9586843881999436.11861390065414837.9675146419998809.13291322615006.9747821963184906.14791487608093778L0.9426867531250714.17378850826509218C0.9488774472241766.1885654143767808.953280052493686.20394163733964055.9558294564552107.2196315652167852L0.9974613734064749.22703874692422862C0.9991893584098659.2434263170734397.9991893584098659.25993562345804494.9974613734064749.2763278757701558L0.9558294564552107.2837350574775992C0.953280052493686.2994249853547438.9488774472241766.31480120831760366.9426867531250714.32957811442929225L0.9747821963184906.3554517466134466C0.9675146419998809.37045339654432435.9586843881999436.38474803987733625.948421659875456.3981390257706916L0.9084025289273567.3850898377690198C0.8978042209065583.3974882051276229.8857634210568424.40874412473868105.8725005100742934.41865158143460407L0.8864596237103826.4560620630038031C0.8721398871170639.46565113262254143.8568434633479156.4739057858148441.8407957417512061.4807042863453168L0.8131179297838472.45070098648356116C0.7973106234919571.4564834576647828.7808622097205482.4606037610165844.7640782162528285.4629869819325697L0.756845722499505.4985199161789591L0.7576471068489037.4985901486224557L0.7655707946035836.5375129688082819C0.7823547880713033.5398915075613674.7988032018427124.5440118109131691.8146105081346023.5497989642572905L0.8422883201019612.519790982232635C0.8583360416986708.5265894827631077.8736324654678193.5348441359554104.8879522020611378.5444378877370485L0.8739930884250485.5818483693062474C0.8872559994075976.5917558260021705.8992967992573135.6030117456132287.9098951072781118.615405430808932L0.9499142382262111.6023609249701599C0.9601769665506987.6157472287006156.969007220350636.6300465541965272.9762747746692458.6450482041274048L0.9441793314758266.6709218363115593C0.9503650169227481.685698742423248.9547726308444411.7010749653861077.9573220348059658.7167648932632523L0.9989539517572301.7241720749706957C1.000681936760621.7405596451199068 1.000681936760621.757068951504512.9989539517572301.7734612038166229L0.9573220348059658.7808637033611664C0.9547726308444411.796553631238311.950365016922748.8119298542011708.9441793314758266.8267114424757592L0.9762747746692458.8525850746599137C0.969007220350636.8675867245907916.9601769665506987.8818813679238033.9499142382262111.8952676716542589L0.9098951072781118.8822231658154869C0.8992967992573135.89462153317409.8872559994075976.9058774527851481.8739930884250485.9157849094810713L0.8879522020611378.9531907088873705C0.8736324654678191.9627844606690087.8583360416986707.9710391138613113.8422883201019612.977837614391784L0.8146105081346023.9478343145300284C0.7988032018427124.9536167857112502.7823547880713033.9577370890630518.7655707946035836.9601156278161371L0.7576471068489037.9990384480019633C0.7401168242058064 1.0006537942023856.7224563166059317 1.0006537942023856.7049210253106508.9990384480019633L0.6969973375559708.9601156278161371C0.6802133440882511.9577370890630517.6637649303168421.95361678571125.647957624024952.9478343145300284L0.6202798120575933.977837614391784C0.6042320904608837.9710391138613113.5889356666917354.9627844606690087.5746159300984167.9531907088873705L0.5885750437345059.9157849094810713C0.5753121327519569.9058774527851481.563271332902241.89462153317409.5526730248814425.8822231658154869L0.5126538939333434.8952676716542589C0.5023961742610396.8818813679238033.49356592046110226.8675867245907916.4862933574903087.8525850746599138L0.518388800683728.8267114424757593C0.5122031152368065.8119298542011709.5077955013151135.7965536312383112.5052460973535888.7808637033611665L0.46361418040232455.773461203816623C0.46188619539893355.757068951504512.46188619539893355.7405596451199069.46361418040232455.7241720749706959L0.5052460973535888.7167648932632524C0.5077955013151135.7010749653861078.5122031152368065.6856987424232481.518388800683728.6709218363115594L0.4862933574903087.645048204127405C0.48646365166455596.6446923597470222.48663394583880315.644341197529539.4868092486652341.6439900353120559L0.4478269087191694.6312826452020677C0.43722860069837116.6436810125606708.4251878008486552.6549369321717289.4119248898661061.6648397067047522L0.42588400350219535.7022501882739512C0.411559258256693.7118439400555895.3962678431397284.720098593247892.3802201215430187.7268970937783648L0.35254230957565996.6968937939166092C0.3367350032837699.702676265097831.32028658951236094.7067965684496326.3035025960446412.7091751072027179L0.2955789082899612.7480979273885441C0.27804361699468016.7497132735889663.2603831093948056.7497132735889663.24285282675170825.7480979273885441L0.23492913899702825.7091751072027179C0.21814514552930853.7067965684496325.2016967317578995.7026762650978308.1858894254660095.6968937939166092L0.15821161349865073.7268970937783648C0.14216389190194106.720098593247892.12686746813279273.7118439400555895.1125477315394741.7022501882739512L0.1265068451755633.6648397067047522C0.11324393419301426.6549369321717289.10120313434329828.6436810125606708.09060482632250003.6312826452020677L0.05058569537440074.6443271510408397C0.04032296704991321.6309408473103841.03149271324997591.6166415218144725.024225158931366137.6016445540464946L0.056320602124785436.5757662396994404C0.050129908025680216.5609893335877518.04572730275617092.545613110624892.0431778987946462.5299231827477474L0.001545981843381972.5225206832032038C-0.00018200316000904808.5061284308910928 -0.00018200316000904808.48961912450648765.001545981843381972.4732268721943768L0.0431778987946462.4658243726498331C0.04572730275617092.4501344447726885.050129908025680216.4347582218098287.056320602124785436.4199813156981401L0.02422515893136614.39410300135108595C0.03149271324997591.3791013514202082.04032296704991321.3648067080871963.05058569537440075.3514204043567407L0.09060482632250003.36446491019551275C0.10120313434329828.35206654283690964.11324393419301426.34081062322585154.1265068451755633.33090784869282824L0.1125477315394741.2934973671236292C0.12686746813279273.2839036153419911.14216389190194106.2756489621496885.15821161349865073.2688504616192157L0.1858894254660095.29885376148097137C0.2016967317578995.2930712902997497.21814514552930853.2889509869479481.23492913899702825.2865724481948626L0.24285282675170825.2476496280090364C0.2603831093948056.24603428180861417.27804361699468016.24603428180861417.2955789082899612.2476496280090364L0.3035025960446412.2865724481948626C0.32028658951236094.28895098694794813.33673500328377.2930712902997497.35254230957565996.29885376148097137L0.3802201215430187.2688504616192157C0.39626784313972835.2756489621496884.411559258256693.2839036153419911.42588400350219535.2934973671236292L0.4119248898661061.33090784869282824C0.42518780084865515.3408106232258515.43722860069837116.35206654283690964.4478269087191694.36446491019551275L0.4878460396672687.3514204043567407C0.4880914636242721.3517434735968252.4883418962334592.35207122499980936.48858732019046247.3523989764027936ZM0.731286570405869.6985278687686304C0.7609728518989083.6985278687686304.7850794948592591.7210631188052454.7850794948592591.7488142983122096C0.7850794948592591.7765701599820733.7609728518989085.7991007278557888.731286570405869.7991007278557888C0.701595280260646.7991007278557888.6774886373002953.7765701599820733.6774886373002953.7488142983122096C0.6774886373002953.7210631188052455.701595280260646.6985278687686304.731286570405869.6985278687686304ZM0.7042298313092943.5019800345618924L0.7034284469598956.501905119955496L0.6955047592052157.46298698193256965C0.678720765737496.46060376101658435.6622723519660869.45648345766478277.6464650456741969.4507009864835611L0.6187872337068382.4807042863453167C0.6027395121101286.473905785814844.5874480969931639.4656511326225414.5731233517476615.45606206300380303L0.5870824653837508.418651581434604C0.5738195544012017.408744124738681.5617787545514857.3974882051276229.5511804465306874.3850898377690197L0.5111613155825881.39813902577069155C0.5109158916255848.39781127436770736.5106654590163977.3974882051276229.5104200350593944.39716045372463865L0.48211113291688407.41998131569813996C0.48829681836380556.4347582218098286.4927044322854986.4501344447726883.4952538362470233.465824372649833L0.5368857531982875.47322687219437665C0.5386137382016786.48961912450648754.5386137382016786.5061284308910927.5368857531982875.5225206832032036L0.4952538362470233.5299231827477473C0.4927044322854986.5456131106248919.48829681836380556.5609893335877517.48211113291688407.5757662396994403L0.5142065761103034.6016445540464944C0.5140362819360561.6019957162639775.5138659877618089.6023468784814607.513690684935378.6026980406989437L0.5526730248814427.615405430808932C0.563271332902241.6030117456132287.5753121327519569.5917558260021705.588575043734506.5818483693062474L0.5746159300984167.5444378877370485C0.5889356666917354.5348441359554102.6042320904608837.5265894827631077.6202798120575934.519790982232635L0.6479576240249522.5497989642572905C0.6637649303168422.544011810913169.6802133440882512.5398915075613674.6969973375559709.5375129688082819L0.7042298313092945.5019800345618926ZM0.2692133631947428.447587348155211C0.2989046533399659.447587348155211.32300628764813283.47011791602892633.32300628764813283.4978737776987901C0.32300628764813283.5256296393686539.2989046533399659.5481602072423692.2692133631947428.5481602072423692C0.23952708170170345.5481602072423692.21542043874135278.5256296393686539.21542043874135278.4978737776987901C0.21542043874135278.47011791602892633.23952708170170345.447587348155211.2692133631947428.447587348155211ZM0.7297939920551139.20139454072216306C0.7594802735481532.20139454072216306.783586916508504.2239297907587782.783586916508504.2516809702657422C0.783586916508504.279436831935606.7594802735481533.30196739980932136.7297939920551139.30196739980932136C0.7001027019098908.30196739980932136.6760010676017238.27943683193560603.6760010676017238.2516809702657422C0.6760010676017238.2239297907587782.7001027019098908.20139454072216306.7297939920551139.20139454072216306Z '\n\n  var data3 = 'M10 10-45-30.5.5 .89L2e-2.5.5.5-.5C.5.5.5.5.5.5L-3-4z'\n\n  var draw = SVG().addTo('body')\n  var path = SVG.create('path')\n  path.style.visibility = 'hidden'\n\n  bench.test('using SVG.js v3.0.0', function () {\n    for (var i = 0; i < 10000; i++) {\n      SVG.parser.path.setAttribute('d', data)\n      SVG.parser.path.getBBox()\n    }\n  })\n\n  bench.test('using SVG.js v3.0.0 more data', function () {\n    for (var i = 0; i < 10000; i++) {\n      SVG.parser.path.setAttribute('d', data2)\n      SVG.parser.path.getBBox()\n    }\n  })\n\n  bench.test('using SVG.js v3.0.0 complicated data', function () {\n    for (var i = 0; i < 10000; i++) {\n      SVG.parser.path.setAttribute('d', data3)\n      SVG.parser.path.getBBox()\n    }\n  })\n\n  bench.test('using SVG.js v3.0.0 without parser', function () {\n    for (var i = 0; i < 10000; i++) {\n      path.setAttribute('d', data)\n      draw.node.appendChild(path)\n      path.getBBox()\n    }\n    //new SVG.Path().attr('d', data).addTo(draw).bbox()\n  })\n\n  bench.test('using SVG.js v3.0.0 more data without parser', function () {\n    for (var i = 0; i < 10000; i++) {\n      path.setAttribute('d', data2)\n      draw.node.appendChild(path)\n      path.getBBox()\n    }\n    //new SVG.Path().attr('d', data2).addTo(draw).bbox()\n  })\n\n  bench.test(\n    'using SVG.js v3.0.0 complicated data without parser',\n    function () {\n      for (var i = 0; i < 10000; i++) {\n        path.setAttribute('d', data3)\n        draw.node.appendChild(path)\n        path.getBBox()\n      }\n      //new SVG.Path().attr('d', data3).addTo(draw).bbox()\n    }\n  )\n})\n"
  },
  {
    "path": "bench/tests/10000-pathArrays.js",
    "content": "SVG.bench.describe('Generate 10000 pathArrays', function (bench) {\n  var data =\n    'M97.499,75.211l5.652,-4.874c-1.235,-3.156 -2.115,-6.44 -2.623,-9.791l-8.313,-1.582c-0.345,-3.501 -0.345,-7.027 0,-10.527l8.313,-1.582c0.508,-3.351 1.388,-6.635 2.623,-9.791l-6.408,-5.526c1.452,-3.204 3.215,-6.258 5.263,-9.117l7.99,2.787c2.116,-2.648 4.52,-5.052 7.168,-7.168l-2.787,-7.99c2.86,-2.049 5.913,-3.812 9.117,-5.263l5.526,6.408c3.156,-1.236 6.44,-2.115 9.791,-2.624l1.582,-8.312c3.501,-0.345 7.027,-0.345 10.527,0l1.582,8.312c3.351,0.509 6.635,1.388 9.791,2.624l5.526,-6.408c3.204,1.451 6.258,3.214 9.117,5.263l-2.787,7.99c2.648,2.116 5.052,4.52 7.168,7.168l7.99,-2.787c2.049,2.859 3.812,5.913 5.263,9.117l-6.408,5.526c1.236,3.156 2.115,6.44 2.624,9.791l8.312,1.582c0.345,3.5 0.345,7.026 0,10.527l-8.312,1.582c-0.509,3.351 -1.388,6.635 -2.624,9.791l6.408,5.526c-1.451,3.204 -3.214,6.257 -5.263,9.117l-7.99,-2.787c-2.116,2.648 -4.52,5.052 -7.168,7.168l2.787,7.99c-2.859,2.048 -5.913,3.811 -9.117,5.263l-5.526,-6.408c-3.156,1.235 -6.44,2.115 -9.791,2.624l-1.444,7.589l0.16,0.015l1.582,8.313c3.351,0.508 6.635,1.388 9.791,2.624l5.526,-6.409c3.204,1.452 6.258,3.215 9.117,5.264l-2.787,7.99c2.648,2.116 5.052,4.52 7.168,7.167l7.99,-2.786c2.049,2.859 3.812,5.913 5.263,9.117l-6.408,5.526c1.235,3.156 2.115,6.44 2.624,9.791l8.312,1.582c0.345,3.5 0.345,7.026 0,10.527l-8.312,1.581c-0.509,3.351 -1.389,6.635 -2.624,9.792l6.408,5.526c-1.451,3.204 -3.214,6.257 -5.263,9.116l-7.99,-2.786c-2.116,2.648 -4.52,5.052 -7.168,7.168l2.787,7.989c-2.859,2.049 -5.913,3.812 -9.117,5.264l-5.526,-6.408c-3.156,1.235 -6.44,2.115 -9.791,2.623l-1.582,8.313c-3.5,0.345 -7.026,0.345 -10.527,0l-1.582,-8.313c-3.351,-0.508 -6.635,-1.388 -9.791,-2.623l-5.526,6.408c-3.204,-1.452 -6.258,-3.215 -9.117,-5.264l2.787,-7.989c-2.648,-2.116 -5.052,-4.52 -7.168,-7.168l-7.99,2.786c-2.048,-2.859 -3.811,-5.912 -5.263,-9.116l6.408,-5.526c-1.235,-3.157 -2.115,-6.441 -2.624,-9.792l-8.312,-1.581c-0.345,-3.501 -0.345,-7.027 0,-10.527l8.312,-1.582c0.509,-3.351 1.389,-6.635 2.624,-9.791l-6.408,-5.526c0.034,-0.076 0.068,-0.151 0.103,-0.226l-7.783,-2.714c-2.116,2.648 -4.52,5.052 -7.168,7.167l2.787,7.99c-2.86,2.049 -5.913,3.812 -9.117,5.264l-5.526,-6.408c-3.156,1.235 -6.44,2.115 -9.791,2.623l-1.582,8.313c-3.501,0.345 -7.027,0.345 -10.527,0l-1.582,-8.313c-3.351,-0.508 -6.635,-1.388 -9.791,-2.623l-5.526,6.408c-3.204,-1.452 -6.258,-3.215 -9.117,-5.264l2.787,-7.99c-2.648,-2.115 -5.052,-4.519 -7.168,-7.167l-7.99,2.786c-2.049,-2.859 -3.812,-5.913 -5.263,-9.116l6.408,-5.527c-1.236,-3.156 -2.115,-6.44 -2.624,-9.791l-8.312,-1.581c-0.345,-3.501 -0.345,-7.027 0,-10.528l8.312,-1.581c0.509,-3.351 1.388,-6.635 2.624,-9.791l-6.408,-5.527c1.451,-3.204 3.214,-6.257 5.263,-9.116l7.99,2.786c2.116,-2.648 4.52,-5.052 7.168,-7.167l-2.787,-7.99c2.859,-2.049 5.913,-3.812 9.117,-5.264l5.526,6.408c3.156,-1.235 6.44,-2.115 9.791,-2.623l1.582,-8.313c3.5,-0.345 7.026,-0.345 10.527,0l1.582,8.313c3.351,0.508 6.635,1.388 9.791,2.623l5.526,-6.408c3.204,1.452 6.257,3.215 9.117,5.264l-2.787,7.99c2.648,2.115 5.052,4.519 7.168,7.167l7.99,-2.786c0.049,0.069 0.099,0.139 0.148,0.209Zm48.456,73.925c5.927,0 10.74,4.813 10.74,10.74c0,5.928 -4.813,10.74 -10.74,10.74c-5.928,0 -10.741,-4.812 -10.741,-10.74c0,-5.927 4.813,-10.74 10.741,-10.74Zm-5.402,-41.978l-0.16,-0.016l-1.582,-8.312c-3.351,-0.509 -6.635,-1.389 -9.791,-2.624l-5.526,6.408c-3.204,-1.452 -6.257,-3.215 -9.117,-5.263l2.787,-7.99c-2.648,-2.116 -5.052,-4.52 -7.168,-7.168l-7.99,2.787c-0.049,-0.07 -0.099,-0.139 -0.148,-0.209l-5.652,4.874c1.235,3.156 2.115,6.44 2.624,9.791l8.312,1.581c0.345,3.501 0.345,7.027 0,10.528l-8.312,1.581c-0.509,3.351 -1.389,6.635 -2.624,9.791l6.408,5.527c-0.034,0.075 -0.068,0.15 -0.103,0.225l7.783,2.714c2.116,-2.647 4.52,-5.051 7.168,-7.167l-2.787,-7.99c2.859,-2.049 5.913,-3.812 9.117,-5.264l5.526,6.409c3.156,-1.236 6.44,-2.116 9.791,-2.624l1.444,-7.589Zm-86.853,-11.617c5.928,0 10.74,4.812 10.74,10.74c0,5.928 -4.812,10.74 -10.74,10.74c-5.927,0 -10.74,-4.812 -10.74,-10.74c0,-5.928 4.813,-10.74 10.74,-10.74Zm91.957,-52.581c5.927,0 10.74,4.813 10.74,10.74c0,5.928 -4.813,10.74 -10.74,10.74c-5.928,0 -10.74,-4.812 -10.74,-10.74c0,-5.927 4.812,-10.74 10.74,-10.74Z'\n\n  var data2 =\n    'M0.48858732019046247.35239897640279355L0.5168962223329727.32957811442929225C0.5107105368860513.31480120831760355.5063029229643583.2994249853547438.5037585276550173.2837350574775992L0.46212160205156927.2763278757701558C0.46039361704817827.25993562345804494.46039361704817827.2434263170734397.46212160205156927.22703874692422862L0.5037585276550173.2196315652167852C0.5063029229643582.20394163733964055.5107105368860513.1885654143767808.5168962223329727.17378850826509218L0.4848007791395535.14791487608093778C0.492073342110347.13291322615006.5009035959102843.11861390065414837.5111613155825881.1052275969236928L0.5511804465306873.11827678492536459C0.5617787545514856.10587841756676146.5738195544012016.09462249795570335.5870824653837506.0847150412597803L0.5731233517476615.047304559690581297C0.5874480969931638.037710807908943156.6027395121101284.02945615471664055.6187872337068381.022662336349067613L0.6464650456741969.052665636210823215C0.6622723519660869.046878482866701814.678720765737496.04276286167779995.6955047592052157.040379640761814675L0.7034284469598956.0014615027388882582C0.7209637382551767 -0.0001538434615339766.7386242458550512 -0.0001538434615339766.7561545284981485.0014615027388882582L0.7640782162528285.040379640761814675C0.7808622097205482.04276286167779995.7973106234919571.046878482866701814.8131179297838472.052665636210823215L0.8407957417512061.022662336349067617C0.8568434633479157.02945615471664055.872139887117064.037710807908943156.8864596237103826.04730455969058131L0.8725005100742934.0847150412597803C0.8857634210568424.09462249795570335.8978042209065583.10587841756676146.9084025289273567.11827678492536459L0.948421659875456.1052275969236928C0.9586843881999436.11861390065414837.9675146419998809.13291322615006.9747821963184906.14791487608093778L0.9426867531250714.17378850826509218C0.9488774472241766.1885654143767808.953280052493686.20394163733964055.9558294564552107.2196315652167852L0.9974613734064749.22703874692422862C0.9991893584098659.2434263170734397.9991893584098659.25993562345804494.9974613734064749.2763278757701558L0.9558294564552107.2837350574775992C0.953280052493686.2994249853547438.9488774472241766.31480120831760366.9426867531250714.32957811442929225L0.9747821963184906.3554517466134466C0.9675146419998809.37045339654432435.9586843881999436.38474803987733625.948421659875456.3981390257706916L0.9084025289273567.3850898377690198C0.8978042209065583.3974882051276229.8857634210568424.40874412473868105.8725005100742934.41865158143460407L0.8864596237103826.4560620630038031C0.8721398871170639.46565113262254143.8568434633479156.4739057858148441.8407957417512061.4807042863453168L0.8131179297838472.45070098648356116C0.7973106234919571.4564834576647828.7808622097205482.4606037610165844.7640782162528285.4629869819325697L0.756845722499505.4985199161789591L0.7576471068489037.4985901486224557L0.7655707946035836.5375129688082819C0.7823547880713033.5398915075613674.7988032018427124.5440118109131691.8146105081346023.5497989642572905L0.8422883201019612.519790982232635C0.8583360416986708.5265894827631077.8736324654678193.5348441359554104.8879522020611378.5444378877370485L0.8739930884250485.5818483693062474C0.8872559994075976.5917558260021705.8992967992573135.6030117456132287.9098951072781118.615405430808932L0.9499142382262111.6023609249701599C0.9601769665506987.6157472287006156.969007220350636.6300465541965272.9762747746692458.6450482041274048L0.9441793314758266.6709218363115593C0.9503650169227481.685698742423248.9547726308444411.7010749653861077.9573220348059658.7167648932632523L0.9989539517572301.7241720749706957C1.000681936760621.7405596451199068 1.000681936760621.757068951504512.9989539517572301.7734612038166229L0.9573220348059658.7808637033611664C0.9547726308444411.796553631238311.950365016922748.8119298542011708.9441793314758266.8267114424757592L0.9762747746692458.8525850746599137C0.969007220350636.8675867245907916.9601769665506987.8818813679238033.9499142382262111.8952676716542589L0.9098951072781118.8822231658154869C0.8992967992573135.89462153317409.8872559994075976.9058774527851481.8739930884250485.9157849094810713L0.8879522020611378.9531907088873705C0.8736324654678191.9627844606690087.8583360416986707.9710391138613113.8422883201019612.977837614391784L0.8146105081346023.9478343145300284C0.7988032018427124.9536167857112502.7823547880713033.9577370890630518.7655707946035836.9601156278161371L0.7576471068489037.9990384480019633C0.7401168242058064 1.0006537942023856.7224563166059317 1.0006537942023856.7049210253106508.9990384480019633L0.6969973375559708.9601156278161371C0.6802133440882511.9577370890630517.6637649303168421.95361678571125.647957624024952.9478343145300284L0.6202798120575933.977837614391784C0.6042320904608837.9710391138613113.5889356666917354.9627844606690087.5746159300984167.9531907088873705L0.5885750437345059.9157849094810713C0.5753121327519569.9058774527851481.563271332902241.89462153317409.5526730248814425.8822231658154869L0.5126538939333434.8952676716542589C0.5023961742610396.8818813679238033.49356592046110226.8675867245907916.4862933574903087.8525850746599138L0.518388800683728.8267114424757593C0.5122031152368065.8119298542011709.5077955013151135.7965536312383112.5052460973535888.7808637033611665L0.46361418040232455.773461203816623C0.46188619539893355.757068951504512.46188619539893355.7405596451199069.46361418040232455.7241720749706959L0.5052460973535888.7167648932632524C0.5077955013151135.7010749653861078.5122031152368065.6856987424232481.518388800683728.6709218363115594L0.4862933574903087.645048204127405C0.48646365166455596.6446923597470222.48663394583880315.644341197529539.4868092486652341.6439900353120559L0.4478269087191694.6312826452020677C0.43722860069837116.6436810125606708.4251878008486552.6549369321717289.4119248898661061.6648397067047522L0.42588400350219535.7022501882739512C0.411559258256693.7118439400555895.3962678431397284.720098593247892.3802201215430187.7268970937783648L0.35254230957565996.6968937939166092C0.3367350032837699.702676265097831.32028658951236094.7067965684496326.3035025960446412.7091751072027179L0.2955789082899612.7480979273885441C0.27804361699468016.7497132735889663.2603831093948056.7497132735889663.24285282675170825.7480979273885441L0.23492913899702825.7091751072027179C0.21814514552930853.7067965684496325.2016967317578995.7026762650978308.1858894254660095.6968937939166092L0.15821161349865073.7268970937783648C0.14216389190194106.720098593247892.12686746813279273.7118439400555895.1125477315394741.7022501882739512L0.1265068451755633.6648397067047522C0.11324393419301426.6549369321717289.10120313434329828.6436810125606708.09060482632250003.6312826452020677L0.05058569537440074.6443271510408397C0.04032296704991321.6309408473103841.03149271324997591.6166415218144725.024225158931366137.6016445540464946L0.056320602124785436.5757662396994404C0.050129908025680216.5609893335877518.04572730275617092.545613110624892.0431778987946462.5299231827477474L0.001545981843381972.5225206832032038C-0.00018200316000904808.5061284308910928 -0.00018200316000904808.48961912450648765.001545981843381972.4732268721943768L0.0431778987946462.4658243726498331C0.04572730275617092.4501344447726885.050129908025680216.4347582218098287.056320602124785436.4199813156981401L0.02422515893136614.39410300135108595C0.03149271324997591.3791013514202082.04032296704991321.3648067080871963.05058569537440075.3514204043567407L0.09060482632250003.36446491019551275C0.10120313434329828.35206654283690964.11324393419301426.34081062322585154.1265068451755633.33090784869282824L0.1125477315394741.2934973671236292C0.12686746813279273.2839036153419911.14216389190194106.2756489621496885.15821161349865073.2688504616192157L0.1858894254660095.29885376148097137C0.2016967317578995.2930712902997497.21814514552930853.2889509869479481.23492913899702825.2865724481948626L0.24285282675170825.2476496280090364C0.2603831093948056.24603428180861417.27804361699468016.24603428180861417.2955789082899612.2476496280090364L0.3035025960446412.2865724481948626C0.32028658951236094.28895098694794813.33673500328377.2930712902997497.35254230957565996.29885376148097137L0.3802201215430187.2688504616192157C0.39626784313972835.2756489621496884.411559258256693.2839036153419911.42588400350219535.2934973671236292L0.4119248898661061.33090784869282824C0.42518780084865515.3408106232258515.43722860069837116.35206654283690964.4478269087191694.36446491019551275L0.4878460396672687.3514204043567407C0.4880914636242721.3517434735968252.4883418962334592.35207122499980936.48858732019046247.3523989764027936ZM0.731286570405869.6985278687686304C0.7609728518989083.6985278687686304.7850794948592591.7210631188052454.7850794948592591.7488142983122096C0.7850794948592591.7765701599820733.7609728518989085.7991007278557888.731286570405869.7991007278557888C0.701595280260646.7991007278557888.6774886373002953.7765701599820733.6774886373002953.7488142983122096C0.6774886373002953.7210631188052455.701595280260646.6985278687686304.731286570405869.6985278687686304ZM0.7042298313092943.5019800345618924L0.7034284469598956.501905119955496L0.6955047592052157.46298698193256965C0.678720765737496.46060376101658435.6622723519660869.45648345766478277.6464650456741969.4507009864835611L0.6187872337068382.4807042863453167C0.6027395121101286.473905785814844.5874480969931639.4656511326225414.5731233517476615.45606206300380303L0.5870824653837508.418651581434604C0.5738195544012017.408744124738681.5617787545514857.3974882051276229.5511804465306874.3850898377690197L0.5111613155825881.39813902577069155C0.5109158916255848.39781127436770736.5106654590163977.3974882051276229.5104200350593944.39716045372463865L0.48211113291688407.41998131569813996C0.48829681836380556.4347582218098286.4927044322854986.4501344447726883.4952538362470233.465824372649833L0.5368857531982875.47322687219437665C0.5386137382016786.48961912450648754.5386137382016786.5061284308910927.5368857531982875.5225206832032036L0.4952538362470233.5299231827477473C0.4927044322854986.5456131106248919.48829681836380556.5609893335877517.48211113291688407.5757662396994403L0.5142065761103034.6016445540464944C0.5140362819360561.6019957162639775.5138659877618089.6023468784814607.513690684935378.6026980406989437L0.5526730248814427.615405430808932C0.563271332902241.6030117456132287.5753121327519569.5917558260021705.588575043734506.5818483693062474L0.5746159300984167.5444378877370485C0.5889356666917354.5348441359554102.6042320904608837.5265894827631077.6202798120575934.519790982232635L0.6479576240249522.5497989642572905C0.6637649303168422.544011810913169.6802133440882512.5398915075613674.6969973375559709.5375129688082819L0.7042298313092945.5019800345618926ZM0.2692133631947428.447587348155211C0.2989046533399659.447587348155211.32300628764813283.47011791602892633.32300628764813283.4978737776987901C0.32300628764813283.5256296393686539.2989046533399659.5481602072423692.2692133631947428.5481602072423692C0.23952708170170345.5481602072423692.21542043874135278.5256296393686539.21542043874135278.4978737776987901C0.21542043874135278.47011791602892633.23952708170170345.447587348155211.2692133631947428.447587348155211ZM0.7297939920551139.20139454072216306C0.7594802735481532.20139454072216306.783586916508504.2239297907587782.783586916508504.2516809702657422C0.783586916508504.279436831935606.7594802735481533.30196739980932136.7297939920551139.30196739980932136C0.7001027019098908.30196739980932136.6760010676017238.27943683193560603.6760010676017238.2516809702657422C0.6760010676017238.2239297907587782.7001027019098908.20139454072216306.7297939920551139.20139454072216306Z '\n\n  var data3 = 'M10 10-45-30.5.5 .89L2e-2.5.5.5-.5C.5.5.5.5.5.5L-3-4z'\n\n  bench.test('using SVG.js v2.5.3', function () {\n    for (var i = 0; i < 10000; i++) new SVG.PathArray(data)\n  })\n\n  bench.test('using SVG.js v2.5.3 more data', function () {\n    for (var i = 0; i < 10000; i++) new SVG.PathArray(data2)\n  })\n\n  bench.test('using SVG.js v2.5.3 complicated data', function () {\n    for (var i = 0; i < 10000; i++) new SVG.PathArray(data3)\n  })\n})\n"
  },
  {
    "path": "bench/tests/10000-paths.js",
    "content": "SVG.bench.describe('Generate 10000 paths', function (bench) {\n  var data =\n    'M 100 200 C 200 100 300  0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100'\n\n  bench.test('using SVG.js v2.5.3', function () {\n    for (var i = 0; i < 10000; i++) bench.draw.path(data)\n  })\n  bench.test('using vanilla js', function () {\n    for (var i = 0; i < 10000; i++) {\n      var path = document.createElementNS(SVG.ns, 'path')\n      path.setAttributeNS(null, 'd', data)\n      bench.raw.appendChild(path)\n    }\n  })\n  bench.test('using Snap.svg v0.5.1', function () {\n    for (var i = 0; i < 10000; i++) bench.snap.path(data)\n  })\n})\n"
  },
  {
    "path": "bench/tests/10000-pointArray-bbox.js",
    "content": "SVG.bench.describe('Generate 10000 pathArrays bbox', function (bench) {\n  var data =\n    '209,153 389,107 547,188 482,289 374,287 91,254 407,243 391,185 166,226 71,177 65,52 234,50 107,136 163,199 158,131 323,45 218,145 305,190 374,143 174,216 296,241'\n\n  var dataArr = [\n    [209, 153],\n    [389, 107],\n    [547, 188],\n    [482, 289],\n    [374, 287],\n    [91, 254],\n    [407, 243],\n    [391, 185],\n    [166, 226],\n    [71, 177],\n    [65, 52],\n    [234, 50],\n    [107, 136],\n    [163, 199],\n    [158, 131],\n    [323, 45],\n    [218, 145],\n    [305, 190],\n    [374, 143],\n    [174, 216],\n    [296, 241]\n  ]\n\n  bench.test('using SVG.js v3.0.0', function () {\n    for (var i = 0; i < 10000; i++) {\n      SVG.parser.poly.setAttribute('points', data)\n      SVG.parser.poly.getBBox()\n    }\n  })\n\n  bench.test('using SVG.js v3.0.0 without parser', function () {\n    for (var i = 0; i < 10000; i++) {\n      var maxX = -Infinity,\n        maxY = -Infinity,\n        minX = Infinity,\n        minY = Infinity\n      dataArr.forEach(function (el) {\n        maxX = Math.max(el[0], maxX)\n        maxY = Math.max(el[1], maxY)\n        minX = Math.min(el[0], minX)\n        minY = Math.min(el[1], minY)\n      })\n      var a = { x: minX, y: minY, width: maxX - minX, height: maxY - minY }\n    }\n    //new SVG.Path().attr('d', data).addTo(draw).bbox()\n  })\n})\n"
  },
  {
    "path": "bench/tests/10000-rects.js",
    "content": "SVG.bench.describe('Generate 10000 rects', function (bench) {\n  bench.test('using SVG.js v3.0.6', function () {\n    for (var i = 0; i < 10000; i++) bench.draw.rect(100, 100)\n  })\n  bench.test('using vanilla js', function () {\n    for (var i = 0; i < 10000; i++) {\n      var rect = document.createElementNS(SVG.ns, 'rect')\n      rect.setAttributeNS(null, 'height', 100)\n      rect.setAttributeNS(null, 'width', 100)\n      bench.raw.appendChild(rect)\n    }\n  })\n  bench.test('using Snap.svg v0.5.1', function () {\n    for (var i = 0; i < 10000; i++) bench.snap.rect(50, 50, 100, 100)\n  })\n})\n\nSVG.bench.describe('Generate 10000 rects with fill', function (bench) {\n  bench.test('using SVG.js v3.0.6', function () {\n    for (var i = 0; i < 10000; i++) bench.draw.rect(100, 100).fill('#f06')\n  })\n  bench.test('using vanilla js', function () {\n    for (var i = 0; i < 10000; i++) {\n      var rect = document.createElementNS(SVG.ns, 'rect')\n      rect.setAttributeNS(null, 'height', 100)\n      rect.setAttributeNS(null, 'width', 100)\n      rect.setAttributeNS(null, 'fill', '#f06')\n      bench.raw.appendChild(rect)\n    }\n  })\n  bench.test('using Snap.svg v0.5.1', function () {\n    for (var i = 0; i < 10000; i++)\n      bench.snap.rect(50, 50, 100, 100).attr('fill', '#f06')\n  })\n})\n\nSVG.bench.describe(\n  'Generate 10000 rects with position and fill',\n  function (bench) {\n    bench.test('using SVG.js v3.0.6', function () {\n      for (var i = 0; i < 10000; i++)\n        bench.draw.rect(100, 100).move(50, 50).fill('#f06')\n    })\n    bench.test('using vanilla js', function () {\n      for (var i = 0; i < 10000; i++) {\n        var rect = document.createElementNS(SVG.ns, 'rect')\n        rect.setAttributeNS(null, 'height', 100)\n        rect.setAttributeNS(null, 'width', 100)\n        rect.setAttributeNS(null, 'fill', '#f06')\n        rect.setAttributeNS(null, 'x', 50)\n        rect.setAttributeNS(null, 'y', 50)\n        bench.raw.appendChild(rect)\n      }\n    })\n    bench.test('using Snap.svg v0.5.1', function () {\n      for (var i = 0; i < 10000; i++)\n        bench.snap.rect(50, 50, 100, 100).attr('fill', '#f06')\n    })\n  }\n)\n\nSVG.bench.describe('Generate 10000 rects with gradient fill', function (bench) {\n  bench.test('using SVG.js v3.0.6', function () {\n    for (var i = 0; i < 10000; i++) {\n      var g = bench.draw.gradient('linear', function (add) {\n        add.stop(0, '#000')\n        add.stop(0.25, '#f00')\n        add.stop(1, '#fff')\n      })\n\n      bench.draw.rect(100, 100).fill(g)\n    }\n  })\n  bench.test('using vanilla js', function () {\n    for (var i = 0; i < 10000; i++) {\n      var g = document.createElementNS(SVG.ns, 'linearGradient')\n      var stop = document.createElementNS(SVG.ns, 'stop')\n      stop.setAttributeNS(null, 'offset', '0%')\n      stop.setAttributeNS(null, 'color', '#000')\n      g.appendChild(stop)\n      stop = document.createElementNS(SVG.ns, 'stop')\n      stop.setAttributeNS(null, 'offset', '25%')\n      stop.setAttributeNS(null, 'color', '#f00')\n      g.appendChild(stop)\n      stop = document.createElementNS(SVG.ns, 'stop')\n      stop.setAttributeNS(null, 'offset', '100%')\n      stop.setAttributeNS(null, 'color', '#fff')\n      g.appendChild(stop)\n      bench.raw.appendChild(g)\n\n      var rect = document.createElementNS(SVG.ns, 'rect')\n      rect.setAttributeNS(null, 'height', 100)\n      rect.setAttributeNS(null, 'width', 100)\n      rect.setAttributeNS(null, 'fill', '#f06')\n      bench.raw.appendChild(rect)\n    }\n  })\n  bench.test('using Snap.svg v0.5.1', function () {\n    for (var i = 0; i < 10000; i++) {\n      var g = bench.snap.gradient('L(0, 0, 100, 100)#000-#f00:25%-#fff')\n\n      bench.snap.rect(50, 50, 100, 100).attr({\n        fill: g\n      })\n    }\n  })\n})\n"
  },
  {
    "path": "bench/tests/10000-textContent.js",
    "content": "SVG.bench.describe('Change textContent 10000 times', function (bench) {\n  var data =\n    'M 100 200 C 200 100 300  0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100'\n\n  var node = bench.draw.plain('').node\n\n  bench.test('using appendChild', function () {\n    for (var i = 0; i < 1000000; i++) {\n      while (node.hasChildNodes()) {\n        node.removeChild(node.lastChild)\n      }\n\n      node.appendChild(document.createTextNode('test' + i))\n    }\n  })\n  bench.test('using textContent', function () {\n    for (var i = 0; i < 1000000; i++) {\n      node.textContent = 'test' + i\n    }\n  })\n})\n"
  },
  {
    "path": "bench/tests/10000-transform.js",
    "content": "SVG.bench.describe('Transform 1000000 rects', function (bench) {\n  let parameters = {\n    translate: [20, 30],\n    origin: [100, 100],\n    rotate: 25,\n    skew: [10, 30],\n    scale: 0.5\n  }\n\n  let matrixLike = { a: 2, b: 3, c: 1, d: 2, e: 49, f: 100 }\n  let matrix = new SVG.Matrix(matrixLike)\n\n  let worker = new SVG.Matrix()\n  bench.test('with parameters', function () {\n    for (var i = 0; i < 1000000; i++) worker.transform(parameters)\n  })\n\n  worker = new SVG.Matrix()\n  bench.test('with matrix like', function () {\n    for (var i = 0; i < 1000000; i++) {\n      worker.transform(matrixLike)\n    }\n  })\n\n  worker = new SVG.Matrix()\n  bench.test('with SVG.Matrix', function () {\n    for (var i = 0; i < 1000000; i++) worker.transform(matrix)\n  })\n})\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@svgdotjs/svg.js\",\n  \"version\": \"3.2.5\",\n  \"type\": \"module\",\n  \"description\": \"A lightweight library for manipulating and animating SVG.\",\n  \"url\": \"https://svgjs.dev/\",\n  \"homepage\": \"https://svgjs.dev/\",\n  \"keywords\": [\n    \"svg\",\n    \"vector\",\n    \"graphics\",\n    \"animation\"\n  ],\n  \"author\": \"Wout Fierens <wout@mick-wout.com>\",\n  \"main\": \"dist/svg.node.cjs\",\n  \"unpkg\": \"dist/svg.min.js\",\n  \"jsdelivr\": \"dist/svg.min.js\",\n  \"browser\": \"dist/svg.esm.js\",\n  \"module\": \"src/main.js\",\n  \"exports\": {\n    \".\": {\n      \"import\": {\n        \"types\": \"./svg.js.d.ts\",\n        \"default\": \"./src/main.js\"\n      },\n      \"require\": {\n        \"types\": \"./svg.js.d.ts\",\n        \"default\": \"./dist/svg.node.cjs\"\n      }\n    }\n  },\n  \"files\": [\n    \"/dist\",\n    \"/src\",\n    \"/svg.js.d.ts\",\n    \"/.config\"\n  ],\n  \"maintainers\": [\n    {\n      \"name\": \"Wout Fierens\",\n      \"email\": \"wout@mick-wout.com\"\n    },\n    {\n      \"name\": \"Alex Ewerlöf\",\n      \"email\": \"alex@userpixel.com\",\n      \"web\": \"http://www.ewerlof.name\"\n    },\n    {\n      \"name\": \"Ulrich-Matthias Schäfer\",\n      \"email\": \"ulima.ums@googlemail.com\",\n      \"web\": \"https://svgdotjs.github.io/\"\n    },\n    {\n      \"name\": \"Jon Ege Ronnenberg\",\n      \"email\": \"jon@svgjs.dev\",\n      \"url\": \"https://keybase.io/dotnetcarpenter\"\n    }\n  ],\n  \"licenses\": [\n    {\n      \"type\": \"MIT\",\n      \"url\": \"http://www.opensource.org/licenses/mit-license.php\"\n    }\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/svgdotjs/svg.js.git\"\n  },\n  \"github\": \"https://github.com/svgdotjs/svg.js\",\n  \"license\": \"MIT\",\n  \"funding\": {\n    \"type\": \"github\",\n    \"url\": \"https://github.com/sponsors/Fuzzyma\"\n  },\n  \"typings\": \"./svg.js.d.ts\",\n  \"scripts\": {\n    \"build\": \"npm run format && npm run rollup\",\n    \"build:polyfills\": \"npx rollup -c .config/rollup.polyfills.js\",\n    \"build:tests\": \"npx rollup -c .config/rollup.tests.js\",\n    \"fix\": \"npx eslint ./src --fix\",\n    \"lint\": \"npx eslint ./src\",\n    \"prettier\": \"npx prettier --write .\",\n    \"format\": \"npm run fix && npm run prettier\",\n    \"rollup\": \"npx rollup -c .config/rollup.config.js\",\n    \"server\": \"npx http-server ./ -d\",\n    \"test\": \"npx karma start .config/karma.conf.cjs || true\",\n    \"test:ci\": \"karma start .config/karma.conf.saucelabs.cjs\",\n    \"test:svgdom\": \"node ./spec/runSVGDomTest.js || true\",\n    \"zip\": \"zip -j dist/svg.js.zip -- LICENSE.txt README.md CHANGELOG.md dist/svg.js dist/svg.js.map dist/svg.min.js dist/svg.min.js.map dist/polyfills.js dist/polyfillsIE.js\",\n    \"prepublishOnly\": \"rm -rf ./dist && npm run build && npm run build:polyfills && npm test\",\n    \"postpublish\": \"npm run zip\",\n    \"checkTests\": \"node spec/checkForAllTests.js\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.24.7\",\n    \"@babel/eslint-parser\": \"^7.24.7\",\n    \"@babel/plugin-transform-runtime\": \"^7.24.7\",\n    \"@babel/preset-env\": \"^7.24.7\",\n    \"@rollup/plugin-babel\": \"^6.0.4\",\n    \"@rollup/plugin-commonjs\": \"^26.0.1\",\n    \"@rollup/plugin-node-resolve\": \"^15.2.3\",\n    \"@rollup/plugin-terser\": \"^0.4.4\",\n    \"@target/custom-event-polyfill\": \"github:Adobe-Marketing-Cloud/custom-event-polyfill\",\n    \"@types/jasmine\": \"^5.1.4\",\n    \"babel-plugin-polyfill-corejs3\": \"^0.10.4\",\n    \"core-js\": \"^3.37.1\",\n    \"coveralls\": \"^3.1.1\",\n    \"eslint\": \"^8.57.0\",\n    \"eslint-config-prettier\": \"^9.1.0\",\n    \"eslint-config-standard\": \"^17.1.0\",\n    \"http-server\": \"^14.1.1\",\n    \"jasmine\": \"^5.1.0\",\n    \"jasmine-core\": \"^5.1.2\",\n    \"karma\": \"^6.4.3\",\n    \"karma-chrome-launcher\": \"^3.2.0\",\n    \"karma-coverage\": \"^2.2.1\",\n    \"karma-firefox-launcher\": \"^2.1.3\",\n    \"karma-jasmine\": \"^5.1.0\",\n    \"karma-sauce-launcher\": \"^4.3.6\",\n    \"prettier\": \"^3.3.2\",\n    \"rollup\": \"^4.18.0\",\n    \"rollup-plugin-filesize\": \"^10.0.0\",\n    \"svgdom\": \"^0.1.19\",\n    \"typescript\": \"^5.4.5\",\n    \"yargs\": \"^17.7.2\"\n  },\n  \"browserslist\": \">0.3%, last 2 version, not dead, not op_mini all\"\n}\n"
  },
  {
    "path": "playgrounds/colors/main.js",
    "content": "let canvas = SVG('#canvas').group().translate(-150, 230)\n\n// Make a bunch of rectangles\nfunction rectangles(method = 'Vibrant') {\n  // Make a group\n  let group = canvas.group()\n  group.text(method).attr('font-size', 50).move(-230, 20)\n\n  // Add the squares\n  for (let i = 0; i < 20; i++) {\n    let color = SVG.Color.random(method.toLowerCase()).toHex()\n    let rect = group\n      .rect(100, 100)\n      .x(20 + 100 * i)\n      .fill(color)\n  }\n  return group\n}\n\nrectangles('Vibrant').translate(0, 100)\nrectangles('Sine').translate(0, 220)\nrectangles('Pastel').translate(0, 340)\nrectangles('Dark').translate(0, 460)\nrectangles('RGB').translate(0, 580)\nrectangles('LAB').translate(0, 700)\nrectangles('Grey').translate(0, 820)\n"
  },
  {
    "path": "playgrounds/colors/style.css",
    "content": "* {\n  box-sizing: border-box;\n}\n\nhtml {\n  background-color: #fefefe;\n}\n\nbody {\n  margin: 0;\n  width: 100vw;\n  height: 99vh;\n  grid-gap: 30px;\n  display: inline-grid;\n  align-items: center;\n  grid-template-columns: 10vw 40vw auto 10vw;\n  grid-template-rows: 0 10vw auto 0;\n}\n\nh1 {\n  text-align: right;\n  border-right: solid 3px #f06;\n  padding-right: 12px;\n  color: #f06;\n  font-size: 52px;\n  font-family: Helvetica;\n  grid-row: 2;\n  grid-column: 2;\n  line-height: 1.8em;\n}\n\np {\n  padding-right: 50px;\n  color: #444;\n  font-size: 18px;\n  font-family: Helvetica;\n  grid-row: 2;\n  grid-column: 3;\n}\n\nsvg {\n  height: 100%;\n  width: 100%;\n  grid-row: 3;\n  grid-column: 2/4;\n  background-color: #f5f6f7;\n  border-radius: 20px;\n  border: #f065 1px solid;\n}\n\n.pink {\n  fill: #ff0066;\n}\n\n.green {\n  fill: #00ff99;\n}\n\n.dark-pink {\n  fill: #660029;\n}\n\n.light-pink {\n  fill: #ff99c2;\n}\n\n.off-white {\n  fill: #ffcce0;\n}\n"
  },
  {
    "path": "playgrounds/matrix/drag.js",
    "content": "function reactToDrag(element, onDrag, beforeDrag) {\n  let xStart, yStart\n  let startDrag = (event) => {\n    // Avoid the default events\n    event.preventDefault()\n\n    // Store the position where the drag started\n    xStart = event.pageX\n    yStart = event.pageY\n\n    // Fire the start drag event\n    if (beforeDrag) {\n      var { x, y } = parent.point(event.pageX, event.pageY)\n      beforeDrag(event, x, y)\n    }\n\n    // Register events to react to dragging and drag ends\n    SVG.on(window, ['mousemove.drag', 'touchmove.drag'], reactDrag)\n    SVG.on(window, ['mouseup.drag', 'touchend.drag'], stopDrag)\n  }\n\n  let reactDrag = (event) => {\n    // Convert screen coordinates to svg coordinates and use them\n    var { x, y } = parent.point(event.pageX, event.pageY)\n    if (onDrag) onDrag(event, x, y)\n  }\n\n  let stopDrag = (event) => {\n    SVG.off(window, ['mousemove.drag', 'touchmove.drag'])\n    SVG.off(window, ['mouseup.drag', 'touchend.drag'])\n  }\n\n  // Bind the drag tracker to this element directly\n  let parent = element.root()\n  let point = new SVG.Point()\n  element.mousedown(startDrag).touchstart(startDrag)\n}\n\nSVG.extend(SVG.Element, {\n  draggable: function (after) {\n    let sx, sy\n\n    reactToDrag(\n      this,\n      (e, x, y) => {\n        this.transform({\n          origin: [sx, sy],\n          position: [x, y]\n        })\n\n        if (after) {\n          after(this, x, y)\n        }\n      },\n      (e, x, y) => {\n        var toAbsolute = new SVG.Matrix(this).inverse()\n        var p = new SVG.Point(x, y).transform(toAbsolute)\n        sx = p.x\n        sy = p.y\n      }\n    )\n    return this\n  }\n})\n"
  },
  {
    "path": "playgrounds/matrix/matrix.js",
    "content": "function print(mat) {\n  let { a, b, c, d } = mat\n  console.log(`\n    a: ${a.toFixed(2)}\n    b: ${b.toFixed(2)}\n    c: ${c.toFixed(2)}\n    d: ${d.toFixed(2)}\n    `)\n}\n\nfunction moveit() {\n  let { cx: x0, cy: y0 } = or.rbox(svg)\n  let { cx: x1, cy: y1 } = b1.rbox(svg)\n  let { cx: x2, cy: y2 } = b2.rbox(svg)\n\n  let m = new SVG.Matrix(\n    (x1 - x0) / 50,\n    (y1 - y0) / 50,\n    (x2 - x0) / 50,\n    (y2 - y0) / 50,\n    x0,\n    y0\n  )\n  let com = m.decompose()\n  let g = new SVG.Matrix().compose(com)\n\n  // Transform both of the items\n  target.transform(m)\n  mover.transform(g)\n\n  console.log(com)\n  print(m)\n  print(g)\n}\n\n// Declare the two points\nlet svg = SVG('svg')\nvar or = SVG('#or').draggable(moveit)\nvar b1 = SVG('#b1').draggable(moveit)\nvar b2 = SVG('#b2').draggable(moveit)\n\n// Declare the squares\nlet target = SVG('#true')\nlet mover = SVG('#guess')\nlet tester = SVG('#tester')\n"
  },
  {
    "path": "playgrounds/matrix/style.css",
    "content": "* {\n  box-sizing: border-box;\n}\n\nhtml {\n  background-color: #fefefe;\n}\n\nbody {\n  margin: 0;\n  width: 100vw;\n  height: 99vh;\n  grid-gap: 30px;\n  display: inline-grid;\n  align-items: center;\n  grid-template-columns: 10vw 40vw auto 10vw;\n  grid-template-rows: 0 10vw auto 0;\n}\n\nh1 {\n  text-align: right;\n  border-right: solid 3px #f06;\n  padding-right: 12px;\n  color: #f06;\n  font-size: 52px;\n  font-family: Helvetica;\n  grid-row: 2;\n  grid-column: 2;\n  line-height: 1.8em;\n}\n\np {\n  padding-right: 50px;\n  color: #444;\n  font-size: 18px;\n  font-family: Helvetica;\n  grid-row: 2;\n  grid-column: 3;\n}\n\nsvg {\n  height: 100%;\n  width: 100%;\n  grid-row: 3;\n  grid-column: 2/4;\n  background-color: #f5f6f7;\n  border-radius: 20px;\n  border: #f065 1px solid;\n}\n\n.pink {\n  fill: #ff0066;\n}\n\n.green {\n  fill: #00ff99;\n}\n\n.dark-pink {\n  fill: #660029;\n}\n\n.light-pink {\n  fill: #ff99c2;\n}\n\n.off-white {\n  fill: #ffcce0;\n}\n"
  },
  {
    "path": "playgrounds/transforms/style.css",
    "content": "* {\n  box-sizing: border-box;\n}\n\nhtml {\n  background-color: #fefefe;\n}\n\nbody {\n  margin: 0;\n  width: 100vw;\n  height: 99vh;\n  grid-gap: 30px;\n  display: inline-grid;\n  align-items: center;\n  grid-template-columns: 10vw 40vw auto 10vw;\n  grid-template-rows: 0 10vw auto 0;\n}\n\nh1 {\n  text-align: right;\n  border-right: solid 3px #f06;\n  padding-right: 12px;\n  color: #f06;\n  font-size: 52px;\n  font-family: Helvetica;\n  grid-row: 2;\n  grid-column: 2;\n  line-height: 1.8em;\n}\n\np {\n  padding-right: 50px;\n  color: #444;\n  font-size: 18px;\n  font-family: Helvetica;\n  grid-row: 2;\n  grid-column: 3;\n}\n\nsvg {\n  height: 100%;\n  width: 100%;\n  grid-row: 3;\n  grid-column: 2/4;\n  background-color: #f5f6f7;\n  border-radius: 20px;\n  border: #f065 1px solid;\n}\n\n.pink {\n  fill: #ff0066;\n}\n\n.green {\n  fill: #00ff99;\n}\n\n.dark-pink {\n  fill: #660029;\n}\n\n.light-pink {\n  fill: #ff99c2;\n}\n\n.off-white {\n  fill: #ffcce0;\n}\n"
  },
  {
    "path": "playgrounds/transforms/transforms.js",
    "content": "let canvas = SVG('#canvas')\n\n// Make the green rectangle\ncanvas.rect(200, 400).move(200, 400).attr('opacity', 0.3).addClass('green')\n\n// Make the pink rectangle\nlet a = canvas\n  .rect(200, 400)\n  .move(200, 400)\n  .attr('opacity', 0.3)\n  .addClass('pink')\n  .transform({ px: 100, py: 500, origin: 'top-left' })\n\na.animate().rotate({ rotate: 500, origin: 'top-right' })\n\n// Put an ellipse where we expect the object to be\ncanvas\n  .ellipse(30, 30)\n  .center(100, 500)\n  .attr('opacity', 0.3)\n  .addClass('dark-pink')\n"
  },
  {
    "path": "playgrounds/webpack.config.js",
    "content": "var path = require('path')\nmodule.exports = function (env) {\n  let currentTest = path.resolve(__dirname, env)\n  return {\n    mode: 'development',\n    devtool: 'eval-source-map',\n    devServer: {\n      contentBase: [currentTest, __dirname]\n    },\n\n    devServer: {\n      contentBase: [currentTest, '..']\n    },\n\n    entry: {\n      app: path.resolve(currentTest, 'main.js')\n    },\n\n    output: {\n      path: currentTest,\n      filename: 'bundle.js'\n    },\n\n    resolve: {\n      modules: [path.resolve(__dirname, 'node_modules'), 'node_modules']\n    }\n  }\n}\n"
  },
  {
    "path": "spec/RAFPlugin.js",
    "content": "/* globals jasmine */\n/**\n * Jasmine RequestAnimationFrame: a set of helpers for testing functionality\n * that uses requestAnimationFrame under the Jasmine BDD framework for JavaScript.\n */\nfunction RAFPlugin(jasmine) {\n  var index = 0\n  var callbacks = []\n\n  function MockRAF() {\n    this.nextTime = 0\n\n    var _this = this\n\n    /**\n     * Mock for window.requestAnimationFrame\n     */\n    this.mockRAF = function (fn) {\n      if (typeof fn !== 'function') {\n        throw new Error('You should pass a function to requestAnimationFrame')\n      }\n\n      const i = index++\n      callbacks[i] = fn\n\n      return i\n    }\n\n    /**\n     * Mock for window.cancelAnimationFrame\n     */\n    this.mockCAF = function (requestID) {\n      callbacks.splice(requestID, 1)\n    }\n\n    this.mockPerf = {\n      now: function () {\n        return _this.nextTime\n      }\n    }\n\n    /**\n     * Install request animation frame mocks.\n     */\n    this.install = function (global) {\n      _this.realRAF = global.requestAnimationFrame\n      _this.realCAF = global.cancelAnimationFrame\n      _this.realPerf = global.performance\n      global.requestAnimationFrame = _this.mockRAF\n      global.cancelAnimationFrame = _this.mockCAF\n      global.performance = _this.mockPerf\n    }\n\n    /**\n     * Uninstall request animation frame mocks.\n     */\n    this.uninstall = function (global) {\n      global.requestAnimationFrame = _this.realRAF\n      global.cancelAnimationFrame = _this.realCAF\n      global.performance = _this.realPerf\n      _this.nextTime = 0\n      callbacks = []\n    }\n\n    /**\n     * Simulate animation frame readiness.\n     */\n    this.tick = function (dt) {\n      _this.nextTime += dt || 1\n\n      var fns = callbacks\n      var fn\n      var i\n\n      callbacks = []\n      index = 0\n\n      for (i in fns) {\n        fn = fns[i]\n        fn(_this.nextTime)\n      }\n    }\n  }\n\n  jasmine.RequestAnimationFrame = new MockRAF()\n}\n\nRAFPlugin(jasmine)\n"
  },
  {
    "path": "spec/SpecRunner.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>SVG.js - Jasmine Spec Runner</title>\n\n    <link\n      rel=\"shortcut icon\"\n      type=\"image/png\"\n      href=\"../node_modules/jasmine-core/images/jasmine_favicon.png\"\n    />\n    <link\n      rel=\"stylesheet\"\n      href=\"../node_modules/jasmine-core/lib/jasmine-core/jasmine.css\"\n    />\n\n    <script src=\"../node_modules/jasmine-core/lib/jasmine-core/jasmine.js\"></script>\n    <script src=\"../node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js\"></script>\n    <script src=\"../node_modules/jasmine-core/lib/jasmine-core/boot.js\"></script>\n    <script src=\"RAFPlugin.js\"></script>\n\n    <link rel=\"stylesheet\" href=\"fixtures/fixture.css\" />\n  </head>\n\n  <body>\n    <!-- include spec files here... -->\n\n    <script src=\"../dist/polyfillsIE.js\"></script>\n    <script src=\"es5TestBundle.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "spec/SpecRunnerEs6.html",
    "content": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>SVG.js - Jasmine Spec Runner</title>\n\n    <link\n      rel=\"shortcut icon\"\n      type=\"image/png\"\n      href=\"../node_modules/jasmine-core/images/jasmine_favicon.png\"\n    />\n    <link\n      rel=\"stylesheet\"\n      href=\"../node_modules/jasmine-core/lib/jasmine-core/jasmine.css\"\n    />\n\n    <script src=\"../node_modules/jasmine-core/lib/jasmine-core/jasmine.js\"></script>\n    <script src=\"../node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js\"></script>\n    <script src=\"../node_modules/jasmine-core/lib/jasmine-core/boot.js\"></script>\n    <script src=\"RAFPlugin.js\"></script>\n\n    <link rel=\"stylesheet\" href=\"fixtures/fixture.css\" />\n  </head>\n\n  <body>\n    <!-- include spec files here... -->\n    <script type=\"module\" src=\"setupBrowser.js\"></script>\n    <script type=\"module\" src=\"spec/elements/A.js\"></script>\n    <script type=\"module\" src=\"spec/elements/Circle.js\"></script>\n    <script type=\"module\" src=\"spec/elements/ClipPath.js\"></script>\n    <script type=\"module\" src=\"spec/elements/Container.js\"></script>\n    <script type=\"module\" src=\"spec/elements/Dom.js\"></script>\n    <script type=\"module\" src=\"spec/elements/Element.js\"></script>\n    <script type=\"module\" src=\"spec/elements/ForeignObject.js\"></script>\n    <script type=\"module\" src=\"spec/elements/G.js\"></script>\n    <script type=\"module\" src=\"spec/elements/Image.js\"></script>\n    <script type=\"module\" src=\"spec/elements/Marker.js\"></script>\n    <script type=\"module\" src=\"spec/elements/Svg.js\"></script>\n    <script type=\"module\" src=\"spec/elements/Text.js\"></script>\n    <script type=\"module\" src=\"spec/elements/TextPath.js\"></script>\n    <script type=\"module\" src=\"spec/elements/Tspan.js\"></script>\n    <script type=\"module\" src=\"spec/modules/core/event.js\"></script>\n    <script type=\"module\" src=\"spec/modules/core/textable.js\"></script>\n    <script type=\"module\" src=\"spec/types/ArrayPolyfill.js\"></script>\n    <script type=\"module\" src=\"spec/types/Base.js\"></script>\n    <script type=\"module\" src=\"spec/types/Box.js\"></script>\n    <script type=\"module\" src=\"spec/utils/adopter.js\"></script>\n    <script type=\"module\" src=\"spec/utils/utils.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "spec/checkForAllTests.js",
    "content": "const glob = require('glob')\nconst path = require('path')\n\nglob('./spec/*/**/*.js', (err, tests) => {\n  if (err) {\n    throw err\n  }\n\n  glob('./src/**/*.js', (err, files) => {\n    if (err) {\n      throw err\n    }\n\n    files = files.map((e) => path.basename(e))\n    tests = tests.map((e) => path.basename(e))\n    const difference = files.filter((x) => !tests.includes(x))\n\n    if (difference.length) {\n      console.error(\n        'The following files dont have a test file:\\n\\t' +\n          difference.join('\\n\\t')\n      )\n    } else {\n      console.info('All src files are covered by tests')\n    }\n  })\n})\n"
  },
  {
    "path": "spec/fixtures/fixture.css",
    "content": "#drawing {\n  width: 500px;\n  height: 500px;\n  position: fixed;\n  z-index: -1;\n}\n"
  },
  {
    "path": "spec/helpers.js",
    "content": "import { getWindow } from '../src/utils/window.js'\nimport { svg } from '../src/modules/core/namespaces.js'\n\nfunction tag(name, attrs, children) {\n  const doc = getWindow().document\n  const el = doc.createElementNS(svg, name)\n  let i\n\n  for (i in attrs) {\n    el.setAttribute(i, attrs[i])\n  }\n\n  for (i in children) {\n    if (typeof children[i] === 'string') {\n      children[i] = doc.createTextNode(children[i])\n    }\n\n    el.appendChild(children[i])\n  }\n\n  return el\n}\n\nexport function fixtures() {\n  return tag(\n    'svg',\n    {\n      height: 0,\n      width: 0,\n      id: 'inlineSVG'\n    },\n    [\n      tag('defs', {}, [\n        tag('linearGradient', {}, [\n          tag('stop', { offset: '5%', 'stop-color': 'green' }),\n          tag('stop', { offset: '95%', 'stop-color': 'gold' })\n        ]),\n        tag('radialGradient', {}, [\n          tag('stop', { offset: '5%', 'stop-color': 'green' }),\n          tag('stop', { offset: '95%', 'stop-color': 'gold' })\n        ])\n      ]),\n      tag('desc', {}, ['Some description']),\n      tag('path', {\n        id: 'lineAB',\n        d: 'M 100 350 l 150 -300',\n        stroke: 'red',\n        'stroke-width': '3',\n        fill: 'none'\n      }),\n      tag('path', {\n        id: 'lineBC',\n        d: 'M 250 50 l 150 300',\n        stroke: 'red',\n        'stroke-width': '3',\n        fill: 'none'\n      }),\n      tag('path', {\n        d: 'M 175 200 l 150 0',\n        stroke: 'green',\n        'stroke-width': '3',\n        fill: 'none'\n      }),\n      tag('path', {\n        d: 'M 100 350 q 150 -300 300 0',\n        stroke: 'blue',\n        'stroke-width': '5',\n        fill: 'none'\n      }),\n      tag(\n        'g',\n        {\n          stroke: 'black',\n          'stroke-width': '2',\n          fill: 'black',\n          id: 'pointGroup'\n        },\n        [\n          tag('circle', {\n            id: 'pointA',\n            cx: '100',\n            cy: '350',\n            r: '3'\n          }),\n          tag('circle', {\n            id: 'pointB',\n            cx: '250',\n            cy: '50',\n            r: '50'\n          }),\n          tag('circle', {\n            id: 'pointC',\n            cx: '400',\n            cy: '350',\n            r: '50'\n          })\n        ]\n      ),\n      tag(\n        'g',\n        {\n          'font-size': '30',\n          font: 'sans-serif',\n          fill: 'black',\n          stroke: 'none',\n          'text-anchor': 'middle',\n          id: 'labelGroup'\n        },\n        [\n          tag(\n            'text',\n            {\n              x: '100',\n              y: '350',\n              dy: '-30'\n            },\n            ['A']\n          ),\n          tag(\n            'text',\n            {\n              x: '250',\n              y: '50',\n              dy: '-10'\n            },\n            ['B']\n          ),\n          tag(\n            'text',\n            {\n              x: '400',\n              y: '350',\n              dx: '30'\n            },\n            ['C']\n          )\n        ]\n      ),\n      tag('polygon', { points: '200,10 250,190 160,210' }),\n      tag('polyline', { points: '20,20 40,25 60,40 80,120 120,140 200,180' })\n    ]\n  )\n}\n\nexport function buildFixtures() {\n  const doc = getWindow().document\n  const body = doc.body || doc.documentElement\n\n  const div = doc.createElement('div')\n  div.id = 'fixtures'\n\n  try {\n    // FIXME: doesn't work in svgdom\n    div.style.position = 'absolute'\n    div.style.top = 0\n    div.style.left = 0\n  } catch (e) {\n    //\n  }\n\n  div.appendChild(fixtures())\n  body.appendChild(div)\n}\n\nexport function buildCanvas() {\n  const doc = getWindow().document\n  const body = doc.body || doc.documentElement\n\n  const div = doc.createElement('div')\n  div.id = 'canvas'\n\n  try {\n    // FIXME: doesn't work in svgdom\n    div.style.position = 'absolute'\n    div.style.top = 0\n    div.style.left = 0\n  } catch (e) {\n    //\n  }\n  body.appendChild(div)\n}\n\nexport function clear() {\n  const doc = getWindow().document\n  const canvas = doc.getElementById('canvas')\n  const fixtures = doc.getElementById('fixtures')\n\n  // remove if present\n  fixtures && fixtures.parentNode.removeChild(fixtures)\n  canvas.parentNode.removeChild(canvas)\n  ;[...doc.querySelectorAll('svg')].forEach((el) =>\n    el.parentNode.removeChild(el)\n  )\n}\n"
  },
  {
    "path": "spec/runSVGDomTest.js",
    "content": "/*\n  This file has to be run with esm because node does not understand imports yet:\n  node -r esm ./spec/runSvgdomTest.js || true\n\n  Without \"|| true\" node reports a super long error when a test fails\n */\n\nimport Jasmine from 'jasmine'\nconst jasmine = new Jasmine()\n\njasmine.loadConfig({\n  spec_dir: '/',\n  spec_files: ['spec/spec/*/**/*.js'],\n  helpers: ['spec/setupSVGDom.js']\n})\n\njasmine.execute()\n"
  },
  {
    "path": "spec/setupBrowser.js",
    "content": "/* globals beforeEach, afterEach, jasmine */\nimport { buildCanvas, clear } from './helpers.js'\n\njasmine.DEFAULT_TIMEOUT_INTERVAL = 500\n\nbeforeEach(() => {\n  // buildFixtures()\n  buildCanvas()\n  window.container = document.getElementById('canvas')\n})\n\nafterEach(() => {\n  clear()\n  window.container = null\n})\n"
  },
  {
    "path": "spec/setupSVGDom.js",
    "content": "import './RAFPlugin.js'\nimport { createHTMLWindow } from 'svgdom'\n\n/* globals beforeEach, afterEach, jasmine */\nimport { buildCanvas, clear } from './helpers.js'\nimport { registerWindow } from '../src/main.js'\n\njasmine.DEFAULT_TIMEOUT_INTERVAL = 200\n\nfunction setup() {\n  const win = createHTMLWindow()\n  registerWindow(win, win.document)\n  buildCanvas()\n  // buildFixtures()\n  global.container = win.document.getElementById('canvas')\n}\n\nfunction teardown() {\n  clear()\n  global.container = null\n  registerWindow()\n}\n\nbeforeEach(setup)\nafterEach(teardown)\n"
  },
  {
    "path": "spec/spec/animation/Animator.js",
    "content": "/* globals describe, expect, it, beforeEach, afterEach, jasmine */\n\nimport { Animator, Queue } from '../../../src/main.js'\nimport { getWindow } from '../../../src/utils/window.js'\n\ndescribe('Animator.js', () => {\n  beforeEach(() => {\n    jasmine.RequestAnimationFrame.install(getWindow())\n    Animator.timeouts = new Queue()\n    Animator.frames = new Queue()\n    Animator.immediates = new Queue()\n    Animator.nextDraw = null\n  })\n\n  afterEach(() => {\n    jasmine.RequestAnimationFrame.uninstall(getWindow())\n  })\n\n  describe('timeout()', () => {\n    it('calls a function after a specific time', () => {\n      var spy = jasmine.createSpy('tester')\n      Animator.timeout(spy, 100)\n\n      jasmine.RequestAnimationFrame.tick(99)\n      expect(spy).not.toHaveBeenCalled()\n      jasmine.RequestAnimationFrame.tick()\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('cancelTimeout()', () => {\n    it('cancels a timeout which was created with timeout()', () => {\n      var spy = jasmine.createSpy('tester')\n      var id = Animator.timeout(spy, 100)\n      Animator.clearTimeout(id)\n\n      expect(spy).not.toHaveBeenCalled()\n      jasmine.RequestAnimationFrame.tick(100)\n      expect(spy).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('frame()', () => {\n    it('calls a function at the next animationFrame', () => {\n      var spy = jasmine.createSpy('tester')\n\n      Animator.frame(spy)\n      expect(spy).not.toHaveBeenCalled()\n      jasmine.RequestAnimationFrame.tick()\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('cancelFrame()', () => {\n    it('cancels a single frame which was created with frame()', () => {\n      var spy = jasmine.createSpy('tester')\n\n      const id = Animator.frame(spy)\n      Animator.cancelFrame(id)\n\n      expect(spy).not.toHaveBeenCalled()\n      jasmine.RequestAnimationFrame.tick()\n      expect(spy).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('immediate()', () => {\n    it('calls a function at the next animationFrame but after all frames are processed', () => {\n      var spy = jasmine.createSpy('tester')\n\n      Animator.immediate(spy)\n\n      expect(spy).not.toHaveBeenCalled()\n      jasmine.RequestAnimationFrame.tick()\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('cancelImmediate()', () => {\n    it('cancels an immediate cakk which was created with immediate()', () => {\n      var spy = jasmine.createSpy('tester')\n\n      const id = Animator.immediate(spy)\n      Animator.cancelImmediate(id)\n\n      expect(spy).not.toHaveBeenCalled()\n      jasmine.RequestAnimationFrame.tick()\n      expect(spy).not.toHaveBeenCalled()\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/animation/Controller.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport { easing, defaults } from '../../../src/main.js'\nimport {\n  Stepper,\n  Ease,\n  Controller,\n  Spring,\n  PID\n} from '../../../src/animation/Controller.js'\n\nconst { any, createSpy } = jasmine\n\ndescribe('Controller.js', () => {\n  describe('easing', () => {\n    var easedValues = {\n      '-': 0.5,\n      '<>': 0.5,\n      '>': 0.7071,\n      '<': 0.2929\n    }\n\n    ;['-', '<>', '<', '>'].forEach((el) => {\n      describe(el, () => {\n        it('is 0 at 0', () => {\n          expect(easing[el](0)).toBe(0)\n        })\n        it('is 1 at 1', () => {\n          expect(Math.round(easing[el](1) * 1000) / 1000).toBe(1) // we need to round cause for some reason at some point 1==0.999999999\n        })\n        it('is eased at 0.5', () => {\n          expect(easing[el](0.5)).toBeCloseTo(easedValues[el])\n        })\n      })\n    })\n\n    describe('beziere()', () => {\n      const b1 = easing.bezier(0.25, 0.25, 0.75, 0.75)\n      const b2 = easing.bezier(-0.25, -0.25, 0.75, 0.75)\n      const b3 = easing.bezier(0.5, 0.5, 2, 2)\n      const b4 = easing.bezier(1, 1, 2, 2)\n      const b5 = easing.bezier(-1, -1, -2, -2)\n\n      it('is 0 at 0', () => {\n        expect(b1(0)).toBe(0)\n      })\n\n      it('is 1 at 1', () => {\n        expect(b1(1)).toBe(1)\n      })\n\n      it('is eased at 0.5', () => {\n        expect(b1(0.5)).toBe(0.5)\n        expect(b2(0.5)).toBe(0.3125)\n        expect(b3(0.5)).toBe(1.0625)\n        expect(b4(0.5)).toBe(1.25)\n        expect(b5(0.5)).toBe(-1)\n      })\n\n      it('handles values bigger 1', () => {\n        expect(b1(1.5)).toBe(1.5)\n        expect(b2(1.5)).toBe(1.5)\n        expect(b3(1.5)).toBe(1.5)\n        expect(b4(1.5)).toBe(1)\n        expect(b5(1.5)).toBe(1.5)\n      })\n\n      it('handles values lower 0', () => {\n        expect(b1(-0.5)).toBe(-0.5)\n        expect(b2(-0.5)).toBe(-0.5)\n        expect(b3(-0.5)).toBe(-0.5)\n        expect(b4(-0.5)).toBe(-0.5)\n        expect(b5(-0.5)).toBe(0)\n      })\n    })\n\n    describe('steps()', () => {\n      const s1 = easing.steps(5)\n      const s2 = easing.steps(5, 'start')\n      const s3 = easing.steps(5, 'end')\n      const s4 = easing.steps(5, 'none')\n      const s5 = easing.steps(5, 'both')\n\n      it('is 0 at 0', () => {\n        expect(s1(0)).toBe(0)\n        expect(s1(0, true)).toBe(0)\n        expect(s2(0)).toBe(0.2)\n        expect(s2(0, true)).toBe(0)\n        expect(s3(0)).toBe(0)\n        expect(s3(0, true)).toBe(0)\n        expect(s4(0)).toBe(0)\n        expect(s4(0, true)).toBe(0)\n        expect(s5(0)).toBe(1 / 6)\n        expect(s5(0, true)).toBe(0)\n      })\n\n      it('also works at values < 0', () => {\n        expect(s1(-0.1)).toBe(-0.2)\n        expect(s1(-0.1, true)).toBe(-0.2)\n        expect(s2(-0.1)).toBe(0)\n        expect(s2(-0.1, true)).toBe(0)\n        expect(s3(-0.1)).toBe(-0.2)\n        expect(s3(-0.1, true)).toBe(-0.2)\n        expect(s4(-0.1)).toBe(-0.25)\n        expect(s4(-0.1, true)).toBe(-0.25)\n        expect(s5(-0.1)).toBe(0)\n        expect(s5(-0.1, true)).toBe(0)\n      })\n\n      it('is 1 at 1', () => {\n        expect(s1(1)).toBe(1)\n        expect(s1(1, true)).toBe(0.8)\n        expect(s2(1)).toBe(1)\n        expect(s2(1, true)).toBe(1)\n        expect(s3(1)).toBe(1)\n        expect(s3(1, true)).toBe(0.8)\n        expect(s4(1)).toBe(1)\n        expect(s4(1, true)).toBe(1)\n        expect(s5(1)).toBe(1)\n        expect(s5(1, true)).toBe(5 / 6)\n      })\n\n      it('also works at values > 1', () => {\n        expect(s1(1.1)).toBe(1)\n        expect(s1(1.1, true)).toBe(1)\n        expect(s2(1.1)).toBe(1.2)\n        expect(s2(1.1, true)).toBe(1.2)\n        expect(s3(1.1)).toBe(1)\n        expect(s3(1.1, true)).toBe(1)\n        expect(s4(1.1)).toBe(1.25)\n        expect(s4(1.1, true)).toBe(1.25)\n        expect(s5(1.1)).toBe(1)\n        expect(s5(1.1, true)).toBe(1)\n      })\n\n      it('is eased at 0.1', () => {\n        expect(s1(0.1)).toBe(0)\n        expect(s1(0.1, true)).toBe(0)\n        expect(s2(0.1)).toBe(0.2)\n        expect(s2(0.1, true)).toBe(0)\n        expect(s3(0.1)).toBe(0)\n        expect(s3(0.1, true)).toBe(0)\n        expect(s4(0.1)).toBe(0)\n        expect(s4(0.1, true)).toBe(0)\n        expect(s5(0.1)).toBe(1 / 6)\n        expect(s5(0.1, true)).toBe(0)\n      })\n\n      it('is eased at 0.15', () => {\n        expect(s1(0.15)).toBe(0)\n        expect(s1(0.15, true)).toBe(0)\n        expect(s2(0.15)).toBe(0.2)\n        expect(s2(0.15, true)).toBe(0)\n        expect(s3(0.15)).toBe(0)\n        expect(s3(0.15, true)).toBe(0)\n        expect(s4(0.15)).toBe(0)\n        expect(s4(0.15, true)).toBe(0)\n        expect(s5(0.15)).toBe(1 / 6)\n        expect(s5(0.15, true)).toBe(0)\n      })\n\n      it('is eased at 0.2', () => {\n        expect(s1(0.2)).toBe(0.2)\n        expect(s1(0.2, true)).toBe(0.2)\n        expect(s2(0.2)).toBe(0.4)\n        expect(s2(0.2, true)).toBe(0.4)\n        expect(s3(0.2)).toBe(0.2)\n        expect(s3(0.2, true)).toBe(0.2)\n        expect(s4(0.2)).toBe(0.25)\n        expect(s4(0.2, true)).toBe(0.25)\n        expect(s5(0.2)).toBe(1 / 3)\n        expect(s5(0.2, true)).toBe(1 / 3)\n      })\n\n      it('is eased at 0.25', () => {\n        expect(s1(0.25)).toBe(0.2)\n        expect(s1(0.25, true)).toBe(0.2)\n        expect(s2(0.25)).toBe(0.4)\n        expect(s2(0.25, true)).toBe(0.4)\n        expect(s3(0.25)).toBe(0.2)\n        expect(s3(0.25, true)).toBe(0.2)\n        expect(s4(0.25)).toBe(0.25)\n        expect(s4(0.25, true)).toBe(0.25)\n        expect(s5(0.25)).toBe(1 / 3)\n        expect(s5(0.25, true)).toBe(1 / 3)\n      })\n    })\n  })\n\n  describe('Stepper', () => {\n    it('has a done() method', () => {\n      const stepper = new Stepper()\n      expect(stepper).toEqual(any(Stepper))\n      expect(stepper.done()).toBe(false)\n    })\n  })\n\n  describe('Ease', () => {\n    describe('()', () => {\n      it('wraps the default easing function by default', () => {\n        const ease = new Ease()\n        expect(ease.ease).toBe(easing[defaults.timeline.ease])\n      })\n\n      it('wraps an easing function found in \"easing\"', () => {\n        const ease = new Ease('-')\n        expect(ease.ease).toBe(easing['-'])\n      })\n\n      it('wraps a a custom easing function', () => {\n        const ease = new Ease(easing['-'])\n        expect(ease.ease).toBe(easing['-'])\n      })\n    })\n\n    describe('step()', () => {\n      it('provides an eased value to a position', () => {\n        let ease = new Ease(easing['-'])\n        expect(ease.step(2, 4, 0.5)).toBe(3)\n\n        ease = new Ease(() => 3)\n        expect(ease.step(2, 4, 0.5)).toBe(8)\n\n        ease = new Ease()\n        expect(ease.step(2, 4, 0.5)).toBeCloseTo(3.414, 3)\n      })\n\n      it('jumps to \"to\" value at pos 1 if from is not a number', () => {\n        const ease = new Ease('-')\n        expect(ease.step('Hallo', 'Welt', 0.999)).toBe('Hallo')\n        expect(ease.step('Hallo', 'Welt', 1)).toBe('Welt')\n      })\n    })\n  })\n\n  describe('Controller', () => {\n    describe('()', () => {\n      it('constructs a controller with the given stepper function set', () => {\n        const spy = createSpy()\n        const controller = new Controller(spy)\n        expect(controller).toEqual(any(Controller))\n        expect(controller.stepper).toBe(spy)\n      })\n    })\n\n    describe('step()', () => {\n      it('runs the stepper with current value, target value, dt and context', () => {\n        const spy = createSpy().and.returnValue('foo')\n        const controller = new Controller(spy)\n        expect(controller.step(10, 20, 30, 'bar')).toBe('foo')\n        expect(spy).toHaveBeenCalledWith(10, 20, 30, 'bar')\n      })\n    })\n\n    describe('done()', () => {\n      it('returns given values \"done\" property', () => {\n        const spy = createSpy()\n        const controller = new Controller(spy)\n        expect(controller.done({ done: 'yes' })).toBe('yes')\n      })\n    })\n  })\n\n  describe('Spring', () => {\n    describe('()', () => {\n      it('creates a spring with default duration and overshoot', () => {\n        const spring = new Spring()\n        expect(spring).toEqual(any(Spring))\n        expect(spring.duration()).toBe(500)\n        expect(spring.overshoot()).toBe(0)\n      })\n\n      it('creates a spring with given duration and overshoot', () => {\n        const spring = new Spring(100, 10)\n        expect(spring).toEqual(any(Spring))\n        expect(spring.duration()).toBe(100)\n        expect(spring.overshoot()).toBe(10)\n      })\n    })\n\n    describe('duration()', () => {\n      it('gets and sets a new duration for the spring controller', () => {\n        const spring = new Spring().duration(100)\n        expect(spring.duration()).toBe(100)\n      })\n    })\n\n    describe('overshoot()', () => {\n      it('gets and sets a new overshoot for the spring controller', () => {\n        const spring = new Spring().overshoot(10)\n        expect(spring.overshoot()).toBe(10)\n      })\n    })\n\n    describe('step()', () => {\n      it('calculates the new spring position', () => {\n        const spring = new Spring()\n        expect(spring.step(0, 100, 16, {})).toBeCloseTo(0.793, 3)\n      })\n\n      it('returns current if current is a string', () => {\n        const spring = new Spring()\n        expect(spring.step('Hallo', 'Welt', 16, {})).toBe('Hallo')\n      })\n\n      it('returns current if dt is 0', () => {\n        const spring = new Spring()\n        expect(spring.step(0, 100, 0, {})).toBe(0)\n      })\n\n      it('is done if dt is infinity and returns target', () => {\n        const spring = new Spring()\n        const context = {}\n        expect(spring.step(0, 100, Infinity, context)).toBe(100)\n        expect(spring.done(context)).toBe(true)\n      })\n\n      it('uses dt of 16 if it is over 100', () => {\n        const spring = new Spring()\n        expect(spring.step(0, 100, 101, {})).toBe(spring.step(0, 100, 16, {}))\n      })\n    })\n  })\n\n  describe('PID', () => {\n    describe('()', () => {\n      it('creates a PID controller with default values', () => {\n        const pid = new PID()\n        expect(pid).toEqual(any(PID))\n        expect(pid.p()).toBe(0.1)\n        expect(pid.i()).toBe(0.01)\n        expect(pid.d()).toBe(0)\n        expect(pid.windup()).toBe(1000)\n      })\n\n      it('creates a PID controller with given values', () => {\n        const pid = new PID(1, 2, 3, 4)\n        expect(pid).toEqual(any(PID))\n        expect(pid.p()).toBe(1)\n        expect(pid.i()).toBe(2)\n        expect(pid.d()).toBe(3)\n        expect(pid.windup()).toBe(4)\n      })\n    })\n\n    describe('p()', () => {\n      it('gets and sets the p parameter of the controller', () => {\n        const pid = new PID().p(100)\n        expect(pid.p()).toBe(100)\n      })\n    })\n\n    describe('i()', () => {\n      it('gets and sets the i parameter of the controller', () => {\n        const pid = new PID().i(100)\n        expect(pid.i()).toBe(100)\n      })\n    })\n\n    describe('d()', () => {\n      it('gets and sets the d parameter of the controller', () => {\n        const pid = new PID().d(100)\n        expect(pid.d()).toBe(100)\n      })\n    })\n\n    describe('windup()', () => {\n      it('gets and sets the windup parameter of the controller', () => {\n        const pid = new PID().windup(100)\n        expect(pid.windup()).toBe(100)\n      })\n    })\n\n    describe('step()', () => {\n      it('returns current if current is a string', () => {\n        const pid = new PID()\n        expect(pid.step('Hallo', 'Welt', 16, {})).toBe('Hallo')\n      })\n\n      it('returns current if dt is 0', () => {\n        const pid = new PID()\n        expect(pid.step(0, 100, 0, {})).toBe(0)\n      })\n\n      it('is done if dt is infinity and returns target', () => {\n        const pid = new PID()\n        const context = {}\n        expect(pid.step(0, 100, Infinity, context)).toBe(100)\n        expect(pid.done(context)).toBe(true)\n      })\n\n      it('calculates a new value', () => {\n        const pid = new PID()\n        expect(pid.step(0, 100, 16, {})).toBe(20)\n      })\n\n      it('uses antiwindup to restrict i power', () => {\n        const pid = new PID(0, 5, 0, 100)\n        expect(pid.step(0, 100, 1000, {})).toBe(500)\n      })\n\n      it('does not use antiwindup if disabled', () => {\n        const pid = new PID(0, 5, 0, false)\n        expect(pid.step(0, 100, 1000, {})).toBe(500000)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/animation/Morphable.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport {\n  Morphable,\n  NonMorphable,\n  ObjectBag,\n  Color,\n  Box,\n  Matrix,\n  PointArray,\n  PathArray,\n  TransformBag,\n  Number as SVGNumber,\n  Array as SVGArray\n} from '../../../src/main.js'\nimport { Stepper, easing, Ease } from '../../../src/animation/Controller.js'\n\nconst { objectContaining, arrayContaining, any } = jasmine\n\ndescribe('Morphable.js', () => {\n  describe('()', () => {\n    it('sets a default stepper', () => {\n      const morpher = new Morphable()\n      expect(morpher.stepper().ease).toBe(easing['-'])\n    })\n\n    it('sets the passed stepper', () => {\n      const ease = new Ease()\n      const morpher = new Morphable(ease)\n      expect(morpher.stepper()).toBe(ease)\n    })\n  })\n\n  describe('constructors', () => {\n    it('Morphable with SVGNumber', () => {\n      const morpher = new Morphable().from(10).to(5)\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(SVGNumber)\n      expect(morpher.at(0.5)).toEqual(any(SVGNumber))\n      expect(morpher.at(0.5).valueOf()).toBe(7.5)\n    })\n\n    it('Morphable with String', () => {\n      const morpher = new Morphable().from('foo').to('bar')\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(NonMorphable)\n      expect(morpher.at(0.5)).toEqual(any(NonMorphable))\n      expect(morpher.at(0.5).valueOf()).toBe('foo')\n      expect(morpher.at(1).valueOf()).toBe('bar')\n    })\n\n    it('Morphable with Object', () => {\n      const morpher = new Morphable().from({ a: 5, b: 10 }).to({ a: 10, b: 20 })\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(ObjectBag)\n      expect(morpher.at(0.5)).toEqual(any(Object))\n      expect(morpher.at(0.5).valueOf()).toEqual(\n        objectContaining({ a: new SVGNumber(7.5), b: new SVGNumber(15) })\n      )\n    })\n\n    it('Morphable from object containing css values', () => {\n      const morpher = new Morphable()\n        .from({ opacity: '0', 'stroke-width': '10px' })\n        .to({ opacity: 1, 'stroke-width': 20 })\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(ObjectBag)\n      expect(morpher.at(0.5)).toEqual(any(Object))\n      expect(morpher.at(0.5).valueOf()).toEqual(\n        objectContaining({\n          opacity: new SVGNumber(0.5),\n          'stroke-width': new SVGNumber('15px')\n        })\n      )\n    })\n\n    it('Creates a morphable out of an SVGNumber', () => {\n      const morpher = new SVGNumber(5).to(10)\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(SVGNumber)\n      expect(morpher.at(0.5)).toEqual(any(SVGNumber))\n      expect(morpher.at(0.5).valueOf()).toBe(7.5)\n    })\n\n    it('Creates a morphable out of an Color', () => {\n      const morpher = new Color('#fff').to('#000')\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(Color)\n      expect(morpher.at(0.5)).toEqual(any(Color))\n      expect(morpher.at(0.5).toHex()).toBe('#808080')\n    })\n\n    it('Creates a morphable out of an Box', () => {\n      const morpher = new Box(1, 2, 3, 4).to([5, 6, 7, 8])\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(Box)\n      expect(morpher.at(0.5)).toEqual(any(Box))\n      expect(morpher.at(0.5)).toEqual(\n        objectContaining({ x: 3, y: 4, width: 5, height: 6 })\n      )\n    })\n\n    it('Creates a morphable out of an Matrix', () => {\n      const morpher = new Matrix(1, 2, 3, 4, 5, 6).to([3, 4, 5, 6, 7, 8])\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(Matrix)\n      expect(morpher.at(0.5)).toEqual(any(Matrix))\n      expect(morpher.at(0.5)).toEqual(\n        objectContaining(new Matrix(2, 3, 4, 5, 6, 7))\n      )\n    })\n\n    it('Creates a morphable out of an SVGArray', () => {\n      const morpher = new SVGArray([1, 2, 3, 4, 5, 6]).to([3, 4, 5, 6, 7, 8])\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(SVGArray)\n      expect(morpher.at(0.5)).toEqual(any(SVGArray))\n      expect(morpher.at(0.5).toArray()).toEqual(\n        arrayContaining([2, 3, 4, 5, 6, 7])\n      )\n    })\n\n    it('Creates a morphable out of an PointArray', () => {\n      const morpher = new PointArray([1, 2, 3, 4, 5, 6]).to([3, 4, 5, 6, 7, 8])\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(PointArray)\n      expect(morpher.at(0.5)).toEqual(any(PointArray))\n      expect(morpher.at(0.5).toArray()).toEqual(\n        arrayContaining([2, 3, 4, 5, 6, 7])\n      )\n    })\n\n    it('Creates a morphable out of an PathArray', () => {\n      const morpher = new PathArray(['M', 1, 2, 'L', 3, 4, 'L', 5, 6]).to([\n        'M',\n        3,\n        4,\n        'L',\n        5,\n        6,\n        'L',\n        7,\n        8\n      ])\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(PathArray)\n      expect(morpher.at(0.5)).toEqual(any(PathArray))\n      expect(morpher.at(0.5).toArray()).toEqual(\n        arrayContaining(['M', 2, 3, 'L', 4, 5, 'L', 6, 7])\n      )\n    })\n\n    it('creates a morphable from unmorphable types', () => {\n      const morpher = new Morphable().from('Hallo').to('Welt')\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(NonMorphable)\n      expect(morpher.at(0.5)).toEqual(any(NonMorphable))\n      expect(morpher.at(0.5).valueOf()).toBe('Hallo')\n      expect(morpher.at(1).valueOf()).toBe('Welt')\n    })\n\n    it('Creates a morphable out of an TransformBag', () => {\n      const morpher = new TransformBag({ rotate: 0, translateX: 0 }).to({\n        rotate: 50,\n        translateX: 20\n      })\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(TransformBag)\n      expect(morpher.at(0.5)).toEqual(any(TransformBag))\n\n      expect(morpher.at(0.5)).toEqual(\n        objectContaining({ rotate: 25, translateX: 10 })\n      )\n    })\n\n    it('Creates a morphable out of an ObjectBag', () => {\n      const morpher = new ObjectBag({ a: 5, b: 10 }).to({ a: 10, b: 20 })\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(ObjectBag)\n      expect(morpher.at(0.5)).toEqual(any(Object))\n      expect(morpher.at(0.5).valueOf()).toEqual(\n        objectContaining({ a: new SVGNumber(7.5), b: new SVGNumber(15) })\n      )\n    })\n\n    it('creates a morphable from a color string', () => {\n      let morpher = new Morphable().from('#fff').to('#000')\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(Color)\n      expect(morpher.at(0.5)).toEqual(any(Color))\n      expect(morpher.at(0.5).toHex()).toBe('#808080')\n\n      morpher = new Morphable().from('rgb(255,255,255)').to('rgb(0,0,0)')\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(Color)\n      expect(morpher.at(0.5)).toEqual(any(Color))\n      expect(morpher.at(0.5).toHex()).toBe('#808080')\n    })\n\n    it('creates a morphable from path string', () => {\n      const morpher = new Morphable().from('M 0 0 L 10 10').to('M 0 0 L 20 20')\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(PathArray)\n      expect(morpher.at(0.5)).toEqual(any(PathArray))\n      expect(morpher.at(0.5).toString()).toBe('M0 0L15 15 ')\n    })\n\n    it('creates a morphable from number string', () => {\n      let morpher = new Morphable().from('10').to('20')\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(SVGNumber)\n      expect(morpher.at(0.5)).toEqual(any(SVGNumber))\n      expect(morpher.at(0.5).toString()).toBe('15')\n\n      morpher = new Morphable().from('10px').to('20px')\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(SVGNumber)\n      expect(morpher.at(0.5)).toEqual(any(SVGNumber))\n      expect(morpher.at(0.5).toString()).toBe('15px')\n    })\n\n    it('creates a morphable from delimited string', () => {\n      const morpher = new Morphable().from(' 0 1,  2  , 3  ').to('4,5,6,7')\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(SVGArray)\n      expect(morpher.at(0.5)).toEqual(any(SVGArray))\n      expect(morpher.at(0.5)).toEqual([2, 3, 4, 5])\n    })\n\n    it('creates a morphable from an array', () => {\n      const morpher = new Morphable().from([0, 1, 2, 3]).to([4, 5, 6, 7])\n\n      expect(morpher).toEqual(any(Morphable))\n      expect(morpher.type()).toBe(SVGArray)\n      expect(morpher.at(0.5)).toEqual(any(SVGArray))\n      expect(morpher.at(0.5)).toEqual([2, 3, 4, 5])\n    })\n\n    it('converts the to-color to the from-type', () => {\n      const morpher = new Color('#fff').to(new Color(1, 2, 3, 'hsl'))\n      expect(new Color(morpher.from()).space).toBe('rgb')\n      expect(morpher.at(0.5).space).toBe('rgb')\n    })\n\n    it('converts the from-color to the to-type', () => {\n      const morpher = new Morphable().to(new Color(1, 2, 3, 'hsl')).from('#fff')\n      expect(new Color(morpher.from()).space).toBe('hsl')\n      expect(morpher.at(0.5).space).toBe('hsl')\n    })\n  })\n\n  describe('from()', () => {\n    it('sets the type of the runner', () => {\n      const morpher = new Morphable().from(5)\n      expect(morpher.type()).toBe(SVGNumber)\n    })\n\n    it('sets the from attribute to an array representation of the morphable type', () => {\n      const morpher = new Morphable().from(5)\n      expect(morpher.from()).toEqual(arrayContaining([5]))\n    })\n  })\n\n  describe('type()', () => {\n    it('sets the type of the runner', () => {\n      const morpher = new Morphable().type(SVGNumber)\n      expect(morpher._type).toBe(SVGNumber)\n    })\n\n    it('gets the type of the runner', () => {\n      const morpher = new Morphable().type(SVGNumber)\n      expect(morpher.type()).toBe(SVGNumber)\n    })\n  })\n\n  describe('to()', () => {\n    it('sets the type of the runner', () => {\n      const morpher = new Morphable().to(5)\n      expect(morpher.type()).toBe(SVGNumber)\n    })\n\n    it('sets the from attribute to an array representation of the morphable type', () => {\n      const morpher = new Morphable().to(5)\n      expect(morpher.to()).toEqual(arrayContaining([5]))\n    })\n  })\n\n  describe('stepper()', () => {\n    it('sets and gets the stepper of the Morphable', () => {\n      const stepper = new Stepper()\n      const morpher = new Morphable().stepper(stepper)\n      expect(morpher.stepper()).toBe(stepper)\n    })\n  })\n\n  describe('NonMorphable', () => {\n    describe('()', () => {\n      it('wraps any type into a NonMorphable from an array', () => {\n        const non = new NonMorphable([5])\n        expect(non.valueOf()).toBe(5)\n      })\n\n      it('wraps any type into a NonMorphable from any type', () => {\n        expect(new NonMorphable(5).valueOf()).toBe(5)\n        expect(new NonMorphable('Hello').valueOf()).toBe('Hello')\n      })\n    })\n\n    describe('toArray()', () => {\n      it('returns array representation of NonMorphable', () => {\n        expect(new NonMorphable(5).toArray()).toEqual([5])\n        expect(new NonMorphable('Hello').toArray()).toEqual(['Hello'])\n      })\n    })\n  })\n\n  describe('TransformBag', () => {\n    describe('()', () => {\n      it('creates an object which holds transformations for morphing by passing array', () => {\n        const bag = new TransformBag([0, 1, 2, 3, 4, 5, 6, 7])\n        expect(bag.toArray()).toEqual([0, 1, 2, 3, 4, 5, 6, 7])\n      })\n\n      it('creates an object which holds transformations for morphing by passing object', () => {\n        const bag = new TransformBag({\n          scaleX: 0,\n          scaleY: 1,\n          shear: 2,\n          rotate: 3,\n          translateX: 4,\n          translateY: 5,\n          originX: 6,\n          originY: 7\n        })\n\n        expect(bag.toArray()).toEqual([0, 1, 2, 3, 4, 5, 6, 7])\n      })\n    })\n\n    describe('toArray()', () => {\n      it('creates an array out of the transform values', () => {\n        const bag = new TransformBag([0, 1, 2, 3, 4, 5, 6, 7])\n        expect(bag.toArray()).toEqual([0, 1, 2, 3, 4, 5, 6, 7])\n      })\n    })\n  })\n\n  describe('ObjectBag', () => {\n    describe('()', () => {\n      it('wraps an object into a morphable object by passing an array', () => {\n        const bag = new ObjectBag([\n          'foo',\n          SVGNumber,\n          2,\n          1,\n          '',\n          'bar',\n          SVGNumber,\n          2,\n          2,\n          '',\n          'baz',\n          SVGNumber,\n          2,\n          3,\n          ''\n        ])\n        expect(bag.values).toEqual([\n          'foo',\n          SVGNumber,\n          2,\n          1,\n          '',\n          'bar',\n          SVGNumber,\n          2,\n          2,\n          '',\n          'baz',\n          SVGNumber,\n          2,\n          3,\n          ''\n        ])\n      })\n\n      it('wraps an object into a morphable object by passing an object', () => {\n        const bag = new ObjectBag({ foo: 1, bar: 2, baz: 3 })\n        expect(bag.values).toEqual([\n          'bar',\n          SVGNumber,\n          2,\n          2,\n          '',\n          'baz',\n          SVGNumber,\n          2,\n          3,\n          '',\n          'foo',\n          SVGNumber,\n          2,\n          1,\n          ''\n        ])\n      })\n\n      it('wraps an object with morphable values in an ObjectBag', () => {\n        const bag = new ObjectBag({ fill: new Color(), bar: 2 })\n        expect(bag.values).toEqual([\n          'bar',\n          SVGNumber,\n          2,\n          2,\n          '',\n          'fill',\n          Color,\n          5,\n          0,\n          0,\n          0,\n          0,\n          'rgb'\n        ])\n      })\n\n      it('wraps an array with morphable representation in an ObjectBag', () => {\n        const bag = new ObjectBag([\n          'bar',\n          SVGNumber,\n          2,\n          2,\n          '',\n          'fill',\n          Color,\n          5,\n          0,\n          0,\n          0,\n          0,\n          'rgb'\n        ])\n        expect(bag.toArray()).toEqual([\n          'bar',\n          SVGNumber,\n          2,\n          2,\n          '',\n          'fill',\n          Color,\n          5,\n          0,\n          0,\n          0,\n          0,\n          'rgb'\n        ])\n      })\n    })\n\n    describe('toArray()', () => {\n      it('creates an array out of the object', () => {\n        const bag = new ObjectBag({ foo: 1, bar: 2, baz: 3 })\n        expect(bag.toArray()).toEqual([\n          'bar',\n          SVGNumber,\n          2,\n          2,\n          '',\n          'baz',\n          SVGNumber,\n          2,\n          3,\n          '',\n          'foo',\n          SVGNumber,\n          2,\n          1,\n          ''\n        ])\n      })\n\n      it('creates a flattened array out of the object with morphable values', () => {\n        const bag = new ObjectBag({ fill: new Color(), bar: 2 })\n        expect(bag.toArray()).toEqual([\n          'bar',\n          SVGNumber,\n          2,\n          2,\n          '',\n          'fill',\n          Color,\n          5,\n          0,\n          0,\n          0,\n          0,\n          'rgb'\n        ])\n      })\n    })\n\n    describe('valueOf()', () => {\n      it('creates morphable objects from the stored values', () => {\n        const bag = new ObjectBag({ foo: 1, bar: 2, baz: 3 })\n        expect(bag.valueOf()).toEqual({\n          foo: new SVGNumber(1),\n          bar: new SVGNumber(2),\n          baz: new SVGNumber(3)\n        })\n      })\n\n      it('creates also morphable objects from the stored values', () => {\n        const bag = new ObjectBag({ fill: new Color(), bar: 2 })\n        expect(bag.valueOf()).toEqual({\n          fill: objectContaining(new Color()),\n          bar: new SVGNumber(2)\n        })\n      })\n    })\n\n    describe('align()', () => {\n      it('aligns color spaces between two object bags', () => {\n        const bag1 = new ObjectBag({ x: 1, y: '#fff' })\n        const bag2 = new ObjectBag({ x: 2, y: new Color().hsl() })\n        bag1.align(bag2.toArray())\n        expect(bag1.toArray()).toEqual([\n          'x',\n          SVGNumber,\n          2,\n          1,\n          '',\n          'y',\n          Color,\n          5,\n          0,\n          0,\n          100,\n          0,\n          'hsl'\n        ])\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/animation/Queue.js",
    "content": "/* globals describe, expect, it */\n\nimport { Queue } from '../../../src/main.js'\n\ndescribe('Queue.js', function () {\n  describe('first ()', function () {\n    it('returns null if no item in the queue', function () {\n      var queue = new Queue()\n      expect(queue.first()).toEqual(null)\n    })\n\n    it('returns the first value in the queue', function () {\n      var queue = new Queue()\n      queue.push(1)\n      expect(queue.first()).toBe(1)\n      queue.push(2)\n      expect(queue.first()).toBe(1)\n    })\n  })\n\n  describe('last ()', function () {\n    it('returns null if no item in the queue', function () {\n      var queue = new Queue()\n      expect(queue.last()).toEqual(null)\n    })\n\n    it('returns the last value added', function () {\n      var queue = new Queue()\n      queue.push(1)\n      expect(queue.last()).toBe(1)\n      queue.push(2)\n      expect(queue.last()).toBe(2)\n    })\n  })\n\n  describe('push ()', function () {\n    it('adds an element to the end of the queue', function () {\n      var queue = new Queue()\n      queue.push(1)\n      queue.push(2)\n      queue.push(3)\n\n      expect(queue.first()).toBe(1)\n      expect(queue.last()).toBe(3)\n    })\n\n    it('adds an item to the end of the queue', function () {\n      var queue = new Queue()\n      queue.push(1)\n      const item = queue.push(2)\n      queue.push(3)\n      queue.remove(item)\n      queue.push(item)\n\n      expect(queue.first()).toBe(1)\n      expect(queue.last()).toBe(2)\n    })\n  })\n\n  describe('remove ()', function () {\n    it('removes the given item from the queue', function () {\n      var queue = new Queue()\n      queue.push(1)\n      queue.push(2)\n      var item = queue.push(3)\n\n      queue.remove(item)\n\n      expect(queue.last()).toBe(2)\n      expect(queue.first()).toBe(1)\n    })\n\n    it('removes the given item from the queue', function () {\n      var queue = new Queue()\n      var item = queue.push(1)\n      queue.push(2)\n      queue.push(3)\n\n      queue.remove(item)\n\n      expect(queue.last()).toBe(3)\n      expect(queue.first()).toBe(2)\n    })\n  })\n\n  describe('shift ()', function () {\n    it('returns nothing if queue is empty', function () {\n      var queue = new Queue()\n      var val = queue.shift()\n      expect(val).toBeFalsy()\n    })\n\n    it('returns the first item of the queue and removes it', function () {\n      var queue = new Queue()\n      queue.push(1)\n      queue.push(2)\n      queue.push(3)\n\n      var val = queue.shift()\n\n      expect(queue.last()).toBe(3)\n      expect(queue.first()).toBe(2)\n\n      expect(val).toBe(1)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/animation/Runner.js",
    "content": "/* globals describe, expect, it, beforeEach, afterEach, spyOn, jasmine */\n\nimport {\n  Runner,\n  defaults,\n  Ease,\n  Controller,\n  SVG,\n  Timeline,\n  Rect,\n  Morphable,\n  Animator,\n  Queue,\n  Matrix,\n  Color,\n  Box,\n  Polygon,\n  PointArray\n} from '../../../src/main.js'\nimport { FakeRunner, RunnerArray } from '../../../src/animation/Runner.js'\nimport { getWindow } from '../../../src/utils/window.js'\nimport SVGNumber from '../../../src/types/SVGNumber.js'\n\nconst { any, createSpy, objectContaining, arrayContaining } = jasmine\n\ndescribe('Runner.js', () => {\n  describe('Runner', () => {\n    var initFn = createSpy('initFn')\n    var runFn = createSpy('runFn')\n\n    beforeEach(() => {\n      jasmine.RequestAnimationFrame.install(getWindow())\n      Animator.timeouts = new Queue()\n      Animator.frames = new Queue()\n      Animator.immediates = new Queue()\n      Animator.nextDraw = null\n      initFn.calls.reset()\n      runFn.calls.reset()\n    })\n\n    afterEach(() => {\n      jasmine.RequestAnimationFrame.uninstall(getWindow())\n    })\n\n    describe('sanitise()', () => {\n      it('can handle all form of input', () => {\n        var fn = Runner.sanitise\n\n        expect(fn(200, 200, 'now')).toEqual(\n          objectContaining({\n            duration: 200,\n            delay: 200,\n            when: 'now',\n            times: 1,\n            wait: 0,\n            swing: false\n          })\n        )\n\n        expect(fn(200, 200)).toEqual(\n          objectContaining({\n            duration: 200,\n            delay: 200,\n            when: 'last',\n            times: 1,\n            wait: 0,\n            swing: false\n          })\n        )\n\n        expect(fn(200)).toEqual(\n          objectContaining({\n            duration: 200,\n            delay: defaults.timeline.delay,\n            when: 'last',\n            times: 1,\n            wait: 0,\n            swing: false\n          })\n        )\n\n        expect(fn(runFn)).toEqual(\n          objectContaining({\n            duration: runFn,\n            delay: defaults.timeline.delay,\n            when: 'last',\n            times: 1,\n            wait: 0,\n            swing: false\n          })\n        )\n\n        expect(fn({ delay: 200 })).toEqual(\n          objectContaining({\n            duration: defaults.timeline.duration,\n            delay: 200,\n            when: 'last',\n            times: 1,\n            wait: 0,\n            swing: false\n          })\n        )\n\n        expect(\n          fn({ times: 3, delay: 200, when: 'now', swing: true, wait: 200 })\n        ).toEqual(\n          objectContaining({\n            duration: defaults.timeline.duration,\n            delay: 200,\n            when: 'now',\n            times: 3,\n            wait: 200,\n            swing: true\n          })\n        )\n      })\n    })\n\n    describe('())', () => {\n      it('creates a runner with defaults', () => {\n        var runner = new Runner()\n        expect(runner instanceof Runner).toBe(true)\n        expect(runner._duration).toBe(defaults.timeline.duration)\n        expect(runner._stepper instanceof Ease).toBe(true)\n      })\n\n      it('creates a runner with duration set', () => {\n        var runner = new Runner(1000)\n        expect(runner instanceof Runner).toBe(true)\n        expect(runner._duration).toBe(1000)\n        expect(runner._stepper instanceof Ease).toBe(true)\n      })\n\n      it('creates a runner with controller set', () => {\n        var runner = new Runner(runFn)\n        expect(runner instanceof Runner).toBe(true)\n        expect(runner._duration).toBeFalsy()\n        expect(runner._stepper instanceof Controller).toBe(true)\n      })\n    })\n\n    describe('queue()', () => {\n      it('adds another closure to the runner', () => {\n        var runner = new Runner()\n        runner.queue(initFn, runFn, true)\n\n        expect(runner._queue[0]).toEqual(\n          objectContaining({\n            initialiser: initFn,\n            initialised: false,\n            runner: runFn,\n            finished: false\n          })\n        )\n      })\n    })\n\n    describe('step()', () => {\n      it('returns itself', () => {\n        var runner = new Runner()\n        expect(runner.step()).toBe(runner)\n      })\n\n      it('does nothing when not active', () => {\n        const runner = new Runner().active(false)\n        const frozen = Object.freeze(runner)\n        expect(frozen.step()).toEqual(runner)\n      })\n\n      it('calls initFn once and runFn at every step', () => {\n        var runner = new Runner()\n        runner.queue(initFn, runFn, false)\n\n        runner.step()\n        expect(initFn).toHaveBeenCalled()\n        expect(runFn).toHaveBeenCalled()\n\n        runner.step()\n        expect(initFn.calls.count()).toBe(1)\n        expect(runFn.calls.count()).toBe(2)\n      })\n\n      it('calls initFn on every step if its declarative', () => {\n        var runner = new Runner(new Controller())\n        runner.queue(initFn, runFn, true)\n\n        runner.step()\n        expect(initFn).toHaveBeenCalled()\n        expect(runFn).toHaveBeenCalled()\n\n        runner.step()\n        expect(initFn.calls.count()).toBe(2)\n        expect(runFn.calls.count()).toBe(2)\n      })\n\n      function getLoop(r) {\n        var loopDuration = r._duration + r._wait\n        var loopsDone = Math.floor(r._time / loopDuration)\n        return loopsDone\n      }\n\n      // step in time\n      it('steps forward a certain time', () => {\n        var spy = createSpy('stepper')\n        var r = new Runner(1000).loop(10, false, 100)\n        r.queue(null, spy)\n\n        r.step(300) // should be 0.3s\n        expect(spy).toHaveBeenCalledWith(0.3)\n        expect(getLoop(r)).toBe(0)\n\n        r.step(300) // should be 0.6s\n        expect(spy).toHaveBeenCalledWith(0.6)\n        expect(getLoop(r)).toBe(0)\n\n        r.step(600) // should be 0.1s\n        expect(spy).toHaveBeenCalledWith(0.1)\n        expect(getLoop(r)).toBe(1)\n\n        r.step(-300) // should be 0.9s\n        expect(spy).toHaveBeenCalledWith(0.9)\n        expect(getLoop(r)).toBe(0)\n\n        r.step(2000) // should be 0.7s\n        expect(spy).toHaveBeenCalledWith(0.7)\n        expect(getLoop(r)).toBe(2)\n\n        r.step(-2000) // should be 0.9s\n        expect(spy).toHaveBeenCalledWith(0.9)\n        expect(getLoop(r)).toBe(0)\n      })\n\n      it('handles dts which are bigger than the animation time', () => {\n        var runner = new Runner(1000)\n        runner.queue(initFn, runFn, true)\n\n        runner.step(1100)\n        expect(initFn).toHaveBeenCalled()\n        expect(runFn).toHaveBeenCalledWith(1)\n      })\n\n      describe('looping', () => {\n        describe('without wait', () => {\n          describe('unreversed', () => {\n            describe('nonswinging', () => {\n              it('does behave correctly at the end of an even loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(6, false)\n                runner.queue(null, spy)\n\n                runner.step(5750)\n                expect(spy).toHaveBeenCalledWith(0.75)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(1)\n              })\n\n              it('does behave correctly at the end of an uneven loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(5, false)\n                runner.queue(null, spy)\n\n                runner.step(4750)\n                expect(spy).toHaveBeenCalledWith(0.75)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(1)\n              })\n            })\n\n            describe('swinging', () => {\n              it('does behave correctly at the end of an even loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(6, true)\n                runner.queue(null, spy)\n\n                runner.step(5750)\n                expect(spy).toHaveBeenCalledWith(0.25)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(0)\n              })\n\n              it('does behave correctly at the end of an uneven loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(5, true)\n                runner.queue(null, spy)\n\n                runner.step(4750)\n                expect(spy).toHaveBeenCalledWith(0.75)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(1)\n              })\n            })\n          })\n\n          describe('reversed', () => {\n            describe('nonswinging', () => {\n              it('does behave correctly at the end of an even loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(6, false).reverse()\n                runner.queue(null, spy)\n\n                runner.step(5750)\n                expect(spy).toHaveBeenCalledWith(0.25)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(0)\n              })\n\n              it('does behave correctly at the end of an uneven loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(5, false).reverse()\n                runner.queue(null, spy)\n\n                runner.step(4750)\n                expect(spy).toHaveBeenCalledWith(0.25)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(0)\n              })\n            })\n\n            describe('swinging', () => {\n              it('does behave correctly at the end of an even loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(6, true).reverse()\n                runner.queue(null, spy)\n\n                runner.step(5750)\n                expect(spy).toHaveBeenCalledWith(0.75)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(1)\n              })\n\n              it('does behave correctly at the end of an uneven loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(5, true).reverse()\n                runner.queue(null, spy)\n\n                runner.step(4750)\n                expect(spy).toHaveBeenCalledWith(0.25)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(0)\n              })\n            })\n          })\n        })\n\n        describe('with wait', () => {\n          describe('unreversed', () => {\n            describe('nonswinging', () => {\n              it('does behave correctly at the end of an even loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(6, false, 100)\n                runner.queue(null, spy)\n\n                runner.step(5450)\n                expect(spy).toHaveBeenCalledWith(1)\n                spy.calls.reset()\n\n                runner.step(800)\n                expect(spy).toHaveBeenCalledWith(0.75)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(1)\n              })\n\n              it('does behave correctly at the end of an uneven loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(5, false, 100)\n                runner.queue(null, spy)\n\n                runner.step(4350)\n                expect(spy).toHaveBeenCalledWith(1)\n                spy.calls.reset()\n\n                runner.step(800)\n                expect(spy).toHaveBeenCalledWith(0.75)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(1)\n              })\n            })\n\n            describe('swinging', () => {\n              it('does behave correctly at the end of an even loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(6, true, 100)\n                runner.queue(null, spy)\n\n                runner.step(5450)\n                expect(spy).toHaveBeenCalledWith(1)\n                spy.calls.reset()\n\n                runner.step(800)\n                expect(spy).toHaveBeenCalledWith(0.25)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(0)\n              })\n\n              it('does behave correctly at the end of an uneven loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(5, true, 100)\n                runner.queue(null, spy)\n\n                runner.step(4350)\n                expect(spy).toHaveBeenCalledWith(0)\n                spy.calls.reset()\n\n                runner.step(800)\n                expect(spy).toHaveBeenCalledWith(0.75)\n\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(1)\n              })\n            })\n          })\n\n          describe('reversed', () => {\n            describe('nonswinging', () => {\n              it('does behave correctly at the end of an even loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(6, false, 100).reverse()\n                runner.queue(null, spy)\n\n                runner.step(5450)\n                expect(spy).toHaveBeenCalledWith(0)\n                spy.calls.reset()\n\n                runner.step(800)\n                expect(spy).toHaveBeenCalledWith(0.25)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(0)\n              })\n\n              it('does behave correctly at the end of an uneven loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(5, false, 100).reverse()\n                runner.queue(null, spy)\n\n                runner.step(4350)\n                expect(spy).toHaveBeenCalledWith(0)\n                spy.calls.reset()\n\n                runner.step(800)\n                expect(spy).toHaveBeenCalledWith(0.25)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(0)\n              })\n            })\n\n            describe('swinging', () => {\n              it('does behave correctly at the end of an even loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(6, true, 100).reverse()\n                runner.queue(null, spy)\n\n                runner.step(5450)\n                expect(spy).toHaveBeenCalledWith(0)\n                spy.calls.reset()\n\n                runner.step(800)\n                expect(spy).toHaveBeenCalledWith(0.75)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(1)\n              })\n\n              it('does behave correctly at the end of an uneven loop', () => {\n                var spy = createSpy('stepper')\n                var runner = new Runner(1000).loop(5, true, 100).reverse()\n                runner.queue(null, spy)\n\n                runner.step(4350)\n                expect(spy).toHaveBeenCalledWith(1)\n                spy.calls.reset()\n\n                runner.step(800)\n                expect(spy).toHaveBeenCalledWith(0.25)\n                runner.step(250)\n                expect(spy).toHaveBeenCalledWith(0)\n              })\n            })\n          })\n        })\n      })\n    })\n\n    describe('active()', () => {\n      it('acts as a getter without parameters', () => {\n        var runner = new Runner()\n        expect(runner.active()).toBe(true)\n      })\n\n      it('disables the runner when false is passed', () => {\n        var runner = new Runner()\n        expect(runner.active(false)).toBe(runner)\n        expect(runner.active()).toBe(false)\n      })\n\n      it('enables the runner when true is passed', () => {\n        var runner = new Runner()\n        expect(runner.active(false)).toBe(runner)\n        expect(runner.active(true)).toBe(runner)\n        expect(runner.active()).toBe(true)\n      })\n    })\n\n    describe('duration()', () => {\n      it('return the full duration of the runner including all loops and waits', () => {\n        var runner = new Runner(800).loop(10, true, 200)\n        expect(runner.duration()).toBe(9800)\n      })\n    })\n\n    describe('loop()', () => {\n      it('makes this runner looping', () => {\n        var runner = new Runner(1000).loop(5)\n        expect(runner.duration()).toBe(5000)\n      })\n\n      it('makes this runner indefinitey by passing true', () => {\n        var runner = new Runner(1000).loop(true)\n        expect(runner.duration()).toBe(Infinity)\n      })\n\n      it('makes this runner indefinitey by passing nothing', () => {\n        var runner = new Runner(1000).loop()\n        expect(runner.duration()).toBe(Infinity)\n      })\n    })\n\n    describe('time()', () => {\n      it('returns itself', () => {\n        var runner = new Runner()\n        expect(runner.time(0)).toBe(runner)\n      })\n\n      it('acts as a getter with no parameter passed', () => {\n        var runner = new Runner()\n        expect(runner.time()).toBe(0)\n      })\n\n      it('reschedules the runner to a new time', () => {\n        var runner = new Runner()\n        runner.time(10)\n\n        expect(runner.time()).toBe(10)\n      })\n\n      it('calls step to reschedule', () => {\n        var runner = new Runner()\n        spyOn(runner, 'step')\n        runner.time(10)\n\n        expect(runner.step).toHaveBeenCalledWith(10)\n      })\n    })\n\n    describe('loops()', () => {\n      it('get the loops of a runner', () => {\n        var spy = createSpy('stepper')\n        var runner = new Runner(1000).queue(null, spy)\n\n        runner.step(300)\n        expect(spy).toHaveBeenCalledWith(0.3)\n\n        expect(runner.loops()).toBe(0.3)\n      })\n      it('sets the loops of the runner', () => {\n        var spy = createSpy('stepper')\n        var runner = new Runner(1000).queue(null, spy)\n\n        expect(runner.loops(0.5).loops()).toBe(0.5)\n        expect(spy).toHaveBeenCalledWith(0.5)\n\n        expect(runner.loops(0.1).loops()).toBe(0.1)\n        expect(spy).toHaveBeenCalledWith(0.1)\n\n        expect(runner.loops(1.5).loops()).toBe(1)\n        expect(spy).toHaveBeenCalledWith(1)\n      })\n      it('sets the loops of the runner in a loop', () => {\n        var spy = createSpy('stepper')\n        var runner = new Runner(1000).loop(5, true, 500).queue(null, spy)\n\n        expect(runner.loops(1.3).loops()).toBe(1.3)\n        expect(spy).toHaveBeenCalledWith(0.7)\n\n        expect(runner.loops(0.3).loops()).toBe(0.3)\n      })\n    })\n\n    describe('progress()', () => {\n      it('gets the progress of a runner', () => {\n        var spy = createSpy('stepper')\n        var runner = new Runner(1000).queue(null, spy)\n\n        runner.step(300)\n        expect(spy).toHaveBeenCalledWith(0.3)\n\n        expect(runner.progress()).toBe(0.3)\n      })\n\n      it('gets the progress of a runner when looping', () => {\n        var spy = createSpy('stepper')\n        var runner = new Runner(800).queue(null, spy).loop(10, false, 200) // duration should be 9800\n\n        // middle of animation, in the middle of wait time\n        runner.step(4900)\n        expect(runner.progress()).toBe(0.5)\n        expect(spy).toHaveBeenCalledWith(1)\n\n        // start of next loop\n        runner.step(100)\n        expect(spy).toHaveBeenCalledWith(0)\n\n        // move 400 into current loop which is 0.5 progress\n        // the progress value is 5400 / 9800\n        runner.step(400)\n        expect(spy).toHaveBeenCalledWith(0.5)\n        expect(runner.progress()).toBe(5400 / 9800)\n      })\n\n      it('sets the progress of a runner', () => {\n        var spy = createSpy('stepper')\n        var runner = new Runner(1000).queue(null, spy)\n\n        expect(runner.progress(0.5).progress()).toBe(0.5)\n        expect(spy).toHaveBeenCalledWith(0.5)\n      })\n\n      it('sets the progress of a runner when looping', () => {\n        var spy = createSpy('stepper')\n        var runner = new Runner(800).queue(null, spy).loop(10, false, 200)\n\n        // progress 0.5 somewhere in the middle of wait time\n        expect(runner.progress(0.5).progress()).toBe(0.5)\n        expect(spy).toHaveBeenCalledWith(1)\n\n        // start of next loop\n        runner.step(100)\n        expect(spy).toHaveBeenCalledWith(0)\n\n        // should move 0.5 into the next loop\n        expect(runner.progress(5400 / 9800).progress()).toBe(5400 / 9800)\n        expect(spy.calls.mostRecent().args[0]).toBeCloseTo(0.5)\n      })\n    })\n\n    describe('position()', () => {\n      it('gets the position of a runner', () => {\n        var spy = createSpy('stepper')\n        var runner = new Runner(1000).queue(null, spy)\n\n        runner.step(300)\n        expect(spy).toHaveBeenCalledWith(0.3)\n\n        expect(runner.position()).toBe(0.3)\n      })\n\n      it('gets the position of a runner when looping', () => {\n        var spy = createSpy('stepper')\n        var runner = new Runner(1000).loop(5, true, 100).queue(null, spy)\n\n        runner.step(1200)\n        expect(spy).toHaveBeenCalledWith(0.9)\n\n        expect(runner.position()).toBe(0.9)\n      })\n\n      it('sets the position of a runner', () => {\n        var spy = createSpy('stepper')\n        var runner = new Runner(1000).queue(null, spy)\n\n        expect(runner.position(0.5).position()).toBe(0.5)\n        expect(spy).toHaveBeenCalledWith(0.5)\n      })\n\n      it('sets the position of a runner in a loop', () => {\n        var spy = createSpy('stepper')\n        var runner = new Runner(1000).loop(5, true, 100).queue(null, spy)\n\n        runner.step(1200)\n        expect(runner.position(0.4).position()).toBe(0.4)\n        expect(spy).toHaveBeenCalledWith(0.4)\n\n        expect(runner.position(0).position()).toBe(0)\n        expect(spy).toHaveBeenCalledWith(0)\n\n        expect(runner.position(1).position()).toBe(1)\n        expect(spy).toHaveBeenCalledWith(1)\n      })\n    })\n\n    describe('element()', () => {\n      it('returns the element bound to this runner if any', () => {\n        var runner1 = new Runner()\n        expect(runner1.element()).toBe(null)\n\n        var element = SVG('<rect />')\n        var runner2 = element.animate()\n        expect(runner2.element()).toBe(element)\n      })\n\n      it('sets an element to be bound to the runner', () => {\n        var runner = new Runner()\n        var element = SVG('<rect />')\n        expect(runner.element(element)).toBe(runner)\n        expect(runner.element()).toBe(element)\n      })\n    })\n\n    describe('timeline()', () => {\n      it('returns the timeline bound to this runner if any', () => {\n        var runner1 = new Runner()\n        expect(runner1.element()).toBe(null)\n\n        var element = SVG('<rect />')\n        var runner2 = element.animate()\n        expect(runner2.timeline()).toBe(element.timeline())\n      })\n\n      it('sets a timeline to be bound to the runner', () => {\n        var runner = new Runner()\n        var timeline = new Timeline()\n        expect(runner.timeline(timeline)).toBe(runner)\n        expect(runner.timeline()).toBe(timeline)\n      })\n    })\n\n    describe('schedule()', () => {\n      it('schedules the runner on a timeline', () => {\n        var runner = new Runner()\n        var timeline = new Timeline()\n        var spy = spyOn(timeline, 'schedule').and.callThrough()\n\n        expect(runner.schedule(timeline, 200, 'now')).toBe(runner)\n        expect(runner.timeline()).toBe(timeline)\n        expect(spy).toHaveBeenCalledWith(runner, 200, 'now')\n      })\n\n      it('schedules the runner on its own timeline', () => {\n        var runner = new Runner()\n        var timeline = new Timeline()\n        var spy = spyOn(timeline, 'schedule')\n        runner.timeline(timeline)\n\n        expect(runner.schedule(200, 'now')).toBe(runner)\n        expect(runner.timeline()).toBe(timeline)\n        expect(spy).toHaveBeenCalledWith(runner, 200, 'now')\n      })\n\n      it('throws if no timeline is given', () => {\n        var runner = new Runner()\n        expect(() => runner.schedule(200, 'now')).toThrowError(\n          'Runner cannot be scheduled without timeline'\n        )\n      })\n    })\n\n    describe('unschedule()', () => {\n      it('unschedules this runner from its timeline', () => {\n        var runner = new Runner()\n        var timeline = new Timeline()\n        var spy = spyOn(timeline, 'unschedule').and.callThrough()\n\n        expect(runner.schedule(timeline, 200, 'now')).toBe(runner)\n        expect(runner.unschedule()).toBe(runner)\n        expect(spy).toHaveBeenCalledWith(runner)\n        expect(runner.timeline()).toBe(null)\n      })\n    })\n\n    describe('animate()', () => {\n      it('creates a new runner scheduled after the first', () => {\n        var runner = new Runner(1000)\n        var timeline = new Timeline()\n\n        runner.schedule(timeline)\n\n        var runner2 = runner.animate(500, 1000)\n\n        var t = timeline.time()\n\n        expect(runner2.timeline()).toBe(timeline)\n        expect(runner2.time()).toBe(0)\n\n        expect(timeline.schedule()).toEqual(\n          arrayContaining([\n            objectContaining({\n              start: t,\n              duration: 1000,\n              end: t + 1000,\n              runner: runner\n            }),\n            objectContaining({\n              start: t + 2000,\n              duration: 500,\n              end: t + 2500,\n              runner: runner2\n            })\n          ])\n        )\n      })\n\n      it('reuses timeline and element of current runner', () => {\n        const element = new Rect()\n        const timeline = new Timeline()\n        const runner = new Runner().element(element).timeline(timeline)\n        const after = runner.animate()\n        expect(after.timeline()).toBe(timeline)\n        expect(after.element()).toBe(element)\n      })\n\n      it('does not reuse element if not set', () => {\n        const timeline = new Timeline()\n        const runner = new Runner().timeline(timeline)\n        const after = runner.animate()\n        expect(after.element()).toBe(null)\n      })\n    })\n\n    describe('delay()', () => {\n      it('calls animate with delay parameters', () => {\n        var runner = new Runner(1000)\n        spyOn(runner, 'animate')\n\n        runner.delay(500)\n        expect(runner.animate).toHaveBeenCalledWith(0, 500)\n      })\n    })\n\n    describe('during()', () => {\n      it('returns itself', () => {\n        var runner = new Runner()\n        expect(runner.during(runFn)).toBe(runner)\n      })\n\n      it('calls queue passing only a function to call on every step', () => {\n        var runner = new Runner()\n        spyOn(runner, 'queue')\n        runner.during(runFn)\n\n        expect(runner.queue).toHaveBeenCalledWith(null, runFn)\n      })\n    })\n\n    describe('after()', () => {\n      it('returns itself', () => {\n        var runner = new Runner()\n        expect(runner.after(runFn)).toBe(runner)\n      })\n\n      it('binds a function to the after event', () => {\n        var runner = new Runner()\n        spyOn(runner, 'on')\n        runner.after(runFn)\n\n        expect(runner.on).toHaveBeenCalledWith('finished', runFn)\n      })\n    })\n\n    describe('finish()', () => {\n      it('returns itself', () => {\n        var runner = new Runner()\n        expect(runner.finish()).toBe(runner)\n      })\n\n      it('calls step with Infinity as argument', () => {\n        var runner = new Runner()\n        spyOn(runner, 'step')\n        runner.finish()\n\n        expect(runner.step).toHaveBeenCalledWith(Infinity)\n      })\n    })\n\n    describe('reverse()', () => {\n      it('returns itself', () => {\n        var runner = new Runner()\n        expect(runner.reverse()).toBe(runner)\n      })\n\n      it('reverses the runner', () => {\n        var spy = createSpy('stepper')\n        var runner = new Runner(1000).reverse().queue(null, spy)\n        runner.step(750)\n        expect(spy).toHaveBeenCalledWith(0.25)\n      })\n\n      it('reverses the runner when true is passed', () => {\n        var spy = createSpy('stepper')\n        var runner = new Runner(1000).reverse(true).queue(null, spy)\n        runner.step(750)\n        expect(spy).toHaveBeenCalledWith(0.25)\n      })\n\n      it('unreverses the runner when true is passed', () => {\n        var spy = createSpy('stepper')\n        var runner = new Runner(1000).reverse(false).queue(null, spy)\n        runner.step(750)\n        expect(spy).toHaveBeenCalledWith(0.75)\n      })\n    })\n\n    describe('ease()', () => {\n      it('returns itself', () => {\n        var runner = new Runner()\n        expect(runner.ease(() => {})).toBe(runner)\n      })\n\n      it('creates an easing Controller from the easing function', () => {\n        var runner = new Runner()\n        runner.ease(() => {})\n\n        expect(runner._stepper instanceof Ease).toBe(true)\n      })\n    })\n\n    describe('reset()', () => {\n      it('resets the runner by setting it to time 0', () => {\n        var runner = new Runner().step(16)\n        expect(runner.time()).toBe(16)\n        expect(runner.reset()).toBe(runner)\n        expect(runner.time()).toBe(0)\n      })\n\n      it('does not reset if already reset', () => {\n        var runner = Object.freeze(new Runner().reset())\n        expect(runner.reset()).toBe(runner)\n      })\n    })\n\n    describe('private Methods', () => {\n      describe('_rememberMorpher()', () => {\n        it('adds a morpher for a method to the runner', () => {\n          const runner = new Runner()\n          const morpher = new Morphable()\n          runner._rememberMorpher('move', morpher)\n          expect(runner._history.move).toEqual({ morpher, caller: undefined })\n        })\n\n        it('resumes the timeline in case this runner uses a controller', () => {\n          const timeline = new Timeline()\n          const spy = spyOn(timeline, 'play')\n          const runner = new Runner(new Controller(() => 0)).timeline(timeline)\n          const morpher = new Morphable()\n          runner._rememberMorpher('move', morpher)\n          expect(spy).toHaveBeenCalled()\n        })\n      })\n\n      describe('_tryRetarget()', () => {\n        it('tries to retarget a morpher for the animation and returns true', () => {\n          const rect = new Rect().move(0, 0)\n          const runner = rect.animate().move(10, 10)\n          jasmine.RequestAnimationFrame.tick(16)\n          expect(runner._tryRetarget('x', 20)).toBe(true)\n          expect(runner._history.x.morpher.to()).toEqual([20, ''])\n        })\n\n        it('throws away the morpher if it was not initialized yet and returns false', () => {\n          const rect = new Rect().move(0, 0)\n          const runner = rect.animate().move(10, 10)\n          // In that case tryRetarget is not successful\n          expect(runner._tryRetarget('x', 20)).toBe(false)\n        })\n\n        it('does nothing if method was not found', () => {\n          const rect = new Rect().move(0, 0)\n          const runner = rect.animate().move(10, 10)\n          jasmine.RequestAnimationFrame.tick(16)\n          // In that case tryRetarget is not successful\n          expect(runner._tryRetarget('foo', 20)).toBe(false)\n        })\n\n        it('does only work with controller for transformations and uses retarget function when retargeting transformations', () => {\n          const rect = new Rect()\n          const runner = rect\n            .animate(new Controller(() => 0))\n            .transform({ translate: [10, 10] })\n          jasmine.RequestAnimationFrame.tick(16)\n          // In that case tryRetarget is not successful\n          expect(\n            runner._tryRetarget('transform', { translate: [20, 20] })\n          ).toBe(true)\n        })\n\n        it('starts the timeline if retarget was successful', () => {\n          const timeline = new Timeline()\n          const rect = new Rect().move(0, 0).timeline(timeline)\n          const runner = rect.animate().move(10, 10)\n          jasmine.RequestAnimationFrame.tick(16)\n          const spy = spyOn(timeline, 'play')\n          expect(runner._tryRetarget('x', 20)).toBe(true)\n          expect(runner._history.x.morpher.to()).toEqual([20, ''])\n          expect(spy).toHaveBeenCalledTimes(1)\n        })\n      })\n\n      describe('_initialise', () => {\n        it('does nothing if false is passed', () => {\n          const runner = Object.freeze(new Runner())\n          expect(runner._initialise(false)).toBe(undefined)\n        })\n\n        it('does nothing if true is passed and runner is not declarative', () => {\n          const runner = Object.freeze(new Runner())\n          expect(runner._initialise(true)).toBe(undefined)\n        })\n\n        it('calls the initializer function on the queue when runner is declarative', () => {\n          const runner = new Runner(() => 0).queue(initFn, runFn)\n          runner._initialise()\n          expect(initFn).toHaveBeenCalledTimes(1)\n        })\n\n        it('calls the initializer function on the queue when true is passed and runner is not declarative', () => {\n          const runner = new Runner().queue(initFn, runFn)\n          runner._initialise(true)\n          expect(initFn).toHaveBeenCalledTimes(1)\n        })\n\n        it('does nothing if function is already initialized', () => {\n          const runner = new Runner().queue(initFn, runFn)\n          runner._initialise(true)\n          runner._initialise(true)\n          expect(initFn).toHaveBeenCalledTimes(1)\n        })\n      })\n\n      describe('_run()', () => {\n        it('runs each queued function for the position or dt given', () => {\n          const runner = new Runner().queue(initFn, runFn)\n          runner._run(16)\n          expect(runFn).toHaveBeenCalledWith(16)\n        })\n\n        it('returns true if all runners converged', () => {\n          const spy = createSpy().and.returnValue(true)\n          const runner = new Runner().queue(initFn, spy)\n          expect(runner._run(16)).toBe(true)\n        })\n\n        it('returns true if all runners finished', () => {\n          const spy = createSpy().and.returnValue(true)\n          const runner = new Runner(100).queue(initFn, spy)\n          runner._run(200)\n          expect(runner._run(1)).toBe(true)\n        })\n      })\n\n      describe('addTransform()', () => {\n        it('adds a transformation by multiplying', () => {\n          const runner = new Runner()\n          runner.addTransform({ translate: [10, 10] })\n          expect(runner.transforms).toEqual(new Matrix(1, 0, 0, 1, 10, 10))\n        })\n      })\n\n      describe('clearTransform()', () => {\n        it('resets the transformations to identity', () => {\n          const runner = new Runner()\n          runner.addTransform({ translate: [10, 10] })\n          runner.clearTransform()\n          expect(runner.transforms).toEqual(new Matrix())\n        })\n      })\n\n      describe('clearTransformsFromQueue', () => {\n        it('deletes all functions from the queue which are transformations', () => {\n          const runner = new Runner().queue(initFn, runFn)\n          runner.transform({ translate: [10, 20] })\n          runner.clearTransformsFromQueue()\n          expect(runner._queue.length).toBe(1)\n        })\n      })\n    })\n\n    describe('Element', () => {\n      describe('animate()', () => {\n        it('creates a runner with the element set and schedules it on the timeline', () => {\n          var element = SVG('<rect />')\n          var runner = element.animate()\n          expect(runner instanceof Runner)\n          expect(runner.element()).toBe(element)\n          expect(runner.timeline()).toBe(element.timeline())\n          expect(element.timeline().getLastRunnerInfo().runner).toBe(runner)\n        })\n      })\n\n      describe('delay()', () => {\n        it('calls animate with correct parameters', () => {\n          var element = SVG('<rect />')\n\n          spyOn(element, 'animate')\n          element.delay(100, 'now')\n          expect(element.animate).toHaveBeenCalledWith(0, 100, 'now')\n        })\n      })\n\n      describe('_clearTransformRunnersBefore()', () => {\n        it('calls clearBefore on the runner array', () => {\n          const rect = new Rect()\n          rect._prepareRunner()\n          const spy = spyOn(rect._transformationRunners, 'clearBefore')\n          rect._clearTransformRunnersBefore({ id: 1 })\n          expect(spy).toHaveBeenCalledWith(1)\n        })\n      })\n\n      describe('_currentTransform()', () => {\n        it('calculates the current transformation of this element', () => {\n          const rect = new Rect()\n          rect._prepareRunner()\n          const runner1 = new Runner().addTransform({ translate: [10, 20] })\n          const runner2 = new Runner().addTransform({ rotate: 45 })\n          const runner3 = new Runner().addTransform({ translate: [10, 20] })\n\n          rect._addRunner(runner1)\n          rect._addRunner(runner2)\n          rect._addRunner(runner3)\n          expect(rect._currentTransform(runner3)).toEqual(\n            new Matrix({ translate: [10, 20] }).rotate(45).translate(10, 20)\n          )\n        })\n      })\n\n      describe('_addRunner()', () => {\n        it('adds a runner to the runner array of this element', () => {\n          const rect = new Rect()\n          rect._prepareRunner()\n          const spy = spyOn(rect._transformationRunners, 'add')\n          const runner = new Runner()\n          rect._addRunner(runner)\n          expect(spy).toHaveBeenCalledWith(runner)\n        })\n      })\n\n      describe('_prepareRunner()', () => {\n        it('adds a runner array to the element', () => {\n          const rect = new Rect()\n          expect(rect._transformationRunners).toBe(undefined)\n          rect._prepareRunner()\n          expect(rect._transformationRunners).toEqual(any(RunnerArray))\n        })\n\n        it('only adds it if no animation is in progress', () => {\n          const rect = new Rect()\n          expect(rect._transformationRunners).toBe(undefined)\n          rect._prepareRunner()\n          const arr = rect._transformationRunners\n          rect._frameId = 1\n          rect._prepareRunner()\n          expect(rect._transformationRunners).toBe(arr)\n        })\n      })\n    })\n\n    describe('methods', () => {\n      describe('attr()', () => {\n        it('relays to styleAttr with \"attr\" as parameter', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, 'styleAttr')\n          runner.attr(1, 2)\n          expect(spy).toHaveBeenCalledWith('attr', 1, 2)\n        })\n      })\n\n      describe('css()', () => {\n        it('relays to styleAttr with \"css\" as parameter', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, 'styleAttr')\n          runner.css(1, 2)\n          expect(spy).toHaveBeenCalledWith('css', 1, 2)\n        })\n      })\n\n      describe('styleAttr()', () => {\n        it('adds a morpher for attr', () => {\n          const runner = new Runner()\n          runner.styleAttr('attr', 'x', 5)\n          expect(runner._history.attr.morpher).toEqual(any(Morphable))\n          expect(runner._history.attr.morpher.to()).toEqual([\n            'x',\n            SVGNumber,\n            2,\n            5,\n            ''\n          ])\n        })\n\n        it('adds a morpher for css', () => {\n          const runner = new Runner()\n          runner.styleAttr('css', 'x', 5)\n          expect(runner._history.css.morpher).toEqual(any(Morphable))\n          expect(runner._history.css.morpher.to()).toEqual([\n            'x',\n            SVGNumber,\n            2,\n            5,\n            ''\n          ])\n        })\n\n        it('adds init and run fn for execution when runner runs', () => {\n          const element = new Rect().move(0, 0)\n          const runner = new Runner(100).ease('-').element(element)\n          runner.styleAttr('attr', 'x', 5)\n          runner.step(50)\n          expect(runner._history.attr.morpher.from()).toEqual([\n            'x',\n            SVGNumber,\n            2,\n            0,\n            ''\n          ])\n          expect(runner._history.attr.morpher.to()).toEqual([\n            'x',\n            SVGNumber,\n            2,\n            5,\n            ''\n          ])\n          expect(element.x()).toBe(2.5)\n        })\n\n        it('it also works when the object contains other morphable values', () => {\n          const element = new Rect().fill('#fff').stroke('#000')\n          const runner = new Runner(100).ease('-').element(element)\n          runner.styleAttr('attr', { fill: '#000', stroke: new Color('#fff') })\n          runner.step(50)\n          expect(runner._history.attr.morpher.from()).toEqual([\n            'fill',\n            Color,\n            5,\n            255,\n            255,\n            255,\n            0,\n            'rgb',\n            'stroke',\n            Color,\n            5,\n            0,\n            0,\n            0,\n            0,\n            'rgb'\n          ])\n\n          expect(runner._history.attr.morpher.to()).toEqual([\n            'fill',\n            Color,\n            5,\n            0,\n            0,\n            0,\n            0,\n            'rgb',\n            'stroke',\n            Color,\n            5,\n            255,\n            255,\n            255,\n            0,\n            'rgb'\n          ])\n          const result = runner._history.attr.morpher.at(0.5).valueOf()\n          expect(result.fill).toEqual(any(Color))\n          expect(result.stroke).toEqual(any(Color))\n          expect(result.fill.toArray()).toEqual([127.5, 127.5, 127.5, 0, 'rgb'])\n          expect(result.stroke.toArray()).toEqual([\n            127.5,\n            127.5,\n            127.5,\n            0,\n            'rgb'\n          ])\n        })\n\n        it('it changes color space', () => {\n          const element = new Rect().fill('#fff')\n          const runner = new Runner(100).ease('-').element(element)\n          runner.styleAttr('attr', { fill: new Color(100, 12, 12, 'hsl') })\n          runner.step(50)\n          expect(runner._history.attr.morpher.from()).toEqual([\n            'fill',\n            Color,\n            5,\n            0,\n            0,\n            100,\n            0,\n            'hsl'\n          ])\n\n          expect(runner._history.attr.morpher.to()).toEqual([\n            'fill',\n            Color,\n            5,\n            100,\n            12,\n            12,\n            0,\n            'hsl'\n          ])\n          const result = runner._history.attr.morpher.at(0.5).valueOf()\n          expect(result.fill).toEqual(any(Color))\n          expect(result.fill.toArray()).toEqual([50, 6, 56, 0, 'hsl'])\n          expect(element.fill()).toBe('#969388')\n        })\n\n        it('retargets if called two times with new key', () => {\n          const element = new Rect().fill('#fff')\n          const runner = new Runner(100).ease('-').element(element)\n          runner.styleAttr('attr', { fill: new Color(100, 12, 12, 'hsl') })\n          runner.step(50)\n          expect(element.fill()).toBe('#969388')\n          runner.styleAttr('attr', {\n            fill: new Color(100, 50, 50, 'hsl'),\n            x: 50\n          })\n          runner.step(25)\n          expect(element.fill()).toBe('#b1c37c')\n          expect(element.x()).toBe(37.5)\n        })\n\n        it('retargets if called two times without new key', () => {\n          const element = new Rect().fill('#fff')\n          const runner = new Runner(100).ease('-').element(element)\n          runner.styleAttr('attr', { fill: new Color(100, 12, 12, 'hsl') })\n          runner.step(50)\n          expect(element.fill()).toBe('#969388')\n          runner.styleAttr('attr', { fill: new Color(100, 50, 50, 'hsl') })\n          runner.step(25)\n          expect(element.fill()).toBe('#b1c37c')\n        })\n      })\n\n      function closeTo(number, precision = 2) {\n        return {\n          /*\n           * The asymmetricMatch function is required, and must return a boolean.\n           */\n          asymmetricMatch: function (compareTo) {\n            const factor = 10 ** precision\n            return (\n              Math.round(~~(compareTo * factor) / factor) ===\n              Math.round(~~(number * factor) / factor)\n            )\n          },\n\n          /*\n           * The jasmineToString method is used in the Jasmine pretty printer, and will\n           * be seen by the user in the message when a test fails.\n           */\n          jasmineToString: function () {\n            return (\n              '<close to: ' + number + ' with precision: ' + precision + '>'\n            )\n          }\n        }\n      }\n\n      function equal(obj) {\n        return {\n          /*\n           * The asymmetricMatch function is required, and must return a boolean.\n           */\n          asymmetricMatch: function (compareTo) {\n            if (!(compareTo instanceof obj.constructor)) return false\n\n            const keys = Object.keys(obj)\n            const difference = Object.keys(compareTo).filter(\n              (el) => !keys.includes(el)\n            )\n\n            if (difference.length) return false\n\n            for (const key in obj) {\n              if (obj[key] !== compareTo[key]) return false\n            }\n\n            return true\n          },\n\n          /*\n           * The jasmineToString method is used in the Jasmine pretty printer, and will\n           * be seen by the user in the message when a test fails.\n           */\n          jasmineToString: function () {\n            return '<equal to: ' + obj + '>'\n          }\n        }\n      }\n\n      describe('zoom()', () => {\n        it('adds a zoom morpher to the queue', () => {\n          const element = SVG().size(100, 100).viewbox(0, 0, 100, 100)\n          const runner = new Runner(100).ease('-').element(element)\n          runner.zoom(2, { x: 0, y: 0 })\n          runner.step(50)\n          expect(runner._history.zoom.morpher.from()).toEqual([1, ''])\n          expect(runner._history.zoom.morpher.to()).toEqual([2, ''])\n\n          expect(element.zoom()).toBeCloseTo(1.5, 10)\n          expect(element.viewbox().toArray()).toEqual([\n            0,\n            0,\n            closeTo(66.666, 3),\n            closeTo(66.666, 3)\n          ])\n        })\n\n        it('retargets if called twice', () => {\n          const element = SVG().size(100, 100).viewbox(0, 0, 100, 100)\n          const runner = new Runner(100).ease('-').element(element)\n          runner.zoom(2, { x: 0, y: 0 })\n          runner.step(50)\n          runner.zoom(4, { x: 0, y: 0 })\n          expect(runner._history.zoom.morpher.from()).toEqual([1, ''])\n          expect(runner._history.zoom.morpher.to()).toEqual([4, ''])\n\n          runner.step(25)\n          expect(element.zoom()).toBeCloseTo(3.25, 10)\n          expect(element.viewbox().toArray()).toEqual([\n            0,\n            0,\n            closeTo(30.769, 3),\n            closeTo(30.769, 3)\n          ])\n        })\n      })\n\n      describe('transform()', () => {\n        it('does not retarget for non-declarative transformations', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, '_tryRetarget')\n          runner.transform({ translate: [10, 20] })\n          expect(spy).not.toHaveBeenCalled()\n        })\n\n        it('does not retarget for relative transformations', () => {\n          const runner = new Runner(new Controller(() => 0))\n          const spy = spyOn(runner, '_tryRetarget')\n          runner.transform({ translate: [10, 20] }, true)\n          expect(spy).not.toHaveBeenCalled()\n        })\n\n        it('does retarget for absolute declarative transformations', () => {\n          const runner = new Runner(new Controller(() => 0))\n          const spy = spyOn(runner, '_tryRetarget')\n          runner.transform({ translate: [10, 20] })\n          expect(spy).toHaveBeenCalled()\n        })\n\n        it('calls queue with isTransform=true', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, 'queue')\n          runner.transform({ translate: [10, 20] })\n          expect(spy).toHaveBeenCalledWith(\n            any(Function),\n            any(Function),\n            any(Function),\n            true\n          )\n        })\n\n        it('steps an affine transformation correctly', () => {\n          const element = new Rect()\n          const runner = new Runner(100).ease('-').element(element)\n          runner.transform({ translate: [10, 20], scale: 2, rotate: 90 })\n          runner.step(50)\n          // transform sets an immediate callback to apply all merged transforms\n          // when every runner had the chance to add its bit of transforms\n          jasmine.RequestAnimationFrame.tick(1)\n          expect(element.matrix().decompose()).toEqual(\n            objectContaining({\n              translateX: 5,\n              translateY: 10,\n              scaleX: closeTo(1.5, 10),\n              scaleY: closeTo(1.5),\n              rotate: closeTo(45, 10)\n            })\n          )\n        })\n\n        it('retargets an affine transformation correctly', () => {\n          const element = new Rect()\n          const runner = new Runner(() => 1).element(element)\n          runner.transform({ translate: [10, 20], scale: 2, rotate: 90 })\n          runner.step(50)\n          runner.transform({ scale: 2 })\n\n          // transform sets its to-target to the morpher in the initialisation step\n          // because it depends on the from-target. Declaritive animation run the init-step\n          // on every frame. That is why we step here to see the effect of our retargeting\n          runner.step(25)\n\n          expect(runner._history.transform.morpher.to()).toEqual([\n            2, 2, 0, 0, 0, 0, 0, 0\n          ])\n        })\n\n        it('retargets an affine transformation correctly and sets new origin', () => {\n          const element = new Rect()\n          const runner = new Runner(() => 1).element(element)\n          runner.transform({ translate: [10, 20], scale: 2, rotate: 90 })\n          runner.step(50)\n          runner.transform({ scale: 2, origin: [10, 10] })\n\n          // transform sets its to-target to the morpher in the initialisation step\n          // because it depends on the from-target. Declaritive animation run the init-step\n          // on every frame. That is why we step here to see the effect of our retargeting\n          runner.step(25)\n\n          expect(runner._history.transform.morpher.to()).toEqual([\n            2, 2, 0, 0, 0, 0, 10, 10\n          ])\n        })\n\n        it('steps multiple relative animations correctly', () => {\n          const element = new Rect()\n          const runner = new Runner(100).ease('-').element(element)\n          runner.translate(10, 20).scale(2).rotate(45)\n          runner.step(50)\n          // transform sets an immediate callback to apply all merged transforms\n          // when every runner had the chance to add its bit of transforms\n          jasmine.RequestAnimationFrame.tick(1)\n\n          // The origin is transformed with every\n          expect(element.matrix()).toEqual(\n            new Matrix().translate(5, 10).scale(1.5, 5, 10).rotate(22.5, 5, 10)\n          )\n        })\n\n        it('steps multiple relative animations correctly from multiple runners', () => {\n          const element = new Rect()\n          const runner1 = new Runner(100).ease('-').element(element)\n          const runner2 = new Runner(100).ease('-').element(element)\n          const runner3 = new Runner(100).ease('-').element(element)\n          runner1.translate(10, 20)\n          runner2.scale(2)\n          runner3.rotate(45)\n          runner1.step(50)\n          runner2.step(50)\n          runner3.step(50)\n          // transform sets an immediate callback to apply all merged transforms\n          // when every runner had the chance to add its bit of transforms\n          jasmine.RequestAnimationFrame.tick(1)\n\n          // The origin is transformed with every\n          expect(element.matrix()).toEqual(\n            new Matrix().translate(5, 10).scale(1.5, 5, 10).rotate(22.5, 5, 10)\n          )\n        })\n\n        it('absolute transformations correctly overwrite relatives', () => {\n          const element = new Rect()\n          const runner1 = new Runner(100).ease('-').element(element)\n          const runner2 = new Runner(100).ease('-').element(element)\n          const runner3 = new Runner(100).ease('-').element(element)\n          runner1.translate(10, 20)\n          runner2.transform({ scale: 2 })\n          runner3.rotate(45)\n          runner1.step(50)\n          runner2.step(50)\n          runner3.step(50)\n          // transform sets an immediate callback to apply all merged transforms\n          // when every runner had the chance to add its bit of transforms\n          jasmine.RequestAnimationFrame.tick(1)\n\n          expect(runner1._queue.length).toBe(0)\n\n          // The origin is transformed with every\n          expect(element.matrix()).toEqual(new Matrix().scale(1.5).rotate(22.5))\n        })\n\n        it('correctly animates matrices directly', () => {\n          const element = new Rect()\n          const runner = new Runner(100).ease('-').element(element)\n          runner.transform(new Matrix({ rotate: 90 }))\n          runner.step(50)\n          // transform sets an immediate callback to apply all merged transforms\n          // when every runner had the chance to add its bit of transforms\n          jasmine.RequestAnimationFrame.tick(1)\n\n          // The origin is transformed with every\n          expect(element.matrix()).toEqual(\n            new Matrix(0.5, 0.5, -0.5, 0.5, 0, 0)\n          )\n        })\n\n        it('correctly animates matrices affine', () => {\n          const element = new Rect()\n          const runner = new Runner(100).ease('-').element(element)\n          runner.transform(\n            Object.assign({ affine: true }, new Matrix({ rotate: 90 }))\n          )\n          runner.step(50)\n          // transform sets an immediate callback to apply all merged transforms\n          // when every runner had the chance to add its bit of transforms\n          jasmine.RequestAnimationFrame.tick(1)\n\n          // The origin is transformed with every\n          expect(element.matrix()).toEqual(new Matrix({ rotate: 45 }))\n        })\n\n        it('correctly animates matrices affine by passing third parameter', () => {\n          const element = new Rect()\n          const runner = new Runner(100).ease('-').element(element)\n          runner.transform(new Matrix({ rotate: 90 }), true, true)\n          runner.step(50)\n          // transform sets an immediate callback to apply all merged transforms\n          // when every runner had the chance to add its bit of transforms\n          jasmine.RequestAnimationFrame.tick(1)\n\n          // The origin is transformed with every\n          expect(element.matrix()).toEqual(new Matrix({ rotate: 45 }))\n        })\n\n        it('correctly animates a declarative relative rotation', () => {\n          const element = new Rect()\n          const runner = new Runner(() => 1).element(element)\n          runner.transform({ rotate: 90 }, true)\n          runner.step(16)\n          jasmine.RequestAnimationFrame.tick(1)\n          runner.step(16)\n          jasmine.RequestAnimationFrame.tick(1)\n          expect(element.matrix()).not.toEqual(new Matrix())\n        })\n      })\n\n      describe('x()', () => {\n        it('queues a numer', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, '_queueNumber')\n          runner.x(10)\n          expect(spy).toHaveBeenCalledWith('x', 10)\n        })\n      })\n\n      describe('y()', () => {\n        it('queues a numer', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, '_queueNumber')\n          runner.y(10)\n          expect(spy).toHaveBeenCalledWith('y', 10)\n        })\n      })\n\n      describe('dx()', () => {\n        it('queues a numer', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, '_queueNumberDelta')\n          runner.dx(10)\n          expect(spy).toHaveBeenCalledWith('x', 10)\n        })\n\n        it('uses a delta of 0 by default', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, '_queueNumberDelta')\n          runner.dx()\n          expect(spy).toHaveBeenCalledWith('x', 0)\n        })\n      })\n\n      describe('dy()', () => {\n        it('queues a number', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, '_queueNumberDelta')\n          runner.dy(10)\n          expect(spy).toHaveBeenCalledWith('y', 10)\n        })\n\n        it('uses a delta of 0 by default', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, '_queueNumberDelta')\n          runner.dy()\n          expect(spy).toHaveBeenCalledWith('y', 0)\n        })\n      })\n\n      describe('dmove()', () => {\n        it('calls dx and dy', () => {\n          const runner = new Runner()\n          const spy1 = spyOn(runner, 'dx').and.returnValue(runner)\n          const spy2 = spyOn(runner, 'dy').and.returnValue(runner)\n          runner.dmove(10, 20)\n          expect(spy1).toHaveBeenCalledWith(10)\n          expect(spy2).toHaveBeenCalledWith(20)\n        })\n      })\n\n      describe('_queueNumberDelta', () => {\n        it('queues a morpher of type SVGNumber', () => {\n          const element = new Rect().x(10)\n          const runner = new Runner(100).ease('-').element(element)\n          runner._queueNumberDelta('x', 10)\n          runner.step(50)\n          expect(runner._history.x.morpher.type()).toEqual(SVGNumber)\n          expect(runner._history.x.morpher.from()).toEqual([10, ''])\n          expect(runner._history.x.morpher.to()).toEqual([20, ''])\n\n          expect(element.x()).toBe(15)\n        })\n\n        it('retargets correctly', () => {\n          const element = new Rect().x(10)\n          const runner = new Runner(100).ease('-').element(element)\n          runner._queueNumberDelta('x', 10)\n          runner.step(25)\n          runner._queueNumberDelta('x', 20)\n\n          expect(runner._history.x.morpher.to()).toEqual([30, ''])\n\n          runner.step(25)\n          expect(element.x()).toBe(20)\n        })\n      })\n\n      describe('_queueObject', () => {\n        it('queues a morphable object', () => {\n          const element = new Rect().x(10)\n          const runner = new Runner(100).ease('-').element(element)\n          runner._queueObject('x', new SVGNumber(20))\n          runner.step(50)\n          expect(runner._history.x.morpher.type()).toEqual(SVGNumber)\n          expect(runner._history.x.morpher.from()).toEqual([10, ''])\n          expect(runner._history.x.morpher.to()).toEqual([20, ''])\n\n          expect(element.x()).toBe(15)\n        })\n\n        it('queues a morphable primitive', () => {\n          const element = new Rect().fill('#000')\n          const runner = new Runner(100).ease('-').element(element)\n          runner._queueObject('fill', '#fff')\n          runner.step(50)\n          expect(runner._history.fill.morpher.type()).toEqual(Color)\n\n          expect(element.fill()).toBe('#808080')\n        })\n\n        it('retargets correctly', () => {\n          const element = new Rect().x(10)\n          const runner = new Runner(100).ease('-').element(element)\n          runner._queueObject('x', 20)\n\n          runner.step(25)\n\n          runner._queueObject('x', 30)\n          runner.step(25)\n          expect(element.x()).toBe(20)\n        })\n      })\n\n      describe('_queueNumber', () => {\n        it('queues an SVGNumber with _queueObject', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, '_queueObject')\n          runner._queueNumber('x', 10)\n          expect(spy).toHaveBeenCalledWith('x', equal(new SVGNumber(10)))\n        })\n      })\n\n      describe('cy()', () => {\n        it('queues a numer', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, '_queueNumber')\n          runner.cy(10)\n          expect(spy).toHaveBeenCalledWith('cy', 10)\n        })\n      })\n\n      describe('cx()', () => {\n        it('queues a numer', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, '_queueNumber')\n          runner.cx(10)\n          expect(spy).toHaveBeenCalledWith('cx', 10)\n        })\n      })\n\n      describe('move()', () => {\n        it('calls x and y', () => {\n          const runner = new Runner()\n          const spy1 = spyOn(runner, 'x').and.returnValue(runner)\n          const spy2 = spyOn(runner, 'y').and.returnValue(runner)\n          runner.move(10, 20)\n          expect(spy1).toHaveBeenCalledWith(10)\n          expect(spy2).toHaveBeenCalledWith(20)\n        })\n      })\n\n      describe('center()', () => {\n        it('calls cx and cy', () => {\n          const runner = new Runner()\n          const spy1 = spyOn(runner, 'cx').and.returnValue(runner)\n          const spy2 = spyOn(runner, 'cy').and.returnValue(runner)\n          runner.center(10, 20)\n          expect(spy1).toHaveBeenCalledWith(10)\n          expect(spy2).toHaveBeenCalledWith(20)\n        })\n      })\n\n      describe('size()', () => {\n        it('calls width and height', () => {\n          const runner = new Runner()\n          const spy1 = spyOn(runner, 'width').and.returnValue(runner)\n          const spy2 = spyOn(runner, 'height').and.returnValue(runner)\n          runner.size(10, 20)\n          expect(spy1).toHaveBeenCalledWith(10)\n          expect(spy2).toHaveBeenCalledWith(20)\n        })\n\n        it('figures out height if only width given', () => {\n          const element = new Rect().size(10, 10)\n          const runner = new Runner().element(element)\n          const spy1 = spyOn(runner, 'width').and.returnValue(runner)\n          const spy2 = spyOn(runner, 'height').and.returnValue(runner)\n          runner.size(20)\n          expect(spy1).toHaveBeenCalledWith(20)\n          expect(spy2).toHaveBeenCalledWith(20)\n        })\n\n        it('figures out width if only height given', () => {\n          const element = new Rect().size(10, 10)\n          const runner = new Runner().element(element)\n          const spy1 = spyOn(runner, 'width').and.returnValue(runner)\n          const spy2 = spyOn(runner, 'height').and.returnValue(runner)\n          runner.size(null, 20)\n          expect(spy1).toHaveBeenCalledWith(20)\n          expect(spy2).toHaveBeenCalledWith(20)\n        })\n      })\n\n      describe('width()', () => {\n        it('queues a numer', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, '_queueNumber')\n          runner.width(10)\n          expect(spy).toHaveBeenCalledWith('width', 10)\n        })\n      })\n\n      describe('height()', () => {\n        it('queues a numer', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, '_queueNumber')\n          runner.height(10)\n          expect(spy).toHaveBeenCalledWith('height', 10)\n        })\n      })\n\n      describe('plot()', () => {\n        it('queues a morphable array', () => {\n          const element = new Polygon().plot([10, 10, 20, 20])\n          const runner = new Runner(100).ease('-').element(element)\n          runner.plot(20, 20, 30, 30)\n          runner.step(50)\n          expect(runner._history.plot.morpher.from()).toEqual([10, 10, 20, 20])\n          expect(runner._history.plot.morpher.to()).toEqual([20, 20, 30, 30])\n          expect(element.array()).toEqual(new PointArray([15, 15, 25, 25]))\n        })\n\n        it('retargets correctly', () => {\n          const element = new Polygon().plot([10, 10, 20, 20])\n          const runner = new Runner(100).ease('-').element(element)\n          runner.plot(20, 20, 30, 30)\n          runner.step(25)\n          runner.plot(30, 30, 40, 40)\n          runner.step(25)\n          expect(runner._history.plot.morpher.from()).toEqual([10, 10, 20, 20])\n          expect(runner._history.plot.morpher.to()).toEqual([30, 30, 40, 40])\n          expect(element.array()).toEqual(new PointArray([20, 20, 30, 30]))\n        })\n      })\n\n      describe('leading()', () => {\n        it('queues a numer', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, '_queueNumber')\n          runner.leading(10)\n          expect(spy).toHaveBeenCalledWith('leading', 10)\n        })\n      })\n\n      describe('viewbox()', () => {\n        it('queues a numer', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, '_queueObject')\n          runner.viewbox(10, 10, 100, 100)\n          expect(spy).toHaveBeenCalledWith(\n            'viewbox',\n            equal(new Box(10, 10, 100, 100))\n          )\n        })\n      })\n\n      describe('update()', () => {\n        it('relays to attr call', () => {\n          const runner = new Runner()\n          const spy = spyOn(runner, 'attr')\n          runner.update(0.5, '#fff', 1)\n          expect(spy).toHaveBeenCalledWith('offset', 0.5)\n          expect(spy).toHaveBeenCalledWith('stop-color', '#fff')\n          expect(spy).toHaveBeenCalledWith('stop-opacity', 1)\n        })\n      })\n    })\n  })\n\n  describe('FakeRunner', () => {\n    describe('()', () => {\n      it('creates a new FakeRunner with a new matrix which is always done', () => {\n        const runner = new FakeRunner()\n        expect(runner.transforms).toEqual(new Matrix())\n        expect(runner.id).toBe(-1)\n        expect(runner.done).toBe(true)\n      })\n    })\n\n    describe('mergeWith()', () => {\n      it('merges the transformations of a runner with another and returns a FakeRunner', () => {\n        const fake = new FakeRunner()\n        const runner = new Runner().addTransform({ translate: [10, 20] })\n        const newRunner = fake.mergeWith(runner)\n        expect(newRunner).toEqual(any(FakeRunner))\n        expect(newRunner.transforms).toEqual(\n          new Matrix({ translate: [10, 20] })\n        )\n      })\n    })\n  })\n\n  describe('RunnerArray', () => {\n    describe('add()', () => {\n      it('adds a runner to the runner array', () => {\n        const runner = new Runner()\n        const arr = new RunnerArray()\n        arr.add(runner)\n        expect(arr.length()).toBe(1)\n      })\n\n      it('does not add the same runner twice', () => {\n        const runner = new Runner()\n        const arr = new RunnerArray()\n        arr.add(runner)\n        arr.add(runner)\n        expect(arr.length()).toBe(1)\n      })\n    })\n\n    describe('getByID()', () => {\n      it('returns a runner by its id', () => {\n        const runner = new Runner()\n        const arr = new RunnerArray()\n        arr.add(runner)\n        expect(arr.getByID(runner.id)).toBe(runner)\n      })\n    })\n\n    describe('remove()', () => {\n      it('removes a runner by its id', () => {\n        const runner = new Runner()\n        const arr = new RunnerArray()\n        arr.add(runner)\n        arr.remove(runner.id)\n        expect(arr.length()).toBe(0)\n      })\n    })\n\n    describe('merge()', () => {\n      it('merges all runners which are done', () => {\n        const runner1 = new Runner().addTransform({ translate: [10, 20] })\n        const runner2 = new Runner().addTransform({ rotate: 45 })\n        const runner3 = new Runner().addTransform({ translate: [10, 20] })\n        const arr = new RunnerArray()\n        arr.add(runner1).add(runner2).add(runner3)\n        runner1.done = true\n        runner2.done = true\n        runner3.done = true\n        arr.merge()\n        expect(arr.runners[0]).toEqual(any(FakeRunner))\n        expect(arr.runners[0].transforms).toEqual(\n          new Matrix({ translate: [10, 20] }).rotate(45).translate(10, 20)\n        )\n      })\n\n      it('skips runners which are not done', () => {\n        const runner1 = new Runner().addTransform({ translate: [10, 20] })\n        const runner2 = new Runner().addTransform({ rotate: 45 })\n        const runner3 = new Runner().addTransform({ rotate: 45 })\n        const runner4 = new Runner().addTransform({ translate: [10, 20] })\n        const runner5 = new Runner().addTransform({ rotate: 45 })\n        const arr = new RunnerArray()\n        arr.add(runner1).add(runner2).add(runner3).add(runner4).add(runner5)\n        runner1.done = true\n        runner2.done = true\n        runner3.done = false\n        runner4.done = true\n        runner5.done = true\n        arr.merge()\n        expect(arr.runners[0]).toEqual(any(FakeRunner))\n        expect(arr.runners[0].transforms).toEqual(\n          new Matrix({ translate: [10, 20] }).rotate(45)\n        )\n\n        expect(arr.runners[2]).toEqual(any(FakeRunner))\n        expect(arr.runners[2].transforms).toEqual(\n          new Matrix({ translate: [10, 20] }).rotate(45)\n        )\n\n        expect(arr.runners[1]).toBe(runner3)\n      })\n\n      it('skips runners which have a timeline and are scheduled on that timeline', () => {\n        const runner1 = new Runner().addTransform({ translate: [10, 20] })\n        const runner2 = new Runner().addTransform({ rotate: 45 })\n        const runner3 = new Runner().addTransform({ rotate: 45 })\n        const runner4 = new Runner().addTransform({ translate: [10, 20] })\n        const runner5 = new Runner().addTransform({ rotate: 45 })\n        const arr = new RunnerArray()\n        arr.add(runner1).add(runner2).add(runner3).add(runner4).add(runner5)\n        runner1.done = true\n        runner2.done = true\n        runner3.done = true\n        runner4.done = true\n        runner5.done = true\n\n        runner3.schedule(new Timeline())\n        arr.merge()\n        expect(arr.runners[0]).toEqual(any(FakeRunner))\n        expect(arr.runners[0].transforms).toEqual(\n          new Matrix({ translate: [10, 20] }).rotate(45)\n        )\n\n        expect(arr.runners[2]).toEqual(any(FakeRunner))\n        expect(arr.runners[2].transforms).toEqual(\n          new Matrix({ translate: [10, 20] }).rotate(45)\n        )\n\n        expect(arr.runners[1]).toBe(runner3)\n      })\n    })\n\n    describe('edit()', () => {\n      it('replaces one runner with another', () => {\n        const arr = new RunnerArray()\n        const runner1 = new Runner()\n        const runner2 = new Runner()\n        arr.add(runner1)\n        arr.edit(runner1.id, runner2)\n        expect(arr.length()).toBe(1)\n        expect(arr.runners[0]).toBe(runner2)\n      })\n    })\n\n    describe('length()', () => {\n      it('returns the number of runners in the array', () => {\n        const arr = new RunnerArray().add(new Runner()).add(new Runner())\n        expect(arr.length()).toBe(2)\n      })\n    })\n\n    describe('clearBefore', () => {\n      it('removes all runners before a certain runner', () => {\n        const runner1 = new Runner()\n        const runner2 = new Runner()\n        const runner3 = new Runner()\n        const runner4 = new Runner()\n        const runner5 = new Runner()\n        const arr = new RunnerArray()\n        arr.add(runner1).add(runner2).add(runner3).add(runner4).add(runner5)\n        arr.clearBefore(runner3.id)\n        expect(arr.length()).toBe(4)\n        expect(arr.runners).toEqual([\n          any(FakeRunner),\n          runner3,\n          runner4,\n          runner5\n        ])\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/animation/Timeline.js",
    "content": "/* globals describe, expect, it, beforeEach, afterEach, spyOn, container, jasmine */\n\nimport {\n  Timeline,\n  SVG,\n  Runner,\n  Animator,\n  Queue,\n  Rect\n} from '../../../src/main.js'\nimport { getWindow } from '../../../src/utils/window.js'\n\nconst { createSpy, any } = jasmine\n\ndescribe('Timeline.js', () => {\n  beforeEach(() => {\n    jasmine.RequestAnimationFrame.install(getWindow())\n    Animator.timeouts = new Queue()\n    Animator.frames = new Queue()\n    Animator.immediates = new Queue()\n    Animator.nextDraw = null\n  })\n\n  afterEach(() => {\n    getWindow().cancelAnimationFrame(Animator.nextDraw)\n    jasmine.RequestAnimationFrame.uninstall(getWindow())\n  })\n\n  describe('()', () => {\n    it('creates a new Timeline with a default timesource', () => {\n      const timeline = new Timeline()\n      expect(timeline.source()).toEqual(any(Function))\n    })\n\n    it('creates a new Timeline with the passed timesource', () => {\n      const source = createSpy()\n      const timeline = new Timeline(source)\n      expect(timeline.source()).toBe(source)\n    })\n  })\n\n  describe('schedule()', () => {\n    it('schedules a runner at the start of the queue with a default delay of 0', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000)\n      timeline.schedule(runner)\n      expect(timeline._runners[0].start).toEqual(0)\n    })\n\n    it('sets a reference of the timeline to the runner', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000)\n      timeline.schedule(runner)\n      expect(runner.timeline()).toBe(timeline)\n    })\n\n    it('schedules after when no when is past', () => {\n      const timeline = new Timeline().schedule(new Runner(1000))\n      const runner = new Runner(1000)\n      timeline.schedule(runner)\n      expect(timeline._runners[1].start).toBe(1000)\n    })\n\n    it('schedules after when when is last', () => {\n      const timeline = new Timeline().schedule(new Runner(1000))\n      const runner = new Runner(1000)\n      timeline.schedule(runner, 0, 'last')\n      expect(timeline._runners[1].start).toBe(1000)\n    })\n\n    it('schedules after when when is after', () => {\n      const timeline = new Timeline().schedule(new Runner(1000))\n      const runner = new Runner(1000)\n      timeline.schedule(runner, 0, 'after')\n      expect(timeline._runners[1].start).toBe(1000)\n    })\n\n    it('starts the animation right away when there is no runner to schedule after and when is after', () => {\n      const timeline = new Timeline().time(100)\n      const runner = new Runner(1000)\n      timeline.schedule(runner, 0, 'after')\n      expect(timeline._runners[0].start).toBe(100)\n    })\n\n    it('schedules with start of the last runner when when is with-last', () => {\n      const timeline = new Timeline().schedule(new Runner(1000), 200)\n      const runner = new Runner(1000)\n      timeline.schedule(runner, 0, 'with-last')\n      expect(timeline._runners[1].start).toBe(200)\n    })\n\n    it('starts the animation right away when there is no runner to schedule after and when is after', () => {\n      const timeline = new Timeline().time(100)\n      const runner = new Runner(1000)\n      timeline.schedule(runner, 0, 'with-last')\n      expect(timeline._runners[0].start).toBe(100)\n    })\n\n    it('respects passed delay', () => {\n      const timeline = new Timeline().schedule(new Runner(1000), 1000)\n      const runner = new Runner(1000)\n      timeline.schedule(runner, 0, 'after')\n      expect(timeline._runners[1].start).toBe(2000)\n    })\n\n    it('schedules the runner absolutely with absolute', () => {\n      const timeline = new Timeline().schedule(new Runner(1000))\n      const runner = new Runner(1000)\n      timeline.schedule(runner, 0, 'absolute')\n      expect(timeline._runners[1].start).toBe(0)\n    })\n\n    it('schedules the runner absolutely with start', () => {\n      const timeline = new Timeline().schedule(new Runner(1000))\n      const runner = new Runner(1000)\n      timeline.schedule(runner, 0, 'start')\n      expect(timeline._runners[1].start).toBe(0)\n    })\n\n    it('schedules the runner relatively to old start with relative', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000)\n      timeline.schedule(runner, 100).schedule(runner, 100, 'relative')\n      expect(timeline._runners[0].start).toBe(200)\n    })\n\n    it('schedules the runner as absolute if this runner was not on the timeline', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000)\n      timeline.schedule(runner, 100, 'relative')\n      expect(timeline._runners[0].start).toBe(100)\n    })\n\n    it('throws if when is not supported', () => {\n      const timeline = new Timeline().schedule(new Runner(1000), 1000)\n      const runner = new Runner(1000)\n      expect(() => timeline.schedule(runner, 0, 'not supported')).toThrowError(\n        'Invalid value for the \"when\" parameter'\n      )\n    })\n\n    it('uses persist value of the runner of present', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000).persist(100)\n      timeline.schedule(runner)\n      expect(timeline._runners[0].persist).toBe(100)\n    })\n  })\n\n  describe('unschedule()', () => {\n    it('removes a runner from the timeline', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000)\n      timeline.schedule(runner).unschedule(runner)\n      expect(runner.timeline()).toBe(null)\n      expect(timeline._runners).toEqual([])\n    })\n  })\n\n  describe('getRunnerInfoById()', () => {\n    it('gets a runner by its id from the timeline', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000)\n      expect(\n        timeline.schedule(runner).getRunnerInfoById(runner.id).runner\n      ).toBe(runner)\n    })\n\n    it('returns null of runner not found', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000)\n      expect(timeline.getRunnerInfoById(runner.id)).toBe(null)\n    })\n  })\n\n  describe('getLastRunnerInfo()', () => {\n    it('gets a runner by its id from the timeline', () => {\n      const timeline = new Timeline().schedule(new Runner(1000))\n      const runner = new Runner(1000)\n      expect(timeline.schedule(runner).getLastRunnerInfo().runner).toBe(runner)\n    })\n  })\n\n  describe('getEndTime()', () => {\n    it('returns the end time of the runner which started last', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000)\n      const runner2 = new Runner(100)\n      timeline.schedule(runner).schedule(runner2, 500, 'start')\n      expect(timeline.getEndTime()).toBe(600)\n    })\n\n    it('returns the timeline time if no runner is scheduled', () => {\n      const timeline = new Timeline().time(100)\n      expect(timeline.getEndTime()).toBe(100)\n    })\n  })\n\n  describe('getEndTimeOfTimeline', () => {\n    it('returns 0 if no runners are scheduled', () => {\n      const timeline = new Timeline()\n      const endTime = timeline.getEndTimeOfTimeline()\n      expect(endTime).toEqual(0)\n    })\n\n    it('returns the time all runners are finished', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000)\n      const runner2 = new Runner(100)\n      timeline.schedule(runner).schedule(runner2, 500, 'start')\n      expect(timeline.getEndTimeOfTimeline()).toBe(1000)\n    })\n  })\n\n  describe('finish - issue #964', () => {\n    let canvas\n\n    beforeEach(() => {\n      canvas = SVG().addTo(container)\n    })\n\n    it('places all elements at the right position - single runner', () => {\n      const timeline = new Timeline()\n\n      const rect = canvas.rect(20, 20)\n      rect.timeline(timeline)\n      rect.animate().move(100, 200)\n\n      timeline.finish()\n      expect(rect.x()).toEqual(100)\n      expect(rect.y()).toEqual(200)\n    })\n\n    it('places all elements at the right position - runner that finishes latest is in first position', () => {\n      const timeline = new Timeline()\n\n      const rect1 = canvas.rect(10, 10)\n      rect1.timeline(timeline)\n\n      const rect2 = canvas.rect(10, 10)\n      rect2.timeline(timeline)\n\n      const rect3 = canvas.rect(10, 10)\n      rect3.timeline(timeline)\n\n      rect1.animate(2000, 0, 'now').move(100, 200)\n      rect2.animate(1000, 0, 'now').move(100, 200)\n      rect3.animate(1000, 500, 'now').move(100, 200)\n\n      timeline.finish()\n\n      expect(rect1.x()).toEqual(100)\n      expect(rect1.y()).toEqual(200)\n\n      expect(rect2.x()).toEqual(100)\n      expect(rect2.y()).toEqual(200)\n\n      expect(rect3.x()).toEqual(100)\n      expect(rect3.y()).toEqual(200)\n    })\n\n    it('places all elements at the right position - runner that finishes latest is in middle position', () => {\n      const timeline = new Timeline()\n\n      const rect1 = canvas.rect(10, 10)\n      rect1.timeline(timeline)\n\n      const rect2 = canvas.rect(10, 10)\n      rect2.timeline(timeline)\n\n      const rect3 = canvas.rect(10, 10)\n      rect3.timeline(timeline)\n\n      rect2.animate(1000, 0, 'now').move(100, 200)\n      rect1.animate(2000, 0, 'now').move(100, 200)\n      rect3.animate(1000, 500, 'now').move(100, 200)\n\n      timeline.finish()\n\n      expect(rect1.x()).toEqual(100)\n      expect(rect1.y()).toEqual(200)\n\n      expect(rect2.x()).toEqual(100)\n      expect(rect2.y()).toEqual(200)\n\n      expect(rect3.x()).toEqual(100)\n      expect(rect3.y()).toEqual(200)\n    })\n\n    it('places all elements at the right position - runner that finishes latest is in last position', () => {\n      const timeline = new Timeline()\n\n      const rect1 = canvas.rect(10, 10)\n      rect1.timeline(timeline)\n\n      const rect2 = canvas.rect(10, 10)\n      rect2.timeline(timeline)\n\n      const rect3 = canvas.rect(10, 10)\n      rect3.timeline(timeline)\n\n      rect2.animate(1000, 0, 'now').move(100, 200)\n      rect3.animate(1000, 500, 'now').move(100, 200)\n      rect1.animate(2000, 0, 'now').move(100, 200)\n\n      timeline.finish()\n\n      expect(rect1.x()).toEqual(100)\n      expect(rect1.y()).toEqual(200)\n\n      expect(rect2.x()).toEqual(100)\n      expect(rect2.y()).toEqual(200)\n\n      expect(rect3.x()).toEqual(100)\n      expect(rect3.y()).toEqual(200)\n    })\n  })\n\n  describe('updateTime()', () => {\n    it('sets the time to the current time', () => {\n      const timeline = new Timeline(() => 200).play()\n      expect(timeline._lastSourceTime).toBe(200)\n    })\n  })\n\n  describe('stop()', () => {\n    it('sets the time to 0 and pauses the timeline', () => {\n      const timeline = new Timeline().time(100)\n      expect(timeline.stop().time()).toBe(0)\n      expect(timeline._paused).toBe(true)\n    })\n  })\n\n  describe('speed()', () => {\n    it('gets or sets the speed of the timeline', () => {\n      const timeline = new Timeline().speed(2)\n      expect(timeline.speed()).toBe(2)\n    })\n  })\n\n  describe('reverse()', () => {\n    it('reverses the timeline with no parameter given', () => {\n      const timeline = new Timeline().speed(2)\n      const spy = spyOn(timeline, 'speed').and.callThrough()\n      timeline.reverse()\n      expect(spy).toHaveBeenCalledWith(-2)\n      timeline.reverse()\n      expect(spy).toHaveBeenCalledWith(2)\n    })\n\n    it('reverses the timeline when true was passed', () => {\n      const timeline = new Timeline().speed(2)\n      const spy = spyOn(timeline, 'speed').and.callThrough()\n      timeline.reverse(true)\n      expect(spy).toHaveBeenCalledWith(-2)\n    })\n\n    it('plays normal direction when false was passed', () => {\n      const timeline = new Timeline().speed(-2)\n      const spy = spyOn(timeline, 'speed').and.callThrough()\n      timeline.reverse(false)\n      expect(spy).toHaveBeenCalledWith(2)\n    })\n  })\n\n  describe('seek()', () => {\n    it('seeks the time by a given delta', () => {\n      const timeline = new Timeline().time(100).seek(200)\n      expect(timeline.time()).toBe(300)\n    })\n  })\n\n  describe('time()', () => {\n    it('gets and sets the current time of the timeline', () => {\n      const timeline = new Timeline().time(100)\n      expect(timeline.time()).toBe(100)\n    })\n  })\n\n  describe('persist()', () => {\n    it('gets and sets the persist property of the timeline', () => {\n      const timeline = new Timeline().persist(true)\n      expect(timeline.persist()).toBe(true)\n    })\n  })\n\n  describe('source()', () => {\n    it('gets or sets the time source of the timeline', () => {\n      const source = () => 200\n      const timeline = new Timeline().source(source)\n      expect(timeline.source()).toBe(source)\n    })\n  })\n\n  describe('_stepFn', () => {\n    it('does a step in the timeline and runs all runners', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000)\n      timeline.schedule(runner).play() // we have to play because its synchronous here\n      jasmine.RequestAnimationFrame.tick(16)\n      expect(runner.time()).toBe(16)\n    })\n\n    it('doenst run runners which start later', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000)\n      timeline.schedule(runner, 100).play() // we have to play because its synchronous here\n      jasmine.RequestAnimationFrame.tick(16)\n      expect(runner.time()).toBe(0)\n    })\n\n    it('reset runner if timeline was seeked backwards', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000)\n      timeline.schedule(runner)\n      const spy = spyOn(runner, 'reset').and.callThrough()\n      jasmine.RequestAnimationFrame.tick(1000)\n      timeline.seek(-1000)\n      expect(runner.time()).toBe(0)\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('does not run runners if they are not active', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000).active(false)\n      timeline.schedule(runner).play() // we have to play because its synchronous here\n      jasmine.RequestAnimationFrame.tick(16)\n      expect(runner.time()).toBe(0)\n    })\n\n    it('unschedules runner if its finished', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000)\n      timeline.schedule(runner).play() // we have to play because its synchronous here\n      jasmine.RequestAnimationFrame.tick(1000)\n      jasmine.RequestAnimationFrame.tick(1)\n      expect(runner.time()).toBe(1001)\n      expect(timeline.getRunnerInfoById(runner.id)).toBe(null)\n    })\n\n    it('does not unschedule if runner is persistent forever', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000).persist(true)\n      timeline.schedule(runner).play() // we have to play because its synchronous here\n      jasmine.RequestAnimationFrame.tick(1000)\n      jasmine.RequestAnimationFrame.tick(1)\n      expect(runner.time()).toBe(1001)\n      expect(timeline.getRunnerInfoById(runner.id)).not.toBe(null)\n    })\n\n    it('does not unschedule if runner is persistent for a certain time', () => {\n      const timeline = new Timeline()\n      const runner = new Runner(1000).persist(100)\n      timeline.schedule(runner).play() // we have to play because its synchronous here\n      jasmine.RequestAnimationFrame.tick(1000)\n      jasmine.RequestAnimationFrame.tick(1)\n      expect(runner.time()).toBe(1001)\n      expect(timeline.getRunnerInfoById(runner.id)).not.toBe(null)\n    })\n\n    it('fires finish if no runners left', () => {\n      const spy = createSpy()\n      const timeline = new Timeline().on('finished', spy)\n      const runner = new Runner(1000)\n      spy.calls.reset()\n      timeline.schedule(runner).play() // we have to play because its synchronous here\n      jasmine.RequestAnimationFrame.tick(1000)\n      jasmine.RequestAnimationFrame.tick(1)\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('continues if there are still runners left from us when going back in time', () => {\n      const spy = createSpy()\n      const timeline = new Timeline()\n        .on('finished', spy)\n        .time(1200)\n        .reverse(true)\n      const runner = new Runner(1000)\n      spy.calls.reset()\n      timeline.schedule(runner, 0).play() // we have to play because its synchronous here\n      jasmine.RequestAnimationFrame.tick(1)\n      expect(spy).not.toHaveBeenCalled()\n    })\n\n    it('finishes if time is backwards and 0', () => {\n      const spy = createSpy()\n      const timeline = new Timeline().on('finished', spy).reverse(true)\n      const runner = new Runner(1000)\n      spy.calls.reset()\n      timeline.schedule(runner, 0).play() // we have to play because its synchronous here\n      jasmine.RequestAnimationFrame.tick(1)\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('Element', () => {\n    describe('timeline()', () => {\n      it('sets and gets the timeline of the element', () => {\n        const timeline = new Timeline()\n        const rect = new Rect().timeline(timeline)\n        expect(rect.timeline()).toBe(timeline)\n      })\n\n      it('creates a timeline on the fly when getting it', () => {\n        expect(new Rect().timeline()).toEqual(any(Timeline))\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/A.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport { A, G, Rect } from '../../../src/main.js'\n\nconst { any } = jasmine\n\nconst url = 'https://svgjs.dev'\ndescribe('A.js', () => {\n  describe('()', () => {\n    it('creates a new object of type A', () => {\n      expect(new A()).toEqual(any(A))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new A({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('to()', () => {\n    it('creates xlink:href attribute', () => {\n      const link = new A()\n      link.to(url)\n      expect(link.attr('href')).toBe(url)\n    })\n  })\n\n  describe('target()', () => {\n    it('creates target attribute', () => {\n      const link = new A()\n      link.target('_blank')\n      expect(link.attr('target')).toBe('_blank')\n    })\n  })\n\n  describe('Container', () => {\n    describe('link()', () => {\n      it('creates a link with given url', () => {\n        const group = new G()\n        const link = group.link(url)\n        expect(link.attr('href')).toBe(url)\n        expect(link).toEqual(any(A))\n      })\n    })\n  })\n\n  describe('Element', () => {\n    describe('linker()', () => {\n      it('returns the instance of the link of a linked element', () => {\n        const link = new A().to(url)\n        const rect = link.rect(100, 100)\n\n        expect(rect.linker()).toBe(link)\n      })\n\n      it('returns null if no link is found', () => {\n        const group = new G()\n        const rect = group.rect(100, 100)\n\n        expect(rect.linker()).toBe(null)\n      })\n\n      it('returns null when el is not in dom at all', () => {\n        const group = new G()\n        expect(group.linker()).toBe(null)\n      })\n    })\n\n    describe('unlink()', () => {\n      it('returns itself', () => {\n        const group = new G()\n        expect(group.unlink()).toBe(group)\n      })\n\n      it('removes the link', () => {\n        const group = new G()\n        const link = group.link(url)\n        const rect = link.rect(100, 100)\n\n        expect(rect.unlink().parent()).toBe(group)\n        expect(link.parent()).toBe(null)\n      })\n\n      it(\"removes also the link when link wasn't in document\", () => {\n        const link = new A().to(url)\n        const rect = link.rect(100, 100)\n\n        expect(rect.unlink().parent()).toBe(null)\n        expect(link.parent()).toBe(null)\n      })\n    })\n\n    describe('linkTo()', () => {\n      it('wraps the called element in a link with given url', () => {\n        const rect = new Rect()\n        rect.linkTo(url)\n        expect(rect.linker()).toEqual(any(A))\n        expect(rect.linker().attr('href')).toBe(url)\n      })\n\n      it('wraps the called element in a link with given block', () => {\n        const rect = new Rect()\n        rect.linkTo(function (link) {\n          link.to(url).target('_blank')\n        })\n        expect(rect.linker().attr('href')).toBe(url)\n        expect(rect.linker().attr('target')).toBe('_blank')\n      })\n\n      it('reuses existing link if possible', () => {\n        const rect = new Rect()\n        rect.linkTo(url)\n        const link = rect.linker()\n        rect.linkTo(url + '/something')\n        expect(rect.linker()).toBe(link)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Circle.js",
    "content": "/* globals describe, expect, it, beforeEach, spyOn, jasmine */\n\nimport { Circle, G } from '../../../src/main.js'\n\nconst { any, objectContaining } = jasmine\n\ndescribe('Circle.js', () => {\n  let circle\n\n  beforeEach(() => {\n    circle = new Circle()\n  })\n\n  describe('()', () => {\n    it('creates a new object of type Circle', () => {\n      expect(new Circle()).toEqual(any(Circle))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Circle({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('radius()', () => {\n    it('calls attr with r', () => {\n      const spy = spyOn(circle, 'attr').and.callThrough()\n      circle.radius(123)\n      expect(spy).toHaveBeenCalledWith('r', 123)\n    })\n  })\n\n  describe('rx()', () => {\n    it('calls attr with r', () => {\n      const spy = spyOn(circle, 'attr')\n      circle.rx(123)\n      expect(spy).toHaveBeenCalledWith('r', 123)\n    })\n  })\n\n  describe('ry()', () => {\n    it('calls rx', () => {\n      const spy = spyOn(circle, 'rx')\n      circle.ry(123)\n      expect(spy).toHaveBeenCalledWith(123)\n    })\n  })\n\n  describe('size()', () => {\n    it('calls radius with half of the size', () => {\n      const spy = spyOn(circle, 'radius')\n      circle.size(100)\n      expect(spy).toHaveBeenCalledWith(objectContaining({ value: 50 }))\n    })\n  })\n\n  describe('Container', () => {\n    describe('circle()', () => {\n      it('creates a circle with given size', () => {\n        const group = new G()\n        const circle = group.circle(50)\n        expect(circle.attr('r')).toBe(25)\n        expect(circle).toEqual(any(Circle))\n      })\n\n      it('defaults to zero size', () => {\n        const group = new G()\n        const circle = group.circle()\n        expect(circle.attr('r')).toBe(0)\n        expect(circle).toEqual(any(Circle))\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/ClipPath.js",
    "content": "/* globals describe, expect, it, spyOn, jasmine, container */\n\nimport { ClipPath, SVG, Container, Rect } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('ClipPath.js', () => {\n  describe('()', () => {\n    it('creates a new object of type ClipPath', () => {\n      expect(new ClipPath()).toEqual(any(ClipPath))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new ClipPath({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('remove()', () => {\n    it('unclips all targets', () => {\n      const canvas = SVG().addTo(container)\n      const clip = canvas.clip()\n      const rect = canvas.rect(100, 100).clipWith(clip)\n      expect(clip.remove()).toBe(clip)\n      expect(rect.clipper()).toBe(null)\n    })\n\n    it('calls remove on parent class', () => {\n      const clip = new ClipPath()\n      const spy = spyOn(Container.prototype, 'remove')\n      clip.remove()\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('targets()', () => {\n    it('gets all targets of this clipPath', () => {\n      const canvas = SVG().addTo(container)\n      const clip = canvas.clip()\n      const rect = canvas.rect(100, 100).clipWith(clip)\n      expect(clip.targets()).toEqual([rect])\n    })\n  })\n\n  describe('Container', () => {\n    describe('clip()', () => {\n      it('creates a clipPath in the defs', () => {\n        const canvas = SVG()\n        const clip = canvas.clip()\n        expect(clip).toEqual(any(ClipPath))\n        expect(canvas.defs().children()).toEqual([clip])\n      })\n    })\n  })\n\n  describe('Element', () => {\n    describe('clipper()', () => {\n      it('returns the instance of ClipPath the current element is clipped with', () => {\n        const canvas = SVG().addTo(container)\n        const clip = canvas.clip()\n        const rect = canvas.rect(100, 100).clipWith(clip)\n        expect(rect.clipper()).toEqual(clip)\n      })\n\n      it('returns null if no clipPath was found', () => {\n        expect(new Rect().clipper()).toBe(null)\n      })\n    })\n\n    describe('clipWith()', () => {\n      it('sets the clip-path attribute on the element to the id of the clipPath', () => {\n        const clip = new ClipPath().id('foo')\n        const rect = new Rect().clipWith(clip)\n        expect(rect.attr('clip-path')).toBe('url(#foo)')\n      })\n\n      it('creates a clipPath and appends the passed element to it to clip current element', () => {\n        const canvas = SVG().addTo(container)\n        const circle = canvas.circle(40)\n        const rect = canvas.rect(100, 100).clipWith(circle)\n        expect(circle.parent()).toEqual(any(ClipPath))\n        expect(rect.attr('clip-path')).toBe(`url(#${circle.parent().id()})`)\n      })\n    })\n\n    describe('unclip()', () => {\n      it('sets the clip-target attribute to null and returns itself', () => {\n        const clip = new ClipPath().id('foo')\n        const rect = new Rect().clipWith(clip)\n        expect(rect.unclip()).toBe(rect)\n        expect(rect.attr('clip-path')).toBe(undefined)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Container.js",
    "content": "/* globals describe, expect, it, beforeEach, jasmine, container */\n\nimport { Container, create, SVG } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Container.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Container', () => {\n      expect(new Container(create('g'))).toEqual(any(Container))\n    })\n  })\n\n  let canvas\n  let rect1\n  let group1\n  let rect2\n  let circle1\n  let group2\n  let circle2\n  let group3\n  let line1\n  let line2\n  let circle3\n  let group4\n  let rect3\n\n  beforeEach(() => {\n    canvas = SVG().addTo(container)\n    rect1 = canvas.rect(100, 100).id('rect1')\n    group1 = canvas.group().id('group1')\n    rect2 = group1.rect(100, 100).id('rect2')\n    circle1 = group1.circle(50).id('circle1')\n    group2 = group1.group().id('group2')\n    circle2 = group2.circle(50).id('circle2')\n    group3 = group2.group().id('group3')\n    line1 = group3.line(1, 1, 2, 2).id('line1')\n    line2 = group3.line(1, 1, 2, 2).id('line2')\n    circle3 = group2.circle(50).id('circle3')\n    group4 = canvas.group().id('group4')\n    rect3 = group4.rect(100, 100).id('rect3')\n\n    /* should be:\n        canvas\n          rect1\n          group1\n            rect2\n            circle1\n            group2\n              circle2\n              group3\n                line1\n                line2\n              circle3\n          group4\n            rect3\n      */\n  })\n\n  describe('flatten()', () => {\n    it('flattens the whole document when called on the root', () => {\n      canvas.flatten()\n\n      expect(canvas.children()).toEqual([\n        rect1,\n        rect2,\n        circle1,\n        circle2,\n        line1,\n        line2,\n        circle3,\n        rect3\n      ])\n    })\n\n    it('flattens a group and places all children into its parent when called on a group - 1', () => {\n      group1.flatten()\n\n      /* now should be:\n        canvas\n          rect1\n          group1\n            rect2\n            circle1\n            circle2\n            line1\n            line2\n            circle3\n          group4\n            rect3\n      */\n\n      expect(canvas.children()).toEqual([rect1, group1, group4])\n      expect(group1.children()).toEqual([\n        rect2,\n        circle1,\n        circle2,\n        line1,\n        line2,\n        circle3\n      ])\n    })\n\n    it('flattens a group and places all children into its parent when called on a group - 2', () => {\n      group2.flatten()\n\n      /* now should be:\n        canvas\n          rect1\n          group1\n            rect2\n            circle1\n            group2\n              circle2\n              line1\n              line2\n              circle3\n          group4\n            rect3\n      */\n\n      expect(group2.children()).toEqual([circle2, line1, line2, circle3])\n    })\n  })\n\n  describe('ungroup()', () => {\n    it('ungroups a group and inserts all children in the correct order in the parent parent of the group', () => {\n      group1.ungroup()\n\n      expect(canvas.children()).toEqual([rect1, rect2, circle1, group2, group4])\n\n      group4.ungroup()\n\n      expect(canvas.children()).toEqual([rect1, rect2, circle1, group2, rect3])\n    })\n\n    it('ungroups a group into another group and appends the elements to the other group', () => {\n      group1.ungroup(group4)\n      expect(group4.children()).toEqual([rect3, rect2, circle1, group2])\n    })\n\n    it('ungroups a group into another group at the specified position', () => {\n      group2.ungroup(group1, 1)\n      expect(group1.children()).toEqual([\n        rect2,\n        circle2,\n        group3,\n        circle3,\n        circle1\n      ])\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Defs.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport { Defs } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Defs.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Defs', () => {\n      expect(new Defs()).toEqual(any(Defs))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Defs({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('flatten()', () => {\n    it('does nothing and returns itself', () => {\n      const defs = Object.freeze(new Defs())\n      expect(defs.flatten()).toBe(defs)\n    })\n  })\n\n  describe('ungroup()', () => {\n    it('does nothing and returns itself', () => {\n      const defs = Object.freeze(new Defs())\n      expect(defs.ungroup()).toBe(defs)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Dom.js",
    "content": "/* globals describe, expect, it, beforeEach, spyOn, jasmine, container */\n\nimport {\n  SVG,\n  G,\n  Rect,\n  Svg,\n  Dom,\n  List,\n  Fragment,\n  Circle,\n  Tspan,\n  create\n} from '../../../src/main.js'\nimport { getWindow } from '../../../src/utils/window.js'\nimport { svg, html } from '../../../src/modules/core/namespaces.js'\nconst { any, createSpy, objectContaining } = jasmine\n\ndescribe('Dom.js', function () {\n  describe('()', () => {\n    it('creates a new object of type Dom', () => {\n      const rect = new Rect()\n      expect(new Dom(rect.node)).toEqual(any(Dom))\n    })\n\n    it('sets passed attributes on the element', () => {\n      const rect = new Rect()\n      expect(new Dom(rect.node, { id: 'foo' }).id()).toBe('foo')\n    })\n\n    it('references the passed node on the instance', () => {\n      const rect = new Rect()\n      expect(new Dom(rect.node).node).toBe(rect.node)\n    })\n\n    it('sets the type according to the nodename', () => {\n      const rect = new Rect()\n      expect(new Dom(rect.node).type).toBe(rect.node.nodeName)\n    })\n  })\n\n  describe('add()', () => {\n    it('adds an element as child to the end with no second argument given', () => {\n      const g = new G()\n      g.add(new Rect())\n      const rect = new Rect()\n      g.add(rect)\n      expect(g.children().length).toBe(2)\n      expect(g.get(1)).toBe(rect)\n    })\n\n    it('adds an element at the specified position with second argument given', () => {\n      const g = new G()\n      g.add(new Rect())\n      g.add(new Rect())\n      const rect = new Rect()\n      g.add(rect, 1)\n      expect(g.children().length).toBe(3)\n      expect(g.get(1)).toBe(rect)\n    })\n\n    it('does nothing if element is already the element at that position', () => {\n      const g = new G()\n      g.rect(100, 100)\n      const rect = g.rect(100, 100)\n      g.add(rect, 1)\n      expect(g.get(1)).toBe(rect)\n    })\n\n    it('handles svg strings', () => {\n      const g = new G()\n      g.add('<rect />')\n      expect(g.children().length).toBe(1)\n      expect(g.get(0)).toEqual(any(Rect))\n    })\n\n    it('handles query selectors', () => {\n      const canvas = SVG().addTo(container)\n      const rect = canvas.rect(100, 100).addClass('test')\n      const g = canvas.group()\n      g.add('.test')\n      expect(g.children().length).toBe(1)\n      expect(g.get(0)).toBe(rect)\n    })\n\n    it('handles a node', () => {\n      const g = new G()\n      const node = create('rect')\n      g.add(node)\n      expect(g.children().length).toBe(1)\n      expect(g.get(0)).toEqual(any(Rect))\n    })\n  })\n\n  describe('addTo()', () => {\n    it('returns the current element', () => {\n      const g = new G()\n      const rect = new Rect()\n      expect(rect.addTo(g)).toBe(rect)\n    })\n\n    it('puts an element into another element', () => {\n      const g = new G()\n      const rect = new Rect()\n      const spy = spyOn(g, 'put')\n      rect.addTo(g, 0)\n      expect(spy).toHaveBeenCalledWith(rect, 0)\n    })\n\n    it('works with svg strings', () => {\n      const rect = new Rect()\n      rect.addTo('<g />')\n      expect(rect.parent()).toEqual(any(G))\n    })\n\n    it('works with query selector', () => {\n      const canvas = SVG().addTo(container)\n      const rect = canvas.rect(100, 100)\n      const g = canvas.group().addClass('test')\n      rect.addTo('.test')\n      expect(g.children().length).toBe(1)\n      expect(g.get(0)).toBe(rect)\n    })\n  })\n\n  describe('children()', () => {\n    it('returns a List of all children', () => {\n      const g = new G()\n      const rect = g.rect(100, 100)\n      const circle = g.circle(100, 100)\n      const children = g.children()\n      expect(children).toEqual([rect, circle])\n      expect(children).toEqual(any(List))\n    })\n  })\n\n  describe('clear()', () => {\n    it('returns the current element', () => {\n      const g = new G()\n      g.rect(100, 100)\n      g.circle(100, 100)\n      expect(g.clear()).toBe(g)\n    })\n\n    it('removes all children from an element', () => {\n      const g = new G()\n      g.rect(100, 100)\n      g.circle(100, 100)\n      g.clear()\n      expect(g.children()).toEqual([])\n    })\n  })\n\n  describe('clone()', () => {\n    it('clones the current element and returns it', () => {\n      const rect = new Rect()\n      const clone = rect.clone()\n      expect(rect).not.toBe(clone)\n      expect(clone).toEqual(any(Rect))\n      expect(clone.type).toBe(rect.type)\n    })\n\n    it('also clones the children by default', () => {\n      const group = new G()\n      const rect = group.rect(100, 100)\n      const clone = group.clone()\n      expect(clone.get(0)).not.toBe(rect)\n      expect(clone.get(0)).toEqual(any(Rect))\n    })\n\n    it('does not clone the children when passing false', () => {\n      const group = new G()\n      group.rect(100, 100)\n      const clone = group.clone(false)\n      expect(clone.children()).toEqual([])\n    })\n\n    it('assigns a new id to the element and to child elements by default', () => {\n      const group = new G().id('group')\n      const rect = group.rect(100, 100).id('rect')\n      const clone = group.clone()\n      expect(clone.get(0).id()).not.toBe(rect.id())\n      expect(clone.id()).not.toBe(group.id())\n    })\n\n    it('does not assign a new id to the element and to child elements', () => {\n      const group = new G().id('group')\n      const rect = group.rect(100, 100).id('rect')\n      const clone = group.clone(true, false)\n      expect(clone.get(0).id()).toBe(rect.id())\n      expect(clone.id()).toBe(group.id())\n    })\n\n    it('returns an instance of the same class the method was called on', () => {\n      const rect = new Dom(create('rect'))\n      expect(rect.constructor).toBe(Dom)\n      expect(rect.clone().constructor).toBe(Dom)\n    })\n  })\n\n  describe('each()', () => {\n    it('iterates over all children and executes the passed function on then', () => {\n      const group = new G()\n      const group2 = group.group()\n      const circle = group.circle(100, 100)\n      const spy = createSpy('each')\n      group.each(spy)\n\n      expect(spy.calls.all()).toEqual([\n        objectContaining({ object: group2, args: [0, [group2, circle]] }),\n        objectContaining({ object: circle, args: [1, [group2, circle]] })\n      ])\n    })\n\n    it('iterates over all children recursively and executes the passed function on then when deep is true', () => {\n      const group = new G()\n      const group2 = group.group()\n      const rect = group2.rect(100, 100)\n      const circle = group.circle(100, 100)\n      const spy = createSpy('each')\n      group.each(spy, true)\n\n      expect(spy.calls.all()).toEqual([\n        objectContaining({ object: group2, args: [0, [group2, circle]] }),\n        objectContaining({ object: rect, args: [0, [rect]] }),\n        objectContaining({ object: circle, args: [1, [group2, circle]] })\n      ])\n    })\n  })\n\n  describe('element()', () => {\n    it('creates an element of given type and appends it to the current element', () => {\n      const g = new G()\n      const el = g.element('title')\n      expect(el).toEqual(any(Dom))\n      expect(el.type).toBe('title')\n    })\n\n    it('sets the specified attributes passed as second argument', () => {\n      const g = new G()\n      const el = g.element('title', { id: 'foo' })\n      expect(el.id()).toBe('foo')\n    })\n  })\n\n  describe('first()', () => {\n    it('returns the first child', () => {\n      const g = new G()\n      const rect = g.rect(100, 100)\n      g.circle(100, 100)\n      expect(g.first()).toBe(rect)\n    })\n\n    it('returns null if no first child exists', () => {\n      expect(new G().first()).toBe(null)\n    })\n  })\n\n  describe('get()', () => {\n    it('returns the child at the given position', () => {\n      const g = new G()\n      const rect = g.rect(100, 100)\n      const circle = g.circle(100, 100)\n      expect(g.get(0)).toBe(rect)\n      expect(g.get(1)).toBe(circle)\n    })\n  })\n\n  describe('getEventHolder()', () => {\n    it('returns the node because it holds all events on the object', () => {\n      const dom = new Dom({})\n      expect(dom.getEventHolder()).toBe(dom.node)\n    })\n  })\n\n  describe('getEventTarget()', () => {\n    it('returns the node because it is the target of the event', () => {\n      const dom = new Dom({})\n      expect(dom.getEventTarget()).toBe(dom.node)\n    })\n  })\n\n  describe('has()', () => {\n    it('returns true if the element has the passed element as child', () => {\n      const g = new G()\n      const rect = g.rect(100, 100)\n      expect(g.has(rect)).toBe(true)\n    })\n\n    it(\"returns false if the element hasn't the passed element as child\", () => {\n      const g = new G()\n      const rect = new Rect()\n      expect(g.has(rect)).toBe(false)\n    })\n  })\n\n  describe('html()', () => {\n    it('calls xml with the html namespace', () => {\n      const group = new G()\n      const spy = spyOn(group, 'xml')\n      group.html('<foo>')\n      expect(spy).toHaveBeenCalledWith('<foo>', undefined, html)\n    })\n  })\n\n  describe('id()', () => {\n    it('returns current element when called as setter', () => {\n      const g = new G()\n      expect(g.id('asd')).toBe(g)\n    })\n\n    it('sets the id with argument given', () => {\n      expect(new G().id('foo').node.id).toBe('foo')\n    })\n\n    it('gets the id when no argument given', () => {\n      const g = new G({ id: 'foo' })\n      expect(g.id()).toBe('foo')\n    })\n\n    it('generates an id on getting if none is set', () => {\n      const g = new G()\n      expect(g.node.id).toBe('')\n      g.id()\n      expect(g.node.id).not.toBe('')\n    })\n  })\n\n  describe('index()', () => {\n    it('gets the position of the passed child', () => {\n      const g = new G()\n      g.rect(100, 100)\n      const rect = g.rect(100, 100)\n      expect(g.index(rect)).toBe(1)\n    })\n\n    it('returns -1 if element is no child', () => {\n      const g = new G()\n      const rect = new Rect()\n      expect(g.index(rect)).toBe(-1)\n    })\n  })\n\n  describe('last()', () => {\n    it('gets the last child of the element', () => {\n      const g = new G()\n      g.rect(100, 100)\n      const rect = g.rect(100, 100)\n      expect(g.last()).toBe(rect)\n    })\n\n    it('returns null if no last child exists', () => {\n      expect(new G().last()).toBe(null)\n    })\n  })\n\n  describe('parent()', () => {\n    var canvas, rect, group1, group2\n\n    beforeEach(function () {\n      canvas = SVG().addTo(container)\n      group1 = canvas.group().addClass('test')\n      group2 = group1.group()\n      rect = group2.rect(100, 100)\n    })\n\n    it('returns the svg parent with no argument given', () => {\n      expect(rect.parent()).toBe(group2)\n    })\n\n    it('returns the closest parent with the correct type', () => {\n      expect(rect.parent(Svg)).toBe(canvas)\n    })\n\n    it('returns the closest parent matching the selector', () => {\n      expect(rect.parent('.test')).toBe(group1)\n    })\n\n    it('returns null if it cannot find a parent matching the argument', () => {\n      expect(rect.parent('.not-there')).toBe(null)\n    })\n\n    it('returns null if it cannot find a parent matching the argument in a #document-fragment', () => {\n      const fragment = getWindow().document.createDocumentFragment()\n      const svg = new Svg().addTo(fragment)\n      const rect = svg.rect(100, 100)\n      expect(rect.parent('.not-there')).toBe(null)\n    })\n\n    it('returns Dom if parent is #document-fragment', () => {\n      const fragment = getWindow().document.createDocumentFragment()\n      const svg = new Svg().addTo(fragment)\n      expect(svg.parent()).toEqual(any(Dom))\n    })\n\n    it('returns html parents, too', () => {\n      expect(canvas.parent().node).toBe(container)\n    })\n  })\n\n  describe('put()', () => {\n    it('calls add() but returns the added element instead', () => {\n      const g = new G()\n      const rect = new Rect()\n      const spy = spyOn(g, 'add').and.callThrough()\n      expect(g.put(rect, 0)).toBe(rect)\n      expect(spy).toHaveBeenCalledWith(rect, 0)\n    })\n\n    it('creates object from svg string', () => {\n      const g = new G()\n      const rect = '<rect />'\n      const spy = spyOn(g, 'add').and.callThrough()\n      const ret = g.put(rect, 0)\n      expect(ret).toEqual(any(Rect))\n      expect(spy).toHaveBeenCalledWith(ret, 0)\n    })\n\n    it('works with a query selector', () => {\n      const canvas = SVG().addTo(container)\n      const rect = canvas.rect().addClass('test')\n      const g = canvas.group()\n      const spy = spyOn(g, 'add').and.callThrough()\n      const ret = g.put('.test', 0)\n      expect(ret).toEqual(rect)\n      expect(spy).toHaveBeenCalledWith(rect, 0)\n    })\n  })\n\n  describe('putIn()', () => {\n    it('calls add on the given parent', () => {\n      const g = new G()\n      const rect = new Rect()\n      const spy = spyOn(g, 'add')\n      rect.putIn(g, 0)\n      expect(spy).toHaveBeenCalledWith(rect, 0)\n    })\n\n    it('returns the passed element', () => {\n      const g = new G()\n      const rect = new Rect()\n      expect(rect.putIn(g, 0)).toBe(g)\n    })\n\n    it('returns an instance when svg string given', () => {\n      const g = '<g />'\n      const rect = new Rect()\n      const ret = rect.putIn(g)\n      expect(ret).toEqual(any(G))\n      expect(ret.children()).toEqual([rect])\n    })\n\n    it('works with a query selector', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group().addClass('test')\n      const rect = canvas.rect(100, 100)\n      const ret = rect.putIn('.test')\n      expect(ret).toBe(g)\n      expect(g.children()).toEqual([rect])\n    })\n  })\n\n  describe('remove()', () => {\n    it('returns the removed element', () => {\n      const canvas = SVG().addTo(container)\n      const rect = canvas.rect(100, 100)\n      expect(rect.remove()).toBe(rect)\n    })\n\n    it('removes the element from the parent', () => {\n      const canvas = SVG().addTo(container)\n      const rect = canvas.rect(100, 100)\n      expect(canvas.children()).toEqual([rect])\n      rect.remove()\n      expect(canvas.children()).toEqual([])\n    })\n\n    it('is a noop when element is not attached to the dom', () => {\n      const rect = new Rect()\n      expect(rect.remove()).toBe(rect)\n    })\n\n    it('also works when direct child of document-fragment', () => {\n      const fragment = new Fragment()\n      const rect = fragment.rect(100, 100)\n      expect(fragment.children()).toEqual([rect])\n      expect(rect.remove()).toBe(rect)\n      expect(fragment.children()).toEqual([])\n    })\n  })\n\n  describe('removeElement()', () => {\n    it('returns itself', () => {\n      const g = new G()\n      const rect = g.rect(100, 100)\n      expect(g.removeElement(rect)).toBe(g)\n    })\n\n    it('removes the given child', () => {\n      const g = new G()\n      const rect = g.rect(100, 100)\n      expect(g.removeElement(rect).children()).toEqual([])\n    })\n\n    it('throws if the given element is not a child', () => {\n      const g = new G()\n      const rect = new Rect()\n      try {\n        g.removeElement(rect)\n      } catch (e) {\n        expect(e).toEqual(objectContaining({ code: 8 }))\n      }\n    })\n  })\n\n  describe('replace()', () => {\n    it('returns the new element', () => {\n      const g = new G()\n      const rect = g.rect(100, 100)\n      const circle = new Circle()\n      expect(rect.replace(circle)).toBe(circle)\n    })\n\n    it('replaces the child at the correct position', () => {\n      const g = new G()\n      const rect1 = g.rect(100, 100)\n      const rect2 = g.rect(100, 100)\n      const rect3 = g.rect(100, 100)\n      const circle = new Circle()\n      rect2.replace(circle)\n      expect(g.children()).toEqual([rect1, circle, rect3])\n    })\n\n    it('also works without a parent', () => {\n      const rect = new Rect()\n      const circle = new Circle()\n      expect(rect.replace(circle)).toBe(circle)\n    })\n  })\n\n  describe('round()', () => {\n    it('rounds all attributes whose values are numbers to two decimals by default', () => {\n      const rect = new Rect({ id: 'foo', x: 10.678, y: 3, width: 123.456 })\n      expect(rect.round().attr()).toEqual({\n        id: 'foo',\n        x: 10.68,\n        y: 3,\n        width: 123.46\n      })\n    })\n\n    it('rounds all attributes whose values are numbers to the passed precision', () => {\n      const rect = new Rect({ id: 'foo', x: 10.678, y: 3, width: 123.456 })\n      expect(rect.round(1).attr()).toEqual({\n        id: 'foo',\n        x: 10.7,\n        y: 3,\n        width: 123.5\n      })\n    })\n\n    it('rounds the given attributes whose values are numbers to the passed precision', () => {\n      const rect = new Rect({ id: 'foo', x: 10.678, y: 3, width: 123.456 })\n      expect(rect.round(1, ['id', 'x']).attr()).toEqual({\n        id: 'foo',\n        x: 10.7,\n        y: 3,\n        width: 123.456\n      })\n    })\n  })\n\n  describe('svg()', () => {\n    it('calls xml with the svg namespace', () => {\n      const group = new G()\n      const spy = spyOn(group, 'xml')\n      group.svg('<foo>')\n      expect(spy).toHaveBeenCalledWith('<foo>', undefined, svg)\n    })\n  })\n\n  describe('toString()', () => {\n    it('calls id() and returns its result', () => {\n      const rect = new Rect({ id: 'foo' })\n      const spy = spyOn(rect, 'id').and.callThrough()\n      expect(rect.toString()).toBe('foo')\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('words', () => {\n    it('sets the nodes textContent to the given value', () => {\n      const tspan = new Tspan().words('Hello World')\n      expect(tspan.text()).toBe('Hello World')\n    })\n  })\n\n  describe('wrap()', function () {\n    var canvas\n    var rect\n\n    beforeEach(function () {\n      canvas = SVG()\n      rect = canvas.rect(100, 100)\n    })\n\n    it('returns the current element', function () {\n      expect(rect.wrap(new G())).toBe(rect)\n    })\n\n    it('wraps the passed element around the current element', function () {\n      var g = new G()\n      expect(rect.wrap(g).parent()).toBe(g)\n      expect(g.parent()).toBe(canvas)\n    })\n\n    it('wraps also when element is not in the dom', () => {\n      var g = new G()\n      var rect = new Rect()\n      expect(rect.wrap(g).parent()).toBe(g)\n      expect(g.parent()).toBe(null)\n    })\n\n    it('inserts at the correct position', () => {\n      canvas.rect(100, 100)\n      rect = canvas.rect(100, 100)\n      var position = rect.position()\n      var g = new G()\n      expect(rect.wrap(g).parent().position()).toBe(position)\n    })\n\n    it('allows to pass an svg string as element', () => {\n      rect.wrap('<g />')\n      expect(rect.parent()).toEqual(any(G))\n      expect(rect.parent().parent()).toBe(canvas)\n    })\n\n    it('allows to pass an svg string as element', () => {\n      rect.wrap('<g />')\n      expect(rect.parent()).toEqual(any(G))\n      expect(rect.parent().parent()).toBe(canvas)\n    })\n\n    it('allows to pass an svg string as element when element not in the dom', () => {\n      var rect = new Rect()\n      rect.wrap(SVG('<g />'))\n      expect(rect.parent()).toEqual(any(G))\n      expect(rect.parent().parent()).toBe(null)\n    })\n\n    it('allows to pass an svg node as element', () => {\n      const node = create('g')\n      rect.wrap(node)\n      expect(rect.parent()).toEqual(any(G))\n      expect(rect.parent().node).toBe(node)\n      expect(rect.parent().parent()).toBe(canvas)\n    })\n  })\n\n  describe('xml()', () => {\n    describe('as setter', () => {\n      it('returns itself', () => {\n        const g = new G()\n        expect(g.xml('<rect />', undefined, svg)).toBe(g)\n      })\n\n      it('imports a single element', () => {\n        const g = new G().xml('<rect />', undefined, svg)\n        expect(g.children()).toEqual([any(Rect)])\n        expect(g.children()[0].node.namespaceURI).toBe(svg)\n      })\n\n      it('imports multiple elements', () => {\n        const g = new G().xml('<rect /><circle />', undefined, svg)\n        expect(g.children()).toEqual([any(Rect), any(Circle)])\n      })\n\n      it('replaces the current element with the imported elements with outerHtml = true', () => {\n        const canvas = new Svg()\n        const g = canvas.group()\n        g.xml('<rect /><circle />', true, svg)\n        expect(canvas.children()).toEqual([any(Rect), any(Circle)])\n      })\n\n      it('returns the parent when outerHtml = true', () => {\n        const canvas = new Svg()\n        const g = canvas.group()\n        expect(g.xml('<rect /><circle />', true, svg)).toBe(canvas)\n        expect(canvas.children()).toEqual([any(Rect), any(Circle)])\n      })\n\n      it('works without a parent', () => {\n        const canvas = new Svg()\n        expect(canvas.xml('<rect /><circle />', undefined, svg)).toBe(canvas)\n      })\n    })\n\n    describe('as getter', () => {\n      let canvas, group, rect\n\n      beforeEach(() => {\n        canvas = new Svg().removeNamespace()\n        group = canvas.group()\n        rect = group.rect(123.456, 234.567)\n      })\n\n      it('returns the svg string of the element by default', () => {\n        expect(rect.xml(), svg).toBe(\n          '<rect width=\"123.456\" height=\"234.567\"></rect>'\n        )\n        expect(canvas.xml(), svg).toBe(\n          '<svg><g><rect width=\"123.456\" height=\"234.567\"></rect></g></svg>'\n        )\n      })\n\n      it('returns the innerHtml when outerHtml = false', () => {\n        expect(rect.xml(false, svg)).toBe('')\n        expect(canvas.xml(false, svg)).toBe(\n          '<g><rect width=\"123.456\" height=\"234.567\"></rect></g>'\n        )\n      })\n\n      it('runs a function on every exported node', () => {\n        expect(rect.xml((el) => el.round(1))).toBe(\n          '<rect width=\"123.5\" height=\"234.6\"></rect>'\n        )\n      })\n\n      it('runs a function on every exported node and replaces node with returned node if return value is not falsy', () => {\n        expect(rect.xml(() => new Circle(), svg)).toBe('<circle></circle>')\n        expect(canvas.xml(() => new G(), svg)).toBe('<g></g>') // outer <svg> was replaced by an empty g\n        expect(\n          canvas.xml((el) => {\n            if (el instanceof Rect) return new Circle()\n            if (el instanceof Svg) el.removeNamespace()\n          }, svg)\n        ).toBe('<svg><g><circle></circle></g></svg>')\n      })\n\n      it('runs a function on every exported node and removes node if return value is false', () => {\n        expect(group.xml(() => false, svg)).toBe('')\n        expect(canvas.xml(() => false, svg)).toBe('')\n        expect(\n          canvas.xml((el) => {\n            if (el instanceof Svg) {\n              el.removeNamespace()\n            } else {\n              return false\n            }\n          }, svg)\n        ).toBe('<svg></svg>')\n      })\n\n      it('runs a function on every inner node and exports it when outerHtml = false', () => {\n        expect(canvas.xml(() => false, false, svg)).toBe('')\n        expect(canvas.xml(() => undefined, false, svg)).toBe(\n          '<g><rect width=\"123.456\" height=\"234.567\"></rect></g>'\n        )\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Element.js",
    "content": "/* globals describe, expect, it, beforeEach, spyOn, jasmine, container */\n\nimport { Element, create, Rect, G, SVG, Text } from '../../../src/main.js'\nconst { any, objectContaining } = jasmine\n\ndescribe('Element.js', function () {\n  let element\n\n  beforeEach(() => {\n    element = new Element(create('rect'))\n  })\n\n  describe('()', () => {\n    it('creates a new object of type Element', () => {\n      expect(element).toEqual(any(Element))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Element(create('rect'), { id: 'foo' }).id()).toBe('foo')\n    })\n\n    it('references the instance on the passed node', () => {\n      expect(element.node.instance).toBe(element)\n    })\n\n    it('sets the dom property to an empty object', () => {\n      expect(element.dom).toEqual({})\n    })\n\n    it('hydrates the dom property with data found in the dom', () => {\n      element.dom = { foo: 'bar' }\n      element.writeDataToDom()\n      expect(new Element(element.node).dom).toEqual({ foo: 'bar' })\n    })\n\n    it('falls back to empty object when attribute is null', () => {\n      element.node.setAttribute('data-svg', 'null')\n      expect(new Element(element.node).dom).toEqual({})\n    })\n\n    it('uses old svgjs:data attribute if present', () => {\n      element.node.setAttribute('svgjs:data', '{\"foo\":\"bar\"}')\n      expect(new Element(element.node).dom).toEqual({ foo: 'bar' })\n    })\n  })\n\n  describe('center()', () => {\n    it('calls cx and cy with passed parameters and returns itself', () => {\n      const spyCx = spyOn(element, 'cx').and.callThrough()\n      const spyCy = spyOn(element, 'cy').and.callThrough()\n      expect(element.center(1, 2)).toBe(element)\n      expect(spyCx).toHaveBeenCalledWith(1)\n      expect(spyCy).toHaveBeenCalledWith(2)\n    })\n  })\n\n  describe('cx()', () => {\n    it('gets the elements center along the x axis', () => {\n      element.attr({ x: 10, width: 100 })\n      expect(element.cx()).toBe(60)\n    })\n\n    it('centers the element along the x axis and returns itself', () => {\n      element.attr({ x: 10, width: 100 })\n      expect(element.cx(100)).toBe(element)\n      expect(element.attr('x')).toBe(50)\n    })\n  })\n\n  describe('cy()', () => {\n    it('gets the elements center along the y axis', () => {\n      element.attr({ y: 10, height: 100 })\n      expect(element.cy()).toBe(60)\n    })\n\n    it('centers the element along the y axis and returns itself', () => {\n      element.attr({ y: 10, height: 100 })\n      expect(element.cy(100)).toBe(element)\n      expect(element.attr('y')).toBe(50)\n    })\n  })\n\n  describe('defs()', () => {\n    it('returns null if detached', () => {\n      expect(new Rect().defs()).toBe(null)\n      expect(new G().put(new Rect()).defs()).toBe(null)\n    })\n\n    it('calls defs on root node', () => {\n      const canvas = SVG()\n      const rect = canvas.rect(100, 100)\n      const spy = spyOn(canvas, 'defs').and.callThrough()\n      expect(rect.defs()).toBe(canvas.defs())\n      expect(spy.calls.count()).toBe(2)\n    })\n  })\n\n  describe('dmove()', () => {\n    it('calls dx and dy with passed parameters and returns itself', () => {\n      const spyDx = spyOn(element, 'dx').and.callThrough()\n      const spyDy = spyOn(element, 'dy').and.callThrough()\n      expect(element.dmove(1, 2)).toBe(element)\n      expect(spyDx).toHaveBeenCalledWith(1)\n      expect(spyDy).toHaveBeenCalledWith(2)\n    })\n  })\n\n  describe('dx()', () => {\n    it('moves by zero by default', () => {\n      element.attr({ x: 10, width: 100 })\n      expect(element.dx().x()).toBe(10)\n    })\n\n    it('moves the element along the x axis relatively and returns itself', () => {\n      element.attr({ x: 10, width: 100 })\n      expect(element.dx(100)).toBe(element)\n      expect(element.attr('x')).toBe(110)\n    })\n  })\n\n  describe('dy()', () => {\n    it('moves by zero by default', () => {\n      element.attr({ y: 10, height: 100 })\n      expect(element.dy().y()).toBe(10)\n    })\n\n    it('moves the element along the x axis relatively and returns itself', () => {\n      element.attr({ y: 10, height: 100 })\n      expect(element.dy(100)).toBe(element)\n      expect(element.attr('y')).toBe(110)\n    })\n  })\n\n  describe('root()', () => {\n    it('returns the root of this element', () => {\n      const canvas = SVG()\n      const rect = canvas.rect()\n      expect(rect.root()).toBe(canvas)\n    })\n\n    it('returns null if element is detached', () => {\n      expect(new G().put(new Rect()).root()).toBe(null)\n    })\n  })\n\n  describe('getEventHolder()', () => {\n    it('returns itself', () => {\n      expect(element.getEventHolder()).toBe(element)\n    })\n  })\n\n  describe('height()', () => {\n    it('calls attr with height', () => {\n      const spy = spyOn(element, 'attr')\n      element.height(123)\n      expect(spy).toHaveBeenCalledWith('height', 123)\n    })\n  })\n\n  describe('move()', () => {\n    it('calls x and y with passed parameters and returns itself', () => {\n      const spyx = spyOn(element, 'x').and.callThrough()\n      const spyy = spyOn(element, 'y').and.callThrough()\n      expect(element.move(1, 2)).toBe(element)\n      expect(spyx).toHaveBeenCalledWith(1)\n      expect(spyy).toHaveBeenCalledWith(2)\n    })\n  })\n\n  describe('parents()', () => {\n    it('returns array of parents until the passed element or root svg', () => {\n      const canvas = SVG().addTo(container)\n      const _groupA = canvas.group().addClass('test')\n      const group1 = canvas.group().addClass('test')\n      const group2 = group1.group()\n      const group3 = group2.group()\n      const rect = group3.rect(100, 100)\n\n      expect(rect.parents('.test')).toEqual([group3, group2, group1])\n      expect(rect.parents(group2)).toEqual([group3, group2])\n      expect(rect.parents(group1).length).toBe(3)\n      expect(rect.parents()).toEqual([group3, group2, group1, canvas])\n    })\n\n    it('returns array of parents until the closest matching parent', () => {\n      const canvas = SVG().addTo(container)\n      const _groupA = canvas.group().addClass('test')\n      const group1 = canvas.group().addClass('test')\n      const group2 = group1.group().addClass('test').addClass('foo')\n      const group3 = group2.group().addClass('foo')\n      const rect = group3.rect(100, 100)\n\n      expect(rect.parents('.test')).toEqual([group3, group2])\n      expect(rect.parents('.foo')).toEqual([group3])\n      expect(rect.parents('.test:not(.foo)')).toEqual([group3, group2, group1])\n    })\n\n    it('returns null if the passed element is not an ancestor', () => {\n      const canvas = SVG().addTo(container)\n      const groupA = canvas.group().addClass('test')\n      const group1 = canvas.group()\n      const group2 = group1.group()\n      const group3 = group2.group()\n      const rect = group3.rect(100, 100)\n\n      expect(rect.parents('.does-not-exist')).toEqual(null)\n      expect(rect.parents('.test')).toEqual(null)\n      expect(rect.parents(groupA)).toEqual(null)\n    })\n  })\n\n  describe('reference()', () => {\n    it('gets a referenced element from a given attribute', () => {\n      const canvas = SVG().addTo(container)\n      const rect = canvas.defs().rect(100, 100)\n      const use = canvas.use(rect)\n      const mark = canvas.marker(10, 10)\n      const path = canvas.path('M0 0 50 50').marker('end', mark)\n\n      expect(use.reference('href')).toBe(rect)\n      expect(path.reference('marker-end')).toBe(mark)\n      expect(rect.reference('width')).toBe(null)\n    })\n  })\n\n  describe('setData()', () => {\n    it('sets the given data to the dom property and returns itself', () => {\n      expect(element.setData({ foo: 'bar' })).toBe(element)\n      expect(element.dom).toEqual({ foo: 'bar' })\n    })\n  })\n\n  describe('size()', () => {\n    it('calls width and height with passed parameters and returns itself', () => {\n      const spyWidth = spyOn(element, 'width').and.callThrough()\n      const spyHeight = spyOn(element, 'height').and.callThrough()\n      expect(element.size(1, 2)).toBe(element)\n      expect(spyWidth).toHaveBeenCalledWith(objectContaining({ value: 1 }))\n      expect(spyHeight).toHaveBeenCalledWith(objectContaining({ value: 2 }))\n    })\n\n    it('changes height proportionally if null', () => {\n      const canvas = SVG().addTo(container)\n      const element = canvas.rect(100, 100)\n      const spyWidth = spyOn(element, 'width').and.callThrough()\n      const spyHeight = spyOn(element, 'height').and.callThrough()\n      expect(element.size(200, null)).toBe(element)\n      expect(spyWidth).toHaveBeenCalledWith(objectContaining({ value: 200 }))\n      expect(spyHeight).toHaveBeenCalledWith(objectContaining({ value: 200 }))\n    })\n\n    it('changes width proportionally if null', () => {\n      const canvas = SVG().addTo(container)\n      const element = canvas.rect(100, 100)\n      const spyWidth = spyOn(element, 'width').and.callThrough()\n      const spyHeight = spyOn(element, 'height').and.callThrough()\n      expect(element.size(null, 200)).toBe(element)\n      expect(spyWidth).toHaveBeenCalledWith(objectContaining({ value: 200 }))\n      expect(spyHeight).toHaveBeenCalledWith(objectContaining({ value: 200 }))\n    })\n  })\n\n  describe('width()', () => {\n    it('calls attr with width', () => {\n      const spy = spyOn(element, 'attr')\n      element.width(123)\n      expect(spy).toHaveBeenCalledWith('width', 123)\n    })\n  })\n\n  describe('writeDataToDom()', () => {\n    it('removes previously set data', () => {\n      element.node.setAttribute('data-svgjs', JSON.stringify({ foo: 'bar' }))\n      element.writeDataToDom()\n      expect(element.node.getAttribute('data-svgjs')).toBe(null)\n    })\n\n    it('writes data from the dom property into the dom', () => {\n      element.dom = { foo: 'bar' }\n      element.writeDataToDom()\n      expect(element.node.getAttribute('data-svgjs')).toBe(\n        JSON.stringify({ foo: 'bar' })\n      )\n    })\n\n    it('recursively calls writeDataToDom on all children', () => {\n      const g = new G()\n      const rect = g.rect(100, 100)\n      const spy = spyOn(rect, 'writeDataToDom')\n      g.writeDataToDom()\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('filters out default data', () => {\n      const node1 = new Text()\n      const node2 = new Text()\n      node2.dom.foo = 'bar'\n      node1.writeDataToDom()\n      node2.writeDataToDom()\n      expect(node1.node.getAttribute('data-svgjs')).toBe(null)\n      expect(node2.node.getAttribute('data-svgjs')).toBe('{\"foo\":\"bar\"}')\n    })\n  })\n\n  describe('x()', () => {\n    it('calls attr with x', () => {\n      const spy = spyOn(element, 'attr')\n      element.x(123)\n      expect(spy).toHaveBeenCalledWith('x', 123)\n    })\n  })\n\n  describe('y()', () => {\n    it('calls attr with y', () => {\n      const spy = spyOn(element, 'attr')\n      element.y(123)\n      expect(spy).toHaveBeenCalledWith('y', 123)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Ellipse.js",
    "content": "/* globals describe, expect, it, spyOn, jasmine, container */\n\nimport { Ellipse, SVG, G } from '../../../src/main.js'\n\nconst { any, objectContaining } = jasmine\n\ndescribe('Ellipse.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Ellipse', () => {\n      expect(new Ellipse()).toEqual(any(Ellipse))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Ellipse({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('size()', () => {\n    it('calls rx and ry with passed parameters and returns itself', () => {\n      const ellipse = new Ellipse()\n      const spyrx = spyOn(ellipse, 'rx').and.callThrough()\n      const spyry = spyOn(ellipse, 'ry').and.callThrough()\n      expect(ellipse.size(4, 2)).toBe(ellipse)\n      expect(spyrx).toHaveBeenCalledWith(objectContaining({ value: 2 }))\n      expect(spyry).toHaveBeenCalledWith(objectContaining({ value: 1 }))\n    })\n\n    it('changes ry proportionally if null', () => {\n      const canvas = SVG().addTo(container)\n      const ellipse = canvas.ellipse(100, 100)\n      const spyrx = spyOn(ellipse, 'rx').and.callThrough()\n      const spyry = spyOn(ellipse, 'ry').and.callThrough()\n      expect(ellipse.size(200, null)).toBe(ellipse)\n      expect(spyrx).toHaveBeenCalledWith(objectContaining({ value: 100 }))\n      expect(spyry).toHaveBeenCalledWith(objectContaining({ value: 100 }))\n    })\n\n    it('changes rx proportionally if null', () => {\n      const canvas = SVG().addTo(container)\n      const ellipse = canvas.ellipse(100, 100)\n      const spyrx = spyOn(ellipse, 'rx').and.callThrough()\n      const spyry = spyOn(ellipse, 'ry').and.callThrough()\n      expect(ellipse.size(null, 200)).toBe(ellipse)\n      expect(spyrx).toHaveBeenCalledWith(objectContaining({ value: 100 }))\n      expect(spyry).toHaveBeenCalledWith(objectContaining({ value: 100 }))\n    })\n  })\n\n  describe('Container', () => {\n    describe('ellipse()', () => {\n      it('creates a ellipse with given size', () => {\n        const group = new G()\n        const ellipse = group.ellipse(50, 50)\n        expect(ellipse.attr('rx')).toBe(25)\n        expect(ellipse.attr('ry')).toBe(25)\n        expect(ellipse).toEqual(any(Ellipse))\n      })\n\n      it('defaults to same radius with one argument', () => {\n        const group = new G()\n        const ellipse = group.ellipse(50)\n        expect(ellipse.attr('rx')).toBe(25)\n        expect(ellipse.attr('ry')).toBe(25)\n        expect(ellipse).toEqual(any(Ellipse))\n      })\n\n      it('defaults to zero radius with no argument', () => {\n        const group = new G()\n        const ellipse = group.ellipse()\n        expect(ellipse.attr('rx')).toBe(0)\n        expect(ellipse.attr('ry')).toBe(0)\n        expect(ellipse).toEqual(any(Ellipse))\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/ForeignObject.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport { makeInstance, ForeignObject } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('ForeignObject.js', () => {\n  describe('()', () => {\n    it('creates a new object of type ForeignObject', () => {\n      expect(new ForeignObject()).toEqual(any(ForeignObject))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new ForeignObject({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('Container', () => {\n    describe('foreignObject()', () => {\n      it('creates a foreignObject in the container', () => {\n        const canvas = makeInstance().addTo('#canvas')\n        const foreignObject = canvas.foreignObject()\n        expect(foreignObject).toEqual(any(ForeignObject))\n        expect(foreignObject.parent()).toBe(canvas)\n      })\n\n      it('sets width and height correctly on construction', () => {\n        const canvas = makeInstance().addTo('#canvas')\n        const foreignObject = canvas.foreignObject(100, 200)\n        expect(foreignObject.width()).toBe(100)\n        expect(foreignObject.height()).toBe(200)\n      })\n    })\n  })\n\n  describe('Element methods', () => {\n    it('is usable with Elements methods such as height() and width()', () => {\n      const canvas = makeInstance().addTo('#canvas')\n      const foreignObject = canvas.foreignObject()\n      foreignObject.width(100).height(200).x(10).y(20)\n      expect(foreignObject.width()).toBe(100)\n      expect(foreignObject.height()).toBe(200)\n      expect(foreignObject.x()).toBe(10)\n      expect(foreignObject.y()).toBe(20)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Fragment.js",
    "content": "/* globals describe, expect, it, spyOn, jasmine */\n\nimport { Fragment, Dom } from '../../../src/main.js'\nimport { getWindow } from '../../../src/utils/window.js'\nimport { svg } from '../../../src/modules/core/namespaces.js'\n\nconst { any } = jasmine\n\ndescribe('Fragment.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Fragment', () => {\n      expect(new Fragment()).toEqual(any(Fragment))\n    })\n\n    it('uses passed node instead of creating', () => {\n      const fragment = getWindow().document.createDocumentFragment()\n      expect(new Fragment(fragment).node).toBe(fragment)\n    })\n\n    it('has all Container methods available', () => {\n      const frag = new Fragment()\n      const rect = frag.rect(100, 100)\n\n      expect(frag.children()).toEqual([rect])\n    })\n  })\n\n  describe('xml()', () => {\n    describe('as setter', () => {\n      it('calls parent method with outerHtml = false', () => {\n        const frag = new Fragment()\n        const spy = spyOn(Dom.prototype, 'xml').and.callThrough()\n        frag.xml('<rect />', true, svg)\n        expect(spy).toHaveBeenCalledWith('<rect />', false, svg)\n      })\n    })\n\n    describe('as getter', () => {\n      it('calls parent method with outerHtml = false - 1', () => {\n        const frag = new Fragment()\n        const group = frag.group()\n        group.rect(123.456, 234.567)\n        const spy = spyOn(Dom.prototype, 'xml').and.callThrough()\n\n        expect(frag.xml(false, svg)).toBe(\n          '<g><rect width=\"123.456\" height=\"234.567\"></rect></g>'\n        )\n        expect(spy).toHaveBeenCalledWith(false, svg)\n      })\n\n      it('calls parent method with outerHtml = false - 2', () => {\n        const frag = new Fragment()\n        const group = frag.group()\n        group.rect(123.456, 234.567)\n        const spy = spyOn(Dom.prototype, 'xml').and.callThrough()\n\n        expect(frag.xml(true, svg)).toBe(\n          '<g><rect width=\"123.456\" height=\"234.567\"></rect></g>'\n        )\n        expect(spy).toHaveBeenCalledWith(false, svg)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/G.js",
    "content": "/* globals describe, expect, it, jasmine, container */\n\nimport { G, SVG } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('G.js', () => {\n  describe('()', () => {\n    it('creates a new object of type G', () => {\n      expect(new G()).toEqual(any(G))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new G({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('Container', () => {\n    describe('group()', () => {\n      it('creates a group in the container', () => {\n        const canvas = SVG().addTo(container)\n        const g = canvas.group()\n        expect(g).toEqual(any(G))\n        expect(g.parent()).toBe(canvas)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Gradient.js",
    "content": "/* globals describe, expect, it, spyOn, jasmine, container */\n\nimport { Gradient, SVG, Container } from '../../../src/main.js'\n\nconst { any, objectContaining, createSpy } = jasmine\n\ndescribe('Gradient.js', () => {\n  describe('()', () => {\n    it('creates a new object of type LinearGradient', () => {\n      const gradient = new Gradient('linear')\n      expect(gradient).toEqual(any(Gradient))\n      expect(gradient.type).toBe('linearGradient')\n    })\n\n    it('creates a new object of type RadialGradient', () => {\n      const gradient = new Gradient('radial')\n      expect(gradient).toEqual(any(Gradient))\n      expect(gradient.type).toBe('radialGradient')\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Gradient('linear', { id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('attr()', () => {\n    it('relays to parents attr method for any call except transformation', () => {\n      const gradient = new Gradient('linear')\n      const spy = spyOn(Container.prototype, 'attr')\n      gradient.attr(1, 2, 3)\n      gradient.attr('transform', 2, 3)\n\n      expect(spy).toHaveBeenCalledWith(1, 2, 3)\n      expect(spy).toHaveBeenCalledWith('gradientTransform', 2, 3)\n    })\n  })\n\n  describe('bbox()', () => {\n    it('returns an empty box', () => {\n      expect(new Gradient('linear').bbox().isNulled()).toBe(true)\n    })\n  })\n\n  describe('targets()', () => {\n    it('gets all targets of this gradient', () => {\n      const canvas = SVG().addTo(container)\n      const gradient = canvas.gradient('linear')\n      const rect = canvas.rect(100, 100).fill(gradient)\n      expect(gradient.targets()).toEqual([rect])\n    })\n  })\n\n  describe('toString()', () => {\n    it('calls url() and returns the result', () => {\n      const gradient = new Gradient('linear')\n      expect(gradient.toString()).toBe(gradient.url())\n    })\n  })\n\n  describe('update()', () => {\n    it('clears the element', () => {\n      const gradient = new Gradient('linear')\n      gradient.stop(0.1, '#fff')\n      expect(gradient.update().children()).toEqual([])\n    })\n\n    it('executes a function in the context of the gradient', () => {\n      const gradient = new Gradient('linear')\n      const spy = createSpy('gradient')\n      gradient.update(spy)\n      expect(spy.calls.all()).toEqual([\n        objectContaining({ object: gradient, args: [gradient] })\n      ])\n    })\n  })\n\n  describe('url()', () => {\n    it('returns url(#id)', () => {\n      const gradient = new Gradient('linear').id('foo')\n      expect(gradient.url()).toBe('url(#foo)')\n    })\n  })\n\n  describe('Container', () => {\n    it('relays the call to defs', () => {\n      const canvas = new SVG()\n      const defs = canvas.defs()\n      const spy = spyOn(defs, 'gradient').and.callThrough()\n      const spy2 = createSpy('gradient')\n\n      canvas.gradient('linear', spy2)\n      expect(spy).toHaveBeenCalledWith('linear', spy2)\n      expect(spy2).toHaveBeenCalled()\n    })\n  })\n\n  describe('Defs', () => {\n    it('creates a pattern in the defs', () => {\n      const canvas = new SVG()\n      const defs = canvas.defs()\n      const spy = createSpy('gradient')\n      const gradient = defs.gradient('linear', spy)\n      expect(gradient).toEqual(any(Gradient))\n      expect(gradient.type).toBe('linearGradient')\n      expect(defs.children()).toEqual([gradient])\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Image.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport { Image, Pattern, SVG } from '../../../src/main.js'\nimport { getWindow } from '../../../src/utils/window.js'\n\nconst { any, objectContaining, createSpy } = jasmine\n\nconst url = 'spec/fixtures/pixel.png'\ndescribe('Image.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Image', () => {\n      expect(new Image()).toEqual(any(Image))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Image({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('load()', () => {\n    it('is a noop when url is falsy and returns itself', () => {\n      const image = Object.freeze(new Image())\n      expect(image.load()).toBe(image)\n    })\n\n    it('executes a callback when the image is loaded', (done) => {\n      const spy = createSpy('image', (e) => {\n        expect(e.target.complete).toBe(true)\n        expect(spy.calls.all()).toEqual([\n          objectContaining({ object: image, args: [any(getWindow().Event)] })\n        ])\n        done()\n      }).and.callThrough()\n\n      const image = new Image().load(url, spy)\n    })\n\n    it('errors when image cant be loaded', () => {\n      // cant test this because of jasmine timeouts and browser disconnects\n    })\n\n    // it('sets the width and height of the image automatically', () => {\n    //   const image = new Image('spec/fixtures/pixel.png')\n    // })\n\n    it('should set width and height automatically if no size is given', (done) => {\n      const image = new Image().load(url, () => {\n        expect(image.attr('height')).toBe(1)\n        expect(image.attr('width')).toBe(1)\n        done()\n      })\n    })\n\n    it('should not change with and height when size already set', (done) => {\n      const image = new Image()\n        .load(url, () => {\n          expect(image.attr('height')).toBe(100)\n          expect(image.attr('width')).toBe(100)\n          done()\n        })\n        .size(100, 100)\n    })\n\n    it('changes size of pattern to image size if parent is pattern and size is 0', (done) => {\n      const pattern = new Pattern().size(0, 0)\n      new Image()\n        .load(url, () => {\n          expect(pattern.attr('height')).toBe(100)\n          expect(pattern.attr('width')).toBe(100)\n          done()\n        })\n        .size(100, 100)\n        .addTo(pattern)\n    })\n\n    it('does not change size of pattern if pattern has a size set', (done) => {\n      const pattern = new Pattern().size(50, 50)\n      new Image()\n        .load(url, () => {\n          expect(pattern.attr('height')).toBe(50)\n          expect(pattern.attr('width')).toBe(50)\n          done()\n        })\n        .size(100, 100)\n        .addTo(pattern)\n    })\n  })\n\n  describe('Container', () => {\n    describe('image()', () => {\n      it('creates image in the container', () => {\n        const canvas = SVG()\n        const image = canvas.image(url)\n        expect(image).toBe(image)\n        expect(canvas.children()).toEqual([image])\n      })\n    })\n  })\n\n  describe('attribute hook', () => {\n    it('creates a pattern in defs when value is an image and puts image there', () => {\n      const canvas = SVG()\n      const image = new Image()\n      canvas.rect(100, 100).attr('something', image)\n      expect(canvas.defs().children()).toEqual([any(Pattern)])\n      expect(canvas.defs().findOne('image')).toBe(image)\n    })\n\n    it('creates an image from image path in defs with pattern when attr is fill', () => {\n      const canvas = SVG()\n      canvas.rect(100, 100).attr('fill', url)\n      expect(canvas.defs().children()).toEqual([any(Pattern)])\n      expect(canvas.defs().findOne('image').attr('href')).toBe(url)\n    })\n\n    it('creates an image from image path in defs with pattern when attr is stroke', () => {\n      const canvas = SVG()\n      canvas.rect(100, 100).attr('stroke', url)\n      expect(canvas.defs().children()).toEqual([any(Pattern)])\n      expect(canvas.defs().findOne('image').attr('href')).toBe(url)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Line.js",
    "content": "/* globals describe, expect, it, beforeEach, spyOn, jasmine, container */\n\nimport { Line, PointArray, SVG, G } from '../../../src/main.js'\n\nconst { any, objectContaining } = jasmine\n\ndescribe('Line.js', () => {\n  let line\n\n  beforeEach(() => {\n    line = new Line()\n  })\n\n  describe('()', () => {\n    it('creates a new object of type Line', () => {\n      expect(new Line()).toEqual(any(Line))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Line({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('array()', () => {\n    it('returns a PointArray containing the points of the line', () => {\n      const array = line.plot(1, 2, 3, 4).array()\n      expect(array).toEqual(any(PointArray))\n      expect(array).toEqual([\n        [1, 2],\n        [3, 4]\n      ])\n    })\n  })\n\n  describe('move()', () => {\n    it('returns itself', () => {\n      expect(line.move(0, 0)).toBe(line)\n    })\n\n    it('moves the line along x and y axis', () => {\n      const canvas = SVG().addTo(container)\n      const line = canvas.line(1, 2, 3, 4)\n      line.move(50, 50)\n      expect(line.bbox()).toEqual(\n        objectContaining({\n          x: 50,\n          y: 50,\n          width: 2,\n          height: 2\n        })\n      )\n    })\n  })\n\n  describe('plot()', () => {\n    it('relays to array() as getter', () => {\n      const spy = spyOn(line, 'array')\n      line.plot()\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('calls attr with line attributes when 4 parameters given', () => {\n      const spy = spyOn(line, 'attr')\n      line.plot(1, 2, 3, 4)\n      expect(spy).toHaveBeenCalledWith({ x1: 1, y1: 2, x2: 3, y2: 4 })\n    })\n\n    it('calls attr with line attributes when string given', () => {\n      const spy = spyOn(line, 'attr')\n      line.plot('1, 2, 3, 4')\n      expect(spy).toHaveBeenCalledWith({ x1: 1, y1: 2, x2: 3, y2: 4 })\n    })\n\n    it('calls attr with line attributes when array given', () => {\n      const spy = spyOn(line, 'attr')\n      line.plot([1, 2, 3, 4])\n      expect(spy).toHaveBeenCalledWith({ x1: 1, y1: 2, x2: 3, y2: 4 })\n    })\n\n    it('calls attr with line attributes when multi array given', () => {\n      const spy = spyOn(line, 'attr')\n      line.plot([\n        [1, 2],\n        [3, 4]\n      ])\n      expect(spy).toHaveBeenCalledWith({ x1: 1, y1: 2, x2: 3, y2: 4 })\n    })\n\n    it('calls attr with line attributes when PointArray given', () => {\n      const spy = spyOn(line, 'attr')\n      line.plot(\n        new PointArray([\n          [1, 2],\n          [3, 4]\n        ])\n      )\n      expect(spy).toHaveBeenCalledWith({ x1: 1, y1: 2, x2: 3, y2: 4 })\n    })\n  })\n\n  describe('size()', () => {\n    it('returns itself', () => {\n      expect(line.size(50, 50)).toBe(line)\n    })\n\n    it('sets the size of the line', () => {\n      const canvas = SVG().addTo(container)\n      const line = canvas.line(1, 2, 3, 4)\n      line.size(50, 50)\n      expect(line.bbox()).toEqual(\n        objectContaining({\n          width: 50,\n          height: 50,\n          x: 1,\n          y: 2\n        })\n      )\n    })\n\n    it('changes height proportionally', () => {\n      const canvas = SVG().addTo(container)\n      const line = canvas.line(1, 2, 3, 4)\n      line.size(50, null)\n      expect(line.bbox()).toEqual(\n        objectContaining({\n          width: 50,\n          height: 50,\n          x: 1,\n          y: 2\n        })\n      )\n    })\n\n    it('changes width proportionally', () => {\n      const canvas = SVG().addTo(container)\n      const line = canvas.line(1, 2, 3, 4)\n      line.size(null, 50)\n      expect(line.bbox()).toEqual(\n        objectContaining({\n          width: 50,\n          height: 50,\n          x: 1,\n          y: 2\n        })\n      )\n    })\n  })\n\n  describe('Container', () => {\n    describe('line()', () => {\n      it('creates a line with given points', () => {\n        const group = new G()\n        const line = group.line(1, 2, 3, 4)\n        expect(line.array()).toEqual([\n          [1, 2],\n          [3, 4]\n        ])\n        expect(line).toEqual(any(Line))\n      })\n\n      it('defaults to zero line', () => {\n        const group = new G()\n        const line = group.line()\n        expect(line.array()).toEqual([\n          [0, 0],\n          [0, 0]\n        ])\n        expect(line).toEqual(any(Line))\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Marker.js",
    "content": "/* globals describe, expect, it, beforeEach, jasmine, container */\n\nimport { Marker, SVG, Defs } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Marker.js', function () {\n  describe('()', () => {\n    it('creates a new object of type Marker', () => {\n      expect(new Marker()).toEqual(any(Marker))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Marker({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('width()', () => {\n    it('sets the markerWidth attribute', () => {\n      const marker = new Marker().width(100)\n      expect(marker.attr('markerWidth')).toBe(100)\n    })\n  })\n\n  describe('height()', () => {\n    it('sets the markerHeight attribute', () => {\n      const marker = new Marker().height(100)\n      expect(marker.attr('markerHeight')).toBe(100)\n    })\n  })\n\n  describe('orient()', () => {\n    it('sets the orient attribute', () => {\n      const marker = new Marker().orient('auto')\n      expect(marker.attr('orient')).toBe('auto')\n    })\n  })\n\n  describe('ref()', () => {\n    it('sets refX and refY attribute', () => {\n      const marker = new Marker().ref(10, 20)\n      expect(marker.attr('refX')).toBe(10)\n      expect(marker.attr('refY')).toBe(20)\n    })\n  })\n\n  describe('update()', () => {\n    it('updates the marker', () => {\n      const marker = new Marker()\n      marker.rect(100, 100)\n      marker.update(function (m) {\n        m.rect(100, 100)\n        expect(this).toBe(marker)\n        expect(m).toBe(marker)\n      })\n      expect(marker.children().length).toBe(1)\n    })\n  })\n\n  describe('toString()', () => {\n    it('returns the url identifier for this marker', () => {\n      const marker = new Marker()\n      expect(marker.toString()).toBe('url(#' + marker.id() + ')')\n    })\n  })\n\n  describe('Container', () => {\n    var canvas\n    var group\n\n    beforeEach(() => {\n      canvas = SVG()\n      group = canvas.group()\n    })\n\n    describe('marker()', () => {\n      it('creates an instance of Marker', () => {\n        const marker = group.marker(10, 12)\n        expect(marker instanceof Marker).toBeTrue()\n      })\n\n      it('creates marker in defs', () => {\n        const marker = group.marker(10, 12)\n        expect(marker.parent() instanceof Defs).toBeTruthy()\n      })\n\n      it('sets the refX to half of the given width and height', () => {\n        const marker = group.marker(10, 12)\n        expect(marker.node.getAttribute('refX')).toBe('5')\n        expect(marker.node.getAttribute('refY')).toBe('6')\n      })\n    })\n  })\n\n  describe('Defs', () => {\n    describe('marker()', () => {\n      it('creates a marker in the defs and sets all attributes', () => {\n        const canvas = SVG()\n        const defs = canvas.defs()\n        const marker = defs.marker(10, 12)\n        expect(marker.attr('refX')).toBe(5)\n        expect(marker.attr('refY')).toBe(6)\n        expect(marker.attr('markerWidth')).toBe(10)\n        expect(marker.attr('markerHeight')).toBe(12)\n        expect(marker.attr('viewBox')).toBe('0 0 10 12')\n        expect(marker.attr('orient')).toBe('auto')\n        expect(marker).toEqual(any(Marker))\n        expect(defs.children()).toEqual([marker])\n      })\n    })\n  })\n\n  describe('marker', () => {\n    var path, marker, canvas\n\n    beforeEach(() => {\n      // because we use `reference` here we need to put it into the live dom\n      canvas = new SVG().addTo(container)\n      path = canvas.path(\n        'M 100 200 C 200 100 300  0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100'\n      )\n    })\n\n    it('creates an instance of Marker', () => {\n      path.marker('mid', 10, 12, function (add) {\n        add.rect(10, 12)\n\n        this.ref(5, 6)\n      })\n\n      expect(path.reference('marker-mid').children().length).toBe(1)\n      expect(path.reference('marker-mid').attr('refX')).toBe(5)\n      expect(path.reference('marker-mid') instanceof Marker).toBeTruthy()\n    })\n\n    describe('marker()', () => {\n      it('returns the target element', () => {\n        expect(path.marker('start', 10, 12)).toBe(path)\n      })\n\n      it('creates a marker and applies it to the marker-start attribute', () => {\n        path.marker('start', 10, 12)\n        marker = path.reference('marker-start')\n\n        expect(path.node.getAttribute('marker-start')).toBe(marker.toString())\n      })\n\n      it('creates a marker and applies it to the marker-mid attribute', () => {\n        path.marker('mid', 10, 12)\n        marker = path.reference('marker-mid')\n\n        expect(path.node.getAttribute('marker-mid')).toBe(marker.toString())\n      })\n\n      it('creates a marker and applies it to the marker-end attribute', () => {\n        path.marker('end', 10, 12)\n        marker = path.reference('marker-end')\n\n        expect(path.node.getAttribute('marker-end')).toBe(marker.toString())\n      })\n\n      it('creates a marker and applies it to the marker-end attribute', () => {\n        path.marker('all', 10, 12)\n        marker = path.reference('marker')\n\n        expect(path.node.getAttribute('marker')).toBe(marker.toString())\n      })\n\n      it('accepts an instance of an existing marker element as the second argument', () => {\n        marker = new Marker().size(11, 11)\n        path.marker('mid', marker)\n\n        expect(path.node.getAttribute('marker-mid')).toBe(marker.toString())\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Mask.js",
    "content": "/* globals describe, expect, it, spyOn, jasmine, container */\n\nimport { Mask, SVG, Container, Rect } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Mask.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Mask', () => {\n      expect(new Mask()).toEqual(any(Mask))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Mask({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('remove()', () => {\n    it('unmasks all targets', () => {\n      const canvas = SVG().addTo(container)\n      const mask = canvas.mask()\n      const rect = canvas.rect(100, 100).maskWith(mask)\n      expect(mask.remove()).toBe(mask)\n      expect(rect.masker()).toBe(null)\n    })\n\n    it('calls remove on parent class', () => {\n      const mask = new Mask()\n      const spy = spyOn(Container.prototype, 'remove')\n      mask.remove()\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('targets()', () => {\n    it('gets all targets of this maskPath', () => {\n      const canvas = SVG().addTo(container)\n      const mask = canvas.mask()\n      const rect = canvas.rect(100, 100).maskWith(mask)\n      expect(mask.targets()).toEqual([rect])\n    })\n  })\n\n  describe('Container', () => {\n    describe('mask()', () => {\n      it('creates a maskPath in the defs', () => {\n        const canvas = SVG()\n        const mask = canvas.mask()\n        expect(mask).toEqual(any(Mask))\n        expect(canvas.defs().children()).toEqual([mask])\n      })\n    })\n  })\n\n  describe('Element', () => {\n    describe('masker()', () => {\n      it('returns the instance of Mask the current element is masked with', () => {\n        const canvas = SVG().addTo(container)\n        const mask = canvas.mask()\n        const rect = canvas.rect(100, 100).maskWith(mask)\n        expect(rect.masker()).toEqual(mask)\n      })\n\n      it('returns null if no maskPath was found', () => {\n        expect(new Rect().masker()).toBe(null)\n      })\n    })\n\n    describe('maskWith()', () => {\n      it('sets the mask attribute on the element to the id of the maskPath', () => {\n        const mask = new Mask().id('foo')\n        const rect = new Rect().maskWith(mask)\n        expect(rect.attr('mask')).toBe('url(#foo)')\n      })\n\n      it('creates a maskPath and appends the passed element to it to mask current element', () => {\n        const canvas = SVG().addTo(container)\n        const circle = canvas.circle(40)\n        const rect = canvas.rect(100, 100).maskWith(circle)\n        expect(circle.parent()).toEqual(any(Mask))\n        expect(rect.attr('mask')).toBe(`url(#${circle.parent().id()})`)\n      })\n    })\n\n    describe('unmask()', () => {\n      it('sets the mask-target attribute to null and returns itself', () => {\n        const mask = new Mask().id('foo')\n        const rect = new Rect().maskWith(mask)\n        expect(rect.unmask()).toBe(rect)\n        expect(rect.attr('mask')).toBe(undefined)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Path.js",
    "content": "/* globals describe, expect, beforeEach, it, spyOn, jasmine, container */\n\nimport { Path, SVG, PathArray } from '../../../src/main.js'\n\nconst { any, objectContaining } = jasmine\n\ndescribe('Path.js', () => {\n  let path\n\n  beforeEach(() => {\n    path = new Path()\n  })\n\n  describe('()', () => {\n    it('creates a new object of type Path', () => {\n      expect(new Path()).toEqual(any(Path))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Path({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('array()', () => {\n    it('returns the underlying PathArray', () => {\n      const array = path.plot('M1 2 3 4').array()\n      expect(array).toEqual(any(PathArray))\n      expect(array).toEqual([\n        ['M', 1, 2],\n        ['L', 3, 4]\n      ])\n    })\n  })\n\n  describe('clear()', () => {\n    it('clears the array cache and returns itself', () => {\n      const array = path.plot('M1 2 3 4').array()\n      expect(path.clear()).toBe(path)\n      expect(array).not.toBe(path._array)\n    })\n  })\n\n  describe('height()', () => {\n    it('gets the height of the path', () => {\n      const canvas = SVG().addTo(container)\n      const path = canvas.path('M0 0 50, 50')\n      expect(path.height()).toBe(50)\n    })\n\n    it('sets the height of the path and returns itself', () => {\n      const canvas = SVG().addTo(container)\n      const path = canvas.path('M0 0 50, 50')\n      expect(path.height(100)).toBe(path)\n      expect(path.height()).toBe(100)\n    })\n  })\n\n  describe('move()', () => {\n    it('returns itself', () => {\n      expect(path.move(0, 0)).toBe(path)\n    })\n\n    it('moves the path along x and y axis', () => {\n      const canvas = SVG().addTo(container)\n      const path = canvas.path('M0 0 50, 50')\n      path.move(50, 50)\n      expect(path.bbox()).toEqual(\n        objectContaining({\n          x: 50,\n          y: 50,\n          width: 50,\n          height: 50\n        })\n      )\n    })\n  })\n\n  describe('plot()', () => {\n    it('relays to array() as getter', () => {\n      const spy = spyOn(path, 'array')\n      path.plot()\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('works by passing a string', () => {\n      const spy = spyOn(path, 'attr')\n      path.plot('M0 0 50 50')\n      expect(spy).toHaveBeenCalledWith('d', 'M0 0 50 50')\n    })\n\n    it('works with flat array', () => {\n      const spy = spyOn(path, 'attr')\n      path.plot(['M', 0, 0, 'L', 50, 50])\n      expect(spy).toHaveBeenCalledWith('d', [\n        ['M', 0, 0],\n        ['L', 50, 50]\n      ])\n    })\n\n    it('works with multi array', () => {\n      const spy = spyOn(path, 'attr')\n      path.plot([\n        ['M', 0, 0],\n        ['L', 50, 50]\n      ])\n      expect(spy).toHaveBeenCalledWith('d', [\n        ['M', 0, 0],\n        ['L', 50, 50]\n      ])\n    })\n\n    it('works with PathArray', () => {\n      const spy = spyOn(path, 'attr')\n      path.plot(\n        new PathArray([\n          ['M', 0, 0],\n          ['L', 50, 50]\n        ])\n      )\n      expect(spy).toHaveBeenCalledWith('d', [\n        ['M', 0, 0],\n        ['L', 50, 50]\n      ])\n    })\n  })\n\n  describe('size()', () => {\n    it('returns itself', () => {\n      expect(path.size(50, 50)).toBe(path)\n    })\n\n    it('sets the size of the path', () => {\n      const canvas = SVG().addTo(container)\n      const path = canvas.path('M0 0 50, 50')\n      path.size(100, 100)\n      expect(path.bbox()).toEqual(\n        objectContaining({\n          width: 100,\n          height: 100,\n          x: 0,\n          y: 0\n        })\n      )\n    })\n\n    it('changes height proportionally', () => {\n      const canvas = SVG().addTo(container)\n      const path = canvas.path('M0 0 50, 50')\n      path.size(100, null)\n      expect(path.bbox()).toEqual(\n        objectContaining({\n          width: 100,\n          height: 100,\n          x: 0,\n          y: 0\n        })\n      )\n    })\n\n    it('changes width proportionally', () => {\n      const canvas = SVG().addTo(container)\n      const path = canvas.path('M0 0 50, 50')\n      path.size(null, 100)\n      expect(path.bbox()).toEqual(\n        objectContaining({\n          width: 100,\n          height: 100,\n          x: 0,\n          y: 0\n        })\n      )\n    })\n  })\n\n  describe('width()', () => {\n    it('gets the width of the path', () => {\n      const canvas = SVG().addTo(container)\n      const path = canvas.path('M0 0 50, 50')\n      expect(path.width()).toBe(50)\n    })\n\n    it('sets the width of the path and returns itself', () => {\n      const canvas = SVG().addTo(container)\n      const path = canvas.path('M0 0 50, 50')\n      expect(path.width(100)).toBe(path)\n      expect(path.width()).toBe(100)\n    })\n  })\n\n  describe('x()', () => {\n    it('gets the x position of the path', () => {\n      const canvas = SVG().addTo(container)\n      const path = canvas.path('M10 10 50, 50')\n      expect(path.x()).toBe(10)\n    })\n\n    it('sets the x position of the path and returns itself', () => {\n      const canvas = SVG().addTo(container)\n      const path = canvas.path('M0 0 50, 50')\n      expect(path.x(100)).toBe(path)\n      expect(path.x()).toBe(100)\n    })\n  })\n\n  describe('y()', () => {\n    it('gets the y position of the path', () => {\n      const canvas = SVG().addTo(container)\n      const path = canvas.path('M10 10 50, 50')\n      expect(path.y()).toBe(10)\n    })\n\n    it('sets the y position of the path and returns itself', () => {\n      const canvas = SVG().addTo(container)\n      const path = canvas.path('M0 0 50, 50')\n      expect(path.y(100)).toBe(path)\n      expect(path.y()).toBe(100)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Pattern.js",
    "content": "/* globals describe, expect, it, spyOn, jasmine, container */\n\nimport { Pattern, SVG, Container } from '../../../src/main.js'\n\nconst { any, objectContaining, createSpy } = jasmine\n\ndescribe('Pattern.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Pattern', () => {\n      const pattern = new Pattern()\n      expect(pattern).toEqual(any(Pattern))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Pattern({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('attr()', () => {\n    it('relays to parents attr method for any call except transformation', () => {\n      const pattern = new Pattern()\n      const spy = spyOn(Container.prototype, 'attr')\n      pattern.attr(1, 2, 3)\n      pattern.attr('transform', 2, 3)\n\n      expect(spy).toHaveBeenCalledWith(1, 2, 3)\n      expect(spy).toHaveBeenCalledWith('patternTransform', 2, 3)\n    })\n  })\n\n  describe('bbox()', () => {\n    it('returns an empty box', () => {\n      expect(new Pattern().bbox().isNulled()).toBe(true)\n    })\n  })\n\n  describe('targets()', () => {\n    it('gets all targets of this pattern', () => {\n      const canvas = SVG().addTo(container)\n      const pattern = canvas.pattern()\n      const rect = canvas.rect(100, 100).fill(pattern)\n      expect(pattern.targets()).toEqual([rect])\n    })\n  })\n\n  describe('toString()', () => {\n    it('calls url() and returns the result', () => {\n      const pattern = new Pattern()\n      expect(pattern.toString()).toBe(pattern.url())\n    })\n  })\n\n  describe('update()', () => {\n    it('clears the element', () => {\n      const pattern = new Pattern()\n      pattern.rect(100, 100)\n      expect(pattern.update().children()).toEqual([])\n    })\n\n    it('executes a function in the context of the pattern', () => {\n      const pattern = new Pattern()\n      const spy = createSpy('pattern')\n      pattern.update(spy)\n      expect(spy.calls.all()).toEqual([\n        objectContaining({ object: pattern, args: [pattern] })\n      ])\n    })\n  })\n\n  describe('url()', () => {\n    it('returns url(#id)', () => {\n      const pattern = new Pattern().id('foo')\n      expect(pattern.url()).toBe('url(#foo)')\n    })\n  })\n\n  describe('Container', () => {\n    it('relays the call to defs', () => {\n      const canvas = new SVG()\n      const defs = canvas.defs()\n      const spy = spyOn(defs, 'pattern').and.callThrough()\n      const spy2 = createSpy('pattern')\n\n      canvas.pattern(100, 100, spy2)\n      expect(spy).toHaveBeenCalledWith(100, 100, spy2)\n      expect(spy2).toHaveBeenCalled()\n    })\n  })\n\n  describe('Defs', () => {\n    it('creates a pattern in the defs and sets its size and position', () => {\n      const canvas = new SVG()\n      const defs = canvas.defs()\n      const spy = createSpy('pattern')\n      const pattern = defs.pattern(100, 100, spy)\n      expect(pattern).toEqual(any(Pattern))\n      expect(defs.children()).toEqual([pattern])\n      expect(spy).toHaveBeenCalled()\n      expect(pattern.attr(['x', 'y', 'width', 'height'])).toEqual({\n        x: 0,\n        y: 0,\n        width: 100,\n        height: 100\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Polygon.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport { Polygon, G } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Polygon.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Polygon', () => {\n      expect(new Polygon()).toEqual(any(Polygon))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Polygon({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('Container', () => {\n    describe('polygon()', () => {\n      it('creates a polygon with given points', () => {\n        const group = new G()\n        const polygon = group.polygon([1, 2, 3, 4])\n        expect(polygon.array()).toEqual([\n          [1, 2],\n          [3, 4]\n        ])\n        expect(polygon).toEqual(any(Polygon))\n      })\n    })\n\n    it('creates a polygon with one point by default', () => {\n      const group = new G()\n      const polygon = group.polygon()\n      expect(polygon.array()).toEqual([[0, 0]])\n      expect(polygon).toEqual(any(Polygon))\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Polyline.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport { Polyline, G } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Polyline.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Polyline', () => {\n      expect(new Polyline()).toEqual(any(Polyline))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Polyline({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('Container', () => {\n    describe('polyline()', () => {\n      it('creates a polyline with given points', () => {\n        const group = new G()\n        const polyline = group.polyline([1, 2, 3, 4])\n        expect(polyline.array()).toEqual([\n          [1, 2],\n          [3, 4]\n        ])\n        expect(polyline).toEqual(any(Polyline))\n      })\n\n      it('creates a polyline with one point by default', () => {\n        const group = new G()\n        const polyline = group.polyline()\n        expect(polyline.array()).toEqual([[0, 0]])\n        expect(polyline).toEqual(any(Polyline))\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Rect.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport { Rect, G } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Rect.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Rect', () => {\n      expect(new Rect()).toEqual(any(Rect))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Rect({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('Container', () => {\n    describe('rect()', () => {\n      it('creates a rect with given size', () => {\n        const group = new G()\n        const rect = group.rect(100, 100)\n        expect(rect.attr(['width', 'height'])).toEqual({\n          width: 100,\n          height: 100\n        })\n        expect(rect).toEqual(any(Rect))\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Shape.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport { Shape, create } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Rect.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Shape', () => {\n      expect(new Shape(create('rect'))).toEqual(any(Shape))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Shape(create('rect'), { id: 'foo' }).id()).toBe('foo')\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Stop.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport { Stop, Gradient } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Stop.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Stop', () => {\n      expect(new Stop()).toEqual(any(Stop))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Stop({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('update()', () => {\n    it('sets offset, color and opacity with 3 arguments given', () => {\n      const stop = new Stop()\n      stop.update(0.1, '#ffffff', 0.5)\n      expect(stop.attr('offset')).toBe(0.1)\n      expect(stop.attr('stop-color')).toBe('#ffffff')\n      expect(stop.attr('stop-opacity')).toBe(0.5)\n    })\n\n    it('sets offset, color and opacity with object given', () => {\n      const stop = new Stop()\n      stop.update({ offset: 0.1, color: '#ffffff', opacity: 0.5 })\n      expect(stop.attr('offset')).toBe(0.1)\n      expect(stop.attr('stop-color')).toBe('#ffffff')\n      expect(stop.attr('stop-opacity')).toBe(0.5)\n    })\n\n    it('sets efault values if not all supplied', () => {\n      let stop = new Stop()\n      stop.update({ offset: 0.1 })\n      expect(stop.attr('offset')).toBe(0.1)\n      expect(stop.attr('stop-color')).toBe('#000000')\n      expect(stop.attr('stop-opacity')).toBe(1)\n\n      stop = new Stop()\n      stop.update({ color: '#ffffff' })\n      expect(stop.attr('offset')).toBe(0)\n      expect(stop.attr('stop-color')).toBe('#ffffff')\n      expect(stop.attr('stop-opacity')).toBe(1)\n\n      stop = new Stop()\n      stop.update({ opacity: 0.5 })\n      expect(stop.attr('offset')).toBe(0)\n      expect(stop.attr('stop-color')).toBe('#000000')\n      expect(stop.attr('stop-opacity')).toBe(0.5)\n    })\n  })\n\n  describe('Gradient', () => {\n    describe('stop()', () => {\n      it('creates a stop in the gradient with 3 arguments', () => {\n        const gradient = new Gradient('linear')\n        const stop = gradient.stop(0.1, '#ffffff', 0.5)\n        expect(stop).toEqual(any(Stop))\n        expect(stop.attr('offset')).toBe(0.1)\n        expect(stop.attr('stop-color')).toBe('#ffffff')\n        expect(stop.attr('stop-opacity')).toBe(0.5)\n      })\n\n      it('creates stop in the gradient with object given', () => {\n        const gradient = new Gradient('linear')\n        const stop = gradient.stop({\n          offset: 0.1,\n          color: '#ffffff',\n          opacity: 0.5\n        })\n        expect(stop.attr('offset')).toBe(0.1)\n        expect(stop.attr('stop-color')).toBe('#ffffff')\n        expect(stop.attr('stop-opacity')).toBe(0.5)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Style.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport { Style, G } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Style.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Style', () => {\n      expect(new Style()).toEqual(any(Style))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Style({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('addText()', () => {\n    it('appends a string to the current textContent and returns itself', () => {\n      const style = new Style()\n      expect(style.addText('foo').node.textContent).toBe('foo')\n      expect(style.addText('bar').node.textContent).toBe('foobar')\n      expect(style.addText('foobar')).toBe(style)\n    })\n\n    it('appends an empty string if nothing passed', () => {\n      const style = new Style()\n      expect(style.addText().node.textContent).toBe('')\n    })\n  })\n\n  describe('font()', () => {\n    it('adds a font-face rule to load a custom font and returns itself', () => {\n      const style = new Style()\n      expect(style.font('fontName', 'url')).toBe(style)\n      expect(style.node.textContent).toBe(\n        '@font-face{font-family:fontName;src:url;}'\n      )\n    })\n\n    it('adds extra parameters if wanted', () => {\n      const style = new Style()\n      style.font('fontName', 'url', { foo: 'bar' })\n      expect(style.node.textContent).toBe(\n        '@font-face{font-family:fontName;src:url;foo:bar;}'\n      )\n    })\n  })\n\n  describe('rule()', () => {\n    it('adds a css rule', () => {\n      const style = new Style()\n      expect(style.rule('#id', { fontSize: 15 })).toBe(style)\n      expect(style.node.textContent).toBe('#id{font-size:15;}')\n    })\n\n    it('adds only selector when no obj was given', () => {\n      const style = new Style()\n      style.rule('#id')\n      expect(style.node.textContent).toBe('#id')\n    })\n\n    it('adds nothing if no selector was given', () => {\n      const style = new Style()\n      style.rule()\n      expect(style.node.textContent).toBe('')\n    })\n  })\n\n  describe('Container', () => {\n    describe('style()', () => {\n      it('creates a style element in the container and adds a rule', () => {\n        const g = new G()\n        const style = g.style('#id', { fontSize: 15 })\n        expect(style).toEqual(any(Style))\n        expect(style.node.textContent).toBe('#id{font-size:15;}')\n      })\n    })\n\n    describe('fontface()', () => {\n      it('creates a style element in the container and adds a font-face rule', () => {\n        const g = new G()\n        const style = g.fontface('fontName', 'url', { foo: 'bar' })\n        expect(style).toEqual(any(Style))\n        expect(style.node.textContent).toBe(\n          '@font-face{font-family:fontName;src:url;foo:bar;}'\n        )\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Svg.js",
    "content": "/* globals describe, expect, it, jasmine, container */\n\nimport { Svg, SVG, Defs } from '../../../src/main.js'\nimport { svg as ns, xlink } from '../../../src/modules/core/namespaces.js'\nimport { getWindow } from '../../../src/utils/window.js'\n\nconst { any } = jasmine\n\ndescribe('Svg.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Svg', () => {\n      expect(new Svg()).toEqual(any(Svg))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Svg({ id: 'foo' }).id()).toBe('foo')\n    })\n\n    it('creates namespaces on creation', () => {\n      const svg = new Svg()\n\n      expect(svg.attr('xmlns')).toBe(ns)\n      expect(svg.attr('version')).toBe(1.1)\n      expect(svg.attr('xmlns:xlink')).toBe(xlink)\n    })\n  })\n\n  describe('defs()', () => {\n    it('returns the defs if its the root svg', () => {\n      const svg = new Svg()\n      const defs = new Defs().addTo(svg)\n      expect(svg.defs()).toBe(defs)\n    })\n\n    it('returns the defs if its not the root svg', () => {\n      const svg = new Svg()\n      const defs = new Defs().addTo(svg)\n      const nested = new Svg().addTo(svg)\n      expect(nested.defs()).toBe(defs)\n    })\n\n    it('creates the defs if not found', () => {\n      const svg = new SVG()\n\n      expect(svg.findOne('defs')).toBe(null)\n\n      const defs = svg.defs()\n\n      expect(svg.findOne('defs')).toBe(defs)\n    })\n  })\n\n  describe('namespace()', () => {\n    it('returns itself', () => {\n      const svg = SVG('<svg />')\n      expect(svg.namespace()).toBe(svg)\n    })\n\n    it('creates the namespace attributes on the svg', () => {\n      const svg = SVG('<svg />')\n\n      expect(svg.attr('xmlns')).toBe(undefined)\n\n      svg.namespace()\n\n      expect(svg.attr('xmlns')).toBe(ns)\n      expect(svg.attr('version')).toBe(1.1)\n      expect(svg.attr('xmlns:xlink')).toBe(xlink)\n    })\n  })\n\n  describe('isRoot()', () => {\n    it('returns true if svg is the root svg', () => {\n      const canvas = SVG().addTo(container)\n      expect(canvas.isRoot()).toBe(true)\n    })\n\n    it('returns true if its detached from the dom', () => {\n      const svg = new Svg()\n      expect(svg.isRoot()).toBe(true)\n    })\n\n    it('returns true if its the root child of the document', () => {\n      // cannot be tested here\n    })\n\n    it('returns false if its the child of a document-fragment', () => {\n      const fragment = getWindow().document.createDocumentFragment()\n      const svg = new Svg().addTo(fragment)\n      expect(svg.isRoot()).toBe(false)\n    })\n\n    it('returns false if its a child of another svg element', () => {\n      const svg = new Svg()\n      const nested = new Svg().addTo(svg)\n      expect(nested.isRoot()).toBe(false)\n    })\n  })\n\n  describe('removeNamespace()', () => {\n    it('returns itself', () => {\n      const svg = new Svg()\n      expect(svg.removeNamespace()).toBe(svg)\n    })\n\n    it('removes the namespace attributes from the svg element', () => {\n      const svg = new Svg()\n\n      expect(svg.attr('xmlns')).toBe(ns)\n\n      svg.removeNamespace()\n\n      expect(svg.attr('xmlns')).toBe(undefined)\n      expect(svg.attr('version')).toBe(undefined)\n      expect(svg.attr('xmlns:xlink')).toBe(undefined)\n      expect(svg.attr('xmlns:svgjs')).toBe(undefined)\n    })\n  })\n\n  describe('root()', () => {\n    it('returns itself if its the root svg', () => {\n      const svg = new Svg()\n      expect(svg.root()).toBe(svg)\n    })\n\n    it('returns the actual root if its not the root svg', () => {\n      const svg = new Svg()\n      const nested = new Svg().addTo(svg)\n      expect(nested.root()).toBe(svg)\n    })\n  })\n\n  describe('Container', () => {\n    describe('nested()', () => {\n      it('creates an svg element in the container', () => {\n        const svg = new Svg()\n        const nested = svg.nested()\n        expect(nested).toEqual(any(Svg))\n        expect(nested.parent()).toBe(svg)\n      })\n\n      it('has no namespaces set', () => {\n        const svg = new Svg()\n        const nested = svg.nested()\n\n        expect(nested.attr('xmlns')).toBe(undefined)\n        expect(nested.attr('version')).toBe(undefined)\n        expect(nested.attr('xmlns:xlink')).toBe(undefined)\n        expect(nested.attr('xmlns:svgjs')).toBe(undefined)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Symbol.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport { Symbol, G } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Symbol.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Symbol', () => {\n      expect(new Symbol()).toEqual(any(Symbol))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Symbol({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('Container', () => {\n    describe('symbol()', () => {\n      it('creates a symbol in the container', () => {\n        const g = new G()\n        const symbol = g.symbol()\n        expect(symbol).toEqual(any(Symbol))\n        expect(g.children()).toEqual([symbol])\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Text.js",
    "content": "/* globals describe, expect, it, spyOn jasmine, container */\n\nimport {\n  Text,\n  Number as SVGNumber,\n  SVG,\n  G,\n  Path,\n  TextPath,\n  Svg\n} from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Text.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Text', () => {\n      expect(new Text()).toEqual(any(Text))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Text({ id: 'foo' }).id()).toBe('foo')\n    })\n\n    it('recovers leading data from dom', () => {\n      const svg = new Svg().namespace()\n      svg.text('').leading(3)\n      const newSvg = SVG(svg.svg())\n      expect(newSvg.findOne('text').leading().valueOf()).toBe(3)\n    })\n  })\n\n  describe('text()', () => {\n    it('sets the text content of the tspan and returns itself', () => {\n      const text = new Text()\n      expect(text.text('Hello World')).toBe(text)\n      expect(text.node.textContent).toBe('Hello World')\n    })\n\n    it('creates tspans for every line', () => {\n      const text = new Text().text('Hello World\\nHow is it\\ngoing')\n      expect(text.children().length).toBe(3)\n      expect(text.get(0).node.textContent).toBe('Hello World')\n      expect(text.get(1).node.textContent).toBe('How is it')\n      expect(text.get(2).node.textContent).toBe('going')\n    })\n\n    it('increases dy after empty line', () => {\n      const canvas = SVG().addTo(container)\n      const text = canvas.text('Hello World\\n\\nHow is it\\ngoing')\n      expect(text.children().length).toBe(4)\n      expect(text.get(0).node.textContent).toBe('Hello World')\n      expect(text.get(1).node.textContent).toBe('')\n      expect(text.get(2).node.textContent).toBe('How is it')\n      expect(text.get(3).node.textContent).toBe('going')\n      expect(text.get(2).dy()).toBe(text.get(3).dy() * 2)\n    })\n\n    it('returns the correct text with newlines', () => {\n      const text = new Text().text('Hello World\\nHow is it\\ngoing')\n      expect(text.text()).toBe('Hello World\\nHow is it\\ngoing')\n    })\n\n    it('returns the correct text with newlines and skips textPaths and descriptive elements', () => {\n      const path = new Path()\n      const text = new Text()\n      const textPath = text.text('Hello World\\nHow is it\\ngoing').path(path)\n      textPath.children().addTo(text)\n      text.add(new TextPath(), 3)\n      text.add(SVG('<title>MyText</title>'))\n\n      expect(text.text()).toBe('Hello World\\nHow is it\\ngoing')\n    })\n\n    it('executes passed block', () => {\n      const text = new Text()\n      text.text(function (t) {\n        t.tspan('Hello World').newLine()\n        t.tspan('How is it').newLine()\n        t.tspan('going').newLine()\n        expect(this).toBe(text)\n        expect(t).toBe(text)\n      })\n      expect(text.text()).toBe('Hello World\\nHow is it\\ngoing')\n    })\n\n    it('triggers rebuild', () => {\n      const text = new Text()\n      const spy = spyOn(text, 'rebuild')\n      text.text('foo')\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('leading()', () => {\n    it('returns the leading value of the text without an argument', () => {\n      const text = new Text()\n      expect(text.leading() instanceof SVGNumber)\n      expect(text.leading().valueOf()).toBe(1.3)\n    })\n\n    it('sets the leading value of the text with the first argument', () => {\n      const text = new Text()\n      expect(text.leading(1.5).dom.leading.valueOf()).toBe(1.5)\n    })\n  })\n\n  describe('rebuild()', () => {\n    it('disables the rebuild if called with false', () => {\n      const text = new Text()\n      expect(text.rebuild(false)._rebuild).toBeFalse()\n    })\n\n    it('enables the rebuild if called with true', () => {\n      const text = new Text()\n      expect(text.rebuild(true)._rebuild).toBeTrue()\n    })\n\n    it('rebuilds the text without an argument given', () => {\n      const canvas = SVG().addTo(container)\n      const text = new Text().addTo(canvas)\n      text.text((t) => {\n        t.tspan('Hello World').newLine()\n        t.tspan('How is it').newLine()\n        t.tspan('going').newLine()\n        t.add('<title>My Text</title>')\n      })\n\n      const dy = text.get(1).dy()\n      text.leading(1.7)\n      expect(dy).not.toBe(text.get(1).dy())\n    })\n  })\n\n  describe('setData()', () => {\n    it('read all data from the svgjs:data attribute and assign it to el.dom', () => {\n      const text = new Text()\n      text.attr('svgjs:data', '{\"foo\":\"bar\",\"leading\":\"3px\"}')\n      text.setData(JSON.parse(text.attr('svgjs:data')))\n\n      expect(text.dom.foo).toBe('bar')\n      expect(text.dom.leading instanceof SVGNumber).toBeTruthy()\n      expect(text.dom.leading.value).toBe(3)\n      expect(text.dom.leading.unit).toBe('px')\n    })\n\n    it('uses a leading of 1.3 when no leading is set or 0', () => {\n      const text = new Text()\n      text.setData({ leading: 0 })\n\n      expect(text.dom.leading.value).toBe(1.3)\n    })\n  })\n\n  describe('Container', () => {\n    describe('text()', () => {\n      it('creates a text element with lines', () => {\n        const group = new G()\n        const text = group.text('Hello World\\nHow is it\\ngoing')\n        expect(text).toEqual(any(Text))\n        expect(text.text()).toBe('Hello World\\nHow is it\\ngoing')\n      })\n\n      it('defaults to empty string', () => {\n        const group = new G()\n        const text = group.text()\n        expect(text).toEqual(any(Text))\n        expect(text.text()).toBe('')\n      })\n    })\n\n    describe('plain()', () => {\n      it('creates plain text', () => {\n        const group = new G()\n        const text = group.plain('A piece')\n        expect(text).toEqual(any(Text))\n        expect(text.node.childNodes[0].data).toBe('A piece')\n      })\n\n      it('defaults to empty string', () => {\n        const group = new G()\n        const text = group.plain()\n        expect(text).toEqual(any(Text))\n        expect(text.node.childNodes[0].data).toBe('')\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/TextPath.js",
    "content": "/* globals describe, expect, it, beforeEach, jasmine, container */\n\nimport { Text, SVG, TextPath, Path } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('TextPath.js', () => {\n  var canvas, text, path\n  var txt = 'We go up, then we go down, then up again'\n  var data =\n    'M 100 200 C 200 100 300  0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100'\n\n  beforeEach(() => {\n    canvas = new SVG().addTo(container)\n    text = canvas.text(txt)\n    path = canvas.path(data)\n  })\n\n  describe('()', () => {\n    it('creates a new object of type TextPath', () => {\n      expect(new TextPath()).toEqual(any(TextPath))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new TextPath({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('track()', () => {\n    it('returns the referenced path instance', () => {\n      const textPath = text.path(path)\n      expect(textPath.track()).toBe(path)\n    })\n  })\n\n  describe('array()', () => {\n    it('returns the path array of the underlying path', () => {\n      expect(text.path(path).array()).toEqual(path.array())\n    })\n\n    it('returns null if there is no underlying path', () => {\n      const textPath = new TextPath()\n      expect(textPath.array()).toBe(null)\n    })\n  })\n\n  describe('plot()', () => {\n    it('changes the array of the underlying path', () => {\n      expect(text.path().plot(path.array()).array()).toEqual(path.array())\n    })\n\n    it('return the path array of the underlying path when no arguments is passed', () => {\n      const textPath = text.path(path)\n      expect(textPath.plot()).toBe(textPath.array())\n      expect(textPath.plot()).not.toBe(null)\n    })\n\n    it('does nothing if no path is attached as track', () => {\n      const textPath = Object.freeze(new TextPath())\n      expect(textPath.plot('M0 0')).toBe(textPath)\n    })\n  })\n\n  describe('Container', () => {\n    describe('textPath()', () => {\n      it('creates a textPath from string text and string path', () => {\n        const textPath = canvas.textPath(txt, data)\n        expect(textPath).toEqual(any(TextPath))\n        expect(textPath.parent()).toEqual(any(Text))\n        expect(textPath.track()).toEqual(any(Path))\n        expect(textPath.track().parent()).toBe(canvas.defs())\n      })\n\n      it('creates a textPath from Text and Path', () => {\n        const textPath = canvas.textPath(text, path)\n        expect(textPath.parent()).toEqual(text)\n        expect(textPath.track()).toEqual(path)\n      })\n\n      it('passes the text into textPath and not text', () => {\n        const tspan = text.first()\n        const textPath = canvas.textPath(text, path)\n        expect(textPath.first()).toBe(tspan)\n        expect(text.first()).toBe(textPath)\n      })\n    })\n  })\n\n  describe('Text', () => {\n    describe('path()', () => {\n      it('returns an instance of TextPath', () => {\n        expect(text.path(data)).toEqual(any(TextPath))\n      })\n\n      it('creates a textPath node in the text element', () => {\n        text.path(data)\n        expect(text.node.querySelector('textPath')).not.toBe(null)\n      })\n\n      it('references the passed path', () => {\n        const textPath = text.path(path)\n        expect(textPath.reference('href')).toBe(path)\n      })\n\n      it('imports all nodes from the text by default', () => {\n        const children = text.children()\n        const textPath = text.path(path)\n        expect(textPath.children()).toEqual(children)\n      })\n\n      it('does not import all nodes from the text when second parameter false', () => {\n        const textPath = text.path(path, false)\n        expect(textPath.children()).toEqual([])\n      })\n    })\n\n    describe('textPath()', () => {\n      it('returns the textPath element of this text', () => {\n        const textPath = text.path(path)\n        expect(text.textPath()).toBe(textPath)\n      })\n    })\n  })\n\n  describe('Path', () => {\n    describe('text()', () => {\n      it('returns an instance of TextPath', () => {\n        expect(path.text(txt)).toEqual(any(TextPath))\n      })\n\n      it('creates a text with textPath node and inserts it after the path', () => {\n        var textPath = path.text(txt)\n        expect(textPath.parent()).toEqual(any(Text))\n        expect(SVG(path.node.nextSibling)).toBe(textPath.parent())\n      })\n\n      it('transplants the node from text to textPath', () => {\n        const nodesInText = [].slice.call(text.node.childNodes)\n        var textPath = path.text(text)\n        const nodesInTextPath = [].slice.call(textPath.node.childNodes)\n        expect(nodesInText).toEqual(nodesInTextPath)\n      })\n    })\n\n    describe('targets', () => {\n      it('returns all elements referencing this path with href', () => {\n        const textPath = text.path(path)\n        expect(path.targets()).toEqual([textPath])\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Tspan.js",
    "content": "/* globals describe, expect, it, jasmine, container */\n\nimport { Tspan, Text, Number as SVGNumber, SVG } from '../../../src/main.js'\nimport { getWindow } from '../../../src/utils/window.js'\n\nconst { any } = jasmine\n\ndescribe('Tspan.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Tspan', () => {\n      expect(new Tspan()).toEqual(any(Tspan))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Tspan({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('text()', () => {\n    it('sets the text content of the tspan and returns itself', () => {\n      const tspan = new Tspan()\n      expect(tspan.text('Hello World')).toBe(tspan)\n      expect(tspan.node.textContent).toBe('Hello World')\n    })\n\n    it('returns the textContent of the tspan', () => {\n      const tspan = new Tspan().text('Hello World')\n      expect(tspan.text()).toBe('Hello World')\n    })\n\n    it('adds a newline when this tspan is a newline', () => {\n      const tspan = new Tspan().text('Hello World').newLine()\n      expect(tspan.text()).toBe('Hello World\\n')\n    })\n\n    it('executes a function in the context of the tspan', () => {\n      const tspan = new Tspan()\n      tspan.text(function (t) {\n        expect(this).toBe(tspan)\n        expect(t).toBe(tspan)\n      })\n    })\n  })\n\n  describe('dx()', () => {\n    it('sets the dx attribute and returns itself', () => {\n      const tspan = new Tspan()\n      expect(tspan.dx(20)).toBe(tspan)\n      expect(tspan.attr('dx')).toBe(20)\n    })\n\n    it('returns the dx attribute', () => {\n      const tspan = new Tspan().dx(20)\n      expect(tspan.dx()).toBe(20)\n    })\n  })\n\n  describe('dy()', () => {\n    it('sets the dy attribute and returns itself', () => {\n      const tspan = new Tspan()\n      expect(tspan.dy(20)).toBe(tspan)\n      expect(tspan.attr('dy')).toBe(20)\n    })\n\n    it('returns the dy attribute', () => {\n      const tspan = new Tspan().dy(20)\n      expect(tspan.dy()).toBe(20)\n    })\n  })\n\n  describe('newLine()', () => {\n    it('works without text parent', () => {\n      // should not fail\n      const tspan = new Tspan().newLine()\n      expect(tspan.dom.newLined).toBeTrue()\n    })\n\n    it('returns itself', () => {\n      const tspan = new Tspan()\n      expect(tspan.newLine()).toBe(tspan)\n    })\n\n    it('marks the tspan as a newline', () => {\n      const tspan = new Tspan().wrap(new Text()).newLine()\n      expect(tspan.dom.newLined).toBeTrue()\n    })\n\n    it('sets dy to zero of first line', () => {\n      const text = new Text()\n      const first = text.tspan('First Line').newLine()\n      expect(first.dy()).toBe(0)\n    })\n\n    it('sets dy corresponding to line and leading', () => {\n      const canvas = SVG().addTo(container)\n      const text = new Text().leading(2).build(true).addTo(canvas)\n      text.tspan('First Line').newLine()\n      text.tspan('Second Line').newLine()\n      const third = text.tspan('Third Line').newLine()\n\n      const fontSize = getWindow()\n        .getComputedStyle(third.node)\n        .getPropertyValue('font-size')\n      const dy = 2 * new SVGNumber(fontSize)\n      expect(third.dy()).toBe(dy)\n    })\n  })\n\n  describe('Tspan', () => {\n    describe('tspan()', () => {\n      it('creates a tspan in a text', () => {\n        const text = new Text()\n        const tspan = text.tspan()\n        expect(tspan).toEqual(any(Tspan))\n        expect(tspan.parent()).toBe(text)\n      })\n\n      it('creates a tspan in a tspan', () => {\n        const tspan1 = new Tspan()\n        const tspan2 = tspan1.tspan()\n        expect(tspan2).toEqual(any(Tspan))\n        expect(tspan2.parent()).toBe(tspan1)\n      })\n    })\n  })\n\n  describe('Text', () => {\n    describe('newLine()', () => {\n      it('creates a tspan and calls newLine() on it', () => {\n        const text = new Text()\n        const tspan = text.newLine()\n        expect(tspan).toEqual(any(Tspan))\n        expect(tspan.parent()).toBe(text)\n        expect(tspan.dom.newLined).toBeTrue()\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/elements/Use.js",
    "content": "/* globals describe, expect, it, jasmine, container */\n\nimport { Use, Rect, SVG } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Use.js', () => {\n  describe('()', () => {\n    it('creates a new object of type Use', () => {\n      expect(new Use()).toEqual(any(Use))\n    })\n\n    it('sets passed attributes on the element', () => {\n      expect(new Use({ id: 'foo' }).id()).toBe('foo')\n    })\n  })\n\n  describe('use()', () => {\n    it('links an element', () => {\n      const rect = new Rect()\n      const use = new Use().use(rect)\n      expect(use.attr('href')).toBe('#' + rect.id())\n    })\n\n    it('links an element from a different file', () => {\n      const use = new Use().use('id', 'file')\n      expect(use.attr('href')).toBe('file#id')\n    })\n  })\n\n  describe('Container', () => {\n    describe('use()', () => {\n      it('creates a use element linked to the given element', () => {\n        const canvas = new SVG().addTo(container)\n        const rect = canvas.rect(100, 100)\n        const use = canvas.use(rect)\n        expect(use.attr('href')).toBe('#' + rect.id())\n        expect(use.reference('href')).toBe(rect)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/core/attr.js",
    "content": "/* globals describe, expect, it, beforeEach, spyOn, jasmine */\n\nimport { Element, create, Text, Rect } from '../../../../src/main.js'\nimport { registerAttrHook } from '../../../../src/modules/core/attr.js'\n\nconst { objectContaining } = jasmine\n\ndescribe('attr.js', () => {\n  describe('attr()', () => {\n    let element\n\n    beforeEach(() => {\n      element = new Element(create('rect'))\n    })\n\n    it('returns itself as setter', () => {\n      expect(element.attr('fill', '#ff0066')).toBe(element)\n    })\n\n    it('sets one attribute when two arguments are given', () => {\n      element.attr('fill', '#ff0066')\n      expect(element.node.getAttribute('fill')).toBe('#ff0066')\n    })\n\n    it('sets various attributes when an object is given', () => {\n      element.attr({ fill: '#00ff66', stroke: '#ff2233', 'stroke-width': 10 })\n      expect(element.node.getAttribute('fill')).toBe('#00ff66')\n      expect(element.node.getAttribute('stroke')).toBe('#ff2233')\n      expect(element.node.getAttribute('stroke-width')).toBe('10')\n    })\n\n    it('gets the value of the string value given as first argument', () => {\n      element.attr('fill', '#ff0066')\n      expect(element.attr('fill')).toEqual('#ff0066')\n    })\n\n    it('gets an object with all attributes without any arguments', () => {\n      element.attr({ fill: '#00ff66', stroke: '#ff2233' })\n      var attr = element.attr()\n      expect(attr.fill).toBe('#00ff66')\n      expect(attr.stroke).toBe('#ff2233')\n    })\n\n    it('removes an attribute if the second argument is explicitly set to null', () => {\n      element.attr('stroke-width', 10)\n      expect(element.node.getAttribute('stroke-width')).toBe('10')\n      element.attr('stroke-width', null)\n      expect(element.node.getAttribute('stroke-width')).toBe(null)\n    })\n\n    it('correctly parses numeric values as a getter', () => {\n      element.attr('stroke-width', 11)\n      expect(element.node.getAttribute('stroke-width')).toBe('11')\n      expect(element.attr('stroke-width')).toBe(11)\n    })\n\n    it('correctly parses negative numeric values as a getter', () => {\n      element.attr('x', -120)\n      expect(element.node.getAttribute('x')).toBe('-120')\n      expect(element.attr('x')).toBe(-120)\n    })\n\n    it('falls back on default values if attribute is not present', () => {\n      expect(element.attr('stroke-linejoin')).toBe('miter')\n    })\n\n    it('gets the \"style\" attribute as a string', () => {\n      element.css('cursor', 'pointer')\n      expect(element.attr('style')).toBe('cursor: pointer;')\n    })\n\n    it('sets the style attribute correctly', () => {\n      element.attr('style', 'cursor:move;')\n      expect(element.node.style.cursor).toBe('move')\n    })\n\n    it('acts as getter for an array of values passed', () => {\n      element.attr({\n        x: 1,\n        y: 2,\n        width: 20,\n        'fill-opacity': 0.5\n      })\n\n      const ret = element.attr(['x', 'fill-opacity'])\n\n      expect(ret).toEqual({ x: 1, 'fill-opacity': 0.5 })\n    })\n\n    it('correctly creates SVG.Array if array given', () => {\n      element.attr('something', [2, 3, 4])\n      expect(element.attr('something')).toBe('2 3 4')\n    })\n\n    it('redirects to the leading() method when setting leading', () => {\n      const text = new Text().text('Hello World')\n      const spy = spyOn(text, 'leading')\n\n      text.attr('leading', 2)\n      expect(spy).toHaveBeenCalledWith(objectContaining({ value: 2 }))\n    })\n\n    it('ignores leading if no leading method is available', () => {\n      const frozen = Object.freeze(element)\n      expect(frozen.attr('leading', 2)).toBe(frozen)\n    })\n\n    it('only applies transforms color values if the attribute is designed to take a color as input', () => {\n      const rect = new Rect().attr('id', '#ff0')\n\n      expect(rect.attr('id')).toBe('#ff0')\n    })\n\n    it('executes registered hooks', () => {\n      registerAttrHook((attr, val, el) => {\n        if (el.node.id === 'somethingVeryRandom' && attr === 'name') {\n          throw new Error('This hook should only be executed in one test')\n        }\n        return val\n      })\n\n      element.id('somethingVeryRandom')\n\n      const throwingFn = () => {\n        element.attr('name', 'Bob')\n      }\n\n      expect(throwingFn).toThrowError(\n        'This hook should only be executed in one test'\n      )\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/core/circled.js",
    "content": "/* globals describe, expect, it, beforeEach, spyOn, jasmine, container */\n\nimport { Ellipse, SVG } from '../../../../src/main.js'\n\nconst { objectContaining } = jasmine\n\ndescribe('circled.js', () => {\n  let element\n\n  beforeEach(() => {\n    element = new Ellipse().size(50, 50)\n  })\n\n  describe('rx()', () => {\n    it('calls attribute with rx and returns itself', () => {\n      const spy = spyOn(element, 'attr').and.callThrough()\n      expect(element.rx(50)).toBe(element)\n      expect(spy).toHaveBeenCalledWith('rx', 50)\n    })\n  })\n\n  describe('ry()', () => {\n    it('calls attribute with ry and returns itself', () => {\n      const spy = spyOn(element, 'attr').and.callThrough()\n      expect(element.ry(50)).toBe(element)\n      expect(spy).toHaveBeenCalledWith('ry', 50)\n    })\n  })\n\n  describe('x()', () => {\n    it('sets x position and returns itself', () => {\n      element = SVG().addTo(container).ellipse(50, 50)\n      expect(element.x(50)).toBe(element)\n      expect(element.bbox().x).toBe(50)\n    })\n\n    it('gets the x position', () => {\n      element.x(50)\n      expect(element.x()).toBe(50)\n    })\n  })\n\n  describe('y()', () => {\n    it('sets y position and returns itself', () => {\n      element = SVG().addTo(container).ellipse(50, 50)\n      expect(element.y(50)).toBe(element)\n      expect(element.bbox().y).toBe(50)\n    })\n\n    it('gets the y position', () => {\n      element.y(50)\n      expect(element.y()).toBe(50)\n    })\n  })\n\n  describe('cx()', () => {\n    it('calls attribute with cx and returns itself', () => {\n      const spy = spyOn(element, 'attr').and.callThrough()\n      expect(element.cx(50)).toBe(element)\n      expect(spy).toHaveBeenCalledWith('cx', 50)\n    })\n  })\n\n  describe('cy()', () => {\n    it('calls attribute with cy and returns itself', () => {\n      const spy = spyOn(element, 'attr').and.callThrough()\n      expect(element.cy(50)).toBe(element)\n      expect(spy).toHaveBeenCalledWith('cy', 50)\n    })\n  })\n\n  describe('width()', () => {\n    it('sets rx by half the given width', () => {\n      const spy = spyOn(element, 'rx').and.callThrough()\n      expect(element.width(50)).toBe(element)\n      expect(spy).toHaveBeenCalledWith(objectContaining({ value: 25 }))\n    })\n\n    it('gets the width of the element', () => {\n      element.width(100)\n      expect(element.width()).toBe(100)\n    })\n  })\n\n  describe('height()', () => {\n    it('sets ry by half the given height', () => {\n      const spy = spyOn(element, 'ry').and.callThrough()\n      expect(element.height(50)).toBe(element)\n      expect(spy).toHaveBeenCalledWith(objectContaining({ value: 25 }))\n    })\n\n    it('gets the height of the element', () => {\n      element.height(100)\n      expect(element.height()).toBe(100)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/core/containerGeometry.js",
    "content": "/* globals describe, expect, it, jasmine, spyOn, container */\n\nimport { Box, create, Element, G, Rect, SVG } from '../../../../src/main.js'\n\nconst { any, objectContaining } = jasmine\n\ndescribe('containerGeometry.js', () => {\n  describe('dmove()', () => {\n    it('moves the bbox of the group by a certain amount (1)', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n\n      g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))\n      g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))\n\n      g.dmove(10, 10)\n\n      const box = g.bbox()\n      expect(box).toEqual(\n        objectContaining({\n          x: 20,\n          y: 30,\n          width: box.width,\n          height: box.height\n        })\n      )\n    })\n\n    it('moves the bbox of the group by a certain amount (2)', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n\n      g.rect(400, 200).move(123, 312).rotate(34).skew(12)\n      g.rect(100, 50).move(11, 43).translate(123, 32).skew(-12)\n      g.rect(400, 200).rotate(90)\n      g.group().rotate(23).group().skew(32).rect(100, 40).skew(11).rotate(12)\n\n      const oldBox = g.bbox()\n\n      g.dmove(10, 10)\n\n      const newBox = g.bbox()\n\n      expect(newBox.x).toBeCloseTo(oldBox.x + 10, 4)\n      expect(newBox.y).toBeCloseTo(oldBox.y + 10, 4)\n      expect(newBox.w).toBeCloseTo(oldBox.w, 4)\n      expect(newBox.h).toBeCloseTo(oldBox.h, 4)\n    })\n\n    it('moves nested svgs in a group correctly when calling dmove', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n      const s1 = g.nested().size(100, 100).move(100, 100)\n      s1.rect(100, 100).move(50, 50).rotate(10)\n\n      const oldBox = g.bbox()\n\n      g.dmove(10, 10)\n\n      const newBox = g.bbox()\n\n      expect(newBox.x).toBeCloseTo(oldBox.x + 10, 4)\n      expect(newBox.y).toBeCloseTo(oldBox.y + 10, 4)\n      expect(newBox.w).toBeCloseTo(oldBox.w, 4)\n      expect(newBox.h).toBeCloseTo(oldBox.h, 4)\n    })\n\n    it('moves nested svgs in a group correctly when calling dmove (2)', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n      const s1 = g.nested().move(100, 100)\n      s1.rect(100, 100).move(50, 50).rotate(10)\n\n      const oldBox = g.bbox()\n\n      g.dmove(10, 10)\n\n      const newBox = g.bbox()\n\n      expect(newBox.x).toBeCloseTo(oldBox.x + 10, 4)\n      expect(newBox.y).toBeCloseTo(oldBox.y + 10, 4)\n      expect(newBox.w).toBeCloseTo(oldBox.w, 4)\n      expect(newBox.h).toBeCloseTo(oldBox.h, 4)\n    })\n\n    it('moves nested svgs in a group correctly when calling dmove (3)', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n      const s1 = g.nested()\n      s1.rect(100, 100).move(50, 50).rotate(10)\n\n      const oldBox = g.bbox()\n\n      g.dmove(10, 10)\n\n      const newBox = g.bbox()\n\n      expect(newBox.x).toBeCloseTo(oldBox.x + 10, 4)\n      expect(newBox.y).toBeCloseTo(oldBox.y + 10, 4)\n      expect(newBox.w).toBeCloseTo(oldBox.w, 4)\n      expect(newBox.h).toBeCloseTo(oldBox.h, 4)\n    })\n\n    it('it does not fail when hitting elements without bbox', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n\n      g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))\n      g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))\n      g.add(new Element(create('title')))\n\n      const fn = () => g.dmove(10, 10)\n      expect(fn).not.toThrowError()\n\n      const box = g.bbox()\n      expect(box).toEqual(\n        objectContaining({\n          x: 20,\n          y: 30,\n          width: box.width,\n          height: box.height\n        })\n      )\n    })\n  })\n\n  describe('dx()', () => {\n    it('calls dmove with dy=0 and returns itself', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n      const spy = spyOn(g, 'dmove').and.callThrough()\n      expect(g.dx(10)).toBe(g)\n      expect(spy).toHaveBeenCalledWith(10, 0)\n    })\n  })\n\n  describe('dy()', () => {\n    it('calls dmove with dx=0 and returns itself', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n      const spy = spyOn(g, 'dmove').and.callThrough()\n      expect(g.dy(10)).toBe(g)\n      expect(spy).toHaveBeenCalledWith(0, 10)\n    })\n  })\n\n  describe('move()', () => {\n    it('calls dmove() with the correct difference', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n      g.rect(100, 200).move(111, 223)\n\n      spyOn(g, 'dmove')\n\n      g.move(100, 150)\n      expect(g.dmove).toHaveBeenCalledWith(-11, -73)\n    })\n\n    it('defaults to x=0 and y=0', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n      g.rect(100, 200).move(111, 223)\n\n      spyOn(g, 'dmove')\n\n      g.move()\n      expect(g.dmove).toHaveBeenCalledWith(-111, -223)\n    })\n  })\n\n  describe('x()', () => {\n    it('gets the x value of the bbox', () => {\n      const canvas = SVG().addTo(container)\n\n      const g = new G()\n      g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))\n      g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))\n\n      g.addTo(canvas)\n\n      expect(g.x()).toBe(g.bbox().x)\n      expect(g.x()).toBe(10)\n    })\n    it('calls move with the parameter as x', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n      g.rect(100, 200).move(111, 223)\n\n      spyOn(g, 'move')\n\n      g.x(100)\n      expect(g.move).toHaveBeenCalledWith(100, g.bbox().y, any(Box))\n    })\n  })\n\n  describe('y()', () => {\n    it('gets the y value of the bbox', () => {\n      const canvas = SVG().addTo(container)\n\n      const g = new G()\n      g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))\n      g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))\n\n      g.addTo(canvas)\n\n      expect(g.y()).toBe(g.bbox().y)\n      expect(g.y()).toBe(20)\n    })\n\n    it('calls move with the parameter as y', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n      g.rect(100, 200).move(111, 223)\n\n      spyOn(g, 'move')\n\n      g.y(100)\n      expect(g.move).toHaveBeenCalledWith(g.bbox().x, 100, any(Box))\n    })\n  })\n\n  describe('size()', () => {\n    it('changes the dimensions of the bbox (1)', () => {\n      const canvas = SVG().addTo(container)\n\n      const g = new G()\n      g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))\n      g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))\n\n      g.addTo(canvas)\n\n      const oldBox = g.bbox()\n\n      expect(g.size(100, 100)).toBe(g)\n\n      const newBox = g.bbox()\n\n      expect(newBox.x).toBeCloseTo(oldBox.x, 4)\n      expect(newBox.y).toBeCloseTo(oldBox.y, 4)\n      expect(newBox.w).toBeCloseTo(100, 4)\n      expect(newBox.h).toBeCloseTo(100, 4)\n\n      const rbox1 = g.children()[0].rbox()\n      const rbox2 = g.children()[1].rbox()\n\n      expect(rbox1.width).toBeCloseTo(90.9, 1)\n      expect(Math.floor(rbox2.width * 10) / 10).toBeCloseTo(63.6, 1) // Browsers have different opinion on this one (chrome: 63.6, ff: 63.7)\n\n      expect(rbox1.x).toBeCloseTo(10, 1)\n      expect(rbox2.x).toBeCloseTo(46.4, 1)\n      expect(rbox1.height).toBeCloseTo(85.7, 1)\n      expect(rbox2.height).toBeCloseTo(71.4, 1)\n      expect(rbox1.y).toBeCloseTo(20, 1)\n      expect(rbox2.y).toBeCloseTo(48.6, 1)\n    })\n\n    it('changes the dimensions of the bbox (2)', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n\n      g.rect(400, 200).move(123, 312).rotate(34).skew(12)\n      g.rect(100, 50).move(11, 43).translate(123, 32).skew(-12)\n      g.rect(400, 200).rotate(90)\n      g.group().rotate(23).group().skew(32).rect(100, 40).skew(11).rotate(12)\n\n      const oldBox = g.bbox()\n\n      g.size(100, 100)\n\n      const newBox = g.bbox()\n\n      expect(newBox.x).toBeCloseTo(oldBox.x, 4)\n      expect(newBox.y).toBeCloseTo(oldBox.y, 4)\n      expect(newBox.w).toBeCloseTo(100, 4)\n      expect(newBox.h).toBeCloseTo(100, 4)\n    })\n  })\n\n  describe('width()', () => {\n    it('gets the width value of the bbox', () => {\n      const canvas = SVG().addTo(container)\n\n      const g = new G()\n      g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))\n      g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))\n\n      g.addTo(canvas)\n\n      expect(g.width()).toBe(g.bbox().width)\n      expect(g.width()).toBe(110)\n    })\n    it('sets the width value of the bbox by moving all children', () => {\n      const canvas = SVG().addTo(container)\n\n      const g = new G()\n      g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))\n      g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))\n\n      g.addTo(canvas)\n\n      expect(g.width(100)).toBe(g)\n      expect(g.bbox().width).toBe(100)\n\n      const rbox1 = g.children()[0].rbox()\n      const rbox2 = g.children()[1].rbox()\n\n      expect(rbox1.width).toBeCloseTo(90.9, 1)\n      expect(Math.floor(rbox2.width * 10) / 10).toBeCloseTo(63.6, 1) // Browsers have different opinion on this one (chrome: 63.6, ff: 63.7)\n\n      expect(rbox1.x).toBeCloseTo(10, 3)\n      expect(rbox2.x).toBeCloseTo(46.4, 1)\n    })\n  })\n\n  describe('height()', () => {\n    it('gets the height value of the bbox', () => {\n      const canvas = SVG().addTo(container)\n\n      const g = new G()\n      g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))\n      g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))\n\n      g.addTo(canvas)\n\n      expect(g.height()).toBe(g.bbox().height)\n      expect(g.height()).toBe(140)\n    })\n    it('sets the height value of the bbox by moving all children', () => {\n      const canvas = SVG().addTo(container)\n\n      const g = new G()\n      g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 }))\n      g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 }))\n\n      g.addTo(canvas)\n\n      expect(g.height(100)).toBe(g)\n      expect(g.bbox().height).toBeCloseTo(100, 3)\n\n      const rbox1 = g.children()[0].rbox()\n      const rbox2 = g.children()[1].rbox()\n\n      expect(rbox1.height).toBeCloseTo(85.7, 1)\n      expect(rbox2.height).toBeCloseTo(71.4, 1)\n\n      expect(rbox1.y).toBeCloseTo(20, 3)\n      expect(rbox2.y).toBeCloseTo(48.6, 1)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/core/event.js",
    "content": "/* globals describe, expect, it, spyOn, jasmine */\nimport {\n  windowEvents,\n  getEvents,\n  getEventTarget,\n  clearEvents,\n  dispatch,\n  on,\n  off\n} from '../../../../src/modules/core/event.js'\nimport { getWindow } from '../../../../src/utils/window.js'\nimport { EventTarget, SVG } from '../../../../src/main.js'\n\nconst { any, createSpy } = jasmine\n\ndescribe('event.js', () => {\n  describe('getEvents()', () => {\n    it('returns the instance events for an EventTarget', () => {\n      const eventTarget = new EventTarget()\n      eventTarget.events = 'Test'\n      const events = getEvents(eventTarget)\n      expect(events).toBe('Test')\n    })\n\n    it('accesses windowEvents if instance is window', () => {\n      windowEvents.events = 'bla'\n      const events = getEvents(SVG(getWindow()))\n      expect(events).toBe(windowEvents.events)\n    })\n  })\n\n  describe('getEventTarget()', () => {\n    it('calls getEventTarget() on the instance', () => {\n      const eventTarget = new EventTarget()\n      const spy = spyOn(eventTarget, 'getEventTarget')\n      getEventTarget(eventTarget)\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe('clearEvents()', () => {\n    it('sets events to an empty object', () => {\n      const eventTarget = new EventTarget()\n      eventTarget.events = 'Test'\n      clearEvents(eventTarget)\n      expect(eventTarget.events).toEqual({})\n    })\n\n    it('does not do anything if no event object is found on the instance', () => {\n      const eventTarget = new EventTarget()\n      delete eventTarget.events\n      clearEvents(eventTarget)\n      expect(eventTarget.events).toBe(undefined)\n    })\n  })\n\n  describe('on()', () => {\n    it('binds an event to an EventTarget', () => {\n      const eventTarget = new EventTarget()\n      const spy = createSpy('spy')\n      on(eventTarget, 'event', spy)\n      dispatch(eventTarget, 'event')\n      expect(spy).toHaveBeenCalledWith(any(getWindow().CustomEvent))\n    })\n\n    it('binds to multiple events with space or comma separated string', () => {\n      const eventTarget = new EventTarget()\n      const spy = createSpy('spy')\n      on(eventTarget, 'event1 event2, event3', spy)\n      dispatch(eventTarget, 'event1')\n      dispatch(eventTarget, 'event2')\n      dispatch(eventTarget, 'event3')\n      expect(spy).toHaveBeenCalledTimes(3)\n    })\n\n    it('binds to multiple events passed as array', () => {\n      const eventTarget = new EventTarget()\n      const spy = createSpy('spy')\n      on(eventTarget, ['event1', 'event2', 'event3'], spy)\n      dispatch(eventTarget, 'event1')\n      dispatch(eventTarget, 'event2')\n      dispatch(eventTarget, 'event3')\n      expect(spy).toHaveBeenCalledTimes(3)\n    })\n\n    it('binds a namespaced event of form event.namespace', () => {\n      const eventTarget = new EventTarget()\n      const spy = createSpy('spy')\n      on(eventTarget, 'event.namespace', spy)\n      dispatch(eventTarget, 'event')\n      expect(spy).toHaveBeenCalledWith(any(getWindow().CustomEvent))\n    })\n  })\n\n  describe('off()', () => {\n    it('unbinds an event of an EventTarget', () => {\n      const eventTarget = new EventTarget()\n      const spy = createSpy('spy')\n      on(eventTarget, 'event', spy)\n      dispatch(eventTarget, 'event')\n      off(eventTarget, 'event', spy)\n      dispatch(eventTarget, 'event')\n      expect(spy).toHaveBeenCalledTimes(1)\n    })\n\n    it('unbinds multiple events with space or comma separated string', () => {\n      const eventTarget = new EventTarget()\n      const spy = createSpy('spy')\n      on(eventTarget, 'event1 event2, event3', spy)\n      dispatch(eventTarget, 'event1')\n      dispatch(eventTarget, 'event2')\n      dispatch(eventTarget, 'event3')\n      off(eventTarget, 'event1 event2, event3', spy)\n      dispatch(eventTarget, 'event1')\n      dispatch(eventTarget, 'event2')\n      dispatch(eventTarget, 'event3')\n      expect(spy).toHaveBeenCalledTimes(3)\n    })\n\n    it('unbinds multiple events with space or comma separated string', () => {\n      const eventTarget = new EventTarget()\n      const spy = createSpy('spy')\n      on(eventTarget, ['event1', 'event2', 'event3'], spy)\n      dispatch(eventTarget, 'event1')\n      dispatch(eventTarget, 'event2')\n      dispatch(eventTarget, 'event3')\n      off(eventTarget, ['event1', 'event2', 'event3'], spy)\n      dispatch(eventTarget, 'event1')\n      dispatch(eventTarget, 'event2')\n      dispatch(eventTarget, 'event3')\n      expect(spy).toHaveBeenCalledTimes(3)\n    })\n\n    it('unbinds a namespaced event', () => {\n      const eventTarget = new EventTarget()\n      const spy = createSpy('spy')\n      on(eventTarget, 'event.namespace', spy)\n      dispatch(eventTarget, 'event')\n      off(eventTarget, 'event.namespace', spy)\n      dispatch(eventTarget, 'event')\n      expect(spy).toHaveBeenCalledTimes(1)\n    })\n\n    it('unbinds all events including namespaced ones when only event is passed', () => {\n      const eventTarget = new EventTarget()\n      const spy = createSpy('spy')\n      on(eventTarget, ['event1.ns1', 'event2.ns2', 'event3'], spy)\n      dispatch(eventTarget, 'event1')\n      dispatch(eventTarget, 'event2')\n      dispatch(eventTarget, 'event3')\n      off(eventTarget, ['event1', 'event2', 'event3'])\n      dispatch(eventTarget, 'event1')\n      dispatch(eventTarget, 'event2')\n      dispatch(eventTarget, 'event3')\n      expect(spy).toHaveBeenCalledTimes(3)\n    })\n\n    it('unbinds with namespace only', () => {\n      const eventTarget = new EventTarget()\n      const spy1 = createSpy('spy1')\n      const spy2 = createSpy('spy2')\n      const spy3 = createSpy('spy3')\n      on(eventTarget, 'event1.ns1', spy1)\n      on(eventTarget, 'event2.ns1', spy2)\n      on(eventTarget, 'event3.ns2', spy3)\n      dispatch(eventTarget, 'event1')\n      dispatch(eventTarget, 'event2')\n      dispatch(eventTarget, 'event3')\n      off(eventTarget, '.ns1')\n      dispatch(eventTarget, 'event1')\n      dispatch(eventTarget, 'event2')\n      dispatch(eventTarget, 'event3')\n      expect(spy1).toHaveBeenCalledTimes(1)\n      expect(spy2).toHaveBeenCalledTimes(1)\n      expect(spy3).toHaveBeenCalledTimes(2)\n    })\n\n    it('unbinds all events when called without event', () => {\n      const eventTarget = new EventTarget()\n      const spy1 = createSpy('spy1')\n      const spy2 = createSpy('spy2')\n      const spy3 = createSpy('spy3')\n      on(eventTarget, 'event1.ns1', spy1)\n      on(eventTarget, 'event2.ns1', spy2)\n      on(eventTarget, 'event3.ns2', spy3)\n      dispatch(eventTarget, 'event1')\n      dispatch(eventTarget, 'event2')\n      dispatch(eventTarget, 'event3')\n      off(eventTarget)\n      dispatch(eventTarget, 'event1')\n      dispatch(eventTarget, 'event2')\n      dispatch(eventTarget, 'event3')\n      expect(spy1).toHaveBeenCalledTimes(1)\n      expect(spy2).toHaveBeenCalledTimes(1)\n      expect(spy3).toHaveBeenCalledTimes(1)\n    })\n  })\n\n  describe('dispatch()', () => {\n    it('dispatches a custom event on the EventTarget by calling dispatchEvent()', () => {\n      const eventTarget = new EventTarget()\n      const spy = spyOn(eventTarget, 'dispatchEvent')\n      const event = dispatch(\n        eventTarget,\n        'event',\n        { some: 'data' },\n        { cancelable: false }\n      )\n      expect(event).toEqual(any(getWindow().CustomEvent))\n      expect(spy).toHaveBeenCalledWith(event)\n      expect(event.detail).toEqual({ some: 'data' })\n      expect(event.cancelable).toBe(false)\n    })\n\n    it('dispatches the passed event directly', () => {\n      const eventTarget = new EventTarget()\n      const spy = spyOn(eventTarget, 'dispatchEvent')\n\n      const CustomEvent = getWindow().CustomEvent\n      const event1 = new CustomEvent('event', { detail: { some: 'data' } })\n      const event2 = dispatch(eventTarget, event1)\n      expect(event1).toBe(event2)\n      expect(spy).toHaveBeenCalledWith(event1)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/core/gradiented.js",
    "content": "/* globals describe, expect, it */\n\nimport { Gradient } from '../../../../src/main.js'\n\ndescribe('gradiented.js', () => {\n  describe('from()', () => {\n    it('sets fx and fy for radial gradients and returns itself', () => {\n      const gradient = new Gradient('radial')\n      expect(gradient.from(10, 20)).toBe(gradient)\n      expect(gradient.attr('fx')).toBe(10)\n      expect(gradient.attr('fy')).toBe(20)\n    })\n\n    it('sets x1 and y1 for linear gradients and returns itself', () => {\n      const gradient = new Gradient('linear')\n      expect(gradient.from(10, 20)).toBe(gradient)\n      expect(gradient.attr('x1')).toBe(10)\n      expect(gradient.attr('y1')).toBe(20)\n    })\n  })\n\n  describe('to()', () => {\n    it('sets cx and cy for radial gradients and returns itself', () => {\n      const gradient = new Gradient('radial')\n      expect(gradient.to(10, 20)).toBe(gradient)\n      expect(gradient.attr('cx')).toBe(10)\n      expect(gradient.attr('cy')).toBe(20)\n    })\n\n    it('sets x2 and y2 for linear gradients and returns itself', () => {\n      const gradient = new Gradient('linear')\n      expect(gradient.to(10, 20)).toBe(gradient)\n      expect(gradient.attr('x2')).toBe(10)\n      expect(gradient.attr('y2')).toBe(20)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/core/parser.js",
    "content": "/* globals describe, expect, it */\n\nimport parser from '../../../../src/modules/core/parser.js'\nimport { getWindow } from '../../../../src/utils/window.js'\n\ndescribe('parser.js', () => {\n  describe('parser()', () => {\n    it('returns an object with svg and path', () => {\n      const nodes = parser()\n      expect(nodes.path).toBeDefined()\n      expect(nodes.svg).toBeDefined()\n    })\n\n    it('creates an svg node in the dom', () => {\n      expect(getWindow().document.querySelector('svg')).toBe(null)\n      const nodes = parser()\n      expect(getWindow().document.querySelector('svg')).toBe(nodes.svg.node)\n    })\n\n    it('reuses parser instance when it was removed', () => {\n      const nodes = parser()\n      nodes.svg.remove()\n      const nodes2 = parser()\n      expect(nodes.svg).toBe(nodes2.svg)\n      expect(nodes.path).toBe(nodes2.path)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/core/pointed.js",
    "content": "/* globals describe, expect, it, beforeEach, container */\n\nimport { SVG } from '../../../../src/main.js'\n\ndescribe('pointed.js', () => {\n  let canvas, lines\n\n  beforeEach(() => {\n    canvas = SVG().addTo(container)\n    const line = canvas.line(1, 2, 3, 4)\n    const polygon = canvas.polygon([1, 2, 3, 4])\n    const polyline = canvas.polyline([1, 2, 3, 4])\n    lines = { line, polygon, polyline }\n  })\n  ;['line', 'polygon', 'polyline'].forEach((l) => {\n    describe('for ' + l, () => {\n      describe('x()', () => {\n        it('sets the x value of the ' + l + 'and returns itself', () => {\n          expect(lines[l].x(50)).toBe(lines[l])\n          expect(lines[l].bbox().x).toBe(50)\n        })\n\n        it('gets the x value of the ' + l, () => {\n          expect(lines[l].x(50).x()).toBe(50)\n        })\n      })\n\n      describe('y()', () => {\n        it('sets the y value of the ' + l + 'and returns itself', () => {\n          expect(lines[l].y(50)).toBe(lines[l])\n          expect(lines[l].bbox().y).toBe(50)\n        })\n\n        it('gets the y value of the ' + l, () => {\n          expect(lines[l].y(50).y()).toBe(50)\n        })\n      })\n\n      describe('width()', () => {\n        it('sets the width of the ' + l + 'and returns itself', () => {\n          expect(lines[l].width(50)).toBe(lines[l])\n          expect(lines[l].bbox().width).toBe(50)\n        })\n\n        it('gets the width of the ' + l, () => {\n          expect(lines[l].width(50).width()).toBe(50)\n        })\n      })\n\n      describe('height()', () => {\n        it('sets the height of the ' + l + 'and returns itself', () => {\n          expect(lines[l].height(50)).toBe(lines[l])\n          expect(lines[l].bbox().height).toBe(50)\n        })\n\n        it('gets the height of the ' + l, () => {\n          expect(lines[l].height(50).height()).toBe(50)\n        })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/core/poly.js",
    "content": "/* globals describe, expect, beforeEach, it, spyOn, jasmine, container */\n\nimport { Polygon, SVG, PointArray } from '../../../../src/main.js'\n\nconst { any, objectContaining } = jasmine\n\ndescribe('Polygon.js', () => {\n  let poly\n\n  beforeEach(() => {\n    poly = new Polygon()\n  })\n\n  describe('array()', () => {\n    it('returns the underlying PointArray', () => {\n      const array = poly.plot('1 2 3 4').array()\n      expect(array).toEqual(any(PointArray))\n      expect(array).toEqual([\n        [1, 2],\n        [3, 4]\n      ])\n    })\n  })\n\n  describe('clear()', () => {\n    it('clears the array cache and returns itself', () => {\n      const array = poly.plot('1 2 3 4').array()\n      expect(poly.clear()).toBe(poly)\n      expect(array).not.toBe(poly._array)\n    })\n  })\n\n  describe('move()', () => {\n    it('returns itself', () => {\n      expect(poly.move(0, 0)).toBe(poly)\n    })\n\n    it('moves the poly along x and y axis', () => {\n      const canvas = SVG().addTo(container)\n      const poly = canvas.polygon('0 0 50 50')\n      poly.move(50, 50)\n      expect(poly.bbox()).toEqual(\n        objectContaining({\n          x: 50,\n          y: 50,\n          width: 50,\n          height: 50\n        })\n      )\n    })\n  })\n\n  describe('plot()', () => {\n    it('relays to array() as getter', () => {\n      const spy = spyOn(poly, 'array')\n      poly.plot()\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it('works by passing a string', () => {\n      const spy = spyOn(poly, 'attr')\n      poly.plot('1 2 3 4')\n      expect(spy).toHaveBeenCalledWith('points', '1 2 3 4')\n    })\n\n    it('works with flat array', () => {\n      const spy = spyOn(poly, 'attr')\n      poly.plot([1, 2, 3, 4])\n      expect(spy).toHaveBeenCalledWith('points', [\n        [1, 2],\n        [3, 4]\n      ])\n    })\n\n    it('works with multi array', () => {\n      const spy = spyOn(poly, 'attr')\n      poly.plot([\n        [1, 2],\n        [3, 4]\n      ])\n      expect(spy).toHaveBeenCalledWith('points', [\n        [1, 2],\n        [3, 4]\n      ])\n    })\n\n    it('works with PointArray', () => {\n      const spy = spyOn(poly, 'attr')\n      poly.plot(\n        new PointArray([\n          [1, 2],\n          [3, 4]\n        ])\n      )\n      expect(spy).toHaveBeenCalledWith('points', [\n        [1, 2],\n        [3, 4]\n      ])\n    })\n  })\n\n  describe('size()', () => {\n    it('returns itself', () => {\n      expect(poly.size(50, 50)).toBe(poly)\n    })\n\n    it('sets the size of the poly', () => {\n      const canvas = SVG().addTo(container)\n      const poly = canvas.polygon('0 0 50 50')\n      poly.size(100, 100)\n      expect(poly.bbox()).toEqual(\n        objectContaining({\n          width: 100,\n          height: 100,\n          x: 0,\n          y: 0\n        })\n      )\n    })\n\n    it('changes height proportionally', () => {\n      const canvas = SVG().addTo(container)\n      const poly = canvas.polygon('0 0 50 50')\n      poly.size(100, null)\n      expect(poly.bbox()).toEqual(\n        objectContaining({\n          width: 100,\n          height: 100,\n          x: 0,\n          y: 0\n        })\n      )\n    })\n\n    it('changes width proportionally', () => {\n      const canvas = SVG().addTo(container)\n      const poly = canvas.polygon('0 0 50 50')\n      poly.size(null, 100)\n      expect(poly.bbox()).toEqual(\n        objectContaining({\n          width: 100,\n          height: 100,\n          x: 0,\n          y: 0\n        })\n      )\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/core/regex.js",
    "content": "/* globals describe, expect, it */\n\nimport { regex } from '../../../../src/main.js'\n\ndescribe('regex.js', () => {\n  describe('numberAndUnit', () => {\n    it('matches number and unit 12px', () => {\n      const match = '12px'.match(regex.numberAndUnit)\n      expect(match[1]).toBe('12')\n      expect(match[5]).toBe('px')\n    })\n\n    it('matches number and unit 12', () => {\n      const match = '12'.match(regex.numberAndUnit)\n      expect(match[1]).toBe('12')\n      expect(match[5]).toBe('')\n    })\n\n    it('matches number and unit 12%', () => {\n      const match = '12%'.match(regex.numberAndUnit)\n      expect(match[1]).toBe('12')\n      expect(match[5]).toBe('%')\n    })\n\n    it('matches number and unit -12%', () => {\n      const match = '-12%'.match(regex.numberAndUnit)\n      expect(match[1]).toBe('-12')\n      expect(match[5]).toBe('%')\n    })\n\n    it('matches number and unit -12.123%', () => {\n      const match = '-12.123%'.match(regex.numberAndUnit)\n      expect(match[1]).toBe('-12.123')\n      expect(match[5]).toBe('%')\n    })\n\n    it('matches number and unit -12.123e12%', () => {\n      const match = '-12.123e12%'.match(regex.numberAndUnit)\n      expect(match[1]).toBe('-12.123e12')\n      expect(match[5]).toBe('%')\n    })\n  })\n\n  describe('hex', () => {\n    it('matches a 6 digit hex', () => {\n      const match = '#123456'.match(regex.hex)\n      expect(match[1]).toBe('12')\n      expect(match[2]).toBe('34')\n      expect(match[3]).toBe('56')\n    })\n\n    /* it('does not matches without #', () => {\n      const match = '123456'.match(regex.hex)\n      expect(match).toBe(null)\n    }) */\n\n    it('does not matches other then 0-f #', () => {\n      const match = '#09afhz'.match(regex.hex)\n      expect(match).toBe(null)\n    })\n\n    it('does not matches non full hex', () => {\n      const match = '#aaa'.match(regex.hex)\n      expect(match).toBe(null)\n    })\n  })\n\n  describe('rgb', () => {\n    it('matches rgb values of rgb(...) command', () => {\n      const match = 'rgb(12,34,56)'.match(regex.rgb)\n      expect(match[1]).toBe('12')\n      expect(match[2]).toBe('34')\n      expect(match[3]).toBe('56')\n    })\n\n    it('does not match in the wrong format', () => {\n      expect('rgb(   12 , 34  ,     56)'.match(regex.rgb)).toBe(null)\n      expect('12,34,56'.match(regex.rgb)).toBe(null)\n      expect('(12,34,56)'.match(regex.rgb)).toBe(null)\n      expect('rgb(aa,34,56)'.match(regex.rgb)).toBe(null)\n      expect('rgb(12,34)'.match(regex.rgb)).toBe(null)\n    })\n  })\n\n  describe('reference', () => {\n    it('matches a reference', () => {\n      const match = '#soMe_cRazy-1_id'.match(regex.reference)\n      expect(match[1]).toBe('#soMe_cRazy-1_id')\n    })\n\n    it('tries to match malformed references', () => {\n      const match = '#some_crazy%-1_id'.match(regex.reference)\n      expect(match[0]).toBe('#some_crazy')\n    })\n  })\n\n  describe('transforms', () => {\n    it('splits a transform chain', () => {\n      const split =\n        'rotate(34) translate(1,2), translate(1 ,  3),rotate(12)    ,   something(1,2,3)'.split(\n          regex.transforms\n        )\n      expect(split).toEqual([\n        'rotate(34',\n        'translate(1,2',\n        'translate(1 ,  3',\n        'rotate(12',\n        'something(1,2,3',\n        ''\n      ])\n    })\n  })\n\n  describe('whitespace', () => {\n    it('replaces all whitespaces', () => {\n      expect('   \\n \\r   \\t   '.replace(regex.whitespace, ' ')).toBe(\n        '             '\n      )\n    })\n  })\n\n  describe('isHex', () => {\n    it('returns true when testing hex values', () => {\n      expect(regex.isHex.test('#123')).toBe(true)\n      expect(regex.isHex.test('#abc')).toBe(true)\n      expect(regex.isHex.test('#123456')).toBe(true)\n      expect(regex.isHex.test('#abcdef')).toBe(true)\n      expect(regex.isHex.test('#16fde9')).toBe(true)\n    })\n\n    it('returns false when testing non hex values', () => {\n      expect(regex.isHex.test('#12')).toBe(false)\n      expect(regex.isHex.test('abc')).toBe(false)\n      expect(regex.isHex.test('#1234563')).toBe(false)\n      expect(regex.isHex.test('#kasdhs')).toBe(false)\n      expect(regex.isHex.test('#abcd')).toBe(false)\n    })\n  })\n\n  describe('isRgb', () => {\n    it('returns true when testing rgb values', () => {\n      expect(regex.isRgb.test('rgb(1,2,3)')).toBe(true)\n      expect(regex.isRgb.test('rgb( 3,   1,3)')).toBe(true)\n    })\n\n    it('returns false when testing non rgb values', () => {\n      expect(regex.isRgb.test('hsl(1,2,3)')).toBe(false)\n      expect(regex.isRgb.test('#123')).toBe(false)\n      expect(regex.isRgb.test('something')).toBe(false)\n    })\n  })\n\n  describe('isBlank', () => {\n    it('returns true if something is blank', () => {\n      expect(regex.isBlank.test('')).toBe(true)\n      expect(regex.isBlank.test(' ')).toBe(true)\n      expect(regex.isBlank.test('\\n')).toBe(true)\n      expect(regex.isBlank.test('\\r')).toBe(true)\n      expect(regex.isBlank.test('\\t')).toBe(true)\n      expect(regex.isBlank.test(' \\n\\r\\t')).toBe(true)\n    })\n\n    it('returns false if something is not blank', () => {\n      expect(regex.isBlank.test('a')).toBe(false)\n      expect(regex.isBlank.test('1')).toBe(false)\n    })\n  })\n\n  describe('isNumber', () => {\n    it('returns true if something is a number', () => {\n      expect(regex.isNumber.test('123')).toBe(true)\n      expect(regex.isNumber.test('-123')).toBe(true)\n      expect(regex.isNumber.test('-12.3')).toBe(true)\n      expect(regex.isNumber.test('-12.3e12')).toBe(true)\n      expect(regex.isNumber.test('-12.3e-12')).toBe(true)\n      expect(regex.isNumber.test('+12.3e-12')).toBe(true)\n      expect(regex.isNumber.test('+12.3E-12')).toBe(true)\n    })\n\n    it('returns false if something is not a number', () => {\n      expect(regex.isNumber.test('a')).toBe(false)\n      expect(regex.isNumber.test('-a')).toBe(false)\n      expect(regex.isNumber.test('-12a')).toBe(false)\n      expect(regex.isNumber.test('-12.3a12')).toBe(false)\n      expect(regex.isNumber.test('-12.3e-1a')).toBe(false)\n      expect(regex.isNumber.test('12.12.12')).toBe(false)\n      expect(regex.isNumber.test('12.12e12.3')).toBe(false)\n      expect(regex.isNumber.test('12.12e12e4')).toBe(false)\n    })\n  })\n\n  describe('isImage', () => {\n    it('returns true if something is an image filename', () => {\n      expect(regex.isImage.test('a.jpg')).toBe(true)\n      expect(regex.isImage.test('a.jpeg')).toBe(true)\n      expect(regex.isImage.test('a.png')).toBe(true)\n      expect(regex.isImage.test('a.gif')).toBe(true)\n      expect(regex.isImage.test('a.svg')).toBe(true)\n    })\n\n    it('returns false if something is not an image filename', () => {\n      expect(regex.isImage.test('a.abc')).toBe(false)\n      expect(regex.isImage.test('a.txt')).toBe(false)\n      expect(regex.isImage.test('a.doc')).toBe(false)\n    })\n  })\n\n  describe('delimiter', () => {\n    it('splits at whitespace and comma', () => {\n      const split = '1,2 3 , 4   5,,  6'.split(regex.delimiter)\n      expect(split).toEqual(['1', '2', '3', '4', '5', '6'])\n    })\n  })\n\n  describe('isPathLetter', () => {\n    it('returns true if something is a path letter', () => {\n      'MLHVCSQTAZmlhvcsqtaz'.split('').forEach((l) => {\n        expect(regex.isPathLetter.test(l)).toBe(true)\n      })\n    })\n\n    it('returns false if something is not path letter', () => {\n      '123biuBIU$%&'.split('').forEach((l) => {\n        expect(regex.isPathLetter.test(l)).toBe(false)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/core/selector.js",
    "content": "/* globals describe, expect, it, container */\n\nimport { find, SVG, G } from '../../../../src/main.js'\nimport { getWindow } from '../../../../src/utils/window.js'\n\ndescribe('selector.js', () => {\n  describe('baseFind()', () => {\n    it('finds all elements of a selector in the document', () => {\n      const div = SVG('<div />', true).id('foo').addTo(container)\n      const span = SVG('<span />', true).addClass('bar').addTo(div)\n      const span2 = SVG('<span />', true).addTo(div)\n\n      expect(find('#canvas').map((el) => el.node)).toEqual([container])\n      expect(find('span')).toEqual([span, span2])\n      expect(find('#foo')).toEqual([div])\n      expect(find('.bar')).toEqual([span])\n    })\n\n    it('finds all elements of a selector scoped to an element', () => {\n      const div = SVG('<div />', true).id('foo').addTo(container)\n\n      expect(find('#canvas', getWindow().document)[0].node).toBe(container)\n      expect(find('#foo', container)).toEqual([div])\n      expect(find('#canvas', div.node)).toEqual([])\n    })\n  })\n\n  describe('Dom', () => {\n    describe('find()', () => {\n      it('finds all elements matching the selector in this element', () => {\n        const g1 = new G()\n        const g2 = new G().addTo(g1).id('foo')\n        const g3 = new G().addTo(g1).addClass('bar')\n        const g4 = new G().addTo(g2)\n        const g5 = new G().addTo(g3)\n\n        expect(g1.find('g')).toEqual([g2, g4, g3, g5])\n        expect(g2.find('g')).toEqual([g4])\n        expect(g1.find('#foo')).toEqual([g2])\n        expect(g2.find('#foo')).toEqual([])\n        expect(g1.find('.bar')).toEqual([g3])\n      })\n    })\n\n    describe('findOne()', () => {\n      it('finds an element in this element', () => {\n        const g1 = new G()\n        const g2 = new G().addTo(g1).id('foo')\n        const g3 = new G().addTo(g1).addClass('bar')\n        const g4 = new G().addTo(g2)\n\n        expect(g1.findOne('g')).toBe(g2)\n        expect(g2.findOne('g')).toBe(g4)\n        expect(g1.findOne('#foo')).toBe(g2)\n        expect(g2.findOne('#foo')).toBe(null)\n        expect(g1.findOne('.bar')).toBe(g3)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/core/textable.js",
    "content": "/* globals describe, expect, it, beforeEach, spyOn, container */\n\nimport { SVG, Box, Tspan } from '../../../../src/main.js'\n\ndescribe('textable.js', () => {\n  var canvas, text, tspan\n\n  beforeEach(() => {\n    canvas = SVG().addTo(container)\n    text = canvas.text('Hello World\\nIn two lines')\n    tspan = text.get(0)\n  })\n\n  describe('x()', () => {\n    it('returns the value of x without an argument on a text', () => {\n      expect(text.x(0).x()).toBe(0)\n    })\n\n    it('sets the x value of the bbox with the first argument on a text', () => {\n      text.x(123)\n      expect(text.bbox().x).toBe(123)\n    })\n\n    it('sets the value of all lines', () => {\n      text.x(200)\n      text.each(function () {\n        expect(this.x()).toBe(text.x())\n      })\n    })\n\n    it('returns the value of x without an argument on a tspan', () => {\n      expect(tspan.x(10).x()).toBe(10)\n    })\n\n    it('sets the x value of the bbox with the first argument on a tspan', () => {\n      tspan.x(123)\n      expect(tspan.bbox().x).toBe(123)\n    })\n  })\n\n  describe('y()', () => {\n    it('returns the value of y without an argument on a text', () => {\n      expect(text.y(0).y()).toBe(0)\n    })\n\n    it('sets the y value of the bbox with the first argument on a text', () => {\n      text.y(123)\n      expect(text.bbox().y).toBe(123)\n    })\n\n    it('sets the y position of first line', () => {\n      text.y(200)\n      expect(text.first().y()).toBe(text.y())\n    })\n\n    it('returns the value of y without an argument on a tspan', () => {\n      expect(tspan.y(10).y()).toBe(10)\n    })\n\n    it('sets the y value of the bbox with the first argument on a tspan', () => {\n      tspan.y(123)\n      expect(tspan.bbox().y).toBe(123)\n    })\n  })\n\n  describe('move()', () => {\n    it('calls x() and y() with parameters on text', () => {\n      const spyX = spyOn(text, 'x').and.callThrough()\n      const spyY = spyOn(text, 'y').and.callThrough()\n      const box = new Box()\n      text.move(1, 2, box)\n      expect(spyX).toHaveBeenCalledWith(1, box)\n      expect(spyY).toHaveBeenCalledWith(2, box)\n    })\n\n    it('calls x() and y() with parameters on tspan', () => {\n      const spyX = spyOn(tspan, 'x').and.callThrough()\n      const spyY = spyOn(tspan, 'y').and.callThrough()\n      const box = new Box()\n      tspan.move(1, 2, box)\n      expect(spyX).toHaveBeenCalledWith(1, box)\n      expect(spyY).toHaveBeenCalledWith(2, box)\n    })\n  })\n\n  describe('ax()', () => {\n    it('sets the value of x with a percent value with Text', () => {\n      text.ax('40%')\n      expect(text.node.getAttribute('x')).toBe('40%')\n    })\n\n    it('returns the value of x when x is a percentual value with Text', () => {\n      expect(text.ax('40%').ax()).toBe('40%')\n    })\n\n    it('sets the value of x with a percent value with Tspan', () => {\n      tspan.ax('40%')\n      expect(tspan.node.getAttribute('x')).toBe('40%')\n    })\n\n    it('returns the value of x when x is a percentual value with Tspan', () => {\n      tspan.ax('40%')\n      expect(tspan.ax()).toBe('40%')\n    })\n  })\n\n  describe('ay()', () => {\n    it('sets the value of y with a percent value with Text', () => {\n      text.ay('40%')\n      expect(text.node.getAttribute('y')).toBe('40%')\n    })\n\n    it('returns the value of y when y is a percentual value with Tspan', () => {\n      expect(text.ay('45%').ay()).toBe('45%')\n    })\n\n    it('sets the value of y with a percent value with Text', () => {\n      tspan.ay('40%')\n      expect(tspan.node.getAttribute('y')).toBe('40%')\n    })\n\n    it('returns the value of y when y is a percentual value with Tspan', () => {\n      tspan.ay('40%')\n      expect(tspan.ay()).toBe('40%')\n    })\n  })\n\n  describe('move()', () => {\n    it('calls ax() and ay() with parameters on text', () => {\n      const spyX = spyOn(text, 'ax').and.callThrough()\n      const spyY = spyOn(text, 'ay').and.callThrough()\n      text.amove(1, 2)\n      expect(spyX).toHaveBeenCalledWith(1)\n      expect(spyY).toHaveBeenCalledWith(2)\n    })\n\n    it('calls ax() and ay() with parameters on tspan', () => {\n      const spyX = spyOn(tspan, 'ax').and.callThrough()\n      const spyY = spyOn(tspan, 'ay').and.callThrough()\n      tspan.amove(1, 2)\n      expect(spyX).toHaveBeenCalledWith(1)\n      expect(spyY).toHaveBeenCalledWith(2)\n    })\n  })\n\n  // this is a hackish. It should not be neccessary to use toBeCloseTo but bbox with text is a thing...\n  describe('cx()', () => {\n    it('returns the value of cx without an argument with Text', () => {\n      var box = text.bbox()\n      expect(text.cx()).toBeCloseTo(box.x + box.width / 2)\n    })\n\n    it('sets the value of cx with the first argument with Text', () => {\n      text.cx(123)\n      var box = text.bbox()\n      expect(box.cx).toBeCloseTo(box.x + box.width / 2)\n    })\n\n    it('returns the value of cx without an argument with Tspan', () => {\n      var box = tspan.bbox()\n      expect(tspan.cx()).toBeCloseTo(box.x + box.width / 2)\n    })\n\n    it('sets the value of cx with the first argument with Tspan', () => {\n      tspan.cx(123)\n      var box = tspan.bbox()\n      expect(box.cx).toBeCloseTo(box.x + box.width / 2)\n    })\n  })\n\n  describe('cy()', () => {\n    it('returns the value of cy without an argument with Tspan', () => {\n      var box = tspan.bbox()\n      expect(tspan.cy()).toBe(box.cy)\n    })\n\n    it('sets the value of cy with the first argument with Tspan', () => {\n      tspan.cy(345)\n      var box = tspan.bbox()\n      expect(Math.round(box.cy * 10) / 10).toBe(345)\n    })\n\n    it('returns the value of cy without an argument with Tspan', () => {\n      var box = tspan.bbox()\n      expect(tspan.cy()).toBe(box.cy)\n    })\n\n    it('sets the value of cy with the first argument with Tspan', () => {\n      tspan.cy(345)\n      var box = tspan.bbox()\n      expect(Math.round(box.cy * 10) / 10).toBe(345)\n    })\n  })\n\n  describe('center()', () => {\n    it('calls cx() and cy() with parameters on Text', () => {\n      const spyX = spyOn(text, 'cx').and.callThrough()\n      const spyY = spyOn(text, 'cy').and.callThrough()\n      const box = new Box()\n      text.center(1, 2, box)\n      expect(spyX).toHaveBeenCalledWith(1, box)\n      expect(spyY).toHaveBeenCalledWith(2, box)\n    })\n\n    it('calls cx() and cy() with parameters on Tspan', () => {\n      const spyX = spyOn(tspan, 'cx').and.callThrough()\n      const spyY = spyOn(tspan, 'cy').and.callThrough()\n      const box = new Box()\n      tspan.center(1, 2, box)\n      expect(spyX).toHaveBeenCalledWith(1, box)\n      expect(spyY).toHaveBeenCalledWith(2, box)\n    })\n  })\n\n  describe('plain()', () => {\n    it('adds content without a tspan with Text', () => {\n      text.plain('It is a bear!')\n      expect(text.node.childNodes[0].nodeType).toBe(3)\n      expect(text.node.childNodes[0].data).toBe('It is a bear!')\n    })\n\n    it('clears content before adding new content with Text', () => {\n      text.plain('It is not a bear!')\n      expect(text.node.childNodes.length).toBe(1)\n      expect(text.node.childNodes[0].data).toBe('It is not a bear!')\n    })\n\n    it('restores the content from the dom with Text', () => {\n      text.plain('Just plain text!')\n      expect(text.text()).toBe('Just plain text!')\n    })\n\n    it('adds content without a tspan with Tspan', () => {\n      tspan.plain('It is a bear!')\n      expect(tspan.node.childNodes[0].nodeType).toBe(3)\n      expect(tspan.node.childNodes[0].data).toBe('It is a bear!')\n    })\n\n    it('clears content before adding new content with Tspan', () => {\n      tspan.plain('It is not a bear!')\n      expect(tspan.node.childNodes.length).toBe(1)\n      expect(tspan.node.childNodes[0].data).toBe('It is not a bear!')\n    })\n\n    it('restores the content from the dom with Tspan', () => {\n      // We create a new Tspan here because the one used before was part of text creation\n      // and therefore is marked as newline and that is not what we want to test\n      const tspan = new Tspan().plain('Just plain text!')\n      expect(tspan.text()).toBe('Just plain text!')\n    })\n  })\n\n  describe('length()', () => {\n    it('gets total length of text', () => {\n      text.text(function (add) {\n        add.tspan('The first.')\n        add.tspan('The second.')\n        add.tspan('The third.')\n      })\n      expect(text.length()).toBeCloseTo(\n        text.get(0).length() + text.get(1).length() + text.get(2).length(),\n        3\n      )\n    })\n\n    it('gets total length of tspan', () => {\n      tspan.text(function (add) {\n        add.tspan('The first.')\n        add.tspan('The second.')\n        add.tspan('The third.')\n      })\n      expect(tspan.length()).toBeCloseTo(\n        tspan.get(0).length() + tspan.get(1).length() + tspan.get(2).length(),\n        3\n      )\n    })\n  })\n\n  describe('build()', () => {\n    it('enables adding multiple plain text nodes when given true for Text', () => {\n      text.clear().build(true)\n      text.plain('A great piece!')\n      text.plain('Another great piece!')\n      expect(text.node.childNodes[0].data).toBe('A great piece!')\n      expect(text.node.childNodes[1].data).toBe('Another great piece!')\n    })\n\n    it('enables adding multiple tspan nodes when given true for Text', () => {\n      text.clear().build(true)\n      text.tspan('A great piece!')\n      text.tspan('Another great piece!')\n      expect(text.node.childNodes[0].childNodes[0].data).toBe('A great piece!')\n      expect(text.node.childNodes[1].childNodes[0].data).toBe(\n        'Another great piece!'\n      )\n    })\n\n    it('disables adding multiple plain text nodes when given false for Text', () => {\n      text.clear().build(true)\n      text.plain('A great piece!')\n      text.build(false).plain('Another great piece!')\n      expect(text.node.childNodes[0].data).toBe('Another great piece!')\n      expect(text.node.childNodes[1]).toBe(undefined)\n    })\n\n    it('disables adding multiple tspan nodes when given false for Text', () => {\n      text.clear().build(true)\n      text.tspan('A great piece!')\n      text.build(false).tspan('Another great piece!')\n      expect(text.node.childNodes[0].childNodes[0].data).toBe(\n        'Another great piece!'\n      )\n      expect(text.node.childNodes[1]).toBe(undefined)\n    })\n\n    it('enables adding multiple plain text nodes when given true for Tspan', () => {\n      tspan.clear().build(true)\n      tspan.plain('A great piece!')\n      tspan.plain('Another great piece!')\n      expect(tspan.node.childNodes[0].data).toBe('A great piece!')\n      expect(tspan.node.childNodes[1].data).toBe('Another great piece!')\n    })\n\n    it('enables adding multiple text nodes when given true for Tspan', () => {\n      tspan.clear().build(true)\n      tspan.tspan('A great piece!')\n      tspan.tspan('Another great piece!')\n      expect(tspan.node.childNodes[0].childNodes[0].data).toBe('A great piece!')\n      expect(tspan.node.childNodes[1].childNodes[0].data).toBe(\n        'Another great piece!'\n      )\n    })\n\n    it('disables adding multiple plain text nodes when given false for Tspan', () => {\n      tspan.clear().build(true)\n      tspan.plain('A great piece!')\n      tspan.build(false).plain('Another great piece!')\n      expect(tspan.node.childNodes[0].data).toBe('Another great piece!')\n      expect(tspan.node.childNodes[1]).toBe(undefined)\n    })\n\n    it('disables adding multiple tspan nodes when given false for Tspan', () => {\n      tspan.clear().build(true)\n      tspan.tspan('A great piece!')\n      tspan.build(false).tspan('Another great piece!')\n      expect(tspan.node.childNodes[0].childNodes[0].data).toBe(\n        'Another great piece!'\n      )\n      expect(tspan.node.childNodes[1]).toBe(undefined)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/optional/arrange.js",
    "content": "/* globals describe, expect, it */\n\nimport { G, Line } from '../../../../src/main.js'\n\ndescribe('arrange.js', () => {\n  describe('Dom', () => {\n    describe('siblings()', () => {\n      it('returns all siblings including the node itself', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n        const line = g.line(1, 2, 3, 4)\n\n        expect(circle.siblings()).toEqual([rect, circle, line])\n      })\n    })\n\n    describe('position()', () => {\n      it('returns the position in the parent', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n        const line = g.line(1, 2, 3, 4)\n\n        expect(rect.position()).toBe(0)\n        expect(circle.position()).toBe(1)\n        expect(line.position()).toBe(2)\n      })\n    })\n\n    describe('next()', () => {\n      it('returns the next sibling', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n\n        expect(rect.next()).toBe(circle)\n      })\n\n      it('returns undefined if there is no sibling', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n\n        expect(rect.next()).toBe(undefined)\n      })\n    })\n\n    describe('prev()', () => {\n      it('returns the next sibling', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n\n        expect(circle.prev()).toBe(rect)\n      })\n\n      it('returns undefined if there is no sibling', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n\n        expect(rect.prev()).toBe(undefined)\n      })\n    })\n\n    describe('forward()', () => {\n      it('returns itself', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        expect(rect.forward()).toBe(rect)\n      })\n\n      it('moves an element one step forward', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n        const line = g.line(1, 2, 3, 4)\n\n        rect.forward()\n\n        expect(g.children()).toEqual([circle, rect, line])\n      })\n\n      it('does nothing when the element is already the last one', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n        const line = g.line(1, 2, 3, 4)\n\n        line.forward()\n\n        expect(g.children()).toEqual([rect, circle, line])\n      })\n    })\n\n    describe('backward()', () => {\n      it('returns itself', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        expect(rect.backward()).toBe(rect)\n      })\n\n      it('moves an element one step backward', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n        const line = g.line(1, 2, 3, 4)\n\n        line.backward()\n\n        expect(g.children()).toEqual([rect, line, circle])\n      })\n\n      it('does nothing when the element is already the first one', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n        const line = g.line(1, 2, 3, 4)\n\n        rect.backward()\n\n        expect(g.children()).toEqual([rect, circle, line])\n      })\n    })\n\n    describe('front()', () => {\n      it('returns itself', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        expect(rect.front()).toBe(rect)\n      })\n\n      it('moves an element to the front', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n        const line = g.line(1, 2, 3, 4)\n\n        rect.front()\n\n        expect(g.children()).toEqual([circle, line, rect])\n      })\n\n      it('does nothing when the element is already the last one', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n        const line = g.line(1, 2, 3, 4)\n\n        line.front()\n\n        expect(g.children()).toEqual([rect, circle, line])\n      })\n    })\n\n    describe('back()', () => {\n      it('returns itself', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        expect(rect.back()).toBe(rect)\n      })\n\n      it('moves an element to the back', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n        const line = g.line(1, 2, 3, 4)\n\n        line.back()\n\n        expect(g.children()).toEqual([line, rect, circle])\n      })\n\n      it('does nothing when the element is already the first one', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n        const line = g.line(1, 2, 3, 4)\n\n        rect.back()\n\n        expect(g.children()).toEqual([rect, circle, line])\n      })\n    })\n\n    describe('before()', () => {\n      it('inserts an element before this one', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n\n        const line = new Line()\n        circle.before(line)\n\n        expect(g.children()).toEqual([rect, line, circle])\n      })\n    })\n\n    describe('after()', () => {\n      it('inserts an element after this one', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n\n        const line = new Line()\n        rect.after(line)\n\n        expect(g.children()).toEqual([rect, line, circle])\n      })\n    })\n\n    describe('insertBefore()', () => {\n      it('inserts the current element before another one', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n\n        const line = new Line().insertBefore(circle)\n\n        expect(g.children()).toEqual([rect, line, circle])\n      })\n    })\n\n    describe('insertAfter()', () => {\n      it('inserts the current element after another one', () => {\n        const g = new G()\n        const rect = g.rect(100, 100)\n        const circle = g.circle(100)\n\n        const line = new Line().insertAfter(rect)\n\n        expect(g.children()).toEqual([rect, line, circle])\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/optional/class.js",
    "content": "/* globals describe, expect, it */\n\nimport { Rect } from '../../../../src/main.js'\n\ndescribe('class.js', () => {\n  describe('Dom', () => {\n    describe('classes()', () => {\n      it('returns all classes on an element', () => {\n        const rect = new Rect({ class: 'myClass myClass2' })\n        expect(rect.classes()).toEqual(['myClass', 'myClass2'])\n      })\n\n      it('returns an empty array if no class on the element', () => {\n        const rect = new Rect()\n        expect(rect.classes()).toEqual([])\n      })\n    })\n\n    describe('hasClass()', () => {\n      it('returns true if a class is present on the element', () => {\n        const rect = new Rect({ class: 'myClass myClass2' })\n        expect(rect.hasClass('myClass')).toBe(true)\n      })\n\n      it('returns false if a class is not present on the element', () => {\n        const rect = new Rect({ class: 'myClass myClass2' })\n        expect(rect.hasClass('myClass3')).toBe(false)\n      })\n    })\n\n    describe('addClass()', () => {\n      it('returns itself', () => {\n        const rect = new Rect({ class: 'myClass myClass2' })\n        expect(rect.addClass('myClass3')).toBe(rect)\n      })\n\n      it('adds a class to the element', () => {\n        const rect = new Rect({ class: 'myClass myClass2' }).addClass(\n          'myClass3'\n        )\n        expect(rect.classes()).toEqual(['myClass', 'myClass2', 'myClass3'])\n      })\n\n      it('does nothing if class already present on the element', () => {\n        const rect = new Rect({ class: 'myClass myClass2' }).addClass('myClass')\n        expect(rect.classes()).toEqual(['myClass', 'myClass2'])\n      })\n    })\n\n    describe('removeClass()', () => {\n      it('returns itself', () => {\n        const rect = new Rect({ class: 'myClass myClass2' })\n        expect(rect.removeClass('myClass3')).toBe(rect)\n      })\n\n      it('removes a class from the element', () => {\n        const rect = new Rect({ class: 'myClass myClass2' }).removeClass(\n          'myClass2'\n        )\n        expect(rect.classes()).toEqual(['myClass'])\n      })\n\n      it('does nothing if class is not present on the element', () => {\n        const rect = new Rect({ class: 'myClass myClass2' }).removeClass(\n          'myClass3'\n        )\n        expect(rect.classes()).toEqual(['myClass', 'myClass2'])\n      })\n    })\n\n    describe('toggleClass()', () => {\n      it('returns itself', () => {\n        const rect = new Rect({ class: 'myClass myClass2' })\n        expect(rect.toggleClass('myClass3')).toBe(rect)\n      })\n\n      it('removes a class from the element when its present', () => {\n        const rect = new Rect({ class: 'myClass myClass2' }).toggleClass(\n          'myClass2'\n        )\n        expect(rect.classes()).toEqual(['myClass'])\n      })\n\n      it('adds a class to the element if its not present', () => {\n        const rect = new Rect({ class: 'myClass myClass2' }).toggleClass(\n          'myClass3'\n        )\n        expect(rect.classes()).toEqual(['myClass', 'myClass2', 'myClass3'])\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/optional/css.js",
    "content": "/* globals describe, expect, it */\n\nimport { Rect } from '../../../../src/main.js'\n\ndescribe('css.js', () => {\n  describe('Dom', () => {\n    describe('css()', () => {\n      describe('as getter', () => {\n        it('returns all css as object', () => {\n          const rect = new Rect({\n            style: 'fill: none; outline-width: 1px; stroke: none'\n          })\n          expect(rect.css()).toEqual({\n            fill: 'none',\n            'outline-width': '1px',\n            stroke: 'none'\n          })\n        })\n\n        it('returns an object with selected css properties', () => {\n          const rect = new Rect({\n            style: 'fill: none; outline-width: 1px; stroke: none'\n          })\n          expect(rect.css(['fill', 'stroke'])).toEqual({\n            fill: 'none',\n            stroke: 'none'\n          })\n        })\n\n        it('returns a single property with property name given', () => {\n          const rect = new Rect({\n            style: 'fill: none; outline-width: 1px; stroke: none'\n          })\n          expect(rect.css('fill')).toBe('none')\n        })\n\n        it('correctly returns css vars', () => {\n          const rect = new Rect({\n            style: '--foo: red;'\n          })\n          expect(rect.css('--foo')).toBe('red')\n        })\n\n        it('returns undefined if css property is not set', () => {\n          const rect = new Rect({\n            style: 'fill: none; outline-width: 1px; stroke: none'\n          })\n          expect(rect.css('outline-color')).toBe('')\n        })\n      })\n\n      describe('as setter', () => {\n        it('returns itself', () => {\n          const rect = new Rect({\n            style: 'fill: none; outline-width: 1px; stroke: none'\n          })\n          expect(rect.css('fill', 'black')).toBe(rect)\n        })\n\n        it('adds a css property', () => {\n          const rect = new Rect({\n            style: 'fill: none; outline-width: 1px; stroke: none'\n          })\n          expect(rect.css('stroke-width', '2px').css('stroke-width')).toBe(\n            '2px'\n          )\n        })\n\n        it('changes a css property', () => {\n          const rect = new Rect({\n            style: 'fill: none; outline-width: 1px; stroke: none'\n          })\n          expect(rect.css('fill', 'black').css('fill')).toBe('black')\n        })\n\n        it('sets an object of properties', () => {\n          const rect = new Rect()\n          expect(rect.css({ fill: 'none', stroke: 'none' }).css()).toEqual({\n            fill: 'none',\n            stroke: 'none'\n          })\n        })\n\n        it('removes property if empty string is passed as value', () => {\n          const rect = new Rect({\n            style: 'fill: none; outline-width: 1px; stroke: none'\n          })\n          expect(rect.css('fill', '').css('fill')).toBe('')\n        })\n\n        it('removes property if null is passed as value', () => {\n          const rect = new Rect({\n            style: 'fill: none; outline-width: 1px; stroke: none'\n          })\n          expect(rect.css('fill', null).css('fill')).toBe('')\n        })\n\n        it('removes property if null is passed as part of object', () => {\n          const rect = new Rect({\n            style: 'fill: none; outline-width: 1px; stroke: none'\n          })\n          expect(rect.css({ fill: null, stroke: 'black' }).css('fill')).toBe('')\n        })\n\n        it('allows single set of css vars', () => {\n          const rect = new Rect().css('--foo', 'red').css('--foo', 'green')\n          expect(rect.css()).toEqual({\n            '--foo': 'green'\n          })\n        })\n\n        it('allows multiple set of css vars via object', () => {\n          const rect = new Rect().css({ '--foo': 'red', '--bar': 'green' })\n\n          expect(rect.css()).toEqual({\n            '--foo': 'red',\n            '--bar': 'green'\n          })\n        })\n      })\n    })\n\n    describe('show()', () => {\n      it('returns itself', () => {\n        const rect = new Rect()\n        expect(rect.show()).toBe(rect)\n      })\n\n      it('removes the display property', () => {\n        const rect = new Rect().hide()\n        expect(rect.show().css('display')).toBe('')\n      })\n    })\n\n    describe('hide()', () => {\n      it('returns itself', () => {\n        const rect = new Rect()\n        expect(rect.hide()).toBe(rect)\n      })\n\n      it('sets the css display property to none', () => {\n        const rect = new Rect()\n        expect(rect.hide().css('display')).toBe('none')\n      })\n    })\n\n    describe('visible()', () => {\n      it('returns true if display is not none', () => {\n        const rect = new Rect()\n        expect(rect.show().visible()).toBe(true)\n        expect(rect.css('display', 'block').visible()).toBe(true)\n      })\n\n      it('returns false if display is none', () => {\n        const rect = new Rect()\n        expect(rect.hide().visible()).toBe(false)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/optional/data.js",
    "content": "/* globals describe, expect, it */\n\nimport { Rect } from '../../../../src/main.js'\n\ndescribe('data.js', () => {\n  describe('Dom', () => {\n    describe('data()', () => {\n      describe('as getter', () => {\n        it('returns all data as object', () => {\n          const rect = new Rect({\n            'data-fill': 'none',\n            'data-outline-width': '1px',\n            'data-stroke': 'none'\n          })\n          expect(rect.data()).toEqual({\n            fill: 'none',\n            'outline-width': '1px',\n            stroke: 'none'\n          })\n        })\n\n        it('returns an object with selected data properties', () => {\n          const rect = new Rect({\n            'data-fill': 'none',\n            'data-outline-width': '1px',\n            'data-stroke': 'none'\n          })\n          expect(rect.data(['fill', 'stroke'])).toEqual({\n            fill: 'none',\n            stroke: 'none'\n          })\n        })\n\n        it('returns a single property with property name given', () => {\n          const rect = new Rect({\n            'data-fill': 'none',\n            'data-outline-width': '1px',\n            'data-stroke': 'none'\n          })\n          expect(rect.data('fill')).toBe('none')\n        })\n\n        it('returns undefined if data property is not set', () => {\n          const rect = new Rect({\n            'data-fill': 'none',\n            'data-outline-width': '1px',\n            'data-stroke': 'none'\n          })\n          expect(rect.data('outline-color')).toBe(undefined)\n        })\n      })\n\n      describe('as setter', () => {\n        it('returns itself', () => {\n          const rect = new Rect({\n            'data-fill': 'none',\n            'data-outline-width': '1px',\n            'data-stroke': 'none'\n          })\n          expect(rect.data('fill', 'black')).toBe(rect)\n        })\n\n        it('adds a data property', () => {\n          const rect = new Rect({\n            'data-fill': 'none',\n            'data-outline-width': '1px',\n            'data-stroke': 'none'\n          })\n          expect(rect.data('stroke-width', '2px').data('stroke-width')).toBe(\n            '2px'\n          )\n        })\n\n        it('changes a data property', () => {\n          const rect = new Rect({\n            'data-fill': 'none',\n            'data-outline-width': '1px',\n            'data-stroke': 'none'\n          })\n          expect(rect.data('fill', 'black').data('fill')).toBe('black')\n        })\n\n        it('sets an object of properties', () => {\n          const rect = new Rect()\n          expect(rect.data({ fill: 'none', stroke: 'none' }).data()).toEqual({\n            fill: 'none',\n            stroke: 'none'\n          })\n        })\n\n        it('removes property if null is passed as value', () => {\n          const rect = new Rect({\n            'data-fill': 'none',\n            'data-outline-width': '1px',\n            'data-stroke': 'none'\n          })\n          expect(rect.data('fill', null).data('fill')).toBe(undefined)\n        })\n\n        it('removes property if null is passed as part of object', () => {\n          const rect = new Rect({\n            'data-fill': 'none',\n            'data-outline-width': '1px',\n            'data-stroke': 'none'\n          })\n          expect(rect.data({ fill: null, stroke: 'black' }).data('fill')).toBe(\n            undefined\n          )\n        })\n\n        it('converts everything except number and strings to JSON', () => {\n          const rect = new Rect()\n          expect(rect.data('fill', { some: 'object' }).attr('data-fill')).toBe(\n            JSON.stringify({ some: 'object' })\n          )\n          expect(rect.data('fill', 5).attr('data-fill')).toBe(5)\n          expect(rect.data('fill', 'string').attr('data-fill')).toBe('string')\n        })\n\n        it('does not convert to json with third parameter true', () => {\n          const rect = new Rect()\n          expect(\n            rect.data('fill', { some: 'object' }, true).attr('data-fill')\n          ).toBe({}.toString())\n        })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/optional/memory.js",
    "content": "/* globals describe, expect, it */\n\nimport { Rect } from '../../../../src/main.js'\n\ndescribe('memory.js', () => {\n  describe('Dom', () => {\n    describe('memory()', () => {\n      it('returns all memory as object', () => {\n        const rect = new Rect().remember({\n          fill: 'none',\n          'outline-width': '1px',\n          stroke: 'none'\n        })\n        expect(rect.memory()).toEqual({\n          fill: 'none',\n          'outline-width': '1px',\n          stroke: 'none'\n        })\n      })\n    })\n\n    describe('remember()', () => {\n      describe('as getter', () => {\n        it('returns a single property with property name given', () => {\n          const rect = new Rect().remember({\n            fill: 'none',\n            'outline-width': '1px',\n            stroke: 'none'\n          })\n          expect(rect.remember('fill')).toBe('none')\n        })\n\n        it('returns undefined if memory property is not set', () => {\n          const rect = new Rect().remember({\n            fill: 'none',\n            'outline-width': '1px',\n            stroke: 'none'\n          })\n          expect(rect.remember('outline-color')).toBe(undefined)\n        })\n      })\n\n      describe('as setter', () => {\n        it('returns itself', () => {\n          const rect = new Rect().remember({\n            fill: 'none',\n            'outline-width': '1px',\n            stroke: 'none'\n          })\n          expect(rect.remember('fill', 'black')).toBe(rect)\n        })\n\n        it('adds a memory property', () => {\n          const rect = new Rect().remember({\n            fill: 'none',\n            'outline-width': '1px',\n            stroke: 'none'\n          })\n          expect(\n            rect.remember('stroke-width', '2px').remember('stroke-width')\n          ).toBe('2px')\n        })\n\n        it('changes a memory property', () => {\n          const rect = new Rect().remember({\n            fill: 'none',\n            'outline-width': '1px',\n            stroke: 'none'\n          })\n          expect(rect.remember('fill', 'black').remember('fill')).toBe('black')\n        })\n\n        it('sets an object of properties', () => {\n          const rect = new Rect()\n          expect(\n            rect.remember({ fill: 'none', stroke: 'none' }).memory()\n          ).toEqual({ fill: 'none', stroke: 'none' })\n        })\n      })\n    })\n\n    describe('forget()', () => {\n      it('removes property', () => {\n        const rect = new Rect().remember({\n          fill: 'none',\n          'outline-width': '1px',\n          stroke: 'none'\n        })\n        expect(rect.forget('fill').remember('fill')).toBe(undefined)\n      })\n\n      it('removes multiple properties', () => {\n        const rect = new Rect().remember({\n          fill: 'none',\n          'outline-width': '1px',\n          stroke: 'none'\n        })\n        expect(rect.forget('fill', 'stroke').memory()).toEqual({\n          'outline-width': '1px'\n        })\n      })\n\n      it('erases the whole object with nothing passed', () => {\n        const rect = new Rect().remember({\n          fill: 'none',\n          'outline-width': '1px',\n          stroke: 'none'\n        })\n        expect(rect.forget().memory()).toEqual({})\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/optional/sugar.js",
    "content": "/* globals describe, expect, it, beforeEach, spyOn, container */\n\nimport { Rect, SVG, Matrix, Ellipse, Gradient } from '../../../../src/main.js'\n\ndescribe('sugar.js', () => {\n  describe('Element/Runner', () => {\n    describe('fill()', () => {\n      describe('as setter', () => {\n        it('returns itself', () => {\n          const rect = new Rect()\n          expect(rect.fill('black')).toBe(rect)\n        })\n\n        it('sets a fill color', () => {\n          const rect = new Rect()\n          expect(rect.fill('black').attr('fill')).toBe('black')\n        })\n\n        it('sets a fill pattern when pattern given', () => {\n          const canvas = SVG().addTo(container)\n          const pattern = canvas.pattern()\n          const rect = canvas.rect(100, 100)\n          expect(rect.fill(pattern).attr('fill')).toBe(pattern.url())\n        })\n\n        it('sets a fill pattern when image given', () => {\n          const canvas = SVG().addTo(container)\n          const image = canvas.image('spec/fictures/pixel.png')\n          const rect = canvas.rect(100, 100)\n          expect(rect.fill(image).attr('fill')).toBe(image.parent().url())\n        })\n\n        it('sets an object of fill properties', () => {\n          const rect = new Rect()\n          expect(\n            rect\n              .fill({\n                color: 'black',\n                opacity: 0.5,\n                rule: 'even-odd'\n              })\n              .attr()\n          ).toEqual({\n            fill: 'black',\n            'fill-opacity': 0.5,\n            'fill-rule': 'even-odd'\n          })\n        })\n      })\n\n      describe('as getter', () => {\n        it('returns fill color', () => {\n          const rect = new Rect().fill('black')\n          expect(rect.fill()).toBe('black')\n        })\n\n        it('returns default fill color if nothing is set', () => {\n          const rect = new Rect()\n          expect(rect.fill()).toBe('#000000')\n        })\n      })\n    })\n\n    describe('stroke()', () => {\n      describe('as setter', () => {\n        it('returns itself', () => {\n          const rect = new Rect()\n          expect(rect.stroke('black')).toBe(rect)\n        })\n\n        it('sets a stroke color', () => {\n          const rect = new Rect()\n          expect(rect.stroke('black').attr('stroke')).toBe('black')\n        })\n\n        it('sets a stroke pattern when pattern given', () => {\n          const canvas = SVG().addTo(container)\n          const pattern = canvas.pattern()\n          const rect = canvas.rect(100, 100)\n          expect(rect.stroke(pattern).attr('stroke')).toBe(pattern.url())\n        })\n\n        it('sets a stroke pattern when image given', () => {\n          const canvas = SVG().addTo(container)\n          const image = canvas.image('spec/fictures/pixel.png')\n          const rect = canvas.rect(100, 100)\n          expect(rect.stroke(image).attr('stroke')).toBe(image.parent().url())\n        })\n\n        it('sets an object of stroke properties', () => {\n          const rect = new Rect()\n          expect(\n            rect\n              .stroke({\n                color: 'black',\n                width: 2,\n                opacity: 0.5,\n                linecap: 'butt',\n                linejoin: 'miter',\n                miterlimit: 10,\n                dasharray: '2 2',\n                dashoffset: 15\n              })\n              .attr()\n          ).toEqual({\n            stroke: 'black',\n            'stroke-width': 2,\n            'stroke-opacity': 0.5,\n            'stroke-linecap': 'butt',\n            'stroke-linejoin': 'miter',\n            'stroke-miterlimit': 10,\n            'stroke-dasharray': '2 2',\n            'stroke-dashoffset': 15\n          })\n        })\n\n        it('sets stroke dasharray with array passed', () => {\n          const rect = new Rect().stroke({ dasharray: [2, 2] })\n          expect(rect.attr()).toEqual({ 'stroke-dasharray': '2 2' })\n        })\n      })\n\n      describe('as getter', () => {\n        it('returns stroke color', () => {\n          const rect = new Rect().stroke('black')\n          expect(rect.stroke()).toBe('black')\n        })\n\n        it('returns default stroke color if nothing is set', () => {\n          const rect = new Rect()\n          expect(rect.stroke()).toBe('#000000')\n        })\n      })\n    })\n\n    describe('matrix()', () => {\n      it('gets the matrix with no argument passed', () => {\n        const rect = new Rect().transform(new Matrix(1, 0, 1, 1, 1, 0))\n        expect(rect.matrix()).toEqual(new Matrix(1, 0, 1, 1, 1, 0))\n      })\n\n      it('sets the matrix if matrix given', () => {\n        const rect = new Rect().matrix(new Matrix(1, 0, 1, 1, 1, 0))\n        expect(rect.matrix()).toEqual(new Matrix(1, 0, 1, 1, 1, 0))\n      })\n\n      it('sets the matrix with 6 arguments given', () => {\n        const rect = new Rect().matrix(1, 0, 1, 1, 1, 0)\n        expect(rect.matrix()).toEqual(new Matrix(1, 0, 1, 1, 1, 0))\n      })\n    })\n\n    describe('rotate()', function () {\n      it('redirects to transform()', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.rotate(1, 2, 3)\n        expect(spy).toHaveBeenCalledWith({ rotate: 1, ox: 2, oy: 3 }, true)\n      })\n    })\n\n    describe('skew()', function () {\n      it('redirects to transform() with no argument', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.skew()\n        expect(spy).toHaveBeenCalledWith(\n          { skew: [undefined, undefined], ox: undefined, oy: undefined },\n          true\n        )\n      })\n\n      it('redirects to transform() with one argument', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.skew(5)\n        expect(spy).toHaveBeenCalledWith(\n          { skew: 5, ox: undefined, oy: undefined },\n          true\n        )\n      })\n\n      it('redirects to transform() with two argument', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.skew(5, 6)\n        expect(spy).toHaveBeenCalledWith(\n          { skew: [5, 6], ox: undefined, oy: undefined },\n          true\n        )\n      })\n\n      it('redirects to transform() with three arguments', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.skew(5, 6, 7)\n        expect(spy).toHaveBeenCalledWith({ skew: 5, ox: 6, oy: 7 }, true)\n      })\n\n      it('redirects to transform() with four arguments', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.skew(5, 6, 7, 8)\n        expect(spy).toHaveBeenCalledWith({ skew: [5, 6], ox: 7, oy: 8 }, true)\n      })\n    })\n\n    describe('shear', () => {\n      it('redirects to transform()', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.shear(1, 2, 3)\n        expect(spy).toHaveBeenCalledWith({ shear: 1, ox: 2, oy: 3 }, true)\n      })\n    })\n\n    describe('scale()', function () {\n      it('redirects to transform() with no argument', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.scale()\n        expect(spy).toHaveBeenCalledWith(\n          { scale: [undefined, undefined], ox: undefined, oy: undefined },\n          true\n        )\n      })\n\n      it('redirects to transform() with one argument', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.scale(5)\n        expect(spy).toHaveBeenCalledWith(\n          { scale: 5, ox: undefined, oy: undefined },\n          true\n        )\n      })\n\n      it('redirects to transform() with two argument', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.scale(5, 6)\n        expect(spy).toHaveBeenCalledWith(\n          { scale: [5, 6], ox: undefined, oy: undefined },\n          true\n        )\n      })\n\n      it('redirects to transform() with three arguments', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.scale(5, 6, 7)\n        expect(spy).toHaveBeenCalledWith({ scale: 5, ox: 6, oy: 7 }, true)\n      })\n\n      it('redirects to transform() with four arguments', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.scale(5, 6, 7, 8)\n        expect(spy).toHaveBeenCalledWith({ scale: [5, 6], ox: 7, oy: 8 }, true)\n      })\n    })\n\n    describe('translate()', function () {\n      it('redirects to transform()', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.translate(1, 2)\n        expect(spy).toHaveBeenCalledWith({ translate: [1, 2] }, true)\n      })\n    })\n\n    describe('relative()', () => {\n      it('redirects to transform()', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.relative(1, 2)\n        expect(spy).toHaveBeenCalledWith({ relative: [1, 2] }, true)\n      })\n    })\n\n    describe('flip()', function () {\n      it('redirects to transform()', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.flip('x', 2)\n        expect(spy).toHaveBeenCalledWith({ flip: 'x', origin: 2 }, true)\n      })\n\n      it('sets flip to \"both\" when calling without anything', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.flip()\n        expect(spy).toHaveBeenCalledWith(\n          { flip: 'both', origin: 'center' },\n          true\n        )\n      })\n\n      // this works because only x and y are valid flip values. Everything else flips on both axis\n      it('sets flip to both and origin to number when called with origin only', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'transform')\n        rect.flip(5)\n        expect(spy).toHaveBeenCalledWith({ flip: 'both', origin: 5 }, true)\n      })\n    })\n\n    describe('opacity()', function () {\n      it('redirects to attr() directly', function () {\n        const rect = new Rect()\n        const spy = spyOn(rect, 'attr')\n        rect.opacity(0.5)\n        expect(spy).toHaveBeenCalledWith('opacity', 0.5)\n      })\n    })\n\n    describe('font()', function () {\n      const txt = 'Some text'\n      let canvas, text\n\n      beforeEach(() => {\n        canvas = SVG().addTo(container)\n        text = canvas.text(txt)\n      })\n\n      it('sets leading when given', function () {\n        const spy = spyOn(text, 'leading')\n        text.font({ leading: 3 })\n        expect(spy).toHaveBeenCalledWith(3)\n      })\n\n      it('sets text-anchor when anchor given', function () {\n        const spy = spyOn(text, 'attr')\n        text.font({ anchor: 'start' })\n        expect(spy).toHaveBeenCalledWith('text-anchor', 'start')\n      })\n\n      it('sets all font properties via attr()', function () {\n        const spy = spyOn(text, 'attr')\n        text.font({\n          size: 20,\n          family: 'Verdana',\n          weight: 'bold',\n          stretch: 'wider',\n          variant: 'small-caps',\n          style: 'italic'\n        })\n        expect(spy).toHaveBeenCalledWith('font-size', 20)\n        expect(spy).toHaveBeenCalledWith('font-family', 'Verdana')\n        expect(spy).toHaveBeenCalledWith('font-weight', 'bold')\n        expect(spy).toHaveBeenCalledWith('font-stretch', 'wider')\n        expect(spy).toHaveBeenCalledWith('font-variant', 'small-caps')\n        expect(spy).toHaveBeenCalledWith('font-style', 'italic')\n      })\n\n      it('redirects all other stuff directly to attr()', function () {\n        const spy = spyOn(text, 'attr')\n        text.font({\n          foo: 'bar',\n          bar: 'baz'\n        })\n        expect(spy).toHaveBeenCalledWith('foo', 'bar')\n        expect(spy).toHaveBeenCalledWith('bar', 'baz')\n      })\n\n      it('sets key value pair when called with 2 parameters', function () {\n        const spy = spyOn(text, 'attr')\n        text.font('size', 20)\n        expect(spy).toHaveBeenCalledWith('font-size', 20)\n      })\n\n      it('gets value if called with one parameter', function () {\n        const spy = spyOn(text, 'attr')\n        text.font('size')\n        expect(spy).toHaveBeenCalledWith('font-size', undefined)\n      })\n    })\n  })\n\n  describe('radius()', () => {\n    describe('Rect', () => {\n      it('sets rx and ry on the rectangle', () => {\n        const rect = new Rect().radius(5, 10)\n        expect(rect.attr()).toEqual({ rx: 5, ry: 10 })\n      })\n    })\n\n    describe('Ellipse', () => {\n      it('sets rx and ry on the rectangle', () => {\n        const rect = new Ellipse().radius(5, 10)\n        expect(rect.attr()).toEqual({ rx: 5, ry: 10 })\n      })\n    })\n\n    describe('radialGradient', () => {\n      it('sets rx and ry on the rectangle', () => {\n        const rect = new Gradient('radial').radius(5)\n        expect(rect.attr()).toEqual({ r: 5 })\n      })\n    })\n  })\n\n  describe('Path', () => {\n    describe('length', () => {\n      it('returns the full length of the path', () => {\n        const canvas = SVG().addTo(container)\n        const path = canvas.path('M0 0 L 0 5')\n        expect(path.length()).toBe(5)\n      })\n    })\n\n    describe('pointAt', () => {\n      it('returns a point at a specific length', () => {\n        const canvas = SVG().addTo(container)\n        const path = canvas.path('M0 0 L 0 5')\n        const point = path.pointAt(3)\n\n        expect(point.x).toBeCloseTo(0) // chrome has rounding issues -.-\n        expect(point.y).toBe(3)\n      })\n    })\n  })\n\n  describe('events', () => {\n    ;[\n      'click',\n      'dblclick',\n      'mousedown',\n      'mouseup',\n      'mouseover',\n      'mouseout',\n      'mousemove',\n      'mouseenter',\n      'mouseleave',\n      'touchstart',\n      'touchmove',\n      'touchleave',\n      'touchend',\n      'touchcancel'\n    ].forEach((ev) => {\n      describe(ev, () => {\n        it('calls on() with the eventname set to ' + ev, () => {\n          const rect = new Rect()\n          const spy = spyOn(rect, 'on')\n          const fn = () => {}\n          rect[ev](fn)\n          expect(spy).toHaveBeenCalledWith(ev, fn)\n        })\n\n        it(\n          'calls off() with the eventname set to ' +\n            ev +\n            ' when null is passed as second argument',\n          () => {\n            const rect = new Rect()\n            const spy = spyOn(rect, 'off')\n            rect[ev](null)\n            expect(spy).toHaveBeenCalledWith(ev)\n          }\n        )\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/modules/optional/transform.js",
    "content": "/* globals describe, expect, it, spyOn, container */\n\nimport { Rect, Matrix, SVG } from '../../../../src/main.js'\n\ndescribe('transform.js', () => {\n  describe('untransform()', () => {\n    it('returns itself', () => {\n      const rect = new Rect()\n      expect(rect.untransform()).toBe(rect)\n    })\n\n    it('deletes the transform attribute', () => {\n      const rect = new Rect()\n      expect(rect.untransform().attr('transform')).toBe(undefined)\n    })\n  })\n\n  describe('matrixify()', () => {\n    it('reduces all transformations of the transform list into one matrix - 1', () => {\n      const rect = new Rect().attr('transform', 'matrix(1, 0, 1, 1, 0, 1)')\n      expect(rect.matrixify()).toEqual(new Matrix(1, 0, 1, 1, 0, 1))\n    })\n\n    it('reduces all transformations of the transform list into one matrix - 2', () => {\n      const rect = new Rect().attr('transform', 'translate(10, 20) rotate(45)')\n      expect(rect.matrixify()).toEqual(\n        new Matrix().rotate(45).translate(10, 20)\n      )\n    })\n\n    it('reduces all transformations of the transform list into one matrix - 3', () => {\n      const rect = new Rect().attr(\n        'transform',\n        'translate(10, 20) rotate(45) skew(1,2) skewX(10) skewY(20)'\n      )\n      expect(rect.matrixify()).toEqual(\n        new Matrix().skewY(20).skewX(10).skew(1, 2).rotate(45).translate(10, 20)\n      )\n    })\n  })\n\n  describe('toParent()', () => {\n    it('returns itself', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n      const rect = g.rect(100, 100)\n      expect(rect.toParent(canvas)).toBe(rect)\n    })\n\n    it('does nothing if the parent is the the current element', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n      const parent = g.parent()\n      const position = g.position()\n      g.toParent(g)\n      expect(g.parent()).toBe(parent)\n      expect(g.position()).toBe(position)\n    })\n\n    it('moves an element to a different container without changing its visual representation - 1', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group().matrix(1, 0, 1, 1, 0, 1)\n      const rect = g.rect(100, 100)\n      rect.toParent(canvas)\n      expect(rect.matrix()).toEqual(new Matrix(1, 0, 1, 1, 0, 1))\n      expect(rect.parent()).toBe(canvas)\n    })\n\n    it('moves an element to a different container without changing its visual representation - 2', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group().translate(10, 20)\n      const rect = g.rect(100, 100)\n      const g2 = canvas.group().rotate(10)\n      rect.toParent(g2)\n      const actual = rect.matrix()\n      const expected = new Matrix().translate(10, 20).rotate(-10)\n\n      // funny enough the dom seems to shorten the floats and precision gets lost\n      ;[...'abcdef'].forEach((prop) =>\n        expect(actual[prop]).toBeCloseTo(expected[prop], 5)\n      )\n    })\n\n    it('inserts the element at the specified position', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group()\n      const rect = g.rect(100, 100)\n      canvas.rect(100, 100)\n      canvas.rect(100, 100)\n      expect(rect.toParent(canvas, 2).position()).toBe(2)\n    })\n  })\n\n  describe('toRoot()', () => {\n    it('calls toParent() with root node', () => {\n      const canvas = SVG().addTo(container)\n      const g = canvas.group().matrix(1, 0, 1, 1, 0, 1)\n      const rect = g.rect(100, 100)\n      const spy = spyOn(rect, 'toParent')\n      rect.toRoot(3)\n      expect(spy).toHaveBeenCalledWith(canvas, 3)\n    })\n  })\n\n  describe('transform()', () => {\n    it('acts as full getter with no argument', () => {\n      const rect = new Rect().attr('transform', 'translate(10, 20) rotate(45)')\n      const actual = rect.transform()\n      const expected = new Matrix().rotate(45).translate(10, 20).decompose()\n\n      expect(actual).toEqual(expected)\n    })\n\n    it('returns a single transformation value when string was passed', () => {\n      const rect = new Rect().attr('transform', 'translate(10, 20) rotate(45)')\n      expect(rect.transform('rotate')).toBe(45)\n      expect(rect.transform('translateX')).toBe(10)\n      expect(rect.transform('translateY')).toBe(20)\n    })\n\n    it('sets the transformation with an object', () => {\n      const rect = new Rect().transform({ rotate: 45, translate: [10, 20] })\n      expect(rect.transform('rotate')).toBe(45)\n      expect(rect.transform('translateX')).toBe(10)\n      expect(rect.transform('translateY')).toBe(20)\n    })\n\n    it('performs a relative transformation with flag=true', () => {\n      const rect = new Rect()\n        .transform({ rotate: 45, translate: [10, 20] })\n        .transform({ rotate: 10 }, true)\n      expect(rect.transform('rotate')).toBeCloseTo(55, 5) // rounding errors\n      expect(rect.transform('translateX')).toBe(10)\n      expect(rect.transform('translateY')).toBe(20)\n    })\n\n    it('performs a relative transformation with flag=other matrix', () => {\n      const rect = new Rect()\n        .transform({ rotate: 45, translate: [10, 20] })\n        .transform({ rotate: 10 }, new Matrix().rotate(30))\n      expect(rect.transform('rotate')).toBeCloseTo(40, 5) // rounding errors\n      expect(rect.transform('translateX')).toBe(0)\n      expect(rect.transform('translateY')).toBe(0)\n    })\n\n    it('performs a relative transformation with flag=other element', () => {\n      const referenceElement = new Rect().transform({ rotate: 30 })\n      const rect = new Rect()\n        .transform({ rotate: 45, translate: [10, 20] })\n        .transform({ rotate: 10 }, referenceElement)\n      expect(rect.transform('rotate')).toBeCloseTo(40, 5) // rounding errors\n      expect(rect.transform('translateX')).toBe(0)\n      expect(rect.transform('translateY')).toBe(0)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/types/Base.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport Base from '../../../src/types/Base.js'\n\nconst { any } = jasmine\n\ndescribe('Base.js', () => {\n  it('creates a new object of type Base', () => {\n    expect(new Base()).toEqual(any(Base))\n  })\n})\n"
  },
  {
    "path": "spec/spec/types/Box.js",
    "content": "/* globals describe, expect, it, beforeEach, afterEach, spyOn, jasmine, container */\n\nimport { Box, Matrix, Rect, G, makeInstance as SVG } from '../../../src/main.js'\nimport { withWindow, getWindow } from '../../../src/utils/window.js'\nimport { isNulledBox, domContains } from '../../../src/types/Box.js'\n\nconst { any, objectContaining } = jasmine\n\n// const getBody = () => {\n//   const win = getWindow()\n//   return win.document.body || win.document.documentElement\n// }\n\ndescribe('Box.js', () => {\n  describe('isNulledBox', () => {\n    it('returns true if x, y, with and height is 0', () => {\n      expect(isNulledBox({ x: 0, y: 0, width: 0, height: 0 })).toBe(true)\n    })\n\n    it('returns false if one or more of x, y, with and height is not 0', () => {\n      expect(isNulledBox({ x: 0, y: 0, width: 0, height: 1 })).toBe(false)\n      expect(isNulledBox({ x: 0, y: 1, width: 0, height: 1 })).toBe(false)\n    })\n  })\n\n  describe('domContains()', () => {\n    describe('with native function', () => {\n      it('returns true if node is in the dom', () => {\n        expect(domContains(container)).toBe(true)\n      })\n\n      it('returns false if node is not in the dom', () => {\n        const g = new G()\n        const rect = new Rect().addTo(g)\n        expect(domContains(rect.node)).toBe(false)\n      })\n    })\n\n    describe('with polyfill', () => {\n      let containsBackup\n      beforeEach(() => {\n        containsBackup = getWindow().document.documentElement.contains\n        getWindow().document.documentElement.contains = null\n      })\n\n      afterEach(() => {\n        getWindow().document.documentElement.contains = containsBackup\n      })\n\n      it('returns true if node is in the dom', () => {\n        expect(domContains(container)).toBe(true)\n      })\n\n      it('returns false if node is not in the dom', () => {\n        const g = new G()\n        const rect = new Rect().addTo(g)\n        expect(domContains(rect.node)).toBe(false)\n      })\n    })\n  })\n\n  describe('Box', () => {\n    describe('()', () => {\n      it('creates a new Box with default attributes', () => {\n        const box = new Box()\n        expect(box).toEqual(any(Box))\n        expect(box).toEqual(\n          objectContaining({\n            width: 0,\n            height: 0,\n            x: 0,\n            y: 0,\n            w: 0,\n            h: 0,\n            cx: 0,\n            cy: 0,\n            x2: 0,\n            y2: 0\n          })\n        )\n      })\n    })\n\n    describe('init()', () => {\n      it('inits or reinits the box according to input', () => {\n        expect(new Box().init(1, 2, 3, 4).toArray()).toEqual([1, 2, 3, 4])\n      })\n\n      it('works with array input', () => {\n        expect(new Box().init([1, 2, 3, 4]).toArray()).toEqual([1, 2, 3, 4])\n      })\n\n      it('works with 3 arguments as input', () => {\n        expect(new Box().init(1, 2, 3, 4).toArray()).toEqual([1, 2, 3, 4])\n      })\n\n      it('works with string input', () => {\n        expect(new Box().init('1,2,3,4').toArray()).toEqual([1, 2, 3, 4])\n      })\n\n      it('creates a new box from parsed string with exponential values', function () {\n        expect(new Box().init('-1.12e1 1e-2 +2e2 +.3e+4').toArray()).toEqual([\n          -11.2, 0.01, 200, 3000\n        ])\n      })\n\n      it('works with object input', () => {\n        expect(\n          new Box().init({ x: 1, y: 2, width: 3, height: 4 }).toArray()\n        ).toEqual([1, 2, 3, 4])\n      })\n\n      it('calculates all derived values correctly', () => {\n        expect(new Box().init(2, 4, 6, 8)).toEqual(\n          objectContaining({\n            cx: 5,\n            cy: 8,\n            x2: 8,\n            y2: 12,\n            w: 6,\n            h: 8\n          })\n        )\n      })\n\n      it('can handle input with left instead of x and top instead of y', () => {\n        expect(\n          new Box().init({ left: 1, top: 2, width: 3, height: 4 }).toArray()\n        ).toEqual([1, 2, 3, 4])\n      })\n    })\n\n    describe('merge()', () => {\n      it('merges various bounding boxes', () => {\n        var box1 = new Box(50, 50, 100, 100)\n        var box2 = new Box(300, 400, 100, 100)\n        var box3 = new Box(500, 100, 100, 100)\n        var merged = box1.merge(box2).merge(box3)\n\n        expect(merged.toArray()).toEqual([50, 50, 550, 450])\n      })\n\n      it('returns a new instance', () => {\n        var box1 = new Box(50, 50, 100, 100)\n        var box2 = new Box(300, 400, 100, 100)\n        var merged = box1.merge(box2)\n\n        expect(merged).toEqual(any(Box))\n      })\n    })\n\n    describe('transform()', () => {\n      it('transforms the box with given matrix', () => {\n        var box1 = new Box(50, 50, 100, 100).transform(\n          new Matrix(1, 0, 0, 1, 20, 20)\n        )\n        var box2 = new Box(50, 50, 100, 100).transform(\n          new Matrix(2, 0, 0, 2, 0, 0)\n        )\n        var box3 = new Box(-200, -200, 100, 100).transform(\n          new Matrix(1, 0, 0, 1, -20, -20)\n        )\n\n        expect(box1.toArray()).toEqual([70, 70, 100, 100])\n        expect(box2.toArray()).toEqual([100, 100, 200, 200])\n        expect(box3.toArray()).toEqual([-220, -220, 100, 100])\n      })\n\n      it('also works with matrix like input', () => {\n        var box1 = new Box(50, 50, 100, 100).transform(\n          new Matrix(1, 0, 0, 1, 20, 20).toArray()\n        )\n        var box2 = new Box(50, 50, 100, 100).transform(\n          new Matrix(2, 0, 0, 2, 0, 0).toArray()\n        )\n        var box3 = new Box(-200, -200, 100, 100).transform(\n          new Matrix(1, 0, 0, 1, -20, -20).toArray()\n        )\n\n        expect(box1.toArray()).toEqual([70, 70, 100, 100])\n        expect(box2.toArray()).toEqual([100, 100, 200, 200])\n        expect(box3.toArray()).toEqual([-220, -220, 100, 100])\n      })\n    })\n\n    describe('addOffset()', () => {\n      it('returns a new instance', () => {\n        withWindow({ pageXOffset: 50, pageYOffset: 25 }, () => {\n          const box = new Box(100, 100, 100, 100)\n          const box2 = box.addOffset()\n\n          expect(box2).toEqual(any(Box))\n          expect(box2).not.toBe(box)\n        })\n      })\n\n      it('adds the current page offset to the box', () => {\n        withWindow({ pageXOffset: 50, pageYOffset: 25 }, () => {\n          const box = new Box(100, 100, 100, 100).addOffset()\n\n          expect(box.toArray()).toEqual([150, 125, 100, 100])\n        })\n      })\n    })\n\n    describe('toString()', () => {\n      it('returns a string representation of the box', () => {\n        expect(new Box(1, 2, 3, 4).toString()).toBe('1 2 3 4')\n      })\n    })\n\n    describe('toArray()', () => {\n      it('returns an array representation of the box', () => {\n        expect(new Box(1, 2, 3, 4).toArray()).toEqual([1, 2, 3, 4])\n      })\n    })\n\n    describe('isNulled()', () => {\n      it('checks if the box consists of only zeros', () => {\n        expect(new Box().isNulled()).toBe(true)\n        expect(new Box(1, 2, 3, 4).isNulled()).toBe(false)\n      })\n    })\n  })\n\n  describe('Element', () => {\n    describe('bbox()', () => {\n      it('returns the bounding box of the element', () => {\n        const canvas = SVG().addTo(container)\n        const rect = new Rect().size(100, 200).move(20, 30).addTo(canvas)\n\n        expect(rect.bbox()).toEqual(any(Box))\n        expect(rect.bbox().toArray()).toEqual([20, 30, 100, 200])\n      })\n\n      it('returns the bounding box of the element even if the node is not in the dom', () => {\n        const rect = new Rect().size(100, 200).move(20, 30)\n        expect(rect.bbox().toArray()).toEqual([20, 30, 100, 200])\n      })\n\n      it('throws when it is not possible to get a bbox', () => {\n        const spy = spyOn(\n          getWindow().SVGGraphicsElement.prototype,\n          'getBBox'\n        ).and.callFake(() => {\n          throw new Error('No BBox for you')\n        })\n        const rect = new Rect()\n        expect(() => rect.bbox()).toThrow()\n        expect(spy).toHaveBeenCalled()\n      })\n    })\n\n    describe('rbox()', () => {\n      it('returns the BoundingClientRect of the element', () => {\n        const canvas = SVG().addTo(container)\n        const rect = new Rect()\n          .size(100, 200)\n          .move(20, 30)\n          .addTo(canvas)\n          .attr('transform', new Matrix({ scale: 2, translate: [40, 50] }))\n\n        expect(rect.rbox()).toEqual(any(Box))\n        expect(rect.rbox().toArray()).toEqual([80, 110, 200, 400])\n      })\n\n      it('returns the rbox box of the element in the coordinate system of the passed element', () => {\n        const canvas = SVG().addTo(container)\n        const group = canvas.group().translate(1, 1)\n        const rect = new Rect()\n          .size(100, 200)\n          .move(20, 30)\n          .addTo(canvas)\n          .attr('transform', new Matrix({ scale: 2, translate: [40, 50] }))\n\n        expect(rect.rbox(group)).toEqual(any(Box))\n        expect(rect.rbox(group).toArray()).toEqual([79, 109, 200, 400])\n      })\n\n      // svgdom actually only throws here because a new Rect without dimensions has no bounding box\n      // so in case you would create a rect with with and height this test would fail because\n      // svgdom actually can calculate an rbox for the element\n      // in that case we have to change the test like above so that the getBoundingClientRect call is mocked with a spy\n      it('throws when element is not in dom', () => {\n        expect(() => new Rect().rbox()).toThrow()\n      })\n    })\n\n    describe('inside()', () => {\n      it('checks if a point is in the elements borders', () => {\n        const canvas = SVG().addTo(container)\n        const rect = canvas.rect(100, 100)\n        expect(rect.inside(50, 50)).toBe(true)\n        expect(rect.inside(101, 101)).toBe(false)\n      })\n    })\n\n    describe('viewbox()', () => {\n      it('sets the viewbox of the element', () => {\n        const canvas = SVG().addTo(container).viewbox(10, 10, 200, 200)\n        expect(canvas.attr('viewBox')).toEqual('10 10 200 200')\n      })\n\n      it('gets the viewbox of the element', () => {\n        const canvas = SVG().addTo(container).viewbox(10, 10, 200, 200)\n        expect(canvas.viewbox()).toEqual(any(Box))\n        expect(canvas.viewbox().toArray()).toEqual([10, 10, 200, 200])\n      })\n    })\n\n    describe('zoom()', () => {\n      it('zooms around the center by default', () => {\n        const canvas = SVG()\n          .size(100, 50)\n          .viewbox(0, 0, 100, 50)\n          .addTo(container)\n          .zoom(2)\n        expect(canvas.attr('viewBox')).toEqual('25 12.5 50 25')\n      })\n\n      it('zooms around a point', () => {\n        const canvas = SVG()\n          .size(100, 50)\n          .viewbox(0, 0, 100, 50)\n          .addTo(container)\n          .zoom(2, [0, 0])\n        expect(canvas.attr('viewBox')).toEqual('0 0 50 25')\n      })\n\n      it('gets the zoom', () => {\n        // We use a nested here because its actually harder to get a width and height for a nested svg because clientHeight\n        // is not available\n        const svg = SVG()\n          .size(100, 50)\n          .addTo(container)\n          .nested()\n          .size(100, 50)\n          .viewbox(0, 0, 100, 50)\n          .zoom(2)\n        expect(svg.zoom()).toEqual(2)\n      })\n\n      it('gets the zoom with clientHeight', () => {\n        const svg = SVG()\n          .css({ width: '100px', height: '50px' })\n          .addTo(container)\n          .viewbox(25, 12.5, 50, 25)\n\n        const node = svg.node\n\n        // svgdom doesn't support clientHeight\n        // so we mock it here\n        if (typeof node.clientHeight === 'undefined') {\n          node.clientHeight = 50\n          node.clientWidth = 100\n        }\n\n        expect(svg.zoom()).toEqual(2)\n      })\n\n      it('throws an error if it is impossible to get an absolute value', () => {\n        const svg = SVG()\n          .size(100, 50)\n          .addTo(container)\n          .nested()\n          .viewbox(0, 0, 100, 50)\n        expect(() => svg.zoom()).toThrowError(\n          'Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element'\n        )\n      })\n\n      it('handles zoom level 0 which is - which basically sets the viewbox to a very high value', () => {\n        const svg = SVG()\n          .size(100, 50)\n          .viewbox(0, 0, 100, 50)\n          .addTo(container)\n          .zoom(0)\n        expect(svg.zoom()).toBeCloseTo(0, 10)\n      })\n\n      it('handles zoom level 0 and can recover from it', () => {\n        const svg = SVG()\n          .size(100, 50)\n          .viewbox(0, 0, 100, 50)\n          .addTo(container)\n          .zoom(0)\n          .zoom(1)\n        expect(svg.zoom()).toBe(1)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/types/Color.js",
    "content": "/* globals describe, expect, it, beforeEach, spyOn, jasmine */\n\nimport { Color } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Color.js', () => {\n  var color\n\n  beforeEach(() => {\n    color = new Color({ r: 0, g: 102, b: 255 })\n  })\n\n  describe('()', () => {\n    describe('constructs a color from an object in the correct color space', () => {\n      it('rgb', () => {\n        const color = new Color({ r: 255, g: 0, b: 128 })\n        expect(color.r).toBe(255)\n        expect(color.g).toBe(0)\n        expect(color.b).toBe(128)\n        expect(color.space).toBe('rgb')\n      })\n\n      it('xyz', () => {\n        const color = new Color({ x: 255, y: 0, z: 128 })\n        expect(color.x).toBe(255)\n        expect(color.y).toBe(0)\n        expect(color.z).toBe(128)\n        expect(color.space).toBe('xyz')\n      })\n\n      it('hsl', () => {\n        const color = new Color({ h: 255, s: 0, l: 128 })\n        expect(color.h).toBe(255)\n        expect(color.s).toBe(0)\n        expect(color.l).toBe(128)\n        expect(color.space).toBe('hsl')\n      })\n\n      it('lab', () => {\n        const color = new Color({ l: 255, a: 0, b: 128 })\n        expect(color.l).toBe(255)\n        expect(color.a).toBe(0)\n        expect(color.b).toBe(128)\n        expect(color.space).toBe('lab')\n      })\n\n      it('lch', () => {\n        const color = new Color({ l: 255, c: 0, h: 128 })\n        expect(color.l).toBe(255)\n        expect(color.c).toBe(0)\n        expect(color.h).toBe(128)\n        expect(color.space).toBe('lch')\n      })\n\n      it('cmyk', () => {\n        const color2 = new Color({ c: 20, y: 15, m: 10, k: 5 })\n        expect(color2.c).toBe(20)\n        expect(color2.m).toBe(10)\n        expect(color2.y).toBe(15)\n        expect(color2.k).toBe(5)\n        expect(color2.space).toBe('cmyk')\n      })\n\n      it('and default to rgb when unknown parameters are passed', () => {\n        const color = new Color({ h: 12, b: 10, l: 100 })\n        expect(color.r).toBe(0)\n        expect(color.g).toBe(0)\n        expect(color.b).toBe(0)\n        expect(color.space).toBe('rgb')\n      })\n    })\n\n    it('constructs a color from an array', () => {\n      const color = new Color([30, 24, 50])\n      expect(color.r).toBe(30)\n      expect(color.g).toBe(24)\n      expect(color.b).toBe(50)\n      expect(color.space).toBe('rgb')\n    })\n\n    it('constructs a color from an array with space in array', () => {\n      const color = new Color([50, 50, 5, 'lab'])\n      expect(color.l).toBe(50)\n      expect(color.a).toBe(50)\n      expect(color.b).toBe(5)\n      expect(color.space).toBe('lab')\n    })\n\n    it('constructs a color from an array with space given', () => {\n      const color = new Color([50, 50, 5], 'lab')\n      expect(color.l).toBe(50)\n      expect(color.a).toBe(50)\n      expect(color.b).toBe(5)\n      expect(color.space).toBe('lab')\n    })\n\n    it('correclty parses an rgb string', () => {\n      const color = new Color('rgb(255,0,128)')\n      expect(color.r).toBe(255)\n      expect(color.g).toBe(0)\n      expect(color.b).toBe(128)\n    })\n\n    it('correclty parses a 3 digit hex string', () => {\n      color = new Color('#f06')\n      expect(color.r).toBe(255)\n      expect(color.g).toBe(0)\n      expect(color.b).toBe(102)\n    })\n\n    it('correclty parses a 6 digit hex string', () => {\n      color = new Color('#0066ff')\n      expect(color.r).toBe(0)\n      expect(color.g).toBe(102)\n      expect(color.b).toBe(255)\n    })\n\n    it('throws an error if unsupported string format was given', () => {\n      expect(() => new Color('#0066')).toThrowError(\n        \"Unsupported string format, can't construct Color\"\n      )\n    })\n  })\n\n  describe('input and output: Importing and exporting colors', () => {\n    describe('toHex()', () => {\n      it('returns a hex color', () => {\n        expect(color.toHex()).toBe('#0066ff')\n      })\n    })\n\n    describe('toRgb()', () => {\n      it('returns a rgb string color', () => {\n        expect(color.toRgb()).toBe('rgb(0,102,255)')\n      })\n    })\n  })\n\n  describe('color spaces: The color spaces supported by our library', () => {\n    describe('lab()', () => {\n      it('can convert rgb to lab', () => {\n        const color = new Color(255, 0, 128)\n        const lab = color.lab()\n        expect(lab.l).toBeCloseTo(54.88, 1)\n        expect(lab.a).toBeCloseTo(84.55, 1)\n        expect(lab.b).toBeCloseTo(4.065, 1)\n        expect(lab.space).toBe('lab')\n      })\n\n      it('can convert from lab to rgb', () => {\n        const lab = new Color(54.88, 84.55, 4.065, 'lab')\n        const rgb = lab.rgb()\n        expect(rgb.r).toBeCloseTo(255, 0)\n        expect(rgb.g).toBeCloseTo(0, 0)\n        expect(rgb.b).toBeCloseTo(128, 0)\n        expect(rgb.space).toBe('rgb')\n      })\n\n      it('is invertable', () => {\n        const { r, g, b } = new Color(255, 0, 128).lab().rgb()\n        expect(r).toBeCloseTo(255, 0)\n        expect(g).toBeCloseTo(0, 0)\n        expect(b).toBeCloseTo(128, 0)\n      })\n\n      it('handles black', () => {\n        const color = new Color(0, 0, 0).lab().rgb()\n        expect(color.r).toBeCloseTo(0, 0)\n        expect(color.g).toBeCloseTo(0, 0)\n        expect(color.b).toBeCloseTo(0, 0)\n        expect(color.toHex()).toBe('#000000')\n      })\n\n      it('handles white', () => {\n        const color = new Color(255, 255, 255).lab().rgb()\n        expect(color.r).toBeCloseTo(255, 0)\n        expect(color.g).toBeCloseTo(255, 0)\n        expect(color.b).toBeCloseTo(255, 0)\n        expect(color.toHex()).toBe('#ffffff')\n      })\n    })\n\n    describe('lch()', () => {\n      it('can convert rgb to lch', () => {\n        const color = new Color(255, 0, 128)\n        const lch = color.lch()\n        expect(lch.l).toBeCloseTo(54.88, 1)\n        expect(lch.c).toBeCloseTo(84.65, 1)\n        expect(lch.h).toBeCloseTo(2.75, 1)\n        expect(lch.space).toBe('lch')\n      })\n\n      it('can convert from lch to rgb', () => {\n        const lch = new Color(54.88, 84.65, 2.75, 'lch')\n        const rgb = lch.rgb()\n        expect(rgb.r).toBeCloseTo(255, 0)\n        expect(rgb.g).toBeCloseTo(0, 0)\n        expect(rgb.b).toBeCloseTo(128, 0)\n        expect(rgb.space).toBe('rgb')\n      })\n\n      it('is invertable', () => {\n        const { r, g, b } = new Color(255, 0, 128).lch().rgb()\n        expect(r).toBeCloseTo(255, 0)\n        expect(g).toBeCloseTo(0, 0)\n        expect(b).toBeCloseTo(128, 0)\n      })\n\n      it('handles black', () => {\n        const color = new Color(0, 0, 0).lch().rgb()\n        expect(color.r).toBeCloseTo(0, 0)\n        expect(color.g).toBeCloseTo(0, 0)\n        expect(color.b).toBeCloseTo(0, 0)\n        expect(color.toHex()).toBe('#000000')\n      })\n\n      it('handles white', () => {\n        const color = new Color(255, 255, 255).lch().rgb()\n        expect(color.r).toBeCloseTo(255, 0)\n        expect(color.g).toBeCloseTo(255, 0)\n        expect(color.b).toBeCloseTo(255, 0)\n        expect(color.toHex()).toBe('#ffffff')\n      })\n    })\n\n    describe('hsl()', () => {\n      it('can convert from rgb to hsl', () => {\n        const color = new Color(255, 0, 128)\n        const hsl = color.hsl()\n        expect(hsl.h).toBeCloseTo(329.88, 1)\n        expect(hsl.s).toBeCloseTo(100, 1)\n        expect(hsl.l).toBeCloseTo(50, 1)\n        expect(hsl.space).toBe('hsl')\n      })\n\n      it('can convert from hsl to rgb', () => {\n        const hsl = new Color(329.88, 100, 50, 'hsl')\n        const rgb = hsl.rgb()\n        expect(rgb.r).toBeCloseTo(255, 0)\n        expect(rgb.g).toBeCloseTo(0, 0)\n        expect(rgb.b).toBeCloseTo(128, 0)\n        expect(rgb.space).toBe('rgb')\n      })\n\n      it('is invertable', () => {\n        const { r, g, b } = new Color(255, 0, 128).hsl().rgb()\n        expect(r).toBeCloseTo(255, 0)\n        expect(g).toBeCloseTo(0, 0)\n        expect(b).toBeCloseTo(128, 0)\n      })\n\n      it('handles black', () => {\n        const color = new Color(0, 0, 0).hsl().rgb()\n        expect(color.r).toBeCloseTo(0, 0)\n        expect(color.g).toBeCloseTo(0, 0)\n        expect(color.b).toBeCloseTo(0, 0)\n        expect(color.toHex()).toBe('#000000')\n      })\n\n      it('handles white', () => {\n        const color = new Color(255, 255, 255).hsl().rgb()\n        expect(color.r).toBeCloseTo(255, 0)\n        expect(color.g).toBeCloseTo(255, 0)\n        expect(color.b).toBeCloseTo(255, 0)\n        expect(color.toHex()).toBe('#ffffff')\n      })\n    })\n\n    // This conversion is pretty lossy\n    // Especially g is super wrong (which should be 0 at testing and is >170)\n    // describe('xyz()', () => {\n\n    //   it('can convert from rgb to xyz', () => {\n    //     const color = new Color(255, 0, 128)\n    //     const xyz = color.xyz()\n    //     expect(xyz.x).toBeCloseTo(0.780182, 6)\n    //     expect(xyz.y).toBeCloseTo(0.611077, 6)\n    //     expect(xyz.z).toBeCloseTo(0.590749, 6)\n    //     expect(xyz.space).toBe('xyz')\n    //   })\n\n    //   it('can convert from xyz to rgb', () => {\n    //     const xyz = new Color(0.780181754521692, 0.6110767760212142, 0.590748864329329, 'xyz')\n    //     const rgb = xyz.rgb()\n    //     expect(rgb.r).toBeCloseTo(255, 0)\n    //     expect(rgb.g).toBeCloseTo(0, 0)\n    //     expect(rgb.b).toBeCloseTo(128, 0)\n    //     expect(rgb.space).toBe('rgb')\n    //   })\n\n    //   it('is invertable', () => {\n    //     const { r, g, b } = new Color(255, 0, 128).xyz().rgb()\n    //     expect(r).toBeCloseTo(255, 0)\n    //     expect(g).toBeCloseTo(0, 0)\n    //     expect(b).toBeCloseTo(128, 0)\n    //   })\n\n    //   it('handles black', () => {\n    //     const color = new Color(0, 0, 0).xyz().rgb()\n    //     expect(color.r).toBeCloseTo(0, 0)\n    //     expect(color.g).toBeCloseTo(0, 0)\n    //     expect(color.b).toBeCloseTo(0, 0)\n    //     expect(color.toHex()).toBe('#000000')\n    //   })\n\n    //   it('handles white', () => {\n    //     const color = new Color(255, 255, 255).xyz().rgb()\n    //     expect(color.r).toBeCloseTo(255, 0)\n    //     expect(color.g).toBeCloseTo(255, 0)\n    //     expect(color.b).toBeCloseTo(255, 0)\n    //     expect(color.toHex()).toBe('#ffffff')\n    //   })\n    // })\n\n    describe('cmyk()', () => {\n      it('can convert from rgb to cmyk', () => {\n        const color = new Color(255, 0, 128)\n        const cmyk = color.cmyk()\n        expect(cmyk.c).toBeCloseTo(0, 1)\n        expect(cmyk.m).toBeCloseTo(1, 1)\n        expect(cmyk.y).toBeCloseTo(0.49, 1)\n        expect(cmyk.k).toBeCloseTo(0, 1)\n        expect(cmyk.space).toBe('cmyk')\n      })\n\n      it('can convert from cmyk to rgb', () => {\n        const color = new Color(0, 1, 0.49, 0, 'cmyk')\n        const rgb = color.rgb()\n        expect(rgb.r).toBeCloseTo(255, -1)\n        expect(rgb.g).toBeCloseTo(0, -1)\n        expect(rgb.b).toBeCloseTo(128, -1)\n        expect(rgb.space).toBe('rgb')\n      })\n\n      it('is invertable', () => {\n        const { r, g, b } = new Color(255, 0, 128).cmyk().rgb()\n        expect(r).toBeCloseTo(255, 0)\n        expect(g).toBeCloseTo(0, 0)\n        expect(b).toBeCloseTo(128, 0)\n      })\n\n      it('handles black', () => {\n        const color = new Color(0, 0, 0).cmyk().rgb()\n        expect(color.r).toBeCloseTo(0, 0)\n        expect(color.g).toBeCloseTo(0, 0)\n        expect(color.b).toBeCloseTo(0, 0)\n        expect(color.toHex()).toBe('#000000')\n      })\n\n      it('handles white', () => {\n        const color = new Color(255, 255, 255).cmyk().rgb()\n        expect(color.r).toBeCloseTo(255, 0)\n        expect(color.g).toBeCloseTo(255, 0)\n        expect(color.b).toBeCloseTo(255, 0)\n        expect(color.toHex()).toBe('#ffffff')\n      })\n    })\n  })\n\n  describe('static methods', () => {\n    describe('random()', () => {\n      it('returns color', () => {\n        expect(Color.random()).toEqual(any(Color))\n      })\n\n      it('returns color with mode=vibrant', () => {\n        expect(Color.random('vibrant')).toEqual(any(Color))\n      })\n\n      it('returns color with mode=sine', () => {\n        expect(Color.random('sine')).toEqual(any(Color))\n      })\n\n      it('returns color with mode=pastel', () => {\n        expect(Color.random('pastel')).toEqual(any(Color))\n      })\n\n      it('returns color with mode=dark', () => {\n        expect(Color.random('dark')).toEqual(any(Color))\n      })\n\n      it('returns color with mode=rgb', () => {\n        expect(Color.random('rgb')).toEqual(any(Color))\n      })\n\n      it('returns color with mode=lab', () => {\n        expect(Color.random('lab')).toEqual(any(Color))\n      })\n\n      it('returns color with mode=grey', () => {\n        expect(Color.random('grey')).toEqual(any(Color))\n      })\n\n      it('throws an error if mode is unknown', () => {\n        expect(() => Color.random('foo')).toThrowError(\n          'Unsupported random color mode'\n        )\n      })\n    })\n\n    describe('test()', () => {\n      it('returns false for non strings', () => {\n        expect(Color.test(1)).toBe(false)\n      })\n\n      it('returns true if a given string is a color - hex', () => {\n        expect(Color.test('#abc')).toBe(true)\n        expect(Color.test('#abcdef')).toBe(true)\n      })\n\n      it('returns true if a given string is a color - rgb', () => {\n        expect(Color.test('rgb(1,2,3)')).toBe(true)\n      })\n\n      it('returns false if a given string is a not a color', () => {\n        expect(Color.test('#1234')).toBe(false)\n        expect(Color.test('#Hallo Welt')).toBe(false)\n      })\n    })\n\n    describe('isRgb()', () => {\n      it('returns true if object has all rgb properties set', () => {\n        expect(Color.isRgb({ r: 12, g: 45, b: 11, blub: true })).toBe(true)\n      })\n\n      it('returns false if object has not all rgb properties set or they are not numbers', () => {\n        expect(Color.isRgb({ r: 12, g: 45 })).toBe(false)\n        expect(Color.isRgb({ r: 12, g: 45, b: '1' })).toBe(false)\n      })\n    })\n\n    describe('isColor', () => {\n      it('tests if given value is a color', () => {\n        const spy1 = spyOn(Color, 'isRgb')\n        const spy2 = spyOn(Color, 'test')\n        expect(Color.isColor(new Color())).toBe(true)\n        Color.isColor('#000')\n        const color = { r: 12, g: 45, b: 11 }\n        Color.isColor(color)\n        expect(spy1).toHaveBeenCalledWith('#000')\n        expect(spy1).toHaveBeenCalledWith(color)\n        expect(spy2).toHaveBeenCalledWith('#000')\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/types/EventTarget.js",
    "content": "/* globals describe, expect, it, spyOn, jasmine */\n\nimport { EventTarget } from '../../../src/main.js'\nimport { getWindow } from '../../../src/utils/window.js'\n\nconst { any, objectContaining, createSpy } = jasmine\n\nconst event = (name) => {\n  const CustomEvent = getWindow().CustomEvent\n  return new CustomEvent(name)\n}\n\ndescribe('EventTarget.js', () => {\n  describe('()', () => {\n    it('creates a new object of type EventTarget', () => {\n      expect(new EventTarget()).toEqual(any(EventTarget))\n    })\n  })\n\n  describe('addEventListener()', () => {\n    it('is a noop', () => {\n      const target = new EventTarget()\n      const frozen = Object.freeze(target)\n      frozen.addEventListener()\n    })\n  })\n\n  describe('dispatch()', () => {\n    it('eventually calls dispatchEvent on the target and returns the event', () => {\n      const target = new EventTarget()\n      const spy = spyOn(target, 'dispatchEvent').and.callThrough()\n      const options = { cancelable: false }\n      const event = target.dispatch('bla', 'foo', options)\n      expect(spy).toHaveBeenCalledWith(event)\n      expect(event).toEqual(\n        objectContaining({ type: 'bla', detail: 'foo', cancelable: false })\n      )\n    })\n  })\n\n  describe('dispatchEvent()', () => {\n    it('returns true if no events are bound', () => {\n      const target = new EventTarget()\n      expect(target.dispatchEvent(event('event'))).toBe(true)\n    })\n\n    it('calls the handler for a bound event', () => {\n      const target = new EventTarget()\n      const spy = createSpy('event')\n      const ev = event('event')\n      target.on('event', spy)\n      const ret = target.dispatchEvent(ev)\n      expect(spy).toHaveBeenCalledWith(ev)\n      expect(ret).toBe(!ev.defaultPrevented)\n    })\n\n    it('returns negative default prevented', () => {\n      const target = new EventTarget()\n      const ev = event('event')\n      ev.preventDefault()\n      const ret = target.dispatchEvent(ev)\n      expect(ret).toBe(!ev.defaultPrevented)\n    })\n  })\n\n  describe('fire()', () => {\n    it('calls dispatch and returns the element', () => {\n      const target = new EventTarget()\n      const spy = spyOn(target, 'dispatch')\n      expect(target.fire('event', 'foo', 'bar')).toBe(target)\n      expect(spy).toHaveBeenCalledWith('event', 'foo', 'bar')\n    })\n  })\n\n  describe('getEventHolder()', () => {\n    it('returns itself', () => {\n      const target = new EventTarget()\n      expect(target.getEventHolder()).toBe(target)\n    })\n  })\n\n  describe('getEventTarget()', () => {\n    it('returns itself', () => {\n      const target = new EventTarget()\n      expect(target.getEventTarget()).toBe(target)\n    })\n  })\n\n  describe('off()', () => {\n    it('returns itself', () => {\n      const target = new EventTarget()\n      const spy = createSpy()\n      expect(target.on('event', spy)).toBe(target)\n    })\n\n    it('removes an event binding from the target', () => {\n      const target = new EventTarget()\n      const spy = createSpy()\n      target.on('event', spy)\n      target.dispatch('event')\n      expect(spy.calls.count()).toBe(1)\n      target.off('event', spy)\n      target.dispatch('event')\n      expect(spy.calls.count()).toBe(1)\n    })\n\n    it('removes an event binding with options from the target', () => {\n      const target = new EventTarget()\n      const spy = createSpy()\n      target.on('event', spy, undefined, { capture: true })\n      target.dispatch('event')\n      expect(spy.calls.count()).toBe(1)\n      target.off('event', spy, { capture: true })\n      target.dispatch('event')\n      expect(spy.calls.count()).toBe(1)\n    })\n  })\n\n  describe('on()', () => {\n    it('returns itself', () => {\n      const target = new EventTarget()\n      const spy = createSpy()\n      expect(target.off('event', spy)).toBe(target)\n    })\n\n    it('adds an event binding to the target', () => {\n      const target = new EventTarget()\n      const spy = createSpy()\n      expect(spy.calls.count()).toBe(0)\n      target.on('event', spy)\n      target.dispatch('event')\n      expect(spy.calls.count()).toBe(1)\n    })\n  })\n\n  describe('removeEventListener()', () => {\n    it('is a noop', () => {\n      const target = new EventTarget()\n      const frozen = Object.freeze(target)\n      frozen.removeEventListener()\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/types/List.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport { List } from '../../../src/main.js'\n\nconst { any, createSpy, objectContaining } = jasmine\n\ndescribe('List.js', () => {\n  describe('()', () => {\n    it('creates a new List from Array', () => {\n      const list = new List([1, 2, 3])\n      expect(list).toEqual(any(List))\n    })\n\n    it('creates preallocated List like Array(1)', () => {\n      const list = new List(1)\n      expect(list.length).toBe(1)\n    })\n\n    it('is instance of Array', () => {\n      const list = new List([1, 2, 3])\n      expect(list).toEqual(any(Array))\n    })\n\n    it('allows index access', () => {\n      const list = new List([1, 2, 3])\n      expect(list[1]).toBe(2)\n    })\n  })\n\n  describe('each()', () => {\n    it('works like map but with context set to the element when a function is passed', () => {\n      const list = new List([1, 2, 3]).each((el) => el * 2)\n      expect(list).toEqual(any(List))\n      expect(list).toEqual([2, 4, 6])\n\n      const spy = createSpy()\n      const obj = {}\n      const list2 = new List([obj])\n      list2.each(spy)\n      expect(spy.calls.first()).toEqual(\n        objectContaining({\n          object: obj,\n          args: [obj, 0, list2]\n        })\n      )\n    })\n\n    it('calls a method on every element in the list and passes arguments when a string is passed', () => {\n      const list = new List([10, 11, 12])\n      expect(list.each('toString', 16)).toEqual(['a', 'b', 'c'])\n    })\n  })\n\n  describe('toArray()', () => {\n    it('returns a plain array from the contents of the list', () => {\n      const list = new List([1, 2, 3])\n      const arr = list.toArray()\n      expect(arr).toEqual(any(Array))\n      expect(arr).not.toEqual(any(List))\n      expect(arr).toEqual([1, 2, 3])\n    })\n  })\n\n  describe('static extend()', () => {\n    it('adds new method names to the List', () => {\n      List.extend(['fooBar'])\n      expect(new List().fooBar).toEqual(any(Function))\n\n      const obj = { fooBar: createSpy() }\n      new List([obj]).fooBar()\n      expect(obj.fooBar).toHaveBeenCalled()\n\n      delete List.prototype.fooBar\n    })\n\n    it('keeps Array prototype names prefixed with $', () => {\n      // We're picking a function that we know isn't part of core svg.js\n      // If we implement an 'unshift' function at some point, change this to something else\n      if (List.prototype.hasOwnProperty('unshift')) {\n        fail(\n          'List.unshift is already a function - change this test to use a different name!'\n        )\n        return\n      }\n\n      List.extend(['unshift'])\n      expect(new List().unshift).toEqual(any(Function))\n      expect(new List().$unshift).toEqual(Array.prototype.unshift)\n\n      // Check that it works!\n      const sourceArray = [\n        { unshift: () => 1 },\n        { unshift: () => 2 },\n        { unshift: () => 3 }\n      ]\n      const list = new List(sourceArray)\n\n      expect(list).toEqual(sourceArray)\n      expect(list.unshift(0)).toEqual([1, 2, 3])\n\n      expect(list.$unshift(0)).toEqual(4)\n      expect(list).toEqual([0].concat(sourceArray))\n\n      delete List.prototype.unshift\n    })\n\n    it('skips reserved names', () => {\n      const { constructor, each, toArray } = List.prototype\n      List.extend(['constructor', 'each', 'toArray'])\n      expect(List.prototype).toEqual(\n        objectContaining({ constructor, each, toArray })\n      )\n    })\n\n    it('skips private methods starting with an underscore', () => {\n      List.extend(['_private'])\n      expect(new List()._private).toBe(undefined)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/types/Matrix.js",
    "content": "/* globals describe, expect, it, spyOn, jasmine */\n\nimport { Matrix, Rect, SVG } from '../../../src/main.js'\nimport { getWindow } from '../../../src/utils/window.js'\n\nconst { any, objectContaining } = jasmine\n\ndescribe('Matrix.js', () => {\n  const comp = { a: 2, b: 0, c: 0, d: 2, e: 100, f: 50 }\n\n  describe('initialization', () => {\n    it('creates a new matrix with default values', () => {\n      const matrix = new Matrix()\n      expect(matrix).toEqual(\n        objectContaining({ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 })\n      )\n    })\n\n    it('parses the current transform matrix from an element', () => {\n      const rect = new Rect().transform(comp)\n      const matrix = new Matrix(rect)\n      expect(matrix).toEqual(objectContaining(comp))\n    })\n\n    it('parses a string value correctly', () => {\n      const matrix = new Matrix('2, 0, 0, 2, 100, 50')\n      expect(matrix).toEqual(objectContaining(comp))\n    })\n\n    it('parses an array correctly', () => {\n      const matrix = new Matrix([2, 0, 0, 2, 100, 50])\n      expect(matrix).toEqual(objectContaining(comp))\n    })\n\n    it('parses an object correctly', () => {\n      const matrix = new Matrix(comp)\n      expect(matrix).toEqual(objectContaining(comp))\n    })\n\n    it('parses a transform object correctly', () => {\n      const matrix = new Matrix({ scale: 2, translate: [100, 50] })\n      expect(matrix).toEqual(objectContaining(comp))\n    })\n\n    it('parses 6 arguments correctly', () => {\n      const matrix = new Matrix(2, 0, 0, 2, 100, 50)\n      expect(matrix).toEqual(objectContaining(comp))\n    })\n\n    it('falls back to base if source is missing values', () => {\n      const matrix = new Matrix([])\n      expect(matrix).toEqual(new Matrix())\n    })\n  })\n\n  describe('toString()', () => {\n    it('exports correctly to a string', () => {\n      expect(new Matrix().toString()).toBe('matrix(1,0,0,1,0,0)')\n    })\n  })\n\n  describe('transform()', () => {\n    it('does simple left matrix multiplication if matrixlike object is passed', () => {\n      const matrix = new Matrix().transform(new Matrix().scale(2))\n      expect(matrix).toEqual(new Matrix().lmultiplyO(new Matrix().scale(2)))\n    })\n\n    it('forces the origin to a specific place if position.x is passed', () => {\n      const matrix = new Matrix().transform({ px: 10 })\n      expect(matrix.e).toBe(10)\n    })\n\n    it('forces the origin to a specific place if position.y is passed', () => {\n      const matrix = new Matrix().transform({ py: 10 })\n      expect(matrix.f).toBe(10)\n    })\n  })\n\n  describe('decompose()', () => {\n    it('decomposes a matrix properly', () => {\n      var matrix = new Matrix()\n        .scale(3, 2.5)\n        .shear(4)\n        .rotate(30)\n        .translate(20, 30)\n      var decomposed = matrix.decompose()\n      expect(decomposed.scaleX).toBeCloseTo(3)\n      expect(decomposed.scaleY).toBeCloseTo(2.5)\n      expect(decomposed.shear).toBeCloseTo(4)\n      expect(decomposed.rotate).toBeCloseTo(30)\n      expect(decomposed.translateX).toBeCloseTo(20)\n      expect(decomposed.translateY).toBeCloseTo(30)\n    })\n\n    it('can be recomposed to the same matrix', () => {\n      var matrix = new Matrix()\n        .scale(3, 2.5)\n        .shear(4)\n        .rotate(30)\n        .translate(20, 30)\n      var decomposed = matrix.decompose()\n\n      // Get rid of the matrix values before recomposing with the matrix constructor\n      for (const prop in 'abcdef') delete decomposed[prop]\n\n      var composed = new Matrix(decomposed)\n      expect(matrix.a).toBeCloseTo(composed.a)\n      expect(matrix.b).toBeCloseTo(composed.b)\n      expect(matrix.c).toBeCloseTo(composed.c)\n      expect(matrix.d).toBeCloseTo(composed.d)\n      expect(matrix.e).toBeCloseTo(composed.e)\n      expect(matrix.f).toBeCloseTo(composed.f)\n    })\n  })\n\n  describe('clone()', () => {\n    it('returns a clone of the matrix', () => {\n      var matrix = new Matrix(2, 0, 0, 5, 0, 0)\n      var clone = matrix.clone()\n      expect(matrix).not.toBe(clone)\n      for (var i in 'abcdef') {\n        expect(matrix[i]).toEqual(clone[i])\n      }\n    })\n  })\n\n  describe('multiply()', () => {\n    it('multiplies two matrices', () => {\n      var matrix1 = new Matrix(1, 4, 2, 5, 3, 6)\n      var matrix2 = new Matrix(7, 8, 8, 7, 9, 6)\n      var matrix3 = matrix1.multiply(matrix2)\n\n      expect(matrix1.toString()).toBe('matrix(1,4,2,5,3,6)')\n      expect(matrix2.toString()).toBe('matrix(7,8,8,7,9,6)')\n      expect(matrix3.toString()).toBe('matrix(23,68,22,67,24,72)')\n    })\n\n    it('accepts matrices in any form', () => {\n      var matrix1 = new Matrix(1, 4, 2, 5, 3, 6)\n      var matrix2 = matrix1.multiply('7,8,8,7,9,6')\n\n      expect(matrix1.toString()).toBe('matrix(1,4,2,5,3,6)')\n      expect(matrix2.toString()).toBe('matrix(23,68,22,67,24,72)')\n    })\n  })\n\n  describe('inverse()', () => {\n    it('inverses matrix', () => {\n      var matrix1 = new Matrix(2, 0, 0, 5, 4, 3)\n      var matrix2 = matrix1.inverse()\n      var abcdef = [0.5, 0, 0, 0.2, -2, -0.6]\n\n      for (var i in 'abcdef') {\n        expect(matrix2['abcdef'[i]]).toBeCloseTo(abcdef[i])\n      }\n    })\n\n    it('throws if matrix is not invertible', () => {\n      const matrix = new Matrix(0, 0, 0, 0, 0, 0)\n      expect(() => matrix.inverse()).toThrowError(\n        'Cannot invert matrix(0,0,0,0,0,0)'\n      )\n    })\n  })\n\n  describe('translate()', () => {\n    it('translates matrix by given x and y values', () => {\n      var matrix = new Matrix(1, 0, 0, 1, 4, 3).translate(10, 12.5)\n      expect(matrix.e).toBe(14)\n      expect(matrix.f).toBe(15.5)\n    })\n\n    it('does nothing if you give it no x or y value', () => {\n      var matrix = new Matrix(1, 2, 3, 4, 5, 6).translate()\n      expect(matrix.e).toBe(5)\n      expect(matrix.f).toBe(6)\n    })\n  })\n\n  describe('scale()', () => {\n    it('performs a uniformal scale with one value', () => {\n      var matrix = new Matrix(1, 0, 0, 1, 4, 3).scale(3)\n\n      expect(matrix.a).toBe(3)\n      expect(matrix.d).toBe(3)\n      expect(matrix.e).toBe(4 * 3)\n      expect(matrix.f).toBe(3 * 3)\n    })\n\n    it('performs a non-uniformal scale with two values', () => {\n      var matrix = new Matrix(1, 0, 0, 1, 4, 3).scale(2.5, 3.5)\n\n      expect(matrix.a).toBe(2.5)\n      expect(matrix.d).toBe(3.5)\n      expect(matrix.e).toBe(4 * 2.5)\n      expect(matrix.f).toBe(3 * 3.5)\n    })\n\n    it('performs a uniformal scale at a given center point with three values', () => {\n      var matrix = new Matrix(1, 3, 2, 3, 4, 3).scale(3, 2, 3)\n\n      expect(matrix.a).toBe(3)\n      expect(matrix.b).toBe(9)\n      expect(matrix.c).toBe(6)\n      expect(matrix.d).toBe(9)\n      expect(matrix.e).toBe(8)\n      expect(matrix.f).toBe(3)\n    })\n\n    it('performs a non-uniformal scale at a given center point with four values', () => {\n      var matrix = new Matrix(1, 3, 2, 3, 4, 3).scale(3, 2, 2, 3)\n\n      expect(matrix.a).toBe(3)\n      expect(matrix.b).toBe(6)\n      expect(matrix.c).toBe(6)\n      expect(matrix.d).toBe(6)\n      expect(matrix.e).toBe(8)\n      expect(matrix.f).toBe(3)\n    })\n  })\n\n  describe('rotate()', () => {\n    it('performs a rotation with one argument', () => {\n      var matrix = new Matrix(1, 3, 2, 3, 4, 3).rotate(30)\n\n      expect(matrix.a).toBeCloseTo(-0.6339746)\n      expect(matrix.b).toBeCloseTo(3.09807621)\n      expect(matrix.c).toBeCloseTo(0.23205081)\n      expect(matrix.d).toBeCloseTo(3.59807621)\n      expect(matrix.e).toBeCloseTo(1.96410162)\n      expect(matrix.f).toBeCloseTo(4.59807621)\n    })\n\n    it('performs a rotation around a given point with three arguments', () => {\n      var matrix = new Matrix(1, 3, 2, 3, 4, 3).rotate(30, 2, 3)\n\n      expect(matrix.a).toBeCloseTo(-0.633974596216)\n      expect(matrix.b).toBeCloseTo(3.09807621135)\n      expect(matrix.c).toBeCloseTo(0.232050807569)\n      expect(matrix.d).toBeCloseTo(3.59807621135)\n      expect(matrix.e).toBeCloseTo(3.73205080757)\n      expect(matrix.f).toBeCloseTo(4.0)\n    })\n  })\n\n  describe('flip()', () => {\n    describe('with x given', () => {\n      it('performs a flip over the horizontal axis with one argument', () => {\n        var matrix = new Matrix(1, 0, 0, 1, 4, 3).flip('x')\n\n        expect(matrix.a).toBe(-1)\n        expect(matrix.d).toBe(1)\n        expect(matrix.e).toBe(-4)\n        expect(matrix.f).toBe(3)\n      })\n\n      it('performs a flip over the horizontal axis over a given point with two arguments', () => {\n        var matrix = new Matrix(1, 0, 0, 1, 4, 3).flip('x', 150)\n\n        expect(matrix.a).toBe(-1)\n        expect(matrix.d).toBe(1)\n        expect(matrix.e).toBe(296)\n        expect(matrix.f).toBe(3)\n      })\n    })\n\n    describe('with y given', () => {\n      it('performs a flip over the vertical axis with one argument', () => {\n        var matrix = new Matrix(1, 0, 0, 1, 4, 3).flip('y')\n\n        expect(matrix.a).toBe(1)\n        expect(matrix.d).toBe(-1)\n        expect(matrix.e).toBe(4)\n        expect(matrix.f).toBe(-3)\n      })\n\n      it('performs a flip over the vertical axis over a given point with two arguments', () => {\n        var matrix = new Matrix(1, 0, 0, 1, 4, 3).flip('y', 100)\n\n        expect(matrix.a).toBe(1)\n        expect(matrix.d).toBe(-1)\n        expect(matrix.e).toBe(4)\n        expect(matrix.f).toBe(197)\n      })\n    })\n\n    describe('with no axis given', () => {\n      it('performs a flip over the horizontal and vertical axis with no argument', () => {\n        var matrix = new Matrix(1, 0, 0, 1, 4, 3).flip()\n\n        expect(matrix.a).toBe(-1)\n        expect(matrix.d).toBe(-1)\n        expect(matrix.e).toBe(-4)\n        expect(matrix.f).toBe(-3)\n      })\n\n      it('performs a flip over the horizontal and vertical axis over a given point with one argument that represent both coordinates', () => {\n        var matrix = new Matrix(1, 0, 0, 1, 4, 3).flip(100)\n\n        expect(matrix.a).toBe(-1)\n        expect(matrix.d).toBe(-1)\n        expect(matrix.e).toBe(196)\n        expect(matrix.f).toBe(197)\n      })\n\n      it('performs a flip over the horizontal and vertical axis over a given point with two arguments', () => {\n        var matrix = new Matrix(1, 0, 0, 1, 4, 3).flip(50, 100)\n\n        expect(matrix.a).toBe(-1)\n        expect(matrix.d).toBe(-1)\n        expect(matrix.e).toBe(96)\n        expect(matrix.f).toBe(197)\n      })\n    })\n  })\n\n  describe('skew()', () => {\n    it('performs a uniformal skew with one value', () => {\n      var matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(30)\n\n      expect(matrix.a).toBe(1)\n      expect(matrix.b).toBeCloseTo(0.57735026919)\n      expect(matrix.c).toBeCloseTo(0.57735026919)\n      expect(matrix.d).toBe(1)\n      expect(matrix.e).toBeCloseTo(5.73205080757)\n      expect(matrix.f).toBeCloseTo(5.30940107676)\n    })\n\n    it('performs a non-uniformal skew with two values', () => {\n      var matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(30, 20)\n\n      expect(matrix.a).toBe(1)\n      expect(matrix.b).toBeCloseTo(0.363970234266)\n      expect(matrix.c).toBeCloseTo(0.57735026919)\n      expect(matrix.d).toBe(1)\n      expect(matrix.e).toBeCloseTo(5.73205080757)\n      expect(matrix.f).toBeCloseTo(4.45588093706)\n    })\n\n    it('performs a uniformal skew at a given center point with three values', () => {\n      var matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(30, 150, 100)\n\n      expect(matrix.a).toBe(1)\n      expect(matrix.b).toBeCloseTo(0.57735026919)\n      expect(matrix.c).toBeCloseTo(0.57735026919)\n      expect(matrix.d).toBe(1)\n      expect(matrix.e).toBeCloseTo(-52.0029761114)\n      expect(matrix.f).toBeCloseTo(-81.2931393017)\n    })\n\n    it('performs a non-uniformal skew at a given center point with four values', () => {\n      var matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(30, 20, 150, 100)\n\n      expect(matrix.a).toBe(1.0)\n      expect(matrix.b).toBeCloseTo(0.363970234266)\n      expect(matrix.c).toBeCloseTo(0.57735026919)\n      expect(matrix.d).toBe(1.0)\n      expect(matrix.e).toBeCloseTo(-52.0029761114)\n      expect(matrix.f).toBeCloseTo(-50.1396542029)\n    })\n\n    it('can be chained', () => {\n      var matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(20, 30).skew(30, 20)\n      expect(matrix.a).toBeCloseTo(1.33333333333)\n      expect(matrix.b).toBeCloseTo(0.941320503456)\n      expect(matrix.c).toBeCloseTo(0.941320503456)\n      expect(matrix.d).toBeCloseTo(1.13247433143)\n      expect(matrix.e).toBeCloseTo(8.1572948437)\n      expect(matrix.f).toBeCloseTo(7.16270500812)\n    })\n  })\n\n  describe('skewX', () => {\n    it('performs a skew along the x axis with one value', () => {\n      var matrix = new Matrix(1, 0, 0, 1, 4, 3).skewX(30)\n\n      expect(matrix.a).toBe(1)\n      expect(matrix.b).toBe(0)\n      expect(matrix.c).toBeCloseTo(0.57735026919)\n      expect(matrix.d).toBe(1)\n      expect(matrix.e).toBeCloseTo(5.73205080757)\n      expect(matrix.f).toBe(3)\n    })\n\n    it('performs a skew along the x axis at a given center point with three values', () => {\n      var matrix = new Matrix(1, 0, 0, 1, 4, 3).skewX(30, 150, 100)\n\n      expect(matrix.a).toBe(1)\n      expect(matrix.b).toBe(0)\n      expect(matrix.c).toBeCloseTo(0.57735026919)\n      expect(matrix.d).toBe(1)\n      expect(matrix.e).toBeCloseTo(-52.0029761114)\n      expect(matrix.f).toBe(3)\n    })\n  })\n\n  describe('skewY', () => {\n    it('performs a skew along the y axis with one value', () => {\n      var matrix = new Matrix(1, 0, 0, 1, 4, 3).skewY(30)\n\n      expect(matrix.a).toBe(1)\n      expect(matrix.b).toBeCloseTo(0.57735026919)\n      expect(matrix.c).toBe(0)\n      expect(matrix.d).toBe(1)\n      expect(matrix.e).toBe(4)\n      expect(matrix.f).toBeCloseTo(5.30940107676)\n    })\n\n    it('performs a skew along the y axis at a given center point with three values', () => {\n      var matrix = new Matrix(1, 0, 0, 1, 4, 3).skewY(30, 150, 100)\n\n      expect(matrix.a).toBe(1)\n      expect(matrix.b).toBeCloseTo(0.57735026919)\n      expect(matrix.c).toBe(0)\n      expect(matrix.d).toBe(1)\n      expect(matrix.e).toBe(4)\n      expect(matrix.f).toBeCloseTo(-81.2931393017)\n    })\n  })\n\n  describe('around()', () => {\n    it('performs a matrix operation around an origin by shifting the origin to 0,0', () => {\n      const matrix = new Matrix(1, 0, 0, 1, 0, 0).around(\n        10,\n        10,\n        new Matrix().scale(2)\n      )\n\n      expect(matrix).toEqual(new Matrix(2, 0, 0, 2, -10, -10))\n    })\n\n    it('defaults to around center of 0,0', () => {\n      const matrix = new Matrix(1, 0, 0, 1, 0, 0).around(\n        0,\n        0,\n        new Matrix().scale(2)\n      )\n\n      expect(matrix).toEqual(new Matrix(2, 0, 0, 2, 0, 0))\n    })\n  })\n\n  describe('equals()', () => {\n    it('returns true if the same matrix is passed', () => {\n      const matrix = new Matrix()\n      expect(matrix.equals(matrix)).toBe(true)\n    })\n\n    it('returns true if the components match', () => {\n      const matrix = new Matrix()\n      expect(matrix.equals(matrix.clone())).toBe(true)\n    })\n\n    it('returns false if the components do not match', () => {\n      const matrix = new Matrix()\n      expect(matrix.equals(matrix.scale(2))).toBe(false)\n    })\n  })\n\n  describe('valueOf()', () => {\n    it('returns an object containing the matrix components', () => {\n      const matrix = new Matrix().valueOf()\n      expect(matrix).not.toEqual(any(Matrix))\n      expect(matrix).toEqual({ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 })\n    })\n  })\n\n  describe('toArray', () => {\n    it('converts matrix to array', () => {\n      const arr = new Matrix().toArray()\n      expect(arr).toEqual([1, 0, 0, 1, 0, 0])\n    })\n  })\n\n  describe('static', () => {\n    describe('fromArray()', () => {\n      it('creates a matrix like object from an array', () => {\n        const matrix = Matrix.fromArray([1, 2, 3, 4, 5, 6])\n        expect(matrix).not.toEqual(any(Matrix))\n        expect(matrix).toEqual(new Matrix(1, 2, 3, 4, 5, 6).valueOf())\n      })\n    })\n\n    describe('isMatrixLike', () => {\n      it('returns true if object contains all components', () => {\n        expect(Matrix.isMatrixLike(new Matrix())).toBe(true)\n        expect(Matrix.isMatrixLike(new Matrix().valueOf())).toBe(true)\n        expect(Matrix.isMatrixLike({ f: 0 })).toBe(true)\n      })\n\n      it('returns false if no component is found', () => {\n        expect(Matrix.isMatrixLike({ foo: 'bar' })).toBe(false)\n      })\n    })\n\n    describe('formatTransforms()', () => {\n      it('formats all transform input varieties to a canonical form', () => {\n        expect(\n          Matrix.formatTransforms({\n            flip: true,\n            skew: 5,\n            scale: 5,\n            originX: 5,\n            originY: 5,\n            positionX: 5,\n            positionY: 5,\n            translateX: 5,\n            translateY: 5,\n            relativeX: 5,\n            relativeY: 5\n          })\n        ).toEqual({\n          scaleX: -5,\n          scaleY: -5,\n          skewX: 5,\n          skewY: 5,\n          shear: 0,\n          theta: 0,\n          rx: 5,\n          ry: 5,\n          tx: 5,\n          ty: 5,\n          ox: 5,\n          oy: 5,\n          px: 5,\n          py: 5\n        })\n      })\n\n      it('respects flip=x', () => {\n        expect(\n          Matrix.formatTransforms({\n            flip: 'x',\n            scale: [1, 2],\n            skew: [1, 2]\n          })\n        ).toEqual(\n          objectContaining({ scaleX: -1, scaleY: 2, skewX: 1, skewY: 2 })\n        )\n      })\n\n      it('respects flip=y', () => {\n        expect(\n          Matrix.formatTransforms({\n            flip: 'y',\n            scaleX: 1,\n            scaleY: 2,\n            skewX: 1,\n            skewY: 2\n          })\n        ).toEqual(\n          objectContaining({ scaleX: 1, scaleY: -2, skewX: 1, skewY: 2 })\n        )\n      })\n\n      it('makes position NaN if not passed', () => {\n        expect(\n          Matrix.formatTransforms({\n            flip: 'y',\n            scaleX: 1,\n            scaleY: 2,\n            skewX: 1,\n            skewY: 2\n          })\n        ).toEqual(objectContaining({ px: NaN, py: NaN }))\n      })\n    })\n  })\n\n  describe('Element', () => {\n    describe('ctm()', () => {\n      it('returns the native ctm wrapped into a matrix', () => {\n        const rect = new Rect()\n        const spy = spyOn(rect.node, 'getCTM')\n        rect.ctm()\n        expect(spy).toHaveBeenCalled()\n      })\n    })\n\n    describe('screenCTM()', () => {\n      it('returns the native screenCTM wrapped into a matrix for a normal element', () => {\n        const rect = new Rect()\n        const spy = spyOn(rect.node, 'getScreenCTM')\n        rect.screenCTM()\n        expect(spy).toHaveBeenCalled()\n      })\n\n      it('does extra work for nested svgs because firefox needs it', () => {\n        const spy = spyOn(\n          getWindow().SVGGraphicsElement.prototype,\n          'getScreenCTM'\n        )\n        const svg = SVG().nested()\n        svg.screenCTM()\n        expect(spy).toHaveBeenCalled()\n      })\n\n      it('does not throw and returns identity matrix if element is not rendered', () => {\n        const canvas = SVG().viewbox(0, 0, 0, 0)\n        expect(canvas.screenCTM()).toEqual(new Matrix())\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/types/PathArray.js",
    "content": "/* globals describe, expect, it, beforeEach */\n\nimport { PathArray, Box } from '../../../src/main.js'\n\ndescribe('PathArray.js', () => {\n  let p1, p2, p3\n\n  beforeEach(() => {\n    p1 = new PathArray('m10 10 h 80 v 80 h -80 l 300 400 z')\n    p2 = new PathArray(\n      'm10 80 c 40 10 65 10 95 80 s 150 150 180 80 t 300 300 q 52 10 95 80 z'\n    )\n    p3 = new PathArray('m80 80 A 45 45, 0, 0, 0, 125 125 L 125 80 z')\n  })\n\n  it('parses flat arrays correctly', () => {\n    const arr = new PathArray(['M', 0, 0, 'L', 100, 100, 'z'])\n    expect(arr.toString()).toBe('M0 0L100 100Z ')\n  })\n\n  it('parses nested arrays correctly', () => {\n    const arr = new PathArray([['M', 0, 0], ['L', 100, 100], ['z']])\n    expect(arr.toString()).toBe('M0 0L100 100Z ')\n  })\n\n  // this test is designed to cover a certain line but it doesn't work because of #608\n  it('returns the valueOf when PathArray is given', () => {\n    const p = new PathArray('m10 10 h 80 v 80 h -80 l 300 400 z')\n\n    expect(new PathArray(p)).toEqual(p)\n  })\n\n  describe('move()', () => {\n    it('moves all points in a straight path', () => {\n      expect(p1.move(100, 200).toString()).toBe(\n        'M100 200H180V280H100L400 680Z '\n      )\n    })\n\n    it('moves all points in a curved path', () => {\n      expect(p2.move(100, 200).toString()).toBe(\n        'M100 200C140 210 165 210 195 280S345 430 375 360T675 660Q727 670 770 740Z '\n      )\n    })\n\n    it('moves all points in a arc path', () => {\n      expect(p3.move(100, 200).toString()).toBe(\n        'M100 200A45 45 0 0 0 145 245L145 200Z '\n      )\n    })\n\n    it('does nothing if passed number is not a number', () => {\n      expect(p3.move()).toEqual(p3)\n    })\n  })\n\n  describe('size()', () => {\n    it('resizes all points in a straight path', () => {\n      expect(p1.size(600, 200).toString()).toBe(\n        'M10 10H170V43.333333333333336H10L610 210Z '\n      )\n    })\n\n    it('resizes all points in a curved path', () => {\n      expect(p2.size(600, 200).toString()).toBe(\n        'M10 80C45.82089552238806 83.70370370370371 68.2089552238806 83.70370370370371 95.07462686567165 109.62962962962963S229.40298507462686 165.1851851851852 256.2686567164179 139.25925925925927T524.9253731343283 250.37037037037038Q571.4925373134329 254.07407407407408 610 280Z '\n      )\n    })\n\n    it('resizes all points in a arc path', () => {\n      const expected = [\n        ['M', 80, 80],\n        ['A', 600, 200, 0, 0, 0, 680, 280],\n        ['L', 680, 80],\n        ['Z']\n      ]\n\n      const toBeTested = p3.size(600, 200)\n\n      for (let i = toBeTested.length; i--; ) {\n        expect(toBeTested[i].shift().toUpperCase()).toBe(\n          expected[i].shift().toUpperCase()\n        )\n        for (let j = toBeTested[i].length; j--; ) {\n          expect(toBeTested[i][j]).toBeCloseTo(expected[i][j])\n        }\n      }\n    })\n  })\n\n  describe('bbox()', () => {\n    it('calculates the bounding box of the PathArray', () => {\n      const box = new PathArray('M0 0 L 10 10').bbox()\n      expect(box).toEqual(new Box(0, 0, 10, 10))\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/types/Point.js",
    "content": "/* globals describe, expect, it, beforeEach, spyOn */\n\nimport { Point, Rect, Matrix } from '../../../src/main.js'\n\ndescribe('Point.js', () => {\n  var point\n\n  describe('initialization', () => {\n    describe('without a source', () => {\n      beforeEach(() => {\n        point = new Point()\n      })\n\n      it('creates a new point with default values', () => {\n        expect(point.x).toBe(0)\n        expect(point.y).toBe(0)\n      })\n    })\n\n    describe('with x and y given', () => {\n      it('creates a point with given values', () => {\n        var point = new Point(2, 4)\n\n        expect(point.x).toBe(2)\n        expect(point.y).toBe(4)\n      })\n    })\n\n    describe('with only x given', () => {\n      it('sets the y value to 0', () => {\n        var point = new Point(7)\n\n        expect(point.x).toBe(7)\n        expect(point.y).toBe(0)\n      })\n    })\n\n    describe('with array given', () => {\n      it('creates a point from array', () => {\n        var point = new Point([2, 4])\n\n        expect(point.x).toBe(2)\n        expect(point.y).toBe(4)\n      })\n    })\n\n    describe('with object given', () => {\n      it('creates a point from object', () => {\n        var point = new Point({ x: 2, y: 4 })\n\n        expect(point.x).toBe(2)\n        expect(point.y).toBe(4)\n      })\n    })\n\n    describe('with Point given', () => {\n      it('creates a point from Point', () => {\n        var point = new Point(new Point(2, 4))\n\n        expect(point.x).toBe(2)\n        expect(point.y).toBe(4)\n      })\n    })\n  })\n\n  describe('transform()', () => {\n    it('transforms a point with a matrix', () => {\n      expect(\n        new Point().transform(new Matrix({ translate: [10, 10] }))\n      ).toEqual(new Point(10, 10))\n    })\n\n    it('transforms a point with a transformation object', () => {\n      expect(new Point().transform({ translate: [10, 10] })).toEqual(\n        new Point(10, 10)\n      )\n    })\n  })\n\n  describe('clone()', () => {\n    it('returns cloned point', () => {\n      var point1 = new Point(1, 1)\n      var point2 = point1.clone()\n\n      expect(point1).toEqual(point2)\n      expect(point1).not.toBe(point2)\n    })\n  })\n\n  describe('toArray()', () => {\n    it('creates an array representation of Point', () => {\n      const p = new Point(1, 2)\n      expect(p.toArray()).toEqual([1, 2])\n    })\n  })\n\n  describe('Element', () => {\n    describe('point()', () => {\n      it('transforms a screen point into the coordinate system of the element', () => {\n        const rect = new Rect()\n        spyOn(rect, 'screenCTM').and.callFake(\n          () => new Matrix(1, 0, 0, 1, 20, 20)\n        )\n        expect(rect.point({ x: 10, y: 10 })).toEqual(new Point(-10, -10))\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/types/PointArray.js",
    "content": "/* globals describe, expect, it */\n\nimport { PointArray, Matrix, Point } from '../../../src/main.js'\n\ndescribe('PointArray.js', () => {\n  const squareString = '0,0 1,0 1,1 0,1'\n\n  describe('()', () => {\n    it('parses a string to a point array', () => {\n      var array = new PointArray('0,1 -.05,7.95 1000.0001,-200.222')\n      expect(array.valueOf()).toEqual([\n        [0, 1],\n        [-0.05, 7.95],\n        [1000.0001, -200.222]\n      ])\n    })\n\n    it('parses a points array correctly to string', () => {\n      var array = new PointArray([\n        [0, 0.15],\n        [-100, -3.141592654],\n        [50, 100]\n      ])\n      expect(array + '').toBe('0,0.15 -100,-3.141592654 50,100')\n    })\n\n    it('parses a flat array of x/y coordinates to a point array', () => {\n      var array = new PointArray([1, 4, 5, 68, 12, 24])\n      expect(array.valueOf()).toEqual([\n        [1, 4],\n        [5, 68],\n        [12, 24]\n      ])\n    })\n\n    it('parses points with space delimitered x/y coordinates', () => {\n      var array = new PointArray(\n        '221.08 191.79 0.46 191.79 0.46 63.92 63.8 0.46 284.46 0.46 284.46 128.37 221.08 191.79'\n      )\n      expect(array + '').toBe(\n        '221.08,191.79 0.46,191.79 0.46,63.92 63.8,0.46 284.46,0.46 284.46,128.37 221.08,191.79'\n      )\n    })\n\n    it('parses points with comma delimitered x/y coordinates', () => {\n      var array = new PointArray(\n        '221.08,191.79,0.46,191.79,0.46,63.92,63.8,0.46,284.46,0.46,284.46,128.37,221.08,191.79'\n      )\n      expect(array + '').toBe(\n        '221.08,191.79 0.46,191.79 0.46,63.92 63.8,0.46 284.46,0.46 284.46,128.37 221.08,191.79'\n      )\n    })\n\n    it('parses points with comma and space delimitered x/y coordinates', () => {\n      var array = new PointArray(\n        '221.08, 191.79, 0.46, 191.79, 0.46, 63.92, 63.8, 0.46, 284.46, 0.46, 284.46, 128.37, 221.08, 191.79'\n      )\n      expect(array + '').toBe(\n        '221.08,191.79 0.46,191.79 0.46,63.92 63.8,0.46 284.46,0.46 284.46,128.37 221.08,191.79'\n      )\n    })\n\n    it('parses points with space and comma delimitered x/y coordinates', () => {\n      var array = new PointArray(\n        '221.08 ,191.79 ,0.46 ,191.79 ,0.46 ,63.92 ,63.8 ,0.46 ,284.46 ,0.46 ,284.46 ,128.37 ,221.08 ,191.79'\n      )\n      expect(array + '').toBe(\n        '221.08,191.79 0.46,191.79 0.46,63.92 63.8,0.46 284.46,0.46 284.46,128.37 221.08,191.79'\n      )\n    })\n\n    it('parses points with redundant spaces at the end', () => {\n      var array = new PointArray(\n        '2176.6,1708.8 2176.4,1755.8 2245.8,1801.5 2297,1787.8  '\n      )\n      expect(array + '').toBe(\n        '2176.6,1708.8 2176.4,1755.8 2245.8,1801.5 2297,1787.8'\n      )\n    })\n\n    it('parses points with space delimitered x/y coordinates - even with leading or trailing space', () => {\n      var array = new PointArray('  1 2 3 4  ')\n      expect(array + '').toBe('1,2 3,4')\n    })\n\n    it('parses odd number of points with space delimitered x/y coordinates and silently remove the odd point', () => {\n      // this  is according to spec: https://svgwg.org/svg2-draft/shapes.html#DataTypePoints\n      var array = new PointArray('1 2 3')\n      expect(array + '').toBe('1,2')\n    })\n\n    it('parses odd number of points in a flat array of x/y coordinates and silently remove the odd point', () => {\n      // this  is according to spec: https://svgwg.org/svg2-draft/shapes.html#DataTypePoints\n      var array = new PointArray([1, 2, 3])\n      expect(array.valueOf()).toEqual([[1, 2]])\n    })\n  })\n\n  describe('move()', () => {\n    it('moves the whole array by the passed value', () => {\n      const arr = new PointArray([1, 2, 3, 4]).move(10, 10)\n      expect(arr.toArray()).toEqual([10, 10, 12, 12])\n    })\n\n    it('does nothing if values not numbers', () => {\n      const arr = new PointArray([1, 2, 3, 4]).move()\n      expect(arr.toArray()).toEqual([1, 2, 3, 4])\n    })\n  })\n\n  describe('size()', () => {\n    it('correctly sizes the points over the whole area', () => {\n      var array = new PointArray([10, 10, 20, 20, 30, 30])\n      expect(array.size(60, 60).valueOf()).toEqual([\n        [10, 10],\n        [40, 40],\n        [70, 70]\n      ])\n    })\n\n    it('let coordinates untouched when width/height is zero', () => {\n      var array = new PointArray([10, 10, 10, 20, 10, 30])\n      expect(array.size(60, 60).valueOf()).toEqual([\n        [10, 10],\n        [10, 40],\n        [10, 70]\n      ])\n\n      array = new PointArray([10, 10, 20, 10, 30, 10])\n      expect(array.size(60, 60).valueOf()).toEqual([\n        [10, 10],\n        [40, 10],\n        [70, 10]\n      ])\n    })\n  })\n\n  describe('toString()', () => {\n    it('converts to comma sepereated list', () => {\n      const square = new PointArray(squareString)\n      expect(square.toString()).toEqual(squareString)\n    })\n  })\n\n  describe('toLine', () => {\n    it('returns an object which can be passed to a line as point attributes', () => {\n      const arr = new PointArray([1, 2, 3, 4])\n      expect(arr.toLine()).toEqual({ x1: 1, y1: 2, x2: 3, y2: 4 })\n    })\n  })\n\n  describe('transform()', () => {\n    it('translates correctly', () => {\n      const square = new PointArray(squareString)\n      const translation = new Matrix({ translate: [2, 1] })\n      const newSquare = square.transform(translation)\n      expect(newSquare.toString()).toEqual('2,1 3,1 3,2 2,2')\n    })\n\n    it('transforms like Point', () => {\n      const square = new PointArray(squareString)\n      const matrix = new Matrix(1, 2, 3, 4, 5, 6)\n      const newSquare = square.transform(matrix)\n      for (let i = 0; i < square.length; i++) {\n        const squarePoint = new Point(square[i])\n        const newSquarePoint = new Point(newSquare[i])\n        expect(squarePoint.transform(matrix)).toEqual(newSquarePoint)\n      }\n    })\n\n    it('works with transform object instead of matrix', () => {\n      const square = new PointArray(squareString)\n      const newSquare = square.transform({ translate: [2, 1] })\n      expect(newSquare.toString()).toEqual('2,1 3,1 3,2 2,2')\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/types/SVGArray.js",
    "content": "/* globals describe, expect, it, jasmine */\n\nimport { Array as SVGArray, PointArray, PathArray } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('SVGArray.js', () => {\n  describe('()', () => {\n    it('preallocates memory if only number is passed', () => {\n      const arr = new SVGArray(1)\n      expect(arr.length).toBe(1)\n    })\n\n    it('parses a matrix array correctly to string', () => {\n      const array = new SVGArray([\n        0.343, 0.669, 0.119, 0, 0, 0.249, -0.626, 0.13, 0, 0, 0.172, 0.334,\n        0.111, 0, 0, 0.0, 0.0, 0.0, 1, -0\n      ])\n\n      expect(array + '').toBe(\n        '0.343 0.669 0.119 0 0 0.249 -0.626 0.13 0 0 0.172 0.334 0.111 0 0 0 0 0 1 0'\n      )\n    })\n\n    it('parses space separated string and converts it to array', () => {\n      expect(new SVGArray('1 2 3 4').valueOf()).toEqual([1, 2, 3, 4])\n    })\n\n    it('parses comma separated string and converts it to array', () => {\n      expect(new SVGArray('1,2,3,4').valueOf()).toEqual([1, 2, 3, 4])\n    })\n  })\n\n  describe('reverse()', () => {\n    it('reverses the array', () => {\n      const array = new SVGArray([1, 2, 3, 4, 5]).reverse()\n      expect(array.valueOf()).toEqual([5, 4, 3, 2, 1])\n    })\n\n    it('returns itself', () => {\n      const array = new SVGArray()\n      expect(array.reverse()).toBe(array)\n    })\n  })\n\n  describe('clone()', () => {\n    it('creates a shallow clone of the array', () => {\n      const array = new SVGArray([1, 2, 3, 4, 5])\n      const clone = array.clone()\n\n      expect(array).toEqual(clone)\n      expect(array).not.toBe(clone)\n    })\n\n    it('also works with PointArray (one depths clone)', () => {\n      const array = new PointArray([\n        [1, 2],\n        [3, 4],\n        [5, 6]\n      ])\n      const clone = array.clone()\n\n      expect(array).toEqual(clone)\n      expect(array).not.toBe(clone)\n\n      for (let i = array.length; i--; ) {\n        expect(array[i]).not.toBe(clone[i])\n      }\n    })\n\n    it('also works with PathArray (one depths clone)', () => {\n      const array = new PathArray([\n        ['M', 1, 2],\n        ['L', 3, 4],\n        ['L', 5, 6]\n      ])\n      const clone = array.clone()\n\n      expect(array).toEqual(clone)\n      expect(array).not.toBe(clone)\n\n      for (let i = array.length; i--; ) {\n        expect(array[i]).not.toBe(clone[i])\n      }\n    })\n  })\n\n  describe('toSet()', () => {\n    it('creates a Set from the Array', () => {\n      const set = new SVGArray([1, 1, 2, 3]).toSet()\n      expect(set).toEqual(any(Set))\n      expect(set).toEqual(new Set([1, 2, 3]))\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/types/SVGNumber.js",
    "content": "/* globals describe, expect, it, beforeEach, jasmine */\n\nimport { Number as SVGNumber } from '../../../src/main.js'\n\nconst { any } = jasmine\n\ndescribe('Number.js', () => {\n  let number\n\n  beforeEach(() => {\n    number = new SVGNumber()\n  })\n\n  describe('()', () => {\n    it('is zero', () => {\n      expect(number.value).toBe(0)\n    })\n\n    it('has a blank unit', () => {\n      expect(number.unit).toBe('')\n    })\n\n    it('accepts the unit as a second argument', () => {\n      number = new SVGNumber(30, '%')\n      expect(number.value).toBe(30)\n      expect(number.unit).toBe('%')\n    })\n\n    it('parses a pixel value', () => {\n      number = new SVGNumber('20px')\n      expect(number.value).toBe(20)\n      expect(number.unit).toBe('px')\n    })\n\n    it('parses a percent value', () => {\n      number = new SVGNumber('99%')\n      expect(number.value).toBe(0.99)\n      expect(number.unit).toBe('%')\n    })\n\n    it('parses a seconds value', () => {\n      number = new SVGNumber('2s')\n      expect(number.value).toBe(2000)\n      expect(number.unit).toBe('s')\n    })\n\n    it('parses a negative percent value', () => {\n      number = new SVGNumber('-89%')\n      expect(number.value).toBe(-0.89)\n      expect(number.unit).toBe('%')\n    })\n\n    it('falls back to 0 if given value is NaN', () => {\n      number = new SVGNumber(NaN)\n      expect(number.value).toBe(0)\n    })\n\n    it('falls back to maximum value if given number is positive infinite', () => {\n      // eslint-disable-next-line no-loss-of-precision\n      number = new SVGNumber(1.7976931348623157e10308)\n      expect(number.value).toBe(3.4e38)\n    })\n\n    it('falls back to minimum value if given number is negative infinite', () => {\n      // eslint-disable-next-line no-loss-of-precision\n      number = new SVGNumber(-1.7976931348623157e10308)\n      expect(number.value).toBe(-3.4e38)\n    })\n  })\n\n  describe('toString()', () => {\n    it('converts the number to a string', () => {\n      expect(number.toString()).toBe('0')\n    })\n\n    it('appends the unit', () => {\n      number.value = 1.21\n      number.unit = 'px'\n      expect(number.toString()).toBe('1.21px')\n    })\n\n    it('converts percent values properly', () => {\n      number.value = 1.36\n      number.unit = '%'\n      expect(number.toString()).toBe('136%')\n    })\n\n    it('converts second values properly', () => {\n      number.value = 2500\n      number.unit = 's'\n      expect(number.toString()).toBe('2.5s')\n    })\n  })\n\n  describe('valueOf()', () => {\n    it('returns a numeric value for default units', () => {\n      expect(typeof number.valueOf()).toBe('number')\n      number = new SVGNumber('12')\n      expect(typeof number.valueOf()).toBe('number')\n      number = new SVGNumber(13)\n      expect(typeof number.valueOf()).toBe('number')\n    })\n\n    it('returns a numeric value for pixel units', () => {\n      number = new SVGNumber('10px')\n      expect(typeof number.valueOf()).toBe('number')\n    })\n\n    it('returns a numeric value for percent units', () => {\n      number = new SVGNumber('20%')\n      expect(typeof number.valueOf()).toBe('number')\n    })\n\n    it('converts to a primitive when multiplying', () => {\n      number.value = 80\n      expect(number * 4).toBe(320)\n    })\n  })\n\n  describe('plus()', () => {\n    it('returns a new instance', () => {\n      expect(number.plus(4.5)).not.toBe(number)\n      expect(number.plus(4.5)).toEqual(any(SVGNumber))\n    })\n\n    it('adds a given number', () => {\n      expect(number.plus(3.5).valueOf()).toBe(3.5)\n    })\n\n    it('adds a given percentage value', () => {\n      expect(number.plus('225%').valueOf()).toBe(2.25)\n    })\n\n    it('adds a given pixel value', () => {\n      expect(number.plus('83px').valueOf()).toBe(83)\n    })\n\n    it('use the unit of this number as the unit of the returned number by default', () => {\n      expect(new SVGNumber('12s').plus('3%').unit).toBe('s')\n    })\n\n    it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => {\n      expect(number.plus('15%').unit).toBe('%')\n    })\n  })\n\n  describe('minus()', () => {\n    it('subtracts a given number', () => {\n      expect(number.minus(3.7).valueOf()).toBe(-3.7)\n    })\n\n    it('subtracts a given percentage value', () => {\n      expect(number.minus('223%').valueOf()).toBe(-2.23)\n    })\n\n    it('subtracts a given pixel value', () => {\n      expect(number.minus('85px').valueOf()).toBe(-85)\n    })\n\n    it('use the unit of this number as the unit of the returned number by default', () => {\n      expect(new SVGNumber('12s').minus('3%').unit).toBe('s')\n    })\n\n    it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => {\n      expect(number.minus('15%').unit).toBe('%')\n    })\n  })\n\n  describe('times()', () => {\n    beforeEach(() => {\n      number = number.plus(4)\n    })\n\n    it('multiplies with a given number', () => {\n      expect(number.times(3).valueOf()).toBe(12)\n    })\n\n    it('multiplies with a given percentage value', () => {\n      expect(number.times('110%').valueOf()).toBe(4.4)\n    })\n\n    it('multiplies with a given pixel value', () => {\n      expect(number.times('85px').valueOf()).toBe(340)\n    })\n\n    it('use the unit of this number as the unit of the returned number by default', () => {\n      expect(new SVGNumber('12s').times('3%').unit).toBe('s')\n    })\n\n    it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => {\n      expect(number.times('15%').unit).toBe('%')\n    })\n  })\n\n  describe('divide()', () => {\n    beforeEach(() => {\n      number = number.plus(90)\n    })\n\n    it('divides by a given number', () => {\n      expect(number.divide(3).valueOf()).toBe(30)\n    })\n\n    it('divides by a given percentage value', () => {\n      expect(number.divide('3000%').valueOf()).toBe(3)\n    })\n\n    it('divides by a given pixel value', () => {\n      expect(number.divide('45px').valueOf()).toBe(2)\n    })\n\n    it('use the unit of this number as the unit of the returned number by default', () => {\n      expect(new SVGNumber('12s').divide('3%').unit).toBe('s')\n    })\n\n    it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => {\n      expect(number.divide('15%').unit).toBe('%')\n    })\n  })\n\n  describe('convert()', () => {\n    it('changes the unit of the number', () => {\n      const number = new SVGNumber('12px').convert('%')\n      expect(number.toString()).toBe('1200%')\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/utils/adopter.js",
    "content": "/* globals describe, expect, it, beforeEach, afterEach, jasmine */\n\nimport {\n  create,\n  makeInstance,\n  nodeOrNew,\n  register,\n  getClass,\n  eid,\n  extend,\n  wrapWithAttrCheck,\n  Rect,\n  Element,\n  root,\n  G,\n  Gradient,\n  Dom,\n  Path,\n  Fragment\n} from '../../../src/main.js'\n\nimport { mockAdopt, assignNewId, adopt } from '../../../src/utils/adopter.js'\nimport { buildFixtures } from '../../helpers.js'\nimport { globals, getWindow } from '../../../src/utils/window.js'\nimport { svg } from '../../../src/modules/core/namespaces.js'\n\nconst { any, createSpy, objectContaining } = jasmine\n\ndescribe('adopter.js', () => {\n  let Node\n\n  beforeEach(() => {\n    Node = globals.window.Node\n  })\n\n  describe('create()', () => {\n    it('creates a node of the specified type', () => {\n      const rect = create('rect')\n      expect(rect).toEqual(any(Node))\n      expect(rect.nodeName).toBe('rect')\n    })\n  })\n\n  describe('makeInstance()', () => {\n    const adoptSpy = createSpy('adopt', adopt).and.callThrough()\n\n    beforeEach(() => {\n      adoptSpy.calls.reset()\n      mockAdopt(adoptSpy)\n    })\n\n    afterEach(() => {\n      mockAdopt()\n    })\n\n    it('creates a root-object when no argument given', () => {\n      const doc = makeInstance()\n\n      expect(doc).toEqual(any(getClass(root)))\n      expect(doc).toEqual(any(Element))\n    })\n\n    it('returns a given svg.js object directly', () => {\n      const rect = new Rect()\n      const samerect = makeInstance(rect)\n      expect(rect).toBe(samerect)\n    })\n\n    it('creates an element from passed svg string', () => {\n      const rect = makeInstance('<rect width=\"200px\" />')\n\n      expect(adoptSpy).toHaveBeenCalledWith(any(Node))\n      expect(adoptSpy).toHaveBeenCalledWith(\n        objectContaining({ nodeName: 'rect' })\n      )\n      expect(rect).toEqual(any(Rect))\n      expect(rect.parent()).toBe(null)\n    })\n\n    it('ignores leading comments', () => {\n      const rect = makeInstance('<!-- Comment --><rect width=\"200px\" />')\n      expect(adoptSpy).toHaveBeenCalledWith(any(Node))\n      expect(adoptSpy).toHaveBeenCalledWith(\n        objectContaining({ nodeName: 'rect' })\n      )\n      expect(rect).toEqual(any(Rect))\n      expect(rect.parent()).toBe(null)\n    })\n\n    it('creates an element in the html namespace from passed html string', () => {\n      const div = makeInstance('<div />', true)\n\n      expect(adoptSpy).toHaveBeenCalledWith(any(Node))\n      expect(adoptSpy).toHaveBeenCalledWith(\n        objectContaining({\n          nodeName: 'DIV',\n          namespaceURI: 'http://www.w3.org/1999/xhtml'\n        })\n      )\n      expect(div).toEqual(any(Dom))\n      expect(div.parent()).toBe(null)\n    })\n\n    it('does not have its wrapper attached', () => {\n      const rect = makeInstance('<rect width=\"200px\" />')\n      expect(rect.parent()).toBe(null)\n    })\n\n    it('searches for element in dom if selector given', () => {\n      buildFixtures()\n\n      const path = globals.window.document.getElementById('lineAB')\n\n      const pathInst = makeInstance('#lineAB')\n      const noEl = makeInstance('#doesNotExist')\n\n      expect(adoptSpy).toHaveBeenCalledWith(path)\n      expect(adoptSpy).toHaveBeenCalledWith(null)\n      expect(pathInst).toEqual(any(Path))\n      expect(noEl).toBe(null)\n    })\n\n    it('calls adopt when passed a node', () => {\n      const rect = makeInstance(create('rect'))\n\n      expect(adoptSpy).toHaveBeenCalledWith(any(Node))\n      expect(adoptSpy).toHaveBeenCalledWith(\n        objectContaining({ nodeName: 'rect' })\n      )\n      expect(rect).toEqual(any(Rect))\n    })\n  })\n\n  describe('adopt()', () => {\n    it('returns null of passed node is null', () => {\n      expect(adopt(null)).toBe(null)\n    })\n\n    it('returns instance from node if present', () => {\n      const rect = new Rect()\n      expect(adopt(rect.node)).toBe(rect)\n    })\n\n    it('creates Fragment when document fragment is passed', () => {\n      const frag = getWindow().document.createDocumentFragment()\n      expect(adopt(frag)).toEqual(any(Fragment))\n    })\n\n    it('creates instance when node without instance is passed', () => {\n      const rect = new Rect()\n      const node = rect.node\n      delete node.instance\n      expect(adopt(node)).toEqual(any(Rect))\n      expect(adopt(node)).not.toBe(rect)\n    })\n\n    it('creates instance when node without instance is passed with gradients', () => {\n      const gradient = new Gradient('linear')\n      const node = gradient.node\n      delete node.instance\n      expect(adopt(node)).toEqual(any(Gradient))\n      expect(adopt(node).type).toBe('linearGradient')\n      expect(adopt(node)).not.toBe(gradient)\n    })\n\n    it('creates Dom instances for unknown nodes', () => {\n      const div = getWindow().document.createElement('div')\n      expect(adopt(div)).toEqual(any(Dom))\n    })\n  })\n\n  describe('nodeOrNew()', () => {\n    it('creates a node of node argument is null', () => {\n      const rect = nodeOrNew('rect', null)\n      expect(rect).toEqual(any(Node))\n      expect(rect.nodeName).toBe('rect')\n    })\n\n    it('returns the node if one is passed', () => {\n      const div = globals.window.document.createElement('div')\n      const node = nodeOrNew('something', div)\n\n      // jasmine chucks on this when using the node directly\n      expect(node.outerHTML).toBe(div.outerHTML)\n    })\n\n    it('gracefully handles nodes that are not yet imported into the document', () => {\n      const otherDoc = globals.document.implementation.createDocument(\n        svg,\n        'svg'\n      )\n      const rect = otherDoc.createElementNS(svg, 'rect')\n\n      const node = nodeOrNew('rect', rect)\n\n      expect(node).toEqual(rect)\n    })\n  })\n\n  describe('register()/getClass()', () => {\n    it('sets and gets a class from the class register', () => {\n      const A = class {}\n      register(A)\n      expect(getClass('A')).toBe(A)\n    })\n  })\n\n  describe('eid()', () => {\n    it('returns a unique id', () => {\n      expect(eid('foo')).not.toBe(eid('foo'))\n    })\n  })\n\n  describe('assignNewId()', () => {\n    it('assigns a new id if id is present on element', () => {\n      const rect = new Rect().id('foo')\n      assignNewId(rect.node)\n      expect(rect.id()).not.toBe('foo')\n    })\n\n    it('does not set id if no id is present on element', () => {\n      const rect = new Rect()\n      assignNewId(rect.node)\n      expect(rect.attr('id')).toBe(undefined)\n    })\n\n    it('recursively sets new ids on children', () => {\n      const group = new G().id('foo')\n      const rect = group.rect(100, 100).id('bar')\n      assignNewId(group.node)\n      expect(group.id()).not.toBe('foo')\n      expect(rect.id()).not.toBe('bar')\n    })\n  })\n\n  describe('extend()', () => {\n    it('adds all functions in the given object to the target object', () => {\n      const A = class {}\n\n      extend(A, {\n        test() {\n          this.prop = 'test'\n          return this\n        }\n      })\n\n      expect(typeof A.prototype.test).toBe('function')\n      expect(new A().test().prop).toBe('test')\n    })\n\n    it('accepts and extend multiple modules at once', () => {\n      const A = class {}\n      const B = class {}\n      const C = class {}\n\n      extend([A, B, C], {\n        test() {\n          this.prop = 'test'\n          return this\n        }\n      })\n\n      expect(typeof A.prototype.test).toBe('function')\n      expect(new A().test().prop).toBe('test')\n      expect(typeof B.prototype.test).toBe('function')\n      expect(new B().test().prop).toBe('test')\n      expect(typeof C.prototype.test).toBe('function')\n      expect(new C().test().prop).toBe('test')\n    })\n  })\n\n  describe('wrapWithAttrCheck()', () => {\n    it('wraps a function so that it calls an attr function if an object is passed', () => {\n      const attrSpy = createSpy('attr')\n\n      const A = class {}\n      extend(A, {\n        test: wrapWithAttrCheck(function () {\n          this.prop = 'test'\n          return this\n        }),\n        attr: attrSpy\n      })\n\n      const obj = {}\n\n      expect(new A().test().prop).toBe('test')\n      expect(attrSpy).not.toHaveBeenCalled()\n      new A().test(obj)\n      expect(attrSpy).toHaveBeenCalledWith(obj)\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/utils/methods.js",
    "content": "/* globals describe, expect, it */\n\nimport {\n  registerMethods,\n  getMethodsFor,\n  getMethodNames\n} from '../../../src/utils/methods.js'\n\ndescribe('methods.js', () => {\n  describe('registerMethods() / getMethodsFor() / addMethodNames / getMethodNames()', () => {\n    it('adds methods for a given type of classes with object given', () => {\n      const foo = {\n        func1: () => {}\n      }\n      registerMethods({ foo })\n\n      expect(getMethodsFor('foo')).toEqual(foo)\n    })\n\n    it('adds methods for a given type of classes with 2 parameters given', () => {\n      const foo = {\n        func1: () => {}\n      }\n      registerMethods('foo', foo)\n\n      expect(getMethodsFor('foo')).toEqual(foo)\n    })\n\n    it('adds a method name', () => {\n      registerMethods({ bar: { func2: () => {} } })\n      expect(getMethodNames()).toContain('func2')\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/utils/pathParser.js",
    "content": "/* globals describe expect it */\n\nimport { pathParser } from '../../../src/utils/pathParser.js'\n\ndescribe('pathParser.js', () => {\n  describe('pathParser()', () => {\n    it('parses all paths correctly', () => {\n      expect(pathParser('M2,0a2 2 0 00-2 2a2 2 0 002 2a.5.5 0 011 0z')).toEqual(\n        [\n          ['M', 2, 0],\n          ['A', 2, 2, 0, 0, 0, 0, 2],\n          ['A', 2, 2, 0, 0, 0, 2, 4],\n          ['A', 0.5, 0.5, 0, 0, 1, 3, 4],\n          ['Z']\n        ]\n      )\n\n      expect(pathParser('M2,0a2 2 0 00-2 2a2 2 0 002 2a.5.5 0 111 0z')).toEqual(\n        [\n          ['M', 2, 0],\n          ['A', 2, 2, 0, 0, 0, 0, 2],\n          ['A', 2, 2, 0, 0, 0, 2, 4],\n          ['A', 0.5, 0.5, 0, 1, 1, 3, 4],\n          ['Z']\n        ]\n      )\n\n      expect(pathParser('m10 10 h 80 v 80 h -80 l 300 400 z')).toEqual([\n        ['M', 10, 10],\n        ['H', 90],\n        ['V', 90],\n        ['H', 10],\n        ['L', 310, 490],\n        ['Z']\n      ])\n\n      expect(\n        pathParser(\n          'm10 80 c 40 10 65 10 95 80 s 150 150 180 80 t 300 300 q 52 10 95 80 z'\n        )\n      ).toEqual([\n        ['M', 10, 80],\n        ['C', 50, 90, 75, 90, 105, 160],\n        ['S', 255, 310, 285, 240],\n        ['T', 585, 540],\n        ['Q', 637, 550, 680, 620],\n        ['Z']\n      ])\n\n      expect(pathParser('m80 80 A 45 45, 0, 0, 0, 125 125 L 125 80 z')).toEqual(\n        [['M', 80, 80], ['A', 45, 45, 0, 0, 0, 125, 125], ['L', 125, 80], ['Z']]\n      )\n\n      expect(\n        pathParser(\n          'M215.458,245.23c0,0,77.403,0,94.274,0S405,216.451,405,138.054S329.581,15,287.9,15c-41.68,0-139.924,0-170.688,0C86.45,15,15,60.65,15,134.084c0,73.434,96.259,112.137,114.122,112.137C146.984,246.221,215.458,245.23,215.458,245.23z'\n        )\n      ).toEqual([\n        ['M', 215.458, 245.23],\n        ['C', 215.458, 245.23, 292.861, 245.23, 309.73199999999997, 245.23],\n        ['S', 405, 216.451, 405, 138.054],\n        ['S', 329.581, 15, 287.9, 15],\n        [\n          'C',\n          246.21999999999997,\n          15,\n          147.97599999999997,\n          15,\n          117.21199999999999,\n          15\n        ],\n        ['C', 86.45, 15, 15, 60.65, 15, 134.084],\n        ['C', 15, 207.518, 111.259, 246.221, 129.122, 246.221],\n        ['C', 146.984, 246.221, 215.458, 245.23, 215.458, 245.23],\n        ['Z']\n      ])\n\n      expect(\n        pathParser('M10 10-45-30.5.5 .89L2e-2.5.5-.5C.5.5.5.5.5.5L-3-4z')\n      ).toEqual([\n        ['M', 10, 10],\n        ['L', -45, -30.5],\n        ['L', 0.5, 0.89],\n        ['L', 0.02, 0.5],\n        ['L', 0.5, -0.5],\n        ['C', 0.5, 0.5, 0.5, 0.5, 0.5, 0.5],\n        ['L', -3, -4],\n        ['Z']\n      ])\n\n      expect(\n        pathParser(\n          'm 0,0 0,3189 2209,0 0,-3189 -2209,0 z m 154,154 1901,0 0,2881 -1901,0 0,-2881 z'\n        )\n      ).toEqual([\n        ['M', 0, 0],\n        ['L', 0, 3189],\n        ['L', 2209, 3189],\n        ['L', 2209, 0],\n        ['L', 0, 0],\n        ['Z'],\n        ['M', 154, 154],\n        ['L', 2055, 154],\n        ['L', 2055, 3035],\n        ['L', 154, 3035],\n        ['L', 154, 154],\n        ['Z']\n      ])\n\n      expect(pathParser('m 0,0 a 45 45, 0, 0, 0, 125 125')).toEqual([\n        ['M', 0, 0],\n        ['A', 45, 45, 0, 0, 0, 125, 125]\n      ])\n\n      expect(pathParser('M10 10 80 80 30 30 Z')).toEqual([\n        ['M', 10, 10],\n        ['L', 80, 80],\n        ['L', 30, 30],\n        ['Z']\n      ])\n\n      expect(pathParser('M10 10L.5.5.3.3Z')).toEqual([\n        ['M', 10, 10],\n        ['L', 0.5, 0.5],\n        ['L', 0.3, 0.3],\n        ['Z']\n      ])\n\n      // \"a\" commands without optional whitespace around the flag params and ending coordinate pair\n      expect(pathParser('a32 32 0 00.03-45.22', false)).toEqual([\n        ['a', 32.0, 32.0, 0.0, 0.0, 0.0, 0.03, -45.22]\n      ])\n\n      expect(pathParser('a48 48 0 1148-48', false)).toEqual([\n        ['a', 48.0, 48.0, 0.0, 1.0, 1.0, 48.0, -48.0]\n      ])\n\n      expect(pathParser('a82.6 82.6 0 0033.48-20.25', false)).toEqual([\n        ['a', 82.6, 82.6, 0.0, 0.0, 0.0, 33.48, -20.25]\n      ])\n\n      expect(pathParser('a82.45 82.45 0 00-20.24 33.47', false)).toEqual([\n        ['a', 82.45, 82.45, 0.0, 0.0, 0.0, -20.24, 33.47]\n      ])\n\n      expect(pathParser('a2.51 2.51 0 01.25.32', false)).toEqual([\n        ['a', 2.51, 2.51, 0, 0, 1, 0.25, 0.32]\n      ])\n\n      expect(pathParser('a2.51 2.51 0 00.25.32', false)).toEqual([\n        ['a', 2.51, 2.51, 0, 0, 0, 0.25, 0.32]\n      ])\n\n      expect(pathParser('a2.51 2.51 0 000.25.32', false)).toEqual([\n        ['a', 2.51, 2.51, 0, 0, 0, 0.25, 0.32]\n      ])\n\n      expect(pathParser('a48 48 0 1148-48 48 48 0 01-48 48', false)).toEqual([\n        ['a', 48.0, 48.0, 0.0, 1.0, 1.0, 48.0, -48.0],\n        ['a', 48.0, 48.0, 0.0, 0.0, 1.0, -48.0, 48.0]\n      ])\n\n      expect(pathParser('M0+0 L100+0 L50+100')).toEqual([\n        ['M', 0, 0],\n        ['L', 100, 0],\n        ['L', 50, 100]\n      ])\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/utils/utils.js",
    "content": "/* globals describe, expect, it, beforeEach, jasmine */\n\nimport {\n  map,\n  filter,\n  radians,\n  degrees,\n  unCamelCase,\n  capitalize,\n  proportionalSize,\n  getOrigin\n} from '../../../src/utils/utils.js'\n\nconst { any } = jasmine\n\ndescribe('utils.js', function () {\n  describe('map()', function () {\n    var arr1\n    var arr2\n\n    beforeEach(function () {\n      arr1 = [1, 2, 3, 4]\n      arr2 = map(arr1, function (el) {\n        return el * 2\n      })\n    })\n\n    it('returns a new array', function () {\n      expect(arr2).toEqual(any(Array))\n      expect(arr2).not.toBe(arr1)\n    })\n\n    it('executes a function on every element and returns the result in a new array', function () {\n      expect(arr2).toEqual([2, 4, 6, 8])\n    })\n  })\n\n  describe('filter()', function () {\n    var arr1\n    var arr2\n\n    beforeEach(function () {\n      arr1 = [1, 2, 3, 4]\n      arr2 = filter(arr1, function (el) {\n        return el % 2 === 0\n      })\n    })\n\n    it('returns a new array', function () {\n      expect(arr2).toEqual(any(Array))\n      expect(arr2).not.toBe(arr1)\n    })\n\n    it('filters elements by function', function () {\n      expect(arr2).toEqual([2, 4])\n    })\n  })\n\n  describe('radians()', function () {\n    it('converts degrees to radians', function () {\n      expect(radians(270)).toBe(1.5 * Math.PI)\n      expect(radians(90)).toBe(Math.PI / 2)\n    })\n\n    it('caps at 360 degrees', function () {\n      expect(radians(360)).toBe(0)\n      expect(radians(360 + 180)).toBe(Math.PI)\n    })\n  })\n\n  describe('degrees()', function () {\n    it('converts radians to degrees', function () {\n      expect(degrees(1.5 * Math.PI)).toBe(270)\n      expect(degrees(Math.PI / 2)).toBe(90)\n    })\n\n    it('caps at 2 PI', function () {\n      expect(degrees(2 * Math.PI)).toBe(0)\n      expect(degrees(3 * Math.PI)).toBe(180)\n    })\n  })\n\n  describe('unCamelCase()', function () {\n    it('converts camelCase to dash-case', function () {\n      var dash1 = 'dash-1'\n      var dashTwo = 'dash-two'\n      var camelOne = 'camelOne'\n      var pascalOne = 'PascalOne'\n\n      expect(unCamelCase(dash1)).toBe('dash-1')\n      expect(unCamelCase(dashTwo)).toBe('dash-two')\n      expect(unCamelCase(camelOne)).toBe('camel-one')\n      expect(unCamelCase(pascalOne)).toBe('-pascal-one')\n    })\n  })\n\n  describe('capitalize()', function () {\n    it('capitalizes the first letter', function () {\n      var dash1 = 'dash-1'\n      var dashTwo = 'dash-two'\n      var camelOne = 'camelOne'\n      var pascalOne = 'PascalOne'\n\n      expect(capitalize(dash1)).toBe('Dash-1')\n      expect(capitalize(dashTwo)).toBe('Dash-two')\n      expect(capitalize(camelOne)).toBe('CamelOne')\n      expect(capitalize(pascalOne)).toBe('PascalOne')\n    })\n  })\n\n  describe('proportionalSize()', function () {\n    var box = { width: 150, height: 100 }\n    var el = { bbox: () => ({ width: 200, height: 100 }) }\n\n    it('calculates height proportionally', function () {\n      expect(proportionalSize(el, 400, null)).toEqual({\n        width: 400,\n        height: 200\n      })\n    })\n\n    it('calculates width proportionally', function () {\n      expect(proportionalSize(el, null, 200)).toEqual({\n        width: 400,\n        height: 200\n      })\n    })\n\n    it('prefers passed box over element', function () {\n      expect(proportionalSize(el, 300, null, box)).toEqual({\n        width: 300,\n        height: 200\n      })\n      expect(proportionalSize(el, null, 200, box)).toEqual({\n        width: 300,\n        height: 200\n      })\n    })\n  })\n\n  describe('getOrigin()', function () {\n    var el = { bbox: () => ({ width: 200, height: 100, x: 300, y: 400 }) }\n\n    it('gets the origin from [ox, oy]', function () {\n      var origin = { origin: [10, 20] }\n      expect(getOrigin(origin, el)).toEqual([10, 20])\n    })\n\n    it('gets the origin from [ox, oy] as strings', function () {\n      var origin = { origin: ['center', 'top'] }\n      expect(getOrigin(origin, el)).toEqual([400, 400])\n    })\n\n    it('gets the origin from {x, y}', function () {\n      var origin = { origin: { x: 10, y: 20 } }\n      expect(getOrigin(origin, el)).toEqual([10, 20])\n    })\n\n    it('gets the origin from {ox, oy}', function () {\n      var origin = { ox: 10, oy: 20 }\n      expect(getOrigin(origin, el)).toEqual([10, 20])\n    })\n\n    it('gets the origin from {ox, oy} as strings', function () {\n      var origin = { ox: 'center', oy: 'top' }\n      expect(getOrigin(origin, el)).toEqual([400, 400])\n    })\n\n    it('gets the origin from {originX, originY}', function () {\n      var origin = { originX: 10, originY: 20 }\n      expect(getOrigin(origin, el)).toEqual([10, 20])\n    })\n\n    it('gets the origin from {originX, originY} as strings', function () {\n      var origin = { originX: 'center', originY: 'top' }\n      expect(getOrigin(origin, el)).toEqual([400, 400])\n    })\n\n    it('gets the origin from string', function () {\n      var origin = { origin: 'center top' }\n      expect(getOrigin(origin, el)).toEqual([400, 400])\n    })\n\n    it('gets the origin from number', function () {\n      var origin = { origin: 5 }\n      expect(getOrigin(origin, el)).toEqual([5, 5])\n    })\n  })\n})\n"
  },
  {
    "path": "spec/spec/utils/window.js",
    "content": "/* globals describe, expect, it */\n\nimport {\n  registerWindow,\n  globals,\n  withWindow,\n  getWindow,\n  saveWindow,\n  restoreWindow\n} from '../../../src/utils/window.js'\n\ndescribe('window.js', () => {\n  describe('registerWindow()', () => {\n    it('sets a new window as global', () => {\n      saveWindow()\n      const win = {}\n      const doc = {}\n      registerWindow(win, doc)\n      expect(globals.window).toBe(win)\n      expect(globals.document).toBe(doc)\n      restoreWindow() // we need this or jasmine will fail in afterAll\n    })\n  })\n\n  describe('withWindow()', () => {\n    it('runs a function in the specified window context', () => {\n      const win = { foo: 'bar', document: {} }\n      const oldWindow = globals.window\n      expect(globals.window).not.toBe(win)\n      withWindow({ foo: 'bar', document: {} }, () => {\n        expect(globals.window).toEqual(win)\n        expect(globals.document).toEqual(win.document)\n      })\n      expect(globals.window).toBe(oldWindow)\n    })\n  })\n\n  describe('getWindow()', () => {\n    it('returns the registered window', () => {\n      expect(getWindow()).toBe(globals.window)\n    })\n  })\n})\n"
  },
  {
    "path": "src/animation/Animator.js",
    "content": "import { globals } from '../utils/window.js'\nimport Queue from './Queue.js'\n\nconst Animator = {\n  nextDraw: null,\n  frames: new Queue(),\n  timeouts: new Queue(),\n  immediates: new Queue(),\n  timer: () => globals.window.performance || globals.window.Date,\n  transforms: [],\n\n  frame(fn) {\n    // Store the node\n    const node = Animator.frames.push({ run: fn })\n\n    // Request an animation frame if we don't have one\n    if (Animator.nextDraw === null) {\n      Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw)\n    }\n\n    // Return the node so we can remove it easily\n    return node\n  },\n\n  timeout(fn, delay) {\n    delay = delay || 0\n\n    // Work out when the event should fire\n    const time = Animator.timer().now() + delay\n\n    // Add the timeout to the end of the queue\n    const node = Animator.timeouts.push({ run: fn, time: time })\n\n    // Request another animation frame if we need one\n    if (Animator.nextDraw === null) {\n      Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw)\n    }\n\n    return node\n  },\n\n  immediate(fn) {\n    // Add the immediate fn to the end of the queue\n    const node = Animator.immediates.push(fn)\n    // Request another animation frame if we need one\n    if (Animator.nextDraw === null) {\n      Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw)\n    }\n\n    return node\n  },\n\n  cancelFrame(node) {\n    node != null && Animator.frames.remove(node)\n  },\n\n  clearTimeout(node) {\n    node != null && Animator.timeouts.remove(node)\n  },\n\n  cancelImmediate(node) {\n    node != null && Animator.immediates.remove(node)\n  },\n\n  _draw(now) {\n    // Run all the timeouts we can run, if they are not ready yet, add them\n    // to the end of the queue immediately! (bad timeouts!!! [sarcasm])\n    let nextTimeout = null\n    const lastTimeout = Animator.timeouts.last()\n    while ((nextTimeout = Animator.timeouts.shift())) {\n      // Run the timeout if its time, or push it to the end\n      if (now >= nextTimeout.time) {\n        nextTimeout.run()\n      } else {\n        Animator.timeouts.push(nextTimeout)\n      }\n\n      // If we hit the last item, we should stop shifting out more items\n      if (nextTimeout === lastTimeout) break\n    }\n\n    // Run all of the animation frames\n    let nextFrame = null\n    const lastFrame = Animator.frames.last()\n    while (nextFrame !== lastFrame && (nextFrame = Animator.frames.shift())) {\n      nextFrame.run(now)\n    }\n\n    let nextImmediate = null\n    while ((nextImmediate = Animator.immediates.shift())) {\n      nextImmediate()\n    }\n\n    // If we have remaining timeouts or frames, draw until we don't anymore\n    Animator.nextDraw =\n      Animator.timeouts.first() || Animator.frames.first()\n        ? globals.window.requestAnimationFrame(Animator._draw)\n        : null\n  }\n}\n\nexport default Animator\n"
  },
  {
    "path": "src/animation/Controller.js",
    "content": "import { timeline } from '../modules/core/defaults.js'\nimport { extend } from '../utils/adopter.js'\n\n/***\nBase Class\n==========\nThe base stepper class that will be\n***/\n\nfunction makeSetterGetter(k, f) {\n  return function (v) {\n    if (v == null) return this[k]\n    this[k] = v\n    if (f) f.call(this)\n    return this\n  }\n}\n\nexport const easing = {\n  '-': function (pos) {\n    return pos\n  },\n  '<>': function (pos) {\n    return -Math.cos(pos * Math.PI) / 2 + 0.5\n  },\n  '>': function (pos) {\n    return Math.sin((pos * Math.PI) / 2)\n  },\n  '<': function (pos) {\n    return -Math.cos((pos * Math.PI) / 2) + 1\n  },\n  bezier: function (x1, y1, x2, y2) {\n    // see https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo\n    return function (t) {\n      if (t < 0) {\n        if (x1 > 0) {\n          return (y1 / x1) * t\n        } else if (x2 > 0) {\n          return (y2 / x2) * t\n        } else {\n          return 0\n        }\n      } else if (t > 1) {\n        if (x2 < 1) {\n          return ((1 - y2) / (1 - x2)) * t + (y2 - x2) / (1 - x2)\n        } else if (x1 < 1) {\n          return ((1 - y1) / (1 - x1)) * t + (y1 - x1) / (1 - x1)\n        } else {\n          return 1\n        }\n      } else {\n        return 3 * t * (1 - t) ** 2 * y1 + 3 * t ** 2 * (1 - t) * y2 + t ** 3\n      }\n    }\n  },\n  // see https://www.w3.org/TR/css-easing-1/#step-timing-function-algo\n  steps: function (steps, stepPosition = 'end') {\n    // deal with \"jump-\" prefix\n    stepPosition = stepPosition.split('-').reverse()[0]\n\n    let jumps = steps\n    if (stepPosition === 'none') {\n      --jumps\n    } else if (stepPosition === 'both') {\n      ++jumps\n    }\n\n    // The beforeFlag is essentially useless\n    return (t, beforeFlag = false) => {\n      // Step is called currentStep in referenced url\n      let step = Math.floor(t * steps)\n      const jumping = (t * step) % 1 === 0\n\n      if (stepPosition === 'start' || stepPosition === 'both') {\n        ++step\n      }\n\n      if (beforeFlag && jumping) {\n        --step\n      }\n\n      if (t >= 0 && step < 0) {\n        step = 0\n      }\n\n      if (t <= 1 && step > jumps) {\n        step = jumps\n      }\n\n      return step / jumps\n    }\n  }\n}\n\nexport class Stepper {\n  done() {\n    return false\n  }\n}\n\n/***\nEasing Functions\n================\n***/\n\nexport class Ease extends Stepper {\n  constructor(fn = timeline.ease) {\n    super()\n    this.ease = easing[fn] || fn\n  }\n\n  step(from, to, pos) {\n    if (typeof from !== 'number') {\n      return pos < 1 ? from : to\n    }\n    return from + (to - from) * this.ease(pos)\n  }\n}\n\n/***\nController Types\n================\n***/\n\nexport class Controller extends Stepper {\n  constructor(fn) {\n    super()\n    this.stepper = fn\n  }\n\n  done(c) {\n    return c.done\n  }\n\n  step(current, target, dt, c) {\n    return this.stepper(current, target, dt, c)\n  }\n}\n\nfunction recalculate() {\n  // Apply the default parameters\n  const duration = (this._duration || 500) / 1000\n  const overshoot = this._overshoot || 0\n\n  // Calculate the PID natural response\n  const eps = 1e-10\n  const pi = Math.PI\n  const os = Math.log(overshoot / 100 + eps)\n  const zeta = -os / Math.sqrt(pi * pi + os * os)\n  const wn = 3.9 / (zeta * duration)\n\n  // Calculate the Spring values\n  this.d = 2 * zeta * wn\n  this.k = wn * wn\n}\n\nexport class Spring extends Controller {\n  constructor(duration = 500, overshoot = 0) {\n    super()\n    this.duration(duration).overshoot(overshoot)\n  }\n\n  step(current, target, dt, c) {\n    if (typeof current === 'string') return current\n    c.done = dt === Infinity\n    if (dt === Infinity) return target\n    if (dt === 0) return current\n\n    if (dt > 100) dt = 16\n\n    dt /= 1000\n\n    // Get the previous velocity\n    const velocity = c.velocity || 0\n\n    // Apply the control to get the new position and store it\n    const acceleration = -this.d * velocity - this.k * (current - target)\n    const newPosition = current + velocity * dt + (acceleration * dt * dt) / 2\n\n    // Store the velocity\n    c.velocity = velocity + acceleration * dt\n\n    // Figure out if we have converged, and if so, pass the value\n    c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002\n    return c.done ? target : newPosition\n  }\n}\n\nextend(Spring, {\n  duration: makeSetterGetter('_duration', recalculate),\n  overshoot: makeSetterGetter('_overshoot', recalculate)\n})\n\nexport class PID extends Controller {\n  constructor(p = 0.1, i = 0.01, d = 0, windup = 1000) {\n    super()\n    this.p(p).i(i).d(d).windup(windup)\n  }\n\n  step(current, target, dt, c) {\n    if (typeof current === 'string') return current\n    c.done = dt === Infinity\n\n    if (dt === Infinity) return target\n    if (dt === 0) return current\n\n    const p = target - current\n    let i = (c.integral || 0) + p * dt\n    const d = (p - (c.error || 0)) / dt\n    const windup = this._windup\n\n    // antiwindup\n    if (windup !== false) {\n      i = Math.max(-windup, Math.min(i, windup))\n    }\n\n    c.error = p\n    c.integral = i\n\n    c.done = Math.abs(p) < 0.001\n\n    return c.done ? target : current + (this.P * p + this.I * i + this.D * d)\n  }\n}\n\nextend(PID, {\n  windup: makeSetterGetter('_windup'),\n  p: makeSetterGetter('P'),\n  i: makeSetterGetter('I'),\n  d: makeSetterGetter('D')\n})\n"
  },
  {
    "path": "src/animation/Morphable.js",
    "content": "import { Ease } from './Controller.js'\nimport {\n  delimiter,\n  numberAndUnit,\n  isPathLetter\n} from '../modules/core/regex.js'\nimport { extend } from '../utils/adopter.js'\nimport Color from '../types/Color.js'\nimport PathArray from '../types/PathArray.js'\nimport SVGArray from '../types/SVGArray.js'\nimport SVGNumber from '../types/SVGNumber.js'\n\nconst getClassForType = (value) => {\n  const type = typeof value\n\n  if (type === 'number') {\n    return SVGNumber\n  } else if (type === 'string') {\n    if (Color.isColor(value)) {\n      return Color\n    } else if (delimiter.test(value)) {\n      return isPathLetter.test(value) ? PathArray : SVGArray\n    } else if (numberAndUnit.test(value)) {\n      return SVGNumber\n    } else {\n      return NonMorphable\n    }\n  } else if (morphableTypes.indexOf(value.constructor) > -1) {\n    return value.constructor\n  } else if (Array.isArray(value)) {\n    return SVGArray\n  } else if (type === 'object') {\n    return ObjectBag\n  } else {\n    return NonMorphable\n  }\n}\n\nexport default class Morphable {\n  constructor(stepper) {\n    this._stepper = stepper || new Ease('-')\n\n    this._from = null\n    this._to = null\n    this._type = null\n    this._context = null\n    this._morphObj = null\n  }\n\n  at(pos) {\n    return this._morphObj.morph(\n      this._from,\n      this._to,\n      pos,\n      this._stepper,\n      this._context\n    )\n  }\n\n  done() {\n    const complete = this._context.map(this._stepper.done).reduce(function (\n      last,\n      curr\n    ) {\n      return last && curr\n    }, true)\n    return complete\n  }\n\n  from(val) {\n    if (val == null) {\n      return this._from\n    }\n\n    this._from = this._set(val)\n    return this\n  }\n\n  stepper(stepper) {\n    if (stepper == null) return this._stepper\n    this._stepper = stepper\n    return this\n  }\n\n  to(val) {\n    if (val == null) {\n      return this._to\n    }\n\n    this._to = this._set(val)\n    return this\n  }\n\n  type(type) {\n    // getter\n    if (type == null) {\n      return this._type\n    }\n\n    // setter\n    this._type = type\n    return this\n  }\n\n  _set(value) {\n    if (!this._type) {\n      this.type(getClassForType(value))\n    }\n\n    let result = new this._type(value)\n    if (this._type === Color) {\n      result = this._to\n        ? result[this._to[4]]()\n        : this._from\n          ? result[this._from[4]]()\n          : result\n    }\n\n    if (this._type === ObjectBag) {\n      result = this._to\n        ? result.align(this._to)\n        : this._from\n          ? result.align(this._from)\n          : result\n    }\n\n    result = result.toConsumable()\n\n    this._morphObj = this._morphObj || new this._type()\n    this._context =\n      this._context ||\n      Array.apply(null, Array(result.length))\n        .map(Object)\n        .map(function (o) {\n          o.done = true\n          return o\n        })\n    return result\n  }\n}\n\nexport class NonMorphable {\n  constructor(...args) {\n    this.init(...args)\n  }\n\n  init(val) {\n    val = Array.isArray(val) ? val[0] : val\n    this.value = val\n    return this\n  }\n\n  toArray() {\n    return [this.value]\n  }\n\n  valueOf() {\n    return this.value\n  }\n}\n\nexport class TransformBag {\n  constructor(...args) {\n    this.init(...args)\n  }\n\n  init(obj) {\n    if (Array.isArray(obj)) {\n      obj = {\n        scaleX: obj[0],\n        scaleY: obj[1],\n        shear: obj[2],\n        rotate: obj[3],\n        translateX: obj[4],\n        translateY: obj[5],\n        originX: obj[6],\n        originY: obj[7]\n      }\n    }\n\n    Object.assign(this, TransformBag.defaults, obj)\n    return this\n  }\n\n  toArray() {\n    const v = this\n\n    return [\n      v.scaleX,\n      v.scaleY,\n      v.shear,\n      v.rotate,\n      v.translateX,\n      v.translateY,\n      v.originX,\n      v.originY\n    ]\n  }\n}\n\nTransformBag.defaults = {\n  scaleX: 1,\n  scaleY: 1,\n  shear: 0,\n  rotate: 0,\n  translateX: 0,\n  translateY: 0,\n  originX: 0,\n  originY: 0\n}\n\nconst sortByKey = (a, b) => {\n  return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0\n}\n\nexport class ObjectBag {\n  constructor(...args) {\n    this.init(...args)\n  }\n\n  align(other) {\n    const values = this.values\n    for (let i = 0, il = values.length; i < il; ++i) {\n      // If the type is the same we only need to check if the color is in the correct format\n      if (values[i + 1] === other[i + 1]) {\n        if (values[i + 1] === Color && other[i + 7] !== values[i + 7]) {\n          const space = other[i + 7]\n          const color = new Color(this.values.splice(i + 3, 5))\n            [space]()\n            .toArray()\n          this.values.splice(i + 3, 0, ...color)\n        }\n\n        i += values[i + 2] + 2\n        continue\n      }\n\n      if (!other[i + 1]) {\n        return this\n      }\n\n      // The types differ, so we overwrite the new type with the old one\n      // And initialize it with the types default (e.g. black for color or 0 for number)\n      const defaultObject = new other[i + 1]().toArray()\n\n      // Than we fix the values array\n      const toDelete = values[i + 2] + 3\n\n      values.splice(\n        i,\n        toDelete,\n        other[i],\n        other[i + 1],\n        other[i + 2],\n        ...defaultObject\n      )\n\n      i += values[i + 2] + 2\n    }\n    return this\n  }\n\n  init(objOrArr) {\n    this.values = []\n\n    if (Array.isArray(objOrArr)) {\n      this.values = objOrArr.slice()\n      return\n    }\n\n    objOrArr = objOrArr || {}\n    const entries = []\n\n    for (const i in objOrArr) {\n      const Type = getClassForType(objOrArr[i])\n      const val = new Type(objOrArr[i]).toArray()\n      entries.push([i, Type, val.length, ...val])\n    }\n\n    entries.sort(sortByKey)\n\n    this.values = entries.reduce((last, curr) => last.concat(curr), [])\n    return this\n  }\n\n  toArray() {\n    return this.values\n  }\n\n  valueOf() {\n    const obj = {}\n    const arr = this.values\n\n    // for (var i = 0, len = arr.length; i < len; i += 2) {\n    while (arr.length) {\n      const key = arr.shift()\n      const Type = arr.shift()\n      const num = arr.shift()\n      const values = arr.splice(0, num)\n      obj[key] = new Type(values) // .valueOf()\n    }\n\n    return obj\n  }\n}\n\nconst morphableTypes = [NonMorphable, TransformBag, ObjectBag]\n\nexport function registerMorphableType(type = []) {\n  morphableTypes.push(...[].concat(type))\n}\n\nexport function makeMorphable() {\n  extend(morphableTypes, {\n    to(val) {\n      return new Morphable()\n        .type(this.constructor)\n        .from(this.toArray()) // this.valueOf())\n        .to(val)\n    },\n    fromArray(arr) {\n      this.init(arr)\n      return this\n    },\n    toConsumable() {\n      return this.toArray()\n    },\n    morph(from, to, pos, stepper, context) {\n      const mapper = function (i, index) {\n        return stepper.step(i, to[index], pos, context[index], context)\n      }\n\n      return this.fromArray(from.map(mapper))\n    }\n  })\n}\n"
  },
  {
    "path": "src/animation/Queue.js",
    "content": "export default class Queue {\n  constructor() {\n    this._first = null\n    this._last = null\n  }\n\n  // Shows us the first item in the list\n  first() {\n    return this._first && this._first.value\n  }\n\n  // Shows us the last item in the list\n  last() {\n    return this._last && this._last.value\n  }\n\n  push(value) {\n    // An item stores an id and the provided value\n    const item =\n      typeof value.next !== 'undefined'\n        ? value\n        : { value: value, next: null, prev: null }\n\n    // Deal with the queue being empty or populated\n    if (this._last) {\n      item.prev = this._last\n      this._last.next = item\n      this._last = item\n    } else {\n      this._last = item\n      this._first = item\n    }\n\n    // Return the current item\n    return item\n  }\n\n  // Removes the item that was returned from the push\n  remove(item) {\n    // Relink the previous item\n    if (item.prev) item.prev.next = item.next\n    if (item.next) item.next.prev = item.prev\n    if (item === this._last) this._last = item.prev\n    if (item === this._first) this._first = item.next\n\n    // Invalidate item\n    item.prev = null\n    item.next = null\n  }\n\n  shift() {\n    // Check if we have a value\n    const remove = this._first\n    if (!remove) return null\n\n    // If we do, remove it and relink things\n    this._first = remove.next\n    if (this._first) this._first.prev = null\n    this._last = this._first ? this._last : null\n    return remove.value\n  }\n}\n"
  },
  {
    "path": "src/animation/Runner.js",
    "content": "import { Controller, Ease, Stepper } from './Controller.js'\nimport { extend, register } from '../utils/adopter.js'\nimport { from, to } from '../modules/core/gradiented.js'\nimport { getOrigin } from '../utils/utils.js'\nimport { noop, timeline } from '../modules/core/defaults.js'\nimport { registerMethods } from '../utils/methods.js'\nimport { rx, ry } from '../modules/core/circled.js'\nimport Animator from './Animator.js'\nimport Box from '../types/Box.js'\nimport EventTarget from '../types/EventTarget.js'\nimport Matrix from '../types/Matrix.js'\nimport Morphable, { TransformBag, ObjectBag } from './Morphable.js'\nimport Point from '../types/Point.js'\nimport SVGNumber from '../types/SVGNumber.js'\nimport Timeline from './Timeline.js'\n\nexport default class Runner extends EventTarget {\n  constructor(options) {\n    super()\n\n    // Store a unique id on the runner, so that we can identify it later\n    this.id = Runner.id++\n\n    // Ensure a default value\n    options = options == null ? timeline.duration : options\n\n    // Ensure that we get a controller\n    options = typeof options === 'function' ? new Controller(options) : options\n\n    // Declare all of the variables\n    this._element = null\n    this._timeline = null\n    this.done = false\n    this._queue = []\n\n    // Work out the stepper and the duration\n    this._duration = typeof options === 'number' && options\n    this._isDeclarative = options instanceof Controller\n    this._stepper = this._isDeclarative ? options : new Ease()\n\n    // We copy the current values from the timeline because they can change\n    this._history = {}\n\n    // Store the state of the runner\n    this.enabled = true\n    this._time = 0\n    this._lastTime = 0\n\n    // At creation, the runner is in reset state\n    this._reseted = true\n\n    // Save transforms applied to this runner\n    this.transforms = new Matrix()\n    this.transformId = 1\n\n    // Looping variables\n    this._haveReversed = false\n    this._reverse = false\n    this._loopsDone = 0\n    this._swing = false\n    this._wait = 0\n    this._times = 1\n\n    this._frameId = null\n\n    // Stores how long a runner is stored after being done\n    this._persist = this._isDeclarative ? true : null\n  }\n\n  static sanitise(duration, delay, when) {\n    // Initialise the default parameters\n    let times = 1\n    let swing = false\n    let wait = 0\n    duration = duration ?? timeline.duration\n    delay = delay ?? timeline.delay\n    when = when || 'last'\n\n    // If we have an object, unpack the values\n    if (typeof duration === 'object' && !(duration instanceof Stepper)) {\n      delay = duration.delay ?? delay\n      when = duration.when ?? when\n      swing = duration.swing || swing\n      times = duration.times ?? times\n      wait = duration.wait ?? wait\n      duration = duration.duration ?? timeline.duration\n    }\n\n    return {\n      duration: duration,\n      delay: delay,\n      swing: swing,\n      times: times,\n      wait: wait,\n      when: when\n    }\n  }\n\n  active(enabled) {\n    if (enabled == null) return this.enabled\n    this.enabled = enabled\n    return this\n  }\n\n  /*\n  Private Methods\n  ===============\n  Methods that shouldn't be used externally\n  */\n  addTransform(transform) {\n    this.transforms.lmultiplyO(transform)\n    return this\n  }\n\n  after(fn) {\n    return this.on('finished', fn)\n  }\n\n  animate(duration, delay, when) {\n    const o = Runner.sanitise(duration, delay, when)\n    const runner = new Runner(o.duration)\n    if (this._timeline) runner.timeline(this._timeline)\n    if (this._element) runner.element(this._element)\n    return runner.loop(o).schedule(o.delay, o.when)\n  }\n\n  clearTransform() {\n    this.transforms = new Matrix()\n    return this\n  }\n\n  // TODO: Keep track of all transformations so that deletion is faster\n  clearTransformsFromQueue() {\n    if (\n      !this.done ||\n      !this._timeline ||\n      !this._timeline._runnerIds.includes(this.id)\n    ) {\n      this._queue = this._queue.filter((item) => {\n        return !item.isTransform\n      })\n    }\n  }\n\n  delay(delay) {\n    return this.animate(0, delay)\n  }\n\n  duration() {\n    return this._times * (this._wait + this._duration) - this._wait\n  }\n\n  during(fn) {\n    return this.queue(null, fn)\n  }\n\n  ease(fn) {\n    this._stepper = new Ease(fn)\n    return this\n  }\n  /*\n  Runner Definitions\n  ==================\n  These methods help us define the runtime behaviour of the Runner or they\n  help us make new runners from the current runner\n  */\n\n  element(element) {\n    if (element == null) return this._element\n    this._element = element\n    element._prepareRunner()\n    return this\n  }\n\n  finish() {\n    return this.step(Infinity)\n  }\n\n  loop(times, swing, wait) {\n    // Deal with the user passing in an object\n    if (typeof times === 'object') {\n      swing = times.swing\n      wait = times.wait\n      times = times.times\n    }\n\n    // Sanitise the values and store them\n    this._times = times || Infinity\n    this._swing = swing || false\n    this._wait = wait || 0\n\n    // Allow true to be passed\n    if (this._times === true) {\n      this._times = Infinity\n    }\n\n    return this\n  }\n\n  loops(p) {\n    const loopDuration = this._duration + this._wait\n    if (p == null) {\n      const loopsDone = Math.floor(this._time / loopDuration)\n      const relativeTime = this._time - loopsDone * loopDuration\n      const position = relativeTime / this._duration\n      return Math.min(loopsDone + position, this._times)\n    }\n    const whole = Math.floor(p)\n    const partial = p % 1\n    const time = loopDuration * whole + this._duration * partial\n    return this.time(time)\n  }\n\n  persist(dtOrForever) {\n    if (dtOrForever == null) return this._persist\n    this._persist = dtOrForever\n    return this\n  }\n\n  position(p) {\n    // Get all of the variables we need\n    const x = this._time\n    const d = this._duration\n    const w = this._wait\n    const t = this._times\n    const s = this._swing\n    const r = this._reverse\n    let position\n\n    if (p == null) {\n      /*\n      This function converts a time to a position in the range [0, 1]\n      The full explanation can be found in this desmos demonstration\n        https://www.desmos.com/calculator/u4fbavgche\n      The logic is slightly simplified here because we can use booleans\n      */\n\n      // Figure out the value without thinking about the start or end time\n      const f = function (x) {\n        const swinging = s * Math.floor((x % (2 * (w + d))) / (w + d))\n        const backwards = (swinging && !r) || (!swinging && r)\n        const uncliped =\n          (Math.pow(-1, backwards) * (x % (w + d))) / d + backwards\n        const clipped = Math.max(Math.min(uncliped, 1), 0)\n        return clipped\n      }\n\n      // Figure out the value by incorporating the start time\n      const endTime = t * (w + d) - w\n      position =\n        x <= 0\n          ? Math.round(f(1e-5))\n          : x < endTime\n            ? f(x)\n            : Math.round(f(endTime - 1e-5))\n      return position\n    }\n\n    // Work out the loops done and add the position to the loops done\n    const loopsDone = Math.floor(this.loops())\n    const swingForward = s && loopsDone % 2 === 0\n    const forwards = (swingForward && !r) || (r && swingForward)\n    position = loopsDone + (forwards ? p : 1 - p)\n    return this.loops(position)\n  }\n\n  progress(p) {\n    if (p == null) {\n      return Math.min(1, this._time / this.duration())\n    }\n    return this.time(p * this.duration())\n  }\n\n  /*\n  Basic Functionality\n  ===================\n  These methods allow us to attach basic functions to the runner directly\n  */\n  queue(initFn, runFn, retargetFn, isTransform) {\n    this._queue.push({\n      initialiser: initFn || noop,\n      runner: runFn || noop,\n      retarget: retargetFn,\n      isTransform: isTransform,\n      initialised: false,\n      finished: false\n    })\n    const timeline = this.timeline()\n    timeline && this.timeline()._continue()\n    return this\n  }\n\n  reset() {\n    if (this._reseted) return this\n    this.time(0)\n    this._reseted = true\n    return this\n  }\n\n  reverse(reverse) {\n    this._reverse = reverse == null ? !this._reverse : reverse\n    return this\n  }\n\n  schedule(timeline, delay, when) {\n    // The user doesn't need to pass a timeline if we already have one\n    if (!(timeline instanceof Timeline)) {\n      when = delay\n      delay = timeline\n      timeline = this.timeline()\n    }\n\n    // If there is no timeline, yell at the user...\n    if (!timeline) {\n      throw Error('Runner cannot be scheduled without timeline')\n    }\n\n    // Schedule the runner on the timeline provided\n    timeline.schedule(this, delay, when)\n    return this\n  }\n\n  step(dt) {\n    // If we are inactive, this stepper just gets skipped\n    if (!this.enabled) return this\n\n    // Update the time and get the new position\n    dt = dt == null ? 16 : dt\n    this._time += dt\n    const position = this.position()\n\n    // Figure out if we need to run the stepper in this frame\n    const running = this._lastPosition !== position && this._time >= 0\n    this._lastPosition = position\n\n    // Figure out if we just started\n    const duration = this.duration()\n    const justStarted = this._lastTime <= 0 && this._time > 0\n    const justFinished = this._lastTime < duration && this._time >= duration\n\n    this._lastTime = this._time\n    if (justStarted) {\n      this.fire('start', this)\n    }\n\n    // Work out if the runner is finished set the done flag here so animations\n    // know, that they are running in the last step (this is good for\n    // transformations which can be merged)\n    const declarative = this._isDeclarative\n    this.done = !declarative && !justFinished && this._time >= duration\n\n    // Runner is running. So its not in reset state anymore\n    this._reseted = false\n\n    let converged = false\n    // Call initialise and the run function\n    if (running || declarative) {\n      this._initialise(running)\n\n      // clear the transforms on this runner so they dont get added again and again\n      this.transforms = new Matrix()\n      converged = this._run(declarative ? dt : position)\n\n      this.fire('step', this)\n    }\n    // correct the done flag here\n    // declarative animations itself know when they converged\n    this.done = this.done || (converged && declarative)\n    if (justFinished) {\n      this.fire('finished', this)\n    }\n    return this\n  }\n\n  /*\n  Runner animation methods\n  ========================\n  Control how the animation plays\n  */\n  time(time) {\n    if (time == null) {\n      return this._time\n    }\n    const dt = time - this._time\n    this.step(dt)\n    return this\n  }\n\n  timeline(timeline) {\n    // check explicitly for undefined so we can set the timeline to null\n    if (typeof timeline === 'undefined') return this._timeline\n    this._timeline = timeline\n    return this\n  }\n\n  unschedule() {\n    const timeline = this.timeline()\n    timeline && timeline.unschedule(this)\n    return this\n  }\n\n  // Run each initialise function in the runner if required\n  _initialise(running) {\n    // If we aren't running, we shouldn't initialise when not declarative\n    if (!running && !this._isDeclarative) return\n\n    // Loop through all of the initialisers\n    for (let i = 0, len = this._queue.length; i < len; ++i) {\n      // Get the current initialiser\n      const current = this._queue[i]\n\n      // Determine whether we need to initialise\n      const needsIt = this._isDeclarative || (!current.initialised && running)\n      running = !current.finished\n\n      // Call the initialiser if we need to\n      if (needsIt && running) {\n        current.initialiser.call(this)\n        current.initialised = true\n      }\n    }\n  }\n\n  // Save a morpher to the morpher list so that we can retarget it later\n  _rememberMorpher(method, morpher) {\n    this._history[method] = {\n      morpher: morpher,\n      caller: this._queue[this._queue.length - 1]\n    }\n\n    // We have to resume the timeline in case a controller\n    // is already done without being ever run\n    // This can happen when e.g. this is done:\n    //    anim = el.animate(new SVG.Spring)\n    // and later\n    //    anim.move(...)\n    if (this._isDeclarative) {\n      const timeline = this.timeline()\n      timeline && timeline.play()\n    }\n  }\n\n  // Try to set the target for a morpher if the morpher exists, otherwise\n  // Run each run function for the position or dt given\n  _run(positionOrDt) {\n    // Run all of the _queue directly\n    let allfinished = true\n    for (let i = 0, len = this._queue.length; i < len; ++i) {\n      // Get the current function to run\n      const current = this._queue[i]\n\n      // Run the function if its not finished, we keep track of the finished\n      // flag for the sake of declarative _queue\n      const converged = current.runner.call(this, positionOrDt)\n      current.finished = current.finished || converged === true\n      allfinished = allfinished && current.finished\n    }\n\n    // We report when all of the constructors are finished\n    return allfinished\n  }\n\n  // do nothing and return false\n  _tryRetarget(method, target, extra) {\n    if (this._history[method]) {\n      // if the last method wasn't even initialised, throw it away\n      if (!this._history[method].caller.initialised) {\n        const index = this._queue.indexOf(this._history[method].caller)\n        this._queue.splice(index, 1)\n        return false\n      }\n\n      // for the case of transformations, we use the special retarget function\n      // which has access to the outer scope\n      if (this._history[method].caller.retarget) {\n        this._history[method].caller.retarget.call(this, target, extra)\n        // for everything else a simple morpher change is sufficient\n      } else {\n        this._history[method].morpher.to(target)\n      }\n\n      this._history[method].caller.finished = false\n      const timeline = this.timeline()\n      timeline && timeline.play()\n      return true\n    }\n    return false\n  }\n}\n\nRunner.id = 0\n\nexport class FakeRunner {\n  constructor(transforms = new Matrix(), id = -1, done = true) {\n    this.transforms = transforms\n    this.id = id\n    this.done = done\n  }\n\n  clearTransformsFromQueue() {}\n}\n\nextend([Runner, FakeRunner], {\n  mergeWith(runner) {\n    return new FakeRunner(\n      runner.transforms.lmultiply(this.transforms),\n      runner.id\n    )\n  }\n})\n\n// FakeRunner.emptyRunner = new FakeRunner()\n\nconst lmultiply = (last, curr) => last.lmultiplyO(curr)\nconst getRunnerTransform = (runner) => runner.transforms\n\nfunction mergeTransforms() {\n  // Find the matrix to apply to the element and apply it\n  const runners = this._transformationRunners.runners\n  const netTransform = runners\n    .map(getRunnerTransform)\n    .reduce(lmultiply, new Matrix())\n\n  this.transform(netTransform)\n\n  this._transformationRunners.merge()\n\n  if (this._transformationRunners.length() === 1) {\n    this._frameId = null\n  }\n}\n\nexport class RunnerArray {\n  constructor() {\n    this.runners = []\n    this.ids = []\n  }\n\n  add(runner) {\n    if (this.runners.includes(runner)) return\n    const id = runner.id + 1\n\n    this.runners.push(runner)\n    this.ids.push(id)\n\n    return this\n  }\n\n  clearBefore(id) {\n    const deleteCnt = this.ids.indexOf(id + 1) || 1\n    this.ids.splice(0, deleteCnt, 0)\n    this.runners\n      .splice(0, deleteCnt, new FakeRunner())\n      .forEach((r) => r.clearTransformsFromQueue())\n    return this\n  }\n\n  edit(id, newRunner) {\n    const index = this.ids.indexOf(id + 1)\n    this.ids.splice(index, 1, id + 1)\n    this.runners.splice(index, 1, newRunner)\n    return this\n  }\n\n  getByID(id) {\n    return this.runners[this.ids.indexOf(id + 1)]\n  }\n\n  length() {\n    return this.ids.length\n  }\n\n  merge() {\n    let lastRunner = null\n    for (let i = 0; i < this.runners.length; ++i) {\n      const runner = this.runners[i]\n\n      const condition =\n        lastRunner &&\n        runner.done &&\n        lastRunner.done &&\n        // don't merge runner when persisted on timeline\n        (!runner._timeline ||\n          !runner._timeline._runnerIds.includes(runner.id)) &&\n        (!lastRunner._timeline ||\n          !lastRunner._timeline._runnerIds.includes(lastRunner.id))\n\n      if (condition) {\n        // the +1 happens in the function\n        this.remove(runner.id)\n        const newRunner = runner.mergeWith(lastRunner)\n        this.edit(lastRunner.id, newRunner)\n        lastRunner = newRunner\n        --i\n      } else {\n        lastRunner = runner\n      }\n    }\n\n    return this\n  }\n\n  remove(id) {\n    const index = this.ids.indexOf(id + 1)\n    this.ids.splice(index, 1)\n    this.runners.splice(index, 1)\n    return this\n  }\n}\n\nregisterMethods({\n  Element: {\n    animate(duration, delay, when) {\n      const o = Runner.sanitise(duration, delay, when)\n      const timeline = this.timeline()\n      return new Runner(o.duration)\n        .loop(o)\n        .element(this)\n        .timeline(timeline.play())\n        .schedule(o.delay, o.when)\n    },\n\n    delay(by, when) {\n      return this.animate(0, by, when)\n    },\n\n    // this function searches for all runners on the element and deletes the ones\n    // which run before the current one. This is because absolute transformations\n    // overwrite anything anyway so there is no need to waste time computing\n    // other runners\n    _clearTransformRunnersBefore(currentRunner) {\n      this._transformationRunners.clearBefore(currentRunner.id)\n    },\n\n    _currentTransform(current) {\n      return (\n        this._transformationRunners.runners\n          // we need the equal sign here to make sure, that also transformations\n          // on the same runner which execute before the current transformation are\n          // taken into account\n          .filter((runner) => runner.id <= current.id)\n          .map(getRunnerTransform)\n          .reduce(lmultiply, new Matrix())\n      )\n    },\n\n    _addRunner(runner) {\n      this._transformationRunners.add(runner)\n\n      // Make sure that the runner merge is executed at the very end of\n      // all Animator functions. That is why we use immediate here to execute\n      // the merge right after all frames are run\n      Animator.cancelImmediate(this._frameId)\n      this._frameId = Animator.immediate(mergeTransforms.bind(this))\n    },\n\n    _prepareRunner() {\n      if (this._frameId == null) {\n        this._transformationRunners = new RunnerArray().add(\n          new FakeRunner(new Matrix(this))\n        )\n      }\n    }\n  }\n})\n\n// Will output the elements from array A that are not in the array B\nconst difference = (a, b) => a.filter((x) => !b.includes(x))\n\nextend(Runner, {\n  attr(a, v) {\n    return this.styleAttr('attr', a, v)\n  },\n\n  // Add animatable styles\n  css(s, v) {\n    return this.styleAttr('css', s, v)\n  },\n\n  styleAttr(type, nameOrAttrs, val) {\n    if (typeof nameOrAttrs === 'string') {\n      return this.styleAttr(type, { [nameOrAttrs]: val })\n    }\n\n    let attrs = nameOrAttrs\n    if (this._tryRetarget(type, attrs)) return this\n\n    let morpher = new Morphable(this._stepper).to(attrs)\n    let keys = Object.keys(attrs)\n\n    this.queue(\n      function () {\n        morpher = morpher.from(this.element()[type](keys))\n      },\n      function (pos) {\n        this.element()[type](morpher.at(pos).valueOf())\n        return morpher.done()\n      },\n      function (newToAttrs) {\n        // Check if any new keys were added\n        const newKeys = Object.keys(newToAttrs)\n        const differences = difference(newKeys, keys)\n\n        // If their are new keys, initialize them and add them to morpher\n        if (differences.length) {\n          // Get the values\n          const addedFromAttrs = this.element()[type](differences)\n\n          // Get the already initialized values\n          const oldFromAttrs = new ObjectBag(morpher.from()).valueOf()\n\n          // Merge old and new\n          Object.assign(oldFromAttrs, addedFromAttrs)\n          morpher.from(oldFromAttrs)\n        }\n\n        // Get the object from the morpher\n        const oldToAttrs = new ObjectBag(morpher.to()).valueOf()\n\n        // Merge in new attributes\n        Object.assign(oldToAttrs, newToAttrs)\n\n        // Change morpher target\n        morpher.to(oldToAttrs)\n\n        // Make sure that we save the work we did so we don't need it to do again\n        keys = newKeys\n        attrs = newToAttrs\n      }\n    )\n\n    this._rememberMorpher(type, morpher)\n    return this\n  },\n\n  zoom(level, point) {\n    if (this._tryRetarget('zoom', level, point)) return this\n\n    let morpher = new Morphable(this._stepper).to(new SVGNumber(level))\n\n    this.queue(\n      function () {\n        morpher = morpher.from(this.element().zoom())\n      },\n      function (pos) {\n        this.element().zoom(morpher.at(pos), point)\n        return morpher.done()\n      },\n      function (newLevel, newPoint) {\n        point = newPoint\n        morpher.to(newLevel)\n      }\n    )\n\n    this._rememberMorpher('zoom', morpher)\n    return this\n  },\n\n  /**\n   ** absolute transformations\n   **/\n\n  //\n  // M v -----|-----(D M v = F v)------|----->  T v\n  //\n  // 1. define the final state (T) and decompose it (once)\n  //    t = [tx, ty, the, lam, sy, sx]\n  // 2. on every frame: pull the current state of all previous transforms\n  //    (M - m can change)\n  //   and then write this as m = [tx0, ty0, the0, lam0, sy0, sx0]\n  // 3. Find the interpolated matrix F(pos) = m + pos * (t - m)\n  //   - Note F(0) = M\n  //   - Note F(1) = T\n  // 4. Now you get the delta matrix as a result: D = F * inv(M)\n\n  transform(transforms, relative, affine) {\n    // If we have a declarative function, we should retarget it if possible\n    relative = transforms.relative || relative\n    if (\n      this._isDeclarative &&\n      !relative &&\n      this._tryRetarget('transform', transforms)\n    ) {\n      return this\n    }\n\n    // Parse the parameters\n    const isMatrix = Matrix.isMatrixLike(transforms)\n    affine =\n      transforms.affine != null\n        ? transforms.affine\n        : affine != null\n          ? affine\n          : !isMatrix\n\n    // Create a morpher and set its type\n    const morpher = new Morphable(this._stepper).type(\n      affine ? TransformBag : Matrix\n    )\n\n    let origin\n    let element\n    let current\n    let currentAngle\n    let startTransform\n\n    function setup() {\n      // make sure element and origin is defined\n      element = element || this.element()\n      origin = origin || getOrigin(transforms, element)\n\n      startTransform = new Matrix(relative ? undefined : element)\n\n      // add the runner to the element so it can merge transformations\n      element._addRunner(this)\n\n      // Deactivate all transforms that have run so far if we are absolute\n      if (!relative) {\n        element._clearTransformRunnersBefore(this)\n      }\n    }\n\n    function run(pos) {\n      // clear all other transforms before this in case something is saved\n      // on this runner. We are absolute. We dont need these!\n      if (!relative) this.clearTransform()\n\n      const { x, y } = new Point(origin).transform(\n        element._currentTransform(this)\n      )\n\n      let target = new Matrix({ ...transforms, origin: [x, y] })\n      let start = this._isDeclarative && current ? current : startTransform\n\n      if (affine) {\n        target = target.decompose(x, y)\n        start = start.decompose(x, y)\n\n        // Get the current and target angle as it was set\n        const rTarget = target.rotate\n        const rCurrent = start.rotate\n\n        // Figure out the shortest path to rotate directly\n        const possibilities = [rTarget - 360, rTarget, rTarget + 360]\n        const distances = possibilities.map((a) => Math.abs(a - rCurrent))\n        const shortest = Math.min(...distances)\n        const index = distances.indexOf(shortest)\n        target.rotate = possibilities[index]\n      }\n\n      if (relative) {\n        // we have to be careful here not to overwrite the rotation\n        // with the rotate method of Matrix\n        if (!isMatrix) {\n          target.rotate = transforms.rotate || 0\n        }\n        if (this._isDeclarative && currentAngle) {\n          start.rotate = currentAngle\n        }\n      }\n\n      morpher.from(start)\n      morpher.to(target)\n\n      const affineParameters = morpher.at(pos)\n      currentAngle = affineParameters.rotate\n      current = new Matrix(affineParameters)\n\n      this.addTransform(current)\n      element._addRunner(this)\n      return morpher.done()\n    }\n\n    function retarget(newTransforms) {\n      // only get a new origin if it changed since the last call\n      if (\n        (newTransforms.origin || 'center').toString() !==\n        (transforms.origin || 'center').toString()\n      ) {\n        origin = getOrigin(newTransforms, element)\n      }\n\n      // overwrite the old transformations with the new ones\n      transforms = { ...newTransforms, origin }\n    }\n\n    this.queue(setup, run, retarget, true)\n    this._isDeclarative && this._rememberMorpher('transform', morpher)\n    return this\n  },\n\n  // Animatable x-axis\n  x(x) {\n    return this._queueNumber('x', x)\n  },\n\n  // Animatable y-axis\n  y(y) {\n    return this._queueNumber('y', y)\n  },\n\n  ax(x) {\n    return this._queueNumber('ax', x)\n  },\n\n  ay(y) {\n    return this._queueNumber('ay', y)\n  },\n\n  dx(x = 0) {\n    return this._queueNumberDelta('x', x)\n  },\n\n  dy(y = 0) {\n    return this._queueNumberDelta('y', y)\n  },\n\n  dmove(x, y) {\n    return this.dx(x).dy(y)\n  },\n\n  _queueNumberDelta(method, to) {\n    to = new SVGNumber(to)\n\n    // Try to change the target if we have this method already registered\n    if (this._tryRetarget(method, to)) return this\n\n    // Make a morpher and queue the animation\n    const morpher = new Morphable(this._stepper).to(to)\n    let from = null\n    this.queue(\n      function () {\n        from = this.element()[method]()\n        morpher.from(from)\n        morpher.to(from + to)\n      },\n      function (pos) {\n        this.element()[method](morpher.at(pos))\n        return morpher.done()\n      },\n      function (newTo) {\n        morpher.to(from + new SVGNumber(newTo))\n      }\n    )\n\n    // Register the morpher so that if it is changed again, we can retarget it\n    this._rememberMorpher(method, morpher)\n    return this\n  },\n\n  _queueObject(method, to) {\n    // Try to change the target if we have this method already registered\n    if (this._tryRetarget(method, to)) return this\n\n    // Make a morpher and queue the animation\n    const morpher = new Morphable(this._stepper).to(to)\n    this.queue(\n      function () {\n        morpher.from(this.element()[method]())\n      },\n      function (pos) {\n        this.element()[method](morpher.at(pos))\n        return morpher.done()\n      }\n    )\n\n    // Register the morpher so that if it is changed again, we can retarget it\n    this._rememberMorpher(method, morpher)\n    return this\n  },\n\n  _queueNumber(method, value) {\n    return this._queueObject(method, new SVGNumber(value))\n  },\n\n  // Animatable center x-axis\n  cx(x) {\n    return this._queueNumber('cx', x)\n  },\n\n  // Animatable center y-axis\n  cy(y) {\n    return this._queueNumber('cy', y)\n  },\n\n  // Add animatable move\n  move(x, y) {\n    return this.x(x).y(y)\n  },\n\n  amove(x, y) {\n    return this.ax(x).ay(y)\n  },\n\n  // Add animatable center\n  center(x, y) {\n    return this.cx(x).cy(y)\n  },\n\n  // Add animatable size\n  size(width, height) {\n    // animate bbox based size for all other elements\n    let box\n\n    if (!width || !height) {\n      box = this._element.bbox()\n    }\n\n    if (!width) {\n      width = (box.width / box.height) * height\n    }\n\n    if (!height) {\n      height = (box.height / box.width) * width\n    }\n\n    return this.width(width).height(height)\n  },\n\n  // Add animatable width\n  width(width) {\n    return this._queueNumber('width', width)\n  },\n\n  // Add animatable height\n  height(height) {\n    return this._queueNumber('height', height)\n  },\n\n  // Add animatable plot\n  plot(a, b, c, d) {\n    // Lines can be plotted with 4 arguments\n    if (arguments.length === 4) {\n      return this.plot([a, b, c, d])\n    }\n\n    if (this._tryRetarget('plot', a)) return this\n\n    const morpher = new Morphable(this._stepper)\n      .type(this._element.MorphArray)\n      .to(a)\n\n    this.queue(\n      function () {\n        morpher.from(this._element.array())\n      },\n      function (pos) {\n        this._element.plot(morpher.at(pos))\n        return morpher.done()\n      }\n    )\n\n    this._rememberMorpher('plot', morpher)\n    return this\n  },\n\n  // Add leading method\n  leading(value) {\n    return this._queueNumber('leading', value)\n  },\n\n  // Add animatable viewbox\n  viewbox(x, y, width, height) {\n    return this._queueObject('viewbox', new Box(x, y, width, height))\n  },\n\n  update(o) {\n    if (typeof o !== 'object') {\n      return this.update({\n        offset: arguments[0],\n        color: arguments[1],\n        opacity: arguments[2]\n      })\n    }\n\n    if (o.opacity != null) this.attr('stop-opacity', o.opacity)\n    if (o.color != null) this.attr('stop-color', o.color)\n    if (o.offset != null) this.attr('offset', o.offset)\n\n    return this\n  }\n})\n\nextend(Runner, { rx, ry, from, to })\nregister(Runner, 'Runner')\n"
  },
  {
    "path": "src/animation/Timeline.js",
    "content": "import { globals } from '../utils/window.js'\nimport { registerMethods } from '../utils/methods.js'\nimport Animator from './Animator.js'\nimport EventTarget from '../types/EventTarget.js'\n\nconst makeSchedule = function (runnerInfo) {\n  const start = runnerInfo.start\n  const duration = runnerInfo.runner.duration()\n  const end = start + duration\n  return {\n    start: start,\n    duration: duration,\n    end: end,\n    runner: runnerInfo.runner\n  }\n}\n\nconst defaultSource = function () {\n  const w = globals.window\n  return (w.performance || w.Date).now()\n}\n\nexport default class Timeline extends EventTarget {\n  // Construct a new timeline on the given element\n  constructor(timeSource = defaultSource) {\n    super()\n\n    this._timeSource = timeSource\n\n    // terminate resets all variables to their initial state\n    this.terminate()\n  }\n\n  active() {\n    return !!this._nextFrame\n  }\n\n  finish() {\n    // Go to end and pause\n    this.time(this.getEndTimeOfTimeline() + 1)\n    return this.pause()\n  }\n\n  // Calculates the end of the timeline\n  getEndTime() {\n    const lastRunnerInfo = this.getLastRunnerInfo()\n    const lastDuration = lastRunnerInfo ? lastRunnerInfo.runner.duration() : 0\n    const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time\n    return lastStartTime + lastDuration\n  }\n\n  getEndTimeOfTimeline() {\n    const endTimes = this._runners.map((i) => i.start + i.runner.duration())\n    return Math.max(0, ...endTimes)\n  }\n\n  getLastRunnerInfo() {\n    return this.getRunnerInfoById(this._lastRunnerId)\n  }\n\n  getRunnerInfoById(id) {\n    return this._runners[this._runnerIds.indexOf(id)] || null\n  }\n\n  pause() {\n    this._paused = true\n    return this._continue()\n  }\n\n  persist(dtOrForever) {\n    if (dtOrForever == null) return this._persist\n    this._persist = dtOrForever\n    return this\n  }\n\n  play() {\n    // Now make sure we are not paused and continue the animation\n    this._paused = false\n    return this.updateTime()._continue()\n  }\n\n  reverse(yes) {\n    const currentSpeed = this.speed()\n    if (yes == null) return this.speed(-currentSpeed)\n\n    const positive = Math.abs(currentSpeed)\n    return this.speed(yes ? -positive : positive)\n  }\n\n  // schedules a runner on the timeline\n  schedule(runner, delay, when) {\n    if (runner == null) {\n      return this._runners.map(makeSchedule)\n    }\n\n    // The start time for the next animation can either be given explicitly,\n    // derived from the current timeline time or it can be relative to the\n    // last start time to chain animations directly\n\n    let absoluteStartTime = 0\n    const endTime = this.getEndTime()\n    delay = delay || 0\n\n    // Work out when to start the animation\n    if (when == null || when === 'last' || when === 'after') {\n      // Take the last time and increment\n      absoluteStartTime = endTime\n    } else if (when === 'absolute' || when === 'start') {\n      absoluteStartTime = delay\n      delay = 0\n    } else if (when === 'now') {\n      absoluteStartTime = this._time\n    } else if (when === 'relative') {\n      const runnerInfo = this.getRunnerInfoById(runner.id)\n      if (runnerInfo) {\n        absoluteStartTime = runnerInfo.start + delay\n        delay = 0\n      }\n    } else if (when === 'with-last') {\n      const lastRunnerInfo = this.getLastRunnerInfo()\n      const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time\n      absoluteStartTime = lastStartTime\n    } else {\n      throw new Error('Invalid value for the \"when\" parameter')\n    }\n\n    // Manage runner\n    runner.unschedule()\n    runner.timeline(this)\n\n    const persist = runner.persist()\n    const runnerInfo = {\n      persist: persist === null ? this._persist : persist,\n      start: absoluteStartTime + delay,\n      runner\n    }\n\n    this._lastRunnerId = runner.id\n\n    this._runners.push(runnerInfo)\n    this._runners.sort((a, b) => a.start - b.start)\n    this._runnerIds = this._runners.map((info) => info.runner.id)\n\n    this.updateTime()._continue()\n    return this\n  }\n\n  seek(dt) {\n    return this.time(this._time + dt)\n  }\n\n  source(fn) {\n    if (fn == null) return this._timeSource\n    this._timeSource = fn\n    return this\n  }\n\n  speed(speed) {\n    if (speed == null) return this._speed\n    this._speed = speed\n    return this\n  }\n\n  stop() {\n    // Go to start and pause\n    this.time(0)\n    return this.pause()\n  }\n\n  time(time) {\n    if (time == null) return this._time\n    this._time = time\n    return this._continue(true)\n  }\n\n  // Remove the runner from this timeline\n  unschedule(runner) {\n    const index = this._runnerIds.indexOf(runner.id)\n    if (index < 0) return this\n\n    this._runners.splice(index, 1)\n    this._runnerIds.splice(index, 1)\n\n    runner.timeline(null)\n    return this\n  }\n\n  // Makes sure, that after pausing the time doesn't jump\n  updateTime() {\n    if (!this.active()) {\n      this._lastSourceTime = this._timeSource()\n    }\n    return this\n  }\n\n  // Checks if we are running and continues the animation\n  _continue(immediateStep = false) {\n    Animator.cancelFrame(this._nextFrame)\n    this._nextFrame = null\n\n    if (immediateStep) return this._stepImmediate()\n    if (this._paused) return this\n\n    this._nextFrame = Animator.frame(this._step)\n    return this\n  }\n\n  _stepFn(immediateStep = false) {\n    // Get the time delta from the last time and update the time\n    const time = this._timeSource()\n    let dtSource = time - this._lastSourceTime\n\n    if (immediateStep) dtSource = 0\n\n    const dtTime = this._speed * dtSource + (this._time - this._lastStepTime)\n    this._lastSourceTime = time\n\n    // Only update the time if we use the timeSource.\n    // Otherwise use the current time\n    if (!immediateStep) {\n      // Update the time\n      this._time += dtTime\n      this._time = this._time < 0 ? 0 : this._time\n    }\n    this._lastStepTime = this._time\n    this.fire('time', this._time)\n\n    // This is for the case that the timeline was seeked so that the time\n    // is now before the startTime of the runner. That is why we need to set\n    // the runner to position 0\n\n    // FIXME:\n    // However, resetting in insertion order leads to bugs. Considering the case,\n    // where 2 runners change the same attribute but in different times,\n    // resetting both of them will lead to the case where the later defined\n    // runner always wins the reset even if the other runner started earlier\n    // and therefore should win the attribute battle\n    // this can be solved by resetting them backwards\n    for (let k = this._runners.length; k--; ) {\n      // Get and run the current runner and ignore it if its inactive\n      const runnerInfo = this._runners[k]\n      const runner = runnerInfo.runner\n\n      // Make sure that we give the actual difference\n      // between runner start time and now\n      const dtToStart = this._time - runnerInfo.start\n\n      // Dont run runner if not started yet\n      // and try to reset it\n      if (dtToStart <= 0) {\n        runner.reset()\n      }\n    }\n\n    // Run all of the runners directly\n    let runnersLeft = false\n    for (let i = 0, len = this._runners.length; i < len; i++) {\n      // Get and run the current runner and ignore it if its inactive\n      const runnerInfo = this._runners[i]\n      const runner = runnerInfo.runner\n      let dt = dtTime\n\n      // Make sure that we give the actual difference\n      // between runner start time and now\n      const dtToStart = this._time - runnerInfo.start\n\n      // Dont run runner if not started yet\n      if (dtToStart <= 0) {\n        runnersLeft = true\n        continue\n      } else if (dtToStart < dt) {\n        // Adjust dt to make sure that animation is on point\n        dt = dtToStart\n      }\n\n      if (!runner.active()) continue\n\n      // If this runner is still going, signal that we need another animation\n      // frame, otherwise, remove the completed runner\n      const finished = runner.step(dt).done\n      if (!finished) {\n        runnersLeft = true\n        // continue\n      } else if (runnerInfo.persist !== true) {\n        // runner is finished. And runner might get removed\n        const endTime = runner.duration() - runner.time() + this._time\n\n        if (endTime + runnerInfo.persist < this._time) {\n          // Delete runner and correct index\n          runner.unschedule()\n          --i\n          --len\n        }\n      }\n    }\n\n    // Basically: we continue when there are runners right from us in time\n    // when -->, and when runners are left from us when <--\n    if (\n      (runnersLeft && !(this._speed < 0 && this._time === 0)) ||\n      (this._runnerIds.length && this._speed < 0 && this._time > 0)\n    ) {\n      this._continue()\n    } else {\n      this.pause()\n      this.fire('finished')\n    }\n\n    return this\n  }\n\n  terminate() {\n    // cleanup memory\n\n    // Store the timing variables\n    this._startTime = 0\n    this._speed = 1.0\n\n    // Determines how long a runner is hold in memory. Can be a dt or true/false\n    this._persist = 0\n\n    // Keep track of the running animations and their starting parameters\n    this._nextFrame = null\n    this._paused = true\n    this._runners = []\n    this._runnerIds = []\n    this._lastRunnerId = -1\n    this._time = 0\n    this._lastSourceTime = 0\n    this._lastStepTime = 0\n\n    // Make sure that step is always called in class context\n    this._step = this._stepFn.bind(this, false)\n    this._stepImmediate = this._stepFn.bind(this, true)\n  }\n}\n\nregisterMethods({\n  Element: {\n    timeline: function (timeline) {\n      if (timeline == null) {\n        this._timeline = this._timeline || new Timeline()\n        return this._timeline\n      } else {\n        this._timeline = timeline\n        return this\n      }\n    }\n  }\n})\n"
  },
  {
    "path": "src/elements/A.js",
    "content": "import {\n  nodeOrNew,\n  register,\n  wrapWithAttrCheck,\n  extend\n} from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport { xlink } from '../modules/core/namespaces.js'\nimport Container from './Container.js'\nimport * as containerGeometry from '../modules/core/containerGeometry.js'\n\nexport default class A extends Container {\n  constructor(node, attrs = node) {\n    super(nodeOrNew('a', node), attrs)\n  }\n\n  // Link target attribute\n  target(target) {\n    return this.attr('target', target)\n  }\n\n  // Link url\n  to(url) {\n    return this.attr('href', url, xlink)\n  }\n}\n\nextend(A, containerGeometry)\n\nregisterMethods({\n  Container: {\n    // Create a hyperlink element\n    link: wrapWithAttrCheck(function (url) {\n      return this.put(new A()).to(url)\n    })\n  },\n  Element: {\n    unlink() {\n      const link = this.linker()\n\n      if (!link) return this\n\n      const parent = link.parent()\n\n      if (!parent) {\n        return this.remove()\n      }\n\n      const index = parent.index(link)\n      parent.add(this, index)\n\n      link.remove()\n      return this\n    },\n    linkTo(url) {\n      // reuse old link if possible\n      let link = this.linker()\n\n      if (!link) {\n        link = new A()\n        this.wrap(link)\n      }\n\n      if (typeof url === 'function') {\n        url.call(link, link)\n      } else {\n        link.to(url)\n      }\n\n      return this\n    },\n    linker() {\n      const link = this.parent()\n      if (link && link.node.nodeName.toLowerCase() === 'a') {\n        return link\n      }\n\n      return null\n    }\n  }\n})\n\nregister(A, 'A')\n"
  },
  {
    "path": "src/elements/Circle.js",
    "content": "import { cx, cy, height, width, x, y } from '../modules/core/circled.js'\nimport {\n  extend,\n  nodeOrNew,\n  register,\n  wrapWithAttrCheck\n} from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport SVGNumber from '../types/SVGNumber.js'\nimport Shape from './Shape.js'\n\nexport default class Circle extends Shape {\n  constructor(node, attrs = node) {\n    super(nodeOrNew('circle', node), attrs)\n  }\n\n  radius(r) {\n    return this.attr('r', r)\n  }\n\n  // Radius x value\n  rx(rx) {\n    return this.attr('r', rx)\n  }\n\n  // Alias radius x value\n  ry(ry) {\n    return this.rx(ry)\n  }\n\n  size(size) {\n    return this.radius(new SVGNumber(size).divide(2))\n  }\n}\n\nextend(Circle, { x, y, cx, cy, width, height })\n\nregisterMethods({\n  Container: {\n    // Create circle element\n    circle: wrapWithAttrCheck(function (size = 0) {\n      return this.put(new Circle()).size(size).move(0, 0)\n    })\n  }\n})\n\nregister(Circle, 'Circle')\n"
  },
  {
    "path": "src/elements/ClipPath.js",
    "content": "import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport Container from './Container.js'\nimport baseFind from '../modules/core/selector.js'\n\nexport default class ClipPath extends Container {\n  constructor(node, attrs = node) {\n    super(nodeOrNew('clipPath', node), attrs)\n  }\n\n  // Unclip all clipped elements and remove itself\n  remove() {\n    // unclip all targets\n    this.targets().forEach(function (el) {\n      el.unclip()\n    })\n\n    // remove clipPath from parent\n    return super.remove()\n  }\n\n  targets() {\n    return baseFind('svg [clip-path*=' + this.id() + ']')\n  }\n}\n\nregisterMethods({\n  Container: {\n    // Create clipping element\n    clip: wrapWithAttrCheck(function () {\n      return this.defs().put(new ClipPath())\n    })\n  },\n  Element: {\n    // Distribute clipPath to svg element\n    clipper() {\n      return this.reference('clip-path')\n    },\n\n    clipWith(element) {\n      // use given clip or create a new one\n      const clipper =\n        element instanceof ClipPath\n          ? element\n          : this.parent().clip().add(element)\n\n      // apply mask\n      return this.attr('clip-path', 'url(#' + clipper.id() + ')')\n    },\n\n    // Unclip element\n    unclip() {\n      return this.attr('clip-path', null)\n    }\n  }\n})\n\nregister(ClipPath, 'ClipPath')\n"
  },
  {
    "path": "src/elements/Container.js",
    "content": "import { register } from '../utils/adopter.js'\nimport Element from './Element.js'\n\nexport default class Container extends Element {\n  flatten() {\n    this.each(function () {\n      if (this instanceof Container) {\n        return this.flatten().ungroup()\n      }\n    })\n\n    return this\n  }\n\n  ungroup(parent = this.parent(), index = parent.index(this)) {\n    // when parent != this, we want append all elements to the end\n    index = index === -1 ? parent.children().length : index\n\n    this.each(function (i, children) {\n      // reverse each\n      return children[children.length - i - 1].toParent(parent, index)\n    })\n\n    return this.remove()\n  }\n}\n\nregister(Container, 'Container')\n"
  },
  {
    "path": "src/elements/Defs.js",
    "content": "import { nodeOrNew, register } from '../utils/adopter.js'\nimport Container from './Container.js'\n\nexport default class Defs extends Container {\n  constructor(node, attrs = node) {\n    super(nodeOrNew('defs', node), attrs)\n  }\n\n  flatten() {\n    return this\n  }\n\n  ungroup() {\n    return this\n  }\n}\n\nregister(Defs, 'Defs')\n"
  },
  {
    "path": "src/elements/Dom.js",
    "content": "import {\n  adopt,\n  assignNewId,\n  eid,\n  extend,\n  makeInstance,\n  create,\n  register\n} from '../utils/adopter.js'\nimport { find, findOne } from '../modules/core/selector.js'\nimport { globals } from '../utils/window.js'\nimport { map } from '../utils/utils.js'\nimport { svg, html } from '../modules/core/namespaces.js'\nimport EventTarget from '../types/EventTarget.js'\nimport List from '../types/List.js'\nimport attr from '../modules/core/attr.js'\n\nexport default class Dom extends EventTarget {\n  constructor(node, attrs) {\n    super()\n    this.node = node\n    this.type = node.nodeName\n\n    if (attrs && node !== attrs) {\n      this.attr(attrs)\n    }\n  }\n\n  // Add given element at a position\n  add(element, i) {\n    element = makeInstance(element)\n\n    // If non-root svg nodes are added we have to remove their namespaces\n    if (\n      element.removeNamespace &&\n      this.node instanceof globals.window.SVGElement\n    ) {\n      element.removeNamespace()\n    }\n\n    if (i == null) {\n      this.node.appendChild(element.node)\n    } else if (element.node !== this.node.childNodes[i]) {\n      this.node.insertBefore(element.node, this.node.childNodes[i])\n    }\n\n    return this\n  }\n\n  // Add element to given container and return self\n  addTo(parent, i) {\n    return makeInstance(parent).put(this, i)\n  }\n\n  // Returns all child elements\n  children() {\n    return new List(\n      map(this.node.children, function (node) {\n        return adopt(node)\n      })\n    )\n  }\n\n  // Remove all elements in this container\n  clear() {\n    // remove children\n    while (this.node.hasChildNodes()) {\n      this.node.removeChild(this.node.lastChild)\n    }\n\n    return this\n  }\n\n  // Clone element\n  clone(deep = true, assignNewIds = true) {\n    // write dom data to the dom so the clone can pickup the data\n    this.writeDataToDom()\n\n    // clone element\n    let nodeClone = this.node.cloneNode(deep)\n    if (assignNewIds) {\n      // assign new id\n      nodeClone = assignNewId(nodeClone)\n    }\n    return new this.constructor(nodeClone)\n  }\n\n  // Iterates over all children and invokes a given block\n  each(block, deep) {\n    const children = this.children()\n    let i, il\n\n    for (i = 0, il = children.length; i < il; i++) {\n      block.apply(children[i], [i, children])\n\n      if (deep) {\n        children[i].each(block, deep)\n      }\n    }\n\n    return this\n  }\n\n  element(nodeName, attrs) {\n    return this.put(new Dom(create(nodeName), attrs))\n  }\n\n  // Get first child\n  first() {\n    return adopt(this.node.firstChild)\n  }\n\n  // Get a element at the given index\n  get(i) {\n    return adopt(this.node.childNodes[i])\n  }\n\n  getEventHolder() {\n    return this.node\n  }\n\n  getEventTarget() {\n    return this.node\n  }\n\n  // Checks if the given element is a child\n  has(element) {\n    return this.index(element) >= 0\n  }\n\n  html(htmlOrFn, outerHTML) {\n    return this.xml(htmlOrFn, outerHTML, html)\n  }\n\n  // Get / set id\n  id(id) {\n    // generate new id if no id set\n    if (typeof id === 'undefined' && !this.node.id) {\n      this.node.id = eid(this.type)\n    }\n\n    // don't set directly with this.node.id to make `null` work correctly\n    return this.attr('id', id)\n  }\n\n  // Gets index of given element\n  index(element) {\n    return [].slice.call(this.node.childNodes).indexOf(element.node)\n  }\n\n  // Get the last child\n  last() {\n    return adopt(this.node.lastChild)\n  }\n\n  // matches the element vs a css selector\n  matches(selector) {\n    const el = this.node\n    const matcher =\n      el.matches ||\n      el.matchesSelector ||\n      el.msMatchesSelector ||\n      el.mozMatchesSelector ||\n      el.webkitMatchesSelector ||\n      el.oMatchesSelector ||\n      null\n    return matcher && matcher.call(el, selector)\n  }\n\n  // Returns the parent element instance\n  parent(type) {\n    let parent = this\n\n    // check for parent\n    if (!parent.node.parentNode) return null\n\n    // get parent element\n    parent = adopt(parent.node.parentNode)\n\n    if (!type) return parent\n\n    // loop through ancestors if type is given\n    do {\n      if (\n        typeof type === 'string' ? parent.matches(type) : parent instanceof type\n      )\n        return parent\n    } while ((parent = adopt(parent.node.parentNode)))\n\n    return parent\n  }\n\n  // Basically does the same as `add()` but returns the added element instead\n  put(element, i) {\n    element = makeInstance(element)\n    this.add(element, i)\n    return element\n  }\n\n  // Add element to given container and return container\n  putIn(parent, i) {\n    return makeInstance(parent).add(this, i)\n  }\n\n  // Remove element\n  remove() {\n    if (this.parent()) {\n      this.parent().removeElement(this)\n    }\n\n    return this\n  }\n\n  // Remove a given child\n  removeElement(element) {\n    this.node.removeChild(element.node)\n\n    return this\n  }\n\n  // Replace this with element\n  replace(element) {\n    element = makeInstance(element)\n\n    if (this.node.parentNode) {\n      this.node.parentNode.replaceChild(element.node, this.node)\n    }\n\n    return element\n  }\n\n  round(precision = 2, map = null) {\n    const factor = 10 ** precision\n    const attrs = this.attr(map)\n\n    for (const i in attrs) {\n      if (typeof attrs[i] === 'number') {\n        attrs[i] = Math.round(attrs[i] * factor) / factor\n      }\n    }\n\n    this.attr(attrs)\n    return this\n  }\n\n  // Import / Export raw svg\n  svg(svgOrFn, outerSVG) {\n    return this.xml(svgOrFn, outerSVG, svg)\n  }\n\n  // Return id on string conversion\n  toString() {\n    return this.id()\n  }\n\n  words(text) {\n    // This is faster than removing all children and adding a new one\n    this.node.textContent = text\n    return this\n  }\n\n  wrap(node) {\n    const parent = this.parent()\n\n    if (!parent) {\n      return this.addTo(node)\n    }\n\n    const position = parent.index(this)\n    return parent.put(node, position).put(this)\n  }\n\n  // write svgjs data to the dom\n  writeDataToDom() {\n    // dump variables recursively\n    this.each(function () {\n      this.writeDataToDom()\n    })\n\n    return this\n  }\n\n  // Import / Export raw svg\n  xml(xmlOrFn, outerXML, ns) {\n    if (typeof xmlOrFn === 'boolean') {\n      ns = outerXML\n      outerXML = xmlOrFn\n      xmlOrFn = null\n    }\n\n    // act as getter if no svg string is given\n    if (xmlOrFn == null || typeof xmlOrFn === 'function') {\n      // The default for exports is, that the outerNode is included\n      outerXML = outerXML == null ? true : outerXML\n\n      // write svgjs data to the dom\n      this.writeDataToDom()\n      let current = this\n\n      // An export modifier was passed\n      if (xmlOrFn != null) {\n        current = adopt(current.node.cloneNode(true))\n\n        // If the user wants outerHTML we need to process this node, too\n        if (outerXML) {\n          const result = xmlOrFn(current)\n          current = result || current\n\n          // The user does not want this node? Well, then he gets nothing\n          if (result === false) return ''\n        }\n\n        // Deep loop through all children and apply modifier\n        current.each(function () {\n          const result = xmlOrFn(this)\n          const _this = result || this\n\n          // If modifier returns false, discard node\n          if (result === false) {\n            this.remove()\n\n            // If modifier returns new node, use it\n          } else if (result && this !== _this) {\n            this.replace(_this)\n          }\n        }, true)\n      }\n\n      // Return outer or inner content\n      return outerXML ? current.node.outerHTML : current.node.innerHTML\n    }\n\n    // Act as setter if we got a string\n\n    // The default for import is, that the current node is not replaced\n    outerXML = outerXML == null ? false : outerXML\n\n    // Create temporary holder\n    const well = create('wrapper', ns)\n    const fragment = globals.document.createDocumentFragment()\n\n    // Dump raw svg\n    well.innerHTML = xmlOrFn\n\n    // Transplant nodes into the fragment\n    for (let len = well.children.length; len--; ) {\n      fragment.appendChild(well.firstElementChild)\n    }\n\n    const parent = this.parent()\n\n    // Add the whole fragment at once\n    return outerXML ? this.replace(fragment) && parent : this.add(fragment)\n  }\n}\n\nextend(Dom, { attr, find, findOne })\nregister(Dom, 'Dom')\n"
  },
  {
    "path": "src/elements/Element.js",
    "content": "import { bbox, rbox, inside } from '../types/Box.js'\nimport { ctm, screenCTM } from '../types/Matrix.js'\nimport {\n  extend,\n  getClass,\n  makeInstance,\n  register,\n  root\n} from '../utils/adopter.js'\nimport { globals } from '../utils/window.js'\nimport { point } from '../types/Point.js'\nimport { proportionalSize, writeDataToDom } from '../utils/utils.js'\nimport { reference } from '../modules/core/regex.js'\nimport Dom from './Dom.js'\nimport List from '../types/List.js'\nimport SVGNumber from '../types/SVGNumber.js'\n\nexport default class Element extends Dom {\n  constructor(node, attrs) {\n    super(node, attrs)\n\n    // initialize data object\n    this.dom = {}\n\n    // create circular reference\n    this.node.instance = this\n\n    if (node.hasAttribute('data-svgjs') || node.hasAttribute('svgjs:data')) {\n      // pull svgjs data from the dom (getAttributeNS doesn't work in html5)\n      this.setData(\n        JSON.parse(node.getAttribute('data-svgjs')) ??\n          JSON.parse(node.getAttribute('svgjs:data')) ??\n          {}\n      )\n    }\n  }\n\n  // Move element by its center\n  center(x, y) {\n    return this.cx(x).cy(y)\n  }\n\n  // Move by center over x-axis\n  cx(x) {\n    return x == null\n      ? this.x() + this.width() / 2\n      : this.x(x - this.width() / 2)\n  }\n\n  // Move by center over y-axis\n  cy(y) {\n    return y == null\n      ? this.y() + this.height() / 2\n      : this.y(y - this.height() / 2)\n  }\n\n  // Get defs\n  defs() {\n    const root = this.root()\n    return root && root.defs()\n  }\n\n  // Relative move over x and y axes\n  dmove(x, y) {\n    return this.dx(x).dy(y)\n  }\n\n  // Relative move over x axis\n  dx(x = 0) {\n    return this.x(new SVGNumber(x).plus(this.x()))\n  }\n\n  // Relative move over y axis\n  dy(y = 0) {\n    return this.y(new SVGNumber(y).plus(this.y()))\n  }\n\n  getEventHolder() {\n    return this\n  }\n\n  // Set height of element\n  height(height) {\n    return this.attr('height', height)\n  }\n\n  // Move element to given x and y values\n  move(x, y) {\n    return this.x(x).y(y)\n  }\n\n  // return array of all ancestors of given type up to the root svg\n  parents(until = this.root()) {\n    const isSelector = typeof until === 'string'\n    if (!isSelector) {\n      until = makeInstance(until)\n    }\n    const parents = new List()\n    let parent = this\n\n    while (\n      (parent = parent.parent()) &&\n      parent.node !== globals.document &&\n      parent.nodeName !== '#document-fragment'\n    ) {\n      parents.push(parent)\n\n      if (!isSelector && parent.node === until.node) {\n        break\n      }\n      if (isSelector && parent.matches(until)) {\n        break\n      }\n      if (parent.node === this.root().node) {\n        // We worked our way to the root and didn't match `until`\n        return null\n      }\n    }\n\n    return parents\n  }\n\n  // Get referenced element form attribute value\n  reference(attr) {\n    attr = this.attr(attr)\n    if (!attr) return null\n\n    const m = (attr + '').match(reference)\n    return m ? makeInstance(m[1]) : null\n  }\n\n  // Get parent document\n  root() {\n    const p = this.parent(getClass(root))\n    return p && p.root()\n  }\n\n  // set given data to the elements data property\n  setData(o) {\n    this.dom = o\n    return this\n  }\n\n  // Set element size to given width and height\n  size(width, height) {\n    const p = proportionalSize(this, width, height)\n\n    return this.width(new SVGNumber(p.width)).height(new SVGNumber(p.height))\n  }\n\n  // Set width of element\n  width(width) {\n    return this.attr('width', width)\n  }\n\n  // write svgjs data to the dom\n  writeDataToDom() {\n    writeDataToDom(this, this.dom)\n    return super.writeDataToDom()\n  }\n\n  // Move over x-axis\n  x(x) {\n    return this.attr('x', x)\n  }\n\n  // Move over y-axis\n  y(y) {\n    return this.attr('y', y)\n  }\n}\n\nextend(Element, {\n  bbox,\n  rbox,\n  inside,\n  point,\n  ctm,\n  screenCTM\n})\n\nregister(Element, 'Element')\n"
  },
  {
    "path": "src/elements/Ellipse.js",
    "content": "import {\n  extend,\n  nodeOrNew,\n  register,\n  wrapWithAttrCheck\n} from '../utils/adopter.js'\nimport { proportionalSize } from '../utils/utils.js'\nimport { registerMethods } from '../utils/methods.js'\nimport SVGNumber from '../types/SVGNumber.js'\nimport Shape from './Shape.js'\nimport * as circled from '../modules/core/circled.js'\n\nexport default class Ellipse extends Shape {\n  constructor(node, attrs = node) {\n    super(nodeOrNew('ellipse', node), attrs)\n  }\n\n  size(width, height) {\n    const p = proportionalSize(this, width, height)\n\n    return this.rx(new SVGNumber(p.width).divide(2)).ry(\n      new SVGNumber(p.height).divide(2)\n    )\n  }\n}\n\nextend(Ellipse, circled)\n\nregisterMethods('Container', {\n  // Create an ellipse\n  ellipse: wrapWithAttrCheck(function (width = 0, height = width) {\n    return this.put(new Ellipse()).size(width, height).move(0, 0)\n  })\n})\n\nregister(Ellipse, 'Ellipse')\n"
  },
  {
    "path": "src/elements/ForeignObject.js",
    "content": "import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport Element from './Element.js'\n\nexport default class ForeignObject extends Element {\n  constructor(node, attrs = node) {\n    super(nodeOrNew('foreignObject', node), attrs)\n  }\n}\n\nregisterMethods({\n  Container: {\n    foreignObject: wrapWithAttrCheck(function (width, height) {\n      return this.put(new ForeignObject()).size(width, height)\n    })\n  }\n})\n\nregister(ForeignObject, 'ForeignObject')\n"
  },
  {
    "path": "src/elements/Fragment.js",
    "content": "import Dom from './Dom.js'\nimport { globals } from '../utils/window.js'\nimport { register, create } from '../utils/adopter.js'\n\nclass Fragment extends Dom {\n  constructor(node = globals.document.createDocumentFragment()) {\n    super(node)\n  }\n\n  // Import / Export raw xml\n  xml(xmlOrFn, outerXML, ns) {\n    if (typeof xmlOrFn === 'boolean') {\n      ns = outerXML\n      outerXML = xmlOrFn\n      xmlOrFn = null\n    }\n\n    // because this is a fragment we have to put all elements into a wrapper first\n    // before we can get the innerXML from it\n    if (xmlOrFn == null || typeof xmlOrFn === 'function') {\n      const wrapper = new Dom(create('wrapper', ns))\n      wrapper.add(this.node.cloneNode(true))\n\n      return wrapper.xml(false, ns)\n    }\n\n    // Act as setter if we got a string\n    return super.xml(xmlOrFn, false, ns)\n  }\n}\n\nregister(Fragment, 'Fragment')\n\nexport default Fragment\n"
  },
  {
    "path": "src/elements/G.js",
    "content": "import {\n  nodeOrNew,\n  register,\n  wrapWithAttrCheck,\n  extend\n} from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport Container from './Container.js'\nimport * as containerGeometry from '../modules/core/containerGeometry.js'\n\nexport default class G extends Container {\n  constructor(node, attrs = node) {\n    super(nodeOrNew('g', node), attrs)\n  }\n}\n\nextend(G, containerGeometry)\n\nregisterMethods({\n  Container: {\n    // Create a group element\n    group: wrapWithAttrCheck(function () {\n      return this.put(new G())\n    })\n  }\n})\n\nregister(G, 'G')\n"
  },
  {
    "path": "src/elements/Gradient.js",
    "content": "import {\n  extend,\n  nodeOrNew,\n  register,\n  wrapWithAttrCheck\n} from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport Box from '../types/Box.js'\nimport Container from './Container.js'\nimport baseFind from '../modules/core/selector.js'\nimport * as gradiented from '../modules/core/gradiented.js'\n\nexport default class Gradient extends Container {\n  constructor(type, attrs) {\n    super(\n      nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type),\n      attrs\n    )\n  }\n\n  // custom attr to handle transform\n  attr(a, b, c) {\n    if (a === 'transform') a = 'gradientTransform'\n    return super.attr(a, b, c)\n  }\n\n  bbox() {\n    return new Box()\n  }\n\n  targets() {\n    return baseFind('svg [fill*=' + this.id() + ']')\n  }\n\n  // Alias string conversion to fill\n  toString() {\n    return this.url()\n  }\n\n  // Update gradient\n  update(block) {\n    // remove all stops\n    this.clear()\n\n    // invoke passed block\n    if (typeof block === 'function') {\n      block.call(this, this)\n    }\n\n    return this\n  }\n\n  // Return the fill id\n  url() {\n    return 'url(#' + this.id() + ')'\n  }\n}\n\nextend(Gradient, gradiented)\n\nregisterMethods({\n  Container: {\n    // Create gradient element in defs\n    gradient(...args) {\n      return this.defs().gradient(...args)\n    }\n  },\n  // define gradient\n  Defs: {\n    gradient: wrapWithAttrCheck(function (type, block) {\n      return this.put(new Gradient(type)).update(block)\n    })\n  }\n})\n\nregister(Gradient, 'Gradient')\n"
  },
  {
    "path": "src/elements/Image.js",
    "content": "import { isImage } from '../modules/core/regex.js'\nimport { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'\nimport { off, on } from '../modules/core/event.js'\nimport { registerAttrHook } from '../modules/core/attr.js'\nimport { registerMethods } from '../utils/methods.js'\nimport { xlink } from '../modules/core/namespaces.js'\nimport Pattern from './Pattern.js'\nimport Shape from './Shape.js'\nimport { globals } from '../utils/window.js'\n\nexport default class Image extends Shape {\n  constructor(node, attrs = node) {\n    super(nodeOrNew('image', node), attrs)\n  }\n\n  // (re)load image\n  load(url, callback) {\n    if (!url) return this\n\n    const img = new globals.window.Image()\n\n    on(\n      img,\n      'load',\n      function (e) {\n        const p = this.parent(Pattern)\n\n        // ensure image size\n        if (this.width() === 0 && this.height() === 0) {\n          this.size(img.width, img.height)\n        }\n\n        if (p instanceof Pattern) {\n          // ensure pattern size if not set\n          if (p.width() === 0 && p.height() === 0) {\n            p.size(this.width(), this.height())\n          }\n        }\n\n        if (typeof callback === 'function') {\n          callback.call(this, e)\n        }\n      },\n      this\n    )\n\n    on(img, 'load error', function () {\n      // dont forget to unbind memory leaking events\n      off(img)\n    })\n\n    return this.attr('href', (img.src = url), xlink)\n  }\n}\n\nregisterAttrHook(function (attr, val, _this) {\n  // convert image fill and stroke to patterns\n  if (attr === 'fill' || attr === 'stroke') {\n    if (isImage.test(val)) {\n      val = _this.root().defs().image(val)\n    }\n  }\n\n  if (val instanceof Image) {\n    val = _this\n      .root()\n      .defs()\n      .pattern(0, 0, (pattern) => {\n        pattern.add(val)\n      })\n  }\n\n  return val\n})\n\nregisterMethods({\n  Container: {\n    // create image element, load image and set its size\n    image: wrapWithAttrCheck(function (source, callback) {\n      return this.put(new Image()).size(0, 0).load(source, callback)\n    })\n  }\n})\n\nregister(Image, 'Image')\n"
  },
  {
    "path": "src/elements/Line.js",
    "content": "import {\n  extend,\n  nodeOrNew,\n  register,\n  wrapWithAttrCheck\n} from '../utils/adopter.js'\nimport { proportionalSize } from '../utils/utils.js'\nimport { registerMethods } from '../utils/methods.js'\nimport PointArray from '../types/PointArray.js'\nimport Shape from './Shape.js'\nimport * as pointed from '../modules/core/pointed.js'\n\nexport default class Line extends Shape {\n  // Initialize node\n  constructor(node, attrs = node) {\n    super(nodeOrNew('line', node), attrs)\n  }\n\n  // Get array\n  array() {\n    return new PointArray([\n      [this.attr('x1'), this.attr('y1')],\n      [this.attr('x2'), this.attr('y2')]\n    ])\n  }\n\n  // Move by left top corner\n  move(x, y) {\n    return this.attr(this.array().move(x, y).toLine())\n  }\n\n  // Overwrite native plot() method\n  plot(x1, y1, x2, y2) {\n    if (x1 == null) {\n      return this.array()\n    } else if (typeof y1 !== 'undefined') {\n      x1 = { x1, y1, x2, y2 }\n    } else {\n      x1 = new PointArray(x1).toLine()\n    }\n\n    return this.attr(x1)\n  }\n\n  // Set element size to given width and height\n  size(width, height) {\n    const p = proportionalSize(this, width, height)\n    return this.attr(this.array().size(p.width, p.height).toLine())\n  }\n}\n\nextend(Line, pointed)\n\nregisterMethods({\n  Container: {\n    // Create a line element\n    line: wrapWithAttrCheck(function (...args) {\n      // make sure plot is called as a setter\n      // x1 is not necessarily a number, it can also be an array, a string and a PointArray\n      return Line.prototype.plot.apply(\n        this.put(new Line()),\n        args[0] != null ? args : [0, 0, 0, 0]\n      )\n    })\n  }\n})\n\nregister(Line, 'Line')\n"
  },
  {
    "path": "src/elements/Marker.js",
    "content": "import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport Container from './Container.js'\n\nexport default class Marker extends Container {\n  // Initialize node\n  constructor(node, attrs = node) {\n    super(nodeOrNew('marker', node), attrs)\n  }\n\n  // Set height of element\n  height(height) {\n    return this.attr('markerHeight', height)\n  }\n\n  orient(orient) {\n    return this.attr('orient', orient)\n  }\n\n  // Set marker refX and refY\n  ref(x, y) {\n    return this.attr('refX', x).attr('refY', y)\n  }\n\n  // Return the fill id\n  toString() {\n    return 'url(#' + this.id() + ')'\n  }\n\n  // Update marker\n  update(block) {\n    // remove all content\n    this.clear()\n\n    // invoke passed block\n    if (typeof block === 'function') {\n      block.call(this, this)\n    }\n\n    return this\n  }\n\n  // Set width of element\n  width(width) {\n    return this.attr('markerWidth', width)\n  }\n}\n\nregisterMethods({\n  Container: {\n    marker(...args) {\n      // Create marker element in defs\n      return this.defs().marker(...args)\n    }\n  },\n  Defs: {\n    // Create marker\n    marker: wrapWithAttrCheck(function (width, height, block) {\n      // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto\n      return this.put(new Marker())\n        .size(width, height)\n        .ref(width / 2, height / 2)\n        .viewbox(0, 0, width, height)\n        .attr('orient', 'auto')\n        .update(block)\n    })\n  },\n  marker: {\n    // Create and attach markers\n    marker(marker, width, height, block) {\n      let attr = ['marker']\n\n      // Build attribute name\n      if (marker !== 'all') attr.push(marker)\n      attr = attr.join('-')\n\n      // Set marker attribute\n      marker =\n        arguments[1] instanceof Marker\n          ? arguments[1]\n          : this.defs().marker(width, height, block)\n\n      return this.attr(attr, marker)\n    }\n  }\n})\n\nregister(Marker, 'Marker')\n"
  },
  {
    "path": "src/elements/Mask.js",
    "content": "import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport Container from './Container.js'\nimport baseFind from '../modules/core/selector.js'\n\nexport default class Mask extends Container {\n  // Initialize node\n  constructor(node, attrs = node) {\n    super(nodeOrNew('mask', node), attrs)\n  }\n\n  // Unmask all masked elements and remove itself\n  remove() {\n    // unmask all targets\n    this.targets().forEach(function (el) {\n      el.unmask()\n    })\n\n    // remove mask from parent\n    return super.remove()\n  }\n\n  targets() {\n    return baseFind('svg [mask*=' + this.id() + ']')\n  }\n}\n\nregisterMethods({\n  Container: {\n    mask: wrapWithAttrCheck(function () {\n      return this.defs().put(new Mask())\n    })\n  },\n  Element: {\n    // Distribute mask to svg element\n    masker() {\n      return this.reference('mask')\n    },\n\n    maskWith(element) {\n      // use given mask or create a new one\n      const masker =\n        element instanceof Mask ? element : this.parent().mask().add(element)\n\n      // apply mask\n      return this.attr('mask', 'url(#' + masker.id() + ')')\n    },\n\n    // Unmask element\n    unmask() {\n      return this.attr('mask', null)\n    }\n  }\n})\n\nregister(Mask, 'Mask')\n"
  },
  {
    "path": "src/elements/Path.js",
    "content": "import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'\nimport { proportionalSize } from '../utils/utils.js'\nimport { registerMethods } from '../utils/methods.js'\nimport PathArray from '../types/PathArray.js'\nimport Shape from './Shape.js'\n\nexport default class Path extends Shape {\n  // Initialize node\n  constructor(node, attrs = node) {\n    super(nodeOrNew('path', node), attrs)\n  }\n\n  // Get array\n  array() {\n    return this._array || (this._array = new PathArray(this.attr('d')))\n  }\n\n  // Clear array cache\n  clear() {\n    delete this._array\n    return this\n  }\n\n  // Set height of element\n  height(height) {\n    return height == null\n      ? this.bbox().height\n      : this.size(this.bbox().width, height)\n  }\n\n  // Move by left top corner\n  move(x, y) {\n    return this.attr('d', this.array().move(x, y))\n  }\n\n  // Plot new path\n  plot(d) {\n    return d == null\n      ? this.array()\n      : this.clear().attr(\n          'd',\n          typeof d === 'string' ? d : (this._array = new PathArray(d))\n        )\n  }\n\n  // Set element size to given width and height\n  size(width, height) {\n    const p = proportionalSize(this, width, height)\n    return this.attr('d', this.array().size(p.width, p.height))\n  }\n\n  // Set width of element\n  width(width) {\n    return width == null\n      ? this.bbox().width\n      : this.size(width, this.bbox().height)\n  }\n\n  // Move by left top corner over x-axis\n  x(x) {\n    return x == null ? this.bbox().x : this.move(x, this.bbox().y)\n  }\n\n  // Move by left top corner over y-axis\n  y(y) {\n    return y == null ? this.bbox().y : this.move(this.bbox().x, y)\n  }\n}\n\n// Define morphable array\nPath.prototype.MorphArray = PathArray\n\n// Add parent method\nregisterMethods({\n  Container: {\n    // Create a wrapped path element\n    path: wrapWithAttrCheck(function (d) {\n      // make sure plot is called as a setter\n      return this.put(new Path()).plot(d || new PathArray())\n    })\n  }\n})\n\nregister(Path, 'Path')\n"
  },
  {
    "path": "src/elements/Pattern.js",
    "content": "import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport Box from '../types/Box.js'\nimport Container from './Container.js'\nimport baseFind from '../modules/core/selector.js'\n\nexport default class Pattern extends Container {\n  // Initialize node\n  constructor(node, attrs = node) {\n    super(nodeOrNew('pattern', node), attrs)\n  }\n\n  // custom attr to handle transform\n  attr(a, b, c) {\n    if (a === 'transform') a = 'patternTransform'\n    return super.attr(a, b, c)\n  }\n\n  bbox() {\n    return new Box()\n  }\n\n  targets() {\n    return baseFind('svg [fill*=' + this.id() + ']')\n  }\n\n  // Alias string conversion to fill\n  toString() {\n    return this.url()\n  }\n\n  // Update pattern by rebuilding\n  update(block) {\n    // remove content\n    this.clear()\n\n    // invoke passed block\n    if (typeof block === 'function') {\n      block.call(this, this)\n    }\n\n    return this\n  }\n\n  // Return the fill id\n  url() {\n    return 'url(#' + this.id() + ')'\n  }\n}\n\nregisterMethods({\n  Container: {\n    // Create pattern element in defs\n    pattern(...args) {\n      return this.defs().pattern(...args)\n    }\n  },\n  Defs: {\n    pattern: wrapWithAttrCheck(function (width, height, block) {\n      return this.put(new Pattern()).update(block).attr({\n        x: 0,\n        y: 0,\n        width: width,\n        height: height,\n        patternUnits: 'userSpaceOnUse'\n      })\n    })\n  }\n})\n\nregister(Pattern, 'Pattern')\n"
  },
  {
    "path": "src/elements/Polygon.js",
    "content": "import {\n  extend,\n  nodeOrNew,\n  register,\n  wrapWithAttrCheck\n} from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport PointArray from '../types/PointArray.js'\nimport Shape from './Shape.js'\nimport * as pointed from '../modules/core/pointed.js'\nimport * as poly from '../modules/core/poly.js'\n\nexport default class Polygon extends Shape {\n  // Initialize node\n  constructor(node, attrs = node) {\n    super(nodeOrNew('polygon', node), attrs)\n  }\n}\n\nregisterMethods({\n  Container: {\n    // Create a wrapped polygon element\n    polygon: wrapWithAttrCheck(function (p) {\n      // make sure plot is called as a setter\n      return this.put(new Polygon()).plot(p || new PointArray())\n    })\n  }\n})\n\nextend(Polygon, pointed)\nextend(Polygon, poly)\nregister(Polygon, 'Polygon')\n"
  },
  {
    "path": "src/elements/Polyline.js",
    "content": "import {\n  extend,\n  nodeOrNew,\n  register,\n  wrapWithAttrCheck\n} from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport PointArray from '../types/PointArray.js'\nimport Shape from './Shape.js'\nimport * as pointed from '../modules/core/pointed.js'\nimport * as poly from '../modules/core/poly.js'\n\nexport default class Polyline extends Shape {\n  // Initialize node\n  constructor(node, attrs = node) {\n    super(nodeOrNew('polyline', node), attrs)\n  }\n}\n\nregisterMethods({\n  Container: {\n    // Create a wrapped polygon element\n    polyline: wrapWithAttrCheck(function (p) {\n      // make sure plot is called as a setter\n      return this.put(new Polyline()).plot(p || new PointArray())\n    })\n  }\n})\n\nextend(Polyline, pointed)\nextend(Polyline, poly)\nregister(Polyline, 'Polyline')\n"
  },
  {
    "path": "src/elements/Rect.js",
    "content": "import {\n  extend,\n  nodeOrNew,\n  register,\n  wrapWithAttrCheck\n} from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport { rx, ry } from '../modules/core/circled.js'\nimport Shape from './Shape.js'\n\nexport default class Rect extends Shape {\n  // Initialize node\n  constructor(node, attrs = node) {\n    super(nodeOrNew('rect', node), attrs)\n  }\n}\n\nextend(Rect, { rx, ry })\n\nregisterMethods({\n  Container: {\n    // Create a rect element\n    rect: wrapWithAttrCheck(function (width, height) {\n      return this.put(new Rect()).size(width, height)\n    })\n  }\n})\n\nregister(Rect, 'Rect')\n"
  },
  {
    "path": "src/elements/Shape.js",
    "content": "import { register } from '../utils/adopter.js'\nimport Element from './Element.js'\n\nexport default class Shape extends Element {}\n\nregister(Shape, 'Shape')\n"
  },
  {
    "path": "src/elements/Stop.js",
    "content": "import { nodeOrNew, register } from '../utils/adopter.js'\nimport Element from './Element.js'\nimport SVGNumber from '../types/SVGNumber.js'\nimport { registerMethods } from '../utils/methods.js'\n\nexport default class Stop extends Element {\n  constructor(node, attrs = node) {\n    super(nodeOrNew('stop', node), attrs)\n  }\n\n  // add color stops\n  update(o) {\n    if (typeof o === 'number' || o instanceof SVGNumber) {\n      o = {\n        offset: arguments[0],\n        color: arguments[1],\n        opacity: arguments[2]\n      }\n    }\n\n    // set attributes\n    if (o.opacity != null) this.attr('stop-opacity', o.opacity)\n    if (o.color != null) this.attr('stop-color', o.color)\n    if (o.offset != null) this.attr('offset', new SVGNumber(o.offset))\n\n    return this\n  }\n}\n\nregisterMethods({\n  Gradient: {\n    // Add a color stop\n    stop: function (offset, color, opacity) {\n      return this.put(new Stop()).update(offset, color, opacity)\n    }\n  }\n})\n\nregister(Stop, 'Stop')\n"
  },
  {
    "path": "src/elements/Style.js",
    "content": "import { nodeOrNew, register } from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport { unCamelCase } from '../utils/utils.js'\nimport Element from './Element.js'\n\nfunction cssRule(selector, rule) {\n  if (!selector) return ''\n  if (!rule) return selector\n\n  let ret = selector + '{'\n\n  for (const i in rule) {\n    ret += unCamelCase(i) + ':' + rule[i] + ';'\n  }\n\n  ret += '}'\n\n  return ret\n}\n\nexport default class Style extends Element {\n  constructor(node, attrs = node) {\n    super(nodeOrNew('style', node), attrs)\n  }\n\n  addText(w = '') {\n    this.node.textContent += w\n    return this\n  }\n\n  font(name, src, params = {}) {\n    return this.rule('@font-face', {\n      fontFamily: name,\n      src: src,\n      ...params\n    })\n  }\n\n  rule(selector, obj) {\n    return this.addText(cssRule(selector, obj))\n  }\n}\n\nregisterMethods('Dom', {\n  style(selector, obj) {\n    return this.put(new Style()).rule(selector, obj)\n  },\n  fontface(name, src, params) {\n    return this.put(new Style()).font(name, src, params)\n  }\n})\n\nregister(Style, 'Style')\n"
  },
  {
    "path": "src/elements/Svg.js",
    "content": "import {\n  adopt,\n  nodeOrNew,\n  register,\n  wrapWithAttrCheck\n} from '../utils/adopter.js'\nimport { svg, xlink, xmlns } from '../modules/core/namespaces.js'\nimport { registerMethods } from '../utils/methods.js'\nimport Container from './Container.js'\nimport Defs from './Defs.js'\nimport { globals } from '../utils/window.js'\n\nexport default class Svg extends Container {\n  constructor(node, attrs = node) {\n    super(nodeOrNew('svg', node), attrs)\n    this.namespace()\n  }\n\n  // Creates and returns defs element\n  defs() {\n    if (!this.isRoot()) return this.root().defs()\n\n    return adopt(this.node.querySelector('defs')) || this.put(new Defs())\n  }\n\n  isRoot() {\n    return (\n      !this.node.parentNode ||\n      (!(this.node.parentNode instanceof globals.window.SVGElement) &&\n        this.node.parentNode.nodeName !== '#document-fragment')\n    )\n  }\n\n  // Add namespaces\n  namespace() {\n    if (!this.isRoot()) return this.root().namespace()\n    return this.attr({ xmlns: svg, version: '1.1' }).attr(\n      'xmlns:xlink',\n      xlink,\n      xmlns\n    )\n  }\n\n  removeNamespace() {\n    return this.attr({ xmlns: null, version: null })\n      .attr('xmlns:xlink', null, xmlns)\n      .attr('xmlns:svgjs', null, xmlns)\n  }\n\n  // Check if this is a root svg\n  // If not, call root() from this element\n  root() {\n    if (this.isRoot()) return this\n    return super.root()\n  }\n}\n\nregisterMethods({\n  Container: {\n    // Create nested svg document\n    nested: wrapWithAttrCheck(function () {\n      return this.put(new Svg())\n    })\n  }\n})\n\nregister(Svg, 'Svg', true)\n"
  },
  {
    "path": "src/elements/Symbol.js",
    "content": "import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport Container from './Container.js'\n\nexport default class Symbol extends Container {\n  // Initialize node\n  constructor(node, attrs = node) {\n    super(nodeOrNew('symbol', node), attrs)\n  }\n}\n\nregisterMethods({\n  Container: {\n    symbol: wrapWithAttrCheck(function () {\n      return this.put(new Symbol())\n    })\n  }\n})\n\nregister(Symbol, 'Symbol')\n"
  },
  {
    "path": "src/elements/Text.js",
    "content": "import {\n  adopt,\n  extend,\n  nodeOrNew,\n  register,\n  wrapWithAttrCheck\n} from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport SVGNumber from '../types/SVGNumber.js'\nimport Shape from './Shape.js'\nimport { globals } from '../utils/window.js'\nimport * as textable from '../modules/core/textable.js'\nimport { isDescriptive, writeDataToDom } from '../utils/utils.js'\n\nexport default class Text extends Shape {\n  // Initialize node\n  constructor(node, attrs = node) {\n    super(nodeOrNew('text', node), attrs)\n\n    this.dom.leading = this.dom.leading ?? new SVGNumber(1.3) // store leading value for rebuilding\n    this._rebuild = true // enable automatic updating of dy values\n    this._build = false // disable build mode for adding multiple lines\n  }\n\n  // Set / get leading\n  leading(value) {\n    // act as getter\n    if (value == null) {\n      return this.dom.leading\n    }\n\n    // act as setter\n    this.dom.leading = new SVGNumber(value)\n\n    return this.rebuild()\n  }\n\n  // Rebuild appearance type\n  rebuild(rebuild) {\n    // store new rebuild flag if given\n    if (typeof rebuild === 'boolean') {\n      this._rebuild = rebuild\n    }\n\n    // define position of all lines\n    if (this._rebuild) {\n      const self = this\n      let blankLineOffset = 0\n      const leading = this.dom.leading\n\n      this.each(function (i) {\n        if (isDescriptive(this.node)) return\n\n        const fontSize = globals.window\n          .getComputedStyle(this.node)\n          .getPropertyValue('font-size')\n\n        const dy = leading * new SVGNumber(fontSize)\n\n        if (this.dom.newLined) {\n          this.attr('x', self.attr('x'))\n\n          if (this.text() === '\\n') {\n            blankLineOffset += dy\n          } else {\n            this.attr('dy', i ? dy + blankLineOffset : 0)\n            blankLineOffset = 0\n          }\n        }\n      })\n\n      this.fire('rebuild')\n    }\n\n    return this\n  }\n\n  // overwrite method from parent to set data properly\n  setData(o) {\n    this.dom = o\n    this.dom.leading = new SVGNumber(o.leading || 1.3)\n    return this\n  }\n\n  writeDataToDom() {\n    writeDataToDom(this, this.dom, { leading: 1.3 })\n    return this\n  }\n\n  // Set the text content\n  text(text) {\n    // act as getter\n    if (text === undefined) {\n      const children = this.node.childNodes\n      let firstLine = 0\n      text = ''\n\n      for (let i = 0, len = children.length; i < len; ++i) {\n        // skip textPaths - they are no lines\n        if (children[i].nodeName === 'textPath' || isDescriptive(children[i])) {\n          if (i === 0) firstLine = i + 1\n          continue\n        }\n\n        // add newline if its not the first child and newLined is set to true\n        if (\n          i !== firstLine &&\n          children[i].nodeType !== 3 &&\n          adopt(children[i]).dom.newLined === true\n        ) {\n          text += '\\n'\n        }\n\n        // add content of this node\n        text += children[i].textContent\n      }\n\n      return text\n    }\n\n    // remove existing content\n    this.clear().build(true)\n\n    if (typeof text === 'function') {\n      // call block\n      text.call(this, this)\n    } else {\n      // store text and make sure text is not blank\n      text = (text + '').split('\\n')\n\n      // build new lines\n      for (let j = 0, jl = text.length; j < jl; j++) {\n        this.newLine(text[j])\n      }\n    }\n\n    // disable build mode and rebuild lines\n    return this.build(false).rebuild()\n  }\n}\n\nextend(Text, textable)\n\nregisterMethods({\n  Container: {\n    // Create text element\n    text: wrapWithAttrCheck(function (text = '') {\n      return this.put(new Text()).text(text)\n    }),\n\n    // Create plain text element\n    plain: wrapWithAttrCheck(function (text = '') {\n      return this.put(new Text()).plain(text)\n    })\n  }\n})\n\nregister(Text, 'Text')\n"
  },
  {
    "path": "src/elements/TextPath.js",
    "content": "import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport { xlink } from '../modules/core/namespaces.js'\nimport Path from './Path.js'\nimport PathArray from '../types/PathArray.js'\nimport Text from './Text.js'\nimport baseFind from '../modules/core/selector.js'\n\nexport default class TextPath extends Text {\n  // Initialize node\n  constructor(node, attrs = node) {\n    super(nodeOrNew('textPath', node), attrs)\n  }\n\n  // return the array of the path track element\n  array() {\n    const track = this.track()\n\n    return track ? track.array() : null\n  }\n\n  // Plot path if any\n  plot(d) {\n    const track = this.track()\n    let pathArray = null\n\n    if (track) {\n      pathArray = track.plot(d)\n    }\n\n    return d == null ? pathArray : this\n  }\n\n  // Get the path element\n  track() {\n    return this.reference('href')\n  }\n}\n\nregisterMethods({\n  Container: {\n    textPath: wrapWithAttrCheck(function (text, path) {\n      // Convert text to instance if needed\n      if (!(text instanceof Text)) {\n        text = this.text(text)\n      }\n\n      return text.path(path)\n    })\n  },\n  Text: {\n    // Create path for text to run on\n    path: wrapWithAttrCheck(function (track, importNodes = true) {\n      const textPath = new TextPath()\n\n      // if track is a path, reuse it\n      if (!(track instanceof Path)) {\n        // create path element\n        track = this.defs().path(track)\n      }\n\n      // link textPath to path and add content\n      textPath.attr('href', '#' + track, xlink)\n\n      // Transplant all nodes from text to textPath\n      let node\n      if (importNodes) {\n        while ((node = this.node.firstChild)) {\n          textPath.node.appendChild(node)\n        }\n      }\n\n      // add textPath element as child node and return textPath\n      return this.put(textPath)\n    }),\n\n    // Get the textPath children\n    textPath() {\n      return this.findOne('textPath')\n    }\n  },\n  Path: {\n    // creates a textPath from this path\n    text: wrapWithAttrCheck(function (text) {\n      // Convert text to instance if needed\n      if (!(text instanceof Text)) {\n        text = new Text().addTo(this.parent()).text(text)\n      }\n\n      // Create textPath from text and path and return\n      return text.path(this)\n    }),\n\n    targets() {\n      return baseFind('svg textPath').filter((node) => {\n        return (node.attr('href') || '').includes(this.id())\n      })\n\n      // Does not work in IE11. Use when IE support is dropped\n      // return baseFind('svg textPath[*|href*=' + this.id() + ']')\n    }\n  }\n})\n\nTextPath.prototype.MorphArray = PathArray\nregister(TextPath, 'TextPath')\n"
  },
  {
    "path": "src/elements/Tspan.js",
    "content": "import {\n  extend,\n  nodeOrNew,\n  register,\n  wrapWithAttrCheck\n} from '../utils/adopter.js'\nimport { globals } from '../utils/window.js'\nimport { registerMethods } from '../utils/methods.js'\nimport SVGNumber from '../types/SVGNumber.js'\nimport Shape from './Shape.js'\nimport Text from './Text.js'\nimport * as textable from '../modules/core/textable.js'\n\nexport default class Tspan extends Shape {\n  // Initialize node\n  constructor(node, attrs = node) {\n    super(nodeOrNew('tspan', node), attrs)\n    this._build = false // disable build mode for adding multiple lines\n  }\n\n  // Shortcut dx\n  dx(dx) {\n    return this.attr('dx', dx)\n  }\n\n  // Shortcut dy\n  dy(dy) {\n    return this.attr('dy', dy)\n  }\n\n  // Create new line\n  newLine() {\n    // mark new line\n    this.dom.newLined = true\n\n    // fetch parent\n    const text = this.parent()\n\n    // early return in case we are not in a text element\n    if (!(text instanceof Text)) {\n      return this\n    }\n\n    const i = text.index(this)\n\n    const fontSize = globals.window\n      .getComputedStyle(this.node)\n      .getPropertyValue('font-size')\n    const dy = text.dom.leading * new SVGNumber(fontSize)\n\n    // apply new position\n    return this.dy(i ? dy : 0).attr('x', text.x())\n  }\n\n  // Set text content\n  text(text) {\n    if (text == null)\n      return this.node.textContent + (this.dom.newLined ? '\\n' : '')\n\n    if (typeof text === 'function') {\n      this.clear().build(true)\n      text.call(this, this)\n      this.build(false)\n    } else {\n      this.plain(text)\n    }\n\n    return this\n  }\n}\n\nextend(Tspan, textable)\n\nregisterMethods({\n  Tspan: {\n    tspan: wrapWithAttrCheck(function (text = '') {\n      const tspan = new Tspan()\n\n      // clear if build mode is disabled\n      if (!this._build) {\n        this.clear()\n      }\n\n      // add new tspan\n      return this.put(tspan).text(text)\n    })\n  },\n  Text: {\n    newLine: function (text = '') {\n      return this.tspan(text).newLine()\n    }\n  }\n})\n\nregister(Tspan, 'Tspan')\n"
  },
  {
    "path": "src/elements/Use.js",
    "content": "import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport { xlink } from '../modules/core/namespaces.js'\nimport Shape from './Shape.js'\n\nexport default class Use extends Shape {\n  constructor(node, attrs = node) {\n    super(nodeOrNew('use', node), attrs)\n  }\n\n  // Use element as a reference\n  use(element, file) {\n    // Set lined element\n    return this.attr('href', (file || '') + '#' + element, xlink)\n  }\n}\n\nregisterMethods({\n  Container: {\n    // Create a use element\n    use: wrapWithAttrCheck(function (element, file) {\n      return this.put(new Use()).use(element, file)\n    })\n  }\n})\n\nregister(Use, 'Use')\n"
  },
  {
    "path": "src/main.js",
    "content": "/* Optional Modules */\nimport './modules/optional/arrange.js'\nimport './modules/optional/class.js'\nimport './modules/optional/css.js'\nimport './modules/optional/data.js'\nimport './modules/optional/memory.js'\nimport './modules/optional/sugar.js'\nimport './modules/optional/transform.js'\n\nimport { extend, makeInstance } from './utils/adopter.js'\nimport { getMethodNames, getMethodsFor } from './utils/methods.js'\nimport Box from './types/Box.js'\nimport Color from './types/Color.js'\nimport Container from './elements/Container.js'\nimport Defs from './elements/Defs.js'\nimport Dom from './elements/Dom.js'\nimport Element from './elements/Element.js'\nimport Ellipse from './elements/Ellipse.js'\nimport EventTarget from './types/EventTarget.js'\nimport Fragment from './elements/Fragment.js'\nimport Gradient from './elements/Gradient.js'\nimport Image from './elements/Image.js'\nimport Line from './elements/Line.js'\nimport List from './types/List.js'\nimport Marker from './elements/Marker.js'\nimport Matrix from './types/Matrix.js'\nimport Morphable, {\n  NonMorphable,\n  ObjectBag,\n  TransformBag,\n  makeMorphable,\n  registerMorphableType\n} from './animation/Morphable.js'\nimport Path from './elements/Path.js'\nimport PathArray from './types/PathArray.js'\nimport Pattern from './elements/Pattern.js'\nimport PointArray from './types/PointArray.js'\nimport Point from './types/Point.js'\nimport Polygon from './elements/Polygon.js'\nimport Polyline from './elements/Polyline.js'\nimport Rect from './elements/Rect.js'\nimport Runner from './animation/Runner.js'\nimport SVGArray from './types/SVGArray.js'\nimport SVGNumber from './types/SVGNumber.js'\nimport Shape from './elements/Shape.js'\nimport Svg from './elements/Svg.js'\nimport Symbol from './elements/Symbol.js'\nimport Text from './elements/Text.js'\nimport Tspan from './elements/Tspan.js'\nimport * as defaults from './modules/core/defaults.js'\nimport * as utils from './utils/utils.js'\nimport * as namespaces from './modules/core/namespaces.js'\nimport * as regex from './modules/core/regex.js'\n\nexport {\n  Morphable,\n  registerMorphableType,\n  makeMorphable,\n  TransformBag,\n  ObjectBag,\n  NonMorphable\n}\n\nexport { defaults, utils, namespaces, regex }\nexport const SVG = makeInstance\nexport { default as parser } from './modules/core/parser.js'\nexport { default as find } from './modules/core/selector.js'\nexport * from './modules/core/event.js'\nexport * from './utils/adopter.js'\nexport {\n  getWindow,\n  registerWindow,\n  restoreWindow,\n  saveWindow,\n  withWindow\n} from './utils/window.js'\n\n/* Animation Modules */\nexport { default as Animator } from './animation/Animator.js'\nexport {\n  Controller,\n  Ease,\n  PID,\n  Spring,\n  easing\n} from './animation/Controller.js'\nexport { default as Queue } from './animation/Queue.js'\nexport { default as Runner } from './animation/Runner.js'\nexport { default as Timeline } from './animation/Timeline.js'\n\n/* Types */\nexport { default as Array } from './types/SVGArray.js'\nexport { default as Box } from './types/Box.js'\nexport { default as Color } from './types/Color.js'\nexport { default as EventTarget } from './types/EventTarget.js'\nexport { default as Matrix } from './types/Matrix.js'\nexport { default as Number } from './types/SVGNumber.js'\nexport { default as PathArray } from './types/PathArray.js'\nexport { default as Point } from './types/Point.js'\nexport { default as PointArray } from './types/PointArray.js'\nexport { default as List } from './types/List.js'\n\n/* Elements */\nexport { default as Circle } from './elements/Circle.js'\nexport { default as ClipPath } from './elements/ClipPath.js'\nexport { default as Container } from './elements/Container.js'\nexport { default as Defs } from './elements/Defs.js'\nexport { default as Dom } from './elements/Dom.js'\nexport { default as Element } from './elements/Element.js'\nexport { default as Ellipse } from './elements/Ellipse.js'\nexport { default as ForeignObject } from './elements/ForeignObject.js'\nexport { default as Fragment } from './elements/Fragment.js'\nexport { default as Gradient } from './elements/Gradient.js'\nexport { default as G } from './elements/G.js'\nexport { default as A } from './elements/A.js'\nexport { default as Image } from './elements/Image.js'\nexport { default as Line } from './elements/Line.js'\nexport { default as Marker } from './elements/Marker.js'\nexport { default as Mask } from './elements/Mask.js'\nexport { default as Path } from './elements/Path.js'\nexport { default as Pattern } from './elements/Pattern.js'\nexport { default as Polygon } from './elements/Polygon.js'\nexport { default as Polyline } from './elements/Polyline.js'\nexport { default as Rect } from './elements/Rect.js'\nexport { default as Shape } from './elements/Shape.js'\nexport { default as Stop } from './elements/Stop.js'\nexport { default as Style } from './elements/Style.js'\nexport { default as Svg } from './elements/Svg.js'\nexport { default as Symbol } from './elements/Symbol.js'\nexport { default as Text } from './elements/Text.js'\nexport { default as TextPath } from './elements/TextPath.js'\nexport { default as Tspan } from './elements/Tspan.js'\nexport { default as Use } from './elements/Use.js'\n\nextend([Svg, Symbol, Image, Pattern, Marker], getMethodsFor('viewbox'))\n\nextend([Line, Polyline, Polygon, Path], getMethodsFor('marker'))\n\nextend(Text, getMethodsFor('Text'))\nextend(Path, getMethodsFor('Path'))\n\nextend(Defs, getMethodsFor('Defs'))\n\nextend([Text, Tspan], getMethodsFor('Tspan'))\n\nextend([Rect, Ellipse, Gradient, Runner], getMethodsFor('radius'))\n\nextend(EventTarget, getMethodsFor('EventTarget'))\nextend(Dom, getMethodsFor('Dom'))\nextend(Element, getMethodsFor('Element'))\nextend(Shape, getMethodsFor('Shape'))\nextend([Container, Fragment], getMethodsFor('Container'))\nextend(Gradient, getMethodsFor('Gradient'))\n\nextend(Runner, getMethodsFor('Runner'))\n\nList.extend(getMethodNames())\n\nregisterMorphableType([\n  SVGNumber,\n  Color,\n  Box,\n  Matrix,\n  SVGArray,\n  PointArray,\n  PathArray,\n  Point\n])\n\nmakeMorphable()\n"
  },
  {
    "path": "src/modules/core/attr.js",
    "content": "import { attrs as defaults } from './defaults.js'\nimport { isNumber } from './regex.js'\nimport Color from '../../types/Color.js'\nimport SVGArray from '../../types/SVGArray.js'\nimport SVGNumber from '../../types/SVGNumber.js'\n\nconst colorAttributes = new Set([\n  'fill',\n  'stroke',\n  'color',\n  'bgcolor',\n  'stop-color',\n  'flood-color',\n  'lighting-color'\n])\n\nconst hooks = []\nexport function registerAttrHook(fn) {\n  hooks.push(fn)\n}\n\n// Set svg element attribute\nexport default function attr(attr, val, ns) {\n  // act as full getter\n  if (attr == null) {\n    // get an object of attributes\n    attr = {}\n    val = this.node.attributes\n\n    for (const node of val) {\n      attr[node.nodeName] = isNumber.test(node.nodeValue)\n        ? parseFloat(node.nodeValue)\n        : node.nodeValue\n    }\n\n    return attr\n  } else if (attr instanceof Array) {\n    // loop through array and get all values\n    return attr.reduce((last, curr) => {\n      last[curr] = this.attr(curr)\n      return last\n    }, {})\n  } else if (typeof attr === 'object' && attr.constructor === Object) {\n    // apply every attribute individually if an object is passed\n    for (val in attr) this.attr(val, attr[val])\n  } else if (val === null) {\n    // remove value\n    this.node.removeAttribute(attr)\n  } else if (val == null) {\n    // act as a getter if the first and only argument is not an object\n    val = this.node.getAttribute(attr)\n    return val == null\n      ? defaults[attr]\n      : isNumber.test(val)\n        ? parseFloat(val)\n        : val\n  } else {\n    // Loop through hooks and execute them to convert value\n    val = hooks.reduce((_val, hook) => {\n      return hook(attr, _val, this)\n    }, val)\n\n    // ensure correct numeric values (also accepts NaN and Infinity)\n    if (typeof val === 'number') {\n      val = new SVGNumber(val)\n    } else if (colorAttributes.has(attr) && Color.isColor(val)) {\n      // ensure full hex color\n      val = new Color(val)\n    } else if (val.constructor === Array) {\n      // Check for plain arrays and parse array values\n      val = new SVGArray(val)\n    }\n\n    // if the passed attribute is leading...\n    if (attr === 'leading') {\n      // ... call the leading method instead\n      if (this.leading) {\n        this.leading(val)\n      }\n    } else {\n      // set given attribute on node\n      typeof ns === 'string'\n        ? this.node.setAttributeNS(ns, attr, val.toString())\n        : this.node.setAttribute(attr, val.toString())\n    }\n\n    // rebuild if required\n    if (this.rebuild && (attr === 'font-size' || attr === 'x')) {\n      this.rebuild()\n    }\n  }\n\n  return this\n}\n"
  },
  {
    "path": "src/modules/core/circled.js",
    "content": "import SVGNumber from '../../types/SVGNumber.js'\n\n// Radius x value\nexport function rx(rx) {\n  return this.attr('rx', rx)\n}\n\n// Radius y value\nexport function ry(ry) {\n  return this.attr('ry', ry)\n}\n\n// Move over x-axis\nexport function x(x) {\n  return x == null ? this.cx() - this.rx() : this.cx(x + this.rx())\n}\n\n// Move over y-axis\nexport function y(y) {\n  return y == null ? this.cy() - this.ry() : this.cy(y + this.ry())\n}\n\n// Move by center over x-axis\nexport function cx(x) {\n  return this.attr('cx', x)\n}\n\n// Move by center over y-axis\nexport function cy(y) {\n  return this.attr('cy', y)\n}\n\n// Set width of element\nexport function width(width) {\n  return width == null ? this.rx() * 2 : this.rx(new SVGNumber(width).divide(2))\n}\n\n// Set height of element\nexport function height(height) {\n  return height == null\n    ? this.ry() * 2\n    : this.ry(new SVGNumber(height).divide(2))\n}\n"
  },
  {
    "path": "src/modules/core/containerGeometry.js",
    "content": "import Matrix from '../../types/Matrix.js'\nimport Point from '../../types/Point.js'\nimport Box from '../../types/Box.js'\nimport { proportionalSize } from '../../utils/utils.js'\nimport { getWindow } from '../../utils/window.js'\n\nexport function dmove(dx, dy) {\n  this.children().forEach((child) => {\n    let bbox\n\n    // We have to wrap this for elements that dont have a bbox\n    // e.g. title and other descriptive elements\n    try {\n      // Get the childs bbox\n      // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1905039\n      // Because bbox for nested svgs returns the contents bbox in the coordinate space of the svg itself (weird!), we cant use bbox for svgs\n      // Therefore we have to use getBoundingClientRect. But THAT is broken (as explained in the bug).\n      // Funnily enough the broken behavior would work for us but that breaks it in chrome\n      // So we have to replicate the broken behavior of FF by just reading the attributes of the svg itself\n      bbox =\n        child.node instanceof getWindow().SVGSVGElement\n          ? new Box(child.attr(['x', 'y', 'width', 'height']))\n          : child.bbox()\n    } catch (e) {\n      return\n    }\n\n    // Get childs matrix\n    const m = new Matrix(child)\n    // Translate childs matrix by amount and\n    // transform it back into parents space\n    const matrix = m.translate(dx, dy).transform(m.inverse())\n    // Calculate new x and y from old box\n    const p = new Point(bbox.x, bbox.y).transform(matrix)\n    // Move element\n    child.move(p.x, p.y)\n  })\n\n  return this\n}\n\nexport function dx(dx) {\n  return this.dmove(dx, 0)\n}\n\nexport function dy(dy) {\n  return this.dmove(0, dy)\n}\n\nexport function height(height, box = this.bbox()) {\n  if (height == null) return box.height\n  return this.size(box.width, height, box)\n}\n\nexport function move(x = 0, y = 0, box = this.bbox()) {\n  const dx = x - box.x\n  const dy = y - box.y\n\n  return this.dmove(dx, dy)\n}\n\nexport function size(width, height, box = this.bbox()) {\n  const p = proportionalSize(this, width, height, box)\n  const scaleX = p.width / box.width\n  const scaleY = p.height / box.height\n\n  this.children().forEach((child) => {\n    const o = new Point(box).transform(new Matrix(child).inverse())\n    child.scale(scaleX, scaleY, o.x, o.y)\n  })\n\n  return this\n}\n\nexport function width(width, box = this.bbox()) {\n  if (width == null) return box.width\n  return this.size(width, box.height, box)\n}\n\nexport function x(x, box = this.bbox()) {\n  if (x == null) return box.x\n  return this.move(x, box.y, box)\n}\n\nexport function y(y, box = this.bbox()) {\n  if (y == null) return box.y\n  return this.move(box.x, y, box)\n}\n"
  },
  {
    "path": "src/modules/core/defaults.js",
    "content": "export function noop() {}\n\n// Default animation values\nexport const timeline = {\n  duration: 400,\n  ease: '>',\n  delay: 0\n}\n\n// Default attribute values\nexport const attrs = {\n  // fill and stroke\n  'fill-opacity': 1,\n  'stroke-opacity': 1,\n  'stroke-width': 0,\n  'stroke-linejoin': 'miter',\n  'stroke-linecap': 'butt',\n  fill: '#000000',\n  stroke: '#000000',\n  opacity: 1,\n\n  // position\n  x: 0,\n  y: 0,\n  cx: 0,\n  cy: 0,\n\n  // size\n  width: 0,\n  height: 0,\n\n  // radius\n  r: 0,\n  rx: 0,\n  ry: 0,\n\n  // gradient\n  offset: 0,\n  'stop-opacity': 1,\n  'stop-color': '#000000',\n\n  // text\n  'text-anchor': 'start'\n}\n"
  },
  {
    "path": "src/modules/core/event.js",
    "content": "import { delimiter } from './regex.js'\nimport { makeInstance } from '../../utils/adopter.js'\nimport { globals } from '../../utils/window.js'\n\nlet listenerId = 0\nexport const windowEvents = {}\n\nexport function getEvents(instance) {\n  let n = instance.getEventHolder()\n\n  // We dont want to save events in global space\n  if (n === globals.window) n = windowEvents\n  if (!n.events) n.events = {}\n  return n.events\n}\n\nexport function getEventTarget(instance) {\n  return instance.getEventTarget()\n}\n\nexport function clearEvents(instance) {\n  let n = instance.getEventHolder()\n  if (n === globals.window) n = windowEvents\n  if (n.events) n.events = {}\n}\n\n// Add event binder in the SVG namespace\nexport function on(node, events, listener, binding, options) {\n  const l = listener.bind(binding || node)\n  const instance = makeInstance(node)\n  const bag = getEvents(instance)\n  const n = getEventTarget(instance)\n\n  // events can be an array of events or a string of events\n  events = Array.isArray(events) ? events : events.split(delimiter)\n\n  // add id to listener\n  if (!listener._svgjsListenerId) {\n    listener._svgjsListenerId = ++listenerId\n  }\n\n  events.forEach(function (event) {\n    const ev = event.split('.')[0]\n    const ns = event.split('.')[1] || '*'\n\n    // ensure valid object\n    bag[ev] = bag[ev] || {}\n    bag[ev][ns] = bag[ev][ns] || {}\n\n    // reference listener\n    bag[ev][ns][listener._svgjsListenerId] = l\n\n    // add listener\n    n.addEventListener(ev, l, options || false)\n  })\n}\n\n// Add event unbinder in the SVG namespace\nexport function off(node, events, listener, options) {\n  const instance = makeInstance(node)\n  const bag = getEvents(instance)\n  const n = getEventTarget(instance)\n\n  // listener can be a function or a number\n  if (typeof listener === 'function') {\n    listener = listener._svgjsListenerId\n    if (!listener) return\n  }\n\n  // events can be an array of events or a string or undefined\n  events = Array.isArray(events) ? events : (events || '').split(delimiter)\n\n  events.forEach(function (event) {\n    const ev = event && event.split('.')[0]\n    const ns = event && event.split('.')[1]\n    let namespace, l\n\n    if (listener) {\n      // remove listener reference\n      if (bag[ev] && bag[ev][ns || '*']) {\n        // removeListener\n        n.removeEventListener(\n          ev,\n          bag[ev][ns || '*'][listener],\n          options || false\n        )\n\n        delete bag[ev][ns || '*'][listener]\n      }\n    } else if (ev && ns) {\n      // remove all listeners for a namespaced event\n      if (bag[ev] && bag[ev][ns]) {\n        for (l in bag[ev][ns]) {\n          off(n, [ev, ns].join('.'), l)\n        }\n\n        delete bag[ev][ns]\n      }\n    } else if (ns) {\n      // remove all listeners for a specific namespace\n      for (event in bag) {\n        for (namespace in bag[event]) {\n          if (ns === namespace) {\n            off(n, [event, ns].join('.'))\n          }\n        }\n      }\n    } else if (ev) {\n      // remove all listeners for the event\n      if (bag[ev]) {\n        for (namespace in bag[ev]) {\n          off(n, [ev, namespace].join('.'))\n        }\n\n        delete bag[ev]\n      }\n    } else {\n      // remove all listeners on a given node\n      for (event in bag) {\n        off(n, event)\n      }\n\n      clearEvents(instance)\n    }\n  })\n}\n\nexport function dispatch(node, event, data, options) {\n  const n = getEventTarget(node)\n\n  // Dispatch event\n  if (event instanceof globals.window.Event) {\n    n.dispatchEvent(event)\n  } else {\n    event = new globals.window.CustomEvent(event, {\n      detail: data,\n      cancelable: true,\n      ...options\n    })\n    n.dispatchEvent(event)\n  }\n  return event\n}\n"
  },
  {
    "path": "src/modules/core/gradiented.js",
    "content": "import SVGNumber from '../../types/SVGNumber.js'\n\nexport function from(x, y) {\n  return (this._element || this).type === 'radialGradient'\n    ? this.attr({ fx: new SVGNumber(x), fy: new SVGNumber(y) })\n    : this.attr({ x1: new SVGNumber(x), y1: new SVGNumber(y) })\n}\n\nexport function to(x, y) {\n  return (this._element || this).type === 'radialGradient'\n    ? this.attr({ cx: new SVGNumber(x), cy: new SVGNumber(y) })\n    : this.attr({ x2: new SVGNumber(x), y2: new SVGNumber(y) })\n}\n"
  },
  {
    "path": "src/modules/core/namespaces.js",
    "content": "// Default namespaces\nexport const svg = 'http://www.w3.org/2000/svg'\nexport const html = 'http://www.w3.org/1999/xhtml'\nexport const xmlns = 'http://www.w3.org/2000/xmlns/'\nexport const xlink = 'http://www.w3.org/1999/xlink'\n"
  },
  {
    "path": "src/modules/core/parser.js",
    "content": "import { globals } from '../../utils/window.js'\nimport { makeInstance } from '../../utils/adopter.js'\n\nexport default function parser() {\n  // Reuse cached element if possible\n  if (!parser.nodes) {\n    const svg = makeInstance().size(2, 0)\n    svg.node.style.cssText = [\n      'opacity: 0',\n      'position: absolute',\n      'left: -100%',\n      'top: -100%',\n      'overflow: hidden'\n    ].join(';')\n\n    svg.attr('focusable', 'false')\n    svg.attr('aria-hidden', 'true')\n\n    const path = svg.path().node\n\n    parser.nodes = { svg, path }\n  }\n\n  if (!parser.nodes.svg.node.parentNode) {\n    const b = globals.document.body || globals.document.documentElement\n    parser.nodes.svg.addTo(b)\n  }\n\n  return parser.nodes\n}\n"
  },
  {
    "path": "src/modules/core/pointed.js",
    "content": "import PointArray from '../../types/PointArray.js'\n\nexport const MorphArray = PointArray\n\n// Move by left top corner over x-axis\nexport function x(x) {\n  return x == null ? this.bbox().x : this.move(x, this.bbox().y)\n}\n\n// Move by left top corner over y-axis\nexport function y(y) {\n  return y == null ? this.bbox().y : this.move(this.bbox().x, y)\n}\n\n// Set width of element\nexport function width(width) {\n  const b = this.bbox()\n  return width == null ? b.width : this.size(width, b.height)\n}\n\n// Set height of element\nexport function height(height) {\n  const b = this.bbox()\n  return height == null ? b.height : this.size(b.width, height)\n}\n"
  },
  {
    "path": "src/modules/core/poly.js",
    "content": "import { proportionalSize } from '../../utils/utils.js'\nimport PointArray from '../../types/PointArray.js'\n\n// Get array\nexport function array() {\n  return this._array || (this._array = new PointArray(this.attr('points')))\n}\n\n// Clear array cache\nexport function clear() {\n  delete this._array\n  return this\n}\n\n// Move by left top corner\nexport function move(x, y) {\n  return this.attr('points', this.array().move(x, y))\n}\n\n// Plot new path\nexport function plot(p) {\n  return p == null\n    ? this.array()\n    : this.clear().attr(\n        'points',\n        typeof p === 'string' ? p : (this._array = new PointArray(p))\n      )\n}\n\n// Set element size to given width and height\nexport function size(width, height) {\n  const p = proportionalSize(this, width, height)\n  return this.attr('points', this.array().size(p.width, p.height))\n}\n"
  },
  {
    "path": "src/modules/core/regex.js",
    "content": "// Parse unit value\nexport const numberAndUnit =\n  /^([+-]?(\\d+(\\.\\d*)?|\\.\\d+)(e[+-]?\\d+)?)([a-z%]*)$/i\n\n// Parse hex value\nexport const hex = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i\n\n// Parse rgb value\nexport const rgb = /rgb\\((\\d+),(\\d+),(\\d+)\\)/\n\n// Parse reference id\nexport const reference = /(#[a-z_][a-z0-9\\-_]*)/i\n\n// splits a transformation chain\nexport const transforms = /\\)\\s*,?\\s*/\n\n// Whitespace\nexport const whitespace = /\\s/g\n\n// Test hex value\nexport const isHex = /^#[a-f0-9]{3}$|^#[a-f0-9]{6}$/i\n\n// Test rgb value\nexport const isRgb = /^rgb\\(/\n\n// Test for blank string\nexport const isBlank = /^(\\s+)?$/\n\n// Test for numeric string\nexport const isNumber = /^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)(e[+-]?\\d+)?$/i\n\n// Test for image url\nexport const isImage = /\\.(jpg|jpeg|png|gif|svg)(\\?[^=]+.*)?/i\n\n// split at whitespace and comma\nexport const delimiter = /[\\s,]+/\n\n// Test for path letter\nexport const isPathLetter = /[MLHVCSQTAZ]/i\n"
  },
  {
    "path": "src/modules/core/selector.js",
    "content": "import { adopt } from '../../utils/adopter.js'\nimport { globals } from '../../utils/window.js'\nimport { map } from '../../utils/utils.js'\nimport List from '../../types/List.js'\n\nexport default function baseFind(query, parent) {\n  return new List(\n    map((parent || globals.document).querySelectorAll(query), function (node) {\n      return adopt(node)\n    })\n  )\n}\n\n// Scoped find method\nexport function find(query) {\n  return baseFind(query, this.node)\n}\n\nexport function findOne(query) {\n  return adopt(this.node.querySelector(query))\n}\n"
  },
  {
    "path": "src/modules/core/textable.js",
    "content": "import { globals } from '../../utils/window.js'\n\n// Create plain text node\nexport function plain(text) {\n  // clear if build mode is disabled\n  if (this._build === false) {\n    this.clear()\n  }\n\n  // create text node\n  this.node.appendChild(globals.document.createTextNode(text))\n\n  return this\n}\n\n// Get length of text element\nexport function length() {\n  return this.node.getComputedTextLength()\n}\n\n// Move over x-axis\n// Text is moved by its bounding box\n// text-anchor does NOT matter\nexport function x(x, box = this.bbox()) {\n  if (x == null) {\n    return box.x\n  }\n\n  return this.attr('x', this.attr('x') + x - box.x)\n}\n\n// Move over y-axis\nexport function y(y, box = this.bbox()) {\n  if (y == null) {\n    return box.y\n  }\n\n  return this.attr('y', this.attr('y') + y - box.y)\n}\n\nexport function move(x, y, box = this.bbox()) {\n  return this.x(x, box).y(y, box)\n}\n\n// Move center over x-axis\nexport function cx(x, box = this.bbox()) {\n  if (x == null) {\n    return box.cx\n  }\n\n  return this.attr('x', this.attr('x') + x - box.cx)\n}\n\n// Move center over y-axis\nexport function cy(y, box = this.bbox()) {\n  if (y == null) {\n    return box.cy\n  }\n\n  return this.attr('y', this.attr('y') + y - box.cy)\n}\n\nexport function center(x, y, box = this.bbox()) {\n  return this.cx(x, box).cy(y, box)\n}\n\nexport function ax(x) {\n  return this.attr('x', x)\n}\n\nexport function ay(y) {\n  return this.attr('y', y)\n}\n\nexport function amove(x, y) {\n  return this.ax(x).ay(y)\n}\n\n// Enable / disable build mode\nexport function build(build) {\n  this._build = !!build\n  return this\n}\n"
  },
  {
    "path": "src/modules/optional/arrange.js",
    "content": "import { makeInstance } from '../../utils/adopter.js'\nimport { registerMethods } from '../../utils/methods.js'\n\n// Get all siblings, including myself\nexport function siblings() {\n  return this.parent().children()\n}\n\n// Get the current position siblings\nexport function position() {\n  return this.parent().index(this)\n}\n\n// Get the next element (will return null if there is none)\nexport function next() {\n  return this.siblings()[this.position() + 1]\n}\n\n// Get the next element (will return null if there is none)\nexport function prev() {\n  return this.siblings()[this.position() - 1]\n}\n\n// Send given element one step forward\nexport function forward() {\n  const i = this.position()\n  const p = this.parent()\n\n  // move node one step forward\n  p.add(this.remove(), i + 1)\n\n  return this\n}\n\n// Send given element one step backward\nexport function backward() {\n  const i = this.position()\n  const p = this.parent()\n\n  p.add(this.remove(), i ? i - 1 : 0)\n\n  return this\n}\n\n// Send given element all the way to the front\nexport function front() {\n  const p = this.parent()\n\n  // Move node forward\n  p.add(this.remove())\n\n  return this\n}\n\n// Send given element all the way to the back\nexport function back() {\n  const p = this.parent()\n\n  // Move node back\n  p.add(this.remove(), 0)\n\n  return this\n}\n\n// Inserts a given element before the targeted element\nexport function before(element) {\n  element = makeInstance(element)\n  element.remove()\n\n  const i = this.position()\n\n  this.parent().add(element, i)\n\n  return this\n}\n\n// Inserts a given element after the targeted element\nexport function after(element) {\n  element = makeInstance(element)\n  element.remove()\n\n  const i = this.position()\n\n  this.parent().add(element, i + 1)\n\n  return this\n}\n\nexport function insertBefore(element) {\n  element = makeInstance(element)\n  element.before(this)\n  return this\n}\n\nexport function insertAfter(element) {\n  element = makeInstance(element)\n  element.after(this)\n  return this\n}\n\nregisterMethods('Dom', {\n  siblings,\n  position,\n  next,\n  prev,\n  forward,\n  backward,\n  front,\n  back,\n  before,\n  after,\n  insertBefore,\n  insertAfter\n})\n"
  },
  {
    "path": "src/modules/optional/class.js",
    "content": "import { delimiter } from '../core/regex.js'\nimport { registerMethods } from '../../utils/methods.js'\n\n// Return array of classes on the node\nexport function classes() {\n  const attr = this.attr('class')\n  return attr == null ? [] : attr.trim().split(delimiter)\n}\n\n// Return true if class exists on the node, false otherwise\nexport function hasClass(name) {\n  return this.classes().indexOf(name) !== -1\n}\n\n// Add class to the node\nexport function addClass(name) {\n  if (!this.hasClass(name)) {\n    const array = this.classes()\n    array.push(name)\n    this.attr('class', array.join(' '))\n  }\n\n  return this\n}\n\n// Remove class from the node\nexport function removeClass(name) {\n  if (this.hasClass(name)) {\n    this.attr(\n      'class',\n      this.classes()\n        .filter(function (c) {\n          return c !== name\n        })\n        .join(' ')\n    )\n  }\n\n  return this\n}\n\n// Toggle the presence of a class on the node\nexport function toggleClass(name) {\n  return this.hasClass(name) ? this.removeClass(name) : this.addClass(name)\n}\n\nregisterMethods('Dom', {\n  classes,\n  hasClass,\n  addClass,\n  removeClass,\n  toggleClass\n})\n"
  },
  {
    "path": "src/modules/optional/css.js",
    "content": "import { isBlank } from '../core/regex.js'\nimport { registerMethods } from '../../utils/methods.js'\n\n// Dynamic style generator\nexport function css(style, val) {\n  const ret = {}\n  if (arguments.length === 0) {\n    // get full style as object\n    this.node.style.cssText\n      .split(/\\s*;\\s*/)\n      .filter(function (el) {\n        return !!el.length\n      })\n      .forEach(function (el) {\n        const t = el.split(/\\s*:\\s*/)\n        ret[t[0]] = t[1]\n      })\n    return ret\n  }\n\n  if (arguments.length < 2) {\n    // get style properties as array\n    if (Array.isArray(style)) {\n      for (const name of style) {\n        const cased = name\n        ret[name] = this.node.style.getPropertyValue(cased)\n      }\n      return ret\n    }\n\n    // get style for property\n    if (typeof style === 'string') {\n      return this.node.style.getPropertyValue(style)\n    }\n\n    // set styles in object\n    if (typeof style === 'object') {\n      for (const name in style) {\n        // set empty string if null/undefined/'' was given\n        this.node.style.setProperty(\n          name,\n          style[name] == null || isBlank.test(style[name]) ? '' : style[name]\n        )\n      }\n    }\n  }\n\n  // set style for property\n  if (arguments.length === 2) {\n    this.node.style.setProperty(\n      style,\n      val == null || isBlank.test(val) ? '' : val\n    )\n  }\n\n  return this\n}\n\n// Show element\nexport function show() {\n  return this.css('display', '')\n}\n\n// Hide element\nexport function hide() {\n  return this.css('display', 'none')\n}\n\n// Is element visible?\nexport function visible() {\n  return this.css('display') !== 'none'\n}\n\nregisterMethods('Dom', {\n  css,\n  show,\n  hide,\n  visible\n})\n"
  },
  {
    "path": "src/modules/optional/data.js",
    "content": "import { registerMethods } from '../../utils/methods.js'\nimport { filter, map } from '../../utils/utils.js'\n\n// Store data values on svg nodes\nexport function data(a, v, r) {\n  if (a == null) {\n    // get an object of attributes\n    return this.data(\n      map(\n        filter(\n          this.node.attributes,\n          (el) => el.nodeName.indexOf('data-') === 0\n        ),\n        (el) => el.nodeName.slice(5)\n      )\n    )\n  } else if (a instanceof Array) {\n    const data = {}\n    for (const key of a) {\n      data[key] = this.data(key)\n    }\n    return data\n  } else if (typeof a === 'object') {\n    for (v in a) {\n      this.data(v, a[v])\n    }\n  } else if (arguments.length < 2) {\n    try {\n      return JSON.parse(this.attr('data-' + a))\n    } catch (e) {\n      return this.attr('data-' + a)\n    }\n  } else {\n    this.attr(\n      'data-' + a,\n      v === null\n        ? null\n        : r === true || typeof v === 'string' || typeof v === 'number'\n          ? v\n          : JSON.stringify(v)\n    )\n  }\n\n  return this\n}\n\nregisterMethods('Dom', { data })\n"
  },
  {
    "path": "src/modules/optional/memory.js",
    "content": "import { registerMethods } from '../../utils/methods.js'\n\n// Remember arbitrary data\nexport function remember(k, v) {\n  // remember every item in an object individually\n  if (typeof arguments[0] === 'object') {\n    for (const key in k) {\n      this.remember(key, k[key])\n    }\n  } else if (arguments.length === 1) {\n    // retrieve memory\n    return this.memory()[k]\n  } else {\n    // store memory\n    this.memory()[k] = v\n  }\n\n  return this\n}\n\n// Erase a given memory\nexport function forget() {\n  if (arguments.length === 0) {\n    this._memory = {}\n  } else {\n    for (let i = arguments.length - 1; i >= 0; i--) {\n      delete this.memory()[arguments[i]]\n    }\n  }\n  return this\n}\n\n// This triggers creation of a new hidden class which is not performant\n// However, this function is not rarely used so it will not happen frequently\n// Return local memory object\nexport function memory() {\n  return (this._memory = this._memory || {})\n}\n\nregisterMethods('Dom', { remember, forget, memory })\n"
  },
  {
    "path": "src/modules/optional/sugar.js",
    "content": "import { registerMethods } from '../../utils/methods.js'\nimport Color from '../../types/Color.js'\nimport Element from '../../elements/Element.js'\nimport Matrix from '../../types/Matrix.js'\nimport Point from '../../types/Point.js'\nimport SVGNumber from '../../types/SVGNumber.js'\n\n// Define list of available attributes for stroke and fill\nconst sugar = {\n  stroke: [\n    'color',\n    'width',\n    'opacity',\n    'linecap',\n    'linejoin',\n    'miterlimit',\n    'dasharray',\n    'dashoffset'\n  ],\n  fill: ['color', 'opacity', 'rule'],\n  prefix: function (t, a) {\n    return a === 'color' ? t : t + '-' + a\n  }\n}\n\n// Add sugar for fill and stroke\n;['fill', 'stroke'].forEach(function (m) {\n  const extension = {}\n  let i\n\n  extension[m] = function (o) {\n    if (typeof o === 'undefined') {\n      return this.attr(m)\n    }\n    if (\n      typeof o === 'string' ||\n      o instanceof Color ||\n      Color.isRgb(o) ||\n      o instanceof Element\n    ) {\n      this.attr(m, o)\n    } else {\n      // set all attributes from sugar.fill and sugar.stroke list\n      for (i = sugar[m].length - 1; i >= 0; i--) {\n        if (o[sugar[m][i]] != null) {\n          this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]])\n        }\n      }\n    }\n\n    return this\n  }\n\n  registerMethods(['Element', 'Runner'], extension)\n})\n\nregisterMethods(['Element', 'Runner'], {\n  // Let the user set the matrix directly\n  matrix: function (mat, b, c, d, e, f) {\n    // Act as a getter\n    if (mat == null) {\n      return new Matrix(this)\n    }\n\n    // Act as a setter, the user can pass a matrix or a set of numbers\n    return this.attr('transform', new Matrix(mat, b, c, d, e, f))\n  },\n\n  // Map rotation to transform\n  rotate: function (angle, cx, cy) {\n    return this.transform({ rotate: angle, ox: cx, oy: cy }, true)\n  },\n\n  // Map skew to transform\n  skew: function (x, y, cx, cy) {\n    return arguments.length === 1 || arguments.length === 3\n      ? this.transform({ skew: x, ox: y, oy: cx }, true)\n      : this.transform({ skew: [x, y], ox: cx, oy: cy }, true)\n  },\n\n  shear: function (lam, cx, cy) {\n    return this.transform({ shear: lam, ox: cx, oy: cy }, true)\n  },\n\n  // Map scale to transform\n  scale: function (x, y, cx, cy) {\n    return arguments.length === 1 || arguments.length === 3\n      ? this.transform({ scale: x, ox: y, oy: cx }, true)\n      : this.transform({ scale: [x, y], ox: cx, oy: cy }, true)\n  },\n\n  // Map translate to transform\n  translate: function (x, y) {\n    return this.transform({ translate: [x, y] }, true)\n  },\n\n  // Map relative translations to transform\n  relative: function (x, y) {\n    return this.transform({ relative: [x, y] }, true)\n  },\n\n  // Map flip to transform\n  flip: function (direction = 'both', origin = 'center') {\n    if ('xybothtrue'.indexOf(direction) === -1) {\n      origin = direction\n      direction = 'both'\n    }\n\n    return this.transform({ flip: direction, origin: origin }, true)\n  },\n\n  // Opacity\n  opacity: function (value) {\n    return this.attr('opacity', value)\n  }\n})\n\nregisterMethods('radius', {\n  // Add x and y radius\n  radius: function (x, y = x) {\n    const type = (this._element || this).type\n    return type === 'radialGradient'\n      ? this.attr('r', new SVGNumber(x))\n      : this.rx(x).ry(y)\n  }\n})\n\nregisterMethods('Path', {\n  // Get path length\n  length: function () {\n    return this.node.getTotalLength()\n  },\n  // Get point at length\n  pointAt: function (length) {\n    return new Point(this.node.getPointAtLength(length))\n  }\n})\n\nregisterMethods(['Element', 'Runner'], {\n  // Set font\n  font: function (a, v) {\n    if (typeof a === 'object') {\n      for (v in a) this.font(v, a[v])\n      return this\n    }\n\n    return a === 'leading'\n      ? this.leading(v)\n      : a === 'anchor'\n        ? this.attr('text-anchor', v)\n        : a === 'size' ||\n            a === 'family' ||\n            a === 'weight' ||\n            a === 'stretch' ||\n            a === 'variant' ||\n            a === 'style'\n          ? this.attr('font-' + a, v)\n          : this.attr(a, v)\n  }\n})\n\n// Add events to elements\nconst methods = [\n  'click',\n  'dblclick',\n  'mousedown',\n  'mouseup',\n  'mouseover',\n  'mouseout',\n  'mousemove',\n  'mouseenter',\n  'mouseleave',\n  'touchstart',\n  'touchmove',\n  'touchleave',\n  'touchend',\n  'touchcancel',\n  'contextmenu',\n  'wheel',\n  'pointerdown',\n  'pointermove',\n  'pointerup',\n  'pointerleave',\n  'pointercancel'\n].reduce(function (last, event) {\n  // add event to Element\n  const fn = function (f) {\n    if (f === null) {\n      this.off(event)\n    } else {\n      this.on(event, f)\n    }\n    return this\n  }\n\n  last[event] = fn\n  return last\n}, {})\n\nregisterMethods('Element', methods)\n"
  },
  {
    "path": "src/modules/optional/transform.js",
    "content": "import { getOrigin, isDescriptive } from '../../utils/utils.js'\nimport { delimiter, transforms } from '../core/regex.js'\nimport { registerMethods } from '../../utils/methods.js'\nimport Matrix from '../../types/Matrix.js'\n\n// Reset all transformations\nexport function untransform() {\n  return this.attr('transform', null)\n}\n\n// merge the whole transformation chain into one matrix and returns it\nexport function matrixify() {\n  const matrix = (this.attr('transform') || '')\n    // split transformations\n    .split(transforms)\n    .slice(0, -1)\n    .map(function (str) {\n      // generate key => value pairs\n      const kv = str.trim().split('(')\n      return [\n        kv[0],\n        kv[1].split(delimiter).map(function (str) {\n          return parseFloat(str)\n        })\n      ]\n    })\n    .reverse()\n    // merge every transformation into one matrix\n    .reduce(function (matrix, transform) {\n      if (transform[0] === 'matrix') {\n        return matrix.lmultiply(Matrix.fromArray(transform[1]))\n      }\n      return matrix[transform[0]].apply(matrix, transform[1])\n    }, new Matrix())\n\n  return matrix\n}\n\n// add an element to another parent without changing the visual representation on the screen\nexport function toParent(parent, i) {\n  if (this === parent) return this\n\n  if (isDescriptive(this.node)) return this.addTo(parent, i)\n\n  const ctm = this.screenCTM()\n  const pCtm = parent.screenCTM().inverse()\n\n  this.addTo(parent, i).untransform().transform(pCtm.multiply(ctm))\n\n  return this\n}\n\n// same as above with parent equals root-svg\nexport function toRoot(i) {\n  return this.toParent(this.root(), i)\n}\n\n// Add transformations\nexport function transform(o, relative) {\n  // Act as a getter if no object was passed\n  if (o == null || typeof o === 'string') {\n    const decomposed = new Matrix(this).decompose()\n    return o == null ? decomposed : decomposed[o]\n  }\n\n  if (!Matrix.isMatrixLike(o)) {\n    // Set the origin according to the defined transform\n    o = { ...o, origin: getOrigin(o, this) }\n  }\n\n  // The user can pass a boolean, an Element or an Matrix or nothing\n  const cleanRelative = relative === true ? this : relative || false\n  const result = new Matrix(cleanRelative).transform(o)\n  return this.attr('transform', result)\n}\n\nregisterMethods('Element', {\n  untransform,\n  matrixify,\n  toParent,\n  toRoot,\n  transform\n})\n"
  },
  {
    "path": "src/polyfills/children.js",
    "content": "import { filter } from '../utils/utils.js'\n\n// IE11: children does not work for svg nodes\nexport default function children(node) {\n  return filter(node.childNodes, function (child) {\n    return child.nodeType === 1\n  })\n}\n"
  },
  {
    "path": "src/polyfills/innerHTML.js",
    "content": ";(function () {\n  try {\n    if (SVGElement.prototype.innerHTML) return\n  } catch (e) {\n    return\n  }\n\n  const serializeXML = function (node, output) {\n    const nodeType = node.nodeType\n    if (nodeType === 3) {\n      output.push(\n        node.textContent\n          .replace(/&/, '&amp;')\n          .replace(/</, '&lt;')\n          .replace('>', '&gt;')\n      )\n    } else if (nodeType === 1) {\n      output.push('<', node.tagName)\n      if (node.hasAttributes()) {\n        ;[].forEach.call(node.attributes, function (attrNode) {\n          output.push(' ', attrNode.name, '=\"', attrNode.value, '\"')\n        })\n      }\n      output.push('>')\n      if (node.hasChildNodes()) {\n        ;[].forEach.call(node.childNodes, function (childNode) {\n          serializeXML(childNode, output)\n        })\n      } else {\n        // output.push('/>')\n      }\n      output.push('</', node.tagName, '>')\n    } else if (nodeType === 8) {\n      output.push('<!--', node.nodeValue, '-->')\n    }\n  }\n\n  Object.defineProperty(SVGElement.prototype, 'innerHTML', {\n    get: function () {\n      const output = []\n      let childNode = this.firstChild\n      while (childNode) {\n        serializeXML(childNode, output)\n        childNode = childNode.nextSibling\n      }\n      return output.join('')\n    },\n    set: function (markupText) {\n      while (this.firstChild) {\n        this.removeChild(this.firstChild)\n      }\n\n      try {\n        const dXML = new DOMParser()\n        dXML.async = false\n\n        const sXML =\n          \"<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>\" +\n          markupText +\n          '</svg>'\n        const svgDocElement = dXML.parseFromString(\n          sXML,\n          'text/xml'\n        ).documentElement\n\n        let childNode = svgDocElement.firstChild\n        while (childNode) {\n          this.appendChild(this.ownerDocument.importNode(childNode, true))\n          childNode = childNode.nextSibling\n        }\n      } catch (e) {\n        throw new Error('Can not set innerHTML on node')\n      }\n    }\n  })\n\n  Object.defineProperty(SVGElement.prototype, 'outerHTML', {\n    get: function () {\n      const output = []\n      serializeXML(this, output)\n      return output.join('')\n    },\n    set: function (markupText) {\n      while (this.firstChild) {\n        this.removeChild(this.firstChild)\n      }\n\n      try {\n        const dXML = new DOMParser()\n        dXML.async = false\n\n        const sXML =\n          \"<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>\" +\n          markupText +\n          '</svg>'\n        const svgDocElement = dXML.parseFromString(\n          sXML,\n          'text/xml'\n        ).documentElement\n\n        let childNode = svgDocElement.firstChild\n        while (childNode) {\n          this.parentNode.insertBefore(\n            this.ownerDocument.importNode(childNode, true),\n            this\n          )\n          // this.appendChild(this.ownerDocument.importNode(childNode, true));\n          childNode = childNode.nextSibling\n        }\n      } catch (e) {\n        throw new Error('Can not set outerHTML on node')\n      }\n    }\n  })\n})()\n"
  },
  {
    "path": "src/svg.js",
    "content": "import * as svgMembers from './main.js'\nimport { makeInstance } from './utils/adopter.js'\n\n// The main wrapping element\nexport default function SVG(element, isHTML) {\n  return makeInstance(element, isHTML)\n}\n\nObject.assign(SVG, svgMembers)\n"
  },
  {
    "path": "src/types/Base.js",
    "content": "export default class Base {\n  // constructor (node/*, {extensions = []} */) {\n  //   // this.tags = []\n  //   //\n  //   // for (let extension of extensions) {\n  //   //   extension.setup.call(this, node)\n  //   //   this.tags.push(extension.name)\n  //   // }\n  // }\n}\n"
  },
  {
    "path": "src/types/Box.js",
    "content": "import { delimiter } from '../modules/core/regex.js'\nimport { globals } from '../utils/window.js'\nimport { register } from '../utils/adopter.js'\nimport { registerMethods } from '../utils/methods.js'\nimport Matrix from './Matrix.js'\nimport Point from './Point.js'\nimport parser from '../modules/core/parser.js'\n\nexport function isNulledBox(box) {\n  return !box.width && !box.height && !box.x && !box.y\n}\n\nexport function domContains(node) {\n  return (\n    node === globals.document ||\n    (\n      globals.document.documentElement.contains ||\n      function (node) {\n        // This is IE - it does not support contains() for top-level SVGs\n        while (node.parentNode) {\n          node = node.parentNode\n        }\n        return node === globals.document\n      }\n    ).call(globals.document.documentElement, node)\n  )\n}\n\nexport default class Box {\n  constructor(...args) {\n    this.init(...args)\n  }\n\n  addOffset() {\n    // offset by window scroll position, because getBoundingClientRect changes when window is scrolled\n    this.x += globals.window.pageXOffset\n    this.y += globals.window.pageYOffset\n    return new Box(this)\n  }\n\n  init(source) {\n    const base = [0, 0, 0, 0]\n    source =\n      typeof source === 'string'\n        ? source.split(delimiter).map(parseFloat)\n        : Array.isArray(source)\n          ? source\n          : typeof source === 'object'\n            ? [\n                source.left != null ? source.left : source.x,\n                source.top != null ? source.top : source.y,\n                source.width,\n                source.height\n              ]\n            : arguments.length === 4\n              ? [].slice.call(arguments)\n              : base\n\n    this.x = source[0] || 0\n    this.y = source[1] || 0\n    this.width = this.w = source[2] || 0\n    this.height = this.h = source[3] || 0\n\n    // Add more bounding box properties\n    this.x2 = this.x + this.w\n    this.y2 = this.y + this.h\n    this.cx = this.x + this.w / 2\n    this.cy = this.y + this.h / 2\n\n    return this\n  }\n\n  isNulled() {\n    return isNulledBox(this)\n  }\n\n  // Merge rect box with another, return a new instance\n  merge(box) {\n    const x = Math.min(this.x, box.x)\n    const y = Math.min(this.y, box.y)\n    const width = Math.max(this.x + this.width, box.x + box.width) - x\n    const height = Math.max(this.y + this.height, box.y + box.height) - y\n\n    return new Box(x, y, width, height)\n  }\n\n  toArray() {\n    return [this.x, this.y, this.width, this.height]\n  }\n\n  toString() {\n    return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height\n  }\n\n  transform(m) {\n    if (!(m instanceof Matrix)) {\n      m = new Matrix(m)\n    }\n\n    let xMin = Infinity\n    let xMax = -Infinity\n    let yMin = Infinity\n    let yMax = -Infinity\n\n    const pts = [\n      new Point(this.x, this.y),\n      new Point(this.x2, this.y),\n      new Point(this.x, this.y2),\n      new Point(this.x2, this.y2)\n    ]\n\n    pts.forEach(function (p) {\n      p = p.transform(m)\n      xMin = Math.min(xMin, p.x)\n      xMax = Math.max(xMax, p.x)\n      yMin = Math.min(yMin, p.y)\n      yMax = Math.max(yMax, p.y)\n    })\n\n    return new Box(xMin, yMin, xMax - xMin, yMax - yMin)\n  }\n}\n\nfunction getBox(el, getBBoxFn, retry) {\n  let box\n\n  try {\n    // Try to get the box with the provided function\n    box = getBBoxFn(el.node)\n\n    // If the box is worthless and not even in the dom, retry\n    // by throwing an error here...\n    if (isNulledBox(box) && !domContains(el.node)) {\n      throw new Error('Element not in the dom')\n    }\n  } catch (e) {\n    // ... and calling the retry handler here\n    box = retry(el)\n  }\n\n  return box\n}\n\nexport function bbox() {\n  // Function to get bbox is getBBox()\n  const getBBox = (node) => node.getBBox()\n\n  // Take all measures so that a stupid browser renders the element\n  // so we can get the bbox from it when we try again\n  const retry = (el) => {\n    try {\n      const clone = el.clone().addTo(parser().svg).show()\n      const box = clone.node.getBBox()\n      clone.remove()\n      return box\n    } catch (e) {\n      // We give up...\n      throw new Error(\n        `Getting bbox of element \"${\n          el.node.nodeName\n        }\" is not possible: ${e.toString()}`\n      )\n    }\n  }\n\n  const box = getBox(this, getBBox, retry)\n  const bbox = new Box(box)\n\n  return bbox\n}\n\nexport function rbox(el) {\n  const getRBox = (node) => node.getBoundingClientRect()\n  const retry = (el) => {\n    // There is no point in trying tricks here because if we insert the element into the dom ourselves\n    // it obviously will be at the wrong position\n    throw new Error(\n      `Getting rbox of element \"${el.node.nodeName}\" is not possible`\n    )\n  }\n\n  const box = getBox(this, getRBox, retry)\n  const rbox = new Box(box)\n\n  // If an element was passed, we want the bbox in the coordinate system of that element\n  if (el) {\n    return rbox.transform(el.screenCTM().inverseO())\n  }\n\n  // Else we want it in absolute screen coordinates\n  // Therefore we need to add the scrollOffset\n  return rbox.addOffset()\n}\n\n// Checks whether the given point is inside the bounding box\nexport function inside(x, y) {\n  const box = this.bbox()\n\n  return (\n    x > box.x && y > box.y && x < box.x + box.width && y < box.y + box.height\n  )\n}\n\nregisterMethods({\n  viewbox: {\n    viewbox(x, y, width, height) {\n      // act as getter\n      if (x == null) return new Box(this.attr('viewBox'))\n\n      // act as setter\n      return this.attr('viewBox', new Box(x, y, width, height))\n    },\n\n    zoom(level, point) {\n      // Its best to rely on the attributes here and here is why:\n      // clientXYZ: Doesn't work on non-root svgs because they dont have a CSSBox (silly!)\n      // getBoundingClientRect: Doesn't work because Chrome just ignores width and height of nested svgs completely\n      //                        that means, their clientRect is always as big as the content.\n      //                        Furthermore this size is incorrect if the element is further transformed by its parents\n      // computedStyle: Only returns meaningful values if css was used with px. We dont go this route here!\n      // getBBox: returns the bounding box of its content - that doesn't help!\n      let { width, height } = this.attr(['width', 'height'])\n\n      // Width and height is a string when a number with a unit is present which we can't use\n      // So we try clientXYZ\n      if (\n        (!width && !height) ||\n        typeof width === 'string' ||\n        typeof height === 'string'\n      ) {\n        width = this.node.clientWidth\n        height = this.node.clientHeight\n      }\n\n      // Giving up...\n      if (!width || !height) {\n        throw new Error(\n          'Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element'\n        )\n      }\n\n      const v = this.viewbox()\n\n      const zoomX = width / v.width\n      const zoomY = height / v.height\n      const zoom = Math.min(zoomX, zoomY)\n\n      if (level == null) {\n        return zoom\n      }\n\n      let zoomAmount = zoom / level\n\n      // Set the zoomAmount to the highest value which is safe to process and recover from\n      // The * 100 is a bit of wiggle room for the matrix transformation\n      if (zoomAmount === Infinity) zoomAmount = Number.MAX_SAFE_INTEGER / 100\n\n      point =\n        point || new Point(width / 2 / zoomX + v.x, height / 2 / zoomY + v.y)\n\n      const box = new Box(v).transform(\n        new Matrix({ scale: zoomAmount, origin: point })\n      )\n\n      return this.viewbox(box)\n    }\n  }\n})\n\nregister(Box, 'Box')\n"
  },
  {
    "path": "src/types/Color.js",
    "content": "import { hex, isHex, isRgb, rgb, whitespace } from '../modules/core/regex.js'\n\nfunction sixDigitHex(hex) {\n  return hex.length === 4\n    ? [\n        '#',\n        hex.substring(1, 2),\n        hex.substring(1, 2),\n        hex.substring(2, 3),\n        hex.substring(2, 3),\n        hex.substring(3, 4),\n        hex.substring(3, 4)\n      ].join('')\n    : hex\n}\n\nfunction componentHex(component) {\n  const integer = Math.round(component)\n  const bounded = Math.max(0, Math.min(255, integer))\n  const hex = bounded.toString(16)\n  return hex.length === 1 ? '0' + hex : hex\n}\n\nfunction is(object, space) {\n  for (let i = space.length; i--; ) {\n    if (object[space[i]] == null) {\n      return false\n    }\n  }\n  return true\n}\n\nfunction getParameters(a, b) {\n  const params = is(a, 'rgb')\n    ? { _a: a.r, _b: a.g, _c: a.b, _d: 0, space: 'rgb' }\n    : is(a, 'xyz')\n      ? { _a: a.x, _b: a.y, _c: a.z, _d: 0, space: 'xyz' }\n      : is(a, 'hsl')\n        ? { _a: a.h, _b: a.s, _c: a.l, _d: 0, space: 'hsl' }\n        : is(a, 'lab')\n          ? { _a: a.l, _b: a.a, _c: a.b, _d: 0, space: 'lab' }\n          : is(a, 'lch')\n            ? { _a: a.l, _b: a.c, _c: a.h, _d: 0, space: 'lch' }\n            : is(a, 'cmyk')\n              ? { _a: a.c, _b: a.m, _c: a.y, _d: a.k, space: 'cmyk' }\n              : { _a: 0, _b: 0, _c: 0, space: 'rgb' }\n\n  params.space = b || params.space\n  return params\n}\n\nfunction cieSpace(space) {\n  if (space === 'lab' || space === 'xyz' || space === 'lch') {\n    return true\n  } else {\n    return false\n  }\n}\n\nfunction hueToRgb(p, q, t) {\n  if (t < 0) t += 1\n  if (t > 1) t -= 1\n  if (t < 1 / 6) return p + (q - p) * 6 * t\n  if (t < 1 / 2) return q\n  if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6\n  return p\n}\n\nexport default class Color {\n  constructor(...inputs) {\n    this.init(...inputs)\n  }\n\n  // Test if given value is a color\n  static isColor(color) {\n    return (\n      color && (color instanceof Color || this.isRgb(color) || this.test(color))\n    )\n  }\n\n  // Test if given value is an rgb object\n  static isRgb(color) {\n    return (\n      color &&\n      typeof color.r === 'number' &&\n      typeof color.g === 'number' &&\n      typeof color.b === 'number'\n    )\n  }\n\n  /*\n  Generating random colors\n  */\n  static random(mode = 'vibrant', t) {\n    // Get the math modules\n    const { random, round, sin, PI: pi } = Math\n\n    // Run the correct generator\n    if (mode === 'vibrant') {\n      const l = (81 - 57) * random() + 57\n      const c = (83 - 45) * random() + 45\n      const h = 360 * random()\n      const color = new Color(l, c, h, 'lch')\n      return color\n    } else if (mode === 'sine') {\n      t = t == null ? random() : t\n      const r = round(80 * sin((2 * pi * t) / 0.5 + 0.01) + 150)\n      const g = round(50 * sin((2 * pi * t) / 0.5 + 4.6) + 200)\n      const b = round(100 * sin((2 * pi * t) / 0.5 + 2.3) + 150)\n      const color = new Color(r, g, b)\n      return color\n    } else if (mode === 'pastel') {\n      const l = (94 - 86) * random() + 86\n      const c = (26 - 9) * random() + 9\n      const h = 360 * random()\n      const color = new Color(l, c, h, 'lch')\n      return color\n    } else if (mode === 'dark') {\n      const l = 10 + 10 * random()\n      const c = (125 - 75) * random() + 86\n      const h = 360 * random()\n      const color = new Color(l, c, h, 'lch')\n      return color\n    } else if (mode === 'rgb') {\n      const r = 255 * random()\n      const g = 255 * random()\n      const b = 255 * random()\n      const color = new Color(r, g, b)\n      return color\n    } else if (mode === 'lab') {\n      const l = 100 * random()\n      const a = 256 * random() - 128\n      const b = 256 * random() - 128\n      const color = new Color(l, a, b, 'lab')\n      return color\n    } else if (mode === 'grey') {\n      const grey = 255 * random()\n      const color = new Color(grey, grey, grey)\n      return color\n    } else {\n      throw new Error('Unsupported random color mode')\n    }\n  }\n\n  // Test if given value is a color string\n  static test(color) {\n    return typeof color === 'string' && (isHex.test(color) || isRgb.test(color))\n  }\n\n  cmyk() {\n    // Get the rgb values for the current color\n    const { _a, _b, _c } = this.rgb()\n    const [r, g, b] = [_a, _b, _c].map((v) => v / 255)\n\n    // Get the cmyk values in an unbounded format\n    const k = Math.min(1 - r, 1 - g, 1 - b)\n\n    if (k === 1) {\n      // Catch the black case\n      return new Color(0, 0, 0, 1, 'cmyk')\n    }\n\n    const c = (1 - r - k) / (1 - k)\n    const m = (1 - g - k) / (1 - k)\n    const y = (1 - b - k) / (1 - k)\n\n    // Construct the new color\n    const color = new Color(c, m, y, k, 'cmyk')\n    return color\n  }\n\n  hsl() {\n    // Get the rgb values\n    const { _a, _b, _c } = this.rgb()\n    const [r, g, b] = [_a, _b, _c].map((v) => v / 255)\n\n    // Find the maximum and minimum values to get the lightness\n    const max = Math.max(r, g, b)\n    const min = Math.min(r, g, b)\n    const l = (max + min) / 2\n\n    // If the r, g, v values are identical then we are grey\n    const isGrey = max === min\n\n    // Calculate the hue and saturation\n    const delta = max - min\n    const s = isGrey\n      ? 0\n      : l > 0.5\n        ? delta / (2 - max - min)\n        : delta / (max + min)\n    const h = isGrey\n      ? 0\n      : max === r\n        ? ((g - b) / delta + (g < b ? 6 : 0)) / 6\n        : max === g\n          ? ((b - r) / delta + 2) / 6\n          : max === b\n            ? ((r - g) / delta + 4) / 6\n            : 0\n\n    // Construct and return the new color\n    const color = new Color(360 * h, 100 * s, 100 * l, 'hsl')\n    return color\n  }\n\n  init(a = 0, b = 0, c = 0, d = 0, space = 'rgb') {\n    // This catches the case when a falsy value is passed like ''\n    a = !a ? 0 : a\n\n    // Reset all values in case the init function is rerun with new color space\n    if (this.space) {\n      for (const component in this.space) {\n        delete this[this.space[component]]\n      }\n    }\n\n    if (typeof a === 'number') {\n      // Allow for the case that we don't need d...\n      space = typeof d === 'string' ? d : space\n      d = typeof d === 'string' ? 0 : d\n\n      // Assign the values straight to the color\n      Object.assign(this, { _a: a, _b: b, _c: c, _d: d, space })\n      // If the user gave us an array, make the color from it\n    } else if (a instanceof Array) {\n      this.space = b || (typeof a[3] === 'string' ? a[3] : a[4]) || 'rgb'\n      Object.assign(this, { _a: a[0], _b: a[1], _c: a[2], _d: a[3] || 0 })\n    } else if (a instanceof Object) {\n      // Set the object up and assign its values directly\n      const values = getParameters(a, b)\n      Object.assign(this, values)\n    } else if (typeof a === 'string') {\n      if (isRgb.test(a)) {\n        const noWhitespace = a.replace(whitespace, '')\n        const [_a, _b, _c] = rgb\n          .exec(noWhitespace)\n          .slice(1, 4)\n          .map((v) => parseInt(v))\n        Object.assign(this, { _a, _b, _c, _d: 0, space: 'rgb' })\n      } else if (isHex.test(a)) {\n        const hexParse = (v) => parseInt(v, 16)\n        const [, _a, _b, _c] = hex.exec(sixDigitHex(a)).map(hexParse)\n        Object.assign(this, { _a, _b, _c, _d: 0, space: 'rgb' })\n      } else throw Error(\"Unsupported string format, can't construct Color\")\n    }\n\n    // Now add the components as a convenience\n    const { _a, _b, _c, _d } = this\n    const components =\n      this.space === 'rgb'\n        ? { r: _a, g: _b, b: _c }\n        : this.space === 'xyz'\n          ? { x: _a, y: _b, z: _c }\n          : this.space === 'hsl'\n            ? { h: _a, s: _b, l: _c }\n            : this.space === 'lab'\n              ? { l: _a, a: _b, b: _c }\n              : this.space === 'lch'\n                ? { l: _a, c: _b, h: _c }\n                : this.space === 'cmyk'\n                  ? { c: _a, m: _b, y: _c, k: _d }\n                  : {}\n    Object.assign(this, components)\n  }\n\n  lab() {\n    // Get the xyz color\n    const { x, y, z } = this.xyz()\n\n    // Get the lab components\n    const l = 116 * y - 16\n    const a = 500 * (x - y)\n    const b = 200 * (y - z)\n\n    // Construct and return a new color\n    const color = new Color(l, a, b, 'lab')\n    return color\n  }\n\n  lch() {\n    // Get the lab color directly\n    const { l, a, b } = this.lab()\n\n    // Get the chromaticity and the hue using polar coordinates\n    const c = Math.sqrt(a ** 2 + b ** 2)\n    let h = (180 * Math.atan2(b, a)) / Math.PI\n    if (h < 0) {\n      h *= -1\n      h = 360 - h\n    }\n\n    // Make a new color and return it\n    const color = new Color(l, c, h, 'lch')\n    return color\n  }\n  /*\n  Conversion Methods\n  */\n\n  rgb() {\n    if (this.space === 'rgb') {\n      return this\n    } else if (cieSpace(this.space)) {\n      // Convert to the xyz color space\n      let { x, y, z } = this\n      if (this.space === 'lab' || this.space === 'lch') {\n        // Get the values in the lab space\n        let { l, a, b } = this\n        if (this.space === 'lch') {\n          const { c, h } = this\n          const dToR = Math.PI / 180\n          a = c * Math.cos(dToR * h)\n          b = c * Math.sin(dToR * h)\n        }\n\n        // Undo the nonlinear function\n        const yL = (l + 16) / 116\n        const xL = a / 500 + yL\n        const zL = yL - b / 200\n\n        // Get the xyz values\n        const ct = 16 / 116\n        const mx = 0.008856\n        const nm = 7.787\n        x = 0.95047 * (xL ** 3 > mx ? xL ** 3 : (xL - ct) / nm)\n        y = 1.0 * (yL ** 3 > mx ? yL ** 3 : (yL - ct) / nm)\n        z = 1.08883 * (zL ** 3 > mx ? zL ** 3 : (zL - ct) / nm)\n      }\n\n      // Convert xyz to unbounded rgb values\n      const rU = x * 3.2406 + y * -1.5372 + z * -0.4986\n      const gU = x * -0.9689 + y * 1.8758 + z * 0.0415\n      const bU = x * 0.0557 + y * -0.204 + z * 1.057\n\n      // Convert the values to true rgb values\n      const pow = Math.pow\n      const bd = 0.0031308\n      const r = rU > bd ? 1.055 * pow(rU, 1 / 2.4) - 0.055 : 12.92 * rU\n      const g = gU > bd ? 1.055 * pow(gU, 1 / 2.4) - 0.055 : 12.92 * gU\n      const b = bU > bd ? 1.055 * pow(bU, 1 / 2.4) - 0.055 : 12.92 * bU\n\n      // Make and return the color\n      const color = new Color(255 * r, 255 * g, 255 * b)\n      return color\n    } else if (this.space === 'hsl') {\n      // https://bgrins.github.io/TinyColor/docs/tinycolor.html\n      // Get the current hsl values\n      let { h, s, l } = this\n      h /= 360\n      s /= 100\n      l /= 100\n\n      // If we are grey, then just make the color directly\n      if (s === 0) {\n        l *= 255\n        const color = new Color(l, l, l)\n        return color\n      }\n\n      // TODO I have no idea what this does :D If you figure it out, tell me!\n      const q = l < 0.5 ? l * (1 + s) : l + s - l * s\n      const p = 2 * l - q\n\n      // Get the rgb values\n      const r = 255 * hueToRgb(p, q, h + 1 / 3)\n      const g = 255 * hueToRgb(p, q, h)\n      const b = 255 * hueToRgb(p, q, h - 1 / 3)\n\n      // Make a new color\n      const color = new Color(r, g, b)\n      return color\n    } else if (this.space === 'cmyk') {\n      // https://gist.github.com/felipesabino/5066336\n      // Get the normalised cmyk values\n      const { c, m, y, k } = this\n\n      // Get the rgb values\n      const r = 255 * (1 - Math.min(1, c * (1 - k) + k))\n      const g = 255 * (1 - Math.min(1, m * (1 - k) + k))\n      const b = 255 * (1 - Math.min(1, y * (1 - k) + k))\n\n      // Form the color and return it\n      const color = new Color(r, g, b)\n      return color\n    } else {\n      return this\n    }\n  }\n\n  toArray() {\n    const { _a, _b, _c, _d, space } = this\n    return [_a, _b, _c, _d, space]\n  }\n\n  toHex() {\n    const [r, g, b] = this._clamped().map(componentHex)\n    return `#${r}${g}${b}`\n  }\n\n  toRgb() {\n    const [rV, gV, bV] = this._clamped()\n    const string = `rgb(${rV},${gV},${bV})`\n    return string\n  }\n\n  toString() {\n    return this.toHex()\n  }\n\n  xyz() {\n    // Normalise the red, green and blue values\n    const { _a: r255, _b: g255, _c: b255 } = this.rgb()\n    const [r, g, b] = [r255, g255, b255].map((v) => v / 255)\n\n    // Convert to the lab rgb space\n    const rL = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92\n    const gL = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92\n    const bL = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92\n\n    // Convert to the xyz color space without bounding the values\n    const xU = (rL * 0.4124 + gL * 0.3576 + bL * 0.1805) / 0.95047\n    const yU = (rL * 0.2126 + gL * 0.7152 + bL * 0.0722) / 1.0\n    const zU = (rL * 0.0193 + gL * 0.1192 + bL * 0.9505) / 1.08883\n\n    // Get the proper xyz values by applying the bounding\n    const x = xU > 0.008856 ? Math.pow(xU, 1 / 3) : 7.787 * xU + 16 / 116\n    const y = yU > 0.008856 ? Math.pow(yU, 1 / 3) : 7.787 * yU + 16 / 116\n    const z = zU > 0.008856 ? Math.pow(zU, 1 / 3) : 7.787 * zU + 16 / 116\n\n    // Make and return the color\n    const color = new Color(x, y, z, 'xyz')\n    return color\n  }\n\n  /*\n  Input and Output methods\n  */\n\n  _clamped() {\n    const { _a, _b, _c } = this.rgb()\n    const { max, min, round } = Math\n    const format = (v) => max(0, min(round(v), 255))\n    return [_a, _b, _c].map(format)\n  }\n\n  /*\n  Constructing colors\n  */\n}\n"
  },
  {
    "path": "src/types/EventTarget.js",
    "content": "import { dispatch, off, on } from '../modules/core/event.js'\nimport { register } from '../utils/adopter.js'\nimport Base from './Base.js'\n\nexport default class EventTarget extends Base {\n  addEventListener() {}\n\n  dispatch(event, data, options) {\n    return dispatch(this, event, data, options)\n  }\n\n  dispatchEvent(event) {\n    const bag = this.getEventHolder().events\n    if (!bag) return true\n\n    const events = bag[event.type]\n\n    for (const i in events) {\n      for (const j in events[i]) {\n        events[i][j](event)\n      }\n    }\n\n    return !event.defaultPrevented\n  }\n\n  // Fire given event\n  fire(event, data, options) {\n    this.dispatch(event, data, options)\n    return this\n  }\n\n  getEventHolder() {\n    return this\n  }\n\n  getEventTarget() {\n    return this\n  }\n\n  // Unbind event from listener\n  off(event, listener, options) {\n    off(this, event, listener, options)\n    return this\n  }\n\n  // Bind given event to listener\n  on(event, listener, binding, options) {\n    on(this, event, listener, binding, options)\n    return this\n  }\n\n  removeEventListener() {}\n}\n\nregister(EventTarget, 'EventTarget')\n"
  },
  {
    "path": "src/types/List.js",
    "content": "import { extend } from '../utils/adopter.js'\n// import { subClassArray } from './ArrayPolyfill.js'\n\nclass List extends Array {\n  constructor(arr = [], ...args) {\n    super(arr, ...args)\n    if (typeof arr === 'number') return this\n    this.length = 0\n    this.push(...arr)\n  }\n}\n\n/* = subClassArray('List', Array, function (arr = []) {\n  // This catches the case, that native map tries to create an array with new Array(1)\n  if (typeof arr === 'number') return this\n  this.length = 0\n  this.push(...arr)\n}) */\n\nexport default List\n\nextend([List], {\n  each(fnOrMethodName, ...args) {\n    if (typeof fnOrMethodName === 'function') {\n      return this.map((el, i, arr) => {\n        return fnOrMethodName.call(el, el, i, arr)\n      })\n    } else {\n      return this.map((el) => {\n        return el[fnOrMethodName](...args)\n      })\n    }\n  },\n\n  toArray() {\n    return Array.prototype.concat.apply([], this)\n  }\n})\n\nconst reserved = ['toArray', 'constructor', 'each']\n\nList.extend = function (methods) {\n  methods = methods.reduce((obj, name) => {\n    // Don't overwrite own methods\n    if (reserved.includes(name)) return obj\n\n    // Don't add private methods\n    if (name[0] === '_') return obj\n\n    // Allow access to original Array methods through a prefix\n    if (name in Array.prototype) {\n      obj['$' + name] = Array.prototype[name]\n    }\n\n    // Relay every call to each()\n    obj[name] = function (...attrs) {\n      return this.each(name, ...attrs)\n    }\n    return obj\n  }, {})\n\n  extend([List], methods)\n}\n"
  },
  {
    "path": "src/types/Matrix.js",
    "content": "import { delimiter } from '../modules/core/regex.js'\nimport { radians } from '../utils/utils.js'\nimport { register } from '../utils/adopter.js'\nimport Element from '../elements/Element.js'\nimport Point from './Point.js'\n\nfunction closeEnough(a, b, threshold) {\n  return Math.abs(b - a) < (threshold || 1e-6)\n}\n\nexport default class Matrix {\n  constructor(...args) {\n    this.init(...args)\n  }\n\n  static formatTransforms(o) {\n    // Get all of the parameters required to form the matrix\n    const flipBoth = o.flip === 'both' || o.flip === true\n    const flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1\n    const flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1\n    const skewX =\n      o.skew && o.skew.length\n        ? o.skew[0]\n        : isFinite(o.skew)\n          ? o.skew\n          : isFinite(o.skewX)\n            ? o.skewX\n            : 0\n    const skewY =\n      o.skew && o.skew.length\n        ? o.skew[1]\n        : isFinite(o.skew)\n          ? o.skew\n          : isFinite(o.skewY)\n            ? o.skewY\n            : 0\n    const scaleX =\n      o.scale && o.scale.length\n        ? o.scale[0] * flipX\n        : isFinite(o.scale)\n          ? o.scale * flipX\n          : isFinite(o.scaleX)\n            ? o.scaleX * flipX\n            : flipX\n    const scaleY =\n      o.scale && o.scale.length\n        ? o.scale[1] * flipY\n        : isFinite(o.scale)\n          ? o.scale * flipY\n          : isFinite(o.scaleY)\n            ? o.scaleY * flipY\n            : flipY\n    const shear = o.shear || 0\n    const theta = o.rotate || o.theta || 0\n    const origin = new Point(\n      o.origin || o.around || o.ox || o.originX,\n      o.oy || o.originY\n    )\n    const ox = origin.x\n    const oy = origin.y\n    // We need Point to be invalid if nothing was passed because we cannot default to 0 here. That is why NaN\n    const position = new Point(\n      o.position || o.px || o.positionX || NaN,\n      o.py || o.positionY || NaN\n    )\n    const px = position.x\n    const py = position.y\n    const translate = new Point(\n      o.translate || o.tx || o.translateX,\n      o.ty || o.translateY\n    )\n    const tx = translate.x\n    const ty = translate.y\n    const relative = new Point(\n      o.relative || o.rx || o.relativeX,\n      o.ry || o.relativeY\n    )\n    const rx = relative.x\n    const ry = relative.y\n\n    // Populate all of the values\n    return {\n      scaleX,\n      scaleY,\n      skewX,\n      skewY,\n      shear,\n      theta,\n      rx,\n      ry,\n      tx,\n      ty,\n      ox,\n      oy,\n      px,\n      py\n    }\n  }\n\n  static fromArray(a) {\n    return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] }\n  }\n\n  static isMatrixLike(o) {\n    return (\n      o.a != null ||\n      o.b != null ||\n      o.c != null ||\n      o.d != null ||\n      o.e != null ||\n      o.f != null\n    )\n  }\n\n  // left matrix, right matrix, target matrix which is overwritten\n  static matrixMultiply(l, r, o) {\n    // Work out the product directly\n    const a = l.a * r.a + l.c * r.b\n    const b = l.b * r.a + l.d * r.b\n    const c = l.a * r.c + l.c * r.d\n    const d = l.b * r.c + l.d * r.d\n    const e = l.e + l.a * r.e + l.c * r.f\n    const f = l.f + l.b * r.e + l.d * r.f\n\n    // make sure to use local variables because l/r and o could be the same\n    o.a = a\n    o.b = b\n    o.c = c\n    o.d = d\n    o.e = e\n    o.f = f\n\n    return o\n  }\n\n  around(cx, cy, matrix) {\n    return this.clone().aroundO(cx, cy, matrix)\n  }\n\n  // Transform around a center point\n  aroundO(cx, cy, matrix) {\n    const dx = cx || 0\n    const dy = cy || 0\n    return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy)\n  }\n\n  // Clones this matrix\n  clone() {\n    return new Matrix(this)\n  }\n\n  // Decomposes this matrix into its affine parameters\n  decompose(cx = 0, cy = 0) {\n    // Get the parameters from the matrix\n    const a = this.a\n    const b = this.b\n    const c = this.c\n    const d = this.d\n    const e = this.e\n    const f = this.f\n\n    // Figure out if the winding direction is clockwise or counterclockwise\n    const determinant = a * d - b * c\n    const ccw = determinant > 0 ? 1 : -1\n\n    // Since we only shear in x, we can use the x basis to get the x scale\n    // and the rotation of the resulting matrix\n    const sx = ccw * Math.sqrt(a * a + b * b)\n    const thetaRad = Math.atan2(ccw * b, ccw * a)\n    const theta = (180 / Math.PI) * thetaRad\n    const ct = Math.cos(thetaRad)\n    const st = Math.sin(thetaRad)\n\n    // We can then solve the y basis vector simultaneously to get the other\n    // two affine parameters directly from these parameters\n    const lam = (a * c + b * d) / determinant\n    const sy = (c * sx) / (lam * a - b) || (d * sx) / (lam * b + a)\n\n    // Use the translations\n    const tx = e - cx + cx * ct * sx + cy * (lam * ct * sx - st * sy)\n    const ty = f - cy + cx * st * sx + cy * (lam * st * sx + ct * sy)\n\n    // Construct the decomposition and return it\n    return {\n      // Return the affine parameters\n      scaleX: sx,\n      scaleY: sy,\n      shear: lam,\n      rotate: theta,\n      translateX: tx,\n      translateY: ty,\n      originX: cx,\n      originY: cy,\n\n      // Return the matrix parameters\n      a: this.a,\n      b: this.b,\n      c: this.c,\n      d: this.d,\n      e: this.e,\n      f: this.f\n    }\n  }\n\n  // Check if two matrices are equal\n  equals(other) {\n    if (other === this) return true\n    const comp = new Matrix(other)\n    return (\n      closeEnough(this.a, comp.a) &&\n      closeEnough(this.b, comp.b) &&\n      closeEnough(this.c, comp.c) &&\n      closeEnough(this.d, comp.d) &&\n      closeEnough(this.e, comp.e) &&\n      closeEnough(this.f, comp.f)\n    )\n  }\n\n  // Flip matrix on x or y, at a given offset\n  flip(axis, around) {\n    return this.clone().flipO(axis, around)\n  }\n\n  flipO(axis, around) {\n    return axis === 'x'\n      ? this.scaleO(-1, 1, around, 0)\n      : axis === 'y'\n        ? this.scaleO(1, -1, 0, around)\n        : this.scaleO(-1, -1, axis, around || axis) // Define an x, y flip point\n  }\n\n  // Initialize\n  init(source) {\n    const base = Matrix.fromArray([1, 0, 0, 1, 0, 0])\n\n    // ensure source as object\n    source =\n      source instanceof Element\n        ? source.matrixify()\n        : typeof source === 'string'\n          ? Matrix.fromArray(source.split(delimiter).map(parseFloat))\n          : Array.isArray(source)\n            ? Matrix.fromArray(source)\n            : typeof source === 'object' && Matrix.isMatrixLike(source)\n              ? source\n              : typeof source === 'object'\n                ? new Matrix().transform(source)\n                : arguments.length === 6\n                  ? Matrix.fromArray([].slice.call(arguments))\n                  : base\n\n    // Merge the source matrix with the base matrix\n    this.a = source.a != null ? source.a : base.a\n    this.b = source.b != null ? source.b : base.b\n    this.c = source.c != null ? source.c : base.c\n    this.d = source.d != null ? source.d : base.d\n    this.e = source.e != null ? source.e : base.e\n    this.f = source.f != null ? source.f : base.f\n\n    return this\n  }\n\n  inverse() {\n    return this.clone().inverseO()\n  }\n\n  // Inverses matrix\n  inverseO() {\n    // Get the current parameters out of the matrix\n    const a = this.a\n    const b = this.b\n    const c = this.c\n    const d = this.d\n    const e = this.e\n    const f = this.f\n\n    // Invert the 2x2 matrix in the top left\n    const det = a * d - b * c\n    if (!det) throw new Error('Cannot invert ' + this)\n\n    // Calculate the top 2x2 matrix\n    const na = d / det\n    const nb = -b / det\n    const nc = -c / det\n    const nd = a / det\n\n    // Apply the inverted matrix to the top right\n    const ne = -(na * e + nc * f)\n    const nf = -(nb * e + nd * f)\n\n    // Construct the inverted matrix\n    this.a = na\n    this.b = nb\n    this.c = nc\n    this.d = nd\n    this.e = ne\n    this.f = nf\n\n    return this\n  }\n\n  lmultiply(matrix) {\n    return this.clone().lmultiplyO(matrix)\n  }\n\n  lmultiplyO(matrix) {\n    const r = this\n    const l = matrix instanceof Matrix ? matrix : new Matrix(matrix)\n\n    return Matrix.matrixMultiply(l, r, this)\n  }\n\n  // Left multiplies by the given matrix\n  multiply(matrix) {\n    return this.clone().multiplyO(matrix)\n  }\n\n  multiplyO(matrix) {\n    // Get the matrices\n    const l = this\n    const r = matrix instanceof Matrix ? matrix : new Matrix(matrix)\n\n    return Matrix.matrixMultiply(l, r, this)\n  }\n\n  // Rotate matrix\n  rotate(r, cx, cy) {\n    return this.clone().rotateO(r, cx, cy)\n  }\n\n  rotateO(r, cx = 0, cy = 0) {\n    // Convert degrees to radians\n    r = radians(r)\n\n    const cos = Math.cos(r)\n    const sin = Math.sin(r)\n\n    const { a, b, c, d, e, f } = this\n\n    this.a = a * cos - b * sin\n    this.b = b * cos + a * sin\n    this.c = c * cos - d * sin\n    this.d = d * cos + c * sin\n    this.e = e * cos - f * sin + cy * sin - cx * cos + cx\n    this.f = f * cos + e * sin - cx * sin - cy * cos + cy\n\n    return this\n  }\n\n  // Scale matrix\n  scale() {\n    return this.clone().scaleO(...arguments)\n  }\n\n  scaleO(x, y = x, cx = 0, cy = 0) {\n    // Support uniform scaling\n    if (arguments.length === 3) {\n      cy = cx\n      cx = y\n      y = x\n    }\n\n    const { a, b, c, d, e, f } = this\n\n    this.a = a * x\n    this.b = b * y\n    this.c = c * x\n    this.d = d * y\n    this.e = e * x - cx * x + cx\n    this.f = f * y - cy * y + cy\n\n    return this\n  }\n\n  // Shear matrix\n  shear(a, cx, cy) {\n    return this.clone().shearO(a, cx, cy)\n  }\n\n  // eslint-disable-next-line no-unused-vars\n  shearO(lx, cx = 0, cy = 0) {\n    const { a, b, c, d, e, f } = this\n\n    this.a = a + b * lx\n    this.c = c + d * lx\n    this.e = e + f * lx - cy * lx\n\n    return this\n  }\n\n  // Skew Matrix\n  skew() {\n    return this.clone().skewO(...arguments)\n  }\n\n  skewO(x, y = x, cx = 0, cy = 0) {\n    // support uniformal skew\n    if (arguments.length === 3) {\n      cy = cx\n      cx = y\n      y = x\n    }\n\n    // Convert degrees to radians\n    x = radians(x)\n    y = radians(y)\n\n    const lx = Math.tan(x)\n    const ly = Math.tan(y)\n\n    const { a, b, c, d, e, f } = this\n\n    this.a = a + b * lx\n    this.b = b + a * ly\n    this.c = c + d * lx\n    this.d = d + c * ly\n    this.e = e + f * lx - cy * lx\n    this.f = f + e * ly - cx * ly\n\n    return this\n  }\n\n  // SkewX\n  skewX(x, cx, cy) {\n    return this.skew(x, 0, cx, cy)\n  }\n\n  // SkewY\n  skewY(y, cx, cy) {\n    return this.skew(0, y, cx, cy)\n  }\n\n  toArray() {\n    return [this.a, this.b, this.c, this.d, this.e, this.f]\n  }\n\n  // Convert matrix to string\n  toString() {\n    return (\n      'matrix(' +\n      this.a +\n      ',' +\n      this.b +\n      ',' +\n      this.c +\n      ',' +\n      this.d +\n      ',' +\n      this.e +\n      ',' +\n      this.f +\n      ')'\n    )\n  }\n\n  // Transform a matrix into another matrix by manipulating the space\n  transform(o) {\n    // Check if o is a matrix and then left multiply it directly\n    if (Matrix.isMatrixLike(o)) {\n      const matrix = new Matrix(o)\n      return matrix.multiplyO(this)\n    }\n\n    // Get the proposed transformations and the current transformations\n    const t = Matrix.formatTransforms(o)\n    const current = this\n    const { x: ox, y: oy } = new Point(t.ox, t.oy).transform(current)\n\n    // Construct the resulting matrix\n    const transformer = new Matrix()\n      .translateO(t.rx, t.ry)\n      .lmultiplyO(current)\n      .translateO(-ox, -oy)\n      .scaleO(t.scaleX, t.scaleY)\n      .skewO(t.skewX, t.skewY)\n      .shearO(t.shear)\n      .rotateO(t.theta)\n      .translateO(ox, oy)\n\n    // If we want the origin at a particular place, we force it there\n    if (isFinite(t.px) || isFinite(t.py)) {\n      const origin = new Point(ox, oy).transform(transformer)\n      // TODO: Replace t.px with isFinite(t.px)\n      // Doesn't work because t.px is also 0 if it wasn't passed\n      const dx = isFinite(t.px) ? t.px - origin.x : 0\n      const dy = isFinite(t.py) ? t.py - origin.y : 0\n      transformer.translateO(dx, dy)\n    }\n\n    // Translate now after positioning\n    transformer.translateO(t.tx, t.ty)\n    return transformer\n  }\n\n  // Translate matrix\n  translate(x, y) {\n    return this.clone().translateO(x, y)\n  }\n\n  translateO(x, y) {\n    this.e += x || 0\n    this.f += y || 0\n    return this\n  }\n\n  valueOf() {\n    return {\n      a: this.a,\n      b: this.b,\n      c: this.c,\n      d: this.d,\n      e: this.e,\n      f: this.f\n    }\n  }\n}\n\nexport function ctm() {\n  return new Matrix(this.node.getCTM())\n}\n\nexport function screenCTM() {\n  try {\n    /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537\n       This is needed because FF does not return the transformation matrix\n       for the inner coordinate system when getScreenCTM() is called on nested svgs.\n       However all other Browsers do that */\n    if (typeof this.isRoot === 'function' && !this.isRoot()) {\n      const rect = this.rect(1, 1)\n      const m = rect.node.getScreenCTM()\n      rect.remove()\n      return new Matrix(m)\n    }\n    return new Matrix(this.node.getScreenCTM())\n  } catch (e) {\n    console.warn(\n      `Cannot get CTM from SVG node ${this.node.nodeName}. Is the element rendered?`\n    )\n    return new Matrix()\n  }\n}\n\nregister(Matrix, 'Matrix')\n"
  },
  {
    "path": "src/types/PathArray.js",
    "content": "import SVGArray from './SVGArray.js'\nimport parser from '../modules/core/parser.js'\nimport Box from './Box.js'\nimport { pathParser } from '../utils/pathParser.js'\n\nfunction arrayToString(a) {\n  let s = ''\n  for (let i = 0, il = a.length; i < il; i++) {\n    s += a[i][0]\n\n    if (a[i][1] != null) {\n      s += a[i][1]\n\n      if (a[i][2] != null) {\n        s += ' '\n        s += a[i][2]\n\n        if (a[i][3] != null) {\n          s += ' '\n          s += a[i][3]\n          s += ' '\n          s += a[i][4]\n\n          if (a[i][5] != null) {\n            s += ' '\n            s += a[i][5]\n            s += ' '\n            s += a[i][6]\n\n            if (a[i][7] != null) {\n              s += ' '\n              s += a[i][7]\n            }\n          }\n        }\n      }\n    }\n  }\n\n  return s + ' '\n}\n\nexport default class PathArray extends SVGArray {\n  // Get bounding box of path\n  bbox() {\n    parser().path.setAttribute('d', this.toString())\n    return new Box(parser.nodes.path.getBBox())\n  }\n\n  // Move path string\n  move(x, y) {\n    // get bounding box of current situation\n    const box = this.bbox()\n\n    // get relative offset\n    x -= box.x\n    y -= box.y\n\n    if (!isNaN(x) && !isNaN(y)) {\n      // move every point\n      for (let l, i = this.length - 1; i >= 0; i--) {\n        l = this[i][0]\n\n        if (l === 'M' || l === 'L' || l === 'T') {\n          this[i][1] += x\n          this[i][2] += y\n        } else if (l === 'H') {\n          this[i][1] += x\n        } else if (l === 'V') {\n          this[i][1] += y\n        } else if (l === 'C' || l === 'S' || l === 'Q') {\n          this[i][1] += x\n          this[i][2] += y\n          this[i][3] += x\n          this[i][4] += y\n\n          if (l === 'C') {\n            this[i][5] += x\n            this[i][6] += y\n          }\n        } else if (l === 'A') {\n          this[i][6] += x\n          this[i][7] += y\n        }\n      }\n    }\n\n    return this\n  }\n\n  // Absolutize and parse path to array\n  parse(d = 'M0 0') {\n    if (Array.isArray(d)) {\n      d = Array.prototype.concat.apply([], d).toString()\n    }\n\n    return pathParser(d)\n  }\n\n  // Resize path string\n  size(width, height) {\n    // get bounding box of current situation\n    const box = this.bbox()\n    let i, l\n\n    // If the box width or height is 0 then we ignore\n    // transformations on the respective axis\n    box.width = box.width === 0 ? 1 : box.width\n    box.height = box.height === 0 ? 1 : box.height\n\n    // recalculate position of all points according to new size\n    for (i = this.length - 1; i >= 0; i--) {\n      l = this[i][0]\n\n      if (l === 'M' || l === 'L' || l === 'T') {\n        this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x\n        this[i][2] = ((this[i][2] - box.y) * height) / box.height + box.y\n      } else if (l === 'H') {\n        this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x\n      } else if (l === 'V') {\n        this[i][1] = ((this[i][1] - box.y) * height) / box.height + box.y\n      } else if (l === 'C' || l === 'S' || l === 'Q') {\n        this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x\n        this[i][2] = ((this[i][2] - box.y) * height) / box.height + box.y\n        this[i][3] = ((this[i][3] - box.x) * width) / box.width + box.x\n        this[i][4] = ((this[i][4] - box.y) * height) / box.height + box.y\n\n        if (l === 'C') {\n          this[i][5] = ((this[i][5] - box.x) * width) / box.width + box.x\n          this[i][6] = ((this[i][6] - box.y) * height) / box.height + box.y\n        }\n      } else if (l === 'A') {\n        // resize radii\n        this[i][1] = (this[i][1] * width) / box.width\n        this[i][2] = (this[i][2] * height) / box.height\n\n        // move position values\n        this[i][6] = ((this[i][6] - box.x) * width) / box.width + box.x\n        this[i][7] = ((this[i][7] - box.y) * height) / box.height + box.y\n      }\n    }\n\n    return this\n  }\n\n  // Convert array to string\n  toString() {\n    return arrayToString(this)\n  }\n}\n"
  },
  {
    "path": "src/types/Point.js",
    "content": "import Matrix from './Matrix.js'\n\nexport default class Point {\n  // Initialize\n  constructor(...args) {\n    this.init(...args)\n  }\n\n  // Clone point\n  clone() {\n    return new Point(this)\n  }\n\n  init(x, y) {\n    const base = { x: 0, y: 0 }\n\n    // ensure source as object\n    const source = Array.isArray(x)\n      ? { x: x[0], y: x[1] }\n      : typeof x === 'object'\n        ? { x: x.x, y: x.y }\n        : { x: x, y: y }\n\n    // merge source\n    this.x = source.x == null ? base.x : source.x\n    this.y = source.y == null ? base.y : source.y\n\n    return this\n  }\n\n  toArray() {\n    return [this.x, this.y]\n  }\n\n  transform(m) {\n    return this.clone().transformO(m)\n  }\n\n  // Transform point with matrix\n  transformO(m) {\n    if (!Matrix.isMatrixLike(m)) {\n      m = new Matrix(m)\n    }\n\n    const { x, y } = this\n\n    // Perform the matrix multiplication\n    this.x = m.a * x + m.c * y + m.e\n    this.y = m.b * x + m.d * y + m.f\n\n    return this\n  }\n}\n\nexport function point(x, y) {\n  return new Point(x, y).transformO(this.screenCTM().inverseO())\n}\n"
  },
  {
    "path": "src/types/PointArray.js",
    "content": "import { delimiter } from '../modules/core/regex.js'\nimport SVGArray from './SVGArray.js'\nimport Box from './Box.js'\nimport Matrix from './Matrix.js'\n\nexport default class PointArray extends SVGArray {\n  // Get bounding box of points\n  bbox() {\n    let maxX = -Infinity\n    let maxY = -Infinity\n    let minX = Infinity\n    let minY = Infinity\n    this.forEach(function (el) {\n      maxX = Math.max(el[0], maxX)\n      maxY = Math.max(el[1], maxY)\n      minX = Math.min(el[0], minX)\n      minY = Math.min(el[1], minY)\n    })\n    return new Box(minX, minY, maxX - minX, maxY - minY)\n  }\n\n  // Move point string\n  move(x, y) {\n    const box = this.bbox()\n\n    // get relative offset\n    x -= box.x\n    y -= box.y\n\n    // move every point\n    if (!isNaN(x) && !isNaN(y)) {\n      for (let i = this.length - 1; i >= 0; i--) {\n        this[i] = [this[i][0] + x, this[i][1] + y]\n      }\n    }\n\n    return this\n  }\n\n  // Parse point string and flat array\n  parse(array = [0, 0]) {\n    const points = []\n\n    // if it is an array, we flatten it and therefore clone it to 1 depths\n    if (array instanceof Array) {\n      array = Array.prototype.concat.apply([], array)\n    } else {\n      // Else, it is considered as a string\n      // parse points\n      array = array.trim().split(delimiter).map(parseFloat)\n    }\n\n    // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints\n    // Odd number of coordinates is an error. In such cases, drop the last odd coordinate.\n    if (array.length % 2 !== 0) array.pop()\n\n    // wrap points in two-tuples\n    for (let i = 0, len = array.length; i < len; i = i + 2) {\n      points.push([array[i], array[i + 1]])\n    }\n\n    return points\n  }\n\n  // Resize poly string\n  size(width, height) {\n    let i\n    const box = this.bbox()\n\n    // recalculate position of all points according to new size\n    for (i = this.length - 1; i >= 0; i--) {\n      if (box.width)\n        this[i][0] = ((this[i][0] - box.x) * width) / box.width + box.x\n      if (box.height)\n        this[i][1] = ((this[i][1] - box.y) * height) / box.height + box.y\n    }\n\n    return this\n  }\n\n  // Convert array to line object\n  toLine() {\n    return {\n      x1: this[0][0],\n      y1: this[0][1],\n      x2: this[1][0],\n      y2: this[1][1]\n    }\n  }\n\n  // Convert array to string\n  toString() {\n    const array = []\n    // convert to a poly point string\n    for (let i = 0, il = this.length; i < il; i++) {\n      array.push(this[i].join(','))\n    }\n\n    return array.join(' ')\n  }\n\n  transform(m) {\n    return this.clone().transformO(m)\n  }\n\n  // transform points with matrix (similar to Point.transform)\n  transformO(m) {\n    if (!Matrix.isMatrixLike(m)) {\n      m = new Matrix(m)\n    }\n\n    for (let i = this.length; i--; ) {\n      // Perform the matrix multiplication\n      const [x, y] = this[i]\n      this[i][0] = m.a * x + m.c * y + m.e\n      this[i][1] = m.b * x + m.d * y + m.f\n    }\n\n    return this\n  }\n}\n"
  },
  {
    "path": "src/types/SVGArray.js",
    "content": "import { delimiter } from '../modules/core/regex.js'\n\nexport default class SVGArray extends Array {\n  constructor(...args) {\n    super(...args)\n    this.init(...args)\n  }\n\n  clone() {\n    return new this.constructor(this)\n  }\n\n  init(arr) {\n    // This catches the case, that native map tries to create an array with new Array(1)\n    if (typeof arr === 'number') return this\n    this.length = 0\n    this.push(...this.parse(arr))\n    return this\n  }\n\n  // Parse whitespace separated string\n  parse(array = []) {\n    // If already is an array, no need to parse it\n    if (array instanceof Array) return array\n\n    return array.trim().split(delimiter).map(parseFloat)\n  }\n\n  toArray() {\n    return Array.prototype.concat.apply([], this)\n  }\n\n  toSet() {\n    return new Set(this)\n  }\n\n  toString() {\n    return this.join(' ')\n  }\n\n  // Flattens the array if needed\n  valueOf() {\n    const ret = []\n    ret.push(...this)\n    return ret\n  }\n}\n"
  },
  {
    "path": "src/types/SVGNumber.js",
    "content": "import { numberAndUnit } from '../modules/core/regex.js'\n\n// Module for unit conversions\nexport default class SVGNumber {\n  // Initialize\n  constructor(...args) {\n    this.init(...args)\n  }\n\n  convert(unit) {\n    return new SVGNumber(this.value, unit)\n  }\n\n  // Divide number\n  divide(number) {\n    number = new SVGNumber(number)\n    return new SVGNumber(this / number, this.unit || number.unit)\n  }\n\n  init(value, unit) {\n    unit = Array.isArray(value) ? value[1] : unit\n    value = Array.isArray(value) ? value[0] : value\n\n    // initialize defaults\n    this.value = 0\n    this.unit = unit || ''\n\n    // parse value\n    if (typeof value === 'number') {\n      // ensure a valid numeric value\n      this.value = isNaN(value)\n        ? 0\n        : !isFinite(value)\n          ? value < 0\n            ? -3.4e38\n            : +3.4e38\n          : value\n    } else if (typeof value === 'string') {\n      unit = value.match(numberAndUnit)\n\n      if (unit) {\n        // make value numeric\n        this.value = parseFloat(unit[1])\n\n        // normalize\n        if (unit[5] === '%') {\n          this.value /= 100\n        } else if (unit[5] === 's') {\n          this.value *= 1000\n        }\n\n        // store unit\n        this.unit = unit[5]\n      }\n    } else {\n      if (value instanceof SVGNumber) {\n        this.value = value.valueOf()\n        this.unit = value.unit\n      }\n    }\n\n    return this\n  }\n\n  // Subtract number\n  minus(number) {\n    number = new SVGNumber(number)\n    return new SVGNumber(this - number, this.unit || number.unit)\n  }\n\n  // Add number\n  plus(number) {\n    number = new SVGNumber(number)\n    return new SVGNumber(this + number, this.unit || number.unit)\n  }\n\n  // Multiply number\n  times(number) {\n    number = new SVGNumber(number)\n    return new SVGNumber(this * number, this.unit || number.unit)\n  }\n\n  toArray() {\n    return [this.value, this.unit]\n  }\n\n  toJSON() {\n    return this.toString()\n  }\n\n  toString() {\n    return (\n      (this.unit === '%'\n        ? ~~(this.value * 1e8) / 1e6\n        : this.unit === 's'\n          ? this.value / 1e3\n          : this.value) + this.unit\n    )\n  }\n\n  valueOf() {\n    return this.value\n  }\n}\n"
  },
  {
    "path": "src/utils/adopter.js",
    "content": "import { addMethodNames } from './methods.js'\nimport { capitalize } from './utils.js'\nimport { svg } from '../modules/core/namespaces.js'\nimport { globals } from '../utils/window.js'\nimport Base from '../types/Base.js'\n\nconst elements = {}\nexport const root = '___SYMBOL___ROOT___'\n\n// Method for element creation\nexport function create(name, ns = svg) {\n  // create element\n  return globals.document.createElementNS(ns, name)\n}\n\nexport function makeInstance(element, isHTML = false) {\n  if (element instanceof Base) return element\n\n  if (typeof element === 'object') {\n    return adopter(element)\n  }\n\n  if (element == null) {\n    return new elements[root]()\n  }\n\n  if (typeof element === 'string' && element.trim().charAt(0) !== '<') {\n    return adopter(globals.document.querySelector(element))\n  }\n\n  // Make sure, that HTML elements are created with the correct namespace\n  const wrapper = isHTML ? globals.document.createElement('div') : create('svg')\n  wrapper.innerHTML = element.trim()\n\n  // We use firstElementChild here to skip potential comment nodes (#1339),\n  element = adopter(wrapper.firstElementChild)\n\n  // make sure, that element doesn't have its wrapper attached\n  wrapper.removeChild(wrapper.firstElementChild)\n  return element\n}\n\nexport function nodeOrNew(name, node) {\n  return node &&\n    (node instanceof globals.window.Node ||\n      (node.ownerDocument &&\n        node instanceof node.ownerDocument.defaultView.Node))\n    ? node\n    : create(name)\n}\n\n// Adopt existing svg elements\nexport function adopt(node) {\n  // check for presence of node\n  if (!node) return null\n\n  // make sure a node isn't already adopted\n  if (node.instance instanceof Base) return node.instance\n\n  if (node.nodeName === '#document-fragment') {\n    return new elements.Fragment(node)\n  }\n\n  // initialize variables\n  let className = capitalize(node.nodeName || 'Dom')\n\n  // Make sure that gradients are adopted correctly\n  if (className === 'LinearGradient' || className === 'RadialGradient') {\n    className = 'Gradient'\n\n    // Fallback to Dom if element is not known\n  } else if (!elements[className]) {\n    className = 'Dom'\n  }\n\n  return new elements[className](node)\n}\n\nlet adopter = adopt\n\nexport function mockAdopt(mock = adopt) {\n  adopter = mock\n}\n\nexport function register(element, name = element.name, asRoot = false) {\n  elements[name] = element\n  if (asRoot) elements[root] = element\n\n  addMethodNames(Object.getOwnPropertyNames(element.prototype))\n\n  return element\n}\n\nexport function getClass(name) {\n  return elements[name]\n}\n\n// Element id sequence\nlet did = 1000\n\n// Get next named element id\nexport function eid(name) {\n  return 'Svgjs' + capitalize(name) + did++\n}\n\n// Deep new id assignment\nexport function assignNewId(node) {\n  // do the same for SVG child nodes as well\n  for (let i = node.children.length - 1; i >= 0; i--) {\n    assignNewId(node.children[i])\n  }\n\n  if (node.id) {\n    node.id = eid(node.nodeName)\n    return node\n  }\n\n  return node\n}\n\n// Method for extending objects\nexport function extend(modules, methods) {\n  let key, i\n\n  modules = Array.isArray(modules) ? modules : [modules]\n\n  for (i = modules.length - 1; i >= 0; i--) {\n    for (key in methods) {\n      modules[i].prototype[key] = methods[key]\n    }\n  }\n}\n\nexport function wrapWithAttrCheck(fn) {\n  return function (...args) {\n    const o = args[args.length - 1]\n\n    if (o && o.constructor === Object && !(o instanceof Array)) {\n      return fn.apply(this, args.slice(0, -1)).attr(o)\n    } else {\n      return fn.apply(this, args)\n    }\n  }\n}\n"
  },
  {
    "path": "src/utils/methods.js",
    "content": "const methods = {}\nconst names = []\n\nexport function registerMethods(name, m) {\n  if (Array.isArray(name)) {\n    for (const _name of name) {\n      registerMethods(_name, m)\n    }\n    return\n  }\n\n  if (typeof name === 'object') {\n    for (const _name in name) {\n      registerMethods(_name, name[_name])\n    }\n    return\n  }\n\n  addMethodNames(Object.getOwnPropertyNames(m))\n  methods[name] = Object.assign(methods[name] || {}, m)\n}\n\nexport function getMethodsFor(name) {\n  return methods[name] || {}\n}\n\nexport function getMethodNames() {\n  return [...new Set(names)]\n}\n\nexport function addMethodNames(_names) {\n  names.push(..._names)\n}\n"
  },
  {
    "path": "src/utils/pathParser.js",
    "content": "import { isPathLetter } from '../modules/core/regex.js'\nimport Point from '../types/Point.js'\n\nconst segmentParameters = {\n  M: 2,\n  L: 2,\n  H: 1,\n  V: 1,\n  C: 6,\n  S: 4,\n  Q: 4,\n  T: 2,\n  A: 7,\n  Z: 0\n}\n\nconst pathHandlers = {\n  M: function (c, p, p0) {\n    p.x = p0.x = c[0]\n    p.y = p0.y = c[1]\n\n    return ['M', p.x, p.y]\n  },\n  L: function (c, p) {\n    p.x = c[0]\n    p.y = c[1]\n    return ['L', c[0], c[1]]\n  },\n  H: function (c, p) {\n    p.x = c[0]\n    return ['H', c[0]]\n  },\n  V: function (c, p) {\n    p.y = c[0]\n    return ['V', c[0]]\n  },\n  C: function (c, p) {\n    p.x = c[4]\n    p.y = c[5]\n    return ['C', c[0], c[1], c[2], c[3], c[4], c[5]]\n  },\n  S: function (c, p) {\n    p.x = c[2]\n    p.y = c[3]\n    return ['S', c[0], c[1], c[2], c[3]]\n  },\n  Q: function (c, p) {\n    p.x = c[2]\n    p.y = c[3]\n    return ['Q', c[0], c[1], c[2], c[3]]\n  },\n  T: function (c, p) {\n    p.x = c[0]\n    p.y = c[1]\n    return ['T', c[0], c[1]]\n  },\n  Z: function (c, p, p0) {\n    p.x = p0.x\n    p.y = p0.y\n    return ['Z']\n  },\n  A: function (c, p) {\n    p.x = c[5]\n    p.y = c[6]\n    return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]]\n  }\n}\n\nconst mlhvqtcsaz = 'mlhvqtcsaz'.split('')\n\nfor (let i = 0, il = mlhvqtcsaz.length; i < il; ++i) {\n  pathHandlers[mlhvqtcsaz[i]] = (function (i) {\n    return function (c, p, p0) {\n      if (i === 'H') c[0] = c[0] + p.x\n      else if (i === 'V') c[0] = c[0] + p.y\n      else if (i === 'A') {\n        c[5] = c[5] + p.x\n        c[6] = c[6] + p.y\n      } else {\n        for (let j = 0, jl = c.length; j < jl; ++j) {\n          c[j] = c[j] + (j % 2 ? p.y : p.x)\n        }\n      }\n\n      return pathHandlers[i](c, p, p0)\n    }\n  })(mlhvqtcsaz[i].toUpperCase())\n}\n\nfunction makeAbsolut(parser) {\n  const command = parser.segment[0]\n  return pathHandlers[command](parser.segment.slice(1), parser.p, parser.p0)\n}\n\nfunction segmentComplete(parser) {\n  return (\n    parser.segment.length &&\n    parser.segment.length - 1 ===\n      segmentParameters[parser.segment[0].toUpperCase()]\n  )\n}\n\nfunction startNewSegment(parser, token) {\n  parser.inNumber && finalizeNumber(parser, false)\n  const pathLetter = isPathLetter.test(token)\n\n  if (pathLetter) {\n    parser.segment = [token]\n  } else {\n    const lastCommand = parser.lastCommand\n    const small = lastCommand.toLowerCase()\n    const isSmall = lastCommand === small\n    parser.segment = [small === 'm' ? (isSmall ? 'l' : 'L') : lastCommand]\n  }\n\n  parser.inSegment = true\n  parser.lastCommand = parser.segment[0]\n\n  return pathLetter\n}\n\nfunction finalizeNumber(parser, inNumber) {\n  if (!parser.inNumber) throw new Error('Parser Error')\n  parser.number && parser.segment.push(parseFloat(parser.number))\n  parser.inNumber = inNumber\n  parser.number = ''\n  parser.pointSeen = false\n  parser.hasExponent = false\n\n  if (segmentComplete(parser)) {\n    finalizeSegment(parser)\n  }\n}\n\nfunction finalizeSegment(parser) {\n  parser.inSegment = false\n  if (parser.absolute) {\n    parser.segment = makeAbsolut(parser)\n  }\n  parser.segments.push(parser.segment)\n}\n\nfunction isArcFlag(parser) {\n  if (!parser.segment.length) return false\n  const isArc = parser.segment[0].toUpperCase() === 'A'\n  const length = parser.segment.length\n\n  return isArc && (length === 4 || length === 5)\n}\n\nfunction isExponential(parser) {\n  return parser.lastToken.toUpperCase() === 'E'\n}\n\nconst pathDelimiters = new Set([' ', ',', '\\t', '\\n', '\\r', '\\f'])\nexport function pathParser(d, toAbsolute = true) {\n  let index = 0\n  let token = ''\n  const parser = {\n    segment: [],\n    inNumber: false,\n    number: '',\n    lastToken: '',\n    inSegment: false,\n    segments: [],\n    pointSeen: false,\n    hasExponent: false,\n    absolute: toAbsolute,\n    p0: new Point(),\n    p: new Point()\n  }\n\n  while (((parser.lastToken = token), (token = d.charAt(index++)))) {\n    if (!parser.inSegment) {\n      if (startNewSegment(parser, token)) {\n        continue\n      }\n    }\n\n    if (token === '.') {\n      if (parser.pointSeen || parser.hasExponent) {\n        finalizeNumber(parser, false)\n        --index\n        continue\n      }\n      parser.inNumber = true\n      parser.pointSeen = true\n      parser.number += token\n      continue\n    }\n\n    if (!isNaN(parseInt(token))) {\n      if (parser.number === '0' || isArcFlag(parser)) {\n        parser.inNumber = true\n        parser.number = token\n        finalizeNumber(parser, true)\n        continue\n      }\n\n      parser.inNumber = true\n      parser.number += token\n      continue\n    }\n\n    if (pathDelimiters.has(token)) {\n      if (parser.inNumber) {\n        finalizeNumber(parser, false)\n      }\n      continue\n    }\n\n    if (token === '-' || token === '+') {\n      if (parser.inNumber && !isExponential(parser)) {\n        finalizeNumber(parser, false)\n        --index\n        continue\n      }\n      parser.number += token\n      parser.inNumber = true\n      continue\n    }\n\n    if (token.toUpperCase() === 'E') {\n      parser.number += token\n      parser.hasExponent = true\n      continue\n    }\n\n    if (isPathLetter.test(token)) {\n      if (parser.inNumber) {\n        finalizeNumber(parser, false)\n      } else if (!segmentComplete(parser)) {\n        throw new Error('parser Error')\n      } else {\n        finalizeSegment(parser)\n      }\n      --index\n    }\n  }\n\n  if (parser.inNumber) {\n    finalizeNumber(parser, false)\n  }\n\n  if (parser.inSegment && segmentComplete(parser)) {\n    finalizeSegment(parser)\n  }\n\n  return parser.segments\n}\n"
  },
  {
    "path": "src/utils/utils.js",
    "content": "// Map function\nexport function map(array, block) {\n  let i\n  const il = array.length\n  const result = []\n\n  for (i = 0; i < il; i++) {\n    result.push(block(array[i]))\n  }\n\n  return result\n}\n\n// Filter function\nexport function filter(array, block) {\n  let i\n  const il = array.length\n  const result = []\n\n  for (i = 0; i < il; i++) {\n    if (block(array[i])) {\n      result.push(array[i])\n    }\n  }\n\n  return result\n}\n\n// Degrees to radians\nexport function radians(d) {\n  return ((d % 360) * Math.PI) / 180\n}\n\n// Radians to degrees\nexport function degrees(r) {\n  return ((r * 180) / Math.PI) % 360\n}\n\n// Convert camel cased string to dash separated\nexport function unCamelCase(s) {\n  return s.replace(/([A-Z])/g, function (m, g) {\n    return '-' + g.toLowerCase()\n  })\n}\n\n// Capitalize first letter of a string\nexport function capitalize(s) {\n  return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\n// Calculate proportional width and height values when necessary\nexport function proportionalSize(element, width, height, box) {\n  if (width == null || height == null) {\n    box = box || element.bbox()\n\n    if (width == null) {\n      width = (box.width / box.height) * height\n    } else if (height == null) {\n      height = (box.height / box.width) * width\n    }\n  }\n\n  return {\n    width: width,\n    height: height\n  }\n}\n\n/**\n * This function adds support for string origins.\n * It searches for an origin in o.origin o.ox and o.originX.\n * This way, origin: {x: 'center', y: 50} can be passed as well as ox: 'center', oy: 50\n **/\nexport function getOrigin(o, element) {\n  const origin = o.origin\n  // First check if origin is in ox or originX\n  let ox = o.ox != null ? o.ox : o.originX != null ? o.originX : 'center'\n  let oy = o.oy != null ? o.oy : o.originY != null ? o.originY : 'center'\n\n  // Then check if origin was used and overwrite in that case\n  if (origin != null) {\n    ;[ox, oy] = Array.isArray(origin)\n      ? origin\n      : typeof origin === 'object'\n        ? [origin.x, origin.y]\n        : [origin, origin]\n  }\n\n  // Make sure to only call bbox when actually needed\n  const condX = typeof ox === 'string'\n  const condY = typeof oy === 'string'\n  if (condX || condY) {\n    const { height, width, x, y } = element.bbox()\n\n    // And only overwrite if string was passed for this specific axis\n    if (condX) {\n      ox = ox.includes('left')\n        ? x\n        : ox.includes('right')\n          ? x + width\n          : x + width / 2\n    }\n\n    if (condY) {\n      oy = oy.includes('top')\n        ? y\n        : oy.includes('bottom')\n          ? y + height\n          : y + height / 2\n    }\n  }\n\n  // Return the origin as it is if it wasn't a string\n  return [ox, oy]\n}\n\nconst descriptiveElements = new Set(['desc', 'metadata', 'title'])\nexport const isDescriptive = (element) =>\n  descriptiveElements.has(element.nodeName)\n\nexport const writeDataToDom = (element, data, defaults = {}) => {\n  const cloned = { ...data }\n\n  for (const key in cloned) {\n    if (cloned[key].valueOf() === defaults[key]) {\n      delete cloned[key]\n    }\n  }\n\n  if (Object.keys(cloned).length) {\n    element.node.setAttribute('data-svgjs', JSON.stringify(cloned)) // see #428\n  } else {\n    element.node.removeAttribute('data-svgjs')\n    element.node.removeAttribute('svgjs:data')\n  }\n}\n"
  },
  {
    "path": "src/utils/window.js",
    "content": "export const globals = {\n  window: typeof window === 'undefined' ? null : window,\n  document: typeof document === 'undefined' ? null : document\n}\n\nexport function registerWindow(win = null, doc = null) {\n  globals.window = win\n  globals.document = doc\n}\n\nconst save = {}\n\nexport function saveWindow() {\n  save.window = globals.window\n  save.document = globals.document\n}\n\nexport function restoreWindow() {\n  globals.window = save.window\n  globals.document = save.document\n}\n\nexport function withWindow(win, fn) {\n  saveWindow()\n  registerWindow(win, win.document)\n  fn(win, win.document)\n  restoreWindow()\n}\n\nexport function getWindow() {\n  return globals.window\n}\n"
  },
  {
    "path": "svg.js.d.ts",
    "content": "// Type definitions for @svgdotjs version 3.x\n// Project: @svgdotjs/svg.js\n\n// trick to keep reference to Array build-in type\ndeclare class BuiltInArray<T> extends Array<T> {}\n\n// camelCase to kebab-case\ndeclare type CamelToKebab<S extends string> = S extends `${infer T}${infer U}`\n  ? U extends Uncapitalize<U>\n    ? `${Lowercase<T>}${CamelToKebab<U>}`\n    : `${Lowercase<T>}-${CamelToKebab<U>}`\n  : S\n\ndeclare type ConvertKeysToKebab<T> = {\n  [K in keyof T as CamelToKebab<K & string>]: T[K]\n}\n\ndeclare type KebabCSSStyleDeclaration = ConvertKeysToKebab<CSSStyleDeclaration>\n\n// trick to have nice attribute list for CSS\ndeclare type CSSStyleName = Exclude<\n  keyof KebabCSSStyleDeclaration,\n  'parent-rule' | 'length'\n>\n\n// create our own style declaration that includes css vars\ninterface CSSStyleDeclarationWithVars extends KebabCSSStyleDeclaration {\n  [key: `--${string}`]: string\n}\n\ndeclare module '@svgdotjs/svg.js' {\n  function SVG(): Svg\n  /**\n   * @param selectorOrHtml pass in a css selector or an html/svg string\n   * @param isHTML only used if first argument is an html string. Will treat the svg/html as html and not svg\n   */\n  function SVG(selectorOrHtml: QuerySelector, isHTML?: boolean): Element\n  function SVG<T>(el: T): SVGTypeMapping<T>\n  function SVG(domElement: HTMLElement): Element\n\n  function eid(name: string): string\n  function get(id: string): Element\n\n  function create(name: string): any\n  function extend(parent: object, obj: object): void\n  function invent(config: object): any\n  function adopt(node: HTMLElement): Element\n  function prepare(element: HTMLElement): void\n  function getClass(name: string): Element\n\n  function on(\n    el: Node | Window,\n    events: string,\n    cb: EventListener,\n    binbind?: any,\n    options?: AddEventListenerOptions\n  ): void\n  function on(\n    el: Node | Window,\n    events: Event[],\n    cb: EventListener,\n    binbind?: any,\n    options?: AddEventListenerOptions\n  ): void\n\n  function off(\n    el: Node | Window,\n    events?: string,\n    cb?: EventListener | number,\n    options?: AddEventListenerOptions\n  ): void\n  function off(\n    el: Node | Window,\n    events?: Event[],\n    cb?: EventListener | number,\n    options?: AddEventListenerOptions\n  ): void\n\n  function dispatch(\n    node: Node | Window,\n    event: Event,\n    data?: object,\n    options?: object\n  ): Event\n\n  function find(query: QuerySelector): List<Element>\n  function findOne(query: QuerySelector): Element | null\n\n  function getWindow(): Window\n  function registerWindow(win: Window, doc: Document): void\n  function restoreWindow(): void\n  function saveWindow(): void\n  function withWindow(\n    win: Window,\n    fn: (win: Window, doc: Document) => void\n  ): void\n\n  let utils: {\n    map(array: any[], block: Function): any\n    filter(array: any[], block: Function): any\n    radians(d: number): number\n    degrees(r: number): number\n    camelCase(s: string): string\n    unCamelCase(s: string): string\n    capitalize(s: string): string\n    // proportionalSize\n    // getOrigin\n  }\n\n  let defaults: {\n    attrs: {\n      'fill-opacity': number\n      'stroke-opacity': number\n      'stroke-width': number\n      'stroke-linejoin': string\n      'stroke-linecap': string\n      fill: string\n      stroke: string\n      opacity: number\n      x: number\n      y: number\n      cx: number\n      cy: number\n      width: number\n      height: number\n      r: number\n      rx: number\n      ry: number\n      offset: number\n      'stop-opacity': number\n      'stop-color': string\n      'font-size': number\n      'font-family': string\n      'text-anchor': string\n    }\n    timeline: {\n      duration: number\n      ease: string\n      delay: number\n    }\n  }\n\n  // let easing: {\n  //     '-'(pos: number): number;\n  //     '<>'(pos: number): number;\n  //     '>'(pos: number): number;\n  //     '<'(pos: number): number;\n  //     bezier(x1: number, y1: number, x2: number, y2: number): (t: number) => number;\n  //     steps(steps: number, stepPosition?: \"jump-start\"|\"jump-end\"|\"jump-none\"|\"jump-both\"|\"start\"|\"end\"): (t: number, beforeFlag?: boolean) => number;\n  // }\n\n  let regex: {\n    delimiter: RegExp\n    dots: RegExp\n    hex: RegExp\n    hyphen: RegExp\n    isBlank: RegExp\n    isHex: RegExp\n    isImage: RegExp\n    isNumber: RegExp\n    isPathLetter: RegExp\n    isRgb: RegExp\n    numberAndUnit: RegExp\n    numbersWithDots: RegExp\n    pathLetters: RegExp\n    reference: RegExp\n    rgb: RegExp\n    transforms: RegExp\n    whitespace: RegExp\n  }\n\n  let namespaces: {\n    ns: string\n    xmlns: string\n    xlink: string\n    svgjs: string\n  }\n\n  interface LinkedHTMLElement extends HTMLElement {\n    instance: Element\n  }\n\n  // ************ Standard object/option/properties declaration ************\n\n  type AttrNumberValue = number | 'auto'\n\n  /**\n   * The SVG core attributes are all the common attributes that can be specified on any SVG element.\n   * More information see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Core\n   */\n  interface CoreAttr {\n    id?: string\n    lang?: string\n    tabindex?: number\n    'xml:lang'?: string\n  }\n\n  /**\n   * The SVG styling attributes are all the attributes that can be specified on any SVG element to apply CSS styling effects.\n   * More information see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Styling\n   */\n  interface StylingAttr {\n    /**\n     * a valid HTML class name\n     */\n    class?: string\n    /**\n     * SVG css style string format. It all can be find here https://www.w3.org/TR/SVG/styling.html#StyleAttribute\n     */\n    style?: string\n  }\n\n  /**\n   * A global attribute that can be use with any svg element\n   */\n  interface GlobalAttr extends CoreAttr, StylingAttr {}\n\n  // TODO: implement SVG Presentation Attributes. See https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation\n\n  interface PathBaseAttr {\n    pathLength?: number\n  }\n\n  interface RadiusAxisAttr {\n    rx?: AttrNumberValue\n    ry?: AttrNumberValue\n  }\n\n  /**\n   * SVG Rectangle attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect\n   */\n  interface RectAttr extends RadiusAxisAttr, PathBaseAttr, GlobalAttr {\n    x?: number\n    y?: number\n    width: AttrNumberValue\n    height: AttrNumberValue\n  }\n\n  /**\n   * SVG Line attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line\n   */\n  interface LineAttr extends PathBaseAttr, GlobalAttr {\n    x1?: number\n    y1?: number\n    x2?: number\n    y2?: number\n  }\n\n  /**\n   * SVG Circle attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle\n   */\n  interface CircleAttr extends PathBaseAttr, GlobalAttr {\n    cx?: number | string\n    cy?: number | string\n    r?: number | string\n  }\n\n  /**\n   * SVG Ellipse attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse\n   */\n  interface EllipseAttr extends PathBaseAttr, GlobalAttr {\n    cx?: number | string\n    cy?: number | string\n    rx?: number | string\n    ry?: number | string\n  }\n\n  /**\n   * SVG Path attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path\n   */\n  interface PathAttr extends PathBaseAttr, GlobalAttr {\n    d?: string\n  }\n\n  /**\n   * SVG Path attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polygon\n   * or https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polyline\n   */\n  interface PolyAttr extends PathBaseAttr, GlobalAttr {\n    points?: string\n  }\n\n  /**\n   * SVG Text attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/text\n   */\n  interface TextAttr extends GlobalAttr {\n    x?: number | string\n    y?: number | string\n    dx?: number | string\n    dy?: number | string\n    lengthAdjust?: 'spacing' | 'spacingAndGlyphs'\n    textLength?: number | string\n    // see https://developer.mozilla.org/en-US/docs/Web/API/SVGNumberList\n    // or https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#List-of-Ts\n    // TODO: tbd\n    // rotate?: string\n  }\n\n  /**\n   * SVG TextPath attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/textPath\n   */\n  interface TextPathAttr extends GlobalAttr {\n    href?: string\n    lengthAdjust?: 'spacing' | 'spacingAndGlyphs'\n    method?: 'align' | 'stretch'\n    side?: 'left' | 'right'\n    spacing?: 'auto' | 'exact'\n    startOffset?: number | string\n    textLength?: number | string\n    // See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/textPath\n    // TODO: tbd as there is no reference to see the detail of how it would look like\n    // path?: string\n  }\n\n  /**\n   * A generic Dom Box object.\n   * Notice: DOMRect is still in experiment state and document is not complete (Draft)\n   * See https://developer.mozilla.org/en-US/docs/Web/API/DOMRect\n   */\n  interface DOMRect {\n    x?: number\n    y?: number\n    width?: number\n    height?: number\n    top?: number\n    right?: number\n    bottom?: number\n    left?: number\n  }\n\n  // ************ SVG.JS generic Conditional Types declaration ************\n\n  type SVGTypeMapping<T> = T extends HTMLElement\n    ? Dom\n    : T extends SVGSVGElement\n      ? Svg\n      : T extends SVGRectElement\n        ? Rect\n        : T extends SVGCircleElement\n          ? Circle\n          : T extends SVGPathElement\n            ? Path\n            : T extends SVGTextElement\n              ? Text\n              : T extends SVGTextPathElement\n                ? TextPath\n                : T extends SVGGElement\n                  ? G\n                  : T extends SVGLineElement\n                    ? Line\n                    : T extends SVGPolylineElement\n                      ? Polyline\n                      : T extends SVGPolygonElement\n                        ? Polygon\n                        : T extends SVGGradientElement\n                          ? Gradient\n                          : T extends SVGImageElement\n                            ? Image\n                            : T extends SVGEllipseElement\n                              ? Ellipse\n                              : T extends SVGMaskElement\n                                ? Mask\n                                : T extends SVGMarkerElement\n                                  ? Marker\n                                  : T extends SVGClipPathElement\n                                    ? ClipPath\n                                    : T extends SVGTSpanElement\n                                      ? Tspan\n                                      : T extends SVGSymbolElement\n                                        ? Symbol\n                                        : T extends SVGUseElement\n                                          ? Use\n                                          : Element\n\n  // element type as string\n  type SvgType = 'svg'\n  type ClipPathType = 'clipPath'\n  type TextType = 'text'\n  type GType = 'g'\n  type AType = 'a'\n\n  type ParentElement = SvgType | GType | AType\n\n  type AttrTypeMapping<T> = T extends Rect ? RectAttr : GlobalAttr\n\n  type ElementAlias =\n    | Dom\n    | Svg\n    | Rect\n    | Line\n    | Polygon\n    | Polyline\n    | Ellipse\n    | ClipPath\n    | Use\n    | Text\n    | Path\n    | TextPath\n    | Circle\n    | G\n    | Gradient\n    | Image\n    | Element\n\n  type ElementTypeAlias =\n    | typeof Dom\n    | typeof Svg\n    | typeof Rect\n    | typeof Line\n    | typeof Polygon\n    | typeof Polyline\n    | typeof Ellipse\n    | typeof ClipPath\n    | typeof Use\n    | typeof Text\n    | typeof Path\n    | typeof TextPath\n    | typeof Circle\n    | typeof G\n    | typeof Gradient\n    | typeof Image\n    | typeof Element\n\n  type AttributeReference =\n    | 'href'\n    | 'marker-start'\n    | 'marker-mid'\n    | 'marker-end'\n    | 'mask'\n    | 'clip-path'\n    | 'filter'\n    | 'fill'\n\n  // ************* SVG.JS Type Declaration *************\n  // ********** Locate in directory src/types **********\n\n  // SVGArray.js\n  // Notice: below class is defined the name as `Array` rather than `SVGArray`.\n  // The purpose of giving the name as `Array` is to allow it to be aligned with SVG.JS export type\n  // as SVG.JS export it as `Array` (to be precise `SVG.Array`) so reading through JS documentation\n  // should be more straightforward.\n  /**\n   * Type alias to native array.\n   *\n   * **Caution**: If argument is a string, generic type must be a number or array of number and\n   * the string is format as a concatenate of number separate by comma.\n   * This is expensive to build runtime type check for such as case so please use it carefully.\n   */\n  type ArrayAlias<T> = BuiltInArray<T> | T[] | string\n\n  class Array<T> extends BuiltInArray<T> {\n    constructor(array?: ArrayAlias<T>)\n\n    /**\n     * Return array of generic T however it's flatten array by 1 level as it using `apply` function.\n     * For example: if T is a `number[]` which is the number of 2 dimension `Array<number[]>` the result will be `number[]`\n     */\n    toArray(): any[]\n    /**\n     * return a concatenated string of each element separated by space\n     */\n    toString(): string\n    valueOf(): T[]\n    clone(): Array<T>\n    toSet(): Set<T>\n    parse(a?: ArrayAlias<T>): T[]\n    to(a: any): Morphable\n  }\n\n  // point.js\n  class Point {\n    x: number\n    y: number\n    constructor()\n    constructor(position: CoordinateXY)\n    constructor(point: Point)\n    constructor(x: number, y?: number)\n    clone(): Point\n    transform(matrix: Matrix): this\n    transformO(matrix: Matrix): this\n    toArray(): ArrayXY\n  }\n\n  // pointArray.js\n  class PointArray extends Array<ArrayXY> {\n    constructor()\n    constructor(array?: ArrayAlias<ArrayXY> | number[])\n\n    toLine(): LineAttr\n    transform(m: Matrix | MatrixLike): PointArray\n    move(x: number, y: number): this\n    size(width: number, height: number): this\n    bbox(): Box\n    to(a: any): Morphable\n    toString(): string\n  }\n\n  // SVGNumber.js\n  type NumberUnit = [number, string]\n\n  class Number {\n    constructor()\n    constructor(value: Number)\n    constructor(value: string)\n    constructor(value: number, unit?: any)\n    constructor(n: NumberUnit)\n\n    value: number\n    unit: any\n\n    toString(): string\n    toJSON(): object // same as toString\n    toArray(): NumberUnit\n    valueOf(): number\n    plus(number: NumberAlias): Number\n    minus(number: NumberAlias): Number\n    times(number: NumberAlias): Number\n    divide(number: NumberAlias): Number\n    convert(unit: string): Number\n    to(a: any): Morphable\n  }\n\n  type NumberAlias = Number | number | string\n\n  // PathArray.js\n\n  type LineCommand =\n    | ['M' | 'm' | 'L' | 'l', number, number]\n    | ['H' | 'h' | 'V' | 'v', number]\n    | ['Z' | 'z']\n\n  type CurveCommand =\n    // Bezier Curves\n    | ['C' | 'c', number, number, number, number, number, number]\n    | ['S' | 's' | 'Q' | 'q', number, number, number, number]\n    | ['T' | 't', number, number]\n    // Arcs\n    | ['A' | 'a', number, number, number, number, number, number, number]\n\n  type PathCommand = LineCommand | CurveCommand\n\n  type PathArrayAlias = PathArray | PathCommand[] | (string | number)[] | string\n\n  class PathArray extends Array<PathCommand> {\n    constructor()\n    constructor(d: ArrayAlias<PathCommand> | PathArrayAlias)\n\n    move(x: number, y: number): this\n    size(width: number, height: number): this\n    equalCommands(other: PathArray): boolean\n    morph(pa: PathArray): this\n    parse(array?: ArrayAlias<PathCommand> | PathArrayAlias): PathCommand[]\n    bbox(): Box\n    to(a: any): Morphable\n  }\n\n  // Matrix.js\n  interface TransformData {\n    origin?: number[]\n    scaleX?: number\n    scaleY?: number\n    shear?: number\n    rotate?: number\n    translateX?: number\n    translateY?: number\n    originX?: number\n    originY?: number\n  }\n\n  interface MatrixLike {\n    a?: number\n    b?: number\n    c?: number\n    d?: number\n    e?: number\n    f?: number\n  }\n\n  interface MatrixExtract extends TransformData, MatrixLike {}\n\n  type FlipType = 'both' | 'x' | 'y' | boolean\n  type ArrayXY = [number, number]\n  type CoordinateXY = ArrayXY | { x: number; y: number }\n\n  interface MatrixTransformParam {\n    rotate?: number\n    flip?: FlipType\n    skew?: ArrayXY | number\n    skewX?: number\n    skewY?: number\n    scale?: ArrayXY | number\n    scaleX?: number\n    scaleY?: number\n    shear?: number\n    theta?: number\n    origin?: CoordinateXY | string\n    around?: CoordinateXY\n    ox?: number\n    originX?: number\n    oy?: number\n    originY?: number\n    position?: CoordinateXY\n    px?: number\n    positionX?: number\n    py?: number\n    positionY?: number\n    translate?: CoordinateXY\n    tx?: number\n    translateX?: number\n    ty?: number\n    translateY?: number\n    relative?: CoordinateXY\n    rx?: number\n    relativeX?: number\n    ry?: number\n    relativeY?: number\n  }\n\n  type MatrixAlias =\n    | MatrixLike\n    | TransformData\n    | MatrixTransformParam\n    | number[]\n    | Element\n    | string\n\n  class Matrix implements MatrixLike {\n    constructor()\n    constructor(source: MatrixAlias)\n    constructor(\n      a: number,\n      b: number,\n      c: number,\n      d: number,\n      e: number,\n      f: number\n    )\n\n    a: number\n    b: number\n    c: number\n    d: number\n    e: number\n    f: number;\n\n    // *** To Be use by Test Only in restrict mode ***\n    [key: string]: any\n\n    clone(): Matrix\n    transform(o: MatrixLike | MatrixTransformParam): Matrix\n    compose(o: MatrixExtract): Matrix\n    decompose(cx?: number, cy?: number): MatrixExtract\n    multiply(m: MatrixAlias | Matrix): Matrix\n    multiplyO(m: MatrixAlias | Matrix): this\n    lmultiply(m: MatrixAlias | Matrix): Matrix\n    lmultiplyO(m: MatrixAlias | Matrix): this\n    inverse(): Matrix\n    inverseO(): this\n    translate(x?: number, y?: number): Matrix\n    translateO(x?: number, y?: number): this\n    scale(x: number, y?: number, cx?: number, cy?: number): Matrix\n    scaleO(x: number, y?: number, cx?: number, cy?: number): this\n    rotate(r: number, cx?: number, cy?: number): Matrix\n    rotateO(r: number, cx?: number, cy?: number): this\n    flip(a: NumberAlias, offset?: number): Matrix\n    flipO(a: NumberAlias, offset?: number): this\n    flip(offset?: number): Matrix\n    shear(a: number, cx?: number, cy?: number): Matrix\n    shearO(a: number, cx?: number, cy?: number): this\n    skew(y?: number, cx?: number, cy?: number): Matrix\n    skewO(y?: number, cx?: number, cy?: number): this\n    skew(x: number, y?: number, cx?: number, cy?: number): Matrix\n    skewX(x: number, cx?: number, cy?: number): Matrix\n    skewY(y: number, cx?: number, cy?: number): Matrix\n    around(cx?: number, cy?: number, matrix?: Matrix): Matrix\n    aroundO(cx?: number, cy?: number, matrix?: Matrix): this\n    equals(m: Matrix): boolean\n    toString(): string\n    toArray(): number[]\n    valueOf(): MatrixLike\n    to(a: any): Morphable\n  }\n\n  type ListEachCallback<T> = (el: T, index: number, list: List<T>) => any\n\n  // List.js\n  class List<T> extends BuiltInArray<T> {\n    each(fn: ListEachCallback<T>): List<any>\n    each(name: string, ...args: any[]): List<any>\n    toArray(): T[]\n  }\n\n  class Eventobject {\n    [key: string]: Eventobject\n  }\n\n  // EventTarget.js\n  class EventTarget {\n    events: Eventobject\n\n    addEventListener(): void\n    dispatch(event: Event | string, data?: object): Event\n    dispatchEvent(event: Event): boolean\n    fire(event: Event | string, data?: object): this\n    getEventHolder(): this | Node\n    getEventTarget(): this | Node\n\n    on(\n      events: string | Event[],\n      cb: EventListener,\n      binbind?: any,\n      options?: AddEventListenerOptions\n    ): this\n    off(\n      events?: string | Event[],\n      cb?: EventListener | number,\n      options?: AddEventListenerOptions\n    ): this\n\n    removeEventListener(): void\n  }\n\n  // Color.js\n  interface ColorLike {\n    r: number\n    g: number\n    b: number\n\n    x: number\n    y: number\n    z: number\n\n    h: number\n    s: number\n    l: number\n    a: number\n    c: number\n\n    m: number\n    k: number\n\n    space: string\n  }\n\n  type ColorAlias = string | ColorLike\n\n  class Color implements ColorLike {\n    r: number\n    g: number\n    b: number\n\n    x: number\n    y: number\n    z: number\n\n    h: number\n    s: number\n    l: number\n    a: number\n    c: number\n\n    m: number\n    k: number\n\n    space: string\n    constructor()\n    constructor(color: ColorAlias, space?: string)\n    constructor(a: number, b: number, c: number, space?: string)\n    constructor(a: number, b: number, c: number, d: number, space?: string)\n    constructor(a: number[], space?: string)\n\n    rgb(): Color\n    lab(): Color\n    xyz(): Color\n    lch(): Color\n    hsl(): Color\n    cmyk(): Color\n    toHex(): string\n    toString(): string\n    toRgb(): string\n    toArray(): any[]\n\n    to(a: any): Morphable\n    fromArray(a: any): this\n\n    static random(mode: 'sine', time?: number): Color\n    static random(mode?: string): Color\n  }\n\n  // Box.js\n  interface BoxLike {\n    height: number\n    width: number\n    y: number\n    x: number\n    cx?: number\n    cy?: number\n    w?: number\n    h?: number\n    x2?: number\n    y2?: number\n  }\n\n  class Box implements BoxLike {\n    height: number\n    width: number\n    y: number\n    x: number\n    cx: number\n    cy: number\n    w: number\n    h: number\n    x2: number\n    y2: number\n\n    constructor()\n    constructor(source: string)\n    constructor(source: number[])\n    constructor(source: DOMRect)\n    constructor(x: number, y: number, width: number, height: number)\n\n    merge(box: BoxLike): Box\n    transform(m: Matrix): Box\n    addOffset(): this\n    toString(): string\n    toArray(): number[]\n    isNulled(): boolean\n    to(v: MorphValueLike): Morphable\n  }\n\n  // Morphable.js\n  type MorphValueLike =\n    | string\n    | number\n    | objectBag\n    | NonMorphable\n    | MatrixExtract\n    | Array<any>\n    | any[]\n\n  class Morphable {\n    constructor()\n    constructor(st: Stepper)\n\n    from(): MorphValueLike\n    from(v: MorphValueLike): this\n    to(): MorphValueLike\n    to(v: MorphValueLike): this\n    type(): any\n    type(t: any): this\n    stepper(): Stepper\n    stepper(st: Stepper): this\n    done(): boolean\n    at(pos: number): any\n  }\n\n  class objectBag {\n    constructor()\n    constructor(a: object)\n    valueOf(): object\n    toArray(): object[]\n\n    to(a: object): Morphable\n    fromArray(a: any[]): this\n  }\n\n  class NonMorphable {\n    constructor(a: object)\n    valueOf(): object\n    toArray(): object[]\n\n    to(a: object): Morphable\n    fromArray(a: object): this\n  }\n\n  class TransformBag {\n    constructor()\n    constructor(a: number[])\n    constructor(a: TransformData)\n    defaults: TransformData\n    toArray(): number[]\n    to(t: TransformData): Morphable\n    fromArray(t: number[]): this\n  }\n\n  interface Stepper {\n    done(c?: object): boolean\n  }\n\n  class Ease implements Stepper {\n    constructor()\n    constructor(fn: string)\n    constructor(fn: Function)\n\n    step(from: number, to: number, pos: number): number\n    done(): boolean\n  }\n\n  class Controller implements Stepper {\n    constructor(fn?: Function)\n    step(current: number, target: number, dt: number, c: number): number\n    done(c?: object): boolean\n  }\n\n  // Queue.js\n  interface QueueParam {\n    value: any\n    next?: any\n    prev?: any\n  }\n\n  class Queue {\n    constructor()\n\n    push(value: any): QueueParam\n    shift(): any\n    first(): number\n    last(): number\n    remove(item: QueueParam): void\n  }\n\n  // Timeline.js\n  interface ScheduledRunnerInfo {\n    start: number\n    duration: number\n    end: number\n    runner: Runner\n  }\n\n  class Timeline extends EventTarget {\n    constructor()\n    constructor(fn: Function)\n\n    active(): boolean\n    schedule(runner: Runner, delay?: number, when?: string): this\n    schedule(): ScheduledRunnerInfo[]\n    unschedule(runner: Runner): this\n    getEndTime(): number\n    updateTime(): this\n    persist(dtOrForever?: number | boolean): this\n    play(): this\n    pause(): this\n    stop(): this\n    finish(): this\n    speed(speed: number): this\n    reverse(yes: boolean): this\n    seek(dt: number): this\n    time(): number\n    time(time: number): this\n    source(): Function\n    source(fn: Function): this\n  }\n\n  // Runner.js\n  interface TimesParam {\n    duration: number\n    delay: number\n    when: number | string\n    swing: boolean\n    wait: number\n    times: number\n  }\n\n  type TimeLike = number | TimesParam | Stepper\n\n  type EasingCallback = (...any: any) => number\n  type EasingLiteral = '<>' | '-' | '<' | '>'\n\n  class Runner {\n    constructor()\n    constructor(options: Function)\n    constructor(options: number)\n    constructor(options: Controller)\n\n    static sanitise: (\n      duration?: TimeLike,\n      delay?: number,\n      when?: string\n    ) => object\n\n    element(): Element\n    element(el: Element): this\n    timeline(): Timeline\n    timeline(timeline: Timeline): this\n    animate(duration?: TimeLike, delay?: number, when?: string): this\n    schedule(delay: number, when?: string): this\n    schedule(timeline: Timeline, delay?: number, when?: string): this\n    unschedule(): this\n    loop(times?: number, swing?: boolean, wait?: number): this\n    loop(times: TimesParam): this\n    delay(delay: number): this\n\n    during(fn: Function): this\n    queue(\n      initFn: Function,\n      runFn: Function,\n      retargetFn?: boolean | Function,\n      isTransform?: boolean\n    ): this\n    after(fn: EventListener): this\n    time(): number\n    time(time: number): this\n    duration(): number\n    loops(): number\n    loops(p: number): this\n    persist(dtOrForever?: number | boolean): this\n    position(): number\n    position(p: number): this\n    progress(): number\n    progress(p: number): this\n    step(deta?: number): this\n    reset(): this\n    finish(): this\n    reverse(r?: boolean): this\n    ease(fn: EasingCallback): this\n    ease(kind: EasingLiteral): this\n    active(): boolean\n    active(a: boolean): this\n    addTransform(m: Matrix): this\n    clearTransform(): this\n    clearTransformsFromQueue(): void\n\n    // extends prototypes\n    attr(a: string | object, v?: string): this\n    css(s: string | object, v?: string): this\n    styleAttr(type: string, name: string | object, val?: string): this\n    zoom(level: NumberAlias, point?: Point): this\n    transform(\n      transforms: MatrixTransformParam,\n      relative?: boolean,\n      affine?: boolean\n    ): this\n    x(x: number): this\n    y(y: number): this\n    ax(x: number): this\n    ay(y: number): this\n    dx(dx: number): this\n    dy(dy: number): this\n    cx(x: number): this\n    cy(y: number): this\n    dmove(dx: number, dy: number): this\n    move(x: number, y: number): this\n    amove(x: number, y: number): this\n    center(x: number, y: number): this\n    size(width: number, height: number): this\n    width(width: number): this\n    height(height: number): this\n    plot(a: object): this\n    plot(a: number, b: number, c: number, d: number): this\n    leading(value: number): this\n    viewbox(x: number, y: number, width: number, height: number): this\n    update(offset: number, color: number, opacity: number): this\n    update(o: StopProperties): this\n    rx(): number\n    rx(rx: number): this\n    ry(): number\n    ry(ry: number): this\n    from(x: NumberAlias, y: NumberAlias): this\n    to(x: NumberAlias, y: NumberAlias): this\n\n    fill(): string\n    fill(fill: FillData): this\n    fill(color: string): this\n    fill(pattern: Element): this\n    fill(image: Image): this\n    stroke(): string\n    stroke(stroke: StrokeData): this\n    stroke(color: string): this\n    matrix(): Matrix\n    matrix(\n      a: number,\n      b: number,\n      c: number,\n      d: number,\n      e: number,\n      f: number\n    ): this\n    matrix(mat: MatrixAlias): this\n    rotate(degrees: number, cx?: number, cy?: number): this\n    skew(skewX?: number, skewY?: number, cx?: number, cy?: number): this\n    scale(scaleX?: number, scaleY?: number, cx?: number, cy?: number): this\n    translate(x: number, y: number): this\n    shear(lam: number, cx: number, cy: number): this\n    relative(x: number, y: number): this\n    flip(direction?: string, around?: number): this\n    flip(around: number): this\n    opacity(): number\n    opacity(value: number): this\n    font(a: string): string\n    font(a: string, v: string | number): this\n    font(a: object): this\n  }\n\n  // Animator.js\n  let Animator: {\n    nextDraw: any\n    frames: Queue\n    timeouts: Queue\n    immediates: Queue\n\n    timer(): boolean\n    frame(fn: Function): object\n    timeout(fn: Function, delay?: number): object\n    immediate(fn: Function): object\n    cancelFrame(o: object): void\n    clearTimeout(o: object): void\n    cancelImmediate(o: object): void\n  }\n\n  /**\n   * Just fancy type alias to refer to css query selector.\n   */\n  type QuerySelector = string\n\n  class Dom extends EventTarget {\n    node: HTMLElement | SVGElement\n    type: string\n\n    constructor(node?: HTMLElement, attr?: object)\n    constructor(att: object)\n    add(element: Element, i?: number): this\n    addTo(parent: Dom | HTMLElement | string, i?: number): this\n    children(): List<Element>\n    clear(): this\n    clone(deep?: boolean, assignNewIds?: boolean): this\n    each(\n      block: (index: number, children: Element[]) => void,\n      deep?: boolean\n    ): this\n    element(element: string, inherit?: object): this\n    first(): Element\n    get(i: number): Element\n    getEventHolder(): LinkedHTMLElement\n    getEventTarget(): LinkedHTMLElement\n    has(element: Element): boolean\n    id(): string\n    id(id: string): this\n    index(element: Element): number\n    last(): Element\n    matches(selector: string): boolean\n    /**\n     * Finds the closest ancestor which matches the string or is of passed type. If nothing is passed, the parent is returned\n     * @param type can be either string, svg.js object or undefined.\n     */\n    parent(type?: ElementTypeAlias | QuerySelector): Dom | null\n    put(element: Element, i?: number): Element\n    /**\n     * Put the element into the given parent element and returns the parent element\n     * @param parent The parent in which the current element is inserted\n     */\n    putIn(parent: ElementAlias | Node | QuerySelector): Dom\n\n    remove(): this\n    removeElement(element: Element): this\n    replace<T extends Dom>(element: T): T\n    round(precision?: number, map?: string[]): this\n    svg(): string\n    svg(a: string, outer: true): Element\n    svg(a: string, outer?: false): this\n    svg(a: boolean, outer?: boolean): string\n    svg(a: null | Function, outer?: boolean): string\n\n    toString(): string\n    words(text: string): this\n    writeDataToDom(): this\n\n    // prototype extend Attribute in attr.js\n    /**\n     * Get the attribute object of SVG Element. The return object will be vary based on\n     * the instance itself. For example, G element will only return GlobalAttr where Rect\n     * will return RectAttr instead.\n     */\n    attr(): any\n    /**\n     * Add or update the attribute from the SVG Element. To remove the attribute from the element set value to null\n     * @param name name of attribute\n     * @param value value of attribute can be string or number or null\n     * @param namespace optional string that define namespace\n     */\n    attr(name: string, value: any, namespace?: string): this\n    attr(name: string): any\n    attr(obj: object): this\n    attr(obj: string[]): object\n\n    // prototype extend Selector in selector.js\n    find(query: string): List<Element>\n    findOne(query: string): Dom | null\n\n    // prototype method register in data.js\n    data(a: string): object | string | number\n    data(a: string, v: object, substain?: boolean): this\n    data(a: object): this\n\n    // prototype method register in arrange.js\n    siblings(): List<Element>\n    position(): number\n    next(): Element\n    prev(): Element\n    forward(): this\n    backward(): this\n    front(): this\n    back(): this\n    before(el: Element): Element\n    after(el: Element): Element\n    insertBefore(el: Element): this\n    insertAfter(el: Element): this\n\n    // prototype method register in class.js\n    classes(): string[]\n    hasClass(name: string): boolean\n    addClass(name: string): this\n    removeClass(name: string): this\n    toggleClass(name: string): this\n\n    // prototype method register in css.js\n    css(): Partial<CSSStyleDeclarationWithVars>\n    css<T extends CSSStyleName>(style: T): CSSStyleDeclarationWithVars[T]\n    css<T extends CSSStyleName[]>(\n      style: T\n    ): Partial<CSSStyleDeclarationWithVars>\n    css<T extends CSSStyleName>(\n      style: T,\n      val: CSSStyleDeclarationWithVars[T]\n    ): this\n    css(style: Partial<CSSStyleDeclarationWithVars>): this\n    show(): this\n    hide(): this\n    visible(): boolean\n\n    // memory.js\n    remember(name: string, value: any): this\n    remember(name: string): any\n    remember(obj: object): this\n    forget(...keys: string[]): this\n    forget(): this\n    memory(): object\n\n    addEventListener(): void\n    dispatch(event: Event | string, data?: object): Event\n    dispatchEvent(event: Event): boolean\n    fire(event: Event | string, data?: object): this\n    getEventHolder(): this | Node\n    getEventTarget(): this | Node\n\n    // on(events: string | Event[], cb: EventListener, binbind?: any, options?: AddEventListenerOptions): this;\n    // off(events?: string | Event[], cb?: EventListener | number): this;\n    removeEventListener(): void\n  }\n\n  // clip.js\n  class ClipPath extends Container {\n    constructor()\n    constructor(node?: SVGClipPathElement)\n    constructor(attr: object)\n    node: SVGClipPathElement\n\n    targets(): List<Element>\n    remove(): this\n  }\n\n  // container.js\n  interface ViewBoxLike {\n    x: number\n    y: number\n    width: number\n    height: number\n  }\n\n  class Containable {\n    circle(size?: NumberAlias): Circle\n    clip(): ClipPath\n    ellipse(width?: number, height?: number): Ellipse\n    foreignObject(width: number, height: number): ForeignObject\n    gradient(type: string, block?: (stop: Gradient) => void): Gradient\n    group(): G\n\n    image(): Image\n    image(href?: string, callback?: (e: Event) => void): Image\n    line(points?: PointArrayAlias): Line\n    line(x1: number, y1: number, x2: number, y2: number): Line\n    link(url: string): A\n    marker(\n      width?: number,\n      height?: number,\n      block?: (marker: Marker) => void\n    ): Marker\n    mask(): Mask\n    nested(): Svg\n    path(): Path\n    path(d: PathArrayAlias): Path\n    pattern(\n      width?: number,\n      height?: number,\n      block?: (pattern: Pattern) => void\n    ): Pattern\n    plain(text: string): Text\n    polygon(points?: PointArrayAlias): Polygon\n    polyline(points?: PointArrayAlias): Polyline\n    rect(width?: NumberAlias, height?: NumberAlias): Rect\n    style(): Style\n    text(block: (tspan: Tspan) => void): Text\n    text(text: string): Text\n    use(element: Element | string, file?: string): Use\n    viewbox(): Box\n    viewbox(viewbox: ViewBoxLike | string): this\n    viewbox(x: number, y: number, width: number, height: number): this\n    textPath(text: string | Text, path: string | Path): TextPath\n    symbol(): Symbol\n    zoom(): number\n    zoom(level: NumberAlias, point?: Point): this\n  }\n\n  type DynamicExtends<T extends {}> = {\n    new (...args: any[]): Containable & T\n  }\n\n  /**\n   * only for declaration purpose. actually cannot be used.\n   */\n  let ContainableDom: DynamicExtends<Dom>\n  class Fragment extends ContainableDom {\n    constructor(node?: Node)\n  }\n\n  /**\n   * only for declaration purpose. actually cannot be used.\n   */\n  let ContainableElement: DynamicExtends<Element>\n  class Container extends ContainableElement {\n    constructor()\n    flatten(parent: Dom, depth?: number): this\n    ungroup(parent: Dom, depth?: number): this\n  }\n\n  class Defs extends Container {\n    constructor(node?: SVGDefsElement)\n    node: SVGDefsElement\n    marker(\n      width?: number,\n      height?: number,\n      block?: (marker: Marker) => void\n    ): Marker\n  }\n\n  class Svg extends Container {\n    constructor(svgElement?: SVGSVGElement)\n    constructor(id: string)\n    node: SVGSVGElement\n    namespace(): this\n    defs(): Defs\n    remove(): this\n    isRoot(): boolean\n  }\n\n  type EventHandler<T extends Event> = (event: T) => void\n\n  interface Sugar {\n    fill(): string\n    fill(fill: FillData): this\n    fill(color: string): this\n    fill(pattern: Element): this\n    fill(image: Image): this\n    stroke(): string\n    stroke(stroke: StrokeData): this\n    stroke(color: string): this\n    matrix(): Matrix\n    matrix(\n      a: number,\n      b: number,\n      c: number,\n      d: number,\n      e: number,\n      f: number\n    ): this\n    matrix(mat: MatrixAlias): this\n    rotate(degrees: number, cx?: number, cy?: number): this\n    skew(skewX?: number, skewY?: number, cx?: number, cy?: number): this\n    scale(scaleX?: number, scaleY?: number, cx?: number, cy?: number): this\n    translate(x: number, y: number): this\n    shear(lam: number, cx: number, cy: number): this\n    relative(x: number, y: number): this\n    flip(direction?: string, around?: number): this\n    flip(around: number): this\n    opacity(): number\n    opacity(value: number): this\n    font(a: string): string\n    font(a: string, v: string | number): this\n    font(a: object): this\n\n    click(cb: EventHandler<MouseEvent>): this\n    dblclick(cb: EventHandler<MouseEvent>): this\n    mousedown(cb: EventHandler<MouseEvent>): this\n    mouseup(cb: EventHandler<MouseEvent>): this\n    mouseover(cb: EventHandler<MouseEvent>): this\n    mouseout(cb: EventHandler<MouseEvent>): this\n    mousemove(cb: EventHandler<MouseEvent>): this\n    mouseenter(cb: EventHandler<MouseEvent>): this\n    mouseleave(cb: EventHandler<MouseEvent>): this\n    touchstart(cb: EventHandler<TouchEvent>): this\n    touchmove(cb: EventHandler<TouchEvent>): this\n    touchleave(cb: EventHandler<TouchEvent>): this\n    touchend(cb: EventHandler<TouchEvent>): this\n    touchcancel(cb: EventHandler<TouchEvent>): this\n    contextmenu(cb: EventHandler<MouseEvent>): this\n    wheel(cb: EventHandler<WheelEvent>): this\n    pointerdown(cb: EventHandler<PointerEvent>): this\n    pointermove(cb: EventHandler<PointerEvent>): this\n    pointerup(cb: EventHandler<PointerEvent>): this\n    pointerleave(cb: EventHandler<PointerEvent>): this\n    pointercancel(cb: EventHandler<PointerEvent>): this\n  }\n\n  // Symbol.js\n  class Symbol extends Container {\n    constructor(svgElement?: SVGSymbolElement)\n    constructor(attr: object)\n    node: SVGSymbolElement\n  }\n\n  class Element extends Dom implements Sugar {\n    constructor(node?: SVGElement)\n    constructor(attr: object)\n    node: SVGElement\n    type: string\n    dom: any\n\n    addClass(name: string): this\n    after(element: Element): Element\n    animate(duration?: TimeLike, delay?: number, when?: string): Runner\n    delay(by: number, when?: string): Runner\n    attr(): any\n    attr(name: string, value: any, namespace?: string): this\n    attr(name: string): any\n    attr(obj: string[]): object\n    attr(obj: object): this\n    back(): this\n    backward(): this\n    bbox(): Box\n    before(element: Element): Element\n    center(x: number, y: number): this\n    classes(): string[]\n    clipper(): ClipPath\n    clipWith(element: Element): this\n    clone(deep?: boolean, assignNewIds?: boolean): this\n    ctm(): Matrix\n    cx(): number\n    cx(x: number): this\n    cy(): number\n    cy(y: number): this\n    data(name: string, value: any, sustain?: boolean): this\n    data(name: string): any\n    data(val: object): this\n    defs(): Defs\n    dmove(x: NumberAlias, y: NumberAlias): this\n    dx(x: NumberAlias): this\n    dy(y: NumberAlias): this\n    event(): Event | CustomEvent\n\n    fire(event: Event): this\n    fire(event: string, data?: any): this\n    forget(...keys: string[]): this\n    forget(): this\n    forward(): this\n    front(): this\n    hasClass(name: string): boolean\n    height(): NumberAlias\n    height(height: NumberAlias): this\n    hide(): this\n    hide(): this\n    id(): string\n    id(id: string): this\n    inside(x: number, y: number): boolean\n    is(cls: any): boolean\n    linkTo(url: (link: A) => void): A\n    linkTo(url: string): A\n    masker(): Mask\n    maskWith(element: Element): this\n    maskWith(mask: Mask): this\n    matches(selector: string): boolean\n    matrixify(): Matrix\n    memory(): object\n    move(x: NumberAlias, y: NumberAlias): this\n    native(): LinkedHTMLElement\n    next(): Element\n    // off(events?: string | Event[], cb?: EventListener | number): this;\n    // on(event: string, cb: Function, context?: object): this;\n    toRoot(): Svg\n    /**\n     * By default parents will return a list of elements up until the root svg.\n     */\n    parents(): List<Element>\n    /**\n     * List the parent by hierarchy until the given parent type or matcher. If the given value is null\n     * then the result is only provided the list up until Svg root element which mean no Dom parent element is included.\n     * @param util a parent type\n     */\n    parents<T extends Dom>(util: QuerySelector | T | null): List<Element>\n    /**\n     * Get reference svg element based on the given attribute.\n     * @param attr a svg attribute\n     */\n    reference<R extends Dom>(attr: AttributeReference): R | null\n\n    point(): Point\n    point(position: CoordinateXY): Point\n    point(point: Point): Point\n    point(x: number, y: number): Point\n    position(): number\n    prev(): Element\n    rbox(element?: Element): Box\n    reference(type: string): Element\n    remember(name: string, value: any): this\n    remember(name: string): any\n    remember(obj: object): this\n    remove(): this\n    removeClass(name: string): this\n    root(): Svg\n    screenCTM(): Matrix\n    setData(data: object): this\n    show(): this\n    show(): this\n    size(width?: NumberAlias, height?: NumberAlias): this\n    stop(jumpToEnd: boolean, clearQueue: boolean): Animation\n    stop(\n      offset?: NumberAlias | string,\n      color?: NumberAlias,\n      opacity?: NumberAlias\n    ): Stop\n    stop(val: {\n      offset?: NumberAlias | string\n      color?: NumberAlias\n      opacity?: NumberAlias\n    }): Stop\n    timeline(): Timeline\n    timeline(tl: Timeline): this\n    toggleClass(name: string): this\n    toParent(parent: Dom): this\n    toParent(parent: Dom, i: number): this\n    toSvg(): this\n    transform(): MatrixExtract\n    transform(t: MatrixAlias, relative?: boolean): this\n    unclip(): this\n    unmask(): this\n    untransform(): this\n    visible(): boolean\n    width(): NumberAlias\n    width(width: NumberAlias): this\n    x(): NumberAlias\n    x(x: NumberAlias): this\n    y(): NumberAlias\n    y(y: NumberAlias): this\n\n    fill(): string\n    fill(fill: FillData): this\n    fill(color: string): this\n    fill(pattern: Element): this\n    fill(image: Image): this\n    stroke(): string\n    stroke(stroke: StrokeData): this\n    stroke(color: string): this\n    matrix(): Matrix\n    matrix(\n      a: number,\n      b: number,\n      c: number,\n      d: number,\n      e: number,\n      f: number\n    ): this\n    matrix(mat: MatrixAlias): this\n    rotate(degrees: number, cx?: number, cy?: number): this\n    skew(skewX?: number, skewY?: number, cx?: number, cy?: number): this\n    scale(scaleX?: number, scaleY?: number, cx?: number, cy?: number): this\n    translate(x: number, y: number): this\n    shear(lam: number, cx: number, cy: number): this\n    relative(x: number, y: number): this\n    flip(direction?: string, around?: number): this\n    flip(around: number): this\n    opacity(): number\n    opacity(value: number): this\n    font(a: string): string\n    font(a: string, v: string | number): this\n    font(a: object): this\n\n    click(cb: EventHandler<MouseEvent>): this\n    dblclick(cb: EventHandler<MouseEvent>): this\n    mousedown(cb: EventHandler<MouseEvent>): this\n    mouseup(cb: EventHandler<MouseEvent>): this\n    mouseover(cb: EventHandler<MouseEvent>): this\n    mouseout(cb: EventHandler<MouseEvent>): this\n    mousemove(cb: EventHandler<MouseEvent>): this\n    mouseenter(cb: EventHandler<MouseEvent>): this\n    mouseleave(cb: EventHandler<MouseEvent>): this\n    touchstart(cb: EventHandler<TouchEvent>): this\n    touchmove(cb: EventHandler<TouchEvent>): this\n    touchleave(cb: EventHandler<TouchEvent>): this\n    touchend(cb: EventHandler<TouchEvent>): this\n    touchcancel(cb: EventHandler<TouchEvent>): this\n    contextmenu(cb: EventHandler<MouseEvent>): this\n    wheel(cb: EventHandler<WheelEvent>): this\n    pointerdown(cb: EventHandler<PointerEvent>): this\n    pointermove(cb: EventHandler<PointerEvent>): this\n    pointerup(cb: EventHandler<PointerEvent>): this\n    pointerleave(cb: EventHandler<PointerEvent>): this\n    pointercancel(cb: EventHandler<PointerEvent>): this\n  }\n\n  // ellipse.js\n  interface CircleMethods extends Shape {\n    rx(rx: number): this\n    rx(): this\n    ry(ry: number): this\n    ry(): this\n    radius(x: number, y?: number): this\n  }\n  class Circle extends Shape implements CircleMethods {\n    constructor(node?: SVGCircleElement)\n    constructor(attr: CircleAttr)\n\n    node: SVGCircleElement\n\n    rx(rx: number): this\n    rx(): this\n    ry(ry: number): this\n    ry(): this\n    radius(x: number, y?: number): this\n  }\n  class Ellipse extends Shape implements CircleMethods {\n    node: SVGEllipseElement\n    constructor(attr: EllipseAttr)\n    constructor(node?: SVGEllipseElement)\n\n    rx(rx: number): this\n    rx(): this\n    ry(ry: number): this\n    ry(): this\n    radius(x: number, y?: number): this\n  }\n\n  interface StopProperties {\n    color?: ColorAlias\n    offset?: number | string\n    opacity?: number\n  }\n\n  // gradient.js\n  class Stop extends Element {\n    update(offset?: number, color?: ColorAlias, opacity?: number): this\n    update(opts: StopProperties): this\n  }\n  class Gradient extends Container {\n    constructor(node?: SVGGradientElement)\n    constructor(attr: object)\n    constructor(type: string)\n    node: SVGGradientElement\n\n    at(offset?: number, color?: ColorAlias, opacity?: number): Stop\n    at(opts: StopProperties): Stop\n    url(): string\n    toString(): string\n    targets(): List<Element>\n    bbox(): Box\n\n    // gradiented.js\n    from(x: number, y: number): this\n    to(x: number, y: number): this\n\n    // TODO: check with main.js\n    radius(x: number, y?: number): this\n    targets(): List<Element>\n    bbox(): Box\n    update(block?: (gradient: Gradient) => void): this\n  }\n\n  // group.js\n  class G extends Container {\n    constructor(node?: SVGGElement)\n    constructor(attr: object)\n    node: SVGGElement\n  }\n\n  // hyperlink.js\n  class A extends Container {\n    constructor(node?: SVGAElement)\n    constructor(attr: object)\n    node: SVGAElement\n    to(url: string): this\n    to(): string\n    target(target: string): this\n    target(): string\n  }\n\n  // ForeignObject.js\n  class ForeignObject extends Element {\n    constructor(node?: SVGForeignObjectElement, attrs?: object)\n    constructor(attrs?: object)\n    add(element: Dom, i?: number): this\n  }\n\n  // image.js\n  class Image extends Shape {\n    constructor(node?: SVGImageElement)\n    constructor(attr: object)\n    node: SVGImageElement\n    load(url?: string, callback?: (event: Event) => void): this\n  }\n\n  // line.js\n  type PointArrayAlias = number[] | ArrayXY[] | PointArray | string\n\n  class Line extends Shape {\n    constructor(attr: LineAttr)\n    constructor(node?: SVGLineElement)\n\n    node: SVGLineElement\n\n    array(): PointArray\n    plot(): PointArray\n    plot(points?: PointArrayAlias): this\n    plot(x1: number, y1: number, x2: number, y2: number): this\n    move(x: number, y: number): this\n    size(width?: number, height?: number): this\n    marker(\n      position: string,\n      width?: number,\n      height?: number,\n      block?: (marker: Marker) => void\n    ): Marker\n    marker(position: string, marker: Marker): Marker\n  }\n\n  // marker.js\n  // TODO: check register method marker\n  class Marker extends Container {\n    constructor()\n\n    node: SVGMarkerElement\n\n    ref(x: string | number, y: string | number): this\n    update(block: (marker: Marker) => void): this\n    toString(): string\n    orient(orientation: 'auto' | 'auto-start-reverse' | number | Number): this\n    orient(): string\n  }\n  // mask.js\n  class Mask extends Container {\n    constructor(node?: SVGMaskElement)\n    constructor(attr: object)\n    node: SVGMaskElement\n    remove(): this\n    targets(): List<Element>\n  }\n\n  // path.js\n  class Path extends Shape {\n    constructor(attr: PathAttr)\n    constructor(node?: SVGPathElement)\n\n    node: SVGPathElement\n\n    morphArray: PathArray\n    array(): PathArray\n    plot(): PathArray\n    plot(d: PathArrayAlias): this\n    marker(\n      position: string,\n      width?: number,\n      height?: number,\n      block?: (marker: Marker) => void\n    ): this\n    marker(position: string, marker: Marker): this\n\n    // sugar.js\n    length(): number\n    pointAt(length: number): { x: number; y: number }\n    text(text: string): TextPath\n    text(text: Text): TextPath\n    targets(): List<Element>\n  }\n\n  // pattern.js\n  class Pattern extends Container {\n    url(): string\n    url(...rest: any[]): never\n    update(block: (pattern: Pattern) => void): this\n    toString(): string\n  }\n\n  // poly.js\n  interface poly {\n    array(): PointArray\n    plot(): PointArray\n    plot(p: PointArrayAlias): this\n    clear(): this\n    move(x: number, y: number): this\n    size(width: number, height?: number): this\n  }\n\n  // pointed.js\n  interface pointed {\n    x(): number\n    x(x: number): this\n    y(): number\n    y(y: number): this\n    height(): number\n    height(h: number): this\n    width(): number\n    width(w: number): this\n  }\n\n  class Polyline extends Shape implements poly, pointed {\n    constructor(node?: SVGPolylineElement)\n    constructor(attr: PolyAttr)\n\n    node: SVGPolylineElement\n\n    array(): PointArray\n    plot(): PointArray\n    plot(p: PointArrayAlias): this\n    x(): number\n    x(x: number): this\n    y(): number\n    y(y: number): this\n    height(): number\n    height(h: number): this\n    width(): number\n    width(w: number): this\n    move(x: number, y: number): this\n    size(width: number, height?: number): this\n    marker(\n      position: string,\n      width?: number,\n      height?: number,\n      block?: (marker: Marker) => void\n    ): Marker\n    marker(position: string, marker: Marker): Marker\n  }\n\n  class Polygon extends Shape implements poly, pointed {\n    constructor(node?: SVGPolygonElement)\n    constructor(attr: PolyAttr)\n\n    node: SVGPolygonElement\n    array(): PointArray\n    plot(): PointArray\n    plot(p: PointArrayAlias): this\n    x(): number\n    x(x: number): this\n    y(): number\n    y(y: number): this\n    height(): number\n    height(h: number): this\n    width(): number\n    width(w: number): this\n    move(x: number, y: number): this\n    size(width: number, height?: number): this\n    marker(\n      position: string,\n      width?: number,\n      height?: number,\n      block?: (marker: Marker) => void\n    ): Marker\n    marker(position: string, marker: Marker): Marker\n  }\n\n  class Rect extends Shape {\n    constructor(node?: SVGRectElement)\n    constructor(attr: RectAttr)\n    node: SVGRectElement\n    radius(x: number, y?: number): this\n  }\n\n  // shape.js\n  class Shape extends Element {}\n\n  // sugar.js\n  interface StrokeData {\n    color?: string\n    width?: number\n    opacity?: number\n    linecap?: string\n    linejoin?: string\n    miterlimit?: number\n    dasharray?: string\n    dashoffset?: number\n  }\n\n  interface FillData {\n    color?: string\n    opacity?: number\n    rule?: string\n  }\n\n  interface FontData {\n    family?: string\n    size?: NumberAlias\n    anchor?: string\n    leading?: NumberAlias\n    weight?: string\n    style?: string\n  }\n  // textable.js\n  interface Textable {\n    plain(text: string): this\n    length(): number\n  }\n\n  // text.js\n  class Text extends Shape implements Textable {\n    constructor(node?: SVGElement)\n    constructor(attr: TextAttr)\n\n    clone(): this\n    text(): string\n    text(text: string): this\n    text(block: (text: this) => void): this\n    leading(): Number\n    leading(leading: NumberAlias): this\n    rebuild(enabled: boolean): this\n    build(enabled: boolean): this\n    clear(): this\n    plain(text: string): this\n    length(): number\n    get(i: number): Tspan\n    path(): TextPath\n    path(d: PathArrayAlias | Path): TextPath\n    track(): Element\n    ax(): string\n    ax(x: string): this\n    ay(): string\n    ay(y: string): this\n    amove(x: number, y: number): this\n    textPath(): TextPath\n\n    // main.js, from extend/copy prototypes from Tspan\n    tspan(text: string): Tspan\n    tspan(block: (tspan: Tspan) => void): this\n  }\n\n  class Tspan extends Text implements Textable {\n    constructor(node?: SVGElement)\n    constructor(attr: TextAttr)\n    dx(): number\n    dx(x: NumberAlias): this\n    dy(): number\n    dy(y: NumberAlias): this\n    newLine(): this\n    tspan(text: string): Tspan\n    tspan(block: (tspan: Tspan) => void): this\n    length(): number\n    text(): string\n    text(text: string): this\n    text(block: (text: this) => void): this\n    plain(text: string): this\n  }\n\n  // textpath.js\n  class TextPath extends Text {\n    constructor()\n    constructor(attr: TextPathAttr)\n\n    array(): Array<any>\n    plot(): PathArray\n    plot(d: string): this\n    track(): Path\n  }\n\n  // style.js\n  class Style extends Element {\n    constructor(node: SVGElement, attr?: StylingAttr)\n    addText(text: string): this\n    font(a: object): this\n    font(a: string, v: string | number): this\n    font(a: string): string\n    rule(selector: string, obj: any): this\n  }\n\n  // use.js\n  class Use extends Shape {\n    use(element: string, file?: string): this\n  }\n\n  // viewbox.js\n  type ViewBoxAlias = ViewBoxLike | number[] | string | Element\n\n  interface ViewBox {\n    x: number\n    y: number\n    width: number\n    height: number\n    toString(): string\n    at(pos: number): ViewBox\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{}\n"
  }
]