diff --git a/.config/karma.conf.cjs b/.config/karma.conf.cjs new file mode 100644 index 000000000..7584c9265 --- /dev/null +++ b/.config/karma.conf.cjs @@ -0,0 +1,88 @@ +// Karma configuration +const karmaCommon = require('./karma.conf.common.cjs') + +let chromeBin = 'ChromeHeadless' +if (process.platform === 'linux') { + // We need to choose either Chrome or Chromium. + // Canary is not available on linux. + // If we do not find Chromium then we can deduce that + // either Chrome is installed or there is no Chrome variant at all, + // in which case karma-chrome-launcher will output an error. + // If `which` finds nothing it will throw an error. + const { execSync } = require('child_process') + + try { + if (execSync('which chromium-browser')) chromeBin = 'ChromiumHeadless' + } catch (e) {} +} + +module.exports = function (config) { + config.set( + Object.assign(karmaCommon(config), { + files: [ + 'spec/RAFPlugin.js', + { + pattern: 'spec/fixtures/fixture.css', + included: false, + served: true + }, + { + pattern: 'spec/fixtures/pixel.png', + included: false, + served: true + }, + { + pattern: 'src/**/*.js', + included: false, + served: true, + type: 'modules' + }, + { + pattern: 'spec/helpers.js', + included: false, + served: true, + type: 'module' + }, + { + pattern: 'spec/setupBrowser.js', + included: true, + type: 'module' + }, + { + pattern: 'spec/spec/*/**/*.js', + included: true, + type: 'module' + } + ], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'src/**/*.js': ['coverage'] + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress', 'coverage'], + coverageReporter: { + // Specify a reporter type. + type: 'lcov', + dir: 'coverage/', + subdir: function (browser) { + // normalization process to keep a consistent browser name accross different OS + return browser.toLowerCase().split(/[ /-]/)[0] // output the results into: './coverage/firefox/' + }, + instrumenterOptions: { + istanbul: { + esModules: true + } + } + }, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: [chromeBin, 'FirefoxHeadless'] + }) + ) +} diff --git a/.config/karma.quick.js b/.config/karma.conf.common.cjs similarity index 54% rename from .config/karma.quick.js rename to .config/karma.conf.common.cjs index 457470705..480899652 100644 --- a/.config/karma.quick.js +++ b/.config/karma.conf.common.cjs @@ -1,82 +1,67 @@ -// Karma configuration -// Generated on Tue Oct 04 2016 13:53:46 GMT+0200 (CEST) +// Karma shared configuration -module.exports = function(config) { - config.set({ +const os = require('os') +const cpuCount = os.cpus().length +module.exports = function (config) { + return { // base path that will be used to resolve all patterns (eg. files, exclude) basePath: '../', - // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['jasmine'], - // list of files / patterns to load in the browser files: [ '.config/pretest.js', + 'spec/RAFPlugin.js', + { + pattern: 'spec/fixtures/fixture.css', + included: false, + served: true + }, { - pattern: 'spec/fixture.css', + pattern: 'spec/fixtures/fixture.svg', included: false, served: true }, { - pattern: 'spec/fixture.svg', + pattern: 'spec/fixtures/pixel.png', included: false, served: true }, 'dist/svg.js', - 'spec/spec/**/*.js' + 'spec/spec/*.js' ], - - // list of files to exclude - exclude: [], - - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: {}, - - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress'], - - - // configure the coverage reporter - coverageReporter: {}, - + proxies: { + '/fixtures/': '/base/spec/fixtures/', + '/spec/': '/base/spec/' + }, // web server port - port: 9875, - + port: 9876, // enable / disable colors in the output (reporters and logs) colors: true, - // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_ERROR, - + logLevel: config.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes autoWatch: false, - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['PhantomJS'], - // Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: true, // Concurrency level // how many browser should be started simultaneous - concurrency: 1 - }) + concurrency: cpuCount || Infinity, + + // list of files to exclude + exclude: [] + } } diff --git a/.config/karma.conf.js b/.config/karma.conf.js deleted file mode 100644 index 42ceb393c..000000000 --- a/.config/karma.conf.js +++ /dev/null @@ -1,101 +0,0 @@ -// Karma configuration -// Generated on Tue Oct 04 2016 13:53:46 GMT+0200 (CEST) - -module.exports = function(config) { - config.set({ - - // base path that will be used to resolve all patterns (eg. files, exclude) - basePath: '../', - - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['jasmine'], - - - // list of files / patterns to load in the browser - files: [ - '.config/pretest.js', - { - pattern: 'spec/fixtures/fixture.css', - included: false, - served: true - }, - { - pattern: 'spec/fixtures/fixture.svg', - included: false, - served: true - }, - { - pattern: 'spec/fixtures/pixel.png', - included: false, - served: true - }, - 'dist/svg.js', - 'spec/spec/**/*.js' - ], - - proxies: { - '/fixtures/': '/base/spec/fixtures/' - }, - - - // list of files to exclude - exclude: [], - - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - 'dist/svg.js': ['coverage'] - }, - - - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['progress', 'coverage'], - - - // configure the coverage reporter - coverageReporter: { - // Specify a reporter type. - type: 'lcov', - dir: 'coverage/', - subdir: function(browser) { - // normalization process to keep a consistent browser name accross different OS - return browser.toLowerCase().split(/[ /-]/)[0]; // output the results into: './coverage/firefox/' - } - }, - - - // web server port - port: 9876, - - - // enable / disable colors in the output (reporters and logs) - colors: true, - - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - logLevel: config.LOG_INFO, - - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: false, - - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['Firefox'], - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - singleRun: false, - - // Concurrency level - // how many browser should be started simultaneous - concurrency: Infinity - }) -} diff --git a/.config/karma.conf.saucelabs.cjs b/.config/karma.conf.saucelabs.cjs new file mode 100644 index 000000000..484ebeea8 --- /dev/null +++ b/.config/karma.conf.saucelabs.cjs @@ -0,0 +1,144 @@ +// Karma configuration +// https://wiki.saucelabs.com/display/DOCS/Platform+Configurator + +// TODO: remove dotenv after local test +// require('dotenv').config() + +const karmaCommon = require('./karma.conf.common.cjs') + +const SauceLabsLaunchers = { + /** Real mobile devices are not available + * Your account does not have access to Android devices. + * Please contact sales@saucelabs.com to add this feature to your account. */ + /* sl_android_chrome: { + base: 'SauceLabs', + appiumVersion: '1.5.3', + deviceName: 'Samsung Galaxy S7 Device', + deviceOrientation: 'portrait', + browserName: 'Chrome', + platformVersion: '6.0', + platformName: 'Android' + }, */ + /* sl_android: { + base: 'SauceLabs', + browserName: 'Android', + deviceName: 'Android Emulator', + deviceOrientation: 'portrait' + }, */ + SL_firefox_latest: { + base: 'SauceLabs', + browserName: 'firefox', + version: 'latest' + }, + SL_chrome_latest: { + base: 'SauceLabs', + browserName: 'chrome', + version: 'latest' + }, + SL_InternetExplorer: { + base: 'SauceLabs', + browserName: 'internet explorer', + version: '11.0' + } /* + sl_windows_edge: { + base: 'SauceLabs', + browserName: 'MicrosoftEdge', + version: 'latest', + platform: 'Windows 10' + }, + sl_macos_safari: { + base: 'SauceLabs', + browserName: 'safari', + platform: 'macOS 10.13', + version: '12.0', + recordVideo: true, + recordScreenshots: true, + screenResolution: '1024x768' + } */ /*, + sl_macos_iphone: { + base: 'SauceLabs', + browserName: 'Safari', + deviceName: 'iPhone SE Simulator', + deviceOrientation: 'portrait', + platformVersion: '10.2', + platformName: 'iOS' + } + 'SL_Chrome': { + base: 'SauceLabs', + browserName: 'chrome', + version: '48.0', + platform: 'Linux' + }, + 'SL_Firefox': { + base: 'SauceLabs', + browserName: 'firefox', + version: '50.0', + platform: 'Windows 10' + }, + 'SL_Safari': { + base: 'SauceLabs', + browserName: 'safari', + platform: 'OS X 10.11', + version: '10.0' + } */ +} + +module.exports = function (config) { + if (!process.env.SAUCE_USERNAME || !process.env.SAUCE_ACCESS_KEY) { + console.error( + 'SAUCE_USERNAME and SAUCE_ACCESS_KEY must be provided as environment variables.' + ) + console.warn('Aborting Sauce Labs test') + process.exit(1) + } + const settings = Object.assign(karmaCommon(config), { + // Concurrency level + // how many browser should be started simultaneous + // Saucelabs allow up to 5 concurrent sessions on the free open source tier. + concurrency: 5, + + // this specifies which plugins karma should load + // by default all karma plugins, starting with `karma-` will load + // so if you are really puzzled why something isn't working, then comment + // out plugins: [] - it's here to make karma load faster + // get possible karma plugins by `ls node_modules | grep 'karma-*'` + plugins: ['karma-jasmine', 'karma-sauce-launcher'], + + // logLevel: config.LOG_DEBUG, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['dots', 'saucelabs'], + + customLaunchers: SauceLabsLaunchers, + + // start these browsers + browsers: Object.keys(SauceLabsLaunchers), + sauceLabs: { + testName: 'SVG.js Unit Tests' + // connectOptions: { + // noSslBumpDomains: "all" + // }, + // connectOptions: { + // port: 5757, + // logfile: 'sauce_connect.log' + // }, + } + + // The number of disconnections tolerated. + // browserDisconnectTolerance: 0, // well, sometimes it helps to just restart + // // How long does Karma wait for a browser to reconnect (in ms). + // browserDisconnectTimeout: 10 * 60 * 1000, + // // 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 + // browserNoActivityTimeout: 20 * 60 * 1000, + // // 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. + // captureTimeout: 12 * 60 * 1000, // this is useful if saucelabs takes a long time to boot a vm + + // // Required to make Safari on Sauce Labs play nice. + // // hostname: 'karmalocal.dev' + }) + + console.log(settings) + config.set(settings) +} diff --git a/.config/polyfillListIE.js b/.config/polyfillListIE.js new file mode 100644 index 000000000..7c7fc3372 --- /dev/null +++ b/.config/polyfillListIE.js @@ -0,0 +1,33 @@ +/* global SVGElement */ +/* eslint no-new-object: "off" */ + +import CustomEventPolyfill from '@target/custom-event-polyfill/src/index.js6' +import children from '../src/polyfills/children.js' + +/* IE 11 has no innerHTML on SVGElement */ +import '../src/polyfills/innerHTML.js' + +/* IE 11 has no correct CustomEvent implementation */ +CustomEventPolyfill() + +/* IE 11 has no children on SVGElement */ +try { + if (!SVGElement.prototype.children) { + Object.defineProperty(SVGElement.prototype, 'children', { + get: function () { + return children(this) + } + }) + } +} catch (e) {} + +/* IE 11 cannot handle getPrototypeOf(not_obj) */ +try { + delete Object.getPrototypeOf('test') +} catch (e) { + var old = Object.getPrototypeOf + Object.getPrototypeOf = function (o) { + if (typeof o !== 'object') o = new Object(o) + return old.call(this, o) + } +} diff --git a/.config/pretest.js b/.config/pretest.js index 834e8d834..0e6ecb75a 100644 --- a/.config/pretest.js +++ b/.config/pretest.js @@ -1,20 +1,22 @@ +/* global XMLHttpRequest */ 'use strict' function get(uri) { - var xhr = new XMLHttpRequest() - xhr.open('GET', uri, false) - xhr.send() - if(xhr.status !== 200) - console.error('SVG.js fixture could not be loaded. Tests will fail.') - return xhr.responseText + var xhr = new XMLHttpRequest() + xhr.open('GET', uri, false) + xhr.send() + if (xhr.status !== 200) { + console.error('SVG.js fixture could not be loaded. Tests will fail.') + } + return xhr.responseText } function main() { - var style = document.createElement("style") - document.head.appendChild(style) - style.sheet.insertRule( get('/fixtures/fixture.css'), 0 ) + var style = document.createElement('style') + document.head.appendChild(style) + style.sheet.insertRule(get('/fixtures/fixture.css'), 0) - document.body.innerHTML = get('/fixtures/fixture.svg') + document.body.innerHTML = get('/fixtures/fixture.svg') } main() diff --git a/.config/rollup.config.js b/.config/rollup.config.js new file mode 100644 index 000000000..69f656c9c --- /dev/null +++ b/.config/rollup.config.js @@ -0,0 +1,144 @@ +import pkg from '../package.json' with { type: 'json' } +import babel from '@rollup/plugin-babel' +import resolve from '@rollup/plugin-node-resolve' +import commonjs from '@rollup/plugin-commonjs' +import filesize from 'rollup-plugin-filesize' +import terser from '@rollup/plugin-terser' + +const buildDate = Date() + +const headerLong = `/*! +* ${pkg.name} - ${pkg.description} +* @version ${pkg.version} +* ${pkg.homepage} +* +* @copyright ${pkg.author} +* @license ${pkg.license} +* +* BUILT: ${buildDate} +*/;` + +const headerShort = `/*! ${pkg.name} v${pkg.version} ${pkg.license}*/;` + +const getBabelConfig = (node = false) => { + let targets = pkg.browserslist + const plugins = [ + [ + '@babel/transform-runtime', + { + version: '^7.24.7', + regenerator: false, + useESModules: true + } + ], + [ + 'polyfill-corejs3', + { + method: 'usage-pure' + } + ] + ] + + if (node) { + targets = 'maintained node versions' + } + + return babel({ + include: 'src/**', + babelHelpers: 'runtime', + babelrc: false, + targets: targets, + presets: [ + [ + '@babel/preset-env', + { + modules: false, + // useBuildins and plugin-transform-runtime are mutually exclusive + // https://github.com/babel/babel/issues/10271#issuecomment-528379505 + // use babel-polyfills when released + useBuiltIns: false, + bugfixes: true, + loose: true + } + ] + ], + plugins + }) +} + +// When few of these get mangled nothing works anymore +// We loose literally nothing by let these unmangled +const classes = [ + 'A', + 'ClipPath', + 'Defs', + 'Element', + 'G', + 'Image', + 'Marker', + 'Path', + 'Polygon', + 'Rect', + 'Stop', + 'Svg', + 'Text', + 'Tspan', + 'Circle', + 'Container', + 'Dom', + 'Ellipse', + 'Gradient', + 'Line', + 'Mask', + 'Pattern', + 'Polyline', + 'Shape', + 'Style', + 'Symbol', + 'TextPath', + 'Use' +] + +const config = (node, min, esm = false) => ({ + input: node || esm ? './src/main.js' : './src/svg.js', + output: { + file: esm + ? './dist/svg.esm.js' + : node + ? './dist/svg.node.cjs' + : min + ? './dist/svg.min.js' + : './dist/svg.js', + format: esm ? 'esm' : node ? 'cjs' : 'iife', + name: 'SVG', + sourcemap: true, + banner: headerLong, + // remove Object.freeze + freeze: false + }, + treeshake: { + // property getter have no sideeffects + propertyReadSideEffects: false + }, + plugins: [ + resolve({ browser: !node }), + commonjs(), + getBabelConfig(node), + filesize(), + !min + ? {} + : terser({ + mangle: { + reserved: classes + }, + output: { + preamble: headerShort + } + }) + ] +}) + +// [node, minified, esm] +const modes = [[false], [false, true], [true], [false, false, true]] + +export default modes.map((m) => config(...m)) diff --git a/.config/rollup.polyfills.js b/.config/rollup.polyfills.js new file mode 100644 index 000000000..9fdfbfdeb --- /dev/null +++ b/.config/rollup.polyfills.js @@ -0,0 +1,20 @@ +import resolve from '@rollup/plugin-node-resolve' +import commonjs from '@rollup/plugin-commonjs' +import filesize from 'rollup-plugin-filesize' + +// We dont need babel. All polyfills are compatible +const config = (ie) => ({ + input: './.config/polyfillListIE.js', + output: { + file: 'dist/polyfillsIE.js', + format: 'iife' + }, + plugins: [ + resolve({ browser: true }), + commonjs(), + //terser(), + filesize() + ] +}) + +export default [true].map(config) diff --git a/.config/rollup.tests.js b/.config/rollup.tests.js new file mode 100644 index 000000000..fe093b6f2 --- /dev/null +++ b/.config/rollup.tests.js @@ -0,0 +1,55 @@ +import * as pkg from '../package.json' +import babel from '@rollup/plugin-babel' +import multiEntry from '@rollup/plugin-multi-entry' +import resolve from '@rollup/plugin-node-resolve' +import commonjs from '@rollup/plugin-commonjs' + +const getBabelConfig = (targets) => + babel({ + include: ['src/**', 'spec/**/*'], + babelHelpers: 'runtime', + babelrc: false, + presets: [ + [ + '@babel/preset-env', + { + modules: false, + targets: targets || pkg.browserslist, + // useBuildins and plugin-transform-runtime are mutually exclusive + // https://github.com/babel/babel/issues/10271#issuecomment-528379505 + // use babel-polyfills when released + useBuiltIns: false, + // corejs: 3, + bugfixes: true + } + ] + ], + plugins: [ + [ + '@babel/plugin-transform-runtime', + { + corejs: 3, + helpers: true, + useESModules: true, + version: '^7.9.6', + regenerator: false + } + ] + ] + }) + +export default { + input: ['spec/setupBrowser.js', 'spec/spec/*/*.js'], + output: { + file: 'spec/es5TestBundle.js', + name: 'SVGTests', + format: 'iife' + }, + plugins: [ + resolve({ browser: true }), + commonjs(), + getBabelConfig(), + multiEntry() + ], + external: ['@babel/runtime', '@babel/runtime-corejs3'] +} diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..210975eef --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "env": { + "browser": true, + "es6": true + }, + "extends": ["eslint:recommended", "prettier"], + "rules": { + "padded-blocks": "off", + "no-unused-vars": [ + "error", + { + "vars": "all", + "args": "after-used", + "ignoreRestSiblings": true, + "varsIgnorePattern": "^_" + } + ] + } +} diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 000000000..ac96ca86e --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,29 @@ +# Contributing + +When 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. + +## Pull Request Process + +When you want to make contributions to the project, the process is pretty simple: + +1. Discuss in an issue or on gitter what you'd like to change +2. Fork the repository to make your own local copy +3. Make a branch in the format of -. 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` +4. Make the changes to the src and perhaps make a playground by duplicating the playgrounds we already have. + - When you're done making changes, run `npm run build` to build the code and run the linter +5. If applicable - please write new tests, we like to keep our code well tesvted šŸŽ‰. Run the tests by running `npm test` +6. Push the code and make a pull request on the main svg.js repo +7. Enjoy our endless love and gratitude ā¤ļø + +Seriously, we love pull requests! So go wild! + +## Code of Conduct + +We only have a few simple rules, because we know you wouldn't want to read a whole code of conduct guide now would you? šŸ¤” + +- don't say anything you wouldn't want said to you +- If you think you can help, then we'd love it if you did! šŸ˜ƒ +- Respect everybody +- NEVER be rude + +If 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! diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..2f880221b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [fuzzyma] diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 000000000..21ab143de --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,18 @@ +--- +name: Bug Report +about: šŸž Report a bug that you found +--- + +# Bug report + +> **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)**. + +## Fiddle + +Modify [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. + +## Explanation + +- What is the behaviour you expect? +- What is happening instead? +- What error message are you getting? diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 000000000..9cf9d7bb2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,39 @@ +--- +name: Feature Request +about: šŸŽ‚ Ask nicely for something you reaaaaaaaally want +--- + +# Feature request + +> **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 want to make a feature request, here are some guidelines to make a good one: + +- Add example code and usage for feature requests to see how a user would use it +- Tell us the benefits (everything is allowed) +- 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 šŸ˜ƒ + +## **Example** Drawing [Smiley the Meme](http://i0.kym-cdn.com/entries/icons/original/000/000/107/smily.jpg) + +It 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. + +### Benefits + +- Drawing memes would be quick and easy +- Memes are funny + +I think the syntax to achieve this should be: + +```js +let meme = draw.meme({ radius: 300, cx: 50, cy: 80, lookAt: [30, 50] }) +``` + +Then the user could easily change where the smiley is looking with: + +```js +meme.lookAt(50, 40) +// OR +meme.lookAt([30, 20]) +// OR +meme.lookAt(new SVG.Point(30, 20)) +``` diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md new file mode 100644 index 000000000..b3769d0f4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other.md @@ -0,0 +1,6 @@ +--- +name: Other Issue +about: šŸŗ Something else... +--- + +> **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**. diff --git a/.gitignore b/.gitignore index f58d1822d..53304939e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,13 @@ .DS_Store .idea -public -site/ -bleed/ -docs/ -obsolete/ +.importjs.js test/ -src/index.js node_modules/ .vscode/ coverage/ -fonts/ \ No newline at end of file +spec/es5TestBundle.js +.env +dist +index.html +index.js +todo.md \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..f960e6927 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,11 @@ +.DS_Store +.idea +.importjs.js +test/ +node_modules/ +.vscode/ +coverage/ +spec/es5TestBundle.js +.env +dist +package-lock.json diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..c50384fbb --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "none", + "tabWidth": 2, + "semi": false, + "singleQuote": true +} diff --git a/.travis.yml b/.travis.yml index 0f363294d..770ac06bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,18 +1,14 @@ language: node_js node_js: - - "stable" -script: - - npm run build:test - - npm test - - cat coverage/firefox/lcov.info | node_modules/.bin/coveralls -#sudo: required -#dist: trusty + - 'stable' +services: + - xvfb addons: - firefox: "latest" -before_install: - # Start a display server where all graphical operations happens in memory - - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start -cache: - directories: - - node_modules + firefox: latest + chrome: stable +install: + - npm install + - npm run build +script: + - npm test + - cat coverage/firefox/lcov.info | node_modules/.bin/coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md index 12595bbd0..3bc5eddc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,98 +4,499 @@ All notable changes to this project will be documented in this file. The document follows the conventions described in [ā€œKeep a CHANGELOGā€](http://keepachangelog.com). - ==== +## [3.2.4] + +### Fixed + +- fixed dmove for nested svgs (https://github.com/svgdotjs/svg.draggable.js/issues/127) + +## [3.2.3] + +### Fixed + +- fixed import map for father (#1317) + +## [3.2.2] + +### Fixed + +- fixed import map + +## [3.2.1] + +### Fixed + +- skip descriptive elements on rebuild and `toParent()` (#1304) +- allow 0 as animation duration and delay (#1225) +- allow nodes that are not imported yet (#1252) +- only apply color conversion to attributes that can take a color (#1241) +- support css vars (#1230) +- fix import of leading, dont write data to dom if not neccessary +- discontinue use of svgjs:data in favor of data-svg +- allow + as delemiter in paths (#1165) +- added `amove()` methods to runner (#1131) +- fix `css()`, dont throw when screenCtm fails (#968) +- several type fixes + +### Added + +- add terminate method to timeline so memory can be freed (#1295) +- add more events to sugar (#1217) + +## [3.2.0] + +### Fixed + +- improve performance of `point()` by not creating intermediate objects (#1251) +- fixed references by using single quotes instead of double quotes which lead to errors (#1277) +- fixed a few spelling errors in comments (#1277) +- fix several typings (#1253, #1280, #1300) + +### Added + +- added second parameter `assignNewId` to `clone()` to allow cloning with the same id (#1161) + +## [3.1.2] + +### Fixed + +- fixed several type issues (#1249, #1233, #1231, #1223, #1215) +- fixed `css()` returning camelCased properties even though they were specified in kebap-case +- fixed `ObjectBag` loosing information when calling `valueOf()` (Numbers lost its unit) +- fixed `parents()` (#1235) +- fixed `nodeOrNew()` to work in object tags as well (#1219) + +## [3.1.1] + +### Fixed + +- fixed typings for tcs 4.2.4 (#1204, #1206, #1203) + +## [3.1.0] + +### Fixed + +- fixed `zoom()` method of runner which was passed a wrong parameter +- fixed positioning methods of `TSpan` to position them by its bounding box +- fixed `flip()` method which flips correctly by center by default now and accepts correct arguments +- fixed a case in `rbox()` where not always all values of the box were updated +- fixed `getOrigin()` function used by `transform()` so that all origin popssibilities specified in the docs are working (#1085) +- fixed positioning of text by its baseline when using `amove()` +- fixed tons of typings in the svg.d.ts file and relaxed type requirements for `put()` and `parent()` +- fixed adopter when adopting an svg/html string. It had still its wrapper as parentNode attached +- fixed `put()` which correctly creates an svgjs object from the passed element now before returning +- fixed `parent()` which correctly returns a Dom instance when parent is the document or document-fragment +- fixed `add()` which correctly removes namespaces of non-root svg elements now when added to another svg element (#1086) +- fixed `isRoot()` which correctly returns false, if the element is in a document-fragment +- fixed `replace()` which works without a parent now, too +- fixed `defs()` which correctly returns `null` when called on a detached node that is not a root node +- fixed `reference()` which correctly returns `null` instead of throwing when specifying an attribute which holds a number +- fixed `flatten()` which correctly flattens now but doesn't accept parameters anymore (makes no sense) +- fixed `ungroup()` which now inserts the elements at the correct position in the correct order and has position as second argument now +- fixed `position` for `transform()` to also allow a position of 0 +- fixed `bbox()` of `PathArray` and `PointArray` which returns an instance of `Box` now +- fixed bug in creation of PointArray which had still references to source arrays in it +- fixed `PID` controller and makeSetterGetter function +- fixed `Queue.push` which didnt let you push queue items +- fixed `Timeline.reverse()` which did exactly the opposite of what you would expect when passing `true/false` +- fixed cancelAnimationFrame-mock for tests +- fixed animate when=after to be really "now" when no runner is on the timeline +- fixed animate attr which is also retargetable now +- fixed internals of ObjectBag which can hold other Morphable values now +- fixed animate transform which didnt change its origin on retarget for declarative animations +- fixed path parsing (#1145) +- fixed `clone()` to return the correct instance (#1154) + +### Added + +- added second Parameter to `SVG(el, isHTML)` which allows to explicitely create elements in the HTML namespace (#1058) +- added `unlink()` and `linker()` to hyperlinked elements to remove or access the underling `` element +- added `wrap()` method to `Dom` which lets you wrap an element by another one +- added `orient()` method to `Marker` +- added `options` parameter to `dispatch()` and `fire()` to allow for more special needs +- added `newLine()` constructor to `Text` to create a tspan marked as new line (#1088) +- added `Fragment` as a wrapper for document-fragment +- added position argument for `toParent()` +- added position argument for `toRoot()` +- added attr syntax for `data()` method +- added index and array parameter when passing a function to `List.each()` so that it mostly behaves like map +- added possibility to pass a transform object to `PointArray.transform()` similar to Point +- added `with-last` as `when` to `animate` and `schedule` to let an animation start with the start of the last one in the timeline +- added lots of tests in es6 format +- added geometry and positioning methods to `A` (#1110) + +### Deleted + +- deleted undocumented `Matrix.compose()` method which did the same as `new Matrix()` or `Matrix.transform()` +- deleted undocumented `Path.morph()` and `Path.at()` which was replaced with Morphables in v3 + +## [3.0.16] - 2019-11-12 + +### Fixed + +- fixed build of polyfills which was broken because of core-js update + +## [3.0.15] - 2019-11-08 + +### Fixed + +- allow object input of `when` and `delay` to `animate()` + +### Added + +- added missing dmove function to runner + +## [3.0.14] - 2019-10-31 + +### Fixed + +- hide parser from screen readers (#1023) + +### Added + +- added transpiled esm bundle for webpack und co and faster import + +## [3.0.13] - 2019-06-12 + +### Fixed + +- fixed a bug in Timeline.finish() (#964) +- fixed registration of classes with custom bundler +- fixed transform getter (e.g. `el.transform('scale')`) +- fixed typings (#1004) + +## [3.0.12] - 2019-02-19 + +### Fixed + +- fixed package.json which points to correct file for webpack now (browser keyword) +- fixed typescript types + +### Added + +- added `ForeignObject` to the core + +## [3.0.11] - 2019-01-22 + +### Fixed + +- fixed move commands (x, y, move) of text so that it moves text always by the upper left edge. +- fixed center commands (cx, cy, center) of text so that it moves text always by the center. + +## [3.0.10] - 2019-01-14 + +### Fixed + +- fixed `textPath()`, `path().text()` and `text().path()` +- fixed `root()` method +- fixed default values returned by `attr`. Can be missleading if present. + +### Added + +- added `findOne()` for better performance + +## [3.0.9] - 2019-01-14 + +### Fixed + +- renamed `unit()` to `convert()` due to name collision + +## [3.0.8] - 2019-01-13 + +### Fixed + +- added back `to()` as `unit()` of `SVG.Number` which was removed accidentally + +## [3.0.7] - 2019-01-13 + +### Fixed + +- fixed a bug in `isNulledBox()` and `domContains()` +- performance changes: + - replace `getElementsByTagName` with `querySelector` + - make Color check in `attr` more restrictive to prevent expensive `toString` + +## [3.0.6] - 2019-01-12 + +### Fixed -## UNRELEASED 3.0.0 +- fixed group move and size commands +- default font size is not set anymore because it mostly goes against what the user wants +- fix bug in `font()` which set wrong values ### Added -- added `'random'` option and `randomize()` method to `SVG.Color` -> __TODO!__ -- added `precision()` method to round numeric element attributes -> __TODO!__ -- added specs for `SVG.FX` -> __TODO!__ + +- `PointArray.transform()` (#945) + +## [3.0.5] - 2018-12-12 + +### Fixed + +- fixed `parser` which didnt have all required css rules and not focusable=false +- group `x(), y(), width(), height(), dx(), dy()` now correctly change the bbox of the group by moving/resizing all children +- fixed timeline which fired `finished` too early +- fixed `Animator.frame()`. The passed callback gets the current time now (same as RAF) +- allow `loop(true)` which is the same as `loop()` + +## [3.0.4] - 2018-12-07 + +### Fixed + +- fixed `zoom` which was added correctly and is animatable now +- fixed `Runner` which merges transformations on the correct frame and in the correct way now +- fixed condition on which transforms get deleted from an element when animating +- fixed `Timeline` which executes Runner in the correct order now +- fixed `Svg` which correctly deletes the defs reference on `clear()` + +## [3.0.3] - 2018-12-05 + +### Fixed + +- fixed `Runner` which correctly retains transformations when it is still on a timeline +- fixed `plot()` method of Runner +- fixed `timeline()` so that one can set the timeline of an element now +- fixed `G` and added missing `width/height` + +## [3.0.2] - 2018-12-03 + +### Fixed + +- fixed `List` which still didn't have all method names it should have +- fixed `Runner` which correctly handle retargeted controlled animations now +- fixed `Runner` so that it is able to be persisted correctly +- fixed `Color` which correctly handles empty strings now +- fixed `attr` which correctly handles Objects of other kind now +- fixed `Morphable` which correctly calculates the done flag now + +## [3.0.1] - 2018-12-03 + +### Fixed + +- fixed `insertBefore`, `insertAfter` and `flip` correctly returning `this` +- fixed `List` which didn't have all method names it should have + +## [3.0.0] - 2018-12-01 + +### Added + +- added `text()` method to `SVG.Path` to create a textPath from this path (#705) +- added `SVG.HTMLNode` which is the object wrapped around html nodes to put something in them +- added `dispatch()` method on `SVG.Element` which returns the dispatched event for event cancelation (#550) +- added `isRoot()` on `SVG.Doc` (#809) +- added a linter during the npm build process +- added `beziere()` and `steps()` to generate easing functions +- added `insertAfter()` and `insertBefore` +- added `SVG.Style` which can be created with `style()` or `fontface()` (#517) +- added `EventTarget` which is a baseclass to get event abilities (#641) +- added `Dom` which is a baseclass to get dom abilities +- added `round()` which lets you round attribues from a node +- added `ax(), ay(), amove()` to change texts x and y values directly (#787) +- added possibility to pass attributes into a constructor like: `new SVG.Rect({width:100})` +- added possibility to pass in additional attribues to element creators e.g. `canvas.rect({x:100})` or `canvas.rect(100, 100, {x:100})` (#796) +- added `SVG.List` (#645) +- added `words()` and `element()` to `Dom` because of (#935) +- added lab, lch, hsl and cmyk color spaces (#790) +- added `random()` method on `SVG.Color` to create random colors of different kinds (#939) + +### Removed + +- removed `SVG.Array.split()` function +- removed workaround for browser bug with stroke-width +- removed polyfills +- removed `SVG.Set` in favour of `SVG.List` +- removed feature to set style with css string (e.g. "fill:none;display:block;") +- removed `loaded()` and `error()` method on `SVG.Image` (#706) +- removed sub-pixel offset fix +- removed `SVG.Nested` (#809) +- removed `show()` from `SVG.A` to avoid name clash (#802) +- removed `size()` from `SVG.Text` to avoid name clash (#799) +- removed `native()` function +- removed `Bare` in favour of `Dom` (#935) +- removed `bower` support because it is deprecated ### Changed -- made transform-methods relative as default (breaking change) -- changed SVG() to use querySelector instead of getElementById (breaking change) -> __TODO!__ -- made `parents()` method on `SVG.Element` return an instance of SVG.Set (breaking change) -> __TODO!__ -- replaced static reference to `masker` in `SVG.Mask` with the `masker()` method (breaking change) -> __TODO!__ -- replaced static reference to `clipper` in `SVG.ClipPath` with the `clipper()` method (breaking change) -> __TODO!__ -- replaced static reference to `targets` in `SVG.Mask` and `SVG.ClipPath` with the `targets()` method (breaking change) -> __TODO!__ -- moved all regexes to `SVG.regex` (in color, element, pointarray, style, transform and viewbox) -> __TODO!__ + +- gradients now have there corresponding node as type and not only radial/linear +- `SVG.Path.pointAt()` correctly returns an `SVG.Point` now +- replaced static reference to `masker` in `SVG.Mask` with the `masker()` method +- replaced static reference to `clipper` in `SVG.ClipPath` with the `clipper()` method +- replaced static reference to `targets` in `SVG.Mask` and `SVG.ClipPath` with the `targets()` method +- moved all regexes to `SVG.regex` +- new constructor signature for `SVG.Image` and `load()`: `container.image(src, callback) / image.load(src, callback)` (#706) +- changed `style()` to `css()`. Now accepts array as input and returns object when no argument given (#517) +- ids are not generated upon creation anymore. Instead they are generated when requested (#559) +- `SVG.extend()` now expects exactly one module or an array of modules +- `SVG.Text.path()` now returns an instance of SVG.TextPath (#705) +- `SVG.Text.path()` does not move all contents to the textPath (#705) +- `SVG.TextPath` now inherits from `SVG.Text` and can be manipulated the same way (#705) +- `SVG.Text.textPath()` returns the first textPaths in the text element (#705) +- renamed `SVG.Stop` constructor `at()` on `SVG.Gradient` to `stop()` (#707) +- renamed `fill()` method on `SVG.Gradient` and `SVG.Pattern` to `url()` (#708) +- renamed `previous()` method to `prev()` +- changed `childNodes` to `children` (same for `firstChild`, `lastChild`, ...) (#710) - changed it back because of performance drop +- moved `defs()` method from `SVG.Parent` to `SVG.Element` +- `SVG()` can be called with css selector, node or svg string, now. Without an argument it creates a new `SVG.Doc()` (#646) +- `add()`, `put()`, `addTo()`, `putIn()` now excepts all arguments accepted by `SVG()` +- all `SVG.*` objects now can have a node as parameter when constructing +- `SVG()` does not set a default size anymore +- default constructor now has an optional `node` argument which is used to consruct the object e.g. `new SVG.Rect(rectNode)` +- SVG.Elements constructor now tries to import svgjs:data from the node +- `SVG.on()` calls the listener in the context of the passed object. el.on always uses the svg.js object as context +- `SVG.on()/off()` and `el.on()/off()` now accepts multiple comma or space separated events e.g. "mousedown, foo bar" (#727) +- Matrices now apply transformations like `scale`, `translate`, etc... by left multiplying them to simplify transformations +- The way `transform()` works is now completely different. See the docs for more as soon as they are updated +- merged `SVG.Doc` and `SVG.Nested`, added `isRoot()` on `SVG.Doc()` (#809) +- 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 +- The whole lib is now splitted into es6 modules (#875) +- `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 +- `ungroup()` now breaks off one container and not more +- `clone()` does not add the clone to the dom anymore +- `attr()` excepts array now to get multiple values at once +- `SVG.Text.rebuild()` now takes every font-size into account (#512) +- `fill()` and `stroke()` return the fill and stroke attribute when called as getter (#789) +- `parents()` now gives back all parents until the passed one or document +- `Image` callback passes normal `load` event instead of custom object (#931) +- renamed `Doc` to `Svg` and `doc()` to `root` (and `toDoc()/toRoot()`) (#932) + +## [2.7.1] - 2018-11-30 + +### Fixed + +- CustomEvent-polyfill was not used (needed in IE) (#938) + +## [2.7.0] - 2018-11-13 + +### Fixed + +- fixed calling `parent()` on `documentFragment`s children (#927) +- parser is not focusable anymore (#908) +- `SVG.Element.click(null)` correctly unbinds the event (#878) +- fix memory leak (#905) + +### Added + +- `SVG.Set` now accepts another Set as input (#893) +- `on()/off()` accepts multiple event names as input (backport from 3.0) + +## [2.6.6] - 2018-08-30 + +### Added + +- added global reference to support 'window' in bundlers (#767) + +## [2.6.5] - 2018-05-26 + +### Fixed + +- fixed `element.parent()` which sometimes failed when used on detached documents (#759) +- fixed `SVG.Text.y()` which didnt work correctly with `SVG.Number` (#778) +- fixed `SVG.Doc.clone()` which throwed an error (#782) +- fixed `SVG.Mask.clone()` which throwed an error (#782) +- fixed `SVG.PointArray` having a reference to outside array in some cases (#803) +- fixed `reference()` which failed when trying to use a reference which does not exist in the attribuets (#840) +- fixed `animate().attr()` method which doenst work for `d` attribute of paths (#847) +- fixed problems with `CustomEvent` polyfill in IE11 (#852) + +### Added + +- added possibility to pass an array of point objects to SVG.PointArray (#803) + +## [2.6.4] - 2018-02-07 + +### Fixed + +- fixed memory leak when creating images (#805) + +## [2.6.3] - 2017-07-21 ### Fixed -- fixed a bug in clipping and masking where empty nodes persists after removal -> __TODO!__ -- fixed a bug in IE11 with `mouseenter` and `mouseleave` -> __TODO!__ + +- fixed error in parent method when hitting document node (#720) ## [2.6.2] - 2017-06-05 ### Added + - added `width()` and `height()` methods to `SVG.FX` - added the intended functionality to call animate functions with multiple parameter (#671) ### Changed + - updated Jasmine from 2.5.2 to 2.6.0 - removed the typeof check in the initialisation of SVG.Matrix ### Fixed + - fixed `SVG.FX.once` so that it add its callback on the last situation instead of the current one - fixed `SVG.FX.step` so that the animation doesn't stop if an afterAll callback call animate (#677) +## [2.6.1] - 2017-04-25 + +### Fixed + +- fixed a bug in path parser which made it stop parsing when hitting z command (#665) ## [2.6.1] - 2017-04-25 ### Fixed + - fixed a bug in path parser which made it stop parsing when hitting z command (#665) ## [2.6.0] - 2017-04-21 ### Added + - added `options` object to `SVG.on()` and `el.on()` (#661) ### Changed -- back to sloppy mode because of problems with plugins (#660) +- back to sloppy mode because of problems with plugins (#660) ## [2.5.3] - 2017-04-15 ### Added -- added gitter badge in readme +- added gitter badge in readme ### Fixed + - fixed svg.js.d.ts (#644 #648) - fixed bug in `el.flip()` which causes an error when calling flip without any argument ### Removed -- component.json (#652) +- component.json (#652) ## [2.5.2] - 2017-04-11 ### Changed + - SVG.js is now running in strict mode ### Fixed + - `clear()` does not remove the parser in svg documents anymore - `len` not declared in FX module, making it a global variable (9737e8a) - `bbox` not declared in SVG.Box.transform in the Box module (131df0f) - `namespace` not declared in the Event module (e89c97e) - ## [2.5.1] - 2017-03-27 ### Changed + - make svgjs ready to be used on the server ### Fixed + - fixed `SVG.PathArray.parse` that did not correctly parsed flat arrays - prevented unnecessary parsing of point or path strings - ## [2.5.0] - 2017-03-10 ### Added + - added a plot and array method to `SVG.TextPath` (#582) - added `clone()` method to `SVG.Array/PointArray/PathArray` (#590) - added `font()` method to `SVG.Tspan` @@ -104,6 +505,7 @@ The document follows the conventions described in [ā€œKeep a CHANGELOGā€](http: - added `event()` to `SVG.Element` to retrieve the event that was fired last on the element (#550) ### Changed + - changed CHANGELOG to follow the conventions described in [ā€œKeep a CHANGELOGā€](http://keepachangelog.com) (#578) - make the method plot a getter when no parameter is passed for `SVG.Polyline`,`SVG.Polygon`, `SVG.Line`, `SVG.Path` (related #547) - allow `SVG.PointArray` to be passed flat array @@ -118,6 +520,7 @@ The document follows the conventions described in [ā€œKeep a CHANGELOGā€](http: - events are now cancelable by default (#550) ### Fixed + - fixed a bug in the plain morphing part of `SVG.MorphObj` that is in the FX module - fixed bug which produces an error when removing an event from a node which was formerly removed with a global `off()` (#518) - fixed a bug in `size()` for poly elements when their height/width is zero (#505) @@ -131,92 +534,99 @@ The document follows the conventions described in [ā€œKeep a CHANGELOGā€](http: - fixed offset produced by svg parser (#553) - fixed a bug with clone which didnt copy over dom data (#621) - ## [2.4.0] - 2017-01-14 ### Added -- added support for basic path animations (#561) +- added support for basic path animations (#561) ## [2.3.7] - 2017-01-14 ### Added + - added code coverage https://coveralls.io/github/svgdotjs/svg.js (3e614d4) - added `npm run test:quick` which aim at being fast rather than correct - great for git hooks (981ce24) ### Changed + - moved project to [svgdotjs](https://github.com/svgdotjs) - made matrixify work with transformation chain separated by commas (#543) - updated dev dependencies; request and gulp-chmod - `npm run build` now requires nodejs 4.x+ ### Fixed + - fixed `SVG.Matrix.skew()` (#545) - fixed broken animations, if using polyfills for es6/7 proposals (#504) - fixed and improved `SVG.FX.dequeue()` (#546) - fixed an error in `SVG.FX.step`, if custom properties is added to `Array.prototype` (#549) - ## [2.3.6] - 2016-10-21 ### Changed + - make SVG.FX.loop modify the last situation instead of the current one (#532) ### Fixed + - fixed leading and trailing space in SVG.PointArray would return NaN for some points (695f26a) (#529) - fixed test of `SVG.FX.afterAll` (#534) - fixed `SVG.FX.speed()` (#536) - ## [2.3.5] - 2016-10-13 ### Added + - added automated unit tests via [Travis](https://travis-ci.org/svgdotjs/svg.js) (#527) - added `npm run build` to build a new version of SVG.js without requiring gulp to be globally installed ### Changed + - calling `fill()`, `stroke()` without an argument is now a nop - Polygon now accepts comma less points to achieve parity with Adobe Illustrator (#529) - updated dependencies - ## [2.3.4] - 2016-08-04 ### Changed + - reworked parent module for speed improvemenents - reworked `filterSVGElements` utility to use a for loop instead of the native filter function - ## [2.3.3] - 2016-08-02 ### Added + - add error callback on image loading (#508) ### Fixed + - fixed bug when getting bbox of text elements which are not in the dom (#514) - fixed bug when getting bbox of element which is hidden with css (#516) - ## [2.3.2] - 2016-06-21 ### Added + - added specs for `SVG.ViewBox` - added `parent` parameter for `clone()` - added spec for mentioned issue ### Fixed + - fixed string parsing in viewbox (#483) - fixed bbox when element is not in the dom (#480) - fixed line constructor which doesn't work with Array as input (#487) - fixed problem in IE with `document.contains` (#490) related to (#480) - fixed `undo` when undoing transformations (#494) - ## [2.3.1] - 2016-05-05 ### Added + - added typings for svg.js (#470) ### Fixed + - fixed `SVG.morph()` (#473) - fixed parser error (#471) - fixed bug in `SVG.Color` with new fx @@ -226,79 +636,85 @@ The document follows the conventions described in [ā€œKeep a CHANGELOGā€](http: - fixed bug in `SVG.Doc().create` where size was set to 100% even if size was already specified - fixed bug in `parse()` from `SVG.PathArray` which does not correctly handled `S` and `T` (#485) - ## [2.3.0] - 2016-03-30 ### Added + - added `SVG.Point` which serves as Wrapper to the native `SVGPoint` (#437) - added `element.point(x,y)` which transforms a point from screen coordinates to the elements space (#403) - added `element.is()` which helps to check for the object instance faster (instanceof check) - added more fx specs ### Changed + - textpath now is a parent element, the lines method of text will return the tspans inside the textpath (#450) - fx module rewritten to support animation chaining and several other stuff (see docs) ### Fixed + - fixed `svgjs:data` attribute which was not set properly in all browsers (#428) - fixed `isNumber` and `numberAndUnit` regex (#405) - fixed error where a parent node is not found when loading an image but the canvas was cleared (#447) - fixed absolute transformation animations (not perfect but better) - fixed event listeners which didnt work correctly when identic funtions used - ## [2.2.5] - 2015-12-29 ### Added + - added check for existence of node (#431) ### Changed + - `group.move()` now allows string numbers as input (#433) - `matrixify()` will not apply the calculated matrix to the node anymore - ## [2.2.4] - 2015-12-12 ### Fixed + - fixed `transform()` which returns the matrix values (a-f) now, too (#423) - double newlines (\n\n) are correctly handled as blank line from `text()` - fixed use of scrollX vs pageXOffset in `rbox()` (#425) - fixed target array in mask and clip which was removed instead of reinitialized (#429) - ## [2.2.3] - 2015-11-30 ### Fixed + - fixed null check in image (see 2.2.2) - fixed bug related to the new path parser (see 2.2.2) - fixed amd loader (#412) - ## [2.2.2] - 2015-11-28 ### Added + - added null check in image onload callback (#415) ### Changed + - documentation rework (#407) [thanks @snowyplover] ### Fixed -- fixed leading point bug in path parsing (#416) +- fixed leading point bug in path parsing (#416) ## [2.2.1] - 2015-11-18 ### Added + - added workaround for `SvgPathSeg` which is removed in Chrome 48 (#409) - added `gbox()` to group to get bbox with translation included (#405) ### Fixed -- fixed dom data which was not cleaned up properly (#398) +- fixed dom data which was not cleaned up properly (#398) ## [2.2.0] - 2015-11-06 ### Added + - added `ungroup()/flatten()` (#238), `toParent()` and `toDoc()` - added UMD-Wrapper with possibility to pass custom window object (#352) - added `morph()` method for paths via plugin [svg.pathmorphing.js](https://github.com/Fuzzyma/svg.pathmorphing.js) @@ -306,55 +722,60 @@ The document follows the conventions described in [ā€œKeep a CHANGELOGā€](http: - added `parents()` method to get an array of all parenting elements ### Changed + - svgjs now saves crucial data in the dom before export and restores them when element is adopted ### Fixed + - fixed pattern and gradient animation (#385) - fixed mask animation in Firefox (#287) - fixed return value of `text()` after import/clone (#393) - ## [2.1.1] - 2015-10-03 ### Added -- added custom context binding to event callback (default is the element the event is bound to) +- added custom context binding to event callback (default is the element the event is bound to) ## [2.1.0] - 2015-09-20 ### Added + - added transform to pattern and gradients (#383) ### Fixed + - fixed clone of textnodes (#369) - fixed transformlists in IE (#372) - fixed typo that leads to broken gradients (#370) - fixed animate radius for circles (#367) - ## [2.0.2] - 2015-06-22 ### Fixed -- Fixed zoom consideration in circle and ellipse +- Fixed zoom consideration in circle and ellipse ## [2.0.1] - 2015-06-21 ### Added + - added possibility to remove all events from a certain namespace ### Fixed + - fixed bug with `doc()` which always should return root svg - fixed bug in `SVG.FX` when animating with `plot()` ### Removed + - removed target reference from use which caused bugs in `dmove()` and `use()` with external file - removed scale consideration in `move()` duo to incompatibilities with other move-functions e.g. in `SVG.PointArray` - ## [2.0.0] - 2015-06-11 ### Added + - implemented an SVG adoption system to be able to manipulate existing SVG's not created with svg.js - added polyfill for IE9 and IE10 custom events [thanks @Fuzzyma] - added DOM query selector with the `select()` method globally or on parent elements @@ -378,6 +799,7 @@ The document follows the conventions described in [ā€œKeep a CHANGELOGā€](http: - added event-based or complete detaching of event listeners in `off()` method ### Changed + - changed `parent` reference on elements to `parent()` method - using `CustomEvent` instead of `Event` to be able to fire events with a `detail` object [thanks @Fuzzyma] - renamed `SVG.TSpan` class to `SVG.Tspan` to play nice with the adoption system @@ -396,13 +818,14 @@ The document follows the conventions described in [ā€œKeep a CHANGELOGā€](http: - removed `SVG.Symbol` but kept the `symbol()` method using the new `element()` method ### Fixed + - fixed bug in `radius()` method when `y` value equals `0` - fixed a bug where events are not detached properly - ## [1.0.0-rc.9] - 2014-06-17 ### Added + - added `SVG.Marker` - added `SVG.Symbol` - added `first()` and `last()` methods to `SVG.Set` @@ -410,66 +833,74 @@ The document follows the conventions described in [ā€œKeep a CHANGELOGā€](http: - added `reference()` method to get referenced elements from a given attribute value ### Changed + - `SVG.get()` will now also fetch elements with a `xlink:href="#elementId"` or `url(#elementId)` value given ### Fixed -- fixed infinite loop in viewbox when element has a percentage width / height [thanks @shabegger] +- fixed infinite loop in viewbox when element has a percentage width / height [thanks @shabegger] ## [1.0.0-rc.8] - 2014-06-12 ### Fixed + - fixed bug in `SVG.off` - fixed offset by window scroll position in `rbox()` [thanks @bryhoyt] - ## [1.0.0-rc.7] - 2014-06-11 ### Added + - added `classes()`, `hasClass()`, `addClass()`, `removeClass()` and `toggleClass()` [thanks @pklingem] ### Changed + - binding events listeners to svg.js instance - calling `after()` when calling `stop(true)` (fulfill flag) [thanks @vird] - text element fires `rebuild` event whenever the `rebuild()` method is called ### Fixed + - fixed a bug where `Element#style()` would not save empty values in IE11 [thanks @Shtong] - fixed `SVG is not defined error` [thanks @anvaka] - fixed a bug in `move()`on text elements with a string based value - fix for `text()` method on text element when acting as getter [thanks @Lochemage] - fix in `style()` method with a css string [thanks @TobiasHeckel] - ## [1.0.0-rc.6] - 2014-03-03 ### Added + - added `leading()` method to `SVG.FX` - added `reverse()` method to `SVG.Array` (and thereby also to `SVG.PointArray` and `SVG.PathArray`) - added `fulfill` option to `stop()` method in `SVG.FX` to finalise animations - added more output values to `bbox()` and `rbox()` methods ### Changed + - fine-tuned text element positioning - calling `at()` method directly on morphable svg.js instances in `SVG.FX` module - moved most `_private` methods to local named functions - moved helpers to a separate file ### Fixed + - fixed a bug in text `dy()` method ### Removed -- removed internal representation for `style` +- removed internal representation for `style` ## [1.0.0-rc.5] - 2014-02-14 ### Added + - added `plain()` method to `SVG.Text` element to add plain text content, without tspans - added `plain()` method to parent elements to create a text element without tspans - added `build()` to enable/disable build mode ### Changed + - updated `SVG.TSpan` to accept nested tspan elements, not unlike the `text()` method in `SVG.Text` - removed the `relative()` method in favour of `dx()`, `dy()` and `dmove()` - 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) @@ -480,115 +911,127 @@ The document follows the conventions described in [ā€œKeep a CHANGELOGā€](http: - building `SVG.FX` class with `SVG.invent()` function ### Removed -- removed verbose style application to tspans +- removed verbose style application to tspans ## [1.0.0-rc.4] - 2014-02-04 ### Added + - automatic pattern creation by passing an image url or instance as `fill` attribute on elements - added `loaded()` method to image tag - added `pointAt()` method to `SVG.Path`, wrapping the native `getPointAtLength()` ### Changed + - switched to `MAJOR`.`MINOR`.`PATCH` versioning format to play nice with package managers - made svg.pattern.js part of the core library - moved `length()` method to sugar module ### Fixed + - fix in `animate('=').to()` - fix for arcs in patharray `toString()` method [thanks @dotnetCarpenter] - ## [v1.0rc3] - 2014-02-03 ### Added + - added the `SVG.invent` function to ease invention of new elements - added second values for `animate('2s')` - added `length()` mehtod to path, wrapping the native `getTotalLength()` ### Changed + - using `SVG.invent` to generate core shapes as well for leaner code ### Fixed + - fix for html-less documents - fix for arcs in patharray `toString()` method - ## [v1.0rc2] - 2014-02-01 ### Added + - added `index()` method to `SVG.Parent` and `SVG.Set` - added `morph()` and `at()` methods to `SVG.Number` for unit morphing ### Changed -- modified `cx()` and `cy()` methods on elements with native `x`, `y`, `width` and `height` attributes for better performance +- modified `cx()` and `cy()` methods on elements with native `x`, `y`, `width` and `height` attributes for better performance ## [v1.0rc1] - 2014-01-31 ### Added + - added `SVG.PathArray` for real path transformations - added `bbox()` method to `SVG.Set` - added `relative()` method for moves relative to the current position - added `morph()` and `at()` methods to `SVG.Color` for color morphing ### Changed + - enabled proportional resizing on `size()` method with `null` for either `width` or `height` values - moved data module to separate file - `data()` method now accepts object for for multiple key / value assignments ### Removed -- removed `unbiased` system for paths +- removed `unbiased` system for paths ## [v0.38] - 2014-01-28 ### Added + - added `loop()` method to `SVG.FX` ### Changed -- switched from `setInterval` to `requestAnimFrame` for animations +- switched from `setInterval` to `requestAnimFrame` for animations ## [v0.37] - 2014-01-26 ### Added + - added `get()` to `SVG.Set` ### Changed -- moved `SVG.PointArray` to a separate file +- moved `SVG.PointArray` to a separate file ## [v0.36] - 2014-01-25 ### Added + - added `linkTo()`, `addTo()` and `putIn()` methods on `SVG.Element` ### Changed + - provided more detailed documentation on parent elements ### Fixed - ## [v0.35] - 2014-01-23 ### Added -- added `SVG.A` element with the `link()` +- added `SVG.A` element with the `link()` ## [v0.34] - 2014-01-23 ### Added + - added `pause()` and `play()` to `SVG.FX` ### Changed -- storing animation values in `situation` object +- storing animation values in `situation` object ## [v0.33] - 2014-01-22 ### Added + - added `has()` method to `SVG.Set` - added `width()` and `height()` as setter and getter methods on all shapes - added `replace()` method to elements @@ -596,28 +1039,57 @@ The document follows the conventions described in [ā€œKeep a CHANGELOGā€](http: - added reference to parent node in defs ### Changed + - moved sub-pixel offset fix to be an optional method (e.g. `SVG('drawing').fixSubPixelOffset()`) - merged plotable.js and path.js - ## [v0.32] ### Added -- added library to [cdnjs](http://cdnjs.com) +- added library to [cdnjs](http://cdnjs.com) + +[3.2.4]: https://github.com/svgdotjs/svg.js/releases/tag/3.2.4 +[3.2.3]: https://github.com/svgdotjs/svg.js/releases/tag/3.2.3 +[3.2.2]: https://github.com/svgdotjs/svg.js/releases/tag/3.2.2 +[3.2.1]: https://github.com/svgdotjs/svg.js/releases/tag/3.2.1 +[3.2.0]: https://github.com/svgdotjs/svg.js/releases/tag/3.2.0 +[3.1.2]: https://github.com/svgdotjs/svg.js/releases/tag/3.1.2 +[3.1.1]: https://github.com/svgdotjs/svg.js/releases/tag/3.1.1 +[3.1.0]: https://github.com/svgdotjs/svg.js/releases/tag/3.1.0 +[3.0.16]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.16 +[3.0.15]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.15 +[3.0.14]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.14 +[3.0.13]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.13 +[3.0.12]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.12 +[3.0.11]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.11 +[3.0.10]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.10 +[3.0.9]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.9 +[3.0.8]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.8 +[3.0.7]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.7 +[3.0.6]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.6 +[3.0.5]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.5 +[3.0.4]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.4 +[3.0.3]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.3 +[3.0.2]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.2 +[3.0.1]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.1 +[3.0.0]: https://github.com/svgdotjs/svg.js/releases/tag/3.0.0 +[2.7.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.7.1 +[2.7.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.7.0 +[2.6.6]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.6 +[2.6.5]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.5 +[2.6.4]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.4 +[2.6.3]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.3 [2.6.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.2 [2.6.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.1 [2.6.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.6.0 - [2.5.3]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.3 [2.5.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.2 [2.5.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.1 [2.5.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.5.0 - [2.4.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.4.0 - [2.3.7]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.7 [2.3.6]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.6 [2.3.5]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.5 @@ -626,21 +1098,17 @@ The document follows the conventions described in [ā€œKeep a CHANGELOGā€](http: [2.3.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.2 [2.3.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.1 [2.3.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.3.0 - [2.2.5]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.5 [2.2.4]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.4 [2.2.3]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.3 [2.2.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.2 [2.2.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.1 [2.2.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.2.0 - [2.1.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.1.1 [2.1.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.1.0 - [2.0.2]: https://github.com/svgdotjs/svg.js/releases/tag/2.0.2 [2.0.1]: https://github.com/svgdotjs/svg.js/releases/tag/2.0.1 [2.0.0]: https://github.com/svgdotjs/svg.js/releases/tag/2.0.0 - [1.0.0-rc.9]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.9 [1.0.0-rc.8]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.8 [1.0.0-rc.7]: https://github.com/svgdotjs/svg.js/releases/tag/1.0.0-rc.7 @@ -650,7 +1118,6 @@ The document follows the conventions described in [ā€œKeep a CHANGELOGā€](http: [v1.0rc3]: https://github.com/svgdotjs/svg.js/releases/tag/1.0rc3 [v1.0rc2]: https://github.com/svgdotjs/svg.js/releases/tag/1.0rc2 [v1.0rc1]: https://github.com/svgdotjs/svg.js/releases/tag/1.0rc1 - [v0.38]: https://github.com/svgdotjs/svg.js/releases/tag/0.38 [v0.37]: https://github.com/svgdotjs/svg.js/releases/tag/0.37 [v0.36]: https://github.com/svgdotjs/svg.js/releases/tag/0.36 diff --git a/LICENSE.txt b/LICENSE.txt index 148b70af9..41b1b1086 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2012-2017 Wout Fierens +Copyright (c) 2012-2018 Wout Fierens https://svgdotjs.github.io/ Permission is hereby granted, free of charge, to any person obtaining diff --git a/README.md b/README.md index b88c5a54a..e02fcd103 100644 --- a/README.md +++ b/README.md @@ -2,28 +2,33 @@ [![Build Status](https://travis-ci.org/svgdotjs/svg.js.svg?branch=master)](https://travis-ci.org/svgdotjs/svg.js) [![Coverage Status](https://coveralls.io/repos/github/svgdotjs/svg.js/badge.svg?branch=master)](https://coveralls.io/github/svgdotjs/svg.js?branch=master) -[![CDNJS](https://img.shields.io/cdnjs/v/svg.js.svg)](https://cdnjs.com/libraries/svg.js) +[![Cdnjs](https://img.shields.io/cdnjs/v/svg.js.svg)](https://cdnjs.com/libraries/svg.js) +[![jsdelivr](https://badgen.net/jsdelivr/v/npm/@svgdotjs/svg.js)](https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js) [![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) +[![Twitter](https://img.shields.io/badge/Twitter-@svg__js-green.svg)](https://twitter.com/svg_js) -__A lightweight library for manipulating and animating SVG, without any dependencies.__ +**A lightweight library for manipulating and animating SVG, without any dependencies.** SVG.js is licensed under the terms of the MIT License. ## Installation -#### Bower: +#### Npm: -`bower install svg.js` +`npm install @svgdotjs/svg.js` -#### Node: +#### Yarn: -`npm install svg.js` +`yarn add @svgdotjs/svg.js` -#### Cdnjs: +#### CDNs: -[https://cdnjs.com/libraries/svg.js](https://cdnjs.com/libraries/svg.js) +[https://cdnjs.com/libraries/svg.js](https://cdnjs.com/libraries/svg.js) +[https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js](https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js) +[https://unpkg.com/@svgdotjs/svg.js](https://unpkg.com/@svgdotjs/svg.js) ## Documentation -Check [https://svgdotjs.github.io](https://svgdotjs.github.io/) to learn more. -[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=pay%40woutfierens.com&lc=US&item_name=SVG.JS¤cy_code=EUR&bn=PP-DonationsBF%3Abtn_donate_74x21.png%3ANonHostedGuest) +Check [svgjs.dev](https://svgjs.dev/docs/3.0/) to learn more. + +[![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¤cy_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) diff --git a/bench/runner.html b/bench/runner.html index 719f38569..9ccb5e3b5 100644 --- a/bench/runner.html +++ b/bench/runner.html @@ -1,51 +1,63 @@ - + - - SVG.js benchmarker - - - -
- - - - - - - + + SVG.js benchmarker + + + +
+ + + + + + + + + + diff --git a/bench/svg.bench.js b/bench/svg.bench.js index 49ff222ca..bec92a583 100644 --- a/bench/svg.bench.js +++ b/bench/svg.bench.js @@ -1,73 +1,77 @@ -;( function() { +/* global Snap */ +;(function () { SVG.bench = { // Initalize test store - _chain: [] - , _before: function() {} - , _after: function() {} - , draw: SVG('draw') - , snap: Snap(100, 100) - , raw: document.getElementById('native') + _chain: [], + _before: function () {}, + _after: function () {}, + draw: SVG().addTo('#draw'), + snap: Snap(100, 100), + raw: document.getElementById('native'), // Add descriptor - , describe: function(name, closure) { + describe: function (name, closure) { this._chain.push({ - name: name - , run: closure + name: name, + run: closure }) return this - } + }, // Add test - , test: function(name, run) { + test: function (name, run) { // run test - var start = ( new Date ).getTime() + var start = new Date().getTime() run() - this.write( name, ( new Date ).getTime() - start ) + this.write(name, new Date().getTime() - start) // clear everything this.clear() - } + }, // Skip test - , skip: function(name, run) { - this.write( name, false ) - } + skip: function (name, run) { + this.write(name, false) + }, // Run tests - , run: function() { + run: function () { this.pad() - + for (var h, i = 0, il = this._chain.length; i < il; i++) { - var h = document.createElement('h1') + h = document.createElement('h1') h.innerHTML = this._chain[i].name - this.pad().appendChild(h) - this._chain[i].run(this) } - } - + }, + // Write result - , write: function(name, ms) { + write: function (name, ms) { var test = document.createElement('div') if (typeof ms === 'number') { test.className = 'test' - test.innerHTML = '' + name + ' completed in ' + ms + 'ms' + test.innerHTML = + '' + + name + + ' completed in ' + + ms + + 'ms' } else { test.className = 'test skipped' test.innerHTML = name + ' (skipped)' } - + this.pad().appendChild(test) return this - } + }, // Reference writable element - , pad: function() { + pad: function () { var pad = document.getElementById('pad') if (!pad) { @@ -76,15 +80,15 @@ } return pad - } + }, // Clear canvasses - , clear: function() { - while(this.raw.hasChildNodes()) + clear: function () { + while (this.raw.hasChildNodes()) { this.raw.removeChild(this.raw.lastChild) + } this.draw.clear() this.snap.clear() } } - -})(); \ No newline at end of file +})() diff --git a/bench/tests/10000-accesses.js b/bench/tests/10000-accesses.js new file mode 100644 index 000000000..ed149e158 --- /dev/null +++ b/bench/tests/10000-accesses.js @@ -0,0 +1,37 @@ +SVG.bench.describe( + 'Access a dom attributes vs dom properties vs object properties', + function (bench) { + bench.test('using an object', function () { + var sum = 0 + var obj = { x: '30' } + for (var i = 0; i < 1000000; i++) { + sum += obj.x * i + } + console.log(sum) + }) + + bench.test('figure out what the overhead is', function () { + var obj = bench.draw.rect(100, 100).move(0, 0) + }) + + bench.test('using dom attributes', function () { + var sum = 0 + var obj = bench.draw.rect(100, 100).move(0, 0) + var node = obj.node + for (var i = 0; i < 1000000; i++) { + sum += node.getAttribute('x') * i + } + console.log(sum, node.getAttribute('x')) + }) + + bench.test('using dom properties', function () { + var sum = 0 + var obj = bench.draw.rect(100, 100).move(0, 0) + var node = obj.node + for (var i = 0; i < 1000000; i++) { + sum += node.x.baseVal * i + } + console.log(sum, node.x) + }) + } +) diff --git a/bench/tests/10000-boxes.js b/bench/tests/10000-boxes.js new file mode 100644 index 000000000..e20693b5d --- /dev/null +++ b/bench/tests/10000-boxes.js @@ -0,0 +1,52 @@ +SVG.bench.describe('Generate 100000 bbox', function (bench) { + var rect = bench.draw.rect(100, 100) + + bench.test('using SVG.js v3.0.0', function () { + for (var i = 0; i < 100000; i++) rect.bbox() + }) + //bench.test('using vanilla js', function() { + // var node = rect.node + // for (var i = 0; i < 10000; i++) { + // node.getBBox() + // } + //}) + //bench.test('using Snap.svg v0.5.1', function() { + // for (var i = 0; i < 10000; i++) + // bench.snap.rect(50, 50, 100, 100) + //}) +}) + +SVG.bench.describe('Generate 100000 rbox', function (bench) { + var rect = bench.draw.rect(100, 100) + + bench.test('using SVG.js v3.0.0', function () { + for (var i = 0; i < 100000; i++) rect.bbox() + }) + //bench.test('using vanilla js', function() { + // var node = rect.node + // for (var i = 0; i < 10000; i++) { + // node.getBoundingClientRect() + // } + //}) + //bench.test('using Snap.svg v0.5.1', function() { + // for (var i = 0; i < 10000; i++) + // bench.snap.rect(50, 50, 100, 100) + //}) +}) +SVG.bench.describe('Generate 100000 viewbox', function (bench) { + var nested = bench.draw.nested().viewbox(10, 10, 100, 100) + + bench.test('using SVG.js v3.0.0', function () { + for (var i = 0; i < 100000; i++) nested.viewbox() + }) + //bench.test('using vanilla js', function() { + // var node = rect.node + // for (var i = 0; i < 10000; i++) { + // node.getAttribute('viewBox') + // } + //}) + //bench.test('using Snap.svg v0.5.1', function() { + // for (var i = 0; i < 10000; i++) + // bench.snap.rect(50, 50, 100, 100) + //}) +}) diff --git a/bench/tests/10000-circles.js b/bench/tests/10000-circles.js index f090281a2..fcfbf9a1e 100644 --- a/bench/tests/10000-circles.js +++ b/bench/tests/10000-circles.js @@ -1,9 +1,8 @@ -SVG.bench.describe('Generate 10000 circles', function(bench) { - bench.test('using SVG.js v2.5.3', function() { - for (var i = 0; i < 10000; i++) - bench.draw.circle(100,100) +SVG.bench.describe('Generate 10000 circles', function (bench) { + bench.test('using SVG.js v2.5.3', function () { + for (var i = 0; i < 10000; i++) bench.draw.circle(100, 100) }) - bench.test('using vanilla js', function() { + bench.test('using vanilla js', function () { for (var i = 0; i < 10000; i++) { var circle = document.createElementNS(SVG.ns, 'circle') circle.setAttributeNS(null, 'rx', 50) @@ -11,18 +10,16 @@ SVG.bench.describe('Generate 10000 circles', function(bench) { bench.raw.appendChild(circle) } }) - bench.test('using Snap.svg v0.5.1', function() { - for (var i = 0; i < 10000; i++) - bench.snap.circle(50, 50, 100, 100) + bench.test('using Snap.svg v0.5.1', function () { + for (var i = 0; i < 10000; i++) bench.snap.circle(50, 50, 100, 100) }) }) -SVG.bench.describe('Generate 10000 circles with fill', function(bench) { - bench.test('using SVG.js v2.5.3', function() { - for (var i = 0; i < 10000; i++) - bench.draw.circle(100,100).fill('#f06') +SVG.bench.describe('Generate 10000 circles with fill', function (bench) { + bench.test('using SVG.js v2.5.3', function () { + for (var i = 0; i < 10000; i++) bench.draw.circle(100, 100).fill('#f06') }) - bench.test('using vanilla js', function() { + bench.test('using vanilla js', function () { for (var i = 0; i < 10000; i++) { var circle = document.createElementNS(SVG.ns, 'circle') circle.setAttributeNS(null, 'rx', 50) @@ -31,8 +28,8 @@ SVG.bench.describe('Generate 10000 circles with fill', function(bench) { bench.raw.appendChild(circle) } }) - bench.test('using Snap.svg v0.5.1', function() { + bench.test('using Snap.svg v0.5.1', function () { for (var i = 0; i < 10000; i++) bench.snap.circle(50, 50, 100, 100).attr('fill', '#f06') }) -}) \ No newline at end of file +}) diff --git a/bench/tests/10000-each.js b/bench/tests/10000-each.js index c47eb60f0..e8170944c 100644 --- a/bench/tests/10000-each.js +++ b/bench/tests/10000-each.js @@ -1,27 +1,24 @@ -SVG.bench.describe('each() vs forEach()', function(bench) { +SVG.bench.describe('each() vs forEach()', function (bench) { // preparation var list = [] - for (var i = 99; i >= 0; i--) - list.push(bench.draw.rect(100, 50)) + for (var i = 99; i >= 0; i--) list.push(bench.draw.rect(100, 50)) var set = new SVG.Set(list) - - bench.test('10000 x each()', function() { + bench.test('10000 x each()', function () { for (var i = 0; i < 10000; i++) { - set.each(function() { + set.each(function () { this.fill('#f06') }) } }) - bench.test('10000 x forEach()', function() { + bench.test('10000 x forEach()', function () { for (var i = 0; i < 10000; i++) { - list.forEach(function(e) { + list.forEach(function (e) { e.fill('#f06') }) } }) - -}) \ No newline at end of file +}) diff --git a/bench/tests/10000-pathArray-bbox.js b/bench/tests/10000-pathArray-bbox.js new file mode 100644 index 000000000..42640736e --- /dev/null +++ b/bench/tests/10000-pathArray-bbox.js @@ -0,0 +1,64 @@ +SVG.bench.describe('Generate 10000 pathArrays bbox', function (bench) { + var data = + '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' + + var data2 = + '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 ' + + var data3 = 'M10 10-45-30.5.5 .89L2e-2.5.5.5-.5C.5.5.5.5.5.5L-3-4z' + + var draw = SVG().addTo('body') + var path = SVG.create('path') + path.style.visibility = 'hidden' + + bench.test('using SVG.js v3.0.0', function () { + for (var i = 0; i < 10000; i++) { + SVG.parser.path.setAttribute('d', data) + SVG.parser.path.getBBox() + } + }) + + bench.test('using SVG.js v3.0.0 more data', function () { + for (var i = 0; i < 10000; i++) { + SVG.parser.path.setAttribute('d', data2) + SVG.parser.path.getBBox() + } + }) + + bench.test('using SVG.js v3.0.0 complicated data', function () { + for (var i = 0; i < 10000; i++) { + SVG.parser.path.setAttribute('d', data3) + SVG.parser.path.getBBox() + } + }) + + bench.test('using SVG.js v3.0.0 without parser', function () { + for (var i = 0; i < 10000; i++) { + path.setAttribute('d', data) + draw.node.appendChild(path) + path.getBBox() + } + //new SVG.Path().attr('d', data).addTo(draw).bbox() + }) + + bench.test('using SVG.js v3.0.0 more data without parser', function () { + for (var i = 0; i < 10000; i++) { + path.setAttribute('d', data2) + draw.node.appendChild(path) + path.getBBox() + } + //new SVG.Path().attr('d', data2).addTo(draw).bbox() + }) + + bench.test( + 'using SVG.js v3.0.0 complicated data without parser', + function () { + for (var i = 0; i < 10000; i++) { + path.setAttribute('d', data3) + draw.node.appendChild(path) + path.getBBox() + } + //new SVG.Path().attr('d', data3).addTo(draw).bbox() + } + ) +}) diff --git a/bench/tests/10000-pathArrays.js b/bench/tests/10000-pathArrays.js index a4c0fdc3a..72bd74f2e 100644 --- a/bench/tests/10000-pathArrays.js +++ b/bench/tests/10000-pathArrays.js @@ -1,22 +1,21 @@ -SVG.bench.describe('Generate 10000 pathArrays', function(bench) { - var data = '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' - - var data2 = '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 ' +SVG.bench.describe('Generate 10000 pathArrays', function (bench) { + var data = + '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' + + var data2 = + '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 ' var data3 = 'M10 10-45-30.5.5 .89L2e-2.5.5.5-.5C.5.5.5.5.5.5L-3-4z' - - bench.test('using SVG.js v2.5.3', function() { - for (var i = 0; i < 10000; i++) - new SVG.PathArray(data) + + bench.test('using SVG.js v2.5.3', function () { + for (var i = 0; i < 10000; i++) new SVG.PathArray(data) }) - - bench.test('using SVG.js v2.5.3 more data', function() { - for (var i = 0; i < 10000; i++) - new SVG.PathArray(data2) + + bench.test('using SVG.js v2.5.3 more data', function () { + for (var i = 0; i < 10000; i++) new SVG.PathArray(data2) }) - - bench.test('using SVG.js v2.5.3 complicated data', function() { - for (var i = 0; i < 10000; i++) - new SVG.PathArray(data3) + + bench.test('using SVG.js v2.5.3 complicated data', function () { + for (var i = 0; i < 10000; i++) new SVG.PathArray(data3) }) -}) \ No newline at end of file +}) diff --git a/bench/tests/10000-paths.js b/bench/tests/10000-paths.js index cdf1d2215..2e96c5f26 100644 --- a/bench/tests/10000-paths.js +++ b/bench/tests/10000-paths.js @@ -1,19 +1,18 @@ -SVG.bench.describe('Generate 10000 paths', function(bench) { - var data = 'M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100' +SVG.bench.describe('Generate 10000 paths', function (bench) { + var data = + 'M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100' - bench.test('using SVG.js v2.5.3', function() { - for (var i = 0; i < 10000; i++) - bench.draw.path(data) + bench.test('using SVG.js v2.5.3', function () { + for (var i = 0; i < 10000; i++) bench.draw.path(data) }) - bench.test('using vanilla js', function() { + bench.test('using vanilla js', function () { for (var i = 0; i < 10000; i++) { var path = document.createElementNS(SVG.ns, 'path') path.setAttributeNS(null, 'd', data) bench.raw.appendChild(path) } }) - bench.test('using Snap.svg v0.5.1', function() { - for (var i = 0; i < 10000; i++) - bench.snap.path(data) + bench.test('using Snap.svg v0.5.1', function () { + for (var i = 0; i < 10000; i++) bench.snap.path(data) }) -}) \ No newline at end of file +}) diff --git a/bench/tests/10000-pointArray-bbox.js b/bench/tests/10000-pointArray-bbox.js new file mode 100644 index 000000000..8c9c42ce1 --- /dev/null +++ b/bench/tests/10000-pointArray-bbox.js @@ -0,0 +1,52 @@ +SVG.bench.describe('Generate 10000 pathArrays bbox', function (bench) { + var data = + '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' + + var dataArr = [ + [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] + ] + + bench.test('using SVG.js v3.0.0', function () { + for (var i = 0; i < 10000; i++) { + SVG.parser.poly.setAttribute('points', data) + SVG.parser.poly.getBBox() + } + }) + + bench.test('using SVG.js v3.0.0 without parser', function () { + for (var i = 0; i < 10000; i++) { + var maxX = -Infinity, + maxY = -Infinity, + minX = Infinity, + minY = Infinity + dataArr.forEach(function (el) { + maxX = Math.max(el[0], maxX) + maxY = Math.max(el[1], maxY) + minX = Math.min(el[0], minX) + minY = Math.min(el[1], minY) + }) + var a = { x: minX, y: minY, width: maxX - minX, height: maxY - minY } + } + //new SVG.Path().attr('d', data).addTo(draw).bbox() + }) +}) diff --git a/bench/tests/10000-rects.js b/bench/tests/10000-rects.js index aea0c7975..884e3a537 100644 --- a/bench/tests/10000-rects.js +++ b/bench/tests/10000-rects.js @@ -1,9 +1,8 @@ -SVG.bench.describe('Generate 10000 rects', function(bench) { - bench.test('using SVG.js v2.5.3', function() { - for (var i = 0; i < 10000; i++) - bench.draw.rect(100,100) +SVG.bench.describe('Generate 10000 rects', function (bench) { + bench.test('using SVG.js v3.0.6', function () { + for (var i = 0; i < 10000; i++) bench.draw.rect(100, 100) }) - bench.test('using vanilla js', function() { + bench.test('using vanilla js', function () { for (var i = 0; i < 10000; i++) { var rect = document.createElementNS(SVG.ns, 'rect') rect.setAttributeNS(null, 'height', 100) @@ -11,19 +10,16 @@ SVG.bench.describe('Generate 10000 rects', function(bench) { bench.raw.appendChild(rect) } }) - bench.test('using Snap.svg v0.5.1', function() { - for (var i = 0; i < 10000; i++) - bench.snap.rect(50, 50, 100, 100) + bench.test('using Snap.svg v0.5.1', function () { + for (var i = 0; i < 10000; i++) bench.snap.rect(50, 50, 100, 100) }) }) - -SVG.bench.describe('Generate 10000 rects with fill', function(bench) { - bench.test('using SVG.js v2.5.3', function() { - for (var i = 0; i < 10000; i++) - bench.draw.rect(100,100).fill('#f06') +SVG.bench.describe('Generate 10000 rects with fill', function (bench) { + bench.test('using SVG.js v3.0.6', function () { + for (var i = 0; i < 10000; i++) bench.draw.rect(100, 100).fill('#f06') }) - bench.test('using vanilla js', function() { + bench.test('using vanilla js', function () { for (var i = 0; i < 10000; i++) { var rect = document.createElementNS(SVG.ns, 'rect') rect.setAttributeNS(null, 'height', 100) @@ -32,49 +28,50 @@ SVG.bench.describe('Generate 10000 rects with fill', function(bench) { bench.raw.appendChild(rect) } }) - bench.test('using Snap.svg v0.5.1', function() { + bench.test('using Snap.svg v0.5.1', function () { for (var i = 0; i < 10000; i++) bench.snap.rect(50, 50, 100, 100).attr('fill', '#f06') }) }) - -SVG.bench.describe('Generate 10000 rects with position and fill', function(bench) { - bench.test('using SVG.js v2.5.3', function() { - for (var i = 0; i < 10000; i++) - bench.draw.rect(100,100).move(50,50).fill('#f06') - }) - bench.test('using vanilla js', function() { +SVG.bench.describe( + 'Generate 10000 rects with position and fill', + function (bench) { + bench.test('using SVG.js v3.0.6', function () { + for (var i = 0; i < 10000; i++) + bench.draw.rect(100, 100).move(50, 50).fill('#f06') + }) + bench.test('using vanilla js', function () { + for (var i = 0; i < 10000; i++) { + var rect = document.createElementNS(SVG.ns, 'rect') + rect.setAttributeNS(null, 'height', 100) + rect.setAttributeNS(null, 'width', 100) + rect.setAttributeNS(null, 'fill', '#f06') + rect.setAttributeNS(null, 'x', 50) + rect.setAttributeNS(null, 'y', 50) + bench.raw.appendChild(rect) + } + }) + bench.test('using Snap.svg v0.5.1', function () { + for (var i = 0; i < 10000; i++) + bench.snap.rect(50, 50, 100, 100).attr('fill', '#f06') + }) + } +) + +SVG.bench.describe('Generate 10000 rects with gradient fill', function (bench) { + bench.test('using SVG.js v3.0.6', function () { for (var i = 0; i < 10000; i++) { - var rect = document.createElementNS(SVG.ns, 'rect') - rect.setAttributeNS(null, 'height', 100) - rect.setAttributeNS(null, 'width', 100) - rect.setAttributeNS(null, 'fill', '#f06') - rect.setAttributeNS(null, 'x', 50) - rect.setAttributeNS(null, 'y', 50) - bench.raw.appendChild(rect) - } - }) - bench.test('using Snap.svg v0.5.1', function() { - for (var i = 0; i < 10000; i++) - bench.snap.rect(50, 50, 100, 100).attr('fill', '#f06') - }) -}) - - -SVG.bench.describe('Generate 10000 rects with gradient fill', function(bench) { - bench.test('using SVG.js v2.5.3', function() { - for (var i = 0; i < 10000; i++) { - var g = bench.draw.gradient('linear', function(stop) { - stop.at(0, '#000') - stop.at(0.25, '#f00') - stop.at(1, '#fff') + var g = bench.draw.gradient('linear', function (add) { + add.stop(0, '#000') + add.stop(0.25, '#f00') + add.stop(1, '#fff') }) - bench.draw.rect(100,100).fill(g) + bench.draw.rect(100, 100).fill(g) } }) - bench.test('using vanilla js', function() { + bench.test('using vanilla js', function () { for (var i = 0; i < 10000; i++) { var g = document.createElementNS(SVG.ns, 'linearGradient') var stop = document.createElementNS(SVG.ns, 'stop') @@ -98,9 +95,9 @@ SVG.bench.describe('Generate 10000 rects with gradient fill', function(bench) { bench.raw.appendChild(rect) } }) - bench.test('using Snap.svg v0.5.1', function() { + bench.test('using Snap.svg v0.5.1', function () { for (var i = 0; i < 10000; i++) { - var g = bench.snap.gradient("L(0, 0, 100, 100)#000-#f00:25%-#fff") + var g = bench.snap.gradient('L(0, 0, 100, 100)#000-#f00:25%-#fff') bench.snap.rect(50, 50, 100, 100).attr({ fill: g @@ -108,11 +105,3 @@ SVG.bench.describe('Generate 10000 rects with gradient fill', function(bench) { } }) }) - - - - - - - - diff --git a/bench/tests/10000-textContent.js b/bench/tests/10000-textContent.js new file mode 100644 index 000000000..cb0df7716 --- /dev/null +++ b/bench/tests/10000-textContent.js @@ -0,0 +1,21 @@ +SVG.bench.describe('Change textContent 10000 times', function (bench) { + var data = + 'M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100' + + var node = bench.draw.plain('').node + + bench.test('using appendChild', function () { + for (var i = 0; i < 1000000; i++) { + while (node.hasChildNodes()) { + node.removeChild(node.lastChild) + } + + node.appendChild(document.createTextNode('test' + i)) + } + }) + bench.test('using textContent', function () { + for (var i = 0; i < 1000000; i++) { + node.textContent = 'test' + i + } + }) +}) diff --git a/bench/tests/10000-transform.js b/bench/tests/10000-transform.js new file mode 100644 index 000000000..9e22d0961 --- /dev/null +++ b/bench/tests/10000-transform.js @@ -0,0 +1,29 @@ +SVG.bench.describe('Transform 1000000 rects', function (bench) { + let parameters = { + translate: [20, 30], + origin: [100, 100], + rotate: 25, + skew: [10, 30], + scale: 0.5 + } + + let matrixLike = { a: 2, b: 3, c: 1, d: 2, e: 49, f: 100 } + let matrix = new SVG.Matrix(matrixLike) + + let worker = new SVG.Matrix() + bench.test('with parameters', function () { + for (var i = 0; i < 1000000; i++) worker.transform(parameters) + }) + + worker = new SVG.Matrix() + bench.test('with matrix like', function () { + for (var i = 0; i < 1000000; i++) { + worker.transform(matrixLike) + } + }) + + worker = new SVG.Matrix() + bench.test('with SVG.Matrix', function () { + for (var i = 0; i < 1000000; i++) worker.transform(matrix) + }) +}) diff --git a/bower.json b/bower.json deleted file mode 100644 index 8480d1183..000000000 --- a/bower.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "svg.js", - "homepage": "https://svgdotjs.github.io/", - "authors": [ - "Wout Fierens ", - "Ulrich-Matthias SchƤfer ", - "Jon Ege Ronnenberg " - ], - "description": "A lightweight library for manipulating and animating SVG", - "main": "dist/svg.js", - "keywords": [ - "svg", "vector", "graphics" - ], - "license": "MIT", - "ignore": [ - "**/.*", - "spec/", - "src/", - "gulpfile.js" - ] -} \ No newline at end of file diff --git a/dist/svg.js b/dist/svg.js deleted file mode 100644 index d7e93d0ce..000000000 --- a/dist/svg.js +++ /dev/null @@ -1,5551 +0,0 @@ -/*! -* svg.js - A lightweight library for manipulating and animating SVG. -* @version 2.6.2 -* https://svgdotjs.github.io/ -* -* @copyright Wout Fierens -* @license MIT -* -* BUILT: Fri Jun 30 2017 23:15:13 GMT+0200 (MitteleuropƤische Sommerzeit) -*/; -(function(root, factory) { - /* istanbul ignore next */ - if (typeof define === 'function' && define.amd) { - define(function(){ - return factory(root, root.document) - }) - } else if (typeof exports === 'object') { - module.exports = root.document ? factory(root, root.document) : function(w){ return factory(w, w.document) } - } else { - root.SVG = factory(root, root.document) - } -}(typeof window !== "undefined" ? window : this, function(window, document) { - -// The main wrapping element -var SVG = this.SVG = function(element) { - if (SVG.supported) { - element = new SVG.Doc(element) - - if(!SVG.parser.draw) - SVG.prepare() - - return element - } -} - -// Default namespaces -SVG.ns = 'http://www.w3.org/2000/svg' -SVG.xmlns = 'http://www.w3.org/2000/xmlns/' -SVG.xlink = 'http://www.w3.org/1999/xlink' -SVG.svgjs = 'http://svgjs.com/svgjs' - -// Svg support test -SVG.supported = (function() { - return !! document.createElementNS && - !! document.createElementNS(SVG.ns,'svg').createSVGRect -})() - -// Don't bother to continue if SVG is not supported -if (!SVG.supported) return false - -// Element id sequence -SVG.did = 1000 - -// Get next named element id -SVG.eid = function(name) { - return 'Svgjs' + capitalize(name) + (SVG.did++) -} - -// Method for element creation -SVG.create = function(name) { - // create element - var element = document.createElementNS(this.ns, name) - - // apply unique id - element.setAttribute('id', this.eid(name)) - - return element -} - -// Method for extending objects -SVG.extend = function() { - var modules, methods, key, i - - // Get list of modules - modules = [].slice.call(arguments) - - // Get object with extensions - methods = modules.pop() - - for (i = modules.length - 1; i >= 0; i--) - if (modules[i]) - for (key in methods) - modules[i].prototype[key] = methods[key] - - // Make sure SVG.Set inherits any newly added methods - if (SVG.Set && SVG.Set.inherit) - SVG.Set.inherit() -} - -// Invent new element -SVG.invent = function(config) { - // Create element initializer - var initializer = typeof config.create == 'function' ? - config.create : - function() { - this.constructor.call(this, SVG.create(config.create)) - } - - // Inherit prototype - if (config.inherit) - initializer.prototype = new config.inherit - - // Extend with methods - if (config.extend) - SVG.extend(initializer, config.extend) - - // Attach construct method to parent - if (config.construct) - SVG.extend(config.parent || SVG.Container, config.construct) - - return initializer -} - -// Adopt existing svg elements -SVG.adopt = function(node) { - // check for presence of node - if (!node) return null - - // make sure a node isn't already adopted - if (node.instance) return node.instance - - // initialize variables - var element - - // adopt with element-specific settings - if (node.nodeName == 'svg') - element = node.parentNode instanceof window.SVGElement ? new SVG.Nested : new SVG.Doc - else if (node.nodeName == 'linearGradient') - element = new SVG.Gradient('linear') - else if (node.nodeName == 'radialGradient') - element = new SVG.Gradient('radial') - else if (SVG[capitalize(node.nodeName)]) - element = new SVG[capitalize(node.nodeName)] - else - element = new SVG.Element(node) - - // ensure references - element.type = node.nodeName - element.node = node - node.instance = element - - // SVG.Class specific preparations - if (element instanceof SVG.Doc) - element.namespace().defs() - - // pull svgjs data from the dom (getAttributeNS doesn't work in html5) - element.setData(JSON.parse(node.getAttribute('svgjs:data')) || {}) - - return element -} - -// Initialize parsing element -SVG.prepare = function() { - // Select document body and create invisible svg element - var body = document.getElementsByTagName('body')[0] - , draw = (body ? new SVG.Doc(body) : SVG.adopt(document.documentElement).nested()).size(2, 0) - - // Create parser object - SVG.parser = { - body: body || document.documentElement - , draw: draw.style('opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden').node - , poly: draw.polyline().node - , path: draw.path().node - , native: SVG.create('svg') - } -} - -SVG.parser = { - native: SVG.create('svg') -} - -document.addEventListener('DOMContentLoaded', function() { - if(!SVG.parser.draw) - SVG.prepare() -}, false) - -// Storage for regular expressions -SVG.regex = { - // Parse unit value - numberAndUnit: /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i - - // Parse hex value -, hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i - - // Parse rgb value -, rgb: /rgb\((\d+),(\d+),(\d+)\)/ - - // Parse reference id -, reference: /#([a-z0-9\-_]+)/i - - // splits a transformation chain -, transforms: /\)\s*,?\s*/ - - // Whitespace -, whitespace: /\s/g - - // Test hex value -, isHex: /^#[a-f0-9]{3,6}$/i - - // Test rgb value -, isRgb: /^rgb\(/ - - // Test css declaration -, isCss: /[^:]+:[^;]+;?/ - - // Test for blank string -, isBlank: /^(\s+)?$/ - - // Test for numeric string -, isNumber: /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i - - // Test for percent value -, isPercent: /^-?[\d\.]+%$/ - - // Test for image url -, isImage: /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i - - // split at whitespace and comma -, delimiter: /[\s,]+/ - - // The following regex are used to parse the d attribute of a path - - // Matches all hyphens which are not after an exponent -, hyphen: /([^e])\-/gi - - // Replaces and tests for all path letters -, pathLetters: /[MLHVCSQTAZ]/gi - - // yes we need this one, too -, isPathLetter: /[MLHVCSQTAZ]/i - - // matches 0.154.23.45 -, numbersWithDots: /((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi - - // matches . -, dots: /\./g -} - -SVG.utils = { - // Map function - map: function(array, block) { - var i - , il = array.length - , result = [] - - for (i = 0; i < il; i++) - result.push(block(array[i])) - - return result - } - - // Filter function -, filter: function(array, block) { - var i - , il = array.length - , result = [] - - for (i = 0; i < il; i++) - if (block(array[i])) - result.push(array[i]) - - return result - } - - // Degrees to radians -, radians: function(d) { - return d % 360 * Math.PI / 180 - } - - // Radians to degrees -, degrees: function(r) { - return r * 180 / Math.PI % 360 - } - -, filterSVGElements: function(nodes) { - return this.filter( nodes, function(el) { return el instanceof window.SVGElement }) - } - -} - -SVG.defaults = { - // Default attribute values - attrs: { - // fill and stroke - 'fill-opacity': 1 - , 'stroke-opacity': 1 - , 'stroke-width': 0 - , 'stroke-linejoin': 'miter' - , 'stroke-linecap': 'butt' - , fill: '#000000' - , stroke: '#000000' - , opacity: 1 - // position - , x: 0 - , y: 0 - , cx: 0 - , cy: 0 - // size - , width: 0 - , height: 0 - // radius - , r: 0 - , rx: 0 - , ry: 0 - // gradient - , offset: 0 - , 'stop-opacity': 1 - , 'stop-color': '#000000' - // text - , 'font-size': 16 - , 'font-family': 'Helvetica, Arial, sans-serif' - , 'text-anchor': 'start' - } - -} -// Module for color convertions -SVG.Color = function(color) { - var match - - // initialize defaults - this.r = 0 - this.g = 0 - this.b = 0 - - if(!color) return - - // parse color - if (typeof color === 'string') { - if (SVG.regex.isRgb.test(color)) { - // get rgb values - match = SVG.regex.rgb.exec(color.replace(SVG.regex.whitespace,'')) - - // parse numeric values - this.r = parseInt(match[1]) - this.g = parseInt(match[2]) - this.b = parseInt(match[3]) - - } else if (SVG.regex.isHex.test(color)) { - // get hex values - match = SVG.regex.hex.exec(fullHex(color)) - - // parse numeric values - this.r = parseInt(match[1], 16) - this.g = parseInt(match[2], 16) - this.b = parseInt(match[3], 16) - - } - - } else if (typeof color === 'object') { - this.r = color.r - this.g = color.g - this.b = color.b - - } - -} - -SVG.extend(SVG.Color, { - // Default to hex conversion - toString: function() { - return this.toHex() - } - // Build hex value -, toHex: function() { - return '#' - + compToHex(this.r) - + compToHex(this.g) - + compToHex(this.b) - } - // Build rgb value -, toRgb: function() { - return 'rgb(' + [this.r, this.g, this.b].join() + ')' - } - // Calculate true brightness -, brightness: function() { - return (this.r / 255 * 0.30) - + (this.g / 255 * 0.59) - + (this.b / 255 * 0.11) - } - // Make color morphable -, morph: function(color) { - this.destination = new SVG.Color(color) - - return this - } - // Get morphed color at given position -, at: function(pos) { - // make sure a destination is defined - if (!this.destination) return this - - // normalise pos - pos = pos < 0 ? 0 : pos > 1 ? 1 : pos - - // generate morphed color - return new SVG.Color({ - r: ~~(this.r + (this.destination.r - this.r) * pos) - , g: ~~(this.g + (this.destination.g - this.g) * pos) - , b: ~~(this.b + (this.destination.b - this.b) * pos) - }) - } - -}) - -// Testers - -// Test if given value is a color string -SVG.Color.test = function(color) { - color += '' - return SVG.regex.isHex.test(color) - || SVG.regex.isRgb.test(color) -} - -// Test if given value is a rgb object -SVG.Color.isRgb = function(color) { - return color && typeof color.r == 'number' - && typeof color.g == 'number' - && typeof color.b == 'number' -} - -// Test if given value is a color -SVG.Color.isColor = function(color) { - return SVG.Color.isRgb(color) || SVG.Color.test(color) -} -// Module for array conversion -SVG.Array = function(array, fallback) { - array = (array || []).valueOf() - - // if array is empty and fallback is provided, use fallback - if (array.length == 0 && fallback) - array = fallback.valueOf() - - // parse array - this.value = this.parse(array) -} - -SVG.extend(SVG.Array, { - // Make array morphable - morph: function(array) { - this.destination = this.parse(array) - - // normalize length of arrays - if (this.value.length != this.destination.length) { - var lastValue = this.value[this.value.length - 1] - , lastDestination = this.destination[this.destination.length - 1] - - while(this.value.length > this.destination.length) - this.destination.push(lastDestination) - while(this.value.length < this.destination.length) - this.value.push(lastValue) - } - - return this - } - // Clean up any duplicate points -, settle: function() { - // find all unique values - for (var i = 0, il = this.value.length, seen = []; i < il; i++) - if (seen.indexOf(this.value[i]) == -1) - seen.push(this.value[i]) - - // set new value - return this.value = seen - } - // Get morphed array at given position -, at: function(pos) { - // make sure a destination is defined - if (!this.destination) return this - - // generate morphed array - for (var i = 0, il = this.value.length, array = []; i < il; i++) - array.push(this.value[i] + (this.destination[i] - this.value[i]) * pos) - - return new SVG.Array(array) - } - // Convert array to string -, toString: function() { - return this.value.join(' ') - } - // Real value -, valueOf: function() { - return this.value - } - // Parse whitespace separated string -, parse: function(array) { - array = array.valueOf() - - // if already is an array, no need to parse it - if (Array.isArray(array)) return array - - return this.split(array) - } - // Strip unnecessary whitespace -, split: function(string) { - return string.trim().split(SVG.regex.delimiter).map(parseFloat) - } - // Reverse array -, reverse: function() { - this.value.reverse() - - return this - } -, clone: function() { - var clone = new this.constructor() - clone.value = array_clone(this.value) - return clone - } -}) -// Poly points array -SVG.PointArray = function(array, fallback) { - SVG.Array.call(this, array, fallback || [[0,0]]) -} - -// Inherit from SVG.Array -SVG.PointArray.prototype = new SVG.Array -SVG.PointArray.prototype.constructor = SVG.PointArray - -SVG.extend(SVG.PointArray, { - // Convert array to string - toString: function() { - // convert to a poly point string - for (var i = 0, il = this.value.length, array = []; i < il; i++) - array.push(this.value[i].join(',')) - - return array.join(' ') - } - // Convert array to line object -, toLine: function() { - return { - x1: this.value[0][0] - , y1: this.value[0][1] - , x2: this.value[1][0] - , y2: this.value[1][1] - } - } - // Get morphed array at given position -, at: function(pos) { - // make sure a destination is defined - if (!this.destination) return this - - // generate morphed point string - for (var i = 0, il = this.value.length, array = []; i < il; i++) - array.push([ - this.value[i][0] + (this.destination[i][0] - this.value[i][0]) * pos - , this.value[i][1] + (this.destination[i][1] - this.value[i][1]) * pos - ]) - - return new SVG.PointArray(array) - } - // Parse point string and flat array -, parse: function(array) { - var points = [] - - array = array.valueOf() - - // if it is an array - if (Array.isArray(array)) { - // and it is not flat, there is no need to parse it - if(Array.isArray(array[0])) { - return array - } - } else { // Else, it is considered as a string - // parse points - array = array.trim().split(SVG.regex.delimiter).map(parseFloat) - } - - // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints - // Odd number of coordinates is an error. In such cases, drop the last odd coordinate. - if (array.length % 2 !== 0) array.pop() - - // wrap points in two-tuples and parse points as floats - for(var i = 0, len = array.length; i < len; i = i + 2) - points.push([ array[i], array[i+1] ]) - - return points - } - // Move point string -, move: function(x, y) { - var box = this.bbox() - - // get relative offset - x -= box.x - y -= box.y - - // move every point - if (!isNaN(x) && !isNaN(y)) - for (var i = this.value.length - 1; i >= 0; i--) - this.value[i] = [this.value[i][0] + x, this.value[i][1] + y] - - return this - } - // Resize poly string -, size: function(width, height) { - var i, box = this.bbox() - - // recalculate position of all points according to new size - for (i = this.value.length - 1; i >= 0; i--) { - if(box.width) this.value[i][0] = ((this.value[i][0] - box.x) * width) / box.width + box.x - if(box.height) this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y - } - - return this - } - // Get bounding box of points -, bbox: function() { - SVG.parser.poly.setAttribute('points', this.toString()) - - return SVG.parser.poly.getBBox() - } -}) - -var pathHandlers = { - M: function(c, p, p0) { - p.x = p0.x = c[0] - p.y = p0.y = c[1] - - return ['M', p.x, p.y] - }, - L: function(c, p) { - p.x = c[0] - p.y = c[1] - return ['L', c[0], c[1]] - }, - H: function(c, p) { - p.x = c[0] - return ['H', c[0]] - }, - V: function(c, p) { - p.y = c[0] - return ['V', c[0]] - }, - C: function(c, p) { - p.x = c[4] - p.y = c[5] - return ['C', c[0], c[1], c[2], c[3], c[4], c[5]] - }, - S: function(c, p) { - p.x = c[2] - p.y = c[3] - return ['S', c[0], c[1], c[2], c[3]] - }, - Q: function(c, p) { - p.x = c[2] - p.y = c[3] - return ['Q', c[0], c[1], c[2], c[3]] - }, - T: function(c, p) { - p.x = c[0] - p.y = c[1] - return ['T', c[0], c[1]] - }, - Z: function(c, p, p0) { - p.x = p0.x - p.y = p0.y - return ['Z'] - }, - A: function(c, p) { - p.x = c[5] - p.y = c[6] - return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]] - } -} - -var mlhvqtcsa = 'mlhvqtcsaz'.split('') - -for(var i = 0, il = mlhvqtcsa.length; i < il; ++i){ - pathHandlers[mlhvqtcsa[i]] = (function(i){ - return function(c, p, p0) { - if(i == 'H') c[0] = c[0] + p.x - else if(i == 'V') c[0] = c[0] + p.y - else if(i == 'A'){ - c[5] = c[5] + p.x, - c[6] = c[6] + p.y - } - else - for(var j = 0, jl = c.length; j < jl; ++j) { - c[j] = c[j] + (j%2 ? p.y : p.x) - } - - return pathHandlers[i](c, p, p0) - } - })(mlhvqtcsa[i].toUpperCase()) -} - -// Path points array -SVG.PathArray = function(array, fallback) { - SVG.Array.call(this, array, fallback || [['M', 0, 0]]) -} - -// Inherit from SVG.Array -SVG.PathArray.prototype = new SVG.Array -SVG.PathArray.prototype.constructor = SVG.PathArray - -SVG.extend(SVG.PathArray, { - // Convert array to string - toString: function() { - return arrayToString(this.value) - } - // Move path string -, move: function(x, y) { - // get bounding box of current situation - var box = this.bbox() - - // get relative offset - x -= box.x - y -= box.y - - if (!isNaN(x) && !isNaN(y)) { - // move every point - for (var l, i = this.value.length - 1; i >= 0; i--) { - l = this.value[i][0] - - if (l == 'M' || l == 'L' || l == 'T') { - this.value[i][1] += x - this.value[i][2] += y - - } else if (l == 'H') { - this.value[i][1] += x - - } else if (l == 'V') { - this.value[i][1] += y - - } else if (l == 'C' || l == 'S' || l == 'Q') { - this.value[i][1] += x - this.value[i][2] += y - this.value[i][3] += x - this.value[i][4] += y - - if (l == 'C') { - this.value[i][5] += x - this.value[i][6] += y - } - - } else if (l == 'A') { - this.value[i][6] += x - this.value[i][7] += y - } - - } - } - - return this - } - // Resize path string -, size: function(width, height) { - // get bounding box of current situation - var i, l, box = this.bbox() - - // recalculate position of all points according to new size - for (i = this.value.length - 1; i >= 0; i--) { - l = this.value[i][0] - - if (l == 'M' || l == 'L' || l == 'T') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x - this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y - - } else if (l == 'H') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x - - } else if (l == 'V') { - this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y - - } else if (l == 'C' || l == 'S' || l == 'Q') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x - this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y - this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x - this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y - - if (l == 'C') { - this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x - this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y - } - - } else if (l == 'A') { - // resize radii - this.value[i][1] = (this.value[i][1] * width) / box.width - this.value[i][2] = (this.value[i][2] * height) / box.height - - // move position values - this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x - this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y - } - - } - - return this - } - // Test if the passed path array use the same path data commands as this path array -, equalCommands: function(pathArray) { - var i, il, equalCommands - - pathArray = new SVG.PathArray(pathArray) - - equalCommands = this.value.length === pathArray.value.length - for(i = 0, il = this.value.length; equalCommands && i < il; i++) { - equalCommands = this.value[i][0] === pathArray.value[i][0] - } - - return equalCommands - } - // Make path array morphable -, morph: function(pathArray) { - pathArray = new SVG.PathArray(pathArray) - - if(this.equalCommands(pathArray)) { - this.destination = pathArray - } else { - this.destination = null - } - - return this - } - // Get morphed path array at given position -, at: function(pos) { - // make sure a destination is defined - if (!this.destination) return this - - var sourceArray = this.value - , destinationArray = this.destination.value - , array = [], pathArray = new SVG.PathArray() - , i, il, j, jl - - // Animate has specified in the SVG spec - // See: https://www.w3.org/TR/SVG11/paths.html#PathElement - for (i = 0, il = sourceArray.length; i < il; i++) { - array[i] = [sourceArray[i][0]] - for(j = 1, jl = sourceArray[i].length; j < jl; j++) { - array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos - } - // For the two flags of the elliptical arc command, the SVG spec say: - // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true - // Elliptical arc command as an array followed by corresponding indexes: - // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] - // 0 1 2 3 4 5 6 7 - if(array[i][0] === 'A') { - array[i][4] = +(array[i][4] != 0) - array[i][5] = +(array[i][5] != 0) - } - } - - // Directly modify the value of a path array, this is done this way for performance - pathArray.value = array - return pathArray - } - // Absolutize and parse path to array -, parse: function(array) { - // if it's already a patharray, no need to parse it - if (array instanceof SVG.PathArray) return array.valueOf() - - // prepare for parsing - var i, x0, y0, s, seg, arr - , x = 0 - , y = 0 - , paramCnt = { 'M':2, 'L':2, 'H':1, 'V':1, 'C':6, 'S':4, 'Q':4, 'T':2, 'A':7, 'Z':0 } - - if(typeof array == 'string'){ - - array = array - .replace(SVG.regex.numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123 - .replace(SVG.regex.pathLetters, ' $& ') // put some room between letters and numbers - .replace(SVG.regex.hyphen, '$1 -') // add space before hyphen - .trim() // trim - .split(SVG.regex.delimiter) // split into array - - }else{ - array = array.reduce(function(prev, curr){ - return [].concat.call(prev, curr) - }, []) - } - - // array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...] - var arr = [] - , p = new SVG.Point() - , p0 = new SVG.Point() - , index = 0 - , len = array.length - - do{ - // Test if we have a path letter - if(SVG.regex.isPathLetter.test(array[index])){ - s = array[index] - ++index - // If last letter was a move command and we got no new, it defaults to [L]ine - }else if(s == 'M'){ - s = 'L' - }else if(s == 'm'){ - s = 'l' - } - - arr.push(pathHandlers[s].call(null, - array.slice(index, (index = index + paramCnt[s.toUpperCase()])).map(parseFloat), - p, p0 - ) - ) - - }while(len > index) - - return arr - - } - // Get bounding box of path -, bbox: function() { - SVG.parser.path.setAttribute('d', this.toString()) - - return SVG.parser.path.getBBox() - } - -}) - -// Module for unit convertions -SVG.Number = SVG.invent({ - // Initialize - create: function(value, unit) { - // initialize defaults - this.value = 0 - this.unit = unit || '' - - // parse value - if (typeof value === 'number') { - // ensure a valid numeric value - this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value - - } else if (typeof value === 'string') { - unit = value.match(SVG.regex.numberAndUnit) - - if (unit) { - // make value numeric - this.value = parseFloat(unit[1]) - - // normalize - if (unit[5] == '%') - this.value /= 100 - else if (unit[5] == 's') - this.value *= 1000 - - // store unit - this.unit = unit[5] - } - - } else { - if (value instanceof SVG.Number) { - this.value = value.valueOf() - this.unit = value.unit - } - } - - } - // Add methods -, extend: { - // Stringalize - toString: function() { - return ( - this.unit == '%' ? - ~~(this.value * 1e8) / 1e6: - this.unit == 's' ? - this.value / 1e3 : - this.value - ) + this.unit - } - , toJSON: function() { - return this.toString() - } - , // Convert to primitive - valueOf: function() { - return this.value - } - // Add number - , plus: function(number) { - number = new SVG.Number(number) - return new SVG.Number(this + number, this.unit || number.unit) - } - // Subtract number - , minus: function(number) { - number = new SVG.Number(number) - return new SVG.Number(this - number, this.unit || number.unit) - } - // Multiply number - , times: function(number) { - number = new SVG.Number(number) - return new SVG.Number(this * number, this.unit || number.unit) - } - // Divide number - , divide: function(number) { - number = new SVG.Number(number) - return new SVG.Number(this / number, this.unit || number.unit) - } - // Convert to different unit - , to: function(unit) { - var number = new SVG.Number(this) - - if (typeof unit === 'string') - number.unit = unit - - return number - } - // Make number morphable - , morph: function(number) { - this.destination = new SVG.Number(number) - - if(number.relative) { - this.destination.value += this.value - } - - return this - } - // Get morphed number at given position - , at: function(pos) { - // Make sure a destination is defined - if (!this.destination) return this - - // Generate new morphed number - return new SVG.Number(this.destination) - .minus(this) - .times(pos) - .plus(this) - } - - } -}) - - -SVG.Element = SVG.invent({ - // Initialize node - create: function(node) { - // make stroke value accessible dynamically - this._stroke = SVG.defaults.attrs.stroke - this._event = null - - // initialize data object - this.dom = {} - - // create circular reference - if (this.node = node) { - this.type = node.nodeName - this.node.instance = this - - // store current attribute value - this._stroke = node.getAttribute('stroke') || this._stroke - } - } - - // Add class methods -, extend: { - // Move over x-axis - x: function(x) { - return this.attr('x', x) - } - // Move over y-axis - , y: function(y) { - return this.attr('y', y) - } - // Move by center over x-axis - , cx: function(x) { - return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2) - } - // Move by center over y-axis - , cy: function(y) { - return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2) - } - // Move element to given x and y values - , move: function(x, y) { - return this.x(x).y(y) - } - // Move element by its center - , center: function(x, y) { - return this.cx(x).cy(y) - } - // Set width of element - , width: function(width) { - return this.attr('width', width) - } - // Set height of element - , height: function(height) { - return this.attr('height', height) - } - // Set element size to given width and height - , size: function(width, height) { - var p = proportionalSize(this, width, height) - - return this - .width(new SVG.Number(p.width)) - .height(new SVG.Number(p.height)) - } - // Clone element - , clone: function(parent, withData) { - // write dom data to the dom so the clone can pickup the data - this.writeDataToDom() - - // clone element and assign new id - var clone = assignNewId(this.node.cloneNode(true)) - - // insert the clone in the given parent or after myself - if(parent) parent.add(clone) - else this.after(clone) - - return clone - } - // Remove element - , remove: function() { - if (this.parent()) - this.parent().removeElement(this) - - return this - } - // Replace element - , replace: function(element) { - this.after(element).remove() - - return element - } - // Add element to given container and return self - , addTo: function(parent) { - return parent.put(this) - } - // Add element to given container and return container - , putIn: function(parent) { - return parent.add(this) - } - // Get / set id - , id: function(id) { - return this.attr('id', id) - } - // Checks whether the given point inside the bounding box of the element - , inside: function(x, y) { - var box = this.bbox() - - return x > box.x - && y > box.y - && x < box.x + box.width - && y < box.y + box.height - } - // Show element - , show: function() { - return this.style('display', '') - } - // Hide element - , hide: function() { - return this.style('display', 'none') - } - // Is element visible? - , visible: function() { - return this.style('display') != 'none' - } - // Return id on string conversion - , toString: function() { - return this.attr('id') - } - // Return array of classes on the node - , classes: function() { - var attr = this.attr('class') - - return attr == null ? [] : attr.trim().split(SVG.regex.delimiter) - } - // Return true if class exists on the node, false otherwise - , hasClass: function(name) { - return this.classes().indexOf(name) != -1 - } - // Add class to the node - , addClass: function(name) { - if (!this.hasClass(name)) { - var array = this.classes() - array.push(name) - this.attr('class', array.join(' ')) - } - - return this - } - // Remove class from the node - , removeClass: function(name) { - if (this.hasClass(name)) { - this.attr('class', this.classes().filter(function(c) { - return c != name - }).join(' ')) - } - - return this - } - // Toggle the presence of a class on the node - , toggleClass: function(name) { - return this.hasClass(name) ? this.removeClass(name) : this.addClass(name) - } - // Get referenced element form attribute value - , reference: function(attr) { - return SVG.get(this.attr(attr)) - } - // Returns the parent element instance - , parent: function(type) { - var parent = this - - // check for parent - if(!parent.node.parentNode) return null - - // get parent element - parent = SVG.adopt(parent.node.parentNode) - - if(!type) return parent - - // loop trough ancestors if type is given - while(parent && parent.node instanceof window.SVGElement){ - if(typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent - parent = SVG.adopt(parent.node.parentNode) - } - } - // Get parent document - , doc: function() { - return this instanceof SVG.Doc ? this : this.parent(SVG.Doc) - } - // return array of all ancestors of given type up to the root svg - , parents: function(type) { - var parents = [], parent = this - - do{ - parent = parent.parent(type) - if(!parent || !parent.node) break - - parents.push(parent) - } while(parent.parent) - - return parents - } - // matches the element vs a css selector - , matches: function(selector){ - return matches(this.node, selector) - } - // Returns the svg node to call native svg methods on it - , native: function() { - return this.node - } - // Import raw svg - , svg: function(svg) { - // create temporary holder - var well = document.createElement('svg') - - // act as a setter if svg is given - if (svg && this instanceof SVG.Parent) { - // dump raw svg - well.innerHTML = '' + svg.replace(/\n/, '').replace(/<(\w+)([^<]+?)\/>/g, '<$1$2>') + '' - - // transplant nodes - for (var i = 0, il = well.firstChild.childNodes.length; i < il; i++) - this.node.appendChild(well.firstChild.firstChild) - - // otherwise act as a getter - } else { - // create a wrapping svg element in case of partial content - well.appendChild(svg = document.createElement('svg')) - - // write svgjs data to the dom - this.writeDataToDom() - - // insert a copy of this node - svg.appendChild(this.node.cloneNode(true)) - - // return target element - return well.innerHTML.replace(/^/, '').replace(/<\/svg>$/, '') - } - - return this - } - // write svgjs data to the dom - , writeDataToDom: function() { - - // dump variables recursively - if(this.each || this.lines){ - var fn = this.each ? this : this.lines(); - fn.each(function(){ - this.writeDataToDom() - }) - } - - // remove previously set data - this.node.removeAttribute('svgjs:data') - - if(Object.keys(this.dom).length) - this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)) // see #428 - - return this - } - // set given data to the elements data property - , setData: function(o){ - this.dom = o - return this - } - , is: function(obj){ - return is(this, obj) - } - } -}) - -SVG.easing = { - '-': function(pos){return pos} -, '<>':function(pos){return -Math.cos(pos * Math.PI) / 2 + 0.5} -, '>': function(pos){return Math.sin(pos * Math.PI / 2)} -, '<': function(pos){return -Math.cos(pos * Math.PI / 2) + 1} -} - -SVG.morph = function(pos){ - return function(from, to) { - return new SVG.MorphObj(from, to).at(pos) - } -} - -SVG.Situation = SVG.invent({ - - create: function(o){ - this.init = false - this.reversed = false - this.reversing = false - - this.duration = new SVG.Number(o.duration).valueOf() - this.delay = new SVG.Number(o.delay).valueOf() - - this.start = +new Date() + this.delay - this.finish = this.start + this.duration - this.ease = o.ease - - // this.loop is incremented from 0 to this.loops - // it is also incremented when in an infinite loop (when this.loops is true) - this.loop = 0 - this.loops = false - - this.animations = { - // functionToCall: [list of morphable objects] - // e.g. move: [SVG.Number, SVG.Number] - } - - this.attrs = { - // holds all attributes which are not represented from a function svg.js provides - // e.g. someAttr: SVG.Number - } - - this.styles = { - // holds all styles which should be animated - // e.g. fill-color: SVG.Color - } - - this.transforms = [ - // holds all transformations as transformation objects - // e.g. [SVG.Rotate, SVG.Translate, SVG.Matrix] - ] - - this.once = { - // functions to fire at a specific position - // e.g. "0.5": function foo(){} - } - - } - -}) - - -SVG.FX = SVG.invent({ - - create: function(element) { - this._target = element - this.situations = [] - this.active = false - this.situation = null - this.paused = false - this.lastPos = 0 - this.pos = 0 - // The absolute position of an animation is its position in the context of its complete duration (including delay and loops) - // When performing a delay, absPos is below 0 and when performing a loop, its value is above 1 - this.absPos = 0 - this._speed = 1 - } - -, extend: { - - /** - * sets or returns the target of this animation - * @param o object || number In case of Object it holds all parameters. In case of number its the duration of the animation - * @param ease function || string Function which should be used for easing or easing keyword - * @param delay Number indicating the delay before the animation starts - * @return target || this - */ - animate: function(o, ease, delay){ - - if(typeof o == 'object'){ - ease = o.ease - delay = o.delay - o = o.duration - } - - var situation = new SVG.Situation({ - duration: o || 1000, - delay: delay || 0, - ease: SVG.easing[ease || '-'] || ease - }) - - this.queue(situation) - - return this - } - - /** - * sets a delay before the next element of the queue is called - * @param delay Duration of delay in milliseconds - * @return this.target() - */ - , delay: function(delay){ - // The delay is performed by an empty situation with its duration - // attribute set to the duration of the delay - var situation = new SVG.Situation({ - duration: delay, - delay: 0, - ease: SVG.easing['-'] - }) - - return this.queue(situation) - } - - /** - * sets or returns the target of this animation - * @param null || target SVG.Element which should be set as new target - * @return target || this - */ - , target: function(target){ - if(target && target instanceof SVG.Element){ - this._target = target - return this - } - - return this._target - } - - // returns the absolute position at a given time - , timeToAbsPos: function(timestamp){ - return (timestamp - this.situation.start) / (this.situation.duration/this._speed) - } - - // returns the timestamp from a given absolute positon - , absPosToTime: function(absPos){ - return this.situation.duration/this._speed * absPos + this.situation.start - } - - // starts the animationloop - , startAnimFrame: function(){ - this.stopAnimFrame() - this.animationFrame = window.requestAnimationFrame(function(){ this.step() }.bind(this)) - } - - // cancels the animationframe - , stopAnimFrame: function(){ - window.cancelAnimationFrame(this.animationFrame) - } - - // kicks off the animation - only does something when the queue is currently not active and at least one situation is set - , start: function(){ - // dont start if already started - if(!this.active && this.situation){ - this.active = true - this.startCurrent() - } - - return this - } - - // start the current situation - , startCurrent: function(){ - this.situation.start = +new Date + this.situation.delay/this._speed - this.situation.finish = this.situation.start + this.situation.duration/this._speed - return this.initAnimations().step() - } - - /** - * adds a function / Situation to the animation queue - * @param fn function / situation to add - * @return this - */ - , queue: function(fn){ - if(typeof fn == 'function' || fn instanceof SVG.Situation) - this.situations.push(fn) - - if(!this.situation) this.situation = this.situations.shift() - - return this - } - - /** - * pulls next element from the queue and execute it - * @return this - */ - , dequeue: function(){ - // stop current animation - this.stop() - - // get next animation from queue - this.situation = this.situations.shift() - - if(this.situation){ - if(this.situation instanceof SVG.Situation) { - this.start() - } else { - // If it is not a SVG.Situation, then it is a function, we execute it - this.situation.call(this) - } - } - - return this - } - - // updates all animations to the current state of the element - // this is important when one property could be changed from another property - , initAnimations: function() { - var i, j, source - var s = this.situation - - if(s.init) return this - - for(i in s.animations){ - source = this.target()[i]() - - if(!Array.isArray(source)) { - source = [source] - } - - if(!Array.isArray(s.animations[i])) { - s.animations[i] = [s.animations[i]] - } - - //if(s.animations[i].length > source.length) { - // source.concat = source.concat(s.animations[i].slice(source.length, s.animations[i].length)) - //} - - for(j = source.length; j--;) { - // The condition is because some methods return a normal number instead - // of a SVG.Number - if(s.animations[i][j] instanceof SVG.Number) - source[j] = new SVG.Number(source[j]) - - s.animations[i][j] = source[j].morph(s.animations[i][j]) - } - } - - for(i in s.attrs){ - s.attrs[i] = new SVG.MorphObj(this.target().attr(i), s.attrs[i]) - } - - for(i in s.styles){ - s.styles[i] = new SVG.MorphObj(this.target().style(i), s.styles[i]) - } - - s.initialTransformation = this.target().matrixify() - - s.init = true - return this - } - , clearQueue: function(){ - this.situations = [] - return this - } - , clearCurrent: function(){ - this.situation = null - return this - } - /** stops the animation immediately - * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately. - * @param clearQueue A Boolean indicating whether to remove queued animation as well. - * @return this - */ - , stop: function(jumpToEnd, clearQueue){ - var active = this.active - this.active = false - - if(clearQueue){ - this.clearQueue() - } - - if(jumpToEnd && this.situation){ - // initialize the situation if it was not - !active && this.startCurrent() - this.atEnd() - } - - this.stopAnimFrame() - - return this.clearCurrent() - } - - /** resets the element to the state where the current element has started - * @return this - */ - , reset: function(){ - if(this.situation){ - var temp = this.situation - this.stop() - this.situation = temp - this.atStart() - } - return this - } - - // Stop the currently-running animation, remove all queued animations, and complete all animations for the element. - , finish: function(){ - - this.stop(true, false) - - while(this.dequeue().situation && this.stop(true, false)); - - this.clearQueue().clearCurrent() - - return this - } - - // set the internal animation pointer at the start position, before any loops, and updates the visualisation - , atStart: function() { - return this.at(0, true) - } - - // set the internal animation pointer at the end position, after all the loops, and updates the visualisation - , atEnd: function() { - if (this.situation.loops === true) { - // If in a infinite loop, we end the current iteration - this.situation.loops = this.situation.loop + 1 - } - - if(typeof this.situation.loops == 'number') { - // If performing a finite number of loops, we go after all the loops - return this.at(this.situation.loops, true) - } else { - // If no loops, we just go at the end - return this.at(1, true) - } - } - - // set the internal animation pointer to the specified position and updates the visualisation - // if isAbsPos is true, pos is treated as an absolute position - , at: function(pos, isAbsPos){ - var durDivSpd = this.situation.duration/this._speed - - this.absPos = pos - // If pos is not an absolute position, we convert it into one - if (!isAbsPos) { - if (this.situation.reversed) this.absPos = 1 - this.absPos - this.absPos += this.situation.loop - } - - this.situation.start = +new Date - this.absPos * durDivSpd - this.situation.finish = this.situation.start + durDivSpd - - return this.step(true) - } - - /** - * sets or returns the speed of the animations - * @param speed null || Number The new speed of the animations - * @return Number || this - */ - , speed: function(speed){ - if (speed === 0) return this.pause() - - if (speed) { - this._speed = speed - // We use an absolute position here so that speed can affect the delay before the animation - return this.at(this.absPos, true) - } else return this._speed - } - - // Make loopable - , loop: function(times, reverse) { - var c = this.last() - - // store total loops - c.loops = (times != null) ? times : true - c.loop = 0 - - if(reverse) c.reversing = true - return this - } - - // pauses the animation - , pause: function(){ - this.paused = true - this.stopAnimFrame() - - return this - } - - // unpause the animation - , play: function(){ - if(!this.paused) return this - this.paused = false - // We use an absolute position here so that the delay before the animation can be paused - return this.at(this.absPos, true) - } - - /** - * toggle or set the direction of the animation - * true sets direction to backwards while false sets it to forwards - * @param reversed Boolean indicating whether to reverse the animation or not (default: toggle the reverse status) - * @return this - */ - , reverse: function(reversed){ - var c = this.last() - - if(typeof reversed == 'undefined') c.reversed = !c.reversed - else c.reversed = reversed - - return this - } - - - /** - * returns a float from 0-1 indicating the progress of the current animation - * @param eased Boolean indicating whether the returned position should be eased or not - * @return number - */ - , progress: function(easeIt){ - return easeIt ? this.situation.ease(this.pos) : this.pos - } - - /** - * adds a callback function which is called when the current animation is finished - * @param fn Function which should be executed as callback - * @return number - */ - , after: function(fn){ - var c = this.last() - , wrapper = function wrapper(e){ - if(e.detail.situation == c){ - fn.call(this, c) - this.off('finished.fx', wrapper) // prevent memory leak - } - } - - this.target().on('finished.fx', wrapper) - - return this._callStart() - } - - // adds a callback which is called whenever one animation step is performed - , during: function(fn){ - var c = this.last() - , wrapper = function(e){ - if(e.detail.situation == c){ - fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, c) - } - } - - // see above - this.target().off('during.fx', wrapper).on('during.fx', wrapper) - - this.after(function(){ - this.off('during.fx', wrapper) - }) - - return this._callStart() - } - - // calls after ALL animations in the queue are finished - , afterAll: function(fn){ - var wrapper = function wrapper(e){ - fn.call(this) - this.off('allfinished.fx', wrapper) - } - - // see above - this.target().off('allfinished.fx', wrapper).on('allfinished.fx', wrapper) - - return this._callStart() - } - - // calls on every animation step for all animations - , duringAll: function(fn){ - var wrapper = function(e){ - fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, e.detail.situation) - } - - this.target().off('during.fx', wrapper).on('during.fx', wrapper) - - this.afterAll(function(){ - this.off('during.fx', wrapper) - }) - - return this._callStart() - } - - , last: function(){ - return this.situations.length ? this.situations[this.situations.length-1] : this.situation - } - - // adds one property to the animations - , add: function(method, args, type){ - this.last()[type || 'animations'][method] = args - return this._callStart() - } - - /** perform one step of the animation - * @param ignoreTime Boolean indicating whether to ignore time and use position directly or recalculate position based on time - * @return this - */ - , step: function(ignoreTime){ - - // convert current time to an absolute position - if(!ignoreTime) this.absPos = this.timeToAbsPos(+new Date) - - // This part convert an absolute position to a position - if(this.situation.loops !== false) { - var absPos, absPosInt, lastLoop - - // If the absolute position is below 0, we just treat it as if it was 0 - absPos = Math.max(this.absPos, 0) - absPosInt = Math.floor(absPos) - - if(this.situation.loops === true || absPosInt < this.situation.loops) { - this.pos = absPos - absPosInt - lastLoop = this.situation.loop - this.situation.loop = absPosInt - } else { - this.absPos = this.situation.loops - this.pos = 1 - // The -1 here is because we don't want to toggle reversed when all the loops have been completed - lastLoop = this.situation.loop - 1 - this.situation.loop = this.situation.loops - } - - if(this.situation.reversing) { - // Toggle reversed if an odd number of loops as occured since the last call of step - this.situation.reversed = this.situation.reversed != Boolean((this.situation.loop - lastLoop) % 2) - } - - } else { - // If there are no loop, the absolute position must not be above 1 - this.absPos = Math.min(this.absPos, 1) - this.pos = this.absPos - } - - // while the absolute position can be below 0, the position must not be below 0 - if(this.pos < 0) this.pos = 0 - - if(this.situation.reversed) this.pos = 1 - this.pos - - - // apply easing - var eased = this.situation.ease(this.pos) - - // call once-callbacks - for(var i in this.situation.once){ - if(i > this.lastPos && i <= eased){ - this.situation.once[i].call(this.target(), this.pos, eased) - delete this.situation.once[i] - } - } - - // fire during callback with position, eased position and current situation as parameter - if(this.active) this.target().fire('during', {pos: this.pos, eased: eased, fx: this, situation: this.situation}) - - // the user may call stop or finish in the during callback - // so make sure that we still have a valid situation - if(!this.situation){ - return this - } - - // apply the actual animation to every property - this.eachAt() - - // do final code when situation is finished - if((this.pos == 1 && !this.situation.reversed) || (this.situation.reversed && this.pos == 0)){ - - // stop animation callback - this.stopAnimFrame() - - // fire finished callback with current situation as parameter - this.target().fire('finished', {fx:this, situation: this.situation}) - - if(!this.situations.length){ - this.target().fire('allfinished') - - // Recheck the length since the user may call animate in the afterAll callback - if(!this.situations.length){ - this.target().off('.fx') // there shouldnt be any binding left, but to make sure... - this.active = false - } - } - - // start next animation - if(this.active) this.dequeue() - else this.clearCurrent() - - }else if(!this.paused && this.active){ - // we continue animating when we are not at the end - this.startAnimFrame() - } - - // save last eased position for once callback triggering - this.lastPos = eased - return this - - } - - // calculates the step for every property and calls block with it - , eachAt: function(){ - var i, len, at, self = this, target = this.target(), s = this.situation - - // apply animations which can be called trough a method - for(i in s.animations){ - - at = [].concat(s.animations[i]).map(function(el){ - return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el - }) - - target[i].apply(target, at) - - } - - // apply animation which has to be applied with attr() - for(i in s.attrs){ - - at = [i].concat(s.attrs[i]).map(function(el){ - return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el - }) - - target.attr.apply(target, at) - - } - - // apply animation which has to be applied with style() - for(i in s.styles){ - - at = [i].concat(s.styles[i]).map(function(el){ - return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el - }) - - target.style.apply(target, at) - - } - - // animate initialTransformation which has to be chained - if(s.transforms.length){ - - // get initial initialTransformation - at = s.initialTransformation - for(i = 0, len = s.transforms.length; i < len; i++){ - - // get next transformation in chain - var a = s.transforms[i] - - // multiply matrix directly - if(a instanceof SVG.Matrix){ - - if(a.relative){ - at = at.multiply(new SVG.Matrix().morph(a).at(s.ease(this.pos))) - }else{ - at = at.morph(a).at(s.ease(this.pos)) - } - continue - } - - // when transformation is absolute we have to reset the needed transformation first - if(!a.relative) - a.undo(at.extract()) - - // and reapply it after - at = at.multiply(a.at(s.ease(this.pos))) - - } - - // set new matrix on element - target.matrix(at) - } - - return this - - } - - - // adds an once-callback which is called at a specific position and never again - , once: function(pos, fn, isEased){ - var c = this.last() - if(!isEased) pos = c.ease(pos) - - c.once[pos] = fn - - return this - } - - , _callStart: function() { - setTimeout(function(){this.start()}.bind(this), 0) - return this - } - - } - -, parent: SVG.Element - - // Add method to parent elements -, construct: { - // Get fx module or create a new one, then animate with given duration and ease - animate: function(o, ease, delay) { - return (this.fx || (this.fx = new SVG.FX(this))).animate(o, ease, delay) - } - , delay: function(delay){ - return (this.fx || (this.fx = new SVG.FX(this))).delay(delay) - } - , stop: function(jumpToEnd, clearQueue) { - if (this.fx) - this.fx.stop(jumpToEnd, clearQueue) - - return this - } - , finish: function() { - if (this.fx) - this.fx.finish() - - return this - } - // Pause current animation - , pause: function() { - if (this.fx) - this.fx.pause() - - return this - } - // Play paused current animation - , play: function() { - if (this.fx) - this.fx.play() - - return this - } - // Set/Get the speed of the animations - , speed: function(speed) { - if (this.fx) - if (speed == null) - return this.fx.speed() - else - this.fx.speed(speed) - - return this - } - } - -}) - -// MorphObj is used whenever no morphable object is given -SVG.MorphObj = SVG.invent({ - - create: function(from, to){ - // prepare color for morphing - if(SVG.Color.isColor(to)) return new SVG.Color(from).morph(to) - // prepare value list for morphing - if(SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to) - // prepare number for morphing - if(SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) - - // prepare for plain morphing - this.value = from - this.destination = to - } - -, extend: { - at: function(pos, real){ - return real < 1 ? this.value : this.destination - }, - - valueOf: function(){ - return this.value - } - } - -}) - -SVG.extend(SVG.FX, { - // Add animatable attributes - attr: function(a, v, relative) { - // apply attributes individually - if (typeof a == 'object') { - for (var key in a) - this.attr(key, a[key]) - - } else { - this.add(a, v, 'attrs') - } - - return this - } - // Add animatable styles -, style: function(s, v) { - if (typeof s == 'object') - for (var key in s) - this.style(key, s[key]) - - else - this.add(s, v, 'styles') - - return this - } - // Animatable x-axis -, x: function(x, relative) { - if(this.target() instanceof SVG.G){ - this.transform({x:x}, relative) - return this - } - - var num = new SVG.Number(x) - num.relative = relative - return this.add('x', num) - } - // Animatable y-axis -, y: function(y, relative) { - if(this.target() instanceof SVG.G){ - this.transform({y:y}, relative) - return this - } - - var num = new SVG.Number(y) - num.relative = relative - return this.add('y', num) - } - // Animatable center x-axis -, cx: function(x) { - return this.add('cx', new SVG.Number(x)) - } - // Animatable center y-axis -, cy: function(y) { - return this.add('cy', new SVG.Number(y)) - } - // Add animatable move -, move: function(x, y) { - return this.x(x).y(y) - } - // Add animatable center -, center: function(x, y) { - return this.cx(x).cy(y) - } - // Add animatable size -, size: function(width, height) { - if (this.target() instanceof SVG.Text) { - // animate font size for Text elements - this.attr('font-size', width) - - } else { - // animate bbox based size for all other elements - var box - - if(!width || !height){ - box = this.target().bbox() - } - - if(!width){ - width = box.width / box.height * height - } - - if(!height){ - height = box.height / box.width * width - } - - this.add('width' , new SVG.Number(width)) - .add('height', new SVG.Number(height)) - - } - - return this - } - // Add animatable width -, width: function(width) { - return this.add('width', new SVG.Number(width)) - } - // Add animatable height -, height: function(height) { - return this.add('height', new SVG.Number(height)) - } - // Add animatable plot -, plot: function(a, b, c, d) { - // Lines can be plotted with 4 arguments - if(arguments.length == 4) { - return this.plot([a, b, c, d]) - } - - return this.add('plot', new (this.target().morphArray)(a)) - } - // Add leading method -, leading: function(value) { - return this.target().leading ? - this.add('leading', new SVG.Number(value)) : - this - } - // Add animatable viewbox -, viewbox: function(x, y, width, height) { - if (this.target() instanceof SVG.Container) { - this.add('viewbox', new SVG.ViewBox(x, y, width, height)) - } - - return this - } -, update: function(o) { - if (this.target() instanceof SVG.Stop) { - if (typeof o == 'number' || o instanceof SVG.Number) { - return this.update({ - offset: arguments[0] - , color: arguments[1] - , opacity: arguments[2] - }) - } - - if (o.opacity != null) this.attr('stop-opacity', o.opacity) - if (o.color != null) this.attr('stop-color', o.color) - if (o.offset != null) this.attr('offset', o.offset) - } - - return this - } -}) - -SVG.Box = SVG.invent({ - create: function(x, y, width, height) { - if (typeof x == 'object' && !(x instanceof SVG.Element)) { - // chromes getBoundingClientRect has no x and y property - return SVG.Box.call(this, x.left != null ? x.left : x.x , x.top != null ? x.top : x.y, x.width, x.height) - } else if (arguments.length == 4) { - this.x = x - this.y = y - this.width = width - this.height = height - } - - // add center, right, bottom... - fullBox(this) - } -, extend: { - // Merge rect box with another, return a new instance - merge: function(box) { - var b = new this.constructor() - - // merge boxes - b.x = Math.min(this.x, box.x) - b.y = Math.min(this.y, box.y) - b.width = Math.max(this.x + this.width, box.x + box.width) - b.x - b.height = Math.max(this.y + this.height, box.y + box.height) - b.y - - return fullBox(b) - } - - , transform: function(m) { - var xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, p, bbox - - var pts = [ - new SVG.Point(this.x, this.y), - new SVG.Point(this.x2, this.y), - new SVG.Point(this.x, this.y2), - new SVG.Point(this.x2, this.y2) - ] - - pts.forEach(function(p) { - p = p.transform(m) - xMin = Math.min(xMin,p.x) - xMax = Math.max(xMax,p.x) - yMin = Math.min(yMin,p.y) - yMax = Math.max(yMax,p.y) - }) - - bbox = new this.constructor() - bbox.x = xMin - bbox.width = xMax-xMin - bbox.y = yMin - bbox.height = yMax-yMin - - fullBox(bbox) - - return bbox - } - } -}) - -SVG.BBox = SVG.invent({ - // Initialize - create: function(element) { - SVG.Box.apply(this, [].slice.call(arguments)) - - // get values if element is given - if (element instanceof SVG.Element) { - var box - - // yes this is ugly, but Firefox can be a bitch when it comes to elements that are not yet rendered - try { - - if (!document.documentElement.contains){ - // This is IE - it does not support contains() for top-level SVGs - var topParent = element.node - while (topParent.parentNode){ - topParent = topParent.parentNode - } - if (topParent != document) throw new Exception('Element not in the dom') - } else { - // the element is NOT in the dom, throw error - if(!document.documentElement.contains(element.node)) throw new Exception('Element not in the dom') - } - - // find native bbox - box = element.node.getBBox() - } catch(e) { - if(element instanceof SVG.Shape){ - var clone = element.clone(SVG.parser.draw.instance).show() - box = clone.node.getBBox() - clone.remove() - }else{ - box = { - x: element.node.clientLeft - , y: element.node.clientTop - , width: element.node.clientWidth - , height: element.node.clientHeight - } - } - } - - SVG.Box.call(this, box) - } - - } - - // Define ancestor -, inherit: SVG.Box - - // Define Parent -, parent: SVG.Element - - // Constructor -, construct: { - // Get bounding box - bbox: function() { - return new SVG.BBox(this) - } - } - -}) - -SVG.BBox.prototype.constructor = SVG.BBox - - -SVG.extend(SVG.Element, { - tbox: function(){ - console.warn('Use of TBox is deprecated and mapped to RBox. Use .rbox() instead.') - return this.rbox(this.doc()) - } -}) - -SVG.RBox = SVG.invent({ - // Initialize - create: function(element) { - SVG.Box.apply(this, [].slice.call(arguments)) - - if (element instanceof SVG.Element) { - SVG.Box.call(this, element.node.getBoundingClientRect()) - } - } - -, inherit: SVG.Box - - // define Parent -, parent: SVG.Element - -, extend: { - addOffset: function() { - // offset by window scroll position, because getBoundingClientRect changes when window is scrolled - this.x += window.pageXOffset - this.y += window.pageYOffset - return this - } - } - - // Constructor -, construct: { - // Get rect box - rbox: function(el) { - if (el) return new SVG.RBox(this).transform(el.screenCTM().inverse()) - return new SVG.RBox(this).addOffset() - } - } - -}) - -SVG.RBox.prototype.constructor = SVG.RBox - -SVG.Matrix = SVG.invent({ - // Initialize - create: function(source) { - var i, base = arrayToMatrix([1, 0, 0, 1, 0, 0]) - - // ensure source as object - source = source instanceof SVG.Element ? - source.matrixify() : - typeof source === 'string' ? - arrayToMatrix(source.split(SVG.regex.delimiter).map(parseFloat)) : - arguments.length == 6 ? - arrayToMatrix([].slice.call(arguments)) : - Array.isArray(source) ? - arrayToMatrix(source) : - typeof source === 'object' ? - source : base - - // merge source - for (i = abcdef.length - 1; i >= 0; --i) - this[abcdef[i]] = source[abcdef[i]] != null ? - source[abcdef[i]] : base[abcdef[i]] - } - - // Add methods -, extend: { - // Extract individual transformations - extract: function() { - // find delta transform points - var px = deltaTransformPoint(this, 0, 1) - , py = deltaTransformPoint(this, 1, 0) - , skewX = 180 / Math.PI * Math.atan2(px.y, px.x) - 90 - - return { - // translation - x: this.e - , y: this.f - , transformedX:(this.e * Math.cos(skewX * Math.PI / 180) + this.f * Math.sin(skewX * Math.PI / 180)) / Math.sqrt(this.a * this.a + this.b * this.b) - , transformedY:(this.f * Math.cos(skewX * Math.PI / 180) + this.e * Math.sin(-skewX * Math.PI / 180)) / Math.sqrt(this.c * this.c + this.d * this.d) - // skew - , skewX: -skewX - , skewY: 180 / Math.PI * Math.atan2(py.y, py.x) - // scale - , scaleX: Math.sqrt(this.a * this.a + this.b * this.b) - , scaleY: Math.sqrt(this.c * this.c + this.d * this.d) - // rotation - , rotation: skewX - , a: this.a - , b: this.b - , c: this.c - , d: this.d - , e: this.e - , f: this.f - , matrix: new SVG.Matrix(this) - } - } - // Clone matrix - , clone: function() { - return new SVG.Matrix(this) - } - // Morph one matrix into another - , morph: function(matrix) { - // store new destination - this.destination = new SVG.Matrix(matrix) - - return this - } - // Get morphed matrix at a given position - , at: function(pos) { - // make sure a destination is defined - if (!this.destination) return this - - // calculate morphed matrix at a given position - var matrix = new SVG.Matrix({ - a: this.a + (this.destination.a - this.a) * pos - , b: this.b + (this.destination.b - this.b) * pos - , c: this.c + (this.destination.c - this.c) * pos - , d: this.d + (this.destination.d - this.d) * pos - , e: this.e + (this.destination.e - this.e) * pos - , f: this.f + (this.destination.f - this.f) * pos - }) - - return matrix - } - // Multiplies by given matrix - , multiply: function(matrix) { - return new SVG.Matrix(this.native().multiply(parseMatrix(matrix).native())) - } - // Inverses matrix - , inverse: function() { - return new SVG.Matrix(this.native().inverse()) - } - // Translate matrix - , translate: function(x, y) { - return new SVG.Matrix(this.native().translate(x || 0, y || 0)) - } - // Scale matrix - , scale: function(x, y, cx, cy) { - // support uniformal scale - if (arguments.length == 1) { - y = x - } else if (arguments.length == 3) { - cy = cx - cx = y - y = x - } - - return this.around(cx, cy, new SVG.Matrix(x, 0, 0, y, 0, 0)) - } - // Rotate matrix - , rotate: function(r, cx, cy) { - // convert degrees to radians - r = SVG.utils.radians(r) - - return this.around(cx, cy, new SVG.Matrix(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0)) - } - // Flip matrix on x or y, at a given offset - , flip: function(a, o) { - return a == 'x' ? - this.scale(-1, 1, o, 0) : - a == 'y' ? - this.scale(1, -1, 0, o) : - this.scale(-1, -1, a, o != null ? o : a) - } - // Skew - , skew: function(x, y, cx, cy) { - // support uniformal skew - if (arguments.length == 1) { - y = x - } else if (arguments.length == 3) { - cy = cx - cx = y - y = x - } - - // convert degrees to radians - x = SVG.utils.radians(x) - y = SVG.utils.radians(y) - - return this.around(cx, cy, new SVG.Matrix(1, Math.tan(y), Math.tan(x), 1, 0, 0)) - } - // SkewX - , skewX: function(x, cx, cy) { - return this.skew(x, 0, cx, cy) - } - // SkewY - , skewY: function(y, cx, cy) { - return this.skew(0, y, cx, cy) - } - // Transform around a center point - , around: function(cx, cy, matrix) { - return this - .multiply(new SVG.Matrix(1, 0, 0, 1, cx || 0, cy || 0)) - .multiply(matrix) - .multiply(new SVG.Matrix(1, 0, 0, 1, -cx || 0, -cy || 0)) - } - // Convert to native SVGMatrix - , native: function() { - // create new matrix - var matrix = SVG.parser.native.createSVGMatrix() - - // update with current values - for (var i = abcdef.length - 1; i >= 0; i--) - matrix[abcdef[i]] = this[abcdef[i]] - - return matrix - } - // Convert matrix to string - , toString: function() { - return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')' - } - } - - // Define parent -, parent: SVG.Element - - // Add parent method -, construct: { - // Get current matrix - ctm: function() { - return new SVG.Matrix(this.node.getCTM()) - }, - // Get current screen matrix - screenCTM: function() { - /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537 - This is needed because FF does not return the transformation matrix - for the inner coordinate system when getScreenCTM() is called on nested svgs. - However all other Browsers do that */ - if(this instanceof SVG.Nested) { - var rect = this.rect(1,1) - var m = rect.node.getScreenCTM() - rect.remove() - return new SVG.Matrix(m) - } - return new SVG.Matrix(this.node.getScreenCTM()) - } - - } - -}) - -SVG.Point = SVG.invent({ - // Initialize - create: function(x,y) { - var i, source, base = {x:0, y:0} - - // ensure source as object - source = Array.isArray(x) ? - {x:x[0], y:x[1]} : - typeof x === 'object' ? - {x:x.x, y:x.y} : - x != null ? - {x:x, y:(y != null ? y : x)} : base // If y has no value, then x is used has its value - - // merge source - this.x = source.x - this.y = source.y - } - - // Add methods -, extend: { - // Clone point - clone: function() { - return new SVG.Point(this) - } - // Morph one point into another - , morph: function(x, y) { - // store new destination - this.destination = new SVG.Point(x, y) - - return this - } - // Get morphed point at a given position - , at: function(pos) { - // make sure a destination is defined - if (!this.destination) return this - - // calculate morphed matrix at a given position - var point = new SVG.Point({ - x: this.x + (this.destination.x - this.x) * pos - , y: this.y + (this.destination.y - this.y) * pos - }) - - return point - } - // Convert to native SVGPoint - , native: function() { - // create new point - var point = SVG.parser.native.createSVGPoint() - - // update with current values - point.x = this.x - point.y = this.y - - return point - } - // transform point with matrix - , transform: function(matrix) { - return new SVG.Point(this.native().matrixTransform(matrix.native())) - } - - } - -}) - -SVG.extend(SVG.Element, { - - // Get point - point: function(x, y) { - return new SVG.Point(x,y).transform(this.screenCTM().inverse()); - } - -}) - -SVG.extend(SVG.Element, { - // Set svg element attribute - attr: function(a, v, n) { - // act as full getter - if (a == null) { - // get an object of attributes - a = {} - v = this.node.attributes - for (n = v.length - 1; n >= 0; n--) - a[v[n].nodeName] = SVG.regex.isNumber.test(v[n].nodeValue) ? parseFloat(v[n].nodeValue) : v[n].nodeValue - - return a - - } else if (typeof a == 'object') { - // apply every attribute individually if an object is passed - for (v in a) this.attr(v, a[v]) - - } else if (v === null) { - // remove value - this.node.removeAttribute(a) - - } else if (v == null) { - // act as a getter if the first and only argument is not an object - v = this.node.getAttribute(a) - return v == null ? - SVG.defaults.attrs[a] : - SVG.regex.isNumber.test(v) ? - parseFloat(v) : v - - } else { - // BUG FIX: some browsers will render a stroke if a color is given even though stroke width is 0 - if (a == 'stroke-width') - this.attr('stroke', parseFloat(v) > 0 ? this._stroke : null) - else if (a == 'stroke') - this._stroke = v - - // convert image fill and stroke to patterns - if (a == 'fill' || a == 'stroke') { - if (SVG.regex.isImage.test(v)) - v = this.doc().defs().image(v, 0, 0) - - if (v instanceof SVG.Image) - v = this.doc().defs().pattern(0, 0, function() { - this.add(v) - }) - } - - // ensure correct numeric values (also accepts NaN and Infinity) - if (typeof v === 'number') - v = new SVG.Number(v) - - // ensure full hex color - else if (SVG.Color.isColor(v)) - v = new SVG.Color(v) - - // parse array values - else if (Array.isArray(v)) - v = new SVG.Array(v) - - // if the passed attribute is leading... - if (a == 'leading') { - // ... call the leading method instead - if (this.leading) - this.leading(v) - } else { - // set given attribute on node - typeof n === 'string' ? - this.node.setAttributeNS(n, a, v.toString()) : - this.node.setAttribute(a, v.toString()) - } - - // rebuild if required - if (this.rebuild && (a == 'font-size' || a == 'x')) - this.rebuild(a, v) - } - - return this - } -}) -SVG.extend(SVG.Element, { - // Add transformations - transform: function(o, relative) { - // get target in case of the fx module, otherwise reference this - var target = this - , matrix, bbox - - // act as a getter - if (typeof o !== 'object') { - // get current matrix - matrix = new SVG.Matrix(target).extract() - - return typeof o === 'string' ? matrix[o] : matrix - } - - // get current matrix - matrix = new SVG.Matrix(target) - - // ensure relative flag - relative = !!relative || !!o.relative - - // act on matrix - if (o.a != null) { - matrix = relative ? - // relative - matrix.multiply(new SVG.Matrix(o)) : - // absolute - new SVG.Matrix(o) - - // act on rotation - } else if (o.rotation != null) { - // ensure centre point - ensureCentre(o, target) - - // apply transformation - matrix = relative ? - // relative - matrix.rotate(o.rotation, o.cx, o.cy) : - // absolute - matrix.rotate(o.rotation - matrix.extract().rotation, o.cx, o.cy) - - // act on scale - } else if (o.scale != null || o.scaleX != null || o.scaleY != null) { - // ensure centre point - ensureCentre(o, target) - - // ensure scale values on both axes - o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 - o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 - - if (!relative) { - // absolute; multiply inversed values - var e = matrix.extract() - o.scaleX = o.scaleX * 1 / e.scaleX - o.scaleY = o.scaleY * 1 / e.scaleY - } - - matrix = matrix.scale(o.scaleX, o.scaleY, o.cx, o.cy) - - // act on skew - } else if (o.skew != null || o.skewX != null || o.skewY != null) { - // ensure centre point - ensureCentre(o, target) - - // ensure skew values on both axes - o.skewX = o.skew != null ? o.skew : o.skewX != null ? o.skewX : 0 - o.skewY = o.skew != null ? o.skew : o.skewY != null ? o.skewY : 0 - - if (!relative) { - // absolute; reset skew values - var e = matrix.extract() - matrix = matrix.multiply(new SVG.Matrix().skew(e.skewX, e.skewY, o.cx, o.cy).inverse()) - } - - matrix = matrix.skew(o.skewX, o.skewY, o.cx, o.cy) - - // act on flip - } else if (o.flip) { - if(o.flip == 'x' || o.flip == 'y') { - o.offset = o.offset == null ? target.bbox()['c' + o.flip] : o.offset - } else { - if(o.offset == null) { - bbox = target.bbox() - o.flip = bbox.cx - o.offset = bbox.cy - } else { - o.flip = o.offset - } - } - - matrix = new SVG.Matrix().flip(o.flip, o.offset) - - // act on translate - } else if (o.x != null || o.y != null) { - if (relative) { - // relative - matrix = matrix.translate(o.x, o.y) - } else { - // absolute - if (o.x != null) matrix.e = o.x - if (o.y != null) matrix.f = o.y - } - } - - return this.attr('transform', matrix) - } -}) - -SVG.extend(SVG.FX, { - transform: function(o, relative) { - // get target in case of the fx module, otherwise reference this - var target = this.target() - , matrix, bbox - - // act as a getter - if (typeof o !== 'object') { - // get current matrix - matrix = new SVG.Matrix(target).extract() - - return typeof o === 'string' ? matrix[o] : matrix - } - - // ensure relative flag - relative = !!relative || !!o.relative - - // act on matrix - if (o.a != null) { - matrix = new SVG.Matrix(o) - - // act on rotation - } else if (o.rotation != null) { - // ensure centre point - ensureCentre(o, target) - - // apply transformation - matrix = new SVG.Rotate(o.rotation, o.cx, o.cy) - - // act on scale - } else if (o.scale != null || o.scaleX != null || o.scaleY != null) { - // ensure centre point - ensureCentre(o, target) - - // ensure scale values on both axes - o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 - o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 - - matrix = new SVG.Scale(o.scaleX, o.scaleY, o.cx, o.cy) - - // act on skew - } else if (o.skewX != null || o.skewY != null) { - // ensure centre point - ensureCentre(o, target) - - // ensure skew values on both axes - o.skewX = o.skewX != null ? o.skewX : 0 - o.skewY = o.skewY != null ? o.skewY : 0 - - matrix = new SVG.Skew(o.skewX, o.skewY, o.cx, o.cy) - - // act on flip - } else if (o.flip) { - if(o.flip == 'x' || o.flip == 'y') { - o.offset = o.offset == null ? target.bbox()['c' + o.flip] : o.offset - } else { - if(o.offset == null) { - bbox = target.bbox() - o.flip = bbox.cx - o.offset = bbox.cy - } else { - o.flip = o.offset - } - } - - matrix = new SVG.Matrix().flip(o.flip, o.offset) - - // act on translate - } else if (o.x != null || o.y != null) { - matrix = new SVG.Translate(o.x, o.y) - } - - if(!matrix) return this - - matrix.relative = relative - - this.last().transforms.push(matrix) - - return this._callStart() - } -}) - -SVG.extend(SVG.Element, { - // Reset all transformations - untransform: function() { - return this.attr('transform', null) - }, - // merge the whole transformation chain into one matrix and returns it - matrixify: function() { - - var matrix = (this.attr('transform') || '') - // split transformations - .split(SVG.regex.transforms).slice(0,-1).map(function(str){ - // generate key => value pairs - var kv = str.trim().split('(') - return [kv[0], kv[1].split(SVG.regex.delimiter).map(function(str){ return parseFloat(str) })] - }) - // merge every transformation into one matrix - .reduce(function(matrix, transform){ - - if(transform[0] == 'matrix') return matrix.multiply(arrayToMatrix(transform[1])) - return matrix[transform[0]].apply(matrix, transform[1]) - - }, new SVG.Matrix()) - - return matrix - }, - // add an element to another parent without changing the visual representation on the screen - toParent: function(parent) { - if(this == parent) return this - var ctm = this.screenCTM() - var pCtm = parent.screenCTM().inverse() - - this.addTo(parent).untransform().transform(pCtm.multiply(ctm)) - - return this - }, - // same as above with parent equals root-svg - toDoc: function() { - return this.toParent(this.doc()) - } - -}) - -SVG.Transformation = SVG.invent({ - - create: function(source, inversed){ - - if(arguments.length > 1 && typeof inversed != 'boolean'){ - return this.constructor.call(this, [].slice.call(arguments)) - } - - if(Array.isArray(source)){ - for(var i = 0, len = this.arguments.length; i < len; ++i){ - this[this.arguments[i]] = source[i] - } - } else if(typeof source == 'object'){ - for(var i = 0, len = this.arguments.length; i < len; ++i){ - this[this.arguments[i]] = source[this.arguments[i]] - } - } - - this.inversed = false - - if(inversed === true){ - this.inversed = true - } - - } - -, extend: { - - arguments: [] - , method: '' - - , at: function(pos){ - - var params = [] - - for(var i = 0, len = this.arguments.length; i < len; ++i){ - params.push(this[this.arguments[i]]) - } - - var m = this._undo || new SVG.Matrix() - - m = new SVG.Matrix().morph(SVG.Matrix.prototype[this.method].apply(m, params)).at(pos) - - return this.inversed ? m.inverse() : m - - } - - , undo: function(o){ - for(var i = 0, len = this.arguments.length; i < len; ++i){ - o[this.arguments[i]] = typeof this[this.arguments[i]] == 'undefined' ? 0 : o[this.arguments[i]] - } - - // The method SVG.Matrix.extract which was used before calling this - // method to obtain a value for the parameter o doesn't return a cx and - // a cy so we use the ones that were provided to this object at its creation - o.cx = this.cx - o.cy = this.cy - - this._undo = new SVG[capitalize(this.method)](o, true).at(1) - - return this - } - - } - -}) - -SVG.Translate = SVG.invent({ - - parent: SVG.Matrix -, inherit: SVG.Transformation - -, create: function(source, inversed){ - this.constructor.apply(this, [].slice.call(arguments)) - } - -, extend: { - arguments: ['transformedX', 'transformedY'] - , method: 'translate' - } - -}) - -SVG.Rotate = SVG.invent({ - - parent: SVG.Matrix -, inherit: SVG.Transformation - -, create: function(source, inversed){ - this.constructor.apply(this, [].slice.call(arguments)) - } - -, extend: { - arguments: ['rotation', 'cx', 'cy'] - , method: 'rotate' - , at: function(pos){ - var m = new SVG.Matrix().rotate(new SVG.Number().morph(this.rotation - (this._undo ? this._undo.rotation : 0)).at(pos), this.cx, this.cy) - return this.inversed ? m.inverse() : m - } - , undo: function(o){ - this._undo = o - return this - } - } - -}) - -SVG.Scale = SVG.invent({ - - parent: SVG.Matrix -, inherit: SVG.Transformation - -, create: function(source, inversed){ - this.constructor.apply(this, [].slice.call(arguments)) - } - -, extend: { - arguments: ['scaleX', 'scaleY', 'cx', 'cy'] - , method: 'scale' - } - -}) - -SVG.Skew = SVG.invent({ - - parent: SVG.Matrix -, inherit: SVG.Transformation - -, create: function(source, inversed){ - this.constructor.apply(this, [].slice.call(arguments)) - } - -, extend: { - arguments: ['skewX', 'skewY', 'cx', 'cy'] - , method: 'skew' - } - -}) - -SVG.extend(SVG.Element, { - // Dynamic style generator - style: function(s, v) { - if (arguments.length == 0) { - // get full style - return this.node.style.cssText || '' - - } else if (arguments.length < 2) { - // apply every style individually if an object is passed - if (typeof s == 'object') { - for (v in s) this.style(v, s[v]) - - } else if (SVG.regex.isCss.test(s)) { - // parse css string - s = s.split(/\s*;\s*/) - // filter out suffix ; and stuff like ;; - .filter(function(e) { return !!e }) - .map(function(e){ return e.split(/\s*:\s*/) }) - - // apply every definition individually - while (v = s.pop()) { - this.style(v[0], v[1]) - } - } else { - // act as a getter if the first and only argument is not an object - return this.node.style[camelCase(s)] - } - - } else { - this.node.style[camelCase(s)] = v === null || SVG.regex.isBlank.test(v) ? '' : v - } - - return this - } -}) -SVG.Parent = SVG.invent({ - // Initialize node - create: function(element) { - this.constructor.call(this, element) - } - - // Inherit from -, inherit: SVG.Element - - // Add class methods -, extend: { - // Returns all child elements - children: function() { - return SVG.utils.map(SVG.utils.filterSVGElements(this.node.childNodes), function(node) { - return SVG.adopt(node) - }) - } - // Add given element at a position - , add: function(element, i) { - if (i == null) - this.node.appendChild(element.node) - else if (element.node != this.node.childNodes[i]) - this.node.insertBefore(element.node, this.node.childNodes[i]) - - return this - } - // Basically does the same as `add()` but returns the added element instead - , put: function(element, i) { - this.add(element, i) - return element - } - // Checks if the given element is a child - , has: function(element) { - return this.index(element) >= 0 - } - // Gets index of given element - , index: function(element) { - return [].slice.call(this.node.childNodes).indexOf(element.node) - } - // Get a element at the given index - , get: function(i) { - return SVG.adopt(this.node.childNodes[i]) - } - // Get first child - , first: function() { - return this.get(0) - } - // Get the last child - , last: function() { - return this.get(this.node.childNodes.length - 1) - } - // Iterates over all children and invokes a given block - , each: function(block, deep) { - var i, il - , children = this.children() - - for (i = 0, il = children.length; i < il; i++) { - if (children[i] instanceof SVG.Element) - block.apply(children[i], [i, children]) - - if (deep && (children[i] instanceof SVG.Container)) - children[i].each(block, deep) - } - - return this - } - // Remove a given child - , removeElement: function(element) { - this.node.removeChild(element.node) - - return this - } - // Remove all elements in this container - , clear: function() { - // remove children - while(this.node.hasChildNodes()) - this.node.removeChild(this.node.lastChild) - - // remove defs reference - delete this._defs - - return this - } - , // Get defs - defs: function() { - return this.doc().defs() - } - } - -}) - -SVG.extend(SVG.Parent, { - - ungroup: function(parent, depth) { - if(depth === 0 || this instanceof SVG.Defs || this.node == SVG.parser.draw) return this - - parent = parent || (this instanceof SVG.Doc ? this : this.parent(SVG.Parent)) - depth = depth || Infinity - - this.each(function(){ - if(this instanceof SVG.Defs) return this - if(this instanceof SVG.Parent) return this.ungroup(parent, depth-1) - return this.toParent(parent) - }) - - this.node.firstChild || this.remove() - - return this - }, - - flatten: function(parent, depth) { - return this.ungroup(parent, depth) - } - -}) -SVG.Container = SVG.invent({ - // Initialize node - create: function(element) { - this.constructor.call(this, element) - } - - // Inherit from -, inherit: SVG.Parent - -}) - -SVG.ViewBox = SVG.invent({ - - create: function(source) { - var i, base = [0, 0, 0, 0] - - var x, y, width, height, box, view, we, he - , wm = 1 // width multiplier - , hm = 1 // height multiplier - , reg = /[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/gi - - if(source instanceof SVG.Element){ - - we = source - he = source - view = (source.attr('viewBox') || '').match(reg) - box = source.bbox - - // get dimensions of current node - width = new SVG.Number(source.width()) - height = new SVG.Number(source.height()) - - // find nearest non-percentual dimensions - while (width.unit == '%') { - wm *= width.value - width = new SVG.Number(we instanceof SVG.Doc ? we.parent().offsetWidth : we.parent().width()) - we = we.parent() - } - while (height.unit == '%') { - hm *= height.value - height = new SVG.Number(he instanceof SVG.Doc ? he.parent().offsetHeight : he.parent().height()) - he = he.parent() - } - - // ensure defaults - this.x = 0 - this.y = 0 - this.width = width * wm - this.height = height * hm - this.zoom = 1 - - if (view) { - // get width and height from viewbox - x = parseFloat(view[0]) - y = parseFloat(view[1]) - width = parseFloat(view[2]) - height = parseFloat(view[3]) - - // calculate zoom accoring to viewbox - this.zoom = ((this.width / this.height) > (width / height)) ? - this.height / height : - this.width / width - - // calculate real pixel dimensions on parent SVG.Doc element - this.x = x - this.y = y - this.width = width - this.height = height - - } - - }else{ - - // ensure source as object - source = typeof source === 'string' ? - source.match(reg).map(function(el){ return parseFloat(el) }) : - Array.isArray(source) ? - source : - typeof source == 'object' ? - [source.x, source.y, source.width, source.height] : - arguments.length == 4 ? - [].slice.call(arguments) : - base - - this.x = source[0] - this.y = source[1] - this.width = source[2] - this.height = source[3] - } - - - } - -, extend: { - - toString: function() { - return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height - } - , morph: function(x, y, width, height){ - this.destination = new SVG.ViewBox(x, y, width, height) - return this - } - - , at: function(pos) { - - if(!this.destination) return this - - return new SVG.ViewBox([ - this.x + (this.destination.x - this.x) * pos - , this.y + (this.destination.y - this.y) * pos - , this.width + (this.destination.width - this.width) * pos - , this.height + (this.destination.height - this.height) * pos - ]) - - } - - } - - // Define parent -, parent: SVG.Container - - // Add parent method -, construct: { - - // get/set viewbox - viewbox: function(x, y, width, height) { - if (arguments.length == 0) - // act as a getter if there are no arguments - return new SVG.ViewBox(this) - - // otherwise act as a setter - return this.attr('viewBox', new SVG.ViewBox(x, y, width, height)) - } - - } - -}) -// Add events to elements -;[ 'click' - , 'dblclick' - , 'mousedown' - , 'mouseup' - , 'mouseover' - , 'mouseout' - , 'mousemove' - // , 'mouseenter' -> not supported by IE - // , 'mouseleave' -> not supported by IE - , 'touchstart' - , 'touchmove' - , 'touchleave' - , 'touchend' - , 'touchcancel' ].forEach(function(event) { - - // add event to SVG.Element - SVG.Element.prototype[event] = function(f) { - // bind event to element rather than element node - SVG.on(this.node, event, f) - return this - } -}) - -// Initialize listeners stack -SVG.listeners = [] -SVG.handlerMap = [] -SVG.listenerId = 0 - -// Add event binder in the SVG namespace -SVG.on = function(node, event, listener, binding, options) { - // create listener, get object-index - var l = listener.bind(binding || node.instance || node) - , index = (SVG.handlerMap.indexOf(node) + 1 || SVG.handlerMap.push(node)) - 1 - , ev = event.split('.')[0] - , ns = event.split('.')[1] || '*' - - - // ensure valid object - SVG.listeners[index] = SVG.listeners[index] || {} - SVG.listeners[index][ev] = SVG.listeners[index][ev] || {} - SVG.listeners[index][ev][ns] = SVG.listeners[index][ev][ns] || {} - - if(!listener._svgjsListenerId) - listener._svgjsListenerId = ++SVG.listenerId - - // reference listener - SVG.listeners[index][ev][ns][listener._svgjsListenerId] = l - - // add listener - node.addEventListener(ev, l, options || false) -} - -// Add event unbinder in the SVG namespace -SVG.off = function(node, event, listener) { - var index = SVG.handlerMap.indexOf(node) - , ev = event && event.split('.')[0] - , ns = event && event.split('.')[1] - , namespace = '' - - if(index == -1) return - - if (listener) { - if(typeof listener == 'function') listener = listener._svgjsListenerId - if(!listener) return - - // remove listener reference - if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns || '*']) { - // remove listener - node.removeEventListener(ev, SVG.listeners[index][ev][ns || '*'][listener], false) - - delete SVG.listeners[index][ev][ns || '*'][listener] - } - - } else if (ns && ev) { - // remove all listeners for a namespaced event - if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns]) { - for (listener in SVG.listeners[index][ev][ns]) - SVG.off(node, [ev, ns].join('.'), listener) - - delete SVG.listeners[index][ev][ns] - } - - } else if (ns){ - // remove all listeners for a specific namespace - for(event in SVG.listeners[index]){ - for(namespace in SVG.listeners[index][event]){ - if(ns === namespace){ - SVG.off(node, [event, ns].join('.')) - } - } - } - - } else if (ev) { - // remove all listeners for the event - if (SVG.listeners[index][ev]) { - for (namespace in SVG.listeners[index][ev]) - SVG.off(node, [ev, namespace].join('.')) - - delete SVG.listeners[index][ev] - } - - } else { - // remove all listeners on a given node - for (event in SVG.listeners[index]) - SVG.off(node, event) - - delete SVG.listeners[index] - delete SVG.handlerMap[index] - - } -} - -// -SVG.extend(SVG.Element, { - // Bind given event to listener - on: function(event, listener, binding, options) { - SVG.on(this.node, event, listener, binding, options) - - return this - } - // Unbind event from listener -, off: function(event, listener) { - SVG.off(this.node, event, listener) - - return this - } - // Fire given event -, fire: function(event, data) { - - // Dispatch event - if(event instanceof window.Event){ - this.node.dispatchEvent(event) - }else{ - this.node.dispatchEvent(event = new window.CustomEvent(event, {detail:data, cancelable: true})) - } - - this._event = event - return this - } -, event: function() { - return this._event - } -}) - - -SVG.Defs = SVG.invent({ - // Initialize node - create: 'defs' - - // Inherit from -, inherit: SVG.Container - -}) -SVG.G = SVG.invent({ - // Initialize node - create: 'g' - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Move over x-axis - x: function(x) { - return x == null ? this.transform('x') : this.transform({ x: x - this.x() }, true) - } - // Move over y-axis - , y: function(y) { - return y == null ? this.transform('y') : this.transform({ y: y - this.y() }, true) - } - // Move by center over x-axis - , cx: function(x) { - return x == null ? this.gbox().cx : this.x(x - this.gbox().width / 2) - } - // Move by center over y-axis - , cy: function(y) { - return y == null ? this.gbox().cy : this.y(y - this.gbox().height / 2) - } - , gbox: function() { - - var bbox = this.bbox() - , trans = this.transform() - - bbox.x += trans.x - bbox.x2 += trans.x - bbox.cx += trans.x - - bbox.y += trans.y - bbox.y2 += trans.y - bbox.cy += trans.y - - return bbox - } - } - - // Add parent method -, construct: { - // Create a group element - group: function() { - return this.put(new SVG.G) - } - } -}) - -// ### This module adds backward / forward functionality to elements. - -// -SVG.extend(SVG.Element, { - // Get all siblings, including myself - siblings: function() { - return this.parent().children() - } - // Get the curent position siblings -, position: function() { - return this.parent().index(this) - } - // Get the next element (will return null if there is none) -, next: function() { - return this.siblings()[this.position() + 1] - } - // Get the next element (will return null if there is none) -, previous: function() { - return this.siblings()[this.position() - 1] - } - // Send given element one step forward -, forward: function() { - var i = this.position() + 1 - , p = this.parent() - - // move node one step forward - p.removeElement(this).add(this, i) - - // make sure defs node is always at the top - if (p instanceof SVG.Doc) - p.node.appendChild(p.defs().node) - - return this - } - // Send given element one step backward -, backward: function() { - var i = this.position() - - if (i > 0) - this.parent().removeElement(this).add(this, i - 1) - - return this - } - // Send given element all the way to the front -, front: function() { - var p = this.parent() - - // Move node forward - p.node.appendChild(this.node) - - // Make sure defs node is always at the top - if (p instanceof SVG.Doc) - p.node.appendChild(p.defs().node) - - return this - } - // Send given element all the way to the back -, back: function() { - if (this.position() > 0) - this.parent().removeElement(this).add(this, 0) - - return this - } - // Inserts a given element before the targeted element -, before: function(element) { - element.remove() - - var i = this.position() - - this.parent().add(element, i) - - return this - } - // Insters a given element after the targeted element -, after: function(element) { - element.remove() - - var i = this.position() - - this.parent().add(element, i + 1) - - return this - } - -}) -SVG.Mask = SVG.invent({ - // Initialize node - create: function() { - this.constructor.call(this, SVG.create('mask')) - - // keep references to masked elements - this.targets = [] - } - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Unmask all masked elements and remove itself - remove: function() { - // unmask all targets - for (var i = this.targets.length - 1; i >= 0; i--) - if (this.targets[i]) - this.targets[i].unmask() - this.targets = [] - - // remove mask from parent - this.parent().removeElement(this) - - return this - } - } - - // Add parent method -, construct: { - // Create masking element - mask: function() { - return this.defs().put(new SVG.Mask) - } - } -}) - - -SVG.extend(SVG.Element, { - // Distribute mask to svg element - maskWith: function(element) { - // use given mask or create a new one - this.masker = element instanceof SVG.Mask ? element : this.parent().mask().add(element) - - // store reverence on self in mask - this.masker.targets.push(this) - - // apply mask - return this.attr('mask', 'url("#' + this.masker.attr('id') + '")') - } - // Unmask element -, unmask: function() { - delete this.masker - return this.attr('mask', null) - } - -}) - -SVG.ClipPath = SVG.invent({ - // Initialize node - create: function() { - this.constructor.call(this, SVG.create('clipPath')) - - // keep references to clipped elements - this.targets = [] - } - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Unclip all clipped elements and remove itself - remove: function() { - // unclip all targets - for (var i = this.targets.length - 1; i >= 0; i--) - if (this.targets[i]) - this.targets[i].unclip() - this.targets = [] - - // remove clipPath from parent - this.parent().removeElement(this) - - return this - } - } - - // Add parent method -, construct: { - // Create clipping element - clip: function() { - return this.defs().put(new SVG.ClipPath) - } - } -}) - -// -SVG.extend(SVG.Element, { - // Distribute clipPath to svg element - clipWith: function(element) { - // use given clip or create a new one - this.clipper = element instanceof SVG.ClipPath ? element : this.parent().clip().add(element) - - // store reverence on self in mask - this.clipper.targets.push(this) - - // apply mask - return this.attr('clip-path', 'url("#' + this.clipper.attr('id') + '")') - } - // Unclip element -, unclip: function() { - delete this.clipper - return this.attr('clip-path', null) - } - -}) -SVG.Gradient = SVG.invent({ - // Initialize node - create: function(type) { - this.constructor.call(this, SVG.create(type + 'Gradient')) - - // store type - this.type = type - } - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Add a color stop - at: function(offset, color, opacity) { - return this.put(new SVG.Stop).update(offset, color, opacity) - } - // Update gradient - , update: function(block) { - // remove all stops - this.clear() - - // invoke passed block - if (typeof block == 'function') - block.call(this, this) - - return this - } - // Return the fill id - , fill: function() { - return 'url(#' + this.id() + ')' - } - // Alias string convertion to fill - , toString: function() { - return this.fill() - } - // custom attr to handle transform - , attr: function(a, b, c) { - if(a == 'transform') a = 'gradientTransform' - return SVG.Container.prototype.attr.call(this, a, b, c) - } - } - - // Add parent method -, construct: { - // Create gradient element in defs - gradient: function(type, block) { - return this.defs().gradient(type, block) - } - } -}) - -// Add animatable methods to both gradient and fx module -SVG.extend(SVG.Gradient, SVG.FX, { - // From position - from: function(x, y) { - return (this._target || this).type == 'radial' ? - this.attr({ fx: new SVG.Number(x), fy: new SVG.Number(y) }) : - this.attr({ x1: new SVG.Number(x), y1: new SVG.Number(y) }) - } - // To position -, to: function(x, y) { - return (this._target || this).type == 'radial' ? - this.attr({ cx: new SVG.Number(x), cy: new SVG.Number(y) }) : - this.attr({ x2: new SVG.Number(x), y2: new SVG.Number(y) }) - } -}) - -// Base gradient generation -SVG.extend(SVG.Defs, { - // define gradient - gradient: function(type, block) { - return this.put(new SVG.Gradient(type)).update(block) - } - -}) - -SVG.Stop = SVG.invent({ - // Initialize node - create: 'stop' - - // Inherit from -, inherit: SVG.Element - - // Add class methods -, extend: { - // add color stops - update: function(o) { - if (typeof o == 'number' || o instanceof SVG.Number) { - o = { - offset: arguments[0] - , color: arguments[1] - , opacity: arguments[2] - } - } - - // set attributes - if (o.opacity != null) this.attr('stop-opacity', o.opacity) - if (o.color != null) this.attr('stop-color', o.color) - if (o.offset != null) this.attr('offset', new SVG.Number(o.offset)) - - return this - } - } - -}) - -SVG.Pattern = SVG.invent({ - // Initialize node - create: 'pattern' - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Return the fill id - fill: function() { - return 'url(#' + this.id() + ')' - } - // Update pattern by rebuilding - , update: function(block) { - // remove content - this.clear() - - // invoke passed block - if (typeof block == 'function') - block.call(this, this) - - return this - } - // Alias string convertion to fill - , toString: function() { - return this.fill() - } - // custom attr to handle transform - , attr: function(a, b, c) { - if(a == 'transform') a = 'patternTransform' - return SVG.Container.prototype.attr.call(this, a, b, c) - } - - } - - // Add parent method -, construct: { - // Create pattern element in defs - pattern: function(width, height, block) { - return this.defs().pattern(width, height, block) - } - } -}) - -SVG.extend(SVG.Defs, { - // Define gradient - pattern: function(width, height, block) { - return this.put(new SVG.Pattern).update(block).attr({ - x: 0 - , y: 0 - , width: width - , height: height - , patternUnits: 'userSpaceOnUse' - }) - } - -}) -SVG.Doc = SVG.invent({ - // Initialize node - create: function(element) { - if (element) { - // ensure the presence of a dom element - element = typeof element == 'string' ? - document.getElementById(element) : - element - - // If the target is an svg element, use that element as the main wrapper. - // This allows svg.js to work with svg documents as well. - if (element.nodeName == 'svg') { - this.constructor.call(this, element) - } else { - this.constructor.call(this, SVG.create('svg')) - element.appendChild(this.node) - this.size('100%', '100%') - } - - // set svg element attributes and ensure defs node - this.namespace().defs() - } - } - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Add namespaces - namespace: function() { - return this - .attr({ xmlns: SVG.ns, version: '1.1' }) - .attr('xmlns:xlink', SVG.xlink, SVG.xmlns) - .attr('xmlns:svgjs', SVG.svgjs, SVG.xmlns) - } - // Creates and returns defs element - , defs: function() { - if (!this._defs) { - var defs - - // Find or create a defs element in this instance - if (defs = this.node.getElementsByTagName('defs')[0]) - this._defs = SVG.adopt(defs) - else - this._defs = new SVG.Defs - - // Make sure the defs node is at the end of the stack - this.node.appendChild(this._defs.node) - } - - return this._defs - } - // custom parent method - , parent: function() { - return this.node.parentNode.nodeName == '#document' ? null : this.node.parentNode - } - // Fix for possible sub-pixel offset. See: - // https://bugzilla.mozilla.org/show_bug.cgi?id=608812 - , spof: function(spof) { - var pos = this.node.getScreenCTM() - - if (pos) - this - .style('left', (-pos.e % 1) + 'px') - .style('top', (-pos.f % 1) + 'px') - - return this - } - - // Removes the doc from the DOM - , remove: function() { - if(this.parent()) { - this.parent().removeChild(this.node) - } - - return this - } - , clear: function() { - // remove children - while(this.node.hasChildNodes()) - this.node.removeChild(this.node.lastChild) - - // remove defs reference - delete this._defs - - // add back parser - if(!SVG.parser.draw.parentNode) - this.node.appendChild(SVG.parser.draw) - - return this - } - } - -}) - -SVG.Shape = SVG.invent({ - // Initialize node - create: function(element) { - this.constructor.call(this, element) - } - - // Inherit from -, inherit: SVG.Element - -}) - -SVG.Bare = SVG.invent({ - // Initialize - create: function(element, inherit) { - // construct element - this.constructor.call(this, SVG.create(element)) - - // inherit custom methods - if (inherit) - for (var method in inherit.prototype) - if (typeof inherit.prototype[method] === 'function') - this[method] = inherit.prototype[method] - } - - // Inherit from -, inherit: SVG.Element - - // Add methods -, extend: { - // Insert some plain text - words: function(text) { - // remove contents - while (this.node.hasChildNodes()) - this.node.removeChild(this.node.lastChild) - - // create text node - this.node.appendChild(document.createTextNode(text)) - - return this - } - } -}) - - -SVG.extend(SVG.Parent, { - // Create an element that is not described by SVG.js - element: function(element, inherit) { - return this.put(new SVG.Bare(element, inherit)) - } -}) - -SVG.Symbol = SVG.invent({ - // Initialize node - create: 'symbol' - - // Inherit from -, inherit: SVG.Container - -, construct: { - // create symbol - symbol: function() { - return this.put(new SVG.Symbol) - } - } -}) - -SVG.Use = SVG.invent({ - // Initialize node - create: 'use' - - // Inherit from -, inherit: SVG.Shape - - // Add class methods -, extend: { - // Use element as a reference - element: function(element, file) { - // Set lined element - return this.attr('href', (file || '') + '#' + element, SVG.xlink) - } - } - - // Add parent method -, construct: { - // Create a use element - use: function(element, file) { - return this.put(new SVG.Use).element(element, file) - } - } -}) -SVG.Rect = SVG.invent({ - // Initialize node - create: 'rect' - - // Inherit from -, inherit: SVG.Shape - - // Add parent method -, construct: { - // Create a rect element - rect: function(width, height) { - return this.put(new SVG.Rect()).size(width, height) - } - } -}) -SVG.Circle = SVG.invent({ - // Initialize node - create: 'circle' - - // Inherit from -, inherit: SVG.Shape - - // Add parent method -, construct: { - // Create circle element, based on ellipse - circle: function(size) { - return this.put(new SVG.Circle).rx(new SVG.Number(size).divide(2)).move(0, 0) - } - } -}) - -SVG.extend(SVG.Circle, SVG.FX, { - // Radius x value - rx: function(rx) { - return this.attr('r', rx) - } - // Alias radius x value -, ry: function(ry) { - return this.rx(ry) - } -}) - -SVG.Ellipse = SVG.invent({ - // Initialize node - create: 'ellipse' - - // Inherit from -, inherit: SVG.Shape - - // Add parent method -, construct: { - // Create an ellipse - ellipse: function(width, height) { - return this.put(new SVG.Ellipse).size(width, height).move(0, 0) - } - } -}) - -SVG.extend(SVG.Ellipse, SVG.Rect, SVG.FX, { - // Radius x value - rx: function(rx) { - return this.attr('rx', rx) - } - // Radius y value -, ry: function(ry) { - return this.attr('ry', ry) - } -}) - -// Add common method -SVG.extend(SVG.Circle, SVG.Ellipse, { - // Move over x-axis - x: function(x) { - return x == null ? this.cx() - this.rx() : this.cx(x + this.rx()) - } - // Move over y-axis - , y: function(y) { - return y == null ? this.cy() - this.ry() : this.cy(y + this.ry()) - } - // Move by center over x-axis - , cx: function(x) { - return x == null ? this.attr('cx') : this.attr('cx', x) - } - // Move by center over y-axis - , cy: function(y) { - return y == null ? this.attr('cy') : this.attr('cy', y) - } - // Set width of element - , width: function(width) { - return width == null ? this.rx() * 2 : this.rx(new SVG.Number(width).divide(2)) - } - // Set height of element - , height: function(height) { - return height == null ? this.ry() * 2 : this.ry(new SVG.Number(height).divide(2)) - } - // Custom size function - , size: function(width, height) { - var p = proportionalSize(this, width, height) - - return this - .rx(new SVG.Number(p.width).divide(2)) - .ry(new SVG.Number(p.height).divide(2)) - } -}) -SVG.Line = SVG.invent({ - // Initialize node - create: 'line' - - // Inherit from -, inherit: SVG.Shape - - // Add class methods -, extend: { - // Get array - array: function() { - return new SVG.PointArray([ - [ this.attr('x1'), this.attr('y1') ] - , [ this.attr('x2'), this.attr('y2') ] - ]) - } - // Overwrite native plot() method - , plot: function(x1, y1, x2, y2) { - if (x1 == null) - return this.array() - else if (typeof y1 !== 'undefined') - x1 = { x1: x1, y1: y1, x2: x2, y2: y2 } - else - x1 = new SVG.PointArray(x1).toLine() - - return this.attr(x1) - } - // Move by left top corner - , move: function(x, y) { - return this.attr(this.array().move(x, y).toLine()) - } - // Set element size to given width and height - , size: function(width, height) { - var p = proportionalSize(this, width, height) - - return this.attr(this.array().size(p.width, p.height).toLine()) - } - } - - // Add parent method -, construct: { - // Create a line element - line: function(x1, y1, x2, y2) { - // make sure plot is called as a setter - // x1 is not necessarily a number, it can also be an array, a string and a SVG.PointArray - return SVG.Line.prototype.plot.apply( - this.put(new SVG.Line) - , x1 != null ? [x1, y1, x2, y2] : [0, 0, 0, 0] - ) - } - } -}) - -SVG.Polyline = SVG.invent({ - // Initialize node - create: 'polyline' - - // Inherit from -, inherit: SVG.Shape - - // Add parent method -, construct: { - // Create a wrapped polyline element - polyline: function(p) { - // make sure plot is called as a setter - return this.put(new SVG.Polyline).plot(p || new SVG.PointArray) - } - } -}) - -SVG.Polygon = SVG.invent({ - // Initialize node - create: 'polygon' - - // Inherit from -, inherit: SVG.Shape - - // Add parent method -, construct: { - // Create a wrapped polygon element - polygon: function(p) { - // make sure plot is called as a setter - return this.put(new SVG.Polygon).plot(p || new SVG.PointArray) - } - } -}) - -// Add polygon-specific functions -SVG.extend(SVG.Polyline, SVG.Polygon, { - // Get array - array: function() { - return this._array || (this._array = new SVG.PointArray(this.attr('points'))) - } - // Plot new path -, plot: function(p) { - return (p == null) ? - this.array() : - this.clear().attr('points', typeof p == 'string' ? p : (this._array = new SVG.PointArray(p))) - } - // Clear array cache -, clear: function() { - delete this._array - return this - } - // Move by left top corner -, move: function(x, y) { - return this.attr('points', this.array().move(x, y)) - } - // Set element size to given width and height -, size: function(width, height) { - var p = proportionalSize(this, width, height) - - return this.attr('points', this.array().size(p.width, p.height)) - } - -}) - -// unify all point to point elements -SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, { - // Define morphable array - morphArray: SVG.PointArray - // Move by left top corner over x-axis -, x: function(x) { - return x == null ? this.bbox().x : this.move(x, this.bbox().y) - } - // Move by left top corner over y-axis -, y: function(y) { - return y == null ? this.bbox().y : this.move(this.bbox().x, y) - } - // Set width of element -, width: function(width) { - var b = this.bbox() - - return width == null ? b.width : this.size(width, b.height) - } - // Set height of element -, height: function(height) { - var b = this.bbox() - - return height == null ? b.height : this.size(b.width, height) - } -}) -SVG.Path = SVG.invent({ - // Initialize node - create: 'path' - - // Inherit from -, inherit: SVG.Shape - - // Add class methods -, extend: { - // Define morphable array - morphArray: SVG.PathArray - // Get array - , array: function() { - return this._array || (this._array = new SVG.PathArray(this.attr('d'))) - } - // Plot new path - , plot: function(d) { - return (d == null) ? - this.array() : - this.clear().attr('d', typeof d == 'string' ? d : (this._array = new SVG.PathArray(d))) - } - // Clear array cache - , clear: function() { - delete this._array - return this - } - // Move by left top corner - , move: function(x, y) { - return this.attr('d', this.array().move(x, y)) - } - // Move by left top corner over x-axis - , x: function(x) { - return x == null ? this.bbox().x : this.move(x, this.bbox().y) - } - // Move by left top corner over y-axis - , y: function(y) { - return y == null ? this.bbox().y : this.move(this.bbox().x, y) - } - // Set element size to given width and height - , size: function(width, height) { - var p = proportionalSize(this, width, height) - - return this.attr('d', this.array().size(p.width, p.height)) - } - // Set width of element - , width: function(width) { - return width == null ? this.bbox().width : this.size(width, this.bbox().height) - } - // Set height of element - , height: function(height) { - return height == null ? this.bbox().height : this.size(this.bbox().width, height) - } - - } - - // Add parent method -, construct: { - // Create a wrapped path element - path: function(d) { - // make sure plot is called as a setter - return this.put(new SVG.Path).plot(d || new SVG.PathArray) - } - } -}) - -SVG.Image = SVG.invent({ - // Initialize node - create: 'image' - - // Inherit from -, inherit: SVG.Shape - - // Add class methods -, extend: { - // (re)load image - load: function(url) { - if (!url) return this - - var self = this - , img = new window.Image() - - // preload image - SVG.on(img, 'load', function() { - var p = self.parent(SVG.Pattern) - - if(p === null) return - - // ensure image size - if (self.width() == 0 && self.height() == 0) - self.size(img.width, img.height) - - // ensure pattern size if not set - if (p && p.width() == 0 && p.height() == 0) - p.size(self.width(), self.height()) - - // callback - if (typeof self._loaded === 'function') - self._loaded.call(self, { - width: img.width - , height: img.height - , ratio: img.width / img.height - , url: url - }) - }) - - SVG.on(img, 'error', function(e){ - if (typeof self._error === 'function'){ - self._error.call(self, e) - } - }) - - return this.attr('href', (img.src = this.src = url), SVG.xlink) - } - // Add loaded callback - , loaded: function(loaded) { - this._loaded = loaded - return this - } - - , error: function(error) { - this._error = error - return this - } - } - - // Add parent method -, construct: { - // create image element, load image and set its size - image: function(source, width, height) { - return this.put(new SVG.Image).load(source).size(width || 0, height || width || 0) - } - } - -}) -SVG.Text = SVG.invent({ - // Initialize node - create: function() { - this.constructor.call(this, SVG.create('text')) - - this.dom.leading = new SVG.Number(1.3) // store leading value for rebuilding - this._rebuild = true // enable automatic updating of dy values - this._build = false // disable build mode for adding multiple lines - - // set default font - this.attr('font-family', SVG.defaults.attrs['font-family']) - } - - // Inherit from -, inherit: SVG.Shape - - // Add class methods -, extend: { - // Move over x-axis - x: function(x) { - // act as getter - if (x == null) - return this.attr('x') - - return this.attr('x', x) - } - // Move over y-axis - , y: function(y) { - var oy = this.attr('y') - , o = typeof oy === 'number' ? oy - this.bbox().y : 0 - - // act as getter - if (y == null) - return typeof oy === 'number' ? oy - o : oy - - return this.attr('y', typeof y === 'number' ? y + o : y) - } - // Move center over x-axis - , cx: function(x) { - return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2) - } - // Move center over y-axis - , cy: function(y) { - return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2) - } - // Set the text content - , text: function(text) { - // act as getter - if (typeof text === 'undefined'){ - var text = '' - var children = this.node.childNodes - for(var i = 0, len = children.length; i < len; ++i){ - - // add newline if its not the first child and newLined is set to true - if(i != 0 && children[i].nodeType != 3 && SVG.adopt(children[i]).dom.newLined == true){ - text += '\n' - } - - // add content of this node - text += children[i].textContent - } - - return text - } - - // remove existing content - this.clear().build(true) - - if (typeof text === 'function') { - // call block - text.call(this, this) - - } else { - // store text and make sure text is not blank - text = text.split('\n') - - // build new lines - for (var i = 0, il = text.length; i < il; i++) - this.tspan(text[i]).newLine() - } - - // disable build mode and rebuild lines - return this.build(false).rebuild() - } - // Set font size - , size: function(size) { - return this.attr('font-size', size).rebuild() - } - // Set / get leading - , leading: function(value) { - // act as getter - if (value == null) - return this.dom.leading - - // act as setter - this.dom.leading = new SVG.Number(value) - - return this.rebuild() - } - // Get all the first level lines - , lines: function() { - var node = (this.textPath && this.textPath() || this).node - - // filter tspans and map them to SVG.js instances - var lines = SVG.utils.map(SVG.utils.filterSVGElements(node.childNodes), function(el){ - return SVG.adopt(el) - }) - - // return an instance of SVG.set - return new SVG.Set(lines) - } - // Rebuild appearance type - , rebuild: function(rebuild) { - // store new rebuild flag if given - if (typeof rebuild == 'boolean') - this._rebuild = rebuild - - // define position of all lines - if (this._rebuild) { - var self = this - , blankLineOffset = 0 - , dy = this.dom.leading * new SVG.Number(this.attr('font-size')) - - this.lines().each(function() { - if (this.dom.newLined) { - if (!self.textPath()) - this.attr('x', self.attr('x')) - if(this.text() == '\n') { - blankLineOffset += dy - }else{ - this.attr('dy', dy + blankLineOffset) - blankLineOffset = 0 - } - } - }) - - this.fire('rebuild') - } - - return this - } - // Enable / disable build mode - , build: function(build) { - this._build = !!build - return this - } - // overwrite method from parent to set data properly - , setData: function(o){ - this.dom = o - this.dom.leading = new SVG.Number(o.leading || 1.3) - return this - } - } - - // Add parent method -, construct: { - // Create text element - text: function(text) { - return this.put(new SVG.Text).text(text) - } - // Create plain text element - , plain: function(text) { - return this.put(new SVG.Text).plain(text) - } - } - -}) - -SVG.Tspan = SVG.invent({ - // Initialize node - create: 'tspan' - - // Inherit from -, inherit: SVG.Shape - - // Add class methods -, extend: { - // Set text content - text: function(text) { - if(text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '') - - typeof text === 'function' ? text.call(this, this) : this.plain(text) - - return this - } - // Shortcut dx - , dx: function(dx) { - return this.attr('dx', dx) - } - // Shortcut dy - , dy: function(dy) { - return this.attr('dy', dy) - } - // Create new line - , newLine: function() { - // fetch text parent - var t = this.parent(SVG.Text) - - // mark new line - this.dom.newLined = true - - // apply new hyĀ”n - return this.dy(t.dom.leading * t.attr('font-size')).attr('x', t.x()) - } - } - -}) - -SVG.extend(SVG.Text, SVG.Tspan, { - // Create plain text node - plain: function(text) { - // clear if build mode is disabled - if (this._build === false) - this.clear() - - // create text node - this.node.appendChild(document.createTextNode(text)) - - return this - } - // Create a tspan -, tspan: function(text) { - var node = (this.textPath && this.textPath() || this).node - , tspan = new SVG.Tspan - - // clear if build mode is disabled - if (this._build === false) - this.clear() - - // add new tspan - node.appendChild(tspan.node) - - return tspan.text(text) - } - // Clear all lines -, clear: function() { - var node = (this.textPath && this.textPath() || this).node - - // remove existing child nodes - while (node.hasChildNodes()) - node.removeChild(node.lastChild) - - return this - } - // Get length of text element -, length: function() { - return this.node.getComputedTextLength() - } -}) - -SVG.TextPath = SVG.invent({ - // Initialize node - create: 'textPath' - - // Inherit from -, inherit: SVG.Parent - - // Define parent class -, parent: SVG.Text - - // Add parent method -, construct: { - morphArray: SVG.PathArray - // Create path for text to run on - , path: function(d) { - // create textPath element - var path = new SVG.TextPath - , track = this.doc().defs().path(d) - - // move lines to textpath - while (this.node.hasChildNodes()) - path.node.appendChild(this.node.firstChild) - - // add textPath element as child node - this.node.appendChild(path.node) - - // link textPath to path and add content - path.attr('href', '#' + track, SVG.xlink) - - return this - } - // return the array of the path track element - , array: function() { - var track = this.track() - - return track ? track.array() : null - } - // Plot path if any - , plot: function(d) { - var track = this.track() - , pathArray = null - - if (track) { - pathArray = track.plot(d) - } - - return (d == null) ? pathArray : this - } - // Get the path track element - , track: function() { - var path = this.textPath() - - if (path) - return path.reference('href') - } - // Get the textPath child - , textPath: function() { - if (this.node.firstChild && this.node.firstChild.nodeName == 'textPath') - return SVG.adopt(this.node.firstChild) - } - } -}) - -SVG.Nested = SVG.invent({ - // Initialize node - create: function() { - this.constructor.call(this, SVG.create('svg')) - - this.style('overflow', 'visible') - } - - // Inherit from -, inherit: SVG.Container - - // Add parent method -, construct: { - // Create nested svg document - nested: function() { - return this.put(new SVG.Nested) - } - } -}) -SVG.A = SVG.invent({ - // Initialize node - create: 'a' - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Link url - to: function(url) { - return this.attr('href', url, SVG.xlink) - } - // Link show attribute - , show: function(target) { - return this.attr('show', target, SVG.xlink) - } - // Link target attribute - , target: function(target) { - return this.attr('target', target) - } - } - - // Add parent method -, construct: { - // Create a hyperlink element - link: function(url) { - return this.put(new SVG.A).to(url) - } - } -}) - -SVG.extend(SVG.Element, { - // Create a hyperlink element - linkTo: function(url) { - var link = new SVG.A - - if (typeof url == 'function') - url.call(link, link) - else - link.to(url) - - return this.parent().put(link).put(this) - } - -}) -SVG.Marker = SVG.invent({ - // Initialize node - create: 'marker' - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Set width of element - width: function(width) { - return this.attr('markerWidth', width) - } - // Set height of element - , height: function(height) { - return this.attr('markerHeight', height) - } - // Set marker refX and refY - , ref: function(x, y) { - return this.attr('refX', x).attr('refY', y) - } - // Update marker - , update: function(block) { - // remove all content - this.clear() - - // invoke passed block - if (typeof block == 'function') - block.call(this, this) - - return this - } - // Return the fill id - , toString: function() { - return 'url(#' + this.id() + ')' - } - } - - // Add parent method -, construct: { - marker: function(width, height, block) { - // Create marker element in defs - return this.defs().marker(width, height, block) - } - } - -}) - -SVG.extend(SVG.Defs, { - // Create marker - marker: function(width, height, block) { - // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto - return this.put(new SVG.Marker) - .size(width, height) - .ref(width / 2, height / 2) - .viewbox(0, 0, width, height) - .attr('orient', 'auto') - .update(block) - } - -}) - -SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, SVG.Path, { - // Create and attach markers - marker: function(marker, width, height, block) { - var attr = ['marker'] - - // Build attribute name - if (marker != 'all') attr.push(marker) - attr = attr.join('-') - - // Set marker attribute - marker = arguments[1] instanceof SVG.Marker ? - arguments[1] : - this.doc().marker(width, height, block) - - return this.attr(attr, marker) - } - -}) -// Define list of available attributes for stroke and fill -var sugar = { - stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'] -, fill: ['color', 'opacity', 'rule'] -, prefix: function(t, a) { - return a == 'color' ? t : t + '-' + a - } -} - -// Add sugar for fill and stroke -;['fill', 'stroke'].forEach(function(m) { - var i, extension = {} - - extension[m] = function(o) { - if (typeof o == 'undefined') - return this - if (typeof o == 'string' || SVG.Color.isRgb(o) || (o && typeof o.fill === 'function')) - this.attr(m, o) - - else - // set all attributes from sugar.fill and sugar.stroke list - for (i = sugar[m].length - 1; i >= 0; i--) - if (o[sugar[m][i]] != null) - this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]) - - return this - } - - SVG.extend(SVG.Element, SVG.FX, extension) - -}) - -SVG.extend(SVG.Element, SVG.FX, { - // Map rotation to transform - rotate: function(d, cx, cy) { - return this.transform({ rotation: d, cx: cx, cy: cy }) - } - // Map skew to transform -, skew: function(x, y, cx, cy) { - return arguments.length == 1 || arguments.length == 3 ? - this.transform({ skew: x, cx: y, cy: cx }) : - this.transform({ skewX: x, skewY: y, cx: cx, cy: cy }) - } - // Map scale to transform -, scale: function(x, y, cx, cy) { - return arguments.length == 1 || arguments.length == 3 ? - this.transform({ scale: x, cx: y, cy: cx }) : - this.transform({ scaleX: x, scaleY: y, cx: cx, cy: cy }) - } - // Map translate to transform -, translate: function(x, y) { - return this.transform({ x: x, y: y }) - } - // Map flip to transform -, flip: function(a, o) { - o = typeof a == 'number' ? a : o - return this.transform({ flip: a || 'both', offset: o }) - } - // Map matrix to transform -, matrix: function(m) { - return this.attr('transform', new SVG.Matrix(arguments.length == 6 ? [].slice.call(arguments) : m)) - } - // Opacity -, opacity: function(value) { - return this.attr('opacity', value) - } - // Relative move over x axis -, dx: function(x) { - return this.x(new SVG.Number(x).plus(this instanceof SVG.FX ? 0 : this.x()), true) - } - // Relative move over y axis -, dy: function(y) { - return this.y(new SVG.Number(y).plus(this instanceof SVG.FX ? 0 : this.y()), true) - } - // Relative move over x and y axes -, dmove: function(x, y) { - return this.dx(x).dy(y) - } -}) - -SVG.extend(SVG.Rect, SVG.Ellipse, SVG.Circle, SVG.Gradient, SVG.FX, { - // Add x and y radius - radius: function(x, y) { - var type = (this._target || this).type; - return type == 'radial' || type == 'circle' ? - this.attr('r', new SVG.Number(x)) : - this.rx(x).ry(y == null ? x : y) - } -}) - -SVG.extend(SVG.Path, { - // Get path length - length: function() { - return this.node.getTotalLength() - } - // Get point at length -, pointAt: function(length) { - return this.node.getPointAtLength(length) - } -}) - -SVG.extend(SVG.Parent, SVG.Text, SVG.Tspan, SVG.FX, { - // Set font - font: function(a, v) { - if (typeof a == 'object') { - for (v in a) this.font(v, a[v]) - } - - return a == 'leading' ? - this.leading(v) : - a == 'anchor' ? - this.attr('text-anchor', v) : - a == 'size' || a == 'family' || a == 'weight' || a == 'stretch' || a == 'variant' || a == 'style' ? - this.attr('font-'+ a, v) : - this.attr(a, v) - } -}) - -SVG.Set = SVG.invent({ - // Initialize - create: function(members) { - // Set initial state - Array.isArray(members) ? this.members = members : this.clear() - } - - // Add class methods -, extend: { - // Add element to set - add: function() { - var i, il, elements = [].slice.call(arguments) - - for (i = 0, il = elements.length; i < il; i++) - this.members.push(elements[i]) - - return this - } - // Remove element from set - , remove: function(element) { - var i = this.index(element) - - // remove given child - if (i > -1) - this.members.splice(i, 1) - - return this - } - // Iterate over all members - , each: function(block) { - for (var i = 0, il = this.members.length; i < il; i++) - block.apply(this.members[i], [i, this.members]) - - return this - } - // Restore to defaults - , clear: function() { - // initialize store - this.members = [] - - return this - } - // Get the length of a set - , length: function() { - return this.members.length - } - // Checks if a given element is present in set - , has: function(element) { - return this.index(element) >= 0 - } - // retuns index of given element in set - , index: function(element) { - return this.members.indexOf(element) - } - // Get member at given index - , get: function(i) { - return this.members[i] - } - // Get first member - , first: function() { - return this.get(0) - } - // Get last member - , last: function() { - return this.get(this.members.length - 1) - } - // Default value - , valueOf: function() { - return this.members - } - // Get the bounding box of all members included or empty box if set has no items - , bbox: function(){ - // return an empty box of there are no members - if (this.members.length == 0) - return new SVG.RBox() - - // get the first rbox and update the target bbox - var rbox = this.members[0].rbox(this.members[0].doc()) - - this.each(function() { - // user rbox for correct position and visual representation - rbox = rbox.merge(this.rbox(this.doc())) - }) - - return rbox - } - } - - // Add parent method -, construct: { - // Create a new set - set: function(members) { - return new SVG.Set(members) - } - } -}) - -SVG.FX.Set = SVG.invent({ - // Initialize node - create: function(set) { - // store reference to set - this.set = set - } - -}) - -// Alias methods -SVG.Set.inherit = function() { - var m - , methods = [] - - // gather shape methods - for(var m in SVG.Shape.prototype) - if (typeof SVG.Shape.prototype[m] == 'function' && typeof SVG.Set.prototype[m] != 'function') - methods.push(m) - - // apply shape aliasses - methods.forEach(function(method) { - SVG.Set.prototype[method] = function() { - for (var i = 0, il = this.members.length; i < il; i++) - if (this.members[i] && typeof this.members[i][method] == 'function') - this.members[i][method].apply(this.members[i], arguments) - - return method == 'animate' ? (this.fx || (this.fx = new SVG.FX.Set(this))) : this - } - }) - - // clear methods for the next round - methods = [] - - // gather fx methods - for(var m in SVG.FX.prototype) - if (typeof SVG.FX.prototype[m] == 'function' && typeof SVG.FX.Set.prototype[m] != 'function') - methods.push(m) - - // apply fx aliasses - methods.forEach(function(method) { - SVG.FX.Set.prototype[method] = function() { - for (var i = 0, il = this.set.members.length; i < il; i++) - this.set.members[i].fx[method].apply(this.set.members[i].fx, arguments) - - return this - } - }) -} - - - - -SVG.extend(SVG.Element, { - // Store data values on svg nodes - data: function(a, v, r) { - if (typeof a == 'object') { - for (v in a) - this.data(v, a[v]) - - } else if (arguments.length < 2) { - try { - return JSON.parse(this.attr('data-' + a)) - } catch(e) { - return this.attr('data-' + a) - } - - } else { - this.attr( - 'data-' + a - , v === null ? - null : - r === true || typeof v === 'string' || typeof v === 'number' ? - v : - JSON.stringify(v) - ) - } - - return this - } -}) -SVG.extend(SVG.Element, { - // Remember arbitrary data - remember: function(k, v) { - // remember every item in an object individually - if (typeof arguments[0] == 'object') - for (var v in k) - this.remember(v, k[v]) - - // retrieve memory - else if (arguments.length == 1) - return this.memory()[k] - - // store memory - else - this.memory()[k] = v - - return this - } - - // Erase a given memory -, forget: function() { - if (arguments.length == 0) - this._memory = {} - else - for (var i = arguments.length - 1; i >= 0; i--) - delete this.memory()[arguments[i]] - - return this - } - - // Initialize or return local memory object -, memory: function() { - return this._memory || (this._memory = {}) - } - -}) -// Method for getting an element by id -SVG.get = function(id) { - var node = document.getElementById(idFromReference(id) || id) - return SVG.adopt(node) -} - -// Select elements by query string -SVG.select = function(query, parent) { - return new SVG.Set( - SVG.utils.map((parent || document).querySelectorAll(query), function(node) { - return SVG.adopt(node) - }) - ) -} - -SVG.extend(SVG.Parent, { - // Scoped select method - select: function(query) { - return SVG.select(query, this.node) - } - -}) -function pathRegReplace(a, b, c, d) { - return c + d.replace(SVG.regex.dots, ' .') -} - -// creates deep clone of array -function array_clone(arr){ - var clone = arr.slice(0) - for(var i = clone.length; i--;){ - if(Array.isArray(clone[i])){ - clone[i] = array_clone(clone[i]) - } - } - return clone -} - -// tests if a given element is instance of an object -function is(el, obj){ - return el instanceof obj -} - -// tests if a given selector matches an element -function matches(el, selector) { - return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector); -} - -// Convert dash-separated-string to camelCase -function camelCase(s) { - return s.toLowerCase().replace(/-(.)/g, function(m, g) { - return g.toUpperCase() - }) -} - -// Capitalize first letter of a string -function capitalize(s) { - return s.charAt(0).toUpperCase() + s.slice(1) -} - -// Ensure to six-based hex -function fullHex(hex) { - return hex.length == 4 ? - [ '#', - hex.substring(1, 2), hex.substring(1, 2) - , hex.substring(2, 3), hex.substring(2, 3) - , hex.substring(3, 4), hex.substring(3, 4) - ].join('') : hex -} - -// Component to hex value -function compToHex(comp) { - var hex = comp.toString(16) - return hex.length == 1 ? '0' + hex : hex -} - -// Calculate proportional width and height values when necessary -function proportionalSize(element, width, height) { - if (width == null || height == null) { - var box = element.bbox() - - if (width == null) - width = box.width / box.height * height - else if (height == null) - height = box.height / box.width * width - } - - return { - width: width - , height: height - } -} - -// Delta transform point -function deltaTransformPoint(matrix, x, y) { - return { - x: x * matrix.a + y * matrix.c + 0 - , y: x * matrix.b + y * matrix.d + 0 - } -} - -// Map matrix array to object -function arrayToMatrix(a) { - return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] } -} - -// Parse matrix if required -function parseMatrix(matrix) { - if (!(matrix instanceof SVG.Matrix)) - matrix = new SVG.Matrix(matrix) - - return matrix -} - -// Add centre point to transform object -function ensureCentre(o, target) { - o.cx = o.cx == null ? target.bbox().cx : o.cx - o.cy = o.cy == null ? target.bbox().cy : o.cy -} - -// PathArray Helpers -function arrayToString(a) { - for (var i = 0, il = a.length, s = ''; i < il; i++) { - s += a[i][0] - - if (a[i][1] != null) { - s += a[i][1] - - if (a[i][2] != null) { - s += ' ' - s += a[i][2] - - if (a[i][3] != null) { - s += ' ' - s += a[i][3] - s += ' ' - s += a[i][4] - - if (a[i][5] != null) { - s += ' ' - s += a[i][5] - s += ' ' - s += a[i][6] - - if (a[i][7] != null) { - s += ' ' - s += a[i][7] - } - } - } - } - } - } - - return s + ' ' -} - -// Deep new id assignment -function assignNewId(node) { - // do the same for SVG child nodes as well - for (var i = node.childNodes.length - 1; i >= 0; i--) - if (node.childNodes[i] instanceof window.SVGElement) - assignNewId(node.childNodes[i]) - - return SVG.adopt(node).id(SVG.eid(node.nodeName)) -} - -// Add more bounding box properties -function fullBox(b) { - if (b.x == null) { - b.x = 0 - b.y = 0 - b.width = 0 - b.height = 0 - } - - b.w = b.width - b.h = b.height - b.x2 = b.x + b.width - b.y2 = b.y + b.height - b.cx = b.x + b.width / 2 - b.cy = b.y + b.height / 2 - - return b -} - -// Get id from reference string -function idFromReference(url) { - var m = url.toString().match(SVG.regex.reference) - - if (m) return m[1] -} - -// Create matrix array for looping -var abcdef = 'abcdef'.split('') -// Add CustomEvent to IE9 and IE10 -if (typeof window.CustomEvent !== 'function') { - // Code from: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent - var CustomEvent = function(event, options) { - options = options || { bubbles: false, cancelable: false, detail: undefined } - var e = document.createEvent('CustomEvent') - e.initCustomEvent(event, options.bubbles, options.cancelable, options.detail) - return e - } - - CustomEvent.prototype = window.Event.prototype - - window.CustomEvent = CustomEvent -} - -// requestAnimationFrame / cancelAnimationFrame Polyfill with fallback based on Paul Irish -(function(w) { - var lastTime = 0 - var vendors = ['moz', 'webkit'] - - for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { - w.requestAnimationFrame = w[vendors[x] + 'RequestAnimationFrame'] - w.cancelAnimationFrame = w[vendors[x] + 'CancelAnimationFrame'] || - w[vendors[x] + 'CancelRequestAnimationFrame'] - } - - w.requestAnimationFrame = w.requestAnimationFrame || - function(callback) { - var currTime = new Date().getTime() - var timeToCall = Math.max(0, 16 - (currTime - lastTime)) - - var id = w.setTimeout(function() { - callback(currTime + timeToCall) - }, timeToCall) - - lastTime = currTime + timeToCall - return id - } - - w.cancelAnimationFrame = w.cancelAnimationFrame || w.clearTimeout; - -}(window)) - -return SVG - -})); \ No newline at end of file diff --git a/dist/svg.min.js b/dist/svg.min.js deleted file mode 100644 index 03c058e33..000000000 --- a/dist/svg.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! svg.js v2.6.2 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t,t.document)}):"object"==typeof exports?module.exports=t.document?e(t,t.document):function(t){return e(t,t.document)}:t.SVG=e(t,t.document)}("undefined"!=typeof window?window:this,function(t,e){function i(t,e,i,n){return i+n.replace(g.regex.dots," .")}function n(t){for(var e=t.slice(0),i=e.length;i--;)Array.isArray(e[i])&&(e[i]=n(e[i]));return e}function r(t,e){return t instanceof e}function s(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function o(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function a(t){return t.charAt(0).toUpperCase()+t.slice(1)}function h(t){return 4==t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}function u(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function l(t,e,i){if(null==e||null==i){var n=t.bbox();null==e?e=n.width/n.height*i:null==i&&(i=n.height/n.width*e)}return{width:e,height:i}}function c(t,e,i){return{x:e*t.a+i*t.c+0,y:e*t.b+i*t.d+0}}function f(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function d(t){return t instanceof g.Matrix||(t=new g.Matrix(t)),t}function p(t,e){t.cx=null==t.cx?e.bbox().cx:t.cx,t.cy=null==t.cy?e.bbox().cy:t.cy}function m(t){for(var e=0,i=t.length,n="";e=0;i--)e.childNodes[i]instanceof t.SVGElement&&x(e.childNodes[i]);return g.adopt(e).id(g.eid(e.nodeName))}function y(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function v(t){var e=t.toString().match(g.regex.reference);if(e)return e[1]}var g=this.SVG=function(t){if(g.supported)return t=new g.Doc(t),g.parser.draw||g.prepare(),t};if(g.ns="http://www.w3.org/2000/svg",g.xmlns="http://www.w3.org/2000/xmlns/",g.xlink="http://www.w3.org/1999/xlink",g.svgjs="http://svgjs.com/svgjs",g.supported=function(){return!!e.createElementNS&&!!e.createElementNS(g.ns,"svg").createSVGRect}(),!g.supported)return!1;g.did=1e3,g.eid=function(t){return"Svgjs"+a(t)+g.did++},g.create=function(t){var i=e.createElementNS(this.ns,t);return i.setAttribute("id",this.eid(t)),i},g.extend=function(){var t,e,i,n;for(t=[].slice.call(arguments),e=t.pop(),n=t.length-1;n>=0;n--)if(t[n])for(i in e)t[n].prototype[i]=e[i];g.Set&&g.Set.inherit&&g.Set.inherit()},g.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,g.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&g.extend(e,t.extend),t.construct&&g.extend(t.parent||g.Container,t.construct),e},g.adopt=function(e){if(!e)return null;if(e.instance)return e.instance;var i;return i="svg"==e.nodeName?e.parentNode instanceof t.SVGElement?new g.Nested:new g.Doc:"linearGradient"==e.nodeName?new g.Gradient("linear"):"radialGradient"==e.nodeName?new g.Gradient("radial"):g[a(e.nodeName)]?new(g[a(e.nodeName)]):new g.Element(e),i.type=e.nodeName,i.node=e,e.instance=i,i instanceof g.Doc&&i.namespace().defs(),i.setData(JSON.parse(e.getAttribute("svgjs:data"))||{}),i},g.prepare=function(){var t=e.getElementsByTagName("body")[0],i=(t?new g.Doc(t):g.adopt(e.documentElement).nested()).size(2,0);g.parser={body:t||e.documentElement,draw:i.style("opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden").node,poly:i.polyline().node,path:i.path().node,native:g.create("svg")}},g.parser={native:g.create("svg")},e.addEventListener("DOMContentLoaded",function(){g.parser.draw||g.prepare()},!1),g.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,transforms:/\)\s*,?\s*/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,delimiter:/[\s,]+/,hyphen:/([^e])\-/gi,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,numbersWithDots:/((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi,dots:/\./g},g.utils={map:function(t,e){var i,n=t.length,r=[];for(i=0;i1?1:t,new g.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),g.Color.test=function(t){return t+="",g.regex.isHex.test(t)||g.regex.isRgb.test(t)},g.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},g.Color.isColor=function(t){return g.Color.isRgb(t)||g.Color.test(t)},g.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},g.extend(g.Array,{morph:function(t){if(this.destination=this.parse(t),this.value.length!=this.destination.length){for(var e=this.value[this.value.length-1],i=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(i);for(;this.value.length=0;n--)this.value[n]=[this.value[n][0]+t,this.value[n][1]+e];return this},size:function(t,e){var i,n=this.bbox();for(i=this.value.length-1;i>=0;i--)n.width&&(this.value[i][0]=(this.value[i][0]-n.x)*t/n.width+n.x),n.height&&(this.value[i][1]=(this.value[i][1]-n.y)*e/n.height+n.y);return this},bbox:function(){return g.parser.poly.setAttribute("points",this.toString()),g.parser.poly.getBBox()}});for(var w={M:function(t,e,i){return e.x=i.x=t[0],e.y=i.y=t[1],["M",e.x,e.y]},L:function(t,e){return e.x=t[0],e.y=t[1],["L",t[0],t[1]]},H:function(t,e){return e.x=t[0],["H",t[0]]},V:function(t,e){return e.y=t[0],["V",t[0]]},C:function(t,e){return e.x=t[4],e.y=t[5],["C",t[0],t[1],t[2],t[3],t[4],t[5]]},S:function(t,e){return e.x=t[2],e.y=t[3],["S",t[0],t[1],t[2],t[3]]},Q:function(t,e){return e.x=t[2],e.y=t[3],["Q",t[0],t[1],t[2],t[3]]},T:function(t,e){return e.x=t[0],e.y=t[1],["T",t[0],t[1]]},Z:function(t,e,i){return e.x=i.x,e.y=i.y,["Z"]},A:function(t,e){return e.x=t[5],e.y=t[6],["A",t[0],t[1],t[2],t[3],t[4],t[5],t[6]]}},b="mlhvqtcsaz".split(""),C=0,N=b.length;C=0;r--)n=this.value[r][0],"M"==n||"L"==n||"T"==n?(this.value[r][1]+=t,this.value[r][2]+=e):"H"==n?this.value[r][1]+=t:"V"==n?this.value[r][1]+=e:"C"==n||"S"==n||"Q"==n?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"==n&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"==n&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var i,n,r=this.bbox();for(i=this.value.length-1;i>=0;i--)n=this.value[i][0],"M"==n||"L"==n||"T"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y):"H"==n?this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x:"V"==n?this.value[i][1]=(this.value[i][1]-r.y)*e/r.height+r.y:"C"==n||"S"==n||"Q"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y,this.value[i][3]=(this.value[i][3]-r.x)*t/r.width+r.x,this.value[i][4]=(this.value[i][4]-r.y)*e/r.height+r.y,"C"==n&&(this.value[i][5]=(this.value[i][5]-r.x)*t/r.width+r.x,this.value[i][6]=(this.value[i][6]-r.y)*e/r.height+r.y)):"A"==n&&(this.value[i][1]=this.value[i][1]*t/r.width,this.value[i][2]=this.value[i][2]*e/r.height,this.value[i][6]=(this.value[i][6]-r.x)*t/r.width+r.x,this.value[i][7]=(this.value[i][7]-r.y)*e/r.height+r.y);return this},equalCommands:function(t){var e,i,n;for(t=new g.PathArray(t),n=this.value.length===t.value.length,e=0,i=this.value.length;n&&ea);return n},bbox:function(){return g.parser.path.setAttribute("d",this.toString()),g.parser.path.getBBox()}}),g.Number=g.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-3.4e38:3.4e38:"string"==typeof t?(e=t.match(g.regex.numberAndUnit))&&(this.value=parseFloat(e[1]),"%"==e[5]?this.value/=100:"s"==e[5]&&(this.value*=1e3),this.unit=e[5]):t instanceof g.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"==this.unit?~~(1e8*this.value)/1e6:"s"==this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},valueOf:function(){return this.value},plus:function(t){return t=new g.Number(t),new g.Number(this+t,this.unit||t.unit)},minus:function(t){return t=new g.Number(t),new g.Number(this-t,this.unit||t.unit)},times:function(t){return t=new g.Number(t),new g.Number(this*t,this.unit||t.unit)},divide:function(t){return t=new g.Number(t),new g.Number(this/t,this.unit||t.unit)},to:function(t){var e=new g.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new g.Number(t),t.relative&&(this.destination.value+=this.value),this},at:function(t){return this.destination?new g.Number(this.destination).minus(this).times(t).plus(this):this}}}),g.Element=g.invent({create:function(t){this._stroke=g.defaults.attrs.stroke,this._event=null,this.dom={},(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke)},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var i=l(this,t,e);return this.width(new g.Number(i.width)).height(new g.Number(i.height))},clone:function(t,e){this.writeDataToDom();var i=x(this.node.cloneNode(!0));return t?t.add(i):this.after(i),i},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return t.put(this)},putIn:function(t){return t.add(this)},id:function(t){return this.attr("id",t)},inside:function(t,e){var i=this.bbox();return t>i.x&&e>i.y&&t/,"").replace(/<\/svg>$/,"");i.innerHTML=""+t.replace(/\n/,"").replace(/<(\w+)([^<]+?)\/>/g,"<$1$2>")+"";for(var n=0,r=i.firstChild.childNodes.length;n":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return 1-Math.cos(t*Math.PI/2)}},g.morph=function(t){return function(e,i){return new g.MorphObj(e,i).at(t)}},g.Situation=g.invent({create:function(t){this.init=!1,this.reversed=!1,this.reversing=!1,this.duration=new g.Number(t.duration).valueOf(),this.delay=new g.Number(t.delay).valueOf(),this.start=+new Date+this.delay,this.finish=this.start+this.duration,this.ease=t.ease,this.loop=0,this.loops=!1,this.animations={},this.attrs={},this.styles={},this.transforms=[],this.once={}}}),g.FX=g.invent({create:function(t){this._target=t,this.situations=[],this.active=!1,this.situation=null,this.paused=!1,this.lastPos=0,this.pos=0,this.absPos=0,this._speed=1},extend:{animate:function(t,e,i){"object"==typeof t&&(e=t.ease,i=t.delay,t=t.duration);var n=new g.Situation({duration:t||1e3,delay:i||0,ease:g.easing[e||"-"]||e});return this.queue(n),this},delay:function(t){var e=new g.Situation({duration:t,delay:0,ease:g.easing["-"]});return this.queue(e)},target:function(t){return t&&t instanceof g.Element?(this._target=t,this):this._target},timeToAbsPos:function(t){return(t-this.situation.start)/(this.situation.duration/this._speed)},absPosToTime:function(t){return this.situation.duration/this._speed*t+this.situation.start},startAnimFrame:function(){this.stopAnimFrame(),this.animationFrame=t.requestAnimationFrame(function(){this.step()}.bind(this))},stopAnimFrame:function(){t.cancelAnimationFrame(this.animationFrame)},start:function(){return!this.active&&this.situation&&(this.active=!0,this.startCurrent()),this},startCurrent:function(){return this.situation.start=+new Date+this.situation.delay/this._speed,this.situation.finish=this.situation.start+this.situation.duration/this._speed,this.initAnimations().step()},queue:function(t){return("function"==typeof t||t instanceof g.Situation)&&this.situations.push(t),this.situation||(this.situation=this.situations.shift()),this},dequeue:function(){return this.stop(),this.situation=this.situations.shift(),this.situation&&(this.situation instanceof g.Situation?this.start():this.situation.call(this)),this},initAnimations:function(){var t,e,i,n=this.situation;if(n.init)return this;for(t in n.animations)for(i=this.target()[t](),Array.isArray(i)||(i=[i]),Array.isArray(n.animations[t])||(n.animations[t]=[n.animations[t]]),e=i.length;e--;)n.animations[t][e]instanceof g.Number&&(i[e]=new g.Number(i[e])),n.animations[t][e]=i[e].morph(n.animations[t][e]);for(t in n.attrs)n.attrs[t]=new g.MorphObj(this.target().attr(t),n.attrs[t]);for(t in n.styles)n.styles[t]=new g.MorphObj(this.target().style(t),n.styles[t]);return n.initialTransformation=this.target().matrixify(),n.init=!0,this},clearQueue:function(){return this.situations=[],this},clearCurrent:function(){return this.situation=null,this},stop:function(t,e){var i=this.active;return this.active=!1,e&&this.clearQueue(),t&&this.situation&&(!i&&this.startCurrent(),this.atEnd()),this.stopAnimFrame(),this.clearCurrent()},reset:function(){if(this.situation){var t=this.situation;this.stop(),this.situation=t,this.atStart()}return this},finish:function(){for(this.stop(!0,!1);this.dequeue().situation&&this.stop(!0,!1););return this.clearQueue().clearCurrent(),this},atStart:function(){return this.at(0,!0)},atEnd:function(){return!0===this.situation.loops&&(this.situation.loops=this.situation.loop+1),"number"==typeof this.situation.loops?this.at(this.situation.loops,!0):this.at(1,!0)},at:function(t,e){var i=this.situation.duration/this._speed;return this.absPos=t,e||(this.situation.reversed&&(this.absPos=1-this.absPos),this.absPos+=this.situation.loop),this.situation.start=+new Date-this.absPos*i,this.situation.finish=this.situation.start+i,this.step(!0)},speed:function(t){return 0===t?this.pause():t?(this._speed=t,this.at(this.absPos,!0)):this._speed},loop:function(t,e){var i=this.last();return i.loops=null==t||t,i.loop=0,e&&(i.reversing=!0),this},pause:function(){return this.paused=!0,this.stopAnimFrame(),this},play:function(){return this.paused?(this.paused=!1,this.at(this.absPos,!0)):this},reverse:function(t){var e=this.last();return e.reversed=void 0===t?!e.reversed:t,this},progress:function(t){return t?this.situation.ease(this.pos):this.pos},after:function(t){var e=this.last(),i=function i(n){n.detail.situation==e&&(t.call(this,e),this.off("finished.fx",i))};return this.target().on("finished.fx",i),this._callStart()},during:function(t){var e=this.last(),i=function(i){i.detail.situation==e&&t.call(this,i.detail.pos,g.morph(i.detail.pos),i.detail.eased,e)};return this.target().off("during.fx",i).on("during.fx",i),this.after(function(){this.off("during.fx",i)}),this._callStart()},afterAll:function(t){var e=function e(i){t.call(this),this.off("allfinished.fx",e)};return this.target().off("allfinished.fx",e).on("allfinished.fx",e),this._callStart()},duringAll:function(t){var e=function(e){t.call(this,e.detail.pos,g.morph(e.detail.pos),e.detail.eased,e.detail.situation)};return this.target().off("during.fx",e).on("during.fx",e),this.afterAll(function(){this.off("during.fx",e)}),this._callStart()},last:function(){return this.situations.length?this.situations[this.situations.length-1]:this.situation},add:function(t,e,i){return this.last()[i||"animations"][t]=e,this._callStart()},step:function(t){if(t||(this.absPos=this.timeToAbsPos(+new Date)),!1!==this.situation.loops){var e,i,n;e=Math.max(this.absPos,0),i=Math.floor(e),!0===this.situation.loops||ithis.lastPos&&s<=r&&(this.situation.once[s].call(this.target(),this.pos,r),delete this.situation.once[s]);return this.active&&this.target().fire("during",{pos:this.pos,eased:r,fx:this,situation:this.situation}),this.situation?(this.eachAt(),1==this.pos&&!this.situation.reversed||this.situation.reversed&&0==this.pos?(this.stopAnimFrame(),this.target().fire("finished",{fx:this,situation:this.situation}),this.situations.length||(this.target().fire("allfinished"),this.situations.length||(this.target().off(".fx"),this.active=!1)),this.active?this.dequeue():this.clearCurrent()):!this.paused&&this.active&&this.startAnimFrame(),this.lastPos=r,this):this},eachAt:function(){var t,e,i,n=this,r=this.target(),s=this.situation;for(t in s.animations)i=[].concat(s.animations[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r[t].apply(r,i);for(t in s.attrs)i=[t].concat(s.attrs[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.attr.apply(r,i);for(t in s.styles)i=[t].concat(s.styles[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.style.apply(r,i);if(s.transforms.length){for(i=s.initialTransformation,t=0,e=s.transforms.length;t=0;--e)this[M[e]]=null!=t[M[e]]?t[M[e]]:i[M[e]]},extend:{extract:function(){var t=c(this,0,1),e=c(this,1,0),i=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,transformedX:(this.e*Math.cos(i*Math.PI/180)+this.f*Math.sin(i*Math.PI/180))/Math.sqrt(this.a*this.a+this.b*this.b),transformedY:(this.f*Math.cos(i*Math.PI/180)+this.e*Math.sin(-i*Math.PI/180))/Math.sqrt(this.c*this.c+this.d*this.d),skewX:-i,skewY:180/Math.PI*Math.atan2(e.y,e.x),scaleX:Math.sqrt(this.a*this.a+this.b*this.b),scaleY:Math.sqrt(this.c*this.c+this.d*this.d),rotation:i,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f,matrix:new g.Matrix(this)}},clone:function(){return new g.Matrix(this)},morph:function(t){return this.destination=new g.Matrix(t),this},at:function(t){return this.destination?new g.Matrix({a:this.a+(this.destination.a-this.a)*t,b:this.b+(this.destination.b-this.b)*t,c:this.c+(this.destination.c-this.c)*t,d:this.d+(this.destination.d-this.d)*t,e:this.e+(this.destination.e-this.e)*t,f:this.f+(this.destination.f-this.f)*t}):this},multiply:function(t){return new g.Matrix(this.native().multiply(d(t).native()))},inverse:function(){return new g.Matrix(this.native().inverse())},translate:function(t,e){return new g.Matrix(this.native().translate(t||0,e||0))},scale:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),this.around(i,n,new g.Matrix(t,0,0,e,0,0))},rotate:function(t,e,i){return t=g.utils.radians(t),this.around(e,i,new g.Matrix(Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0))},flip:function(t,e){return"x"==t?this.scale(-1,1,e,0):"y"==t?this.scale(1,-1,0,e):this.scale(-1,-1,t,null!=e?e:t)},skew:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),t=g.utils.radians(t),e=g.utils.radians(e),this.around(i,n,new g.Matrix(1,Math.tan(e),Math.tan(t),1,0,0))},skewX:function(t,e,i){return this.skew(t,0,e,i)},skewY:function(t,e,i){return this.skew(0,t,e,i)},around:function(t,e,i){return this.multiply(new g.Matrix(1,0,0,1,t||0,e||0)).multiply(i).multiply(new g.Matrix(1,0,0,1,-t||0,-e||0))},native:function(){ -for(var t=g.parser.native.createSVGMatrix(),e=M.length-1;e>=0;e--)t[M[e]]=this[M[e]];return t},toString:function(){return"matrix("+this.a+","+this.b+","+this.c+","+this.d+","+this.e+","+this.f+")"}},parent:g.Element,construct:{ctm:function(){return new g.Matrix(this.node.getCTM())},screenCTM:function(){if(this instanceof g.Nested){var t=this.rect(1,1),e=t.node.getScreenCTM();return t.remove(),new g.Matrix(e)}return new g.Matrix(this.node.getScreenCTM())}}}),g.Point=g.invent({create:function(t,e){var i,n={x:0,y:0};i=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:null!=t?{x:t,y:null!=e?e:t}:n,this.x=i.x,this.y=i.y},extend:{clone:function(){return new g.Point(this)},morph:function(t,e){return this.destination=new g.Point(t,e),this},at:function(t){return this.destination?new g.Point({x:this.x+(this.destination.x-this.x)*t,y:this.y+(this.destination.y-this.y)*t}):this},native:function(){var t=g.parser.native.createSVGPoint();return t.x=this.x,t.y=this.y,t},transform:function(t){return new g.Point(this.native().matrixTransform(t.native()))}}}),g.extend(g.Element,{point:function(t,e){return new g.Point(t,e).transform(this.screenCTM().inverse())}}),g.extend(g.Element,{attr:function(t,e,i){if(null==t){for(t={},e=this.node.attributes,i=e.length-1;i>=0;i--)t[e[i].nodeName]=g.regex.isNumber.test(e[i].nodeValue)?parseFloat(e[i].nodeValue):e[i].nodeValue;return t}if("object"==typeof t)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return e=this.node.getAttribute(t),null==e?g.defaults.attrs[t]:g.regex.isNumber.test(e)?parseFloat(e):e;"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),"fill"!=t&&"stroke"!=t||(g.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof g.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new g.Number(e):g.Color.isColor(e)?e=new g.Color(e):Array.isArray(e)&&(e=new g.Array(e)),"leading"==t?this.leading&&this.leading(e):"string"==typeof i?this.node.setAttributeNS(i,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this}}),g.extend(g.Element,{transform:function(t,e){var i,n,r=this;if("object"!=typeof t)return i=new g.Matrix(r).extract(),"string"==typeof t?i[t]:i;if(i=new g.Matrix(r),e=!!e||!!t.relative,null!=t.a)i=e?i.multiply(new g.Matrix(t)):new g.Matrix(t);else if(null!=t.rotation)p(t,r),i=e?i.rotate(t.rotation,t.cx,t.cy):i.rotate(t.rotation-i.extract().rotation,t.cx,t.cy);else if(null!=t.scale||null!=t.scaleX||null!=t.scaleY){if(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,!e){var s=i.extract();t.scaleX=1*t.scaleX/s.scaleX,t.scaleY=1*t.scaleY/s.scaleY}i=i.scale(t.scaleX,t.scaleY,t.cx,t.cy)}else if(null!=t.skew||null!=t.skewX||null!=t.skewY){if(p(t,r),t.skewX=null!=t.skew?t.skew:null!=t.skewX?t.skewX:0,t.skewY=null!=t.skew?t.skew:null!=t.skewY?t.skewY:0,!e){var s=i.extract();i=i.multiply((new g.Matrix).skew(s.skewX,s.skewY,t.cx,t.cy).inverse())}i=i.skew(t.skewX,t.skewY,t.cx,t.cy)}else t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new g.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(e?i=i.translate(t.x,t.y):(null!=t.x&&(i.e=t.x),null!=t.y&&(i.f=t.y)));return this.attr("transform",i)}}),g.extend(g.FX,{transform:function(t,e){var i,n,r=this.target();return"object"!=typeof t?(i=new g.Matrix(r).extract(),"string"==typeof t?i[t]:i):(e=!!e||!!t.relative,null!=t.a?i=new g.Matrix(t):null!=t.rotation?(p(t,r),i=new g.Rotate(t.rotation,t.cx,t.cy)):null!=t.scale||null!=t.scaleX||null!=t.scaleY?(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,i=new g.Scale(t.scaleX,t.scaleY,t.cx,t.cy)):null!=t.skewX||null!=t.skewY?(p(t,r),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,i=new g.Skew(t.skewX,t.skewY,t.cx,t.cy)):t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new g.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(i=new g.Translate(t.x,t.y)),i?(i.relative=e,this.last().transforms.push(i),this._callStart()):this)}}),g.extend(g.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){return(this.attr("transform")||"").split(g.regex.transforms).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(g.regex.delimiter).map(function(t){return parseFloat(t)})]}).reduce(function(t,e){return"matrix"==e[0]?t.multiply(f(e[1])):t[e[0]].apply(t,e[1])},new g.Matrix)},toParent:function(t){if(this==t)return this;var e=this.screenCTM(),i=t.screenCTM().inverse();return this.addTo(t).untransform().transform(i.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),g.Transformation=g.invent({create:function(t,e){if(arguments.length>1&&"boolean"!=typeof e)return this.constructor.call(this,[].slice.call(arguments));if(Array.isArray(t))for(var i=0,n=this.arguments.length;i=0},index:function(t){return[].slice.call(this.node.childNodes).indexOf(t.node)},get:function(t){return g.adopt(this.node.childNodes[t])},first:function(){return this.get(0)},last:function(){return this.get(this.node.childNodes.length-1)},each:function(t,e){var i,n,r=this.children();for(i=0,n=r.length;in/r?this.height/r:this.width/n,this.x=e,this.y=i,this.width=n,this.height=r)}else t="string"==typeof t?t.match(c).map(function(t){return parseFloat(t)}):Array.isArray(t)?t:"object"==typeof t?[t.x,t.y,t.width,t.height]:4==arguments.length?[].slice.call(arguments):h,this.x=t[0],this.y=t[1],this.width=t[2],this.height=t[3]},extend:{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height},morph:function(t,e,i,n){return this.destination=new g.ViewBox(t,e,i,n),this},at:function(t){return this.destination?new g.ViewBox([this.x+(this.destination.x-this.x)*t,this.y+(this.destination.y-this.y)*t,this.width+(this.destination.width-this.width)*t,this.height+(this.destination.height-this.height)*t]):this}},parent:g.Container,construct:{viewbox:function(t,e,i,n){return 0==arguments.length?new g.ViewBox(this):this.attr("viewBox",new g.ViewBox(t,e,i,n))}}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){g.Element.prototype[t]=function(e){return g.on(this.node,t,e),this}}),g.listeners=[],g.handlerMap=[],g.listenerId=0,g.on=function(t,e,i,n,r){var s=i.bind(n||t.instance||t),o=(g.handlerMap.indexOf(t)+1||g.handlerMap.push(t))-1,a=e.split(".")[0],h=e.split(".")[1]||"*";g.listeners[o]=g.listeners[o]||{},g.listeners[o][a]=g.listeners[o][a]||{},g.listeners[o][a][h]=g.listeners[o][a][h]||{},i._svgjsListenerId||(i._svgjsListenerId=++g.listenerId),g.listeners[o][a][h][i._svgjsListenerId]=s,t.addEventListener(a,s,r||!1)},g.off=function(t,e,i){var n=g.handlerMap.indexOf(t),r=e&&e.split(".")[0],s=e&&e.split(".")[1],o="";if(-1!=n)if(i){if("function"==typeof i&&(i=i._svgjsListenerId),!i)return;g.listeners[n][r]&&g.listeners[n][r][s||"*"]&&(t.removeEventListener(r,g.listeners[n][r][s||"*"][i],!1),delete g.listeners[n][r][s||"*"][i])}else if(s&&r){if(g.listeners[n][r]&&g.listeners[n][r][s]){for(i in g.listeners[n][r][s])g.off(t,[r,s].join("."),i);delete g.listeners[n][r][s]}}else if(s)for(e in g.listeners[n])for(o in g.listeners[n][e])s===o&&g.off(t,[e,s].join("."));else if(r){if(g.listeners[n][r]){for(o in g.listeners[n][r])g.off(t,[r,o].join("."));delete g.listeners[n][r]}}else{for(e in g.listeners[n])g.off(t,e);delete g.listeners[n],delete g.handlerMap[n]}},g.extend(g.Element,{on:function(t,e,i,n){return g.on(this.node,t,e,i,n),this},off:function(t,e){return g.off(this.node,t,e),this},fire:function(e,i){return e instanceof t.Event?this.node.dispatchEvent(e):this.node.dispatchEvent(e=new t.CustomEvent(e,{detail:i,cancelable:!0})),this._event=e,this},event:function(){return this._event}}),g.Defs=g.invent({create:"defs",inherit:g.Container}),g.G=g.invent({create:"g",inherit:g.Container,extend:{x:function(t){return null==t?this.transform("x"):this.transform({x:t-this.x()},!0)},y:function(t){return null==t?this.transform("y"):this.transform({y:t-this.y()},!0)},cx:function(t){return null==t?this.gbox().cx:this.x(t-this.gbox().width/2)},cy:function(t){return null==t?this.gbox().cy:this.y(t-this.gbox().height/2)},gbox:function(){var t=this.bbox(),e=this.transform();return t.x+=e.x,t.x2+=e.x,t.cx+=e.x,t.y+=e.y,t.y2+=e.y,t.cy+=e.y,t}},construct:{group:function(){return this.put(new g.G)}}}),g.extend(g.Element,{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){var t=this.position()+1,e=this.parent();return e.removeElement(this).add(this,t),e instanceof g.Doc&&e.node.appendChild(e.defs().node),this},backward:function(){var t=this.position();return t>0&&this.parent().removeElement(this).add(this,t-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof g.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(t){t.remove();var e=this.position();return this.parent().add(t,e),this},after:function(t){t.remove();var e=this.position();return this.parent().add(t,e+1),this}}),g.Mask=g.invent({create:function(){this.constructor.call(this,g.create("mask")),this.targets=[]},inherit:g.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return this.targets=[],this.parent().removeElement(this),this}},construct:{mask:function(){return this.defs().put(new g.Mask)}}}),g.extend(g.Element,{maskWith:function(t){return this.masker=t instanceof g.Mask?t:this.parent().mask().add(t),this.masker.targets.push(this),this.attr("mask",'url("#'+this.masker.attr("id")+'")')},unmask:function(){return delete this.masker,this.attr("mask",null)}}),g.ClipPath=g.invent({create:function(){this.constructor.call(this,g.create("clipPath")),this.targets=[]},inherit:g.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return this.targets=[],this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new g.ClipPath)}}}),g.extend(g.Element,{clipWith:function(t){return this.clipper=t instanceof g.ClipPath?t:this.parent().clip().add(t),this.clipper.targets.push(this),this.attr("clip-path",'url("#'+this.clipper.attr("id")+'")')},unclip:function(){return delete this.clipper,this.attr("clip-path",null)}}),g.Gradient=g.invent({create:function(t){this.constructor.call(this,g.create(t+"Gradient")),this.type=t},inherit:g.Container,extend:{at:function(t,e,i){return this.put(new g.Stop).update(t,e,i)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},fill:function(){return"url(#"+this.id()+")"},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="gradientTransform"),g.Container.prototype.attr.call(this,t,e,i)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),g.extend(g.Gradient,g.FX,{from:function(t,e){return"radial"==(this._target||this).type?this.attr({fx:new g.Number(t),fy:new g.Number(e)}):this.attr({x1:new g.Number(t),y1:new g.Number(e)})},to:function(t,e){return"radial"==(this._target||this).type?this.attr({cx:new g.Number(t),cy:new g.Number(e)}):this.attr({x2:new g.Number(t),y2:new g.Number(e)})}}),g.extend(g.Defs,{gradient:function(t,e){return this.put(new g.Gradient(t)).update(e)}}),g.Stop=g.invent({create:"stop",inherit:g.Element,extend:{update:function(t){return("number"==typeof t||t instanceof g.Number)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new g.Number(t.offset)),this}}}),g.Pattern=g.invent({create:"pattern",inherit:g.Container,extend:{fill:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="patternTransform"),g.Container.prototype.attr.call(this,t,e,i)}},construct:{pattern:function(t,e,i){return this.defs().pattern(t,e,i)}}}),g.extend(g.Defs,{pattern:function(t,e,i){return this.put(new g.Pattern).update(i).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),g.Doc=g.invent({create:function(t){t&&(t="string"==typeof t?e.getElementById(t):t,"svg"==t.nodeName?this.constructor.call(this,t):(this.constructor.call(this,g.create("svg")),t.appendChild(this.node),this.size("100%","100%")),this.namespace().defs())},inherit:g.Container,extend:{namespace:function(){return this.attr({xmlns:g.ns,version:"1.1"}).attr("xmlns:xlink",g.xlink,g.xmlns).attr("xmlns:svgjs",g.svgjs,g.xmlns)},defs:function(){if(!this._defs){var t;(t=this.node.getElementsByTagName("defs")[0])?this._defs=g.adopt(t):this._defs=new g.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return"#document"==this.node.parentNode.nodeName?null:this.node.parentNode},spof:function(t){var e=this.node.getScreenCTM();return e&&this.style("left",-e.e%1+"px").style("top",-e.f%1+"px"),this},remove:function(){return this.parent()&&this.parent().removeChild(this.node),this},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return delete this._defs,g.parser.draw.parentNode||this.node.appendChild(g.parser.draw),this}}}),g.Shape=g.invent({create:function(t){this.constructor.call(this,t)},inherit:g.Element}),g.Bare=g.invent({create:function(t,e){if(this.constructor.call(this,g.create(t)),e)for(var i in e.prototype)"function"==typeof e.prototype[i]&&(this[i]=e.prototype[i])},inherit:g.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(e.createTextNode(t)),this}}}),g.extend(g.Parent,{element:function(t,e){return this.put(new g.Bare(t,e))}}),g.Symbol=g.invent({create:"symbol",inherit:g.Container,construct:{symbol:function(){return this.put(new g.Symbol)}}}),g.Use=g.invent({create:"use",inherit:g.Shape,extend:{element:function(t,e){return this.attr("href",(e||"")+"#"+t,g.xlink)}},construct:{use:function(t,e){return this.put(new g.Use).element(t,e)}}}),g.Rect=g.invent({create:"rect",inherit:g.Shape,construct:{rect:function(t,e){return this.put(new g.Rect).size(t,e)}}}),g.Circle=g.invent({create:"circle",inherit:g.Shape,construct:{circle:function(t){return this.put(new g.Circle).rx(new g.Number(t).divide(2)).move(0,0)}}}),g.extend(g.Circle,g.FX,{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),g.Ellipse=g.invent({create:"ellipse",inherit:g.Shape,construct:{ellipse:function(t,e){return this.put(new g.Ellipse).size(t,e).move(0,0)}}}),g.extend(g.Ellipse,g.Rect,g.FX,{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),g.extend(g.Circle,g.Ellipse,{x:function(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())},y:function(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",t)},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",t)},width:function(t){return null==t?2*this.rx():this.rx(new g.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new g.Number(t).divide(2))},size:function(t,e){var i=l(this,t,e);return this.rx(new g.Number(i.width).divide(2)).ry(new g.Number(i.height).divide(2))}}),g.Line=g.invent({create:"line",inherit:g.Shape,extend:{array:function(){return new g.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,i,n){return null==t?this.array():(t=void 0!==e?{x1:t,y1:e,x2:i,y2:n}:new g.PointArray(t).toLine(),this.attr(t))},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var i=l(this,t,e);return this.attr(this.array().size(i.width,i.height).toLine())}},construct:{line:function(t,e,i,n){return g.Line.prototype.plot.apply(this.put(new g.Line),null!=t?[t,e,i,n]:[0,0,0,0])}}}),g.Polyline=g.invent({create:"polyline",inherit:g.Shape,construct:{polyline:function(t){return this.put(new g.Polyline).plot(t||new g.PointArray)}}}),g.Polygon=g.invent({create:"polygon",inherit:g.Shape,construct:{polygon:function(t){return this.put(new g.Polygon).plot(t||new g.PointArray)}}}),g.extend(g.Polyline,g.Polygon,{array:function(){return this._array||(this._array=new g.PointArray(this.attr("points")))},plot:function(t){return null==t?this.array():this.clear().attr("points","string"==typeof t?t:this._array=new g.PointArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var i=l(this,t,e);return this.attr("points",this.array().size(i.width,i.height))}}),g.extend(g.Line,g.Polyline,g.Polygon,{morphArray:g.PointArray,x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},width:function(t){var e=this.bbox();return null==t?e.width:this.size(t,e.height)},height:function(t){var e=this.bbox();return null==t?e.height:this.size(e.width,t)}}),g.Path=g.invent({create:"path",inherit:g.Shape,extend:{morphArray:g.PathArray,array:function(){return this._array||(this._array=new g.PathArray(this.attr("d")))},plot:function(t){return null==t?this.array():this.clear().attr("d","string"==typeof t?t:this._array=new g.PathArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("d",this.array().move(t,e))},x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},size:function(t,e){var i=l(this,t,e);return this.attr("d",this.array().size(i.width,i.height))},width:function(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)},height:function(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}},construct:{path:function(t){return this.put(new g.Path).plot(t||new g.PathArray)}}}),g.Image=g.invent({create:"image",inherit:g.Shape,extend:{load:function(e){if(!e)return this;var i=this,n=new t.Image;return g.on(n,"load",function(){var t=i.parent(g.Pattern);null!==t&&(0==i.width()&&0==i.height()&&i.size(n.width,n.height),t&&0==t.width()&&0==t.height()&&t.size(i.width(),i.height()),"function"==typeof i._loaded&&i._loaded.call(i,{width:n.width,height:n.height,ratio:n.width/n.height,url:e}))}),g.on(n,"error",function(t){"function"==typeof i._error&&i._error.call(i,t)}),this.attr("href",n.src=this.src=e,g.xlink)},loaded:function(t){return this._loaded=t,this},error:function(t){return this._error=t,this}},construct:{image:function(t,e,i){return this.put(new g.Image).load(t).size(e||0,i||e||0)}}}),g.Text=g.invent({create:function(){this.constructor.call(this,g.create("text")),this.dom.leading=new g.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",g.defaults.attrs["font-family"])},inherit:g.Shape,extend:{x:function(t){return null==t?this.attr("x"):this.attr("x",t)},y:function(t){var e=this.attr("y"),i="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-i:e:this.attr("y","number"==typeof t?t+i:t)},cx:function(t){return null==t?this.bbox().cx:this.x(t-this.bbox().width/2)},cy:function(t){return null==t?this.bbox().cy:this.y(t-this.bbox().height/2)},text:function(t){if(void 0===t){for(var t="",e=this.node.childNodes,i=0,n=e.length;i=0;e--)null!=i[A[t][e]]&&this.attr(A.prefix(t,A[t][e]),i[A[t][e]]);return this},g.extend(g.Element,g.FX,i)}),g.extend(g.Element,g.FX,{rotate:function(t,e,i){return this.transform({rotation:t,cx:e,cy:i})},skew:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({skew:t,cx:e,cy:i}):this.transform({skewX:t,skewY:e,cx:i,cy:n})},scale:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({scale:t,cx:e,cy:i}):this.transform({scaleX:t,scaleY:e,cx:i,cy:n})},translate:function(t,e){return this.transform({x:t,y:e})},flip:function(t,e){return e="number"==typeof t?t:e,this.transform({flip:t||"both",offset:e})},matrix:function(t){return this.attr("transform",new g.Matrix(6==arguments.length?[].slice.call(arguments):t))},opacity:function(t){return this.attr("opacity",t)},dx:function(t){return this.x(new g.Number(t).plus(this instanceof g.FX?0:this.x()),!0)},dy:function(t){return this.y(new g.Number(t).plus(this instanceof g.FX?0:this.y()),!0)},dmove:function(t,e){return this.dx(t).dy(e)}}),g.extend(g.Rect,g.Ellipse,g.Circle,g.Gradient,g.FX,{radius:function(t,e){var i=(this._target||this).type;return"radial"==i||"circle"==i?this.attr("r",new g.Number(t)):this.rx(t).ry(null==e?t:e)}}),g.extend(g.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),g.extend(g.Parent,g.Text,g.Tspan,g.FX,{font:function(t,e){if("object"==typeof t)for(e in t)this.font(e,t[e]);return"leading"==t?this.leading(e):"anchor"==t?this.attr("text-anchor",e):"size"==t||"family"==t||"weight"==t||"stretch"==t||"variant"==t||"style"==t?this.attr("font-"+t,e):this.attr(t,e)}}),g.Set=g.invent({create:function(t){Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){var t,e,i=[].slice.call(arguments);for(t=0,e=i.length;t-1&&this.members.splice(e,1),this},each:function(t){for(var e=0,i=this.members.length;e=0},index:function(t){return this.members.indexOf(t)},get:function(t){return this.members[t]},first:function(){return this.get(0)},last:function(){return this.get(this.members.length-1)},valueOf:function(){return this.members},bbox:function(){if(0==this.members.length)return new g.RBox;var t=this.members[0].rbox(this.members[0].doc());return this.each(function(){t=t.merge(this.rbox(this.doc()))}),t}},construct:{set:function(t){return new g.Set(t)}}}),g.FX.Set=g.invent({create:function(t){this.set=t}}),g.Set.inherit=function(){var t,e=[];for(var t in g.Shape.prototype)"function"==typeof g.Shape.prototype[t]&&"function"!=typeof g.Set.prototype[t]&&e.push(t);e.forEach(function(t){g.Set.prototype[t]=function(){for(var e=0,i=this.members.length;e=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),g.get=function(t){var i=e.getElementById(v(t)||t);return g.adopt(i)},g.select=function(t,i){return new g.Set(g.utils.map((i||e).querySelectorAll(t),function(t){return g.adopt(t)}))},g.extend(g.Parent,{select:function(t){return g.select(t,this.node)}});var M="abcdef".split("");if("function"!=typeof t.CustomEvent){var P=function(t,i){i=i||{bubbles:!1,cancelable:!1,detail:void 0};var n=e.createEvent("CustomEvent");return n.initCustomEvent(t,i.bubbles,i.cancelable,i.detail),n};P.prototype=t.Event.prototype,t.CustomEvent=P}return function(e){for(var i=0,n=["moz","webkit"],r=0;r - <%= pkg.description %>' - , '* @version <%= pkg.version %>' - , '* <%= pkg.homepage %>' - , '*' - , '* @copyright <%= pkg.author %>' - , '* @license <%= pkg.license %>' - , '*' - , '* BUILT: <%= pkg.buildDate %>' - , '*/;' - , ''].join('\n') - -var headerShort = '/*! <%= pkg.name %> v<%= pkg.version %> <%= pkg.license %>*/;' - -// all files in the right order (currently we don't use any dependency management system) -var parts = [ - 'src/svg.js' -, 'src/regex.js' -, 'src/utilities.js' -, 'src/default.js' -, 'src/color.js' -, 'src/array.js' -, 'src/pointarray.js' -, 'src/patharray.js' -, 'src/number.js' -, 'src/element.js' -, 'src/fx.js' -, 'src/boxes.js' -, 'src/matrix.js' -, 'src/point.js' -, 'src/attr.js' -, 'src/transform.js' -, 'src/style.js' -, 'src/parent.js' -, 'src/ungroup.js' -, 'src/container.js' -, 'src/viewbox.js' -, 'src/event.js' -, 'src/defs.js' -, 'src/group.js' -, 'src/arrange.js' -, 'src/mask.js' -, 'src/clip.js' -, 'src/gradient.js' -, 'src/pattern.js' -, 'src/doc.js' -, 'src/shape.js' -, 'src/bare.js' -, 'src/symbol.js' -, 'src/use.js' -, 'src/rect.js' -, 'src/ellipse.js' -, 'src/line.js' -, 'src/poly.js' -, 'src/pointed.js' -, 'src/path.js' -, 'src/image.js' -, 'src/text.js' -, 'src/textpath.js' -, 'src/nested.js' -, 'src/hyperlink.js' -, 'src/marker.js' -, 'src/sugar.js' -, 'src/set.js' -, 'src/data.js' -, 'src/memory.js' -, 'src/selector.js' -, 'src/helpers.js' -, 'src/polyfill.js' -] - -gulp.task('clean', function() { - return del([ 'dist/*' ]) -}) - -/** - * Compile everything in /src to one unified file in the order defined in the MODULES constant - * wrap the whole thing in a UMD wrapper (@see https://github.com/umdjs/umd) - * add the license information to the header plus the build time stampā€ - */ -gulp.task('unify', ['clean'], function() { - pkg.buildDate = Date() - return gulp.src(parts) - .pipe(concat('svg.js', { newLine: '\n' })) - // wrap the whole thing in an immediate function call - .pipe(wrapUmd({ src: 'src/umd.js'})) - .pipe(header(headerLong, { pkg: pkg })) - .pipe(trim({ leading: false })) - .pipe(chmod(0o644)) - .pipe(gulp.dest('dist')) - .pipe(size({ showFiles: true, title: 'Full' })) -}) - -/** - ā€Ž* uglify the file and show the size of the result - * add the license info - * show the gzipped file size - */ -gulp.task('minify', ['unify'], function() { - return gulp.src('dist/svg.js') - .pipe(uglify()) - .pipe(rename({ suffix:'.min' })) - .pipe(size({ showFiles: true, title: 'Minified' })) - .pipe(header(headerShort, { pkg: pkg })) - .pipe(chmod(0o644)) - .pipe(gulp.dest('dist')) - .pipe(size({ showFiles: true, gzip: true, title: 'Gzipped' })) -}) - - -gulp.task('default', ['clean', 'unify', 'minify']) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..eb707caf3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,13866 @@ +{ + "name": "@svgdotjs/svg.js", + "version": "3.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@svgdotjs/svg.js", + "version": "3.2.0", + "license": "MIT", + "devDependencies": { + "@babel/core": "^7.24.7", + "@babel/eslint-parser": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^26.0.1", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-terser": "^0.4.4", + "@target/custom-event-polyfill": "github:Adobe-Marketing-Cloud/custom-event-polyfill", + "@types/jasmine": "^5.1.4", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "core-js": "^3.37.1", + "coveralls": "^3.1.1", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-config-standard": "^17.1.0", + "http-server": "^14.1.1", + "jasmine": "^5.1.0", + "jasmine-core": "^5.1.2", + "karma": "^6.4.3", + "karma-chrome-launcher": "^3.2.0", + "karma-coverage": "^2.2.1", + "karma-firefox-launcher": "^2.1.3", + "karma-jasmine": "^5.1.0", + "karma-sauce-launcher": "^4.3.6", + "prettier": "^3.3.2", + "rollup": "^4.18.0", + "rollup-plugin-filesize": "^10.0.0", + "svgdom": "^0.1.19", + "typescript": "^5.4.5", + "yargs": "^17.7.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Fuzzyma" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.7.tgz", + "integrity": "sha512-SO5E3bVxDuxyNxM5agFv480YA2HO6ohZbGxbazZdIk3KQOPOGVNw6q78I9/lbviIf95eq6tPozeYnJLbjnC8IA==", + "dev": true, + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz", + "integrity": "sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", + "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz", + "integrity": "sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz", + "integrity": "sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-wrap-function": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", + "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz", + "integrity": "sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz", + "integrity": "sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz", + "integrity": "sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz", + "integrity": "sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz", + "integrity": "sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", + "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz", + "integrity": "sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz", + "integrity": "sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", + "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", + "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz", + "integrity": "sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", + "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.7.tgz", + "integrity": "sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz", + "integrity": "sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.7.tgz", + "integrity": "sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.24.7", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.24.7", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.7", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.24.7", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.24.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-modules-systemjs": "^7.24.7", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.7", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz", + "integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", + "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", + "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/git": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", + "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@npmcli/git/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/git/node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz", + "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==", + "dev": true, + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "lib/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/move-file/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", + "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "dev": true, + "dependencies": { + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz", + "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^6.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.1.tgz", + "integrity": "sha512-H43VosMzywHCcYcgv0GXXopvwnV21Ud9g2aXbPlQUJj1Xcz9V0wBwHeFz6saFhx/3VKisZfI1GEKEOhQCau7Vw==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.2.1", + "tar-fs": "2.1.1", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@puppeteer/browsers/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/plugin-babel": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz", + "integrity": "sha512-YF7Y52kFdFT/xVSuVdjkV5ZdX/3YtmX0QulG+x0taQOtJdHYzVU61aSSkAgVJ7NOv6qPkIYiJSgSWWN/DM5sGw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.18.6", + "@rollup/pluginutils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-26.0.1.tgz", + "integrity": "sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^10.4.1", + "is-reference": "1.2.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/glob": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/jackspeak": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", + "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz", + "integrity": "sha512-a31EnjuIDSX8IXBUib3cYLDRlPMU36AWX4xS8ysLaNu4ZzUesDiPt83pgrW2X1YLMe5L2HbDyaKK5BrL4cNKaQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-1.0.0.tgz", + "integrity": "sha512-bLzi9GeZgMCvjJeLUIfs8LJYCxrPRA8IXQkzUtaFKKVPTz0mucRyqFcV2U20yg9K+kYAD0YSitzGfRZCFLjdHQ==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.1.0", + "make-fetch-happen": "^11.0.1", + "tuf-js": "^1.1.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/tuf/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sigstore/tuf/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/tuf/node_modules/minipass-fetch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", + "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", + "dev": true, + "dependencies": { + "minipass": "^5.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "dev": true + }, + "node_modules/@swc/helpers": { + "version": "0.4.36", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.36.tgz", + "integrity": "sha512-5lxnyLEYFskErRPenYItLRSge5DjrJngYKdVjRSrWfza9G6KkgHEXi0vUZiyUeMU5JfXH1YnvXZzSp8ul88o2Q==", + "dev": true, + "dependencies": { + "legacy-swc-helpers": "npm:@swc/helpers@=0.4.14", + "tslib": "^2.4.0" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@target/custom-event-polyfill": { + "version": "1.1.0", + "resolved": "git+ssh://git@github.com/Adobe-Marketing-Cloud/custom-event-polyfill.git#2a28329ad98fdaf578054e2390f6ecd77d2eae91", + "integrity": "sha512-kAOa23Rvg3HtSXcn5ka8BRyL4LnPHdn8WnAEVkwU/c7+ejrewv7HaMza4JDnMGRTvfX12H/sQ1dMPU7BxoNvSg==", + "dev": true, + "license": "ISC" + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tufjs/canonical-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", + "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz", + "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==", + "dev": true, + "dependencies": { + "@tufjs/canonical-json": "1.0.0", + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", + "dev": true + }, + "node_modules/@types/jasmine": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.4.tgz", + "integrity": "sha512-px7OMFO/ncXxixDe1zR13V1iycqWae0MxTaw62RpFlksUi5QuNWgQJFkTQjIOvrmutJbI7Fp2Y2N1F6D2R4G6w==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "peer": true + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "16.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz", + "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==", + "dev": true + }, + "node_modules/@types/puppeteer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-7.0.4.tgz", + "integrity": "sha512-ja78vquZc8y+GM2al07GZqWDKQskQXygCDiu0e3uO0DMRKqE0MjrFBFmTulfPYzLB6WnL7Kl2tFPy0WXSpPomg==", + "deprecated": "This is a stub types definition. puppeteer provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "puppeteer": "*" + } + }, + "node_modules/@types/puppeteer-core": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@types/puppeteer-core/-/puppeteer-core-5.4.0.tgz", + "integrity": "sha512-yqRPuv4EFcSkTyin6Yy17pN6Qz2vwVwTCJIDYMXbE3j8vTPhv0nCQlZOl5xfi0WHUkqvQsjAR8hAfjeMCoetwg==", + "dev": true, + "dependencies": { + "@types/puppeteer": "*" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, + "node_modules/@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/which": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-1.3.2.tgz", + "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", + "dev": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@wdio/config": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-6.12.1.tgz", + "integrity": "sha512-V5hTIW5FNlZ1W33smHF4Rd5BKjGW2KeYhyXDQfXHjqLCeRiirZ9fABCo9plaVQDnwWSUMWYaAaIAifV82/oJCQ==", + "dev": true, + "dependencies": { + "@wdio/logger": "6.10.10", + "deepmerge": "^4.0.0", + "glob": "^7.1.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@wdio/logger": { + "version": "6.10.10", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-6.10.10.tgz", + "integrity": "sha512-2nh0hJz9HeZE0VIEMI+oPgjr/Q37ohrR9iqsl7f7GW5ik+PnKYCT9Eab5mR1GNMG60askwbskgGC1S9ygtvrSw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@wdio/logger/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@wdio/logger/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/logger/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@wdio/logger/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@wdio/logger/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wdio/logger/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wdio/protocols": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-6.12.0.tgz", + "integrity": "sha512-UhTBZxClCsM3VjaiDp4DoSCnsa7D1QNmI2kqEBfIpyNkT3GcZhJb7L+nL0fTkzCwi7+/uLastb3/aOwH99gt0A==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@wdio/repl": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-6.11.0.tgz", + "integrity": "sha512-FxrFKiTkFyELNGGVEH1uijyvNY7lUpmff6x+FGskFGZB4uSRs0rxkOMaEjxnxw7QP1zgQKr2xC7GyO03gIGRGg==", + "dev": true, + "dependencies": { + "@wdio/utils": "6.11.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@wdio/utils": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-6.11.0.tgz", + "integrity": "sha512-vf0sOQzd28WbI26d6/ORrQ4XKWTzSlWLm9W/K/eJO0NASKPEzR+E+Q2kaa+MJ4FKXUpjbt+Lxfo+C26TzBk7tg==", + "dev": true, + "dependencies": { + "@wdio/logger": "6.10.10" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz", + "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "depd": "^2.0.0", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/archive-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz", + "integrity": "sha512-zV4Ky0v1F8dBrdYElwTvQhweQ0P7Kwc1aluqJsYtOBP01jXcWCyW2IEfI1YiqsG+Iy7ZR+o5LF1N+PGECBxHWA==", + "dev": true, + "dependencies": { + "file-type": "^4.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/archive-type/node_modules/file-type": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz", + "integrity": "sha512-f2UbFQEk7LXgWpi5ntcO86OeA/cC80fuDDDaX/fZ2ZGel+AF7leRQqBBW1eJNiiQkrZlAoM6P+VYP5P6bOlDEQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/archiver": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", + "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", + "dev": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.3", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver/node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "dev": true + }, + "node_modules/archiver/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-ftp": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz", + "integrity": "sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bin-check": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", + "integrity": "sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==", + "dev": true, + "dependencies": { + "execa": "^0.7.0", + "executable": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-3.1.0.tgz", + "integrity": "sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==", + "dev": true, + "dependencies": { + "execa": "^1.0.0", + "find-versions": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version-check": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-4.0.0.tgz", + "integrity": "sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==", + "dev": true, + "dependencies": { + "bin-version": "^3.0.0", + "semver": "^5.6.0", + "semver-truncate": "^1.1.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version-check/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/bin-version/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/bin-version/node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/bin-version/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/bin-version/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/bin-version/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-version/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bin-version/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/bin-wrapper": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bin-wrapper/-/bin-wrapper-4.1.0.tgz", + "integrity": "sha512-hfRmo7hWIXPkbpi0ZltboCMVrU+0ClXR/JgbCKKjlDjQf6igXa7OwdqNcFWQZPZTgiY7ZpzE3+LjjkLiTN2T7Q==", + "dev": true, + "dependencies": { + "bin-check": "^4.1.0", + "bin-version-check": "^4.0.0", + "download": "^7.1.0", + "import-lazy": "^3.1.0", + "os-filter-obj": "^2.0.0", + "pify": "^4.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dev": true, + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "dev": true + }, + "node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/boxen/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/boxen/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "dev": true, + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/brotli-size": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/brotli-size/-/brotli-size-4.0.0.tgz", + "integrity": "sha512-uA9fOtlTRC0iqKfzff1W34DXUA3GyVqbUaeo3Rw3d4gd1eavKVCETXrn3NzO74W+UVkG3UHu8WxUi+XvKI/huA==", + "dev": true, + "dependencies": { + "duplexer": "0.1.1" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/browserslist": { + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.16" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/builtins/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.3.tgz", + "integrity": "sha512-jAdjGxmPxZh0IipMdR7fK/4sDSrHMLUV0+GvVUsjwyGNKHsh79kW/otg+GkbXwl6Uzvy9wsvHOX4nUoWldeZMg==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "10.2.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.7.tgz", + "integrity": "sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2", + "path-scurry": "^1.7.0" + }, + "bin": { + "glob": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001636", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz", + "integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "node_modules/caw": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", + "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", + "dev": true, + "dependencies": { + "get-proxy": "^2.0.0", + "isurl": "^1.0.0-alpha5", + "tunnel-agent": "^0.6.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/chrome-launcher": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.13.4.tgz", + "integrity": "sha512-nnzXiDbGKjDSK6t2I+35OAPBy5Pw/39bgkb/ZAFwMhwJbdYBp6aH+vW28ZgtjdU890Q7D+3wN/tB8N66q5Gi2A==", + "dev": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^1.0.5", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^0.5.3", + "rimraf": "^3.0.2" + } + }, + "node_modules/chromium-bidi": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.11.tgz", + "integrity": "sha512-p03ajLhlQ5gebw3cmbDBFmBc2wnJM5dnXS8Phu6mblGn/KQd76yOVL5VwE0VAisa7oazNfKGTaXlIZ8Q5Bb9OA==", + "dev": true, + "dependencies": { + "mitt": "3.0.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/compress-commons": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", + "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", + "dev": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "node_modules/constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-js": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", + "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", + "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/cosmiconfig": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", + "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", + "dev": true, + "dependencies": { + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/coveralls": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", + "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.1", + "lcov-parse": "^1.0.0", + "log-driver": "^1.2.7", + "minimist": "^1.2.5", + "request": "^2.88.2" + }, + "bin": { + "coveralls": "bin/coveralls.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", + "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", + "dev": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-fetch": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", + "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.11" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-shorthand-properties": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz", + "integrity": "sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==", + "dev": true + }, + "node_modules/css-value": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", + "integrity": "sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==", + "dev": true + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-5.0.1.tgz", + "integrity": "sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "dev": true, + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dev": true, + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dev": true, + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dev": true, + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz/node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dev": true, + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip/node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dev": true, + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-unzip/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress/node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress/node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/degenerator": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-4.0.2.tgz", + "integrity": "sha512-HKwIFvZROUMfH3qI3gBpD61BYh7q3c3GXD5UGZzoVNJwVSYgZKvYl1fRMXc9ozoTxl/VZxKJ5v/bA+19tywFiw==", + "dev": true, + "dependencies": { + "ast-types": "^0.13.2", + "escodegen": "^1.8.1", + "esprima": "^4.0.0", + "vm2": "^3.9.17" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/degenerator/node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/devtools": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/devtools/-/devtools-6.12.1.tgz", + "integrity": "sha512-JyG46suEiZmld7/UVeogkCWM0zYGt+2ML/TI+SkEp+bTv9cs46cDb0pKF3glYZJA7wVVL2gC07Ic0iCxyJEnCQ==", + "dev": true, + "dependencies": { + "@wdio/config": "6.12.1", + "@wdio/logger": "6.10.10", + "@wdio/protocols": "6.12.0", + "@wdio/utils": "6.11.0", + "chrome-launcher": "^0.13.1", + "edge-paths": "^2.1.0", + "puppeteer-core": "^5.1.0", + "ua-parser-js": "^0.7.21", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.818844", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz", + "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==", + "dev": true + }, + "node_modules/devtools/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "dev": true + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", + "dev": true + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "dev": true, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/download": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/download/-/download-7.1.0.tgz", + "integrity": "sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ==", + "dev": true, + "dependencies": { + "archive-type": "^4.0.0", + "caw": "^2.0.1", + "content-disposition": "^0.5.2", + "decompress": "^4.2.0", + "ext-name": "^5.0.0", + "file-type": "^8.1.0", + "filenamify": "^2.0.0", + "get-stream": "^3.0.0", + "got": "^8.3.1", + "make-dir": "^1.2.0", + "p-event": "^2.1.0", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/download/node_modules/@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==", + "dev": true, + "dependencies": { + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" + } + }, + "node_modules/download/node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/download/node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/download/node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/got": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", + "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true + }, + "node_modules/download/node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "dev": true + }, + "node_modules/download/node_modules/keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/download/node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/download/node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/normalize-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "dev": true, + "dependencies": { + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/p-cancelable": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/download/node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "dev": true, + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/download/node_modules/sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==", + "dev": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "node_modules/duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", + "dev": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/edge-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", + "integrity": "sha512-AI5fC7dfDmCdKo3m5y7PkYE8m6bMqR6pvVpgtrZkkhcJXFLelUgkjrhk3kXXx8Kbw2cRaTT4LkOR7hqf39KJdw==", + "dev": true, + "dependencies": { + "@types/which": "^1.3.2", + "which": "^2.0.2" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.805", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.805.tgz", + "integrity": "sha512-8W4UJwX/w9T0QSzINJckTKG6CYpAUTqsaWcWIsdud3I1FYJcMgW9QqT1/4CBff/pP/TihWh13OmiyY8neto6vw==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", + "dev": true + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dev": true, + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "peer": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "peer": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-config-standard": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es-x": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-6.2.1.tgz", + "integrity": "sha512-uR34zUhZ9EBoiSD2DdV5kHLpydVEvwWqjteUr9sXRgJknwbKZJZhdJ7uFnaTtd+Nr/2G3ceJHnHXrFhJ67n3Tw==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, + "peer": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-n": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.0.0.tgz", + "integrity": "sha512-akkZTE3hsHBrq6CwmGuYCzQREbVUrA855kzcHqe6i0FLBkeY7Y/6tThCVkjUnjhvRBAlc+8lILcSe5QvvDpeZQ==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "builtins": "^5.0.1", + "eslint-plugin-es-x": "^6.1.0", + "ignore": "^5.1.1", + "is-core-module": "^2.12.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.2", + "semver": "^7.5.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "peer": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", + "dev": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/execa/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/execa/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/executable/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "dev": true, + "dependencies": { + "mime-db": "^1.28.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "dev": true, + "dependencies": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-type": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz", + "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", + "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", + "dev": true, + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/filesize": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.4.0.tgz", + "integrity": "sha512-mjFIpOHC4jbfcTfoh4rkWpI31mF7viw9ikj/JyLoKzqlwG/YsefKfvYlYhdYdg/9mtK2z1AzgN/0LvVQ3zdlSQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-versions": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", + "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", + "dev": true, + "dependencies": { + "semver-regex": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fontkit": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.2.tgz", + "integrity": "sha512-jc4k5Yr8iov8QfS6u8w2CnHWVmbOGtdBtOXMze5Y+QD966Rx6PEVWXSEGwXlsDlKtu1G12cJjcsybnqhSk/+LA==", + "dev": true, + "dependencies": { + "@swc/helpers": "^0.4.2", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "peer": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.2.tgz", + "integrity": "sha512-2GAfyfoaCDRrM6jaOS3UsBts8yJ55VioXdWcOL7dK9zdAuKT71+WBA4ifnNYqVjYv+4SsPxjK0JT4yIIn4cA/g==", + "dev": true, + "dependencies": { + "minipass": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proxy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", + "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", + "dev": true, + "dependencies": { + "npm-conf": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-uri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.1.tgz", + "integrity": "sha512-7ZqONUVqaabogsYNWlYj0t3YZaL6dhuEueZXGF+/YVmf6dHmaFg8/6psJKqhx9QykIDKzpGcy2cn4oV4YC7V/Q==", + "dev": true, + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^5.0.1", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-agent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.2.0.tgz", + "integrity": "sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==", + "dev": true, + "dependencies": { + "boolean": "^3.0.1", + "core-js": "^3.6.5", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gzip-size/node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbol-support-x": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "dev": true, + "dependencies": { + "has-symbol-support-x": "^1.4.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "peer": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "dev": true, + "dependencies": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/hosted-git-info": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", + "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "dev": true, + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/http-server/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/http-server/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/http-server/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/http-server/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/http-server/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-server/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.3.tgz", + "integrity": "sha512-C7FfFoTA+bI10qfeydT8aZbvr91vAEU+2W5BZUlzPec47oNb07SsOfwYrtxuvOYdUApPP/Qlh4DtAO51Ekk2QA==", + "dev": true, + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/image-size": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz", + "integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==", + "dev": true, + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-3.1.0.tgz", + "integrity": "sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==", + "dev": true, + "dependencies": { + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "peer": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", + "dev": true + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "peer": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", + "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", + "dev": true, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.5.tgz", + "integrity": "sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "dev": true, + "dependencies": { + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/jackspeak": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.1.tgz", + "integrity": "sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.1.0.tgz", + "integrity": "sha512-prmJlC1dbLhti4nE4XAPDWmfJesYO15sjGXVp7Cs7Ym5I9Xtwa/hUHxxJXjnpfLO72+ySttA0Ztf8g/RiVnUKw==", + "dev": true, + "dependencies": { + "glob": "^10.2.2", + "jasmine-core": "~5.1.0" + }, + "bin": { + "jasmine": "bin/jasmine.js" + } + }, + "node_modules/jasmine-core": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz", + "integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==", + "dev": true + }, + "node_modules/jasmine/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/jasmine/node_modules/glob": { + "version": "10.2.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.7.tgz", + "integrity": "sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2", + "path-scurry": "^1.7.0" + }, + "bin": { + "glob": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jasmine/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", + "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/karma": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.3.tgz", + "integrity": "sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q==", + "dev": true, + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.7.2", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", + "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", + "dev": true, + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/karma-coverage": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", + "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/karma-firefox-launcher": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.3.tgz", + "integrity": "sha512-LMM2bseebLbYjODBOVt7TCPP9OI2vZIXCavIXhkO9m+10Uj5l7u/SKoeRmYx8FYHTVGZSpk6peX+3BMHC1WwNw==", + "dev": true, + "dependencies": { + "is-wsl": "^2.2.0", + "which": "^3.0.0" + } + }, + "node_modules/karma-firefox-launcher/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/karma-jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", + "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", + "dev": true, + "dependencies": { + "jasmine-core": "^4.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "karma": "^6.0.0" + } + }, + "node_modules/karma-jasmine/node_modules/jasmine-core": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.0.tgz", + "integrity": "sha512-O236+gd0ZXS8YAjFx8xKaJ94/erqUliEkJTDedyE7iHvv4ZVqi+q+8acJxu05/WJDKm512EUNn809In37nWlAQ==", + "dev": true + }, + "node_modules/karma-sauce-launcher": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/karma-sauce-launcher/-/karma-sauce-launcher-4.3.6.tgz", + "integrity": "sha512-Ej62q4mUPFktyAm8g0g8J5qhwEkXwdHrwtiV4pZjKNHNnSs+4qgDyzs3VkpOy3AmNTsTqQXUN/lpiy0tZpDJZQ==", + "dev": true, + "dependencies": { + "global-agent": "^2.1.12", + "saucelabs": "^4.6.3", + "webdriverio": "^6.7.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/karma/node_modules/mime": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/karma/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/karma/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lcov-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", + "dev": true, + "bin": { + "lcov-parse": "bin/cli.js" + } + }, + "node_modules/legacy-swc-helpers": { + "name": "@swc/helpers", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", + "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "dev": true, + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "node_modules/lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true + }, + "node_modules/lodash.zip": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", + "integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==", + "dev": true + }, + "node_modules/log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true, + "engines": { + "node": ">=0.8.6" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/loglevel": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", + "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-plugin-prefix": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", + "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", + "dev": true + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/make-fetch-happen/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-fetch-happen/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/make-fetch-happen/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/marky": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", + "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", + "dev": true + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/matcher/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", + "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "dev": true, + "dependencies": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", + "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-conf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", + "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", + "dev": true, + "dependencies": { + "config-chain": "^1.1.11", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-conf/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-install-checks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.1.1.tgz", + "integrity": "sha512-dH3GmQL4vsPtld59cOn8uY0iOqRmqKvV+DLGwNXV/Q7MDgD2QfOADWd/mFXcIE5LVhYYGjA3baz6W9JneqnuCw==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", + "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm-packlist": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", + "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", + "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm-registry-fetch": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", + "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==", + "dev": true, + "dependencies": { + "make-fetch-happen": "^11.0.0", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^10.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/minipass-fetch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", + "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", + "dev": true, + "dependencies": { + "minipass": "^5.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-filter-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/os-filter-obj/-/os-filter-obj-2.0.0.tgz", + "integrity": "sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==", + "dev": true, + "dependencies": { + "arch": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-event": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz", + "integrity": "sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA==", + "dev": true, + "dependencies": { + "p-timeout": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "dev": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pac-proxy-agent": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-6.0.3.tgz", + "integrity": "sha512-5Hr1KgPDoc21Vn3rsXBirwwDnF/iac1jN/zkpsOYruyT+ZgsUhUOgVwq3v9+ukjZd/yGm/0nzO1fDfl7rkGoHQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "pac-resolver": "^6.0.1", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.0.tgz", + "integrity": "sha512-0euwPCRyAPSgGdzD1IVN9nJYHtBhJwb6XPfbpQcYbPCwrBidX6GzxmchnaF4sfF/jPb74Ojx5g4yTg3sixlyPw==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/socks-proxy-agent": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.1.tgz", + "integrity": "sha512-59EjPbbgg8U3x62hhKOFVAmySQUcfRQ4C7Q/D5sEHnZTQRrQlNKINks44DMR1gwXp0p4LaVIeccX2KHTTcHVqQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-6.0.1.tgz", + "integrity": "sha512-dg497MhVT7jZegPRuOScQ/z0aV/5WR0gTdRu1md+Irs9J9o+ls5jIuxjo1WfaTG+eQQkxyn5HMGvWK+w7EIBkQ==", + "dev": true, + "dependencies": { + "degenerator": "^4.0.1", + "ip": "^1.1.5", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver/node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "dev": true + }, + "node_modules/pacote": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz", + "integrity": "sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==", + "dev": true, + "dependencies": { + "@npmcli/git": "^4.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^6.0.1", + "@npmcli/run-script": "^6.0.0", + "cacache": "^17.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^5.0.0", + "npm-package-arg": "^10.0.0", + "npm-packlist": "^7.0.0", + "npm-pick-manifest": "^8.0.0", + "npm-registry-fetch": "^14.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^6.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^1.3.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "dependencies": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "node_modules/proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.2.1.tgz", + "integrity": "sha512-OIbBKlRAT+ycCm6wAYIzMwPejzRtjy8F3QiDX0eKOA3e4pe3U9F/IvzcHP42bmgQxVv97juG+J8/gx+JIeCX/Q==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^6.0.3", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.0.tgz", + "integrity": "sha512-0euwPCRyAPSgGdzD1IVN9nJYHtBhJwb6XPfbpQcYbPCwrBidX6GzxmchnaF4sfF/jPb74Ojx5g4yTg3sixlyPw==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-agent/node_modules/socks-proxy-agent": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.1.tgz", + "integrity": "sha512-59EjPbbgg8U3x62hhKOFVAmySQUcfRQ4C7Q/D5sEHnZTQRrQlNKINks44DMR1gwXp0p4LaVIeccX2KHTTcHVqQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/puppeteer": { + "version": "20.6.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-20.6.0.tgz", + "integrity": "sha512-D/kkEIpDFRqpLOcCoNNdXI+IUcoD1FmdlWteT4FFPOhNLC46urmItMfQDKSwk2NJoO38ncgCQe5XEQ3QHD+piA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "1.4.1", + "cosmiconfig": "8.2.0", + "puppeteer-core": "20.6.0" + }, + "engines": { + "node": ">=16.3.0" + } + }, + "node_modules/puppeteer-core": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-5.5.0.tgz", + "integrity": "sha512-tlA+1n+ziW/Db03hVV+bAecDKse8ihFRXYiEypBe9IlLRvOCzYFG6qrCMBYK34HO/Q/Ecjc+tvkHRAfLVH+NgQ==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "devtools-protocol": "0.0.818844", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^4.0.0", + "node-fetch": "^2.6.1", + "pkg-dir": "^4.2.0", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/puppeteer-core/node_modules/agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/puppeteer-core/node_modules/https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, + "dependencies": { + "agent-base": "5", + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/puppeteer-core/node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/puppeteer/node_modules/devtools-protocol": { + "version": "0.0.1135028", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1135028.tgz", + "integrity": "sha512-jEcNGrh6lOXNRJvZb9RjeevtZGrgugPKSMJZxfyxWQnhlKawMPhMtk/dfC+Z/6xNXExlzTKlY5LzIAK/fRpQIw==", + "dev": true + }, + "node_modules/puppeteer/node_modules/puppeteer-core": { + "version": "20.6.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.6.0.tgz", + "integrity": "sha512-vUE6VnqvOHX1ABssTxtzTJUzEJrTL49DmXflSvlRIcolzOISVni1niI5/oWsDxzyRVE9sfwe8QqFbsWozs5RPA==", + "dev": true, + "dependencies": { + "@puppeteer/browsers": "1.4.1", + "chromium-bidi": "0.4.11", + "cross-fetch": "3.1.6", + "debug": "4.3.4", + "devtools-protocol": "0.0.1135028", + "ws": "8.13.0" + }, + "engines": { + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/puppeteer/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "dependencies": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dev": true, + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/read-package-json": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", + "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==", + "dev": true, + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/read-package-json/node_modules/glob": { + "version": "10.2.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.7.tgz", + "integrity": "sha512-jTKehsravOJo8IJxUGfZILnkvVJM/MOfHRs8QcXolVef2zNI9Tqyy5+SeuOAZd3upViEZQLyFpQhYiHLrMUNmA==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2", + "path-scurry": "^1.7.0" + }, + "bin": { + "glob": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/read-package-json/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/resq": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resq/-/resq-1.11.0.tgz", + "integrity": "sha512-G10EBz+zAAy3zUd/CDoBbXRL6ia9kOo3xRHrMDsHljI0GDkhYlyjwoCx5+3eCC4swi1uCoZQhskuJkj7Gp57Bw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^2.0.1" + } + }, + "node_modules/resq/node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "dev": true + }, + "node_modules/restructure": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.0.tgz", + "integrity": "sha512-Xj8/MEIhhfj9X2rmD9iJ4Gga9EFqVlpMj3vfLnV2r/Mh5jRMryNV+6lWh9GdJtDBcBSPIqzRdfBQ3wDtNFv/uw==", + "dev": true + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rgb2hex": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.3.tgz", + "integrity": "sha512-clEe0m1xv+Tva1B/TOepuIcvLAxP0U+sCDfgt1SX1HmI2Ahr5/Cd/nzJM1e78NKVtWdoo0s33YehpFA8UfIShQ==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/roarr/node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + }, + "node_modules/rollup": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-filesize": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-filesize/-/rollup-plugin-filesize-10.0.0.tgz", + "integrity": "sha512-JAYYhzCcmGjmCzo3LEHSDE3RAPHKIeBdpqRhiyZSv5o/3wFhktUOzYAWg/uUKyEu5dEaVaql6UOmaqHx1qKrZA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.13.8", + "boxen": "^5.0.0", + "brotli-size": "4.0.0", + "colors": "1.4.0", + "filesize": "^6.1.0", + "gzip-size": "^6.0.0", + "pacote": "^15.1.1", + "terser": "^5.6.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/saucelabs": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-4.7.8.tgz", + "integrity": "sha512-K2qaRUixc7+8JiAwpTvEsIQVzzUkYwa0mAfs0akGagRlWXUR1JrsmgJRyz28qkwpERW1KDuByn3Ju96BuW1V7Q==", + "dev": true, + "dependencies": { + "bin-wrapper": "^4.1.0", + "change-case": "^4.1.1", + "form-data": "^3.0.0", + "got": "^11.7.0", + "hash.js": "^1.1.7", + "tunnel": "0.0.6", + "yargs": "^16.0.3" + }, + "bin": { + "sl": "bin/sl" + } + }, + "node_modules/saucelabs/node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/saucelabs/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=", + "dev": true + }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "dev": true, + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true + }, + "node_modules/semver-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", + "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver-truncate": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-1.1.2.tgz", + "integrity": "sha512-V1fGg9i4CL3qesB6U0L6XAm4xOJiHmt4QAacazumuasc03BvtFGIMCduv01JWQ69Nv+JST9TqhSCiJoxoY031w==", + "dev": true, + "dependencies": { + "semver": "^5.3.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver-truncate/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sigstore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.6.0.tgz", + "integrity": "sha512-QODKff/qW/TXOZI6V/Clqu74xnInAS6it05mufj4/fSewexLtfEntgLZZcBtUK44CDQyUE5TUXYy1ARYzlfG9g==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.1.0", + "@sigstore/tuf": "^1.0.0", + "make-fetch-happen": "^11.0.1", + "tuf-js": "^1.1.3" + }, + "bin": { + "sigstore": "bin/sigstore.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/sigstore/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/sigstore/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/sigstore/node_modules/minipass-fetch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", + "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", + "dev": true, + "dependencies": { + "minipass": "^5.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/smob": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.0.tgz", + "integrity": "sha512-MqR3fVulhjWuRNSMydnTlweu38UhQ0HXM4buStD/S3mc/BzX3CuM9OmhyQpmtYCvoYdl5ris6TI0ZqH355Ymqg==", + "dev": true + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "dev": true, + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", + "dev": true, + "dependencies": { + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.20", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz", + "integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssri": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.4.tgz", + "integrity": "sha512-12+IR2CB2C28MMAw0Ncqwj5QbTcs0nGIhgJzYWzDkb21vWmfNI83KS4f3Ci6GI98WreIfG7o9UXp3C0qbpA8nQ==", + "dev": true, + "dependencies": { + "minipass": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dev": true, + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svgdom": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/svgdom/-/svgdom-0.1.19.tgz", + "integrity": "sha512-gBvlZ74RECaG9VyPrj9OdakOarEKKvaXh5NVkbx9oWfAo4XnQehk75b14iOW2UjFHyZThczZ1NrPV9rDrecOVg==", + "dev": true, + "dependencies": { + "fontkit": "^2.0.2", + "image-size": "^1.0.2", + "sax": "^1.2.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Fuzzyma" + } + }, + "node_modules/tar": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", + "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/tar-fs/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tar-fs/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dev": true, + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/tar/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser": { + "version": "5.17.7", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.7.tgz", + "integrity": "sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "peer": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "peer": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", + "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", + "dev": true + }, + "node_modules/tuf-js": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.6.tgz", + "integrity": "sha512-CXwFVIsXGbVY4vFiWF7TJKWmlKJAT8TWkH4RmiohJRcDJInix++F0dznDmoVbtJNzZ8yLprKUG4YrDIhv3nBMg==", + "dev": true, + "dependencies": { + "@tufjs/models": "1.0.4", + "debug": "^4.3.4", + "make-fetch-happen": "^11.1.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/tuf-js/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/tuf-js/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/tuf-js/node_modules/minipass-fetch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.3.tgz", + "integrity": "sha512-n5ITsTkDqYkYJZjcRWzZt9qnZKCT7nKCosJhHoj7S7zD+BP4jVbWs+odsniw5TA3E0sLomhTKOKjF86wf11PuQ==", + "dev": true, + "dependencies": { + "minipass": "^5.0.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.35", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.35.tgz", + "integrity": "sha512-veRf7dawaj9xaWEu9HoTVn5Pggtc/qj+kqTOFvNiN1l0YdxwC1kvel57UCjThjGa3BHBihE8/UJAHI+uQHmd/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "dev": true, + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dev": true, + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "dev": true, + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", + "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "dev": true, + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/vm2": { + "version": "3.9.19", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.19.tgz", + "integrity": "sha512-J637XF0DHDMV57R6JyVsTak7nIL8gy5KH4r1HiwWLf/4GBbb5MKL5y7LpmF4A8E2nR6XmzpmMFQ7V7ppPTmUQg==", + "dev": true, + "dependencies": { + "acorn": "^8.7.0", + "acorn-walk": "^8.2.0" + }, + "bin": { + "vm2": "bin/vm2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/vm2/node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webdriver": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-6.12.1.tgz", + "integrity": "sha512-3rZgAj9o2XHp16FDTzvUYaHelPMSPbO1TpLIMUT06DfdZjNYIzZiItpIb/NbQDTPmNhzd9cuGmdI56WFBGY2BA==", + "dev": true, + "dependencies": { + "@wdio/config": "6.12.1", + "@wdio/logger": "6.10.10", + "@wdio/protocols": "6.12.0", + "@wdio/utils": "6.11.0", + "got": "^11.0.2", + "lodash.merge": "^4.6.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webdriverio": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-6.12.1.tgz", + "integrity": "sha512-Nx7ge0vTWHVIRUbZCT+IuMwB5Q0Q5nLlYdgnmmJviUKLuc3XtaEBkYPTbhHWHgSBXsPZMIc023vZKNkn+6iyeQ==", + "dev": true, + "dependencies": { + "@types/puppeteer-core": "^5.4.0", + "@wdio/config": "6.12.1", + "@wdio/logger": "6.10.10", + "@wdio/repl": "6.11.0", + "@wdio/utils": "6.11.0", + "archiver": "^5.0.0", + "atob": "^2.1.2", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools": "6.12.1", + "fs-extra": "^9.0.1", + "get-port": "^5.1.1", + "grapheme-splitter": "^1.0.2", + "lodash.clonedeep": "^4.5.0", + "lodash.isobject": "^3.0.2", + "lodash.isplainobject": "^4.0.6", + "lodash.zip": "^4.2.0", + "minimatch": "^3.0.4", + "puppeteer-core": "^5.1.0", + "resq": "^1.9.1", + "rgb2hex": "0.2.3", + "serialize-error": "^8.0.0", + "webdriver": "6.12.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webdriverio/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/webdriverio/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/webdriverio/node_modules/serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webdriverio/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "peer": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", + "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", + "dev": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "compress-commons": "^4.1.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + } + } +} diff --git a/package.json b/package.json index b2d22b612..8ed1678e7 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { - "name": "svg.js", - "version": "2.6.2", + "name": "@svgdotjs/svg.js", + "version": "3.2.4", + "type": "module", "description": "A lightweight library for manipulating and animating SVG.", - "url": "https://svgdotjs.github.io/", - "homepage": "https://svgdotjs.github.io/", + "url": "https://svgjs.dev/", + "homepage": "https://svgjs.dev/", "keywords": [ "svg", "vector", @@ -11,31 +12,47 @@ "animation" ], "author": "Wout Fierens ", - "main": "dist/svg.js", - "jam": { - "include": [ - "dist/svg.js", - "README.md", - "LICENSE.txt" - ] + "main": "dist/svg.node.cjs", + "unpkg": "dist/svg.min.js", + "jsdelivr": "dist/svg.min.js", + "browser": "dist/svg.esm.js", + "module": "src/main.js", + "exports": { + ".": { + "import": { + "types": "./svg.js.d.ts", + "default": "./src/main.js" + }, + "require": { + "types": "./svg.js.d.ts", + "default": "./dist/svg.node.cjs" + } + } }, + "files": [ + "/dist", + "/src", + "/svg.js.d.ts", + "/.config" + ], "maintainers": [ { "name": "Wout Fierens", - "email": "wout@mick-wout.com", - "web": "https://svgdotjs.github.io/" + "email": "wout@mick-wout.com" }, { - "name": "Ulrich-Matthias SchƤfer", - "email": "ulima.ums@googlemail.com" + "name": "Alex Ewerlƶf", + "email": "alex@userpixel.com", + "web": "http://www.ewerlof.name" }, { - "name": "RĆ©mi TĆ©treault", - "web": "https://github.com/RmiTtro" + "name": "Ulrich-Matthias SchƤfer", + "email": "ulima.ums@googlemail.com", + "web": "https://svgdotjs.github.io/" }, { "name": "Jon Ege Ronnenberg", - "email": "jon@svgjs.com", + "email": "jon@svgjs.dev", "url": "https://keybase.io/dotnetcarpenter" } ], @@ -51,33 +68,61 @@ }, "github": "https://github.com/svgdotjs/svg.js", "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Fuzzyma" + }, "typings": "./svg.js.d.ts", "scripts": { - "build": "gulp", - "build:test": "gulp unify", - "test": "karma start .config/karma.conf.js --single-run", - "test:quick": "karma start .config/karma.quick.js" + "build": "npm run format && npm run rollup", + "build:polyfills": "npx rollup -c .config/rollup.polyfills.js", + "build:tests": "npx rollup -c .config/rollup.tests.js", + "fix": "npx eslint ./src --fix", + "lint": "npx eslint ./src", + "prettier": "npx prettier --write .", + "format": "npm run fix && npm run prettier", + "rollup": "npx rollup -c .config/rollup.config.js", + "server": "npx http-server ./ -d", + "test": "npx karma start .config/karma.conf.cjs || true", + "test:ci": "karma start .config/karma.conf.saucelabs.cjs", + "test:svgdom": "node ./spec/runSVGDomTest.js || true", + "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", + "prepublishOnly": "rm -rf ./dist && npm run build && npm run build:polyfills && npm test", + "postpublish": "npm run zip", + "checkTests": "node spec/checkForAllTests.js" }, "devDependencies": { - "coveralls": "^2.13.1", - "del": "^2.2.0", - "gulp": "^3.9.1", - "gulp-chmod": "^2.0.0", - "gulp-cli": "^1.3.0", - "gulp-concat": "^2.3.3", - "gulp-header": "^1.0.5", - "gulp-rename": "^1.2.2", - "gulp-size": "^2.1.0", - "gulp-trimlines": "^1.0.0", - "gulp-uglify": "^2.1.2", - "gulp-wrap": "^0.13.0", - "jasmine-core": "^2.6.2", - "karma": "^1.7.0", - "karma-coverage": "^1.1.1", - "karma-firefox-launcher": "^1.0.1", - "karma-jasmine": "^1.1.0", - "karma-phantomjs-launcher": "^1.0.4", - "request": "^2.81.0", - "svgdom": "latest" - } + "@babel/core": "^7.24.7", + "@babel/eslint-parser": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^26.0.1", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-terser": "^0.4.4", + "@target/custom-event-polyfill": "github:Adobe-Marketing-Cloud/custom-event-polyfill", + "@types/jasmine": "^5.1.4", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "core-js": "^3.37.1", + "coveralls": "^3.1.1", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-config-standard": "^17.1.0", + "http-server": "^14.1.1", + "jasmine": "^5.1.0", + "jasmine-core": "^5.1.2", + "karma": "^6.4.3", + "karma-chrome-launcher": "^3.2.0", + "karma-coverage": "^2.2.1", + "karma-firefox-launcher": "^2.1.3", + "karma-jasmine": "^5.1.0", + "karma-sauce-launcher": "^4.3.6", + "prettier": "^3.3.2", + "rollup": "^4.18.0", + "rollup-plugin-filesize": "^10.0.0", + "svgdom": "^0.1.19", + "typescript": "^5.4.5", + "yargs": "^17.7.2" + }, + "browserslist": ">0.3%, last 2 version, not dead, not op_mini all" } diff --git a/playgrounds/colors/index.html b/playgrounds/colors/index.html new file mode 100644 index 000000000..017abbab8 --- /dev/null +++ b/playgrounds/colors/index.html @@ -0,0 +1,19 @@ + + + + + SVG Playground + + + + +

Color Playground

+ +

Lets test the different types of random colors we can make

+ + + + + + + diff --git a/playgrounds/colors/main.js b/playgrounds/colors/main.js new file mode 100644 index 000000000..01be83e34 --- /dev/null +++ b/playgrounds/colors/main.js @@ -0,0 +1,26 @@ +let canvas = SVG('#canvas').group().translate(-150, 230) + +// Make a bunch of rectangles +function rectangles(method = 'Vibrant') { + // Make a group + let group = canvas.group() + group.text(method).attr('font-size', 50).move(-230, 20) + + // Add the squares + for (let i = 0; i < 20; i++) { + let color = SVG.Color.random(method.toLowerCase()).toHex() + let rect = group + .rect(100, 100) + .x(20 + 100 * i) + .fill(color) + } + return group +} + +rectangles('Vibrant').translate(0, 100) +rectangles('Sine').translate(0, 220) +rectangles('Pastel').translate(0, 340) +rectangles('Dark').translate(0, 460) +rectangles('RGB').translate(0, 580) +rectangles('LAB').translate(0, 700) +rectangles('Grey').translate(0, 820) diff --git a/playgrounds/colors/style.css b/playgrounds/colors/style.css new file mode 100644 index 000000000..24a9d4e12 --- /dev/null +++ b/playgrounds/colors/style.css @@ -0,0 +1,69 @@ +* { + box-sizing: border-box; +} + +html { + background-color: #fefefe; +} + +body { + margin: 0; + width: 100vw; + height: 99vh; + grid-gap: 30px; + display: inline-grid; + align-items: center; + grid-template-columns: 10vw 40vw auto 10vw; + grid-template-rows: 0 10vw auto 0; +} + +h1 { + text-align: right; + border-right: solid 3px #f06; + padding-right: 12px; + color: #f06; + font-size: 52px; + font-family: Helvetica; + grid-row: 2; + grid-column: 2; + line-height: 1.8em; +} + +p { + padding-right: 50px; + color: #444; + font-size: 18px; + font-family: Helvetica; + grid-row: 2; + grid-column: 3; +} + +svg { + height: 100%; + width: 100%; + grid-row: 3; + grid-column: 2/4; + background-color: #f5f6f7; + border-radius: 20px; + border: #f065 1px solid; +} + +.pink { + fill: #ff0066; +} + +.green { + fill: #00ff99; +} + +.dark-pink { + fill: #660029; +} + +.light-pink { + fill: #ff99c2; +} + +.off-white { + fill: #ffcce0; +} diff --git a/playgrounds/matrix/drag.js b/playgrounds/matrix/drag.js new file mode 100644 index 000000000..8f294ed7f --- /dev/null +++ b/playgrounds/matrix/drag.js @@ -0,0 +1,64 @@ +function reactToDrag(element, onDrag, beforeDrag) { + let xStart, yStart + let startDrag = (event) => { + // Avoid the default events + event.preventDefault() + + // Store the position where the drag started + xStart = event.pageX + yStart = event.pageY + + // Fire the start drag event + if (beforeDrag) { + var { x, y } = parent.point(event.pageX, event.pageY) + beforeDrag(event, x, y) + } + + // Register events to react to dragging and drag ends + SVG.on(window, ['mousemove.drag', 'touchmove.drag'], reactDrag) + SVG.on(window, ['mouseup.drag', 'touchend.drag'], stopDrag) + } + + let reactDrag = (event) => { + // Convert screen coordinates to svg coordinates and use them + var { x, y } = parent.point(event.pageX, event.pageY) + if (onDrag) onDrag(event, x, y) + } + + let stopDrag = (event) => { + SVG.off(window, ['mousemove.drag', 'touchmove.drag']) + SVG.off(window, ['mouseup.drag', 'touchend.drag']) + } + + // Bind the drag tracker to this element directly + let parent = element.root() + let point = new SVG.Point() + element.mousedown(startDrag).touchstart(startDrag) +} + +SVG.extend(SVG.Element, { + draggable: function (after) { + let sx, sy + + reactToDrag( + this, + (e, x, y) => { + this.transform({ + origin: [sx, sy], + position: [x, y] + }) + + if (after) { + after(this, x, y) + } + }, + (e, x, y) => { + var toAbsolute = new SVG.Matrix(this).inverse() + var p = new SVG.Point(x, y).transform(toAbsolute) + sx = p.x + sy = p.y + } + ) + return this + } +}) diff --git a/playgrounds/matrix/index.html b/playgrounds/matrix/index.html new file mode 100644 index 000000000..9da4c201d --- /dev/null +++ b/playgrounds/matrix/index.html @@ -0,0 +1,40 @@ + + + + + SVG Playground + + + + +

SVG Transformations

+ +

+ This playground tests the compose/decompose functionality in + svg matrix, as well as the draggable code and the transformations. +

+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/playgrounds/matrix/matrix.js b/playgrounds/matrix/matrix.js new file mode 100644 index 000000000..fb7471704 --- /dev/null +++ b/playgrounds/matrix/matrix.js @@ -0,0 +1,45 @@ +function print(mat) { + let { a, b, c, d } = mat + console.log(` + a: ${a.toFixed(2)} + b: ${b.toFixed(2)} + c: ${c.toFixed(2)} + d: ${d.toFixed(2)} + `) +} + +function moveit() { + let { cx: x0, cy: y0 } = or.rbox(svg) + let { cx: x1, cy: y1 } = b1.rbox(svg) + let { cx: x2, cy: y2 } = b2.rbox(svg) + + let m = new SVG.Matrix( + (x1 - x0) / 50, + (y1 - y0) / 50, + (x2 - x0) / 50, + (y2 - y0) / 50, + x0, + y0 + ) + let com = m.decompose() + let g = new SVG.Matrix().compose(com) + + // Transform both of the items + target.transform(m) + mover.transform(g) + + console.log(com) + print(m) + print(g) +} + +// Declare the two points +let svg = SVG('svg') +var or = SVG('#or').draggable(moveit) +var b1 = SVG('#b1').draggable(moveit) +var b2 = SVG('#b2').draggable(moveit) + +// Declare the squares +let target = SVG('#true') +let mover = SVG('#guess') +let tester = SVG('#tester') diff --git a/playgrounds/matrix/style.css b/playgrounds/matrix/style.css new file mode 100644 index 000000000..24a9d4e12 --- /dev/null +++ b/playgrounds/matrix/style.css @@ -0,0 +1,69 @@ +* { + box-sizing: border-box; +} + +html { + background-color: #fefefe; +} + +body { + margin: 0; + width: 100vw; + height: 99vh; + grid-gap: 30px; + display: inline-grid; + align-items: center; + grid-template-columns: 10vw 40vw auto 10vw; + grid-template-rows: 0 10vw auto 0; +} + +h1 { + text-align: right; + border-right: solid 3px #f06; + padding-right: 12px; + color: #f06; + font-size: 52px; + font-family: Helvetica; + grid-row: 2; + grid-column: 2; + line-height: 1.8em; +} + +p { + padding-right: 50px; + color: #444; + font-size: 18px; + font-family: Helvetica; + grid-row: 2; + grid-column: 3; +} + +svg { + height: 100%; + width: 100%; + grid-row: 3; + grid-column: 2/4; + background-color: #f5f6f7; + border-radius: 20px; + border: #f065 1px solid; +} + +.pink { + fill: #ff0066; +} + +.green { + fill: #00ff99; +} + +.dark-pink { + fill: #660029; +} + +.light-pink { + fill: #ff99c2; +} + +.off-white { + fill: #ffcce0; +} diff --git a/playgrounds/transforms/index.html b/playgrounds/transforms/index.html new file mode 100644 index 000000000..77ef1bc37 --- /dev/null +++ b/playgrounds/transforms/index.html @@ -0,0 +1,22 @@ + + + + + SVG Playground + + + + +

Transformations

+ +

+ Here you can try out our transformation code, try moving around the pink + box in your console with its variable name: mover +

+ + + + + + + diff --git a/playgrounds/transforms/style.css b/playgrounds/transforms/style.css new file mode 100644 index 000000000..24a9d4e12 --- /dev/null +++ b/playgrounds/transforms/style.css @@ -0,0 +1,69 @@ +* { + box-sizing: border-box; +} + +html { + background-color: #fefefe; +} + +body { + margin: 0; + width: 100vw; + height: 99vh; + grid-gap: 30px; + display: inline-grid; + align-items: center; + grid-template-columns: 10vw 40vw auto 10vw; + grid-template-rows: 0 10vw auto 0; +} + +h1 { + text-align: right; + border-right: solid 3px #f06; + padding-right: 12px; + color: #f06; + font-size: 52px; + font-family: Helvetica; + grid-row: 2; + grid-column: 2; + line-height: 1.8em; +} + +p { + padding-right: 50px; + color: #444; + font-size: 18px; + font-family: Helvetica; + grid-row: 2; + grid-column: 3; +} + +svg { + height: 100%; + width: 100%; + grid-row: 3; + grid-column: 2/4; + background-color: #f5f6f7; + border-radius: 20px; + border: #f065 1px solid; +} + +.pink { + fill: #ff0066; +} + +.green { + fill: #00ff99; +} + +.dark-pink { + fill: #660029; +} + +.light-pink { + fill: #ff99c2; +} + +.off-white { + fill: #ffcce0; +} diff --git a/playgrounds/transforms/transforms.js b/playgrounds/transforms/transforms.js new file mode 100644 index 000000000..d75079781 --- /dev/null +++ b/playgrounds/transforms/transforms.js @@ -0,0 +1,21 @@ +let canvas = SVG('#canvas') + +// Make the green rectangle +canvas.rect(200, 400).move(200, 400).attr('opacity', 0.3).addClass('green') + +// Make the pink rectangle +let a = canvas + .rect(200, 400) + .move(200, 400) + .attr('opacity', 0.3) + .addClass('pink') + .transform({ px: 100, py: 500, origin: 'top-left' }) + +a.animate().rotate({ rotate: 500, origin: 'top-right' }) + +// Put an ellipse where we expect the object to be +canvas + .ellipse(30, 30) + .center(100, 500) + .attr('opacity', 0.3) + .addClass('dark-pink') diff --git a/playgrounds/webpack.config.js b/playgrounds/webpack.config.js new file mode 100644 index 000000000..c0e7233e0 --- /dev/null +++ b/playgrounds/webpack.config.js @@ -0,0 +1,28 @@ +var path = require('path') +module.exports = function (env) { + let currentTest = path.resolve(__dirname, env) + return { + mode: 'development', + devtool: 'eval-source-map', + devServer: { + contentBase: [currentTest, __dirname] + }, + + devServer: { + contentBase: [currentTest, '..'] + }, + + entry: { + app: path.resolve(currentTest, 'main.js') + }, + + output: { + path: currentTest, + filename: 'bundle.js' + }, + + resolve: { + modules: [path.resolve(__dirname, 'node_modules'), 'node_modules'] + } + } +} diff --git a/spec/RAFPlugin.js b/spec/RAFPlugin.js new file mode 100644 index 000000000..c6b96e2fb --- /dev/null +++ b/spec/RAFPlugin.js @@ -0,0 +1,88 @@ +/* globals jasmine */ +/** + * Jasmine RequestAnimationFrame: a set of helpers for testing functionality + * that uses requestAnimationFrame under the Jasmine BDD framework for JavaScript. + */ +function RAFPlugin(jasmine) { + var index = 0 + var callbacks = [] + + function MockRAF() { + this.nextTime = 0 + + var _this = this + + /** + * Mock for window.requestAnimationFrame + */ + this.mockRAF = function (fn) { + if (typeof fn !== 'function') { + throw new Error('You should pass a function to requestAnimationFrame') + } + + const i = index++ + callbacks[i] = fn + + return i + } + + /** + * Mock for window.cancelAnimationFrame + */ + this.mockCAF = function (requestID) { + callbacks.splice(requestID, 1) + } + + this.mockPerf = { + now: function () { + return _this.nextTime + } + } + + /** + * Install request animation frame mocks. + */ + this.install = function (global) { + _this.realRAF = global.requestAnimationFrame + _this.realCAF = global.cancelAnimationFrame + _this.realPerf = global.performance + global.requestAnimationFrame = _this.mockRAF + global.cancelAnimationFrame = _this.mockCAF + global.performance = _this.mockPerf + } + + /** + * Uninstall request animation frame mocks. + */ + this.uninstall = function (global) { + global.requestAnimationFrame = _this.realRAF + global.cancelAnimationFrame = _this.realCAF + global.performance = _this.realPerf + _this.nextTime = 0 + callbacks = [] + } + + /** + * Simulate animation frame readiness. + */ + this.tick = function (dt) { + _this.nextTime += dt || 1 + + var fns = callbacks + var fn + var i + + callbacks = [] + index = 0 + + for (i in fns) { + fn = fns[i] + fn(_this.nextTime) + } + } + } + + jasmine.RequestAnimationFrame = new MockRAF() +} + +RAFPlugin(jasmine) diff --git a/spec/SpecRunner.html b/spec/SpecRunner.html index 0895a88ed..15a9c60cc 100644 --- a/spec/SpecRunner.html +++ b/spec/SpecRunner.html @@ -1,104 +1,31 @@ - + - - - SVG.js - Jasmine Spec Runner v2.6.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Some description - - - - - - - - - - - A - B - C - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + SVG.js - Jasmine Spec Runner + + + + + + + + + + + + + + + + + + diff --git a/spec/SpecRunnerEs6.html b/spec/SpecRunnerEs6.html new file mode 100644 index 000000000..8caabf539 --- /dev/null +++ b/spec/SpecRunnerEs6.html @@ -0,0 +1,50 @@ + + + + + SVG.js - Jasmine Spec Runner + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spec/checkForAllTests.js b/spec/checkForAllTests.js new file mode 100644 index 000000000..d25a6a4e1 --- /dev/null +++ b/spec/checkForAllTests.js @@ -0,0 +1,27 @@ +const glob = require('glob') +const path = require('path') + +glob('./spec/*/**/*.js', (err, tests) => { + if (err) { + throw err + } + + glob('./src/**/*.js', (err, files) => { + if (err) { + throw err + } + + files = files.map((e) => path.basename(e)) + tests = tests.map((e) => path.basename(e)) + const difference = files.filter((x) => !tests.includes(x)) + + if (difference.length) { + console.error( + 'The following files dont have a test file:\n\t' + + difference.join('\n\t') + ) + } else { + console.info('All src files are covered by tests') + } + }) +}) diff --git a/spec/fixtures/fixture.css b/spec/fixtures/fixture.css index e72e42189..85d434558 100644 --- a/spec/fixtures/fixture.css +++ b/spec/fixtures/fixture.css @@ -1,6 +1,6 @@ #drawing { - width: 500px; - height: 500px; - position: fixed; - z-index: -1; -} \ No newline at end of file + width: 500px; + height: 500px; + position: fixed; + z-index: -1; +} diff --git a/spec/helpers.js b/spec/helpers.js new file mode 100644 index 000000000..5a2568a80 --- /dev/null +++ b/spec/helpers.js @@ -0,0 +1,194 @@ +import { getWindow } from '../src/utils/window.js' +import { svg } from '../src/modules/core/namespaces.js' + +function tag(name, attrs, children) { + const doc = getWindow().document + const el = doc.createElementNS(svg, name) + let i + + for (i in attrs) { + el.setAttribute(i, attrs[i]) + } + + for (i in children) { + if (typeof children[i] === 'string') { + children[i] = doc.createTextNode(children[i]) + } + + el.appendChild(children[i]) + } + + return el +} + +export function fixtures() { + return tag( + 'svg', + { + height: 0, + width: 0, + id: 'inlineSVG' + }, + [ + tag('defs', {}, [ + tag('linearGradient', {}, [ + tag('stop', { offset: '5%', 'stop-color': 'green' }), + tag('stop', { offset: '95%', 'stop-color': 'gold' }) + ]), + tag('radialGradient', {}, [ + tag('stop', { offset: '5%', 'stop-color': 'green' }), + tag('stop', { offset: '95%', 'stop-color': 'gold' }) + ]) + ]), + tag('desc', {}, ['Some description']), + tag('path', { + id: 'lineAB', + d: 'M 100 350 l 150 -300', + stroke: 'red', + 'stroke-width': '3', + fill: 'none' + }), + tag('path', { + id: 'lineBC', + d: 'M 250 50 l 150 300', + stroke: 'red', + 'stroke-width': '3', + fill: 'none' + }), + tag('path', { + d: 'M 175 200 l 150 0', + stroke: 'green', + 'stroke-width': '3', + fill: 'none' + }), + tag('path', { + d: 'M 100 350 q 150 -300 300 0', + stroke: 'blue', + 'stroke-width': '5', + fill: 'none' + }), + tag( + 'g', + { + stroke: 'black', + 'stroke-width': '2', + fill: 'black', + id: 'pointGroup' + }, + [ + tag('circle', { + id: 'pointA', + cx: '100', + cy: '350', + r: '3' + }), + tag('circle', { + id: 'pointB', + cx: '250', + cy: '50', + r: '50' + }), + tag('circle', { + id: 'pointC', + cx: '400', + cy: '350', + r: '50' + }) + ] + ), + tag( + 'g', + { + 'font-size': '30', + font: 'sans-serif', + fill: 'black', + stroke: 'none', + 'text-anchor': 'middle', + id: 'labelGroup' + }, + [ + tag( + 'text', + { + x: '100', + y: '350', + dy: '-30' + }, + ['A'] + ), + tag( + 'text', + { + x: '250', + y: '50', + dy: '-10' + }, + ['B'] + ), + tag( + 'text', + { + x: '400', + y: '350', + dx: '30' + }, + ['C'] + ) + ] + ), + tag('polygon', { points: '200,10 250,190 160,210' }), + tag('polyline', { points: '20,20 40,25 60,40 80,120 120,140 200,180' }) + ] + ) +} + +export function buildFixtures() { + const doc = getWindow().document + const body = doc.body || doc.documentElement + + const div = doc.createElement('div') + div.id = 'fixtures' + + try { + // FIXME: doesn't work in svgdom + div.style.position = 'absolute' + div.style.top = 0 + div.style.left = 0 + } catch (e) { + // + } + + div.appendChild(fixtures()) + body.appendChild(div) +} + +export function buildCanvas() { + const doc = getWindow().document + const body = doc.body || doc.documentElement + + const div = doc.createElement('div') + div.id = 'canvas' + + try { + // FIXME: doesn't work in svgdom + div.style.position = 'absolute' + div.style.top = 0 + div.style.left = 0 + } catch (e) { + // + } + body.appendChild(div) +} + +export function clear() { + const doc = getWindow().document + const canvas = doc.getElementById('canvas') + const fixtures = doc.getElementById('fixtures') + + // remove if present + fixtures && fixtures.parentNode.removeChild(fixtures) + canvas.parentNode.removeChild(canvas) + ;[...doc.querySelectorAll('svg')].forEach((el) => + el.parentNode.removeChild(el) + ) +} diff --git a/spec/lib/jasmine-2.6.0/boot.js b/spec/lib/jasmine-2.6.0/boot.js deleted file mode 100644 index d9b5a80b0..000000000 --- a/spec/lib/jasmine-2.6.0/boot.js +++ /dev/null @@ -1,133 +0,0 @@ -/** - Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. - - If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms. - - The location of `boot.js` can be specified and/or overridden in `jasmine.yml`. - - [jasmine-gem]: http://github.com/pivotal/jasmine-gem - */ - -(function() { - - /** - * ## Require & Instantiate - * - * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. - */ - window.jasmine = jasmineRequire.core(jasmineRequire); - - /** - * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference. - */ - jasmineRequire.html(jasmine); - - /** - * Create the Jasmine environment. This is used to run all specs in a project. - */ - var env = jasmine.getEnv(); - - /** - * ## The Global Interface - * - * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. - */ - var jasmineInterface = jasmineRequire.interface(jasmine, env); - - /** - * Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. - */ - extend(window, jasmineInterface); - - /** - * ## Runner Parameters - * - * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface. - */ - - var queryString = new jasmine.QueryString({ - getWindowLocation: function() { return window.location; } - }); - - var filterSpecs = !!queryString.getParam("spec"); - - var catchingExceptions = queryString.getParam("catch"); - env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); - - var throwingExpectationFailures = queryString.getParam("throwFailures"); - env.throwOnExpectationFailure(throwingExpectationFailures); - - var random = queryString.getParam("random"); - env.randomizeTests(random); - - var seed = queryString.getParam("seed"); - if (seed) { - env.seed(seed); - } - - /** - * ## Reporters - * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any). - */ - var htmlReporter = new jasmine.HtmlReporter({ - env: env, - onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); }, - onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); }, - onRandomClick: function() { queryString.navigateWithNewParam("random", !env.randomTests()); }, - addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); }, - getContainer: function() { return document.body; }, - createElement: function() { return document.createElement.apply(document, arguments); }, - createTextNode: function() { return document.createTextNode.apply(document, arguments); }, - timer: new jasmine.Timer(), - filterSpecs: filterSpecs - }); - - /** - * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. - */ - env.addReporter(jasmineInterface.jsApiReporter); - env.addReporter(htmlReporter); - - /** - * Filter which specs will be run by matching the start of the full name against the `spec` query param. - */ - var specFilter = new jasmine.HtmlSpecFilter({ - filterString: function() { return queryString.getParam("spec"); } - }); - - env.specFilter = function(spec) { - return specFilter.matches(spec.getFullName()); - }; - - /** - * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack. - */ - window.setTimeout = window.setTimeout; - window.setInterval = window.setInterval; - window.clearTimeout = window.clearTimeout; - window.clearInterval = window.clearInterval; - - /** - * ## Execution - * - * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded. - */ - var currentWindowOnload = window.onload; - - window.onload = function() { - if (currentWindowOnload) { - currentWindowOnload(); - } - htmlReporter.initialize(); - env.execute(); - }; - - /** - * Helper function for readability above. - */ - function extend(destination, source) { - for (var property in source) destination[property] = source[property]; - return destination; - } - -}()); diff --git a/spec/lib/jasmine-2.6.0/console.js b/spec/lib/jasmine-2.6.0/console.js deleted file mode 100644 index 38ad952de..000000000 --- a/spec/lib/jasmine-2.6.0/console.js +++ /dev/null @@ -1,190 +0,0 @@ -/* -Copyright (c) 2008-2017 Pivotal Labs - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -function getJasmineRequireObj() { - if (typeof module !== 'undefined' && module.exports) { - return exports; - } else { - window.jasmineRequire = window.jasmineRequire || {}; - return window.jasmineRequire; - } -} - -getJasmineRequireObj().console = function(jRequire, j$) { - j$.ConsoleReporter = jRequire.ConsoleReporter(); -}; - -getJasmineRequireObj().ConsoleReporter = function() { - - var noopTimer = { - start: function(){}, - elapsed: function(){ return 0; } - }; - - function ConsoleReporter(options) { - var print = options.print, - showColors = options.showColors || false, - onComplete = options.onComplete || function() {}, - timer = options.timer || noopTimer, - specCount, - failureCount, - failedSpecs = [], - pendingCount, - ansi = { - green: '\x1B[32m', - red: '\x1B[31m', - yellow: '\x1B[33m', - none: '\x1B[0m' - }, - failedSuites = []; - - print('ConsoleReporter is deprecated and will be removed in a future version.'); - - this.jasmineStarted = function() { - specCount = 0; - failureCount = 0; - pendingCount = 0; - print('Started'); - printNewline(); - timer.start(); - }; - - this.jasmineDone = function() { - printNewline(); - for (var i = 0; i < failedSpecs.length; i++) { - specFailureDetails(failedSpecs[i]); - } - - if(specCount > 0) { - printNewline(); - - var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' + - failureCount + ' ' + plural('failure', failureCount); - - if (pendingCount) { - specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount); - } - - print(specCounts); - } else { - print('No specs found'); - } - - printNewline(); - var seconds = timer.elapsed() / 1000; - print('Finished in ' + seconds + ' ' + plural('second', seconds)); - printNewline(); - - for(i = 0; i < failedSuites.length; i++) { - suiteFailureDetails(failedSuites[i]); - } - - onComplete(failureCount === 0); - }; - - this.specDone = function(result) { - specCount++; - - if (result.status == 'pending') { - pendingCount++; - print(colored('yellow', '*')); - return; - } - - if (result.status == 'passed') { - print(colored('green', '.')); - return; - } - - if (result.status == 'failed') { - failureCount++; - failedSpecs.push(result); - print(colored('red', 'F')); - } - }; - - this.suiteDone = function(result) { - if (result.failedExpectations && result.failedExpectations.length > 0) { - failureCount++; - failedSuites.push(result); - } - }; - - return this; - - function printNewline() { - print('\n'); - } - - function colored(color, str) { - return showColors ? (ansi[color] + str + ansi.none) : str; - } - - function plural(str, count) { - return count == 1 ? str : str + 's'; - } - - function repeat(thing, times) { - var arr = []; - for (var i = 0; i < times; i++) { - arr.push(thing); - } - return arr; - } - - function indent(str, spaces) { - var lines = (str || '').split('\n'); - var newArr = []; - for (var i = 0; i < lines.length; i++) { - newArr.push(repeat(' ', spaces).join('') + lines[i]); - } - return newArr.join('\n'); - } - - function specFailureDetails(result) { - printNewline(); - print(result.fullName); - - for (var i = 0; i < result.failedExpectations.length; i++) { - var failedExpectation = result.failedExpectations[i]; - printNewline(); - print(indent(failedExpectation.message, 2)); - print(indent(failedExpectation.stack, 2)); - } - - printNewline(); - } - - function suiteFailureDetails(result) { - for (var i = 0; i < result.failedExpectations.length; i++) { - printNewline(); - print(colored('red', 'An error was thrown in an afterAll')); - printNewline(); - print(colored('red', 'AfterAll ' + result.failedExpectations[i].message)); - - } - printNewline(); - } - } - - return ConsoleReporter; -}; diff --git a/spec/lib/jasmine-2.6.0/jasmine-html.js b/spec/lib/jasmine-2.6.0/jasmine-html.js deleted file mode 100644 index 90407cc01..000000000 --- a/spec/lib/jasmine-2.6.0/jasmine-html.js +++ /dev/null @@ -1,499 +0,0 @@ -/* -Copyright (c) 2008-2017 Pivotal Labs - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -jasmineRequire.html = function(j$) { - j$.ResultsNode = jasmineRequire.ResultsNode(); - j$.HtmlReporter = jasmineRequire.HtmlReporter(j$); - j$.QueryString = jasmineRequire.QueryString(); - j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter(); -}; - -jasmineRequire.HtmlReporter = function(j$) { - - var noopTimer = { - start: function() {}, - elapsed: function() { return 0; } - }; - - function HtmlReporter(options) { - var env = options.env || {}, - getContainer = options.getContainer, - createElement = options.createElement, - createTextNode = options.createTextNode, - onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, - onThrowExpectationsClick = options.onThrowExpectationsClick || function() {}, - onRandomClick = options.onRandomClick || function() {}, - addToExistingQueryString = options.addToExistingQueryString || defaultQueryString, - filterSpecs = options.filterSpecs, - timer = options.timer || noopTimer, - results = [], - specsExecuted = 0, - failureCount = 0, - pendingSpecCount = 0, - htmlReporterMain, - symbols, - failedSuites = []; - - this.initialize = function() { - clearPrior(); - htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'}, - createDom('div', {className: 'jasmine-banner'}, - createDom('a', {className: 'jasmine-title', href: 'http://jasmine.github.io/', target: '_blank'}), - createDom('span', {className: 'jasmine-version'}, j$.version) - ), - createDom('ul', {className: 'jasmine-symbol-summary'}), - createDom('div', {className: 'jasmine-alert'}), - createDom('div', {className: 'jasmine-results'}, - createDom('div', {className: 'jasmine-failures'}) - ) - ); - getContainer().appendChild(htmlReporterMain); - }; - - var totalSpecsDefined; - this.jasmineStarted = function(options) { - totalSpecsDefined = options.totalSpecsDefined || 0; - timer.start(); - }; - - var summary = createDom('div', {className: 'jasmine-summary'}); - - var topResults = new j$.ResultsNode({}, '', null), - currentParent = topResults; - - this.suiteStarted = function(result) { - currentParent.addChild(result, 'suite'); - currentParent = currentParent.last(); - }; - - this.suiteDone = function(result) { - if (result.status == 'failed') { - failedSuites.push(result); - } - - if (currentParent == topResults) { - return; - } - - currentParent = currentParent.parent; - }; - - this.specStarted = function(result) { - currentParent.addChild(result, 'spec'); - }; - - var failures = []; - this.specDone = function(result) { - if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') { - console.error('Spec \'' + result.fullName + '\' has no expectations.'); - } - - if (result.status != 'disabled') { - specsExecuted++; - } - - if (!symbols){ - symbols = find('.jasmine-symbol-summary'); - } - - symbols.appendChild(createDom('li', { - className: noExpectations(result) ? 'jasmine-empty' : 'jasmine-' + result.status, - id: 'spec_' + result.id, - title: result.fullName - } - )); - - if (result.status == 'failed') { - failureCount++; - - var failure = - createDom('div', {className: 'jasmine-spec-detail jasmine-failed'}, - createDom('div', {className: 'jasmine-description'}, - createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName) - ), - createDom('div', {className: 'jasmine-messages'}) - ); - var messages = failure.childNodes[1]; - - for (var i = 0; i < result.failedExpectations.length; i++) { - var expectation = result.failedExpectations[i]; - messages.appendChild(createDom('div', {className: 'jasmine-result-message'}, expectation.message)); - messages.appendChild(createDom('div', {className: 'jasmine-stack-trace'}, expectation.stack)); - } - - failures.push(failure); - } - - if (result.status == 'pending') { - pendingSpecCount++; - } - }; - - this.jasmineDone = function(doneResult) { - var banner = find('.jasmine-banner'); - var alert = find('.jasmine-alert'); - var order = doneResult && doneResult.order; - alert.appendChild(createDom('span', {className: 'jasmine-duration'}, 'finished in ' + timer.elapsed() / 1000 + 's')); - - banner.appendChild( - createDom('div', { className: 'jasmine-run-options' }, - createDom('span', { className: 'jasmine-trigger' }, 'Options'), - createDom('div', { className: 'jasmine-payload' }, - createDom('div', { className: 'jasmine-exceptions' }, - createDom('input', { - className: 'jasmine-raise', - id: 'jasmine-raise-exceptions', - type: 'checkbox' - }), - createDom('label', { className: 'jasmine-label', 'for': 'jasmine-raise-exceptions' }, 'raise exceptions')), - createDom('div', { className: 'jasmine-throw-failures' }, - createDom('input', { - className: 'jasmine-throw', - id: 'jasmine-throw-failures', - type: 'checkbox' - }), - createDom('label', { className: 'jasmine-label', 'for': 'jasmine-throw-failures' }, 'stop spec on expectation failure')), - createDom('div', { className: 'jasmine-random-order' }, - createDom('input', { - className: 'jasmine-random', - id: 'jasmine-random-order', - type: 'checkbox' - }), - createDom('label', { className: 'jasmine-label', 'for': 'jasmine-random-order' }, 'run tests in random order')) - ) - )); - - var raiseCheckbox = find('#jasmine-raise-exceptions'); - - raiseCheckbox.checked = !env.catchingExceptions(); - raiseCheckbox.onclick = onRaiseExceptionsClick; - - var throwCheckbox = find('#jasmine-throw-failures'); - throwCheckbox.checked = env.throwingExpectationFailures(); - throwCheckbox.onclick = onThrowExpectationsClick; - - var randomCheckbox = find('#jasmine-random-order'); - randomCheckbox.checked = env.randomTests(); - randomCheckbox.onclick = onRandomClick; - - var optionsMenu = find('.jasmine-run-options'), - optionsTrigger = optionsMenu.querySelector('.jasmine-trigger'), - optionsPayload = optionsMenu.querySelector('.jasmine-payload'), - isOpen = /\bjasmine-open\b/; - - optionsTrigger.onclick = function() { - if (isOpen.test(optionsPayload.className)) { - optionsPayload.className = optionsPayload.className.replace(isOpen, ''); - } else { - optionsPayload.className += ' jasmine-open'; - } - }; - - if (specsExecuted < totalSpecsDefined) { - var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all'; - var skippedLink = order && order.random ? '?random=true' : '?'; - alert.appendChild( - createDom('span', {className: 'jasmine-bar jasmine-skipped'}, - createDom('a', {href: skippedLink, title: 'Run all specs'}, skippedMessage) - ) - ); - } - var statusBarMessage = ''; - var statusBarClassName = 'jasmine-bar '; - - if (totalSpecsDefined > 0) { - statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); - if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); } - statusBarClassName += (failureCount > 0) ? 'jasmine-failed' : 'jasmine-passed'; - } else { - statusBarClassName += 'jasmine-skipped'; - statusBarMessage += 'No specs found'; - } - - var seedBar; - if (order && order.random) { - seedBar = createDom('span', {className: 'jasmine-seed-bar'}, - ', randomized with seed ', - createDom('a', {title: 'randomized with seed ' + order.seed, href: seedHref(order.seed)}, order.seed) - ); - } - - alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage, seedBar)); - - var errorBarClassName = 'jasmine-bar jasmine-errored'; - var errorBarMessagePrefix = 'AfterAll '; - - for(var i = 0; i < failedSuites.length; i++) { - var failedSuite = failedSuites[i]; - for(var j = 0; j < failedSuite.failedExpectations.length; j++) { - alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessagePrefix + failedSuite.failedExpectations[j].message)); - } - } - - var globalFailures = (doneResult && doneResult.failedExpectations) || []; - for(i = 0; i < globalFailures.length; i++) { - var failure = globalFailures[i]; - alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessagePrefix + failure.message)); - } - - var results = find('.jasmine-results'); - results.appendChild(summary); - - summaryList(topResults, summary); - - function summaryList(resultsTree, domParent) { - var specListNode; - for (var i = 0; i < resultsTree.children.length; i++) { - var resultNode = resultsTree.children[i]; - if (filterSpecs && !hasActiveSpec(resultNode)) { - continue; - } - if (resultNode.type == 'suite') { - var suiteListNode = createDom('ul', {className: 'jasmine-suite', id: 'suite-' + resultNode.result.id}, - createDom('li', {className: 'jasmine-suite-detail'}, - createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description) - ) - ); - - summaryList(resultNode, suiteListNode); - domParent.appendChild(suiteListNode); - } - if (resultNode.type == 'spec') { - if (domParent.getAttribute('class') != 'jasmine-specs') { - specListNode = createDom('ul', {className: 'jasmine-specs'}); - domParent.appendChild(specListNode); - } - var specDescription = resultNode.result.description; - if(noExpectations(resultNode.result)) { - specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription; - } - if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') { - specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason; - } - specListNode.appendChild( - createDom('li', { - className: 'jasmine-' + resultNode.result.status, - id: 'spec-' + resultNode.result.id - }, - createDom('a', {href: specHref(resultNode.result)}, specDescription) - ) - ); - } - } - } - - if (failures.length) { - alert.appendChild( - createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-spec-list'}, - createDom('span', {}, 'Spec List | '), - createDom('a', {className: 'jasmine-failures-menu', href: '#'}, 'Failures'))); - alert.appendChild( - createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-failure-list'}, - createDom('a', {className: 'jasmine-spec-list-menu', href: '#'}, 'Spec List'), - createDom('span', {}, ' | Failures '))); - - find('.jasmine-failures-menu').onclick = function() { - setMenuModeTo('jasmine-failure-list'); - }; - find('.jasmine-spec-list-menu').onclick = function() { - setMenuModeTo('jasmine-spec-list'); - }; - - setMenuModeTo('jasmine-failure-list'); - - var failureNode = find('.jasmine-failures'); - for (i = 0; i < failures.length; i++) { - failureNode.appendChild(failures[i]); - } - } - }; - - return this; - - function find(selector) { - return getContainer().querySelector('.jasmine_html-reporter ' + selector); - } - - function clearPrior() { - // return the reporter - var oldReporter = find(''); - - if(oldReporter) { - getContainer().removeChild(oldReporter); - } - } - - function createDom(type, attrs, childrenVarArgs) { - var el = createElement(type); - - for (var i = 2; i < arguments.length; i++) { - var child = arguments[i]; - - if (typeof child === 'string') { - el.appendChild(createTextNode(child)); - } else { - if (child) { - el.appendChild(child); - } - } - } - - for (var attr in attrs) { - if (attr == 'className') { - el[attr] = attrs[attr]; - } else { - el.setAttribute(attr, attrs[attr]); - } - } - - return el; - } - - function pluralize(singular, count) { - var word = (count == 1 ? singular : singular + 's'); - - return '' + count + ' ' + word; - } - - function specHref(result) { - return addToExistingQueryString('spec', result.fullName); - } - - function seedHref(seed) { - return addToExistingQueryString('seed', seed); - } - - function defaultQueryString(key, value) { - return '?' + key + '=' + value; - } - - function setMenuModeTo(mode) { - htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode); - } - - function noExpectations(result) { - return (result.failedExpectations.length + result.passedExpectations.length) === 0 && - result.status === 'passed'; - } - - function hasActiveSpec(resultNode) { - if (resultNode.type == 'spec' && resultNode.result.status != 'disabled') { - return true; - } - - if (resultNode.type == 'suite') { - for (var i = 0, j = resultNode.children.length; i < j; i++) { - if (hasActiveSpec(resultNode.children[i])) { - return true; - } - } - } - } - } - - return HtmlReporter; -}; - -jasmineRequire.HtmlSpecFilter = function() { - function HtmlSpecFilter(options) { - var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); - var filterPattern = new RegExp(filterString); - - this.matches = function(specName) { - return filterPattern.test(specName); - }; - } - - return HtmlSpecFilter; -}; - -jasmineRequire.ResultsNode = function() { - function ResultsNode(result, type, parent) { - this.result = result; - this.type = type; - this.parent = parent; - - this.children = []; - - this.addChild = function(result, type) { - this.children.push(new ResultsNode(result, type, this)); - }; - - this.last = function() { - return this.children[this.children.length - 1]; - }; - } - - return ResultsNode; -}; - -jasmineRequire.QueryString = function() { - function QueryString(options) { - - this.navigateWithNewParam = function(key, value) { - options.getWindowLocation().search = this.fullStringWithNewParam(key, value); - }; - - this.fullStringWithNewParam = function(key, value) { - var paramMap = queryStringToParamMap(); - paramMap[key] = value; - return toQueryString(paramMap); - }; - - this.getParam = function(key) { - return queryStringToParamMap()[key]; - }; - - return this; - - function toQueryString(paramMap) { - var qStrPairs = []; - for (var prop in paramMap) { - qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop])); - } - return '?' + qStrPairs.join('&'); - } - - function queryStringToParamMap() { - var paramStr = options.getWindowLocation().search.substring(1), - params = [], - paramMap = {}; - - if (paramStr.length > 0) { - params = paramStr.split('&'); - for (var i = 0; i < params.length; i++) { - var p = params[i].split('='); - var value = decodeURIComponent(p[1]); - if (value === 'true' || value === 'false') { - value = JSON.parse(value); - } - paramMap[decodeURIComponent(p[0])] = value; - } - } - - return paramMap; - } - - } - - return QueryString; -}; diff --git a/spec/lib/jasmine-2.6.0/jasmine.css b/spec/lib/jasmine-2.6.0/jasmine.css deleted file mode 100644 index 631998275..000000000 --- a/spec/lib/jasmine-2.6.0/jasmine.css +++ /dev/null @@ -1,58 +0,0 @@ -body { overflow-y: scroll; } - -.jasmine_html-reporter { background-color: #eee; padding: 5px; margin: -8px; font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333; } -.jasmine_html-reporter a { text-decoration: none; } -.jasmine_html-reporter a:hover { text-decoration: underline; } -.jasmine_html-reporter p, .jasmine_html-reporter h1, .jasmine_html-reporter h2, .jasmine_html-reporter h3, .jasmine_html-reporter h4, .jasmine_html-reporter h5, .jasmine_html-reporter h6 { margin: 0; line-height: 14px; } -.jasmine_html-reporter .jasmine-banner, .jasmine_html-reporter .jasmine-symbol-summary, .jasmine_html-reporter .jasmine-summary, .jasmine_html-reporter .jasmine-result-message, .jasmine_html-reporter .jasmine-spec .jasmine-description, .jasmine_html-reporter .jasmine-spec-detail .jasmine-description, .jasmine_html-reporter .jasmine-alert .jasmine-bar, .jasmine_html-reporter .jasmine-stack-trace { padding-left: 9px; padding-right: 9px; } -.jasmine_html-reporter .jasmine-banner { position: relative; } -.jasmine_html-reporter .jasmine-banner .jasmine-title { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAZCAMAAACGusnyAAACdlBMVEX/////AP+AgICqVaqAQICZM5mAVYCSSZKAQICOOY6ATYCLRouAQICJO4mSSYCIRIiPQICHPIeOR4CGQ4aMQICGPYaLRoCFQ4WKQICPPYWJRYCOQoSJQICNPoSIRICMQoSHQICHRICKQoOHQICKPoOJO4OJQYOMQICMQ4CIQYKLQICIPoKLQ4CKQICNPoKJQISMQ4KJQoSLQYKJQISLQ4KIQoSKQYKIQICIQISMQoSKQYKLQIOLQoOJQYGLQIOKQIOMQoGKQYOLQYGKQIOLQoGJQYOJQIOKQYGJQIOKQoGKQIGLQIKLQ4KKQoGLQYKJQIGKQYKJQIGKQIKJQoGKQYKLQIGKQYKLQIOJQoKKQoOJQYKKQIOJQoKKQoOKQIOLQoKKQYOLQYKJQIOKQoKKQYKKQoKJQYOKQYKLQIOKQoKLQYOKQYKLQIOJQoGKQYKJQYGJQoGKQYKLQoGLQYGKQoGJQYKKQYGJQIKKQoGJQYKLQIKKQYGLQYKKQYGKQYGKQYKJQYOKQoKJQYOKQYKLQYOLQYOKQYKLQYOKQoKKQYKKQYOKQYOJQYKKQYKLQYKKQIKKQoKKQYKKQYKKQoKJQIKKQYKLQYKKQYKKQIKKQYKKQYKKQYKKQIKKQYKJQYGLQYGKQYKKQYKKQYGKQIKKQYGKQYOJQoKKQYOLQYKKQYOKQoKKQYKKQoKKQYKKQYKJQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKJQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKmIDpEAAAA0XRSTlMAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAiIyQlJycoKissLS4wMTQ1Njc4OTo7PDw+P0BCQ0RISUpLTE1OUFNUVVdYWFlaW15fYGFiY2ZnaGlqa2xtb3BxcnN0dnh5ent8fX5/gIGChIWIioyNjo+QkZOUlZaYmZqbnJ2eoKGio6WmqKmsra6vsLGztre4ubq7vL2+wMHDxMjJysvNzs/Q0dLU1tfY2dvc3t/g4eLj5ebn6Onq6+zt7u/w8vP09fb3+Pn6+/z9/vkVQXAAAAMaSURBVHhe5dXxV1N1GMfxz2ABbDgIAm5VDJOyVDIJLUMaVpBWUZUaGbmqoGpZRSiGiRWp6KoZ5AB0ZY50RImZQIlahKkMYXv/R90dBvET/rJfOr3Ouc8v99zPec59zvf56j+vYKlViSf7250X4Mr3O29Tgq08BdGB4DhcekEJ5YkQKFsgWZdtj9JpV+I8xPjLFqkrsEIqO8PHSpis36jWazcqjEsfJjkvRssVU37SdIOu4XCf5vEJPsnwJpnRNU9JmxhMk8l1gehIrq7hTFjzOD+Vf88629qKMJVNltInFeRexRQyJlNeqd1iGDlSzrIUIyXbyFfm3RYprcQRe7lqtWyGYbfc6dT0R2vmdOOkX3u55C1rP37ftiH+tDby4r/RBT0w8TyEkr+epB9XgPDmSYYWbrhCuFYaIyw3fDQAXTnSkh+ANofiHmWf9l+FY1I90FdQTetstO00o23novzVsJ7uB3/C5TkbjRwZ5JerwV4iRWq9HFbFMaK/d0TYqayRiQPuIxxS3Bu8JWU90/60tKi7vkhaznez0a/TbVOKj5CaOZh6fWG6/Lyv9B/ZLR1gw/S/fpbeVD3MCW1li6SvWDOn65tr99/uvWtBS0XDm4s1t+sOHpG0kpBKx/l77wOSnxLpcx6TXmXLTPQOKYOf9Q1dfr8/SJ2mFdCvl1Yl93DiHUZvXeLJbGSzYu5gVJ2slbSakOR8dxCq5adQ2oFLqsE9Ex3L4qQO0eOPeU5x56bypXp4onSEb5OkICX6lDat55TeoztNKQcJaakrz9KCb95oD69IKq+yKW4XPjknaS52V0TZqE2cTtXjcHSCRmUO88e+85hj3EP74i9p8pylw7lxgMDyyl6OV7ZejnjNMfatu87LxRbH0IS35gt2a4ZjmGpVBdKK3Wr6INk8jWWSGqbA55CKgjBRC6E9w78ydTg3ABS3AFV1QN0Y4Aa2pgEjWnQURj9L0ayK6R2ysEqxHUKzYnLvvyU+i9KM2JHJzE4vyZOyDcOwOsySajeLPc8sNvPJkFlyJd20wpqAzZeAfZ3oWybxd+P/3j+SG3uSBdf2VQAAAABJRU5ErkJggg==') no-repeat; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNjgxLjk2MjUyIgogICBoZWlnaHQ9IjE4Ny41IgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhOCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczYiPjxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXBQYXRoMTgiPjxwYXRoCiAgICAgICAgIGQ9Ik0gMCwxNTAwIDAsMCBsIDU0NTUuNzQsMCAwLDE1MDAgTCAwLDE1MDAgeiIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgaWQ9InBhdGgyMCIgLz48L2NsaXBQYXRoPjwvZGVmcz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUsMCwwLC0xLjI1LDAsMTg3LjUpIgogICAgIGlkPSJnMTAiPjxnCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDAuMSwwLjEpIgogICAgICAgaWQ9ImcxMiI+PGcKICAgICAgICAgaWQ9ImcxNCI+PGcKICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgxOCkiCiAgICAgICAgICAgaWQ9ImcxNiI+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTU0NCw1OTkuNDM0IGMgMC45MiwtNDAuMzUyIDI1LjY4LC04MS42MDIgNzEuNTMsLTgxLjYwMiAyNy41MSwwIDQ3LjY4LDEyLjgzMiA2MS40NCwzNS43NTQgMTIuODMsMjIuOTMgMTIuODMsNTYuODUyIDEyLjgzLDgyLjUyNyBsIDAsMzI5LjE4NCAtNzEuNTIsMCAwLDEwNC41NDMgMjY2LjgzLDAgMCwtMTA0LjU0MyAtNzAuNiwwIDAsLTM0NC43NyBjIDAsLTU4LjY5MSAtMy42OCwtMTA0LjUzMSAtNDQuOTMsLTE1Mi4yMTggLTM2LjY4LC00Mi4xOCAtOTYuMjgsLTY2LjAyIC0xNTMuMTQsLTY2LjAyIC0xMTcuMzcsMCAtMjA3LjI0LDc3Ljk0MSAtMjAyLjY0LDE5Ny4xNDUgbCAxMzAuMiwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMjIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDIzMDEuNCw2NjIuNjk1IGMgMCw4MC43MDMgLTY2Ljk0LDE0NS44MTMgLTE0Ny42MywxNDUuODEzIC04My40NCwwIC0xNDcuNjMsLTY4Ljc4MSAtMTQ3LjYzLC0xNTEuMzAxIDAsLTc5Ljc4NSA2Ni45NCwtMTQ1LjgwMSAxNDUuOCwtMTQ1LjgwMSA4NC4zNSwwIDE0OS40Niw2Ny44NTIgMTQ5LjQ2LDE1MS4yODkgeiBtIC0xLjgzLC0xODEuNTQ3IGMgLTM1Ljc3LC01NC4wOTcgLTkzLjUzLC03OC44NTkgLTE1Ny43MiwtNzguODU5IC0xNDAuMywwIC0yNTEuMjQsMTE2LjQ0OSAtMjUxLjI0LDI1NC45MTggMCwxNDIuMTI5IDExMy43LDI2MC40MSAyNTYuNzQsMjYwLjQxIDYzLjI3LDAgMTE4LjI5LC0yOS4zMzYgMTUyLjIyLC04Mi41MjMgbCAwLDY5LjY4NyAxNzUuMTQsMCAwLC0xMDQuNTI3IC02MS40NCwwIDAsLTI4MC41OTggNjEuNDQsMCAwLC0xMDQuNTI3IC0xNzUuMTQsMCAwLDY2LjAxOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAyNjIyLjMzLDU1Ny4yNTggYyAzLjY3LC00NC4wMTYgMzMuMDEsLTczLjM0OCA3OC44NiwtNzMuMzQ4IDMzLjkzLDAgNjYuOTMsMjMuODI0IDY2LjkzLDYwLjUwNCAwLDQ4LjYwNiAtNDUuODQsNTYuODU2IC04My40NCw2Ni45NDEgLTg1LjI4LDIyLjAwNCAtMTc4LjgxLDQ4LjYwNiAtMTc4LjgxLDE1NS44NzkgMCw5My41MzYgNzguODYsMTQ3LjYzMyAxNjUuOTgsMTQ3LjYzMyA0NCwwIDgzLjQzLC05LjE3NiAxMTAuOTQsLTQ0LjAwOCBsIDAsMzMuOTIyIDgyLjUzLDAgMCwtMTMyLjk2NSAtMTA4LjIxLDAgYyAtMS44MywzNC44NTYgLTI4LjQyLDU3Ljc3NCAtNjMuMjYsNTcuNzc0IC0zMC4yNiwwIC02Mi4zNSwtMTcuNDIyIC02Mi4zNSwtNTEuMzQ4IDAsLTQ1Ljg0NyA0NC45MywtNTUuOTMgODAuNjksLTY0LjE4IDg4LjAyLC0yMC4xNzUgMTgyLjQ3LC00Ny42OTUgMTgyLjQ3LC0xNTcuNzM0IDAsLTk5LjAyNyAtODMuNDQsLTE1NC4wMzkgLTE3NS4xMywtMTU0LjAzOSAtNDkuNTMsMCAtOTQuNDYsMTUuNTgyIC0xMjYuNTUsNTMuMTggbCAwLC00MC4zNCAtODUuMjcsMCAwLDE0Mi4xMjkgMTE0LjYyLDAiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGgyNiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMjk4OC4xOCw4MDAuMjU0IC02My4yNiwwIDAsMTA0LjUyNyAxNjUuMDUsMCAwLC03My4zNTUgYyAzMS4xOCw1MS4zNDcgNzguODYsODUuMjc3IDE0MS4yMSw4NS4yNzcgNjcuODUsMCAxMjQuNzEsLTQxLjI1OCAxNTIuMjEsLTEwMi42OTkgMjYuNiw2Mi4zNTEgOTIuNjIsMTAyLjY5OSAxNjAuNDcsMTAyLjY5OSA1My4xOSwwIDEwNS40NiwtMjIgMTQxLjIxLC02Mi4zNTEgMzguNTIsLTQ0LjkzOCAzOC41MiwtOTMuNTMyIDM4LjUyLC0xNDkuNDU3IGwgMCwtMTg1LjIzOSA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40MiwwIDAsMTA0LjUyNyA2My4yOCwwIDAsMTU3LjcxNSBjIDAsMzIuMTAyIDAsNjAuNTI3IC0xNC42Nyw4OC45NTcgLTE4LjM0LDI2LjU4MiAtNDguNjEsNDAuMzQ0IC03OS43Nyw0MC4zNDQgLTMwLjI2LDAgLTYzLjI4LC0xMi44NDQgLTgyLjUzLC0zNi42NzIgLTIyLjkzLC0yOS4zNTUgLTIyLjkzLC01Ni44NjMgLTIyLjkzLC05Mi42MjkgbCAwLC0xNTcuNzE1IDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM4LjQxLDAgMCwxMDQuNTI3IDYzLjI4LDAgMCwxNTAuMzgzIGMgMCwyOS4zNDggMCw2Ni4wMjMgLTE0LjY3LDkxLjY5OSAtMTUuNTksMjkuMzM2IC00Ny42OSw0NC45MzQgLTgwLjcsNDQuOTM0IC0zMS4xOCwwIC01Ny43NywtMTEuMDA4IC03Ny45NCwtMzUuNzc0IC0yNC43NywtMzAuMjUzIC0yNi42LC02Mi4zNDMgLTI2LjYsLTk5Ljk0MSBsIDAsLTE1MS4zMDEgNjMuMjcsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNiwwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAzOTk4LjY2LDk1MS41NDcgLTExMS44NywwIDAsMTE4LjI5MyAxMTEuODcsMCAwLC0xMTguMjkzIHogbSAwLC00MzEuODkxIDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM5LjMzLDAgMCwxMDQuNTI3IDY0LjE5LDAgMCwyODAuNTk4IC02My4yNywwIDAsMTA0LjUyNyAxNzUuMTQsMCAwLC0zODUuMTI1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDQxNTkuMTIsODAwLjI1NCAtNjMuMjcsMCAwLDEwNC41MjcgMTc1LjE0LDAgMCwtNjkuNjg3IGMgMjkuMzUsNTQuMTAxIDg0LjM2LDgwLjY5OSAxNDQuODcsODAuNjk5IDUzLjE5LDAgMTA1LjQ1LC0yMi4wMTYgMTQxLjIyLC02MC41MjcgNDAuMzQsLTQ0LjkzNCA0MS4yNiwtODguMDMyIDQxLjI2LC0xNDMuOTU3IGwgMCwtMTkxLjY1MyA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40LDAgMCwxMDQuNTI3IDYzLjI2LDAgMCwxNTguNjM3IGMgMCwzMC4yNjIgMCw2MS40MzQgLTE5LjI2LDg4LjAzNSAtMjAuMTcsMjYuNTgyIC01My4xOCwzOS40MTQgLTg2LjE5LDM5LjQxNCAtMzMuOTMsMCAtNjguNzcsLTEzLjc1IC04OC45NCwtNDEuMjUgLTIxLjA5LC0yNy41IC0yMS4wOSwtNjkuNjg3IC0yMS4wOSwtMTAyLjcwNyBsIDAsLTE0Mi4xMjkgNjMuMjYsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNywwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDMyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA1MDgyLjQ4LDcwMy45NjUgYyAtMTkuMjQsNzAuNjA1IC04MS42LDExNS41NDcgLTE1NC4wNCwxMTUuNTQ3IC02Ni4wNCwwIC0xMjkuMywtNTEuMzQ4IC0xNDMuMDUsLTExNS41NDcgbCAyOTcuMDksMCB6IG0gODUuMjcsLTE0NC44ODMgYyAtMzguNTEsLTkzLjUyMyAtMTI5LjI3LC0xNTYuNzkzIC0yMzEuMDUsLTE1Ni43OTMgLTE0My4wNywwIC0yNTcuNjgsMTExLjg3MSAtMjU3LjY4LDI1NS44MzYgMCwxNDQuODgzIDEwOS4xMiwyNjEuMzI4IDI1NC45MSwyNjEuMzI4IDY3Ljg3LDAgMTM1LjcyLC0zMC4yNTggMTgzLjM5LC03OC44NjMgNDguNjIsLTUxLjM0NCA2OC43OSwtMTEzLjY5NSA2OC43OSwtMTgzLjM4MyBsIC0zLjY3LC0zOS40MzQgLTM5Ni4xMywwIGMgMTQuNjcsLTY3Ljg2MyA3Ny4wMywtMTE3LjM2MyAxNDYuNzIsLTExNy4zNjMgNDguNTksMCA5MC43NiwxOC4zMjggMTE4LjI4LDU4LjY3MiBsIDExNi40NCwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDY5MC44OTUsODUwLjcwMyA5MC43NSwwIDIyLjU0MywzMS4wMzUgMCwyNDMuMTIyIC0xMzUuODI5LDAgMCwtMjQzLjE0MSAyMi41MzYsLTMxLjAxNiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDM2IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA2MzIuMzk1LDc0Mi4yNTggMjguMDM5LDg2LjMwNCAtMjIuNTUxLDMxLjA0IC0yMzEuMjIzLDc1LjEyOCAtNDEuOTc2LC0xMjkuMTgzIDIzMS4yNTcsLTc1LjEzNyAzNi40NTQsMTEuODQ4IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzgiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDcxNy40NDksNjUzLjEwNSAtNzMuNDEsNTMuMzYgLTM2LjQ4OCwtMTEuODc1IC0xNDIuOTAzLC0xOTYuNjkyIDEwOS44ODMsLTc5LjgyOCAxNDIuOTE4LDE5Ni43MDMgMCwzOC4zMzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0MCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gODI4LjUyLDcwNi40NjUgLTczLjQyNiwtNTMuMzQgMC4wMTEsLTM4LjM1OSBMIDg5OC4wMDQsNDE4LjA3IDEwMDcuOSw0OTcuODk4IDg2NC45NzMsNjk0LjYwOSA4MjguNTIsNzA2LjQ2NSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA4MTIuMDg2LDgyOC41ODYgMjguMDU1LC04Ni4zMiAzNi40ODQsLTExLjgzNiAyMzEuMjI1LDc1LjExNyAtNDEuOTcsMTI5LjE4MyAtMjMxLjIzOSwtNzUuMTQgLTIyLjU1NSwtMzEuMDA0IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNDQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDczNi4zMDEsMTMzNS44OCBjIC0zMjMuMDQ3LDAgLTU4NS44NzUsLTI2Mi43OCAtNTg1Ljg3NSwtNTg1Ljc4MiAwLC0zMjMuMTE4IDI2Mi44MjgsLTU4NS45NzcgNTg1Ljg3NSwtNTg1Ljk3NyAzMjMuMDE5LDAgNTg1LjgwOSwyNjIuODU5IDU4NS44MDksNTg1Ljk3NyAwLDMyMy4wMDIgLTI2Mi43OSw1ODUuNzgyIC01ODUuODA5LDU4NS43ODIgbCAwLDAgeiBtIDAsLTExOC42MSBjIDI1Ny45NzIsMCA0NjcuMTg5LC0yMDkuMTMgNDY3LjE4OSwtNDY3LjE3MiAwLC0yNTguMTI5IC0yMDkuMjE3LC00NjcuMzQ4IC00NjcuMTg5LC00NjcuMzQ4IC0yNTguMDc0LDAgLTQ2Ny4yNTQsMjA5LjIxOSAtNDY3LjI1NCw0NjcuMzQ4IDAsMjU4LjA0MiAyMDkuMTgsNDY3LjE3MiA0NjcuMjU0LDQ2Ny4xNzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0NiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTA5MS4xMyw2MTkuODgzIC0xNzUuNzcxLDU3LjEyMSAxMS42MjksMzUuODA4IDE3NS43NjIsLTU3LjEyMSAtMTEuNjIsLTM1LjgwOCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQ4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA4NjYuOTU3LDkwMi4wNzQgODM2LjUsOTI0LjE5OSA5NDUuMTIxLDEwNzMuNzMgOTc1LjU4NiwxMDUxLjYxIDg2Ni45NTcsOTAyLjA3NCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDUwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA2MDcuNDY1LDkwMy40NDUgNDk4Ljg1NSwxMDUyLjk3IDUyOS4zMiwxMDc1LjEgNjM3LjkzLDkyNS41NjYgNjA3LjQ2NSw5MDMuNDQ1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDM4MC42ODgsNjIyLjEyOSAtMTEuNjI2LDM1LjgwMSAxNzUuNzU4LDU3LjA5IDExLjYyMSwtMzUuODAxIC0xNzUuNzUzLC01Ny4wOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDU0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA3MTYuMjg5LDM3Ni41OSAzNy42NDA2LDAgMCwxODQuODE2IC0zNy42NDA2LDAgMCwtMTg0LjgxNiB6IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=') no-repeat, none; -moz-background-size: 100%; -o-background-size: 100%; -webkit-background-size: 100%; background-size: 100%; display: block; float: left; width: 90px; height: 25px; } -.jasmine_html-reporter .jasmine-banner .jasmine-version { margin-left: 14px; position: relative; top: 6px; } -.jasmine_html-reporter #jasmine_content { position: fixed; right: 100%; } -.jasmine_html-reporter .jasmine-version { color: #aaa; } -.jasmine_html-reporter .jasmine-banner { margin-top: 14px; } -.jasmine_html-reporter .jasmine-duration { color: #fff; float: right; line-height: 28px; padding-right: 9px; } -.jasmine_html-reporter .jasmine-symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; } -.jasmine_html-reporter .jasmine-symbol-summary li { display: inline-block; height: 10px; width: 14px; font-size: 16px; } -.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-passed { font-size: 14px; } -.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-passed:before { color: #007069; content: "\02022"; } -.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-failed { line-height: 9px; } -.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-failed:before { color: #ca3a11; content: "\d7"; font-weight: bold; margin-left: -1px; } -.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-disabled { font-size: 14px; } -.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-disabled:before { color: #bababa; content: "\02022"; } -.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-pending { line-height: 17px; } -.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-pending:before { color: #ba9d37; content: "*"; } -.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-empty { font-size: 14px; } -.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-empty:before { color: #ba9d37; content: "\02022"; } -.jasmine_html-reporter .jasmine-run-options { float: right; margin-right: 5px; border: 1px solid #8a4182; color: #8a4182; position: relative; line-height: 20px; } -.jasmine_html-reporter .jasmine-run-options .jasmine-trigger { cursor: pointer; padding: 8px 16px; } -.jasmine_html-reporter .jasmine-run-options .jasmine-payload { position: absolute; display: none; right: -1px; border: 1px solid #8a4182; background-color: #eee; white-space: nowrap; padding: 4px 8px; } -.jasmine_html-reporter .jasmine-run-options .jasmine-payload.jasmine-open { display: block; } -.jasmine_html-reporter .jasmine-bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } -.jasmine_html-reporter .jasmine-bar.jasmine-failed { background-color: #ca3a11; } -.jasmine_html-reporter .jasmine-bar.jasmine-passed { background-color: #007069; } -.jasmine_html-reporter .jasmine-bar.jasmine-skipped { background-color: #bababa; } -.jasmine_html-reporter .jasmine-bar.jasmine-errored { background-color: #ca3a11; } -.jasmine_html-reporter .jasmine-bar.jasmine-menu { background-color: #fff; color: #aaa; } -.jasmine_html-reporter .jasmine-bar.jasmine-menu a { color: #333; } -.jasmine_html-reporter .jasmine-bar a { color: white; } -.jasmine_html-reporter.jasmine-spec-list .jasmine-bar.jasmine-menu.jasmine-failure-list, .jasmine_html-reporter.jasmine-spec-list .jasmine-results .jasmine-failures { display: none; } -.jasmine_html-reporter.jasmine-failure-list .jasmine-bar.jasmine-menu.jasmine-spec-list, .jasmine_html-reporter.jasmine-failure-list .jasmine-summary { display: none; } -.jasmine_html-reporter .jasmine-results { margin-top: 14px; } -.jasmine_html-reporter .jasmine-summary { margin-top: 14px; } -.jasmine_html-reporter .jasmine-summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; } -.jasmine_html-reporter .jasmine-summary ul.jasmine-suite { margin-top: 7px; margin-bottom: 7px; } -.jasmine_html-reporter .jasmine-summary li.jasmine-passed a { color: #007069; } -.jasmine_html-reporter .jasmine-summary li.jasmine-failed a { color: #ca3a11; } -.jasmine_html-reporter .jasmine-summary li.jasmine-empty a { color: #ba9d37; } -.jasmine_html-reporter .jasmine-summary li.jasmine-pending a { color: #ba9d37; } -.jasmine_html-reporter .jasmine-summary li.jasmine-disabled a { color: #bababa; } -.jasmine_html-reporter .jasmine-description + .jasmine-suite { margin-top: 0; } -.jasmine_html-reporter .jasmine-suite { margin-top: 14px; } -.jasmine_html-reporter .jasmine-suite a { color: #333; } -.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail { margin-bottom: 28px; } -.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description { background-color: #ca3a11; } -.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description a { color: white; } -.jasmine_html-reporter .jasmine-result-message { padding-top: 14px; color: #333; white-space: pre; } -.jasmine_html-reporter .jasmine-result-message span.jasmine-result { display: block; } -.jasmine_html-reporter .jasmine-stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666; border: 1px solid #ddd; background: white; white-space: pre; } diff --git a/spec/lib/jasmine-2.6.0/jasmine.js b/spec/lib/jasmine-2.6.0/jasmine.js deleted file mode 100644 index 57b5afe5e..000000000 --- a/spec/lib/jasmine-2.6.0/jasmine.js +++ /dev/null @@ -1,4943 +0,0 @@ -/* -Copyright (c) 2008-2017 Pivotal Labs - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -var getJasmineRequireObj = (function (jasmineGlobal) { - var jasmineRequire; - - if (typeof module !== 'undefined' && module.exports && typeof exports !== 'undefined') { - if (typeof global !== 'undefined') { - jasmineGlobal = global; - } else { - jasmineGlobal = {}; - } - jasmineRequire = exports; - } else { - if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]') { - jasmineGlobal = window; - } - jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {}; - } - - function getJasmineRequire() { - return jasmineRequire; - } - - getJasmineRequire().core = function(jRequire) { - var j$ = {}; - - jRequire.base(j$, jasmineGlobal); - j$.util = jRequire.util(); - j$.errors = jRequire.errors(); - j$.formatErrorMsg = jRequire.formatErrorMsg(); - j$.Any = jRequire.Any(j$); - j$.Anything = jRequire.Anything(j$); - j$.CallTracker = jRequire.CallTracker(j$); - j$.MockDate = jRequire.MockDate(); - j$.getClearStack = jRequire.clearStack(j$); - j$.Clock = jRequire.Clock(); - j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); - j$.Env = jRequire.Env(j$); - j$.ExceptionFormatter = jRequire.ExceptionFormatter(); - j$.Expectation = jRequire.Expectation(); - j$.buildExpectationResult = jRequire.buildExpectationResult(); - j$.JsApiReporter = jRequire.JsApiReporter(); - j$.matchersUtil = jRequire.matchersUtil(j$); - j$.ObjectContaining = jRequire.ObjectContaining(j$); - j$.ArrayContaining = jRequire.ArrayContaining(j$); - j$.pp = jRequire.pp(j$); - j$.QueueRunner = jRequire.QueueRunner(j$); - j$.ReportDispatcher = jRequire.ReportDispatcher(); - j$.Spec = jRequire.Spec(j$); - j$.Spy = jRequire.Spy(j$); - j$.SpyRegistry = jRequire.SpyRegistry(j$); - j$.SpyStrategy = jRequire.SpyStrategy(j$); - j$.StringMatching = jRequire.StringMatching(j$); - j$.Suite = jRequire.Suite(j$); - j$.Timer = jRequire.Timer(); - j$.TreeProcessor = jRequire.TreeProcessor(); - j$.version = jRequire.version(); - j$.Order = jRequire.Order(); - j$.DiffBuilder = jRequire.DiffBuilder(j$); - j$.NullDiffBuilder = jRequire.NullDiffBuilder(j$); - j$.ObjectPath = jRequire.ObjectPath(j$); - j$.GlobalErrors = jRequire.GlobalErrors(j$); - - j$.matchers = jRequire.requireMatchers(jRequire, j$); - - return j$; - }; - - return getJasmineRequire; -})(this); - -getJasmineRequireObj().requireMatchers = function(jRequire, j$) { - var availableMatchers = [ - 'toBe', - 'toBeCloseTo', - 'toBeDefined', - 'toBeFalsy', - 'toBeGreaterThan', - 'toBeGreaterThanOrEqual', - 'toBeLessThan', - 'toBeLessThanOrEqual', - 'toBeNaN', - 'toBeNegativeInfinity', - 'toBeNull', - 'toBePositiveInfinity', - 'toBeTruthy', - 'toBeUndefined', - 'toContain', - 'toEqual', - 'toHaveBeenCalled', - 'toHaveBeenCalledBefore', - 'toHaveBeenCalledTimes', - 'toHaveBeenCalledWith', - 'toMatch', - 'toThrow', - 'toThrowError' - ], - matchers = {}; - - for (var i = 0; i < availableMatchers.length; i++) { - var name = availableMatchers[i]; - matchers[name] = jRequire[name](j$); - } - - return matchers; -}; - -getJasmineRequireObj().base = function(j$, jasmineGlobal) { - j$.unimplementedMethod_ = function() { - throw new Error('unimplemented method'); - }; - - /** - * Maximum object depth the pretty printer will print to. - * Set this to a lower value to speed up pretty printing if you have large objects. - * @name jasmine.MAX_PRETTY_PRINT_DEPTH - */ - j$.MAX_PRETTY_PRINT_DEPTH = 40; - /** - * Maximum number of array elements to display when pretty printing objects. - * Elements past this number will be ellipised. - * @name jasmine.MAX_PRETTY_PRINT_ARRAY_LENGTH - */ - j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100; - /** - * Default number of milliseconds Jasmine will wait for an asynchronous spec to complete. - * @name jasmine.DEFAULT_TIMEOUT_INTERVAL - */ - j$.DEFAULT_TIMEOUT_INTERVAL = 5000; - - j$.getGlobal = function() { - return jasmineGlobal; - }; - - /** - * Get the currently booted Jasmine Environment. - * - * @name jasmine.getEnv - * @function - * @return {Env} - */ - j$.getEnv = function(options) { - var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options); - //jasmine. singletons in here (setTimeout blah blah). - return env; - }; - - j$.isArray_ = function(value) { - return j$.isA_('Array', value); - }; - - j$.isObject_ = function(value) { - return !j$.util.isUndefined(value) && value !== null && j$.isA_('Object', value); - }; - - j$.isString_ = function(value) { - return j$.isA_('String', value); - }; - - j$.isNumber_ = function(value) { - return j$.isA_('Number', value); - }; - - j$.isFunction_ = function(value) { - return j$.isA_('Function', value); - }; - - j$.isA_ = function(typeName, value) { - return j$.getType_(value) === '[object ' + typeName + ']'; - }; - - j$.getType_ = function(value) { - return Object.prototype.toString.apply(value); - }; - - j$.isDomNode = function(obj) { - return obj.nodeType > 0; - }; - - j$.fnNameFor = function(func) { - if (func.name) { - return func.name; - } - - var matches = func.toString().match(/^\s*function\s*(\w*)\s*\(/); - return matches ? matches[1] : ''; - }; - - /** - * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), - * that will succeed if the actual value being compared is an instance of the specified class/constructor. - * @name jasmine.any - * @function - * @param {Constructor} clazz - The constructor to check against. - */ - j$.any = function(clazz) { - return new j$.Any(clazz); - }; - - /** - * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), - * that will succeed if the actual value being compared is not `null` and not `undefined`. - * @name jasmine.anything - * @function - */ - j$.anything = function() { - return new j$.Anything(); - }; - - /** - * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), - * that will succeed if the actual value being compared contains at least the keys and values. - * @name jasmine.objectContaining - * @function - * @param {Object} sample - The subset of properties that _must_ be in the actual. - */ - j$.objectContaining = function(sample) { - return new j$.ObjectContaining(sample); - }; - - /** - * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), - * that will succeed if the actual value is a `String` that matches the `RegExp` or `String`. - * @name jasmine.stringMatching - * @function - * @param {RegExp|String} expected - */ - j$.stringMatching = function(expected) { - return new j$.StringMatching(expected); - }; - - /** - * Get a matcher, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), - * that will succeed if the actual value is an `Array` that contains at least the elements in the sample. - * @name jasmine.arrayContaining - * @function - * @param {Array} sample - */ - j$.arrayContaining = function(sample) { - return new j$.ArrayContaining(sample); - }; - - /** - * Create a bare {@link Spy} object. This won't be installed anywhere and will not have any implementation behind it. - * @name jasmine.createSpy - * @function - * @param {String} [name] - Name to give the spy. This will be displayed in failure messages. - * @param {Function} [originalFn] - Function to act as the real implementation. - * @return {Spy} - */ - j$.createSpy = function(name, originalFn) { - return j$.Spy(name, originalFn); - }; - - j$.isSpy = function(putativeSpy) { - if (!putativeSpy) { - return false; - } - return putativeSpy.and instanceof j$.SpyStrategy && - putativeSpy.calls instanceof j$.CallTracker; - }; - - /** - * Create an object with multiple {@link Spy}s as its members. - * @name jasmine.createSpyObj - * @function - * @param {String} [baseName] - Base name for the spies in the object. - * @param {String[]|Object} methodNames - Array of method names to create spies for, or Object whose keys will be method names and values the {@link Spy#and#returnValue|returnValue}. - * @return {Object} - */ - j$.createSpyObj = function(baseName, methodNames) { - var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); - - if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { - methodNames = baseName; - baseName = 'unknown'; - } - - var obj = {}; - var spiesWereSet = false; - - if (j$.isArray_(methodNames)) { - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); - spiesWereSet = true; - } - } else if (j$.isObject_(methodNames)) { - for (var key in methodNames) { - if (methodNames.hasOwnProperty(key)) { - obj[key] = j$.createSpy(baseName + '.' + key); - obj[key].and.returnValue(methodNames[key]); - spiesWereSet = true; - } - } - } - - if (!spiesWereSet) { - throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; - } - - return obj; - }; -}; - -getJasmineRequireObj().util = function() { - - var util = {}; - - util.inherit = function(childClass, parentClass) { - var Subclass = function() { - }; - Subclass.prototype = parentClass.prototype; - childClass.prototype = new Subclass(); - }; - - util.htmlEscape = function(str) { - if (!str) { - return str; - } - return str.replace(/&/g, '&') - .replace(//g, '>'); - }; - - util.argsToArray = function(args) { - var arrayOfArgs = []; - for (var i = 0; i < args.length; i++) { - arrayOfArgs.push(args[i]); - } - return arrayOfArgs; - }; - - util.isUndefined = function(obj) { - return obj === void 0; - }; - - util.arrayContains = function(array, search) { - var i = array.length; - while (i--) { - if (array[i] === search) { - return true; - } - } - return false; - }; - - util.clone = function(obj) { - if (Object.prototype.toString.apply(obj) === '[object Array]') { - return obj.slice(); - } - - var cloned = {}; - for (var prop in obj) { - if (obj.hasOwnProperty(prop)) { - cloned[prop] = obj[prop]; - } - } - - return cloned; - }; - - util.getPropertyDescriptor = function(obj, methodName) { - var descriptor, - proto = obj; - - do { - descriptor = Object.getOwnPropertyDescriptor(proto, methodName); - proto = Object.getPrototypeOf(proto); - } while (!descriptor && proto); - - return descriptor; - }; - - util.objectDifference = function(obj, toRemove) { - var diff = {}; - - for (var key in obj) { - if (util.has(obj, key) && !util.has(toRemove, key)) { - diff[key] = obj[key]; - } - } - - return diff; - }; - - util.has = function(obj, key) { - return Object.prototype.hasOwnProperty.call(obj, key); - }; - - return util; -}; - -getJasmineRequireObj().Spec = function(j$) { - function Spec(attrs) { - this.expectationFactory = attrs.expectationFactory; - this.resultCallback = attrs.resultCallback || function() {}; - this.id = attrs.id; - this.description = attrs.description || ''; - this.queueableFn = attrs.queueableFn; - this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; }; - this.userContext = attrs.userContext || function() { return {}; }; - this.onStart = attrs.onStart || function() {}; - this.getSpecName = attrs.getSpecName || function() { return ''; }; - this.expectationResultFactory = attrs.expectationResultFactory || function() { }; - this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; - this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; - this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; - - if (!this.queueableFn.fn) { - this.pend(); - } - - this.result = { - id: this.id, - description: this.description, - fullName: this.getFullName(), - failedExpectations: [], - passedExpectations: [], - pendingReason: '' - }; - } - - Spec.prototype.addExpectationResult = function(passed, data, isError) { - var expectationResult = this.expectationResultFactory(data); - if (passed) { - this.result.passedExpectations.push(expectationResult); - } else { - this.result.failedExpectations.push(expectationResult); - - if (this.throwOnExpectationFailure && !isError) { - throw new j$.errors.ExpectationFailed(); - } - } - }; - - Spec.prototype.expect = function(actual) { - return this.expectationFactory(actual, this); - }; - - Spec.prototype.execute = function(onComplete, enabled) { - var self = this; - - this.onStart(this); - - if (!this.isExecutable() || this.markedPending || enabled === false) { - complete(enabled); - return; - } - - var fns = this.beforeAndAfterFns(); - var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); - - this.queueRunnerFactory({ - queueableFns: allFns, - onException: function() { self.onException.apply(self, arguments); }, - onComplete: complete, - userContext: this.userContext() - }); - - function complete(enabledAgain) { - self.result.status = self.status(enabledAgain); - self.resultCallback(self.result); - - if (onComplete) { - onComplete(); - } - } - }; - - Spec.prototype.onException = function onException(e) { - if (Spec.isPendingSpecException(e)) { - this.pend(extractCustomPendingMessage(e)); - return; - } - - if (e instanceof j$.errors.ExpectationFailed) { - return; - } - - this.addExpectationResult(false, { - matcherName: '', - passed: false, - expected: '', - actual: '', - error: e - }, true); - }; - - Spec.prototype.disable = function() { - this.disabled = true; - }; - - Spec.prototype.pend = function(message) { - this.markedPending = true; - if (message) { - this.result.pendingReason = message; - } - }; - - Spec.prototype.getResult = function() { - this.result.status = this.status(); - return this.result; - }; - - Spec.prototype.status = function(enabled) { - if (this.disabled || enabled === false) { - return 'disabled'; - } - - if (this.markedPending) { - return 'pending'; - } - - if (this.result.failedExpectations.length > 0) { - return 'failed'; - } else { - return 'passed'; - } - }; - - Spec.prototype.isExecutable = function() { - return !this.disabled; - }; - - Spec.prototype.getFullName = function() { - return this.getSpecName(this); - }; - - var extractCustomPendingMessage = function(e) { - var fullMessage = e.toString(), - boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage), - boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length; - - return fullMessage.substr(boilerplateEnd); - }; - - Spec.pendingSpecExceptionMessage = '=> marked Pending'; - - Spec.isPendingSpecException = function(e) { - return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1); - }; - - return Spec; -}; - -if (typeof window == void 0 && typeof exports == 'object') { - exports.Spec = jasmineRequire.Spec; -} - -/*jshint bitwise: false*/ - -getJasmineRequireObj().Order = function() { - function Order(options) { - this.random = 'random' in options ? options.random : true; - var seed = this.seed = options.seed || generateSeed(); - this.sort = this.random ? randomOrder : naturalOrder; - - function naturalOrder(items) { - return items; - } - - function randomOrder(items) { - var copy = items.slice(); - copy.sort(function(a, b) { - return jenkinsHash(seed + a.id) - jenkinsHash(seed + b.id); - }); - return copy; - } - - function generateSeed() { - return String(Math.random()).slice(-5); - } - - // Bob Jenkins One-at-a-Time Hash algorithm is a non-cryptographic hash function - // used to get a different output when the key changes slighly. - // We use your return to sort the children randomly in a consistent way when - // used in conjunction with a seed - - function jenkinsHash(key) { - var hash, i; - for(hash = i = 0; i < key.length; ++i) { - hash += key.charCodeAt(i); - hash += (hash << 10); - hash ^= (hash >> 6); - } - hash += (hash << 3); - hash ^= (hash >> 11); - hash += (hash << 15); - return hash; - } - - } - - return Order; -}; - -getJasmineRequireObj().Env = function(j$) { - /** - * _Note:_ Do not construct this directly, Jasmine will make one during booting. - * @name Env - * @classdesc The Jasmine environment - * @constructor - */ - function Env(options) { - options = options || {}; - - var self = this; - var global = options.global || j$.getGlobal(); - - var totalSpecsDefined = 0; - - var catchExceptions = true; - - var realSetTimeout = j$.getGlobal().setTimeout; - var realClearTimeout = j$.getGlobal().clearTimeout; - var clearStack = j$.getClearStack(j$.getGlobal()); - this.clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global)); - - var runnableResources = {}; - - var currentSpec = null; - var currentlyExecutingSuites = []; - var currentDeclarationSuite = null; - var throwOnExpectationFailure = false; - var random = false; - var seed = null; - - var currentSuite = function() { - return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; - }; - - var currentRunnable = function() { - return currentSpec || currentSuite(); - }; - - var reporter = new j$.ReportDispatcher([ - 'jasmineStarted', - 'jasmineDone', - 'suiteStarted', - 'suiteDone', - 'specStarted', - 'specDone' - ]); - - var globalErrors = new j$.GlobalErrors(); - - this.specFilter = function() { - return true; - }; - - this.addCustomEqualityTester = function(tester) { - if(!currentRunnable()) { - throw new Error('Custom Equalities must be added in a before function or a spec'); - } - runnableResources[currentRunnable().id].customEqualityTesters.push(tester); - }; - - this.addMatchers = function(matchersToAdd) { - if(!currentRunnable()) { - throw new Error('Matchers must be added in a before function or a spec'); - } - var customMatchers = runnableResources[currentRunnable().id].customMatchers; - for (var matcherName in matchersToAdd) { - customMatchers[matcherName] = matchersToAdd[matcherName]; - } - }; - - j$.Expectation.addCoreMatchers(j$.matchers); - - var nextSpecId = 0; - var getNextSpecId = function() { - return 'spec' + nextSpecId++; - }; - - var nextSuiteId = 0; - var getNextSuiteId = function() { - return 'suite' + nextSuiteId++; - }; - - var expectationFactory = function(actual, spec) { - return j$.Expectation.Factory({ - util: j$.matchersUtil, - customEqualityTesters: runnableResources[spec.id].customEqualityTesters, - customMatchers: runnableResources[spec.id].customMatchers, - actual: actual, - addExpectationResult: addExpectationResult - }); - - function addExpectationResult(passed, result) { - return spec.addExpectationResult(passed, result); - } - }; - - var defaultResourcesForRunnable = function(id, parentRunnableId) { - var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; - - if(runnableResources[parentRunnableId]){ - resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); - resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers); - } - - runnableResources[id] = resources; - }; - - var clearResourcesForRunnable = function(id) { - spyRegistry.clearSpies(); - delete runnableResources[id]; - }; - - var beforeAndAfterFns = function(suite) { - return function() { - var befores = [], - afters = []; - - while(suite) { - befores = befores.concat(suite.beforeFns); - afters = afters.concat(suite.afterFns); - - suite = suite.parentSuite; - } - - return { - befores: befores.reverse(), - afters: afters - }; - }; - }; - - var getSpecName = function(spec, suite) { - var fullName = [spec.description], - suiteFullName = suite.getFullName(); - - if (suiteFullName !== '') { - fullName.unshift(suiteFullName); - } - return fullName.join(' '); - }; - - // TODO: we may just be able to pass in the fn instead of wrapping here - var buildExpectationResult = j$.buildExpectationResult, - exceptionFormatter = new j$.ExceptionFormatter(), - expectationResultFactory = function(attrs) { - attrs.messageFormatter = exceptionFormatter.message; - attrs.stackFormatter = exceptionFormatter.stack; - - return buildExpectationResult(attrs); - }; - - // TODO: fix this naming, and here's where the value comes in - this.catchExceptions = function(value) { - catchExceptions = !!value; - return catchExceptions; - }; - - this.catchingExceptions = function() { - return catchExceptions; - }; - - var maximumSpecCallbackDepth = 20; - var currentSpecCallbackDepth = 0; - - var catchException = function(e) { - return j$.Spec.isPendingSpecException(e) || catchExceptions; - }; - - this.throwOnExpectationFailure = function(value) { - throwOnExpectationFailure = !!value; - }; - - this.throwingExpectationFailures = function() { - return throwOnExpectationFailure; - }; - - this.randomizeTests = function(value) { - random = !!value; - }; - - this.randomTests = function() { - return random; - }; - - this.seed = function(value) { - if (value) { - seed = value; - } - return seed; - }; - - var queueRunnerFactory = function(options) { - options.catchException = catchException; - options.clearStack = options.clearStack || clearStack; - options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; - options.fail = self.fail; - options.globalErrors = globalErrors; - - new j$.QueueRunner(options).execute(); - }; - - var topSuite = new j$.Suite({ - env: this, - id: getNextSuiteId(), - description: 'Jasmine__TopLevel__Suite', - expectationFactory: expectationFactory, - expectationResultFactory: expectationResultFactory - }); - defaultResourcesForRunnable(topSuite.id); - currentDeclarationSuite = topSuite; - - this.topSuite = function() { - return topSuite; - }; - - this.execute = function(runnablesToRun) { - if(!runnablesToRun) { - if (focusedRunnables.length) { - runnablesToRun = focusedRunnables; - } else { - runnablesToRun = [topSuite.id]; - } - } - - var order = new j$.Order({ - random: random, - seed: seed - }); - - var processor = new j$.TreeProcessor({ - tree: topSuite, - runnableIds: runnablesToRun, - queueRunnerFactory: queueRunnerFactory, - nodeStart: function(suite) { - currentlyExecutingSuites.push(suite); - defaultResourcesForRunnable(suite.id, suite.parentSuite.id); - reporter.suiteStarted(suite.result); - }, - nodeComplete: function(suite, result) { - if (!suite.markedPending) { - clearResourcesForRunnable(suite.id); - } - currentlyExecutingSuites.pop(); - reporter.suiteDone(result); - }, - orderChildren: function(node) { - return order.sort(node.children); - } - }); - - if(!processor.processTree().valid) { - throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times'); - } - - reporter.jasmineStarted({ - totalSpecsDefined: totalSpecsDefined - }); - - currentlyExecutingSuites.push(topSuite); - - globalErrors.install(); - processor.execute(function() { - clearResourcesForRunnable(topSuite.id); - currentlyExecutingSuites.pop(); - globalErrors.uninstall(); - - reporter.jasmineDone({ - order: order, - failedExpectations: topSuite.result.failedExpectations - }); - }); - }; - - /** - * Add a custom reporter to the Jasmine environment. - * @name Env#addReporter - * @function - * @see custom_reporter - */ - this.addReporter = function(reporterToAdd) { - reporter.addReporter(reporterToAdd); - }; - - this.provideFallbackReporter = function(reporterToAdd) { - reporter.provideFallbackReporter(reporterToAdd); - }; - - this.clearReporters = function() { - reporter.clearReporters(); - }; - - var spyRegistry = new j$.SpyRegistry({currentSpies: function() { - if(!currentRunnable()) { - throw new Error('Spies must be created in a before function or a spec'); - } - return runnableResources[currentRunnable().id].spies; - }}); - - this.allowRespy = function(allow){ - spyRegistry.allowRespy(allow); - }; - - this.spyOn = function() { - return spyRegistry.spyOn.apply(spyRegistry, arguments); - }; - - this.spyOnProperty = function() { - return spyRegistry.spyOnProperty.apply(spyRegistry, arguments); - }; - - var ensureIsFunction = function(fn, caller) { - if (!j$.isFunction_(fn)) { - throw new Error(caller + ' expects a function argument; received ' + j$.getType_(fn)); - } - }; - - var suiteFactory = function(description) { - var suite = new j$.Suite({ - env: self, - id: getNextSuiteId(), - description: description, - parentSuite: currentDeclarationSuite, - expectationFactory: expectationFactory, - expectationResultFactory: expectationResultFactory, - throwOnExpectationFailure: throwOnExpectationFailure - }); - - return suite; - }; - - this.describe = function(description, specDefinitions) { - ensureIsFunction(specDefinitions, 'describe'); - var suite = suiteFactory(description); - if (specDefinitions.length > 0) { - throw new Error('describe does not expect any arguments'); - } - if (currentDeclarationSuite.markedPending) { - suite.pend(); - } - addSpecsToSuite(suite, specDefinitions); - return suite; - }; - - this.xdescribe = function(description, specDefinitions) { - ensureIsFunction(specDefinitions, 'xdescribe'); - var suite = suiteFactory(description); - suite.pend(); - addSpecsToSuite(suite, specDefinitions); - return suite; - }; - - var focusedRunnables = []; - - this.fdescribe = function(description, specDefinitions) { - ensureIsFunction(specDefinitions, 'fdescribe'); - var suite = suiteFactory(description); - suite.isFocused = true; - - focusedRunnables.push(suite.id); - unfocusAncestor(); - addSpecsToSuite(suite, specDefinitions); - - return suite; - }; - - function addSpecsToSuite(suite, specDefinitions) { - var parentSuite = currentDeclarationSuite; - parentSuite.addChild(suite); - currentDeclarationSuite = suite; - - var declarationError = null; - try { - specDefinitions.call(suite); - } catch (e) { - declarationError = e; - } - - if (declarationError) { - self.it('encountered a declaration exception', function() { - throw declarationError; - }); - } - - currentDeclarationSuite = parentSuite; - } - - function findFocusedAncestor(suite) { - while (suite) { - if (suite.isFocused) { - return suite.id; - } - suite = suite.parentSuite; - } - - return null; - } - - function unfocusAncestor() { - var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); - if (focusedAncestor) { - for (var i = 0; i < focusedRunnables.length; i++) { - if (focusedRunnables[i] === focusedAncestor) { - focusedRunnables.splice(i, 1); - break; - } - } - } - } - - var specFactory = function(description, fn, suite, timeout) { - totalSpecsDefined++; - var spec = new j$.Spec({ - id: getNextSpecId(), - beforeAndAfterFns: beforeAndAfterFns(suite), - expectationFactory: expectationFactory, - resultCallback: specResultCallback, - getSpecName: function(spec) { - return getSpecName(spec, suite); - }, - onStart: specStarted, - description: description, - expectationResultFactory: expectationResultFactory, - queueRunnerFactory: queueRunnerFactory, - userContext: function() { return suite.clonedSharedUserContext(); }, - queueableFn: { - fn: fn, - timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } - }, - throwOnExpectationFailure: throwOnExpectationFailure - }); - - if (!self.specFilter(spec)) { - spec.disable(); - } - - return spec; - - function specResultCallback(result) { - clearResourcesForRunnable(spec.id); - currentSpec = null; - reporter.specDone(result); - } - - function specStarted(spec) { - currentSpec = spec; - defaultResourcesForRunnable(spec.id, suite.id); - reporter.specStarted(spec.result); - } - }; - - this.it = function(description, fn, timeout) { - // it() sometimes doesn't have a fn argument, so only check the type if - // it's given. - if (arguments.length > 1) { - ensureIsFunction(fn, 'it'); - } - var spec = specFactory(description, fn, currentDeclarationSuite, timeout); - if (currentDeclarationSuite.markedPending) { - spec.pend(); - } - currentDeclarationSuite.addChild(spec); - return spec; - }; - - this.xit = function(description, fn, timeout) { - // xit(), like it(), doesn't always have a fn argument, so only check the - // type when needed. - if (arguments.length > 1) { - ensureIsFunction(fn, 'xit'); - } - var spec = this.it.apply(this, arguments); - spec.pend('Temporarily disabled with xit'); - return spec; - }; - - this.fit = function(description, fn, timeout){ - ensureIsFunction(fn, 'fit'); - var spec = specFactory(description, fn, currentDeclarationSuite, timeout); - currentDeclarationSuite.addChild(spec); - focusedRunnables.push(spec.id); - unfocusAncestor(); - return spec; - }; - - this.expect = function(actual) { - if (!currentRunnable()) { - throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out'); - } - - return currentRunnable().expect(actual); - }; - - this.beforeEach = function(beforeEachFunction, timeout) { - ensureIsFunction(beforeEachFunction, 'beforeEach'); - currentDeclarationSuite.beforeEach({ - fn: beforeEachFunction, - timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } - }); - }; - - this.beforeAll = function(beforeAllFunction, timeout) { - ensureIsFunction(beforeAllFunction, 'beforeAll'); - currentDeclarationSuite.beforeAll({ - fn: beforeAllFunction, - timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } - }); - }; - - this.afterEach = function(afterEachFunction, timeout) { - ensureIsFunction(afterEachFunction, 'afterEach'); - currentDeclarationSuite.afterEach({ - fn: afterEachFunction, - timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } - }); - }; - - this.afterAll = function(afterAllFunction, timeout) { - ensureIsFunction(afterAllFunction, 'afterAll'); - currentDeclarationSuite.afterAll({ - fn: afterAllFunction, - timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } - }); - }; - - this.pending = function(message) { - var fullMessage = j$.Spec.pendingSpecExceptionMessage; - if(message) { - fullMessage += message; - } - throw fullMessage; - }; - - this.fail = function(error) { - if (!currentRunnable()) { - throw new Error('\'fail\' was used when there was no current spec, this could be because an asynchronous test timed out'); - } - - var message = 'Failed'; - if (error) { - message += ': '; - if (error.message) { - message += error.message; - } else if (jasmine.isString_(error)) { - message += error; - } else { - // pretty print all kind of objects. This includes arrays. - message += jasmine.pp(error); - } - } - - currentRunnable().addExpectationResult(false, { - matcherName: '', - passed: false, - expected: '', - actual: '', - message: message, - error: error && error.message ? error : null - }); - }; - } - - return Env; -}; - -getJasmineRequireObj().JsApiReporter = function() { - - var noopTimer = { - start: function(){}, - elapsed: function(){ return 0; } - }; - - /** - * _Note:_ Do not construct this directly, use the global `jsApiReporter` to retrieve the instantiated object. - * - * @name jsApiReporter - * @classdesc Reporter added by default in `boot.js` to record results for retrieval in javascript code. - * @class - */ - function JsApiReporter(options) { - var timer = options.timer || noopTimer, - status = 'loaded'; - - this.started = false; - this.finished = false; - this.runDetails = {}; - - this.jasmineStarted = function() { - this.started = true; - status = 'started'; - timer.start(); - }; - - var executionTime; - - this.jasmineDone = function(runDetails) { - this.finished = true; - this.runDetails = runDetails; - executionTime = timer.elapsed(); - status = 'done'; - }; - - /** - * Get the current status for the Jasmine environment. - * @name jsApiReporter#status - * @function - * @return {String} - One of `loaded`, `started`, or `done` - */ - this.status = function() { - return status; - }; - - var suites = [], - suites_hash = {}; - - this.suiteStarted = function(result) { - suites_hash[result.id] = result; - }; - - this.suiteDone = function(result) { - storeSuite(result); - }; - - /** - * Get the results for a set of suites. - * - * Retrievable in slices for easier serialization. - * @name jsApiReporter#suiteResults - * @function - * @param {Number} index - The position in the suites list to start from. - * @param {Number} length - Maximum number of suite results to return. - * @return {Object[]} - */ - this.suiteResults = function(index, length) { - return suites.slice(index, index + length); - }; - - function storeSuite(result) { - suites.push(result); - suites_hash[result.id] = result; - } - - /** - * Get all of the suites in a single object, with their `id` as the key. - * @name jsApiReporter#suites - * @function - * @return {Object} - */ - this.suites = function() { - return suites_hash; - }; - - var specs = []; - - this.specDone = function(result) { - specs.push(result); - }; - - /** - * Get the results for a set of specs. - * - * Retrievable in slices for easier serialization. - * @name jsApiReporter#specResults - * @function - * @param {Number} index - The position in the specs list to start from. - * @param {Number} length - Maximum number of specs results to return. - * @return {Object[]} - */ - this.specResults = function(index, length) { - return specs.slice(index, index + length); - }; - - /** - * Get all spec results. - * @name jsApiReporter#specs - * @function - * @return {Object[]} - */ - this.specs = function() { - return specs; - }; - - /** - * Get the number of milliseconds it took for the full Jasmine suite to run. - * @name jsApiReporter#executionTime - * @function - * @return {Number} - */ - this.executionTime = function() { - return executionTime; - }; - - } - - return JsApiReporter; -}; - -getJasmineRequireObj().Any = function(j$) { - - function Any(expectedObject) { - if (typeof expectedObject === 'undefined') { - throw new TypeError( - 'jasmine.any() expects to be passed a constructor function. ' + - 'Please pass one or use jasmine.anything() to match any object.' - ); - } - this.expectedObject = expectedObject; - } - - Any.prototype.asymmetricMatch = function(other) { - if (this.expectedObject == String) { - return typeof other == 'string' || other instanceof String; - } - - if (this.expectedObject == Number) { - return typeof other == 'number' || other instanceof Number; - } - - if (this.expectedObject == Function) { - return typeof other == 'function' || other instanceof Function; - } - - if (this.expectedObject == Object) { - return typeof other == 'object'; - } - - if (this.expectedObject == Boolean) { - return typeof other == 'boolean'; - } - - return other instanceof this.expectedObject; - }; - - Any.prototype.jasmineToString = function() { - return ''; - }; - - return Any; -}; - -getJasmineRequireObj().Anything = function(j$) { - - function Anything() {} - - Anything.prototype.asymmetricMatch = function(other) { - return !j$.util.isUndefined(other) && other !== null; - }; - - Anything.prototype.jasmineToString = function() { - return ''; - }; - - return Anything; -}; - -getJasmineRequireObj().ArrayContaining = function(j$) { - function ArrayContaining(sample) { - this.sample = sample; - } - - ArrayContaining.prototype.asymmetricMatch = function(other, customTesters) { - var className = Object.prototype.toString.call(this.sample); - if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not \'' + this.sample + '\'.'); } - - for (var i = 0; i < this.sample.length; i++) { - var item = this.sample[i]; - if (!j$.matchersUtil.contains(other, item, customTesters)) { - return false; - } - } - - return true; - }; - - ArrayContaining.prototype.jasmineToString = function () { - return ''; - }; - - return ArrayContaining; -}; - -getJasmineRequireObj().ObjectContaining = function(j$) { - - function ObjectContaining(sample) { - this.sample = sample; - } - - function getPrototype(obj) { - if (Object.getPrototypeOf) { - return Object.getPrototypeOf(obj); - } - - if (obj.constructor.prototype == obj) { - return null; - } - - return obj.constructor.prototype; - } - - function hasProperty(obj, property) { - if (!obj) { - return false; - } - - if (Object.prototype.hasOwnProperty.call(obj, property)) { - return true; - } - - return hasProperty(getPrototype(obj), property); - } - - ObjectContaining.prototype.asymmetricMatch = function(other, customTesters) { - if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } - - for (var property in this.sample) { - if (!hasProperty(other, property) || - !j$.matchersUtil.equals(this.sample[property], other[property], customTesters)) { - return false; - } - } - - return true; - }; - - ObjectContaining.prototype.jasmineToString = function() { - return ''; - }; - - return ObjectContaining; -}; - -getJasmineRequireObj().StringMatching = function(j$) { - - function StringMatching(expected) { - if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { - throw new Error('Expected is not a String or a RegExp'); - } - - this.regexp = new RegExp(expected); - } - - StringMatching.prototype.asymmetricMatch = function(other) { - return this.regexp.test(other); - }; - - StringMatching.prototype.jasmineToString = function() { - return ''; - }; - - return StringMatching; -}; - -getJasmineRequireObj().CallTracker = function(j$) { - - /** - * @namespace Spy#calls - */ - function CallTracker() { - var calls = []; - var opts = {}; - - function argCloner(context) { - var clonedArgs = []; - var argsAsArray = j$.util.argsToArray(context.args); - for(var i = 0; i < argsAsArray.length; i++) { - if(Object.prototype.toString.apply(argsAsArray[i]).match(/^\[object/)) { - clonedArgs.push(j$.util.clone(argsAsArray[i])); - } else { - clonedArgs.push(argsAsArray[i]); - } - } - context.args = clonedArgs; - } - - this.track = function(context) { - if(opts.cloneArgs) { - argCloner(context); - } - calls.push(context); - }; - - /** - * Check whether this spy has been invoked. - * @name Spy#calls#any - * @function - * @return {Boolean} - */ - this.any = function() { - return !!calls.length; - }; - - /** - * Get the number of invocations of this spy. - * @name Spy#calls#count - * @function - * @return {Integer} - */ - this.count = function() { - return calls.length; - }; - - /** - * Get the arguments that were passed to a specific invocation of this spy. - * @name Spy#calls#argsFor - * @function - * @param {Integer} index The 0-based invocation index. - * @return {Array} - */ - this.argsFor = function(index) { - var call = calls[index]; - return call ? call.args : []; - }; - - /** - * Get the raw calls array for this spy. - * @name Spy#calls#all - * @function - * @return {Spy.callData[]} - */ - this.all = function() { - return calls; - }; - - /** - * Get all of the arguments for each invocation of this spy in the order they were received. - * @name Spy#calls#allArgs - * @function - * @return {Array} - */ - this.allArgs = function() { - var callArgs = []; - for(var i = 0; i < calls.length; i++){ - callArgs.push(calls[i].args); - } - - return callArgs; - }; - - /** - * Get the first invocation of this spy. - * @name Spy#calls#first - * @function - * @return {ObjecSpy.callData} - */ - this.first = function() { - return calls[0]; - }; - - /** - * Get the most recent invocation of this spy. - * @name Spy#calls#mostRecent - * @function - * @return {ObjecSpy.callData} - */ - this.mostRecent = function() { - return calls[calls.length - 1]; - }; - - /** - * Reset this spy as if it has never been called. - * @name Spy#calls#reset - * @function - */ - this.reset = function() { - calls = []; - }; - - /** - * Set this spy to do a shallow clone of arguments passed to each invocation. - * @name Spy#calls#saveArgumentsByValue - * @function - */ - this.saveArgumentsByValue = function() { - opts.cloneArgs = true; - }; - - } - - return CallTracker; -}; - -getJasmineRequireObj().clearStack = function(j$) { - function messageChannelImpl(global) { - var channel = new global.MessageChannel(), - head = {}, - tail = head; - - channel.port1.onmessage = function() { - head = head.next; - var task = head.task; - delete head.task; - task(); - }; - - return function clearStack(fn) { - tail = tail.next = { task: fn }; - channel.port2.postMessage(0); - }; - } - - function getClearStack(global) { - if (global && global.process && j$.isFunction_(global.process.nextTick)) { - return global.process.nextTick; - } else if (j$.isFunction_(global.setImmediate)) { - var realSetImmediate = global.setImmediate; - return function(fn) { - realSetImmediate(fn); - }; - } else if (!j$.util.isUndefined(global.MessageChannel)) { - return messageChannelImpl(global); - } else { - var realSetTimeout = global.setTimeout; - return function clearStack(fn) { - Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]); - }; - } - } - - return getClearStack; -}; - -getJasmineRequireObj().Clock = function() { - /** - * _Note:_ Do not construct this directly, Jasmine will make one during booting. You can get the current clock with {@link jasmine.clock}. - * @class Clock - * @classdesc Jasmine's mock clock is used when testing time dependent code. - */ - function Clock(global, delayedFunctionSchedulerFactory, mockDate) { - var self = this, - realTimingFunctions = { - setTimeout: global.setTimeout, - clearTimeout: global.clearTimeout, - setInterval: global.setInterval, - clearInterval: global.clearInterval - }, - fakeTimingFunctions = { - setTimeout: setTimeout, - clearTimeout: clearTimeout, - setInterval: setInterval, - clearInterval: clearInterval - }, - installed = false, - delayedFunctionScheduler, - timer; - - - /** - * Install the mock clock over the built-in methods. - * @name Clock#install - * @function - * @return {Clock} - */ - self.install = function() { - if(!originalTimingFunctionsIntact()) { - throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?'); - } - replace(global, fakeTimingFunctions); - timer = fakeTimingFunctions; - delayedFunctionScheduler = delayedFunctionSchedulerFactory(); - installed = true; - - return self; - }; - - /** - * Uninstall the mock clock, returning the built-in methods to their places. - * @name Clock#uninstall - * @function - */ - self.uninstall = function() { - delayedFunctionScheduler = null; - mockDate.uninstall(); - replace(global, realTimingFunctions); - - timer = realTimingFunctions; - installed = false; - }; - - /** - * Execute a function with a mocked Clock - * - * The clock will be {@link Clock#install|install}ed before the function is called and {@link Clock#uninstall|uninstall}ed in a `finally` after the function completes. - * @name Clock#withMock - * @function - * @param {closure} Function The function to be called. - */ - self.withMock = function(closure) { - this.install(); - try { - closure(); - } finally { - this.uninstall(); - } - }; - - /** - * Instruct the installed Clock to also mock the date returned by `new Date()` - * @name Clock#mockDate - * @function - * @param {Date} [initialDate=now] The `Date` to provide. - */ - self.mockDate = function(initialDate) { - mockDate.install(initialDate); - }; - - self.setTimeout = function(fn, delay, params) { - if (legacyIE()) { - if (arguments.length > 2) { - throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); - } - return timer.setTimeout(fn, delay); - } - return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); - }; - - self.setInterval = function(fn, delay, params) { - if (legacyIE()) { - if (arguments.length > 2) { - throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); - } - return timer.setInterval(fn, delay); - } - return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); - }; - - self.clearTimeout = function(id) { - return Function.prototype.call.apply(timer.clearTimeout, [global, id]); - }; - - self.clearInterval = function(id) { - return Function.prototype.call.apply(timer.clearInterval, [global, id]); - }; - - /** - * Tick the Clock forward, running any enqueued timeouts along the way - * @name Clock#tick - * @function - * @param {int} millis The number of milliseconds to tick. - */ - self.tick = function(millis) { - if (installed) { - delayedFunctionScheduler.tick(millis, function(millis) { mockDate.tick(millis); }); - } else { - throw new Error('Mock clock is not installed, use jasmine.clock().install()'); - } - }; - - return self; - - function originalTimingFunctionsIntact() { - return global.setTimeout === realTimingFunctions.setTimeout && - global.clearTimeout === realTimingFunctions.clearTimeout && - global.setInterval === realTimingFunctions.setInterval && - global.clearInterval === realTimingFunctions.clearInterval; - } - - function legacyIE() { - //if these methods are polyfilled, apply will be present - return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; - } - - function replace(dest, source) { - for (var prop in source) { - dest[prop] = source[prop]; - } - } - - function setTimeout(fn, delay) { - return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2)); - } - - function clearTimeout(id) { - return delayedFunctionScheduler.removeFunctionWithId(id); - } - - function setInterval(fn, interval) { - return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); - } - - function clearInterval(id) { - return delayedFunctionScheduler.removeFunctionWithId(id); - } - - function argSlice(argsObj, n) { - return Array.prototype.slice.call(argsObj, n); - } - } - - return Clock; -}; - -getJasmineRequireObj().DelayedFunctionScheduler = function() { - function DelayedFunctionScheduler() { - var self = this; - var scheduledLookup = []; - var scheduledFunctions = {}; - var currentTime = 0; - var delayedFnCount = 0; - - self.tick = function(millis, tickDate) { - millis = millis || 0; - var endTime = currentTime + millis; - - runScheduledFunctions(endTime, tickDate); - currentTime = endTime; - }; - - self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { - var f; - if (typeof(funcToCall) === 'string') { - /* jshint evil: true */ - f = function() { return eval(funcToCall); }; - /* jshint evil: false */ - } else { - f = funcToCall; - } - - millis = millis || 0; - timeoutKey = timeoutKey || ++delayedFnCount; - runAtMillis = runAtMillis || (currentTime + millis); - - var funcToSchedule = { - runAtMillis: runAtMillis, - funcToCall: f, - recurring: recurring, - params: params, - timeoutKey: timeoutKey, - millis: millis - }; - - if (runAtMillis in scheduledFunctions) { - scheduledFunctions[runAtMillis].push(funcToSchedule); - } else { - scheduledFunctions[runAtMillis] = [funcToSchedule]; - scheduledLookup.push(runAtMillis); - scheduledLookup.sort(function (a, b) { - return a - b; - }); - } - - return timeoutKey; - }; - - self.removeFunctionWithId = function(timeoutKey) { - for (var runAtMillis in scheduledFunctions) { - var funcs = scheduledFunctions[runAtMillis]; - var i = indexOfFirstToPass(funcs, function (func) { - return func.timeoutKey === timeoutKey; - }); - - if (i > -1) { - if (funcs.length === 1) { - delete scheduledFunctions[runAtMillis]; - deleteFromLookup(runAtMillis); - } else { - funcs.splice(i, 1); - } - - // intervals get rescheduled when executed, so there's never more - // than a single scheduled function with a given timeoutKey - break; - } - } - }; - - return self; - - function indexOfFirstToPass(array, testFn) { - var index = -1; - - for (var i = 0; i < array.length; ++i) { - if (testFn(array[i])) { - index = i; - break; - } - } - - return index; - } - - function deleteFromLookup(key) { - var value = Number(key); - var i = indexOfFirstToPass(scheduledLookup, function (millis) { - return millis === value; - }); - - if (i > -1) { - scheduledLookup.splice(i, 1); - } - } - - function reschedule(scheduledFn) { - self.scheduleFunction(scheduledFn.funcToCall, - scheduledFn.millis, - scheduledFn.params, - true, - scheduledFn.timeoutKey, - scheduledFn.runAtMillis + scheduledFn.millis); - } - - function forEachFunction(funcsToRun, callback) { - for (var i = 0; i < funcsToRun.length; ++i) { - callback(funcsToRun[i]); - } - } - - function runScheduledFunctions(endTime, tickDate) { - tickDate = tickDate || function() {}; - if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { - tickDate(endTime - currentTime); - return; - } - - do { - var newCurrentTime = scheduledLookup.shift(); - tickDate(newCurrentTime - currentTime); - - currentTime = newCurrentTime; - - var funcsToRun = scheduledFunctions[currentTime]; - delete scheduledFunctions[currentTime]; - - forEachFunction(funcsToRun, function(funcToRun) { - if (funcToRun.recurring) { - reschedule(funcToRun); - } - }); - - forEachFunction(funcsToRun, function(funcToRun) { - funcToRun.funcToCall.apply(null, funcToRun.params || []); - }); - } while (scheduledLookup.length > 0 && - // checking first if we're out of time prevents setTimeout(0) - // scheduled in a funcToRun from forcing an extra iteration - currentTime !== endTime && - scheduledLookup[0] <= endTime); - - // ran out of functions to call, but still time left on the clock - if (currentTime !== endTime) { - tickDate(endTime - currentTime); - } - } - } - - return DelayedFunctionScheduler; -}; - -getJasmineRequireObj().errors = function() { - function ExpectationFailed() {} - - ExpectationFailed.prototype = new Error(); - ExpectationFailed.prototype.constructor = ExpectationFailed; - - return { - ExpectationFailed: ExpectationFailed - }; -}; -getJasmineRequireObj().ExceptionFormatter = function() { - function ExceptionFormatter() { - this.message = function(error) { - var message = ''; - - if (error.name && error.message) { - message += error.name + ': ' + error.message; - } else { - message += error.toString() + ' thrown'; - } - - if (error.fileName || error.sourceURL) { - message += ' in ' + (error.fileName || error.sourceURL); - } - - if (error.line || error.lineNumber) { - message += ' (line ' + (error.line || error.lineNumber) + ')'; - } - - return message; - }; - - this.stack = function(error) { - return error ? error.stack : null; - }; - } - - return ExceptionFormatter; -}; - -getJasmineRequireObj().Expectation = function() { - - /** - * Matchers that come with Jasmine out of the box. - * @namespace matchers - */ - function Expectation(options) { - this.util = options.util || { buildFailureMessage: function() {} }; - this.customEqualityTesters = options.customEqualityTesters || []; - this.actual = options.actual; - this.addExpectationResult = options.addExpectationResult || function(){}; - this.isNot = options.isNot; - - var customMatchers = options.customMatchers || {}; - for (var matcherName in customMatchers) { - this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]); - } - } - - Expectation.prototype.wrapCompare = function(name, matcherFactory) { - return function() { - var args = Array.prototype.slice.call(arguments, 0), - expected = args.slice(0), - message = ''; - - args.unshift(this.actual); - - var matcher = matcherFactory(this.util, this.customEqualityTesters), - matcherCompare = matcher.compare; - - function defaultNegativeCompare() { - var result = matcher.compare.apply(null, args); - result.pass = !result.pass; - return result; - } - - if (this.isNot) { - matcherCompare = matcher.negativeCompare || defaultNegativeCompare; - } - - var result = matcherCompare.apply(null, args); - - if (!result.pass) { - if (!result.message) { - args.unshift(this.isNot); - args.unshift(name); - message = this.util.buildFailureMessage.apply(null, args); - } else { - if (Object.prototype.toString.apply(result.message) === '[object Function]') { - message = result.message(); - } else { - message = result.message; - } - } - } - - if (expected.length == 1) { - expected = expected[0]; - } - - // TODO: how many of these params are needed? - this.addExpectationResult( - result.pass, - { - matcherName: name, - passed: result.pass, - message: message, - error: result.error, - actual: this.actual, - expected: expected // TODO: this may need to be arrayified/sliced - } - ); - }; - }; - - Expectation.addCoreMatchers = function(matchers) { - var prototype = Expectation.prototype; - for (var matcherName in matchers) { - var matcher = matchers[matcherName]; - prototype[matcherName] = prototype.wrapCompare(matcherName, matcher); - } - }; - - Expectation.Factory = function(options) { - options = options || {}; - - var expect = new Expectation(options); - - // TODO: this would be nice as its own Object - NegativeExpectation - // TODO: copy instead of mutate options - options.isNot = true; - expect.not = new Expectation(options); - - return expect; - }; - - return Expectation; -}; - -//TODO: expectation result may make more sense as a presentation of an expectation. -getJasmineRequireObj().buildExpectationResult = function() { - function buildExpectationResult(options) { - var messageFormatter = options.messageFormatter || function() {}, - stackFormatter = options.stackFormatter || function() {}; - - var result = { - matcherName: options.matcherName, - message: message(), - stack: stack(), - passed: options.passed - }; - - if(!result.passed) { - result.expected = options.expected; - result.actual = options.actual; - } - - return result; - - function message() { - if (options.passed) { - return 'Passed.'; - } else if (options.message) { - return options.message; - } else if (options.error) { - return messageFormatter(options.error); - } - return ''; - } - - function stack() { - if (options.passed) { - return ''; - } - - var error = options.error; - if (!error) { - try { - throw new Error(message()); - } catch (e) { - error = e; - } - } - return stackFormatter(error); - } - } - - return buildExpectationResult; -}; - -getJasmineRequireObj().formatErrorMsg = function() { - function generateErrorMsg(domain, usage) { - var usageDefinition = usage ? '\nUsage: ' + usage : ''; - - return function errorMsg(msg) { - return domain + ' : ' + msg + usageDefinition; - }; - } - - return generateErrorMsg; -}; - -getJasmineRequireObj().GlobalErrors = function(j$) { - function GlobalErrors(global) { - var handlers = []; - global = global || j$.getGlobal(); - - var onerror = function onerror() { - var handler = handlers[handlers.length - 1]; - handler.apply(null, Array.prototype.slice.call(arguments, 0)); - }; - - this.uninstall = function noop() {}; - - this.install = function install() { - if (global.process && j$.isFunction_(global.process.on)) { - var originalHandlers = global.process.listeners('uncaughtException'); - global.process.removeAllListeners('uncaughtException'); - global.process.on('uncaughtException', onerror); - - this.uninstall = function uninstall() { - global.process.removeListener('uncaughtException', onerror); - for (var i = 0; i < originalHandlers.length; i++) { - global.process.on('uncaughtException', originalHandlers[i]); - } - }; - } else { - var originalHandler = global.onerror; - global.onerror = onerror; - - this.uninstall = function uninstall() { - global.onerror = originalHandler; - }; - } - }; - - this.pushListener = function pushListener(listener) { - handlers.push(listener); - }; - - this.popListener = function popListener() { - handlers.pop(); - }; - } - - return GlobalErrors; -}; - -getJasmineRequireObj().DiffBuilder = function(j$) { - return function DiffBuilder() { - var path = new j$.ObjectPath(), - mismatches = []; - - return { - record: function (actual, expected, formatter) { - formatter = formatter || defaultFormatter; - mismatches.push(formatter(actual, expected, path)); - }, - - getMessage: function () { - return mismatches.join('\n'); - }, - - withPath: function (pathComponent, block) { - var oldPath = path; - path = path.add(pathComponent); - block(); - path = oldPath; - } - }; - - function defaultFormatter (actual, expected, path) { - return 'Expected ' + - path + (path.depth() ? ' = ' : '') + - j$.pp(actual) + - ' to equal ' + - j$.pp(expected) + - '.'; - } - }; -}; - -getJasmineRequireObj().matchersUtil = function(j$) { - // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? - - return { - equals: equals, - - contains: function(haystack, needle, customTesters) { - customTesters = customTesters || []; - - if ((Object.prototype.toString.apply(haystack) === '[object Set]')) { - return haystack.has(needle); - } - - if ((Object.prototype.toString.apply(haystack) === '[object Array]') || - (!!haystack && !haystack.indexOf)) - { - for (var i = 0; i < haystack.length; i++) { - if (equals(haystack[i], needle, customTesters)) { - return true; - } - } - return false; - } - - return !!haystack && haystack.indexOf(needle) >= 0; - }, - - buildFailureMessage: function() { - var args = Array.prototype.slice.call(arguments, 0), - matcherName = args[0], - isNot = args[1], - actual = args[2], - expected = args.slice(3), - englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); - - var message = 'Expected ' + - j$.pp(actual) + - (isNot ? ' not ' : ' ') + - englishyPredicate; - - if (expected.length > 0) { - for (var i = 0; i < expected.length; i++) { - if (i > 0) { - message += ','; - } - message += ' ' + j$.pp(expected[i]); - } - } - - return message + '.'; - } - }; - - function isAsymmetric(obj) { - return obj && j$.isA_('Function', obj.asymmetricMatch); - } - - function asymmetricMatch(a, b, customTesters, diffBuilder) { - var asymmetricA = isAsymmetric(a), - asymmetricB = isAsymmetric(b), - result; - - if (asymmetricA && asymmetricB) { - return undefined; - } - - if (asymmetricA) { - result = a.asymmetricMatch(b, customTesters); - diffBuilder.record(a, b); - return result; - } - - if (asymmetricB) { - result = b.asymmetricMatch(a, customTesters); - diffBuilder.record(a, b); - return result; - } - } - - function equals(a, b, customTesters, diffBuilder) { - customTesters = customTesters || []; - diffBuilder = diffBuilder || j$.NullDiffBuilder(); - - return eq(a, b, [], [], customTesters, diffBuilder); - } - - // Equality function lovingly adapted from isEqual in - // [Underscore](http://underscorejs.org) - function eq(a, b, aStack, bStack, customTesters, diffBuilder) { - var result = true, i; - - var asymmetricResult = asymmetricMatch(a, b, customTesters, diffBuilder); - if (!j$.util.isUndefined(asymmetricResult)) { - return asymmetricResult; - } - - for (i = 0; i < customTesters.length; i++) { - var customTesterResult = customTesters[i](a, b); - if (!j$.util.isUndefined(customTesterResult)) { - if (!customTesterResult) { - diffBuilder.record(a, b); - } - return customTesterResult; - } - } - - if (a instanceof Error && b instanceof Error) { - result = a.message == b.message; - if (!result) { - diffBuilder.record(a, b); - } - return result; - } - - // Identical objects are equal. `0 === -0`, but they aren't identical. - // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). - if (a === b) { - result = a !== 0 || 1 / a == 1 / b; - if (!result) { - diffBuilder.record(a, b); - } - return result; - } - // A strict comparison is necessary because `null == undefined`. - if (a === null || b === null) { - result = a === b; - if (!result) { - diffBuilder.record(a, b); - } - return result; - } - var className = Object.prototype.toString.call(a); - if (className != Object.prototype.toString.call(b)) { - diffBuilder.record(a, b); - return false; - } - switch (className) { - // Strings, numbers, dates, and booleans are compared by value. - case '[object String]': - // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is - // equivalent to `new String("5")`. - result = a == String(b); - if (!result) { - diffBuilder.record(a, b); - } - return result; - case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for - // other numeric values. - result = a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b); - if (!result) { - diffBuilder.record(a, b); - } - return result; - case '[object Date]': - case '[object Boolean]': - // Coerce dates and booleans to numeric primitive values. Dates are compared by their - // millisecond representations. Note that invalid dates with millisecond representations - // of `NaN` are not equivalent. - result = +a == +b; - if (!result) { - diffBuilder.record(a, b); - } - return result; - // RegExps are compared by their source patterns and flags. - case '[object RegExp]': - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; - } - if (typeof a != 'object' || typeof b != 'object') { - diffBuilder.record(a, b); - return false; - } - - var aIsDomNode = j$.isDomNode(a); - var bIsDomNode = j$.isDomNode(b); - if (aIsDomNode && bIsDomNode) { - // At first try to use DOM3 method isEqualNode - if (a.isEqualNode) { - result = a.isEqualNode(b); - if (!result) { - diffBuilder.record(a, b); - } - return result; - } - // IE8 doesn't support isEqualNode, try to use outerHTML && innerText - var aIsElement = a instanceof Element; - var bIsElement = b instanceof Element; - if (aIsElement && bIsElement) { - result = a.outerHTML == b.outerHTML; - if (!result) { - diffBuilder.record(a, b); - } - return result; - } - if (aIsElement || bIsElement) { - diffBuilder.record(a, b); - return false; - } - result = a.innerText == b.innerText && a.textContent == b.textContent; - if (!result) { - diffBuilder.record(a, b); - } - return result; - } - if (aIsDomNode || bIsDomNode) { - diffBuilder.record(a, b); - return false; - } - - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - var length = aStack.length; - while (length--) { - // Linear search. Performance is inversely proportional to the number of - // unique nested structures. - if (aStack[length] == a) { return bStack[length] == b; } - } - // Add the first object to the stack of traversed objects. - aStack.push(a); - bStack.push(b); - var size = 0; - // Recursively compare objects and arrays. - // Compare array lengths to determine if a deep comparison is necessary. - if (className == '[object Array]') { - size = a.length; - if (size !== b.length) { - diffBuilder.record(a, b); - return false; - } - - for (i = 0; i < size; i++) { - diffBuilder.withPath(i, function() { - result = eq(a[i], b[i], aStack, bStack, customTesters, diffBuilder) && result; - }); - } - if (!result) { - return false; - } - } else if (className == '[object Set]') { - if (a.size != b.size) { - diffBuilder.record(a, b); - return false; - } - var iterA = a.values(), iterB = b.values(); - var valA, valB; - do { - valA = iterA.next(); - valB = iterB.next(); - if (!eq(valA.value, valB.value, aStack, bStack, customTesters, j$.NullDiffBuilder())) { - diffBuilder.record(a, b); - return false; - } - } while (!valA.done && !valB.done); - } else { - - // Objects with different constructors are not equivalent, but `Object`s - // or `Array`s from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if (aCtor !== bCtor && - isFunction(aCtor) && isFunction(bCtor) && - a instanceof aCtor && b instanceof bCtor && - !(aCtor instanceof aCtor && bCtor instanceof bCtor)) { - - diffBuilder.record(a, b, constructorsAreDifferentFormatter); - return false; - } - } - - // Deep compare objects. - var aKeys = keys(a, className == '[object Array]'), key; - size = aKeys.length; - - // Ensure that both objects contain the same number of properties before comparing deep equality. - if (keys(b, className == '[object Array]').length !== size) { - diffBuilder.record(a, b, objectKeysAreDifferentFormatter); - return false; - } - - for (i = 0; i < size; i++) { - key = aKeys[i]; - // Deep compare each member - if (!j$.util.has(b, key)) { - diffBuilder.record(a, b, objectKeysAreDifferentFormatter); - result = false; - continue; - } - - diffBuilder.withPath(key, function() { - if(!eq(a[key], b[key], aStack, bStack, customTesters, diffBuilder)) { - result = false; - } - }); - } - - if (!result) { - return false; - } - - // Remove the first object from the stack of traversed objects. - aStack.pop(); - bStack.pop(); - - return result; - } - - function keys(obj, isArray) { - var allKeys = Object.keys ? Object.keys(obj) : - (function(o) { - var keys = []; - for (var key in o) { - if (j$.util.has(o, key)) { - keys.push(key); - } - } - return keys; - })(obj); - - if (!isArray) { - return allKeys; - } - - if (allKeys.length === 0) { - return allKeys; - } - - var extraKeys = []; - for (var i in allKeys) { - if (!allKeys[i].match(/^[0-9]+$/)) { - extraKeys.push(allKeys[i]); - } - } - - return extraKeys; - } - - function has(obj, key) { - return Object.prototype.hasOwnProperty.call(obj, key); - } - - function isFunction(obj) { - return typeof obj === 'function'; - } - - function objectKeysAreDifferentFormatter(actual, expected, path) { - var missingProperties = j$.util.objectDifference(expected, actual), - extraProperties = j$.util.objectDifference(actual, expected), - missingPropertiesMessage = formatKeyValuePairs(missingProperties), - extraPropertiesMessage = formatKeyValuePairs(extraProperties), - messages = []; - - if (!path.depth()) { - path = 'object'; - } - - if (missingPropertiesMessage.length) { - messages.push('Expected ' + path + ' to have properties' + missingPropertiesMessage); - } - - if (extraPropertiesMessage.length) { - messages.push('Expected ' + path + ' not to have properties' + extraPropertiesMessage); - } - - return messages.join('\n'); - } - - function constructorsAreDifferentFormatter(actual, expected, path) { - if (!path.depth()) { - path = 'object'; - } - - return 'Expected ' + - path + ' to be a kind of ' + - j$.fnNameFor(expected.constructor) + - ', but was ' + j$.pp(actual) + '.'; - } - - function formatKeyValuePairs(obj) { - var formatted = ''; - for (var key in obj) { - formatted += '\n ' + key + ': ' + j$.pp(obj[key]); - } - return formatted; - } -}; - -getJasmineRequireObj().NullDiffBuilder = function(j$) { - return function() { - return { - withPath: function(_, block) { - block(); - }, - record: function() {} - }; - }; -}; - -getJasmineRequireObj().ObjectPath = function(j$) { - function ObjectPath(components) { - this.components = components || []; - } - - ObjectPath.prototype.toString = function() { - if (this.components.length) { - return '$' + map(this.components, formatPropertyAccess).join(''); - } else { - return ''; - } - }; - - ObjectPath.prototype.add = function(component) { - return new ObjectPath(this.components.concat([component])); - }; - - ObjectPath.prototype.depth = function() { - return this.components.length; - }; - - function formatPropertyAccess(prop) { - if (typeof prop === 'number') { - return '[' + prop + ']'; - } - - if (isValidIdentifier(prop)) { - return '.' + prop; - } - - return '[\'' + prop + '\']'; - } - - function map(array, fn) { - var results = []; - for (var i = 0; i < array.length; i++) { - results.push(fn(array[i])); - } - return results; - } - - function isValidIdentifier(string) { - return /^[A-Za-z\$_][A-Za-z0-9\$_]*$/.test(string); - } - - return ObjectPath; -}; - -getJasmineRequireObj().toBe = function() { - /** - * {@link expect} the actual value to be `===` to the expected value. - * @function - * @name matchers#toBe - * @param {Object} expected - The expected value to compare against. - * @example - * expect(thing).toBe(realThing); - */ - function toBe() { - return { - compare: function(actual, expected) { - return { - pass: actual === expected - }; - } - }; - } - - return toBe; -}; - -getJasmineRequireObj().toBeCloseTo = function() { - /** - * {@link expect} the actual value to be within a specified precision of the expected value. - * @function - * @name matchers#toBeCloseTo - * @param {Object} expected - The expected value to compare against. - * @param {Number} [precision=2] - The number of decimal points to check. - * @example - * expect(number).toBeCloseTo(42.2, 3); - */ - function toBeCloseTo() { - return { - compare: function(actual, expected, precision) { - if (precision !== 0) { - precision = precision || 2; - } - - return { - pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2) - }; - } - }; - } - - return toBeCloseTo; -}; - -getJasmineRequireObj().toBeDefined = function() { - /** - * {@link expect} the actual value to be defined. (Not `undefined`) - * @function - * @name matchers#toBeDefined - * @example - * expect(result).toBeDefined(); - */ - function toBeDefined() { - return { - compare: function(actual) { - return { - pass: (void 0 !== actual) - }; - } - }; - } - - return toBeDefined; -}; - -getJasmineRequireObj().toBeFalsy = function() { - /** - * {@link expect} the actual value to be falsy - * @function - * @name matchers#toBeFalsy - * @example - * expect(result).toBeFalsy(); - */ - function toBeFalsy() { - return { - compare: function(actual) { - return { - pass: !!!actual - }; - } - }; - } - - return toBeFalsy; -}; - -getJasmineRequireObj().toBeGreaterThan = function() { - /** - * {@link expect} the actual value to be greater than the expected value. - * @function - * @name matchers#toBeGreaterThan - * @param {Number} expected - The value to compare against. - * @example - * expect(result).toBeGreaterThan(3); - */ - function toBeGreaterThan() { - return { - compare: function(actual, expected) { - return { - pass: actual > expected - }; - } - }; - } - - return toBeGreaterThan; -}; - - -getJasmineRequireObj().toBeGreaterThanOrEqual = function() { - /** - * {@link expect} the actual value to be greater than or equal to the expected value. - * @function - * @name matchers#toBeGreaterThanOrEqual - * @param {Number} expected - The expected value to compare against. - * @example - * expect(result).toBeGreaterThanOrEqual(25); - */ - function toBeGreaterThanOrEqual() { - return { - compare: function(actual, expected) { - return { - pass: actual >= expected - }; - } - }; - } - - return toBeGreaterThanOrEqual; -}; - -getJasmineRequireObj().toBeLessThan = function() { - /** - * {@link expect} the actual value to be less than the expected value. - * @function - * @name matchers#toBeLessThan - * @param {Number} expected - The expected value to compare against. - * @example - * expect(result).toBeLessThan(0); - */ - function toBeLessThan() { - return { - - compare: function(actual, expected) { - return { - pass: actual < expected - }; - } - }; - } - - return toBeLessThan; -}; - -getJasmineRequireObj().toBeLessThanOrEqual = function() { - /** - * {@link expect} the actual value to be less than or equal to the expected value. - * @function - * @name matchers#toBeLessThanOrEqual - * @param {Number} expected - The expected value to compare against. - * @example - * expect(result).toBeLessThanOrEqual(123); - */ - function toBeLessThanOrEqual() { - return { - - compare: function(actual, expected) { - return { - pass: actual <= expected - }; - } - }; - } - - return toBeLessThanOrEqual; -}; - -getJasmineRequireObj().toBeNaN = function(j$) { - /** - * {@link expect} the actual value to be `NaN` (Not a Number). - * @function - * @name matchers#toBeNaN - * @example - * expect(thing).toBeNaN(); - */ - function toBeNaN() { - return { - compare: function(actual) { - var result = { - pass: (actual !== actual) - }; - - if (result.pass) { - result.message = 'Expected actual not to be NaN.'; - } else { - result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; - } - - return result; - } - }; - } - - return toBeNaN; -}; - -getJasmineRequireObj().toBeNegativeInfinity = function(j$) { - /** - * {@link expect} the actual value to be `-Infinity` (-infinity). - * @function - * @name matchers#toBeNegativeInfinity - * @example - * expect(thing).toBeNegativeInfinity(); - */ - function toBeNegativeInfinity() { - return { - compare: function(actual) { - var result = { - pass: (actual === Number.NEGATIVE_INFINITY) - }; - - if (result.pass) { - result.message = 'Expected actual to be -Infinity.'; - } else { - result.message = function() { return 'Expected ' + j$.pp(actual) + ' not to be -Infinity.'; }; - } - - return result; - } - }; - } - - return toBeNegativeInfinity; -}; - -getJasmineRequireObj().toBeNull = function() { - /** - * {@link expect} the actual value to be `null`. - * @function - * @name matchers#toBeNull - * @example - * expect(result).toBeNull(); - */ - function toBeNull() { - return { - compare: function(actual) { - return { - pass: actual === null - }; - } - }; - } - - return toBeNull; -}; - -getJasmineRequireObj().toBePositiveInfinity = function(j$) { - /** - * {@link expect} the actual value to be `Infinity` (infinity). - * @function - * @name matchers#toBePositiveInfinity - * @example - * expect(thing).toBePositiveInfinity(); - */ - function toBePositiveInfinity() { - return { - compare: function(actual) { - var result = { - pass: (actual === Number.POSITIVE_INFINITY) - }; - - if (result.pass) { - result.message = 'Expected actual to be Infinity.'; - } else { - result.message = function() { return 'Expected ' + j$.pp(actual) + ' not to be Infinity.'; }; - } - - return result; - } - }; - } - - return toBePositiveInfinity; -}; - -getJasmineRequireObj().toBeTruthy = function() { - /** - * {@link expect} the actual value to be truthy. - * @function - * @name matchers#toBeTruthy - * @example - * expect(thing).toBeTruthy(); - */ - function toBeTruthy() { - return { - compare: function(actual) { - return { - pass: !!actual - }; - } - }; - } - - return toBeTruthy; -}; - -getJasmineRequireObj().toBeUndefined = function() { - /** - * {@link expect} the actual value to be `undefined`. - * @function - * @name matchers#toBeUndefined - * @example - * expect(result).toBeUndefined(): - */ - function toBeUndefined() { - return { - compare: function(actual) { - return { - pass: void 0 === actual - }; - } - }; - } - - return toBeUndefined; -}; - -getJasmineRequireObj().toContain = function() { - /** - * {@link expect} the actual value to contain a specific value. - * @function - * @name matchers#toContain - * @param {Object} expected - The value to look for. - * @example - * expect(array).toContain(anElement); - * expect(string).toContain(substring); - */ - function toContain(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - - return { - compare: function(actual, expected) { - - return { - pass: util.contains(actual, expected, customEqualityTesters) - }; - } - }; - } - - return toContain; -}; - -getJasmineRequireObj().toEqual = function(j$) { - /** - * {@link expect} the actual value to be equal to the expected, using deep equality comparison. - * @function - * @name matchers#toEqual - * @param {Object} expected - Expected value - * @example - * expect(bigObject).toEqual({"foo": ['bar', 'baz']}); - */ - function toEqual(util, customEqualityTesters) { - customEqualityTesters = customEqualityTesters || []; - - return { - compare: function(actual, expected) { - var result = { - pass: false - }, - diffBuilder = j$.DiffBuilder(); - - result.pass = util.equals(actual, expected, customEqualityTesters, diffBuilder); - - // TODO: only set error message if test fails - result.message = diffBuilder.getMessage(); - - return result; - } - }; - } - - return toEqual; -}; - -getJasmineRequireObj().toHaveBeenCalled = function(j$) { - - var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalled()'); - - /** - * {@link expect} the actual (a {@link Spy}) to have been called. - * @function - * @name matchers#toHaveBeenCalled - * @example - * expect(mySpy).toHaveBeenCalled(); - * expect(mySpy).not.toHaveBeenCalled(); - */ - function toHaveBeenCalled() { - return { - compare: function(actual) { - var result = {}; - - if (!j$.isSpy(actual)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); - } - - if (arguments.length > 1) { - throw new Error(getErrorMsg('Does not take arguments, use toHaveBeenCalledWith')); - } - - result.pass = actual.calls.any(); - - result.message = result.pass ? - 'Expected spy ' + actual.and.identity() + ' not to have been called.' : - 'Expected spy ' + actual.and.identity() + ' to have been called.'; - - return result; - } - }; - } - - return toHaveBeenCalled; -}; - -getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { - - var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledBefore()'); - - /** - * {@link expect} the actual value (a {@link Spy}) to have been called before another {@link Spy}. - * @function - * @name matchers#toHaveBeenCalledBefore - * @param {Spy} expected - {@link Spy} that should have been called after the `actual` {@link Spy}. - * @example - * expect(mySpy).toHaveBeenCalledBefore(otherSpy); - */ - function toHaveBeenCalledBefore() { - return { - compare: function(firstSpy, latterSpy) { - if (!j$.isSpy(firstSpy)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(firstSpy) + '.')); - } - if (!j$.isSpy(latterSpy)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(latterSpy) + '.')); - } - - var result = { pass: false }; - - if (!firstSpy.calls.count()) { - result.message = 'Expected spy ' + firstSpy.and.identity() + ' to have been called.'; - return result; - } - if (!latterSpy.calls.count()) { - result.message = 'Expected spy ' + latterSpy.and.identity() + ' to have been called.'; - return result; - } - - var latest1stSpyCall = firstSpy.calls.mostRecent().invocationOrder; - var first2ndSpyCall = latterSpy.calls.first().invocationOrder; - - result.pass = latest1stSpyCall < first2ndSpyCall; - - if (result.pass) { - result.message = 'Expected spy ' + firstSpy.and.identity() + ' to not have been called before spy ' + latterSpy.and.identity() + ', but it was'; - } else { - var first1stSpyCall = firstSpy.calls.first().invocationOrder; - var latest2ndSpyCall = latterSpy.calls.mostRecent().invocationOrder; - - if(first1stSpyCall < first2ndSpyCall) { - result.message = 'Expected latest call to spy ' + firstSpy.and.identity() + ' to have been called before first call to spy ' + latterSpy.and.identity() + ' (no interleaved calls)'; - } else if (latest2ndSpyCall > latest1stSpyCall) { - result.message = 'Expected first call to spy ' + latterSpy.and.identity() + ' to have been called after latest call to spy ' + firstSpy.and.identity() + ' (no interleaved calls)'; - } else { - result.message = 'Expected spy ' + firstSpy.and.identity() + ' to have been called before spy ' + latterSpy.and.identity(); - } - } - - return result; - } - }; - } - - return toHaveBeenCalledBefore; -}; - -getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) { - - var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledTimes()'); - - /** - * {@link expect} the actual (a {@link Spy}) to have been called the specified number of times. - * @function - * @name matchers#toHaveBeenCalledTimes - * @param {Number} expected - The number of invocations to look for. - * @example - * expect(mySpy).toHaveBeenCalledTimes(3); - */ - function toHaveBeenCalledTimes() { - return { - compare: function(actual, expected) { - if (!j$.isSpy(actual)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); - } - - var args = Array.prototype.slice.call(arguments, 0), - result = { pass: false }; - - if (!j$.isNumber_(expected)){ - throw new Error(getErrorMsg('The expected times failed is a required argument and must be a number.')); - } - - actual = args[0]; - var calls = actual.calls.count(); - var timesMessage = expected === 1 ? 'once' : expected + ' times'; - result.pass = calls === expected; - result.message = result.pass ? - 'Expected spy ' + actual.and.identity() + ' not to have been called ' + timesMessage + '. It was called ' + calls + ' times.' : - 'Expected spy ' + actual.and.identity() + ' to have been called ' + timesMessage + '. It was called ' + calls + ' times.'; - return result; - } - }; - } - - return toHaveBeenCalledTimes; -}; - -getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { - - var getErrorMsg = j$.formatErrorMsg('', 'expect().toHaveBeenCalledWith(...arguments)'); - - /** - * {@link expect} the actual (a {@link Spy}) to have been called with particular arguments at least once. - * @function - * @name matchers#toHaveBeenCalledWith - * @param {...Object} - The arguments to look for - * @example - * expect(mySpy).toHaveBeenCalledWith('foo', 'bar', 2); - */ - function toHaveBeenCalledWith(util, customEqualityTesters) { - return { - compare: function() { - var args = Array.prototype.slice.call(arguments, 0), - actual = args[0], - expectedArgs = args.slice(1), - result = { pass: false }; - - if (!j$.isSpy(actual)) { - throw new Error(getErrorMsg('Expected a spy, but got ' + j$.pp(actual) + '.')); - } - - if (!actual.calls.any()) { - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; - return result; - } - - if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { - result.pass = true; - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; - } else { - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; - } - - return result; - } - }; - } - - return toHaveBeenCalledWith; -}; - -getJasmineRequireObj().toMatch = function(j$) { - - var getErrorMsg = j$.formatErrorMsg('', 'expect().toMatch( || )'); - - /** - * {@link expect} the actual value to match a regular expression - * @function - * @name matchers#toMatch - * @param {RegExp|String} expected - Value to look for in the string. - * @example - * expect("my string").toMatch(/string$/); - * expect("other string").toMatch("her"); - */ - function toMatch() { - return { - compare: function(actual, expected) { - if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { - throw new Error(getErrorMsg('Expected is not a String or a RegExp')); - } - - var regexp = new RegExp(expected); - - return { - pass: regexp.test(actual) - }; - } - }; - } - - return toMatch; -}; - -getJasmineRequireObj().toThrow = function(j$) { - - var getErrorMsg = j$.formatErrorMsg('', 'expect(function() {}).toThrow()'); - - /** - * {@link expect} a function to `throw` something. - * @function - * @name matchers#toThrow - * @param {Object} [expected] - Value that should be thrown. If not provided, simply the fact that something was thrown will be checked. - * @example - * expect(function() { return 'things'; }).toThrow('foo'); - * expect(function() { return 'stuff'; }).toThrow(); - */ - function toThrow(util) { - return { - compare: function(actual, expected) { - var result = { pass: false }, - threw = false, - thrown; - - if (typeof actual != 'function') { - throw new Error(getErrorMsg('Actual is not a Function')); - } - - try { - actual(); - } catch (e) { - threw = true; - thrown = e; - } - - if (!threw) { - result.message = 'Expected function to throw an exception.'; - return result; - } - - if (arguments.length == 1) { - result.pass = true; - result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; - - return result; - } - - if (util.equals(thrown, expected)) { - result.pass = true; - result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; - } else { - result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; - } - - return result; - } - }; - } - - return toThrow; -}; - -getJasmineRequireObj().toThrowError = function(j$) { - - var getErrorMsg = j$.formatErrorMsg('', 'expect(function() {}).toThrowError(, )'); - - /** - * {@link expect} a function to `throw` an `Error`. - * @function - * @name matchers#toThrowError - * @param {Error} [expected] - `Error` constructor the object that was thrown needs to be an instance of. If not provided, `Error` will be used. - * @param {RegExp|String} [message] - The message that should be set on the thrown `Error` - * @example - * expect(function() { return 'things'; }).toThrowError(MyCustomError, 'message'); - * expect(function() { return 'things'; }).toThrowError(MyCustomError, /bar/); - * expect(function() { return 'stuff'; }).toThrowError(MyCustomError); - * expect(function() { return 'other'; }).toThrowError(/foo/); - * expect(function() { return 'other'; }).toThrowError(); - */ - function toThrowError () { - return { - compare: function(actual) { - var threw = false, - pass = {pass: true}, - fail = {pass: false}, - thrown; - - if (typeof actual != 'function') { - throw new Error(getErrorMsg('Actual is not a Function')); - } - - var errorMatcher = getMatcher.apply(null, arguments); - - try { - actual(); - } catch (e) { - threw = true; - thrown = e; - } - - if (!threw) { - fail.message = 'Expected function to throw an Error.'; - return fail; - } - - // Get Error constructor of thrown - if (!isErrorObject(thrown)) { - fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; - return fail; - } - - if (errorMatcher.hasNoSpecifics()) { - pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.'; - return pass; - } - - if (errorMatcher.matches(thrown)) { - pass.message = function() { - return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.'; - }; - return pass; - } else { - fail.message = function() { - return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + - ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.'; - }; - return fail; - } - } - }; - - function getMatcher() { - var expected = null, - errorType = null; - - if (arguments.length == 2) { - expected = arguments[1]; - if (isAnErrorType(expected)) { - errorType = expected; - expected = null; - } - } else if (arguments.length > 2) { - errorType = arguments[1]; - expected = arguments[2]; - if (!isAnErrorType(errorType)) { - throw new Error(getErrorMsg('Expected error type is not an Error.')); - } - } - - if (expected && !isStringOrRegExp(expected)) { - if (errorType) { - throw new Error(getErrorMsg('Expected error message is not a string or RegExp.')); - } else { - throw new Error(getErrorMsg('Expected is not an Error, string, or RegExp.')); - } - } - - function messageMatch(message) { - if (typeof expected == 'string') { - return expected == message; - } else { - return expected.test(message); - } - } - - return { - errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception', - thrownDescription: function(thrown) { - var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', - thrownMessage = ''; - - if (expected) { - thrownMessage = ' with message ' + j$.pp(thrown.message); - } - - return thrownName + thrownMessage; - }, - messageDescription: function() { - if (expected === null) { - return ''; - } else if (expected instanceof RegExp) { - return ' with a message matching ' + j$.pp(expected); - } else { - return ' with message ' + j$.pp(expected); - } - }, - hasNoSpecifics: function() { - return expected === null && errorType === null; - }, - matches: function(error) { - return (errorType === null || error instanceof errorType) && - (expected === null || messageMatch(error.message)); - } - }; - } - - function isStringOrRegExp(potential) { - return potential instanceof RegExp || (typeof potential == 'string'); - } - - function isAnErrorType(type) { - if (typeof type !== 'function') { - return false; - } - - var Surrogate = function() {}; - Surrogate.prototype = type.prototype; - return isErrorObject(new Surrogate()); - } - - function isErrorObject(thrown) { - if (thrown instanceof Error) { - return true; - } - if (thrown && thrown.constructor && thrown.constructor.constructor && - (thrown instanceof (thrown.constructor.constructor('return this')()).Error)) { - return true; - } - return false; - } - } - - return toThrowError; -}; - -getJasmineRequireObj().MockDate = function() { - function MockDate(global) { - var self = this; - var currentTime = 0; - - if (!global || !global.Date) { - self.install = function() {}; - self.tick = function() {}; - self.uninstall = function() {}; - return self; - } - - var GlobalDate = global.Date; - - self.install = function(mockDate) { - if (mockDate instanceof GlobalDate) { - currentTime = mockDate.getTime(); - } else { - currentTime = new GlobalDate().getTime(); - } - - global.Date = FakeDate; - }; - - self.tick = function(millis) { - millis = millis || 0; - currentTime = currentTime + millis; - }; - - self.uninstall = function() { - currentTime = 0; - global.Date = GlobalDate; - }; - - createDateProperties(); - - return self; - - function FakeDate() { - switch(arguments.length) { - case 0: - return new GlobalDate(currentTime); - case 1: - return new GlobalDate(arguments[0]); - case 2: - return new GlobalDate(arguments[0], arguments[1]); - case 3: - return new GlobalDate(arguments[0], arguments[1], arguments[2]); - case 4: - return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]); - case 5: - return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], - arguments[4]); - case 6: - return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], - arguments[4], arguments[5]); - default: - return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], - arguments[4], arguments[5], arguments[6]); - } - } - - function createDateProperties() { - FakeDate.prototype = GlobalDate.prototype; - - FakeDate.now = function() { - if (GlobalDate.now) { - return currentTime; - } else { - throw new Error('Browser does not support Date.now()'); - } - }; - - FakeDate.toSource = GlobalDate.toSource; - FakeDate.toString = GlobalDate.toString; - FakeDate.parse = GlobalDate.parse; - FakeDate.UTC = GlobalDate.UTC; - } - } - - return MockDate; -}; - -getJasmineRequireObj().pp = function(j$) { - - function PrettyPrinter() { - this.ppNestLevel_ = 0; - this.seen = []; - } - - function hasCustomToString(value) { - // value.toString !== Object.prototype.toString if value has no custom toString but is from another context (e.g. - // iframe, web worker) - return value.toString !== Object.prototype.toString && (value.toString() !== Object.prototype.toString.call(value)); - } - - PrettyPrinter.prototype.format = function(value) { - this.ppNestLevel_++; - try { - if (j$.util.isUndefined(value)) { - this.emitScalar('undefined'); - } else if (value === null) { - this.emitScalar('null'); - } else if (value === 0 && 1/value === -Infinity) { - this.emitScalar('-0'); - } else if (value === j$.getGlobal()) { - this.emitScalar(''); - } else if (value.jasmineToString) { - this.emitScalar(value.jasmineToString()); - } else if (typeof value === 'string') { - this.emitString(value); - } else if (j$.isSpy(value)) { - this.emitScalar('spy on ' + value.and.identity()); - } else if (value instanceof RegExp) { - this.emitScalar(value.toString()); - } else if (typeof value === 'function') { - this.emitScalar('Function'); - } else if (typeof value.nodeType === 'number') { - this.emitScalar('HTMLNode'); - } else if (value instanceof Date) { - this.emitScalar('Date(' + value + ')'); - } else if (value.toString && value.toString() == '[object Set]') { - this.emitSet(value); - } else if (value.toString && typeof value === 'object' && !j$.isArray_(value) && hasCustomToString(value)) { - this.emitScalar(value.toString()); - } else if (j$.util.arrayContains(this.seen, value)) { - this.emitScalar(''); - } else if (j$.isArray_(value) || j$.isA_('Object', value)) { - this.seen.push(value); - if (j$.isArray_(value)) { - this.emitArray(value); - } else { - this.emitObject(value); - } - this.seen.pop(); - } else { - this.emitScalar(value.toString()); - } - } finally { - this.ppNestLevel_--; - } - }; - - PrettyPrinter.prototype.iterateObject = function(obj, fn) { - for (var property in obj) { - if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; } - fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && - obj.__lookupGetter__(property) !== null) : false); - } - }; - - PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_; - PrettyPrinter.prototype.emitSet = j$.unimplementedMethod_; - PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; - PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; - PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; - - function StringPrettyPrinter() { - PrettyPrinter.call(this); - - this.string = ''; - } - - j$.util.inherit(StringPrettyPrinter, PrettyPrinter); - - StringPrettyPrinter.prototype.emitScalar = function(value) { - this.append(value); - }; - - StringPrettyPrinter.prototype.emitString = function(value) { - this.append('\'' + value + '\''); - }; - - StringPrettyPrinter.prototype.emitArray = function(array) { - if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { - this.append('Array'); - return; - } - var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); - this.append('[ '); - for (var i = 0; i < length; i++) { - if (i > 0) { - this.append(', '); - } - this.format(array[i]); - } - if(array.length > length){ - this.append(', ...'); - } - - var self = this; - var first = array.length === 0; - this.iterateObject(array, function(property, isGetter) { - if (property.match(/^\d+$/)) { - return; - } - - if (first) { - first = false; - } else { - self.append(', '); - } - - self.formatProperty(array, property, isGetter); - }); - - this.append(' ]'); - }; - - StringPrettyPrinter.prototype.emitSet = function(set) { - if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { - this.append('Set'); - return; - } - this.append('Set( '); - var size = Math.min(set.size, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); - var iter = set.values(); - for (var i = 0; i < size; i++) { - if (i > 0) { - this.append(', '); - } - this.format(iter.next().value); - } - if (set.size > size){ - this.append(', ...'); - } - this.append(' )'); - }; - - StringPrettyPrinter.prototype.emitObject = function(obj) { - var ctor = obj.constructor, - constructorName; - - constructorName = typeof ctor === 'function' && obj instanceof ctor ? - j$.fnNameFor(obj.constructor) : - 'null'; - - this.append(constructorName); - - if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { - return; - } - - var self = this; - this.append('({ '); - var first = true; - - this.iterateObject(obj, function(property, isGetter) { - if (first) { - first = false; - } else { - self.append(', '); - } - - self.formatProperty(obj, property, isGetter); - }); - - this.append(' })'); - }; - - StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) { - this.append(property); - this.append(': '); - if (isGetter) { - this.append(''); - } else { - this.format(obj[property]); - } - }; - - StringPrettyPrinter.prototype.append = function(value) { - this.string += value; - }; - - return function(value) { - var stringPrettyPrinter = new StringPrettyPrinter(); - stringPrettyPrinter.format(value); - return stringPrettyPrinter.string; - }; -}; - -getJasmineRequireObj().QueueRunner = function(j$) { - - function once(fn) { - var called = false; - return function() { - if (!called) { - called = true; - fn(); - } - return null; - }; - } - - function QueueRunner(attrs) { - this.queueableFns = attrs.queueableFns || []; - this.onComplete = attrs.onComplete || function() {}; - this.clearStack = attrs.clearStack || function(fn) {fn();}; - this.onException = attrs.onException || function() {}; - this.catchException = attrs.catchException || function() { return true; }; - this.userContext = attrs.userContext || {}; - this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; - this.fail = attrs.fail || function() {}; - this.globalErrors = attrs.globalErrors || { pushListener: function() {}, popListener: function() {} }; - } - - QueueRunner.prototype.execute = function() { - this.run(this.queueableFns, 0); - }; - - QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { - var length = queueableFns.length, - self = this, - iterativeIndex; - - - for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { - var queueableFn = queueableFns[iterativeIndex]; - if (queueableFn.fn.length > 0) { - attemptAsync(queueableFn); - return; - } else { - attemptSync(queueableFn); - } - } - - this.clearStack(this.onComplete); - - function attemptSync(queueableFn) { - try { - queueableFn.fn.call(self.userContext); - } catch (e) { - handleException(e, queueableFn); - } - } - - function attemptAsync(queueableFn) { - var clearTimeout = function () { - Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]); - }, - handleError = function(error) { - onException(error); - next(); - }, - next = once(function () { - clearTimeout(timeoutId); - self.globalErrors.popListener(handleError); - self.run(queueableFns, iterativeIndex + 1); - }), - timeoutId; - - next.fail = function() { - self.fail.apply(null, arguments); - next(); - }; - - self.globalErrors.pushListener(handleError); - - if (queueableFn.timeout) { - timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() { - var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); - onException(error); - next(); - }, queueableFn.timeout()]]); - } - - try { - queueableFn.fn.call(self.userContext, next); - } catch (e) { - handleException(e, queueableFn); - next(); - } - } - - function onException(e) { - self.onException(e); - } - - function handleException(e, queueableFn) { - onException(e); - if (!self.catchException(e)) { - //TODO: set a var when we catch an exception and - //use a finally block to close the loop in a nice way.. - throw e; - } - } - }; - - return QueueRunner; -}; - -getJasmineRequireObj().ReportDispatcher = function() { - function ReportDispatcher(methods) { - - var dispatchedMethods = methods || []; - - for (var i = 0; i < dispatchedMethods.length; i++) { - var method = dispatchedMethods[i]; - this[method] = (function(m) { - return function() { - dispatch(m, arguments); - }; - }(method)); - } - - var reporters = []; - var fallbackReporter = null; - - this.addReporter = function(reporter) { - reporters.push(reporter); - }; - - this.provideFallbackReporter = function(reporter) { - fallbackReporter = reporter; - }; - - this.clearReporters = function() { - reporters = []; - }; - - return this; - - function dispatch(method, args) { - if (reporters.length === 0 && fallbackReporter !== null) { - reporters.push(fallbackReporter); - } - for (var i = 0; i < reporters.length; i++) { - var reporter = reporters[i]; - if (reporter[method]) { - reporter[method].apply(reporter, args); - } - } - } - } - - return ReportDispatcher; -}; - - -getJasmineRequireObj().interface = function(jasmine, env) { - var jasmineInterface = { - /** - * Create a group of specs (often called a suite). - * - * Calls to `describe` can be nested within other calls to compose your suite as a tree. - * @name describe - * @function - * @global - * @param {String} description Textual description of the group - * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites a specs - */ - describe: function(description, specDefinitions) { - return env.describe(description, specDefinitions); - }, - - /** - * A temporarily disabled [`describe`]{@link describe} - * - * Specs within an `xdescribe` will be marked pending and not executed - * @name xdescribe - * @function - * @global - * @param {String} description Textual description of the group - * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites a specs - */ - xdescribe: function(description, specDefinitions) { - return env.xdescribe(description, specDefinitions); - }, - - /** - * A focused [`describe`]{@link describe} - * - * If suites or specs are focused, only those that are focused will be executed - * @see fit - * @name fdescribe - * @function - * @global - * @param {String} description Textual description of the group - * @param {Function} specDefinitions Function for Jasmine to invoke that will define inner suites a specs - */ - fdescribe: function(description, specDefinitions) { - return env.fdescribe(description, specDefinitions); - }, - - /** - * Define a single spec. A spec should contain one or more {@link expect|expectations} that test the state of the code. - * - * A spec whose expectations all succeed will be passing and a spec with any failures will fail. - * @name it - * @function - * @global - * @param {String} description Textual description of what this spec is checking - * @param {Function} [testFunction] Function that contains the code of your test. If not provided the test will be `pending`. - * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async spec. - */ - it: function() { - return env.it.apply(env, arguments); - }, - - /** - * A temporarily disabled [`it`]{@link it} - * - * The spec will report as `pending` and will not be executed. - * @name xit - * @function - * @global - * @param {String} description Textual description of what this spec is checking. - * @param {Function} [testFunction] Function that contains the code of your test. Will not be executed. - */ - xit: function() { - return env.xit.apply(env, arguments); - }, - - /** - * A focused [`it`]{@link it} - * - * If suites or specs are focused, only those that are focused will be executed. - * @name fit - * @function - * @global - * @param {String} description Textual description of what this spec is checking. - * @param {Function} testFunction Function that contains the code of your test. - * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async spec. - */ - fit: function() { - return env.fit.apply(env, arguments); - }, - - /** - * Run some shared setup before each of the specs in the {@link describe} in which it is called. - * @name beforeEach - * @function - * @global - * @param {Function} [function] Function that contains the code to setup your specs. - * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async beforeEach. - */ - beforeEach: function() { - return env.beforeEach.apply(env, arguments); - }, - - /** - * Run some shared teardown after each of the specs in the {@link describe} in which it is called. - * @name afterEach - * @function - * @global - * @param {Function} [function] Function that contains the code to teardown your specs. - * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async afterEach. - */ - afterEach: function() { - return env.afterEach.apply(env, arguments); - }, - - /** - * Run some shared setup once before all of the specs in the {@link describe} are run. - * - * _Note:_ Be careful, sharing the setup from a beforeAll makes it easy to accidentally leak state between your specs so that they erroneously pass or fail. - * @name beforeAll - * @function - * @global - * @param {Function} [function] Function that contains the code to setup your specs. - * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async beforeAll. - */ - beforeAll: function() { - return env.beforeAll.apply(env, arguments); - }, - - /** - * Run some shared teardown once before all of the specs in the {@link describe} are run. - * - * _Note:_ Be careful, sharing the teardown from a afterAll makes it easy to accidentally leak state between your specs so that they erroneously pass or fail. - * @name afterAll - * @function - * @global - * @param {Function} [function] Function that contains the code to teardown your specs. - * @param {Int} [timeout={@link jasmine.DEFAULT_TIMEOUT_INTERVAL}] Custom timeout for an async afterAll. - */ - afterAll: function() { - return env.afterAll.apply(env, arguments); - }, - - /** - * Create an expectation for a spec. - * @name expect - * @function - * @global - * @param {Object} actual - Actual computed value to test expectations against. - * @return {matchers} - */ - expect: function(actual) { - return env.expect(actual); - }, - - /** - * Mark a spec as pending, expectation results will be ignored. - * @name pending - * @function - * @global - * @param {String} [message] - Reason the spec is pending. - */ - pending: function() { - return env.pending.apply(env, arguments); - }, - - /** - * Explicitly mark a spec as failed. - * @name fail - * @function - * @global - * @param {String|Error} [error] - Reason for the failure. - */ - fail: function() { - return env.fail.apply(env, arguments); - }, - - /** - * Install a spy onto an existing object. - * @name spyOn - * @function - * @global - * @param {Object} obj - The object upon which to install the {@link Spy}. - * @param {String} methodName - The name of the method to replace with a {@link Spy}. - * @returns {Spy} - */ - spyOn: function(obj, methodName) { - return env.spyOn(obj, methodName); - }, - - /** - * Install a spy on a property onto an existing object. - * @name spyOnProperty - * @function - * @global - * @param {Object} obj - The object upon which to install the {@link Spy} - * @param {String} propertyName - The name of the property to replace with a {@link Spy}. - * @param {String} [accessType=get] - The access type (get|set) of the property to {@link Spy} on. - * @returns {Spy} - */ - spyOnProperty: function(obj, methodName, accessType) { - return env.spyOnProperty(obj, methodName, accessType); - }, - - jsApiReporter: new jasmine.JsApiReporter({ - timer: new jasmine.Timer() - }), - - /** - * @namespace jasmine - */ - jasmine: jasmine - }; - - /** - * Add a custom equality tester for the current scope of specs. - * - * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. - * @name jasmine.addCustomEqualityTester - * @function - * @param {Function} tester - A function which takes two arguments to compare and returns a `true` or `false` comparison result if it knows how to compare them, and `undefined` otherwise. - * @see custom_equality - */ - jasmine.addCustomEqualityTester = function(tester) { - env.addCustomEqualityTester(tester); - }; - - /** - * Add custom matchers for the current scope of specs. - * - * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. - * @name jasmine.addMatchers - * @function - * @param {Object} matchers - Keys from this object will be the new matcher names. - * @see custom_matcher - */ - jasmine.addMatchers = function(matchers) { - return env.addMatchers(matchers); - }; - - /** - * Get the currently booted mock {Clock} for this Jasmine environment. - * @name jasmine.clock - * @function - * @returns {Clock} - */ - jasmine.clock = function() { - return env.clock; - }; - - return jasmineInterface; -}; - -getJasmineRequireObj().Spy = function (j$) { - - var nextOrder = (function() { - var order = 0; - - return function() { - return order++; - }; - })(); - - /** - * _Note:_ Do not construct this directly, use {@link spyOn}, {@link spyOnProperty}, {@link jasmine.createSpy}, or {@link jasmine.createSpyObj} - * @constructor - * @name Spy - */ - function Spy(name, originalFn) { - var args = buildArgs(), - /*`eval` is the only option to preserve both this and context: - - former is needed to work as expected with methods, - - latter is needed to access real spy function and allows to reduce eval'ed code to absolute minimum - More explanation here (look at comments): http://www.bennadel.com/blog/1909-javascript-function-constructor-does-not-create-a-closure.htm - */ - /* jshint evil: true */ - wrapper = eval('(0, function (' + args + ') { return spy.apply(this, Array.prototype.slice.call(arguments)); })'), - /* jshint evil: false */ - spyStrategy = new j$.SpyStrategy({ - name: name, - fn: originalFn, - getSpy: function () { - return wrapper; - } - }), - callTracker = new j$.CallTracker(), - spy = function () { - /** - * @name Spy.callData - * @property {object} object - `this` context for the invocation. - * @property {number} invocationOrder - Order of the invocation. - * @property {Array} args - The arguments passed for this invocation. - */ - var callData = { - object: this, - invocationOrder: nextOrder(), - args: Array.prototype.slice.apply(arguments) - }; - - callTracker.track(callData); - var returnValue = spyStrategy.exec.apply(this, arguments); - callData.returnValue = returnValue; - - return returnValue; - }; - - function buildArgs() { - var args = []; - - while (originalFn instanceof Function && args.length < originalFn.length) { - args.push('arg' + args.length); - } - - return args.join(', '); - } - - for (var prop in originalFn) { - if (prop === 'and' || prop === 'calls') { - throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon'); - } - - wrapper[prop] = originalFn[prop]; - } - - wrapper.and = spyStrategy; - wrapper.calls = callTracker; - - return wrapper; - } - - return Spy; -}; - -getJasmineRequireObj().SpyRegistry = function(j$) { - - var getErrorMsg = j$.formatErrorMsg('', 'spyOn(, )'); - - function SpyRegistry(options) { - options = options || {}; - var currentSpies = options.currentSpies || function() { return []; }; - - this.allowRespy = function(allow){ - this.respy = allow; - }; - - this.spyOn = function(obj, methodName) { - - if (j$.util.isUndefined(obj) || obj === null) { - throw new Error(getErrorMsg('could not find an object to spy upon for ' + methodName + '()')); - } - - if (j$.util.isUndefined(methodName) || methodName === null) { - throw new Error(getErrorMsg('No method name supplied')); - } - - if (j$.util.isUndefined(obj[methodName])) { - throw new Error(getErrorMsg(methodName + '() method does not exist')); - } - - if (obj[methodName] && j$.isSpy(obj[methodName]) ) { - if ( !!this.respy ){ - return obj[methodName]; - }else { - throw new Error(getErrorMsg(methodName + ' has already been spied upon')); - } - } - - var descriptor; - try { - descriptor = Object.getOwnPropertyDescriptor(obj, methodName); - } catch(e) { - // IE 8 doesn't support `definePropery` on non-DOM nodes - } - - if (descriptor && !(descriptor.writable || descriptor.set)) { - throw new Error(getErrorMsg(methodName + ' is not declared writable or has no setter')); - } - - var originalMethod = obj[methodName], - spiedMethod = j$.createSpy(methodName, originalMethod), - restoreStrategy; - - if (Object.prototype.hasOwnProperty.call(obj, methodName)) { - restoreStrategy = function() { - obj[methodName] = originalMethod; - }; - } else { - restoreStrategy = function() { - if (!delete obj[methodName]) { - obj[methodName] = originalMethod; - } - }; - } - - currentSpies().push({ - restoreObjectToOriginalState: restoreStrategy - }); - - obj[methodName] = spiedMethod; - - return spiedMethod; - }; - - this.spyOnProperty = function (obj, propertyName, accessType) { - accessType = accessType || 'get'; - - if (j$.util.isUndefined(obj)) { - throw new Error('spyOn could not find an object to spy upon for ' + propertyName + ''); - } - - if (j$.util.isUndefined(propertyName)) { - throw new Error('No property name supplied'); - } - - var descriptor; - try { - descriptor = j$.util.getPropertyDescriptor(obj, propertyName); - } catch(e) { - // IE 8 doesn't support `definePropery` on non-DOM nodes - } - - if (!descriptor) { - throw new Error(propertyName + ' property does not exist'); - } - - if (!descriptor.configurable) { - throw new Error(propertyName + ' is not declared configurable'); - } - - if(!descriptor[accessType]) { - throw new Error('Property ' + propertyName + ' does not have access type ' + accessType); - } - - if (j$.isSpy(descriptor[accessType])) { - //TODO?: should this return the current spy? Downside: may cause user confusion about spy state - throw new Error(propertyName + ' has already been spied upon'); - } - - var originalDescriptor = j$.util.clone(descriptor), - spy = j$.createSpy(propertyName, descriptor[accessType]), - restoreStrategy; - - if (Object.prototype.hasOwnProperty.call(obj, propertyName)) { - restoreStrategy = function() { - Object.defineProperty(obj, propertyName, originalDescriptor); - }; - } else { - restoreStrategy = function() { - delete obj[propertyName]; - }; - } - - currentSpies().push({ - restoreObjectToOriginalState: restoreStrategy - }); - - descriptor[accessType] = spy; - - Object.defineProperty(obj, propertyName, descriptor); - - return spy; - }; - - this.clearSpies = function() { - var spies = currentSpies(); - for (var i = spies.length - 1; i >= 0; i--) { - var spyEntry = spies[i]; - spyEntry.restoreObjectToOriginalState(); - } - }; - } - - return SpyRegistry; -}; - -getJasmineRequireObj().SpyStrategy = function(j$) { - - /** - * @namespace Spy#and - */ - function SpyStrategy(options) { - options = options || {}; - - var identity = options.name || 'unknown', - originalFn = options.fn || function() {}, - getSpy = options.getSpy || function() {}, - plan = function() {}; - - /** - * Return the identifying information for the spy. - * @name Spy#and#identity - * @function - * @returns {String} - */ - this.identity = function() { - return identity; - }; - - /** - * Execute the current spy strategy. - * @name Spy#and#exec - * @function - */ - this.exec = function() { - return plan.apply(this, arguments); - }; - - /** - * Tell the spy to call through to the real implementation when invoked. - * @name Spy#and#callThrough - * @function - */ - this.callThrough = function() { - plan = originalFn; - return getSpy(); - }; - - /** - * Tell the spy to return the value when invoked. - * @name Spy#and#returnValue - * @function - * @param {*} value The value to return. - */ - this.returnValue = function(value) { - plan = function() { - return value; - }; - return getSpy(); - }; - - /** - * Tell the spy to return one of the specified values (sequentially) each time the spy is invoked. - * @name Spy#and#returnValues - * @function - * @param {...*} values - Values to be returned on subsequent calls to the spy. - */ - this.returnValues = function() { - var values = Array.prototype.slice.call(arguments); - plan = function () { - return values.shift(); - }; - return getSpy(); - }; - - /** - * Tell the spy to throw an error when invoked. - * @name Spy#and#throwError - * @function - * @param {Error|String} something Thing to throw - */ - this.throwError = function(something) { - var error = (something instanceof Error) ? something : new Error(something); - plan = function() { - throw error; - }; - return getSpy(); - }; - - /** - * Tell the spy to call a fake implementation when invoked. - * @name Spy#and#callFake - * @function - * @param {Function} fn The function to invoke with the passed parameters. - */ - this.callFake = function(fn) { - if(!j$.isFunction_(fn)) { - throw new Error('Argument passed to callFake should be a function, got ' + fn); - } - plan = fn; - return getSpy(); - }; - - /** - * Tell the spy to do nothing when invoked. This is the default. - * @name Spy#and#stub - * @function - */ - this.stub = function(fn) { - plan = function() {}; - return getSpy(); - }; - } - - return SpyStrategy; -}; - -getJasmineRequireObj().Suite = function(j$) { - function Suite(attrs) { - this.env = attrs.env; - this.id = attrs.id; - this.parentSuite = attrs.parentSuite; - this.description = attrs.description; - this.expectationFactory = attrs.expectationFactory; - this.expectationResultFactory = attrs.expectationResultFactory; - this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; - - this.beforeFns = []; - this.afterFns = []; - this.beforeAllFns = []; - this.afterAllFns = []; - - this.children = []; - - this.result = { - id: this.id, - description: this.description, - fullName: this.getFullName(), - failedExpectations: [] - }; - } - - Suite.prototype.expect = function(actual) { - return this.expectationFactory(actual, this); - }; - - Suite.prototype.getFullName = function() { - var fullName = []; - for (var parentSuite = this; parentSuite; parentSuite = parentSuite.parentSuite) { - if (parentSuite.parentSuite) { - fullName.unshift(parentSuite.description); - } - } - return fullName.join(' '); - }; - - Suite.prototype.pend = function() { - this.markedPending = true; - }; - - Suite.prototype.beforeEach = function(fn) { - this.beforeFns.unshift(fn); - }; - - Suite.prototype.beforeAll = function(fn) { - this.beforeAllFns.push(fn); - }; - - Suite.prototype.afterEach = function(fn) { - this.afterFns.unshift(fn); - }; - - Suite.prototype.afterAll = function(fn) { - this.afterAllFns.unshift(fn); - }; - - Suite.prototype.addChild = function(child) { - this.children.push(child); - }; - - Suite.prototype.status = function() { - if (this.markedPending) { - return 'pending'; - } - - if (this.result.failedExpectations.length > 0) { - return 'failed'; - } else { - return 'finished'; - } - }; - - Suite.prototype.isExecutable = function() { - return !this.markedPending; - }; - - Suite.prototype.canBeReentered = function() { - return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; - }; - - Suite.prototype.getResult = function() { - this.result.status = this.status(); - return this.result; - }; - - Suite.prototype.sharedUserContext = function() { - if (!this.sharedContext) { - this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {}; - } - - return this.sharedContext; - }; - - Suite.prototype.clonedSharedUserContext = function() { - return clone(this.sharedUserContext()); - }; - - Suite.prototype.onException = function() { - if (arguments[0] instanceof j$.errors.ExpectationFailed) { - return; - } - - if(isAfterAll(this.children)) { - var data = { - matcherName: '', - passed: false, - expected: '', - actual: '', - error: arguments[0] - }; - this.result.failedExpectations.push(this.expectationResultFactory(data)); - } else { - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; - child.onException.apply(child, arguments); - } - } - }; - - Suite.prototype.addExpectationResult = function () { - if(isAfterAll(this.children) && isFailure(arguments)){ - var data = arguments[1]; - this.result.failedExpectations.push(this.expectationResultFactory(data)); - if(this.throwOnExpectationFailure) { - throw new j$.errors.ExpectationFailed(); - } - } else { - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; - try { - child.addExpectationResult.apply(child, arguments); - } catch(e) { - // keep going - } - } - } - }; - - function isAfterAll(children) { - return children && children[0].result.status; - } - - function isFailure(args) { - return !args[0]; - } - - function clone(obj) { - var clonedObj = {}; - for (var prop in obj) { - if (obj.hasOwnProperty(prop)) { - clonedObj[prop] = obj[prop]; - } - } - - return clonedObj; - } - - return Suite; -}; - -if (typeof window == void 0 && typeof exports == 'object') { - exports.Suite = jasmineRequire.Suite; -} - -getJasmineRequireObj().Timer = function() { - var defaultNow = (function(Date) { - return function() { return new Date().getTime(); }; - })(Date); - - function Timer(options) { - options = options || {}; - - var now = options.now || defaultNow, - startTime; - - this.start = function() { - startTime = now(); - }; - - this.elapsed = function() { - return now() - startTime; - }; - } - - return Timer; -}; - -getJasmineRequireObj().TreeProcessor = function() { - function TreeProcessor(attrs) { - var tree = attrs.tree, - runnableIds = attrs.runnableIds, - queueRunnerFactory = attrs.queueRunnerFactory, - nodeStart = attrs.nodeStart || function() {}, - nodeComplete = attrs.nodeComplete || function() {}, - orderChildren = attrs.orderChildren || function(node) { return node.children; }, - stats = { valid: true }, - processed = false, - defaultMin = Infinity, - defaultMax = 1 - Infinity; - - this.processTree = function() { - processNode(tree, false); - processed = true; - return stats; - }; - - this.execute = function(done) { - if (!processed) { - this.processTree(); - } - - if (!stats.valid) { - throw 'invalid order'; - } - - var childFns = wrapChildren(tree, 0); - - queueRunnerFactory({ - queueableFns: childFns, - userContext: tree.sharedUserContext(), - onException: function() { - tree.onException.apply(tree, arguments); - }, - onComplete: done - }); - }; - - function runnableIndex(id) { - for (var i = 0; i < runnableIds.length; i++) { - if (runnableIds[i] === id) { - return i; - } - } - } - - function processNode(node, parentEnabled) { - var executableIndex = runnableIndex(node.id); - - if (executableIndex !== undefined) { - parentEnabled = true; - } - - parentEnabled = parentEnabled && node.isExecutable(); - - if (!node.children) { - stats[node.id] = { - executable: parentEnabled && node.isExecutable(), - segments: [{ - index: 0, - owner: node, - nodes: [node], - min: startingMin(executableIndex), - max: startingMax(executableIndex) - }] - }; - } else { - var hasExecutableChild = false; - - var orderedChildren = orderChildren(node); - - for (var i = 0; i < orderedChildren.length; i++) { - var child = orderedChildren[i]; - - processNode(child, parentEnabled); - - if (!stats.valid) { - return; - } - - var childStats = stats[child.id]; - - hasExecutableChild = hasExecutableChild || childStats.executable; - } - - stats[node.id] = { - executable: hasExecutableChild - }; - - segmentChildren(node, orderedChildren, stats[node.id], executableIndex); - - if (!node.canBeReentered() && stats[node.id].segments.length > 1) { - stats = { valid: false }; - } - } - } - - function startingMin(executableIndex) { - return executableIndex === undefined ? defaultMin : executableIndex; - } - - function startingMax(executableIndex) { - return executableIndex === undefined ? defaultMax : executableIndex; - } - - function segmentChildren(node, orderedChildren, nodeStats, executableIndex) { - var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) }, - result = [currentSegment], - lastMax = defaultMax, - orderedChildSegments = orderChildSegments(orderedChildren); - - function isSegmentBoundary(minIndex) { - return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1; - } - - for (var i = 0; i < orderedChildSegments.length; i++) { - var childSegment = orderedChildSegments[i], - maxIndex = childSegment.max, - minIndex = childSegment.min; - - if (isSegmentBoundary(minIndex)) { - currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax}; - result.push(currentSegment); - } - - currentSegment.nodes.push(childSegment); - currentSegment.min = Math.min(currentSegment.min, minIndex); - currentSegment.max = Math.max(currentSegment.max, maxIndex); - lastMax = maxIndex; - } - - nodeStats.segments = result; - } - - function orderChildSegments(children) { - var specifiedOrder = [], - unspecifiedOrder = []; - - for (var i = 0; i < children.length; i++) { - var child = children[i], - segments = stats[child.id].segments; - - for (var j = 0; j < segments.length; j++) { - var seg = segments[j]; - - if (seg.min === defaultMin) { - unspecifiedOrder.push(seg); - } else { - specifiedOrder.push(seg); - } - } - } - - specifiedOrder.sort(function(a, b) { - return a.min - b.min; - }); - - return specifiedOrder.concat(unspecifiedOrder); - } - - function executeNode(node, segmentNumber) { - if (node.children) { - return { - fn: function(done) { - nodeStart(node); - - queueRunnerFactory({ - onComplete: function() { - nodeComplete(node, node.getResult()); - done(); - }, - queueableFns: wrapChildren(node, segmentNumber), - userContext: node.sharedUserContext(), - onException: function() { - node.onException.apply(node, arguments); - } - }); - } - }; - } else { - return { - fn: function(done) { node.execute(done, stats[node.id].executable); } - }; - } - } - - function wrapChildren(node, segmentNumber) { - var result = [], - segmentChildren = stats[node.id].segments[segmentNumber].nodes; - - for (var i = 0; i < segmentChildren.length; i++) { - result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index)); - } - - if (!stats[node.id].executable) { - return result; - } - - return node.beforeAllFns.concat(result).concat(node.afterAllFns); - } - } - - return TreeProcessor; -}; - -getJasmineRequireObj().version = function() { - return '2.6.0'; -}; diff --git a/spec/lib/jasmine-2.6.0/jasmine_favicon.png b/spec/lib/jasmine-2.6.0/jasmine_favicon.png deleted file mode 100644 index 3b84583be..000000000 Binary files a/spec/lib/jasmine-2.6.0/jasmine_favicon.png and /dev/null differ diff --git a/spec/runSVGDomTest.js b/spec/runSVGDomTest.js new file mode 100644 index 000000000..cada48110 --- /dev/null +++ b/spec/runSVGDomTest.js @@ -0,0 +1,17 @@ +/* + This file has to be run with esm because node does not understand imports yet: + node -r esm ./spec/runSvgdomTest.js || true + + Without "|| true" node reports a super long error when a test fails + */ + +import Jasmine from 'jasmine' +const jasmine = new Jasmine() + +jasmine.loadConfig({ + spec_dir: '/', + spec_files: ['spec/spec/*/**/*.js'], + helpers: ['spec/setupSVGDom.js'] +}) + +jasmine.execute() diff --git a/spec/setupBrowser.js b/spec/setupBrowser.js new file mode 100644 index 000000000..429fa88fa --- /dev/null +++ b/spec/setupBrowser.js @@ -0,0 +1,15 @@ +/* globals beforeEach, afterEach, jasmine */ +import { buildCanvas, clear } from './helpers.js' + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 500 + +beforeEach(() => { + // buildFixtures() + buildCanvas() + window.container = document.getElementById('canvas') +}) + +afterEach(() => { + clear() + window.container = null +}) diff --git a/spec/setupSVGDom.js b/spec/setupSVGDom.js new file mode 100644 index 000000000..be5b80317 --- /dev/null +++ b/spec/setupSVGDom.js @@ -0,0 +1,25 @@ +import './RAFPlugin.js' +import { createHTMLWindow } from 'svgdom' + +/* globals beforeEach, afterEach, jasmine */ +import { buildCanvas, clear } from './helpers.js' +import { registerWindow } from '../src/main.js' + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 200 + +function setup() { + const win = createHTMLWindow() + registerWindow(win, win.document) + buildCanvas() + // buildFixtures() + global.container = win.document.getElementById('canvas') +} + +function teardown() { + clear() + global.container = null + registerWindow() +} + +beforeEach(setup) +afterEach(teardown) diff --git a/spec/spec/adopter.js b/spec/spec/adopter.js deleted file mode 100644 index a73bc3525..000000000 --- a/spec/spec/adopter.js +++ /dev/null @@ -1,81 +0,0 @@ -describe('Adopter', function() { - var path, polyline, polygon, linearGradient, radialGradient - - beforeEach(function() { - path = SVG.get('lineAB') - polyline = SVG.get('inlineSVG').select('polyline').first() - polygon = SVG.get('inlineSVG').select('polygon').first() - linearGradient = SVG.get('inlineSVG').select('linearGradient').first() - radialGradient = SVG.get('inlineSVG').select('radialGradient').first() - }) - - describe('with SVG.Doc instance', function() { - it('adopts the main svg document when parent() method is called on first level children', function() { - expect(path.parent() instanceof SVG.Doc).toBeTruthy() - }) - it('defines a xmlns attribute', function() { - expect(path.parent().node.getAttribute('xmlns')).toBe(SVG.ns) - }) - it('defines a version attribute', function() { - expect(path.parent().node.getAttribute('version')).toBe('1.1') - }) - it('defines a xmlns:xlink attribute', function() { - expect(path.parent().node.getAttribute('xmlns:xlink')).toBe(SVG.xlink) - }) - it('initializes a defs node', function() { - expect(path.parent()._defs).toBe(path.parent().defs()) - }) - }) - - describe('with SVG.Path instance', function() { - it('adopts an exiting path element', function() { - expect(path instanceof SVG.Path).toBeTruthy() - }) - it('modifies an adopted element', function() { - path.fill('#f06') - expect(path.node.getAttribute('fill')).toBe('#ff0066') - }) - it('parses d attribute to SVG.PathArray', function() { - expect(path.array() instanceof SVG.PathArray).toBeTruthy() - }) - }) - - describe('with SVG.Polyline instance', function() { - it('parses points attribute to SVG.PointArray', function() { - expect(polyline.array() instanceof SVG.PointArray).toBeTruthy() - }) - }) - - describe('with SVG.Polygon instance', function() { - it('parses points attribute to SVG.PointArray', function() { - expect(polygon.array() instanceof SVG.PointArray).toBeTruthy() - }) - }) - - describe('with linear SVG.Gradient instance', function() { - it('is instance of SVG.Gradient', function() { - expect(linearGradient instanceof SVG.Gradient).toBeTruthy() - }) - it('has type of linear', function() { - expect(linearGradient.type).toBe('linearGradient') // actually it should be 'linear'. see #606 - }) - }) - - describe('with radial SVG.Gradient instance', function() { - it('is instance of SVG.Gradient', function() { - expect(radialGradient instanceof SVG.Gradient).toBeTruthy() - }) - it('has type of radial', function() { - expect(radialGradient.type).toBe('radialGradient') // actually it should be 'radial'. see #606 - }) - }) - - describe('with node that has no matching svg.js class', function() { - it('wraps the node in the base SVG.Element class', function() { - var desc = SVG.get('inlineSVG').select('desc').first() - expect(desc instanceof SVG.Element).toBeTruthy() - }) - }) - - -}) \ No newline at end of file diff --git a/spec/spec/animation/Animator.js b/spec/spec/animation/Animator.js new file mode 100644 index 000000000..695dca7eb --- /dev/null +++ b/spec/spec/animation/Animator.js @@ -0,0 +1,91 @@ +/* globals describe, expect, it, beforeEach, afterEach, jasmine */ + +import { Animator, Queue } from '../../../src/main.js' +import { getWindow } from '../../../src/utils/window.js' + +describe('Animator.js', () => { + beforeEach(() => { + jasmine.RequestAnimationFrame.install(getWindow()) + Animator.timeouts = new Queue() + Animator.frames = new Queue() + Animator.immediates = new Queue() + Animator.nextDraw = null + }) + + afterEach(() => { + jasmine.RequestAnimationFrame.uninstall(getWindow()) + }) + + describe('timeout()', () => { + it('calls a function after a specific time', () => { + var spy = jasmine.createSpy('tester') + Animator.timeout(spy, 100) + + jasmine.RequestAnimationFrame.tick(99) + expect(spy).not.toHaveBeenCalled() + jasmine.RequestAnimationFrame.tick() + expect(spy).toHaveBeenCalled() + }) + }) + + describe('cancelTimeout()', () => { + it('cancels a timeout which was created with timeout()', () => { + var spy = jasmine.createSpy('tester') + var id = Animator.timeout(spy, 100) + Animator.clearTimeout(id) + + expect(spy).not.toHaveBeenCalled() + jasmine.RequestAnimationFrame.tick(100) + expect(spy).not.toHaveBeenCalled() + }) + }) + + describe('frame()', () => { + it('calls a function at the next animationFrame', () => { + var spy = jasmine.createSpy('tester') + + Animator.frame(spy) + expect(spy).not.toHaveBeenCalled() + jasmine.RequestAnimationFrame.tick() + expect(spy).toHaveBeenCalled() + }) + }) + + describe('cancelFrame()', () => { + it('cancels a single frame which was created with frame()', () => { + var spy = jasmine.createSpy('tester') + + const id = Animator.frame(spy) + Animator.cancelFrame(id) + + expect(spy).not.toHaveBeenCalled() + jasmine.RequestAnimationFrame.tick() + expect(spy).not.toHaveBeenCalled() + }) + }) + + describe('immediate()', () => { + it('calls a function at the next animationFrame but after all frames are processed', () => { + var spy = jasmine.createSpy('tester') + + Animator.immediate(spy) + + expect(spy).not.toHaveBeenCalled() + jasmine.RequestAnimationFrame.tick() + expect(spy).toHaveBeenCalled() + }) + }) + + describe('cancelImmediate()', () => { + it('cancels an immediate cakk which was created with immediate()', () => { + var spy = jasmine.createSpy('tester') + + const id = Animator.immediate(spy) + Animator.cancelImmediate(id) + + expect(spy).not.toHaveBeenCalled() + jasmine.RequestAnimationFrame.tick() + expect(spy).not.toHaveBeenCalled() + }) + }) +}) diff --git a/spec/spec/animation/Controller.js b/spec/spec/animation/Controller.js new file mode 100644 index 000000000..b81678bc1 --- /dev/null +++ b/spec/spec/animation/Controller.js @@ -0,0 +1,408 @@ +/* globals describe, expect, it, jasmine */ + +import { easing, defaults } from '../../../src/main.js' +import { + Stepper, + Ease, + Controller, + Spring, + PID +} from '../../../src/animation/Controller.js' + +const { any, createSpy } = jasmine + +describe('Controller.js', () => { + describe('easing', () => { + var easedValues = { + '-': 0.5, + '<>': 0.5, + '>': 0.7071, + '<': 0.2929 + } + + ;['-', '<>', '<', '>'].forEach((el) => { + describe(el, () => { + it('is 0 at 0', () => { + expect(easing[el](0)).toBe(0) + }) + it('is 1 at 1', () => { + expect(Math.round(easing[el](1) * 1000) / 1000).toBe(1) // we need to round cause for some reason at some point 1==0.999999999 + }) + it('is eased at 0.5', () => { + expect(easing[el](0.5)).toBeCloseTo(easedValues[el]) + }) + }) + }) + + describe('beziere()', () => { + const b1 = easing.bezier(0.25, 0.25, 0.75, 0.75) + const b2 = easing.bezier(-0.25, -0.25, 0.75, 0.75) + const b3 = easing.bezier(0.5, 0.5, 2, 2) + const b4 = easing.bezier(1, 1, 2, 2) + const b5 = easing.bezier(-1, -1, -2, -2) + + it('is 0 at 0', () => { + expect(b1(0)).toBe(0) + }) + + it('is 1 at 1', () => { + expect(b1(1)).toBe(1) + }) + + it('is eased at 0.5', () => { + expect(b1(0.5)).toBe(0.5) + expect(b2(0.5)).toBe(0.3125) + expect(b3(0.5)).toBe(1.0625) + expect(b4(0.5)).toBe(1.25) + expect(b5(0.5)).toBe(-1) + }) + + it('handles values bigger 1', () => { + expect(b1(1.5)).toBe(1.5) + expect(b2(1.5)).toBe(1.5) + expect(b3(1.5)).toBe(1.5) + expect(b4(1.5)).toBe(1) + expect(b5(1.5)).toBe(1.5) + }) + + it('handles values lower 0', () => { + expect(b1(-0.5)).toBe(-0.5) + expect(b2(-0.5)).toBe(-0.5) + expect(b3(-0.5)).toBe(-0.5) + expect(b4(-0.5)).toBe(-0.5) + expect(b5(-0.5)).toBe(0) + }) + }) + + describe('steps()', () => { + const s1 = easing.steps(5) + const s2 = easing.steps(5, 'start') + const s3 = easing.steps(5, 'end') + const s4 = easing.steps(5, 'none') + const s5 = easing.steps(5, 'both') + + it('is 0 at 0', () => { + expect(s1(0)).toBe(0) + expect(s1(0, true)).toBe(0) + expect(s2(0)).toBe(0.2) + expect(s2(0, true)).toBe(0) + expect(s3(0)).toBe(0) + expect(s3(0, true)).toBe(0) + expect(s4(0)).toBe(0) + expect(s4(0, true)).toBe(0) + expect(s5(0)).toBe(1 / 6) + expect(s5(0, true)).toBe(0) + }) + + it('also works at values < 0', () => { + expect(s1(-0.1)).toBe(-0.2) + expect(s1(-0.1, true)).toBe(-0.2) + expect(s2(-0.1)).toBe(0) + expect(s2(-0.1, true)).toBe(0) + expect(s3(-0.1)).toBe(-0.2) + expect(s3(-0.1, true)).toBe(-0.2) + expect(s4(-0.1)).toBe(-0.25) + expect(s4(-0.1, true)).toBe(-0.25) + expect(s5(-0.1)).toBe(0) + expect(s5(-0.1, true)).toBe(0) + }) + + it('is 1 at 1', () => { + expect(s1(1)).toBe(1) + expect(s1(1, true)).toBe(0.8) + expect(s2(1)).toBe(1) + expect(s2(1, true)).toBe(1) + expect(s3(1)).toBe(1) + expect(s3(1, true)).toBe(0.8) + expect(s4(1)).toBe(1) + expect(s4(1, true)).toBe(1) + expect(s5(1)).toBe(1) + expect(s5(1, true)).toBe(5 / 6) + }) + + it('also works at values > 1', () => { + expect(s1(1.1)).toBe(1) + expect(s1(1.1, true)).toBe(1) + expect(s2(1.1)).toBe(1.2) + expect(s2(1.1, true)).toBe(1.2) + expect(s3(1.1)).toBe(1) + expect(s3(1.1, true)).toBe(1) + expect(s4(1.1)).toBe(1.25) + expect(s4(1.1, true)).toBe(1.25) + expect(s5(1.1)).toBe(1) + expect(s5(1.1, true)).toBe(1) + }) + + it('is eased at 0.1', () => { + expect(s1(0.1)).toBe(0) + expect(s1(0.1, true)).toBe(0) + expect(s2(0.1)).toBe(0.2) + expect(s2(0.1, true)).toBe(0) + expect(s3(0.1)).toBe(0) + expect(s3(0.1, true)).toBe(0) + expect(s4(0.1)).toBe(0) + expect(s4(0.1, true)).toBe(0) + expect(s5(0.1)).toBe(1 / 6) + expect(s5(0.1, true)).toBe(0) + }) + + it('is eased at 0.15', () => { + expect(s1(0.15)).toBe(0) + expect(s1(0.15, true)).toBe(0) + expect(s2(0.15)).toBe(0.2) + expect(s2(0.15, true)).toBe(0) + expect(s3(0.15)).toBe(0) + expect(s3(0.15, true)).toBe(0) + expect(s4(0.15)).toBe(0) + expect(s4(0.15, true)).toBe(0) + expect(s5(0.15)).toBe(1 / 6) + expect(s5(0.15, true)).toBe(0) + }) + + it('is eased at 0.2', () => { + expect(s1(0.2)).toBe(0.2) + expect(s1(0.2, true)).toBe(0.2) + expect(s2(0.2)).toBe(0.4) + expect(s2(0.2, true)).toBe(0.4) + expect(s3(0.2)).toBe(0.2) + expect(s3(0.2, true)).toBe(0.2) + expect(s4(0.2)).toBe(0.25) + expect(s4(0.2, true)).toBe(0.25) + expect(s5(0.2)).toBe(1 / 3) + expect(s5(0.2, true)).toBe(1 / 3) + }) + + it('is eased at 0.25', () => { + expect(s1(0.25)).toBe(0.2) + expect(s1(0.25, true)).toBe(0.2) + expect(s2(0.25)).toBe(0.4) + expect(s2(0.25, true)).toBe(0.4) + expect(s3(0.25)).toBe(0.2) + expect(s3(0.25, true)).toBe(0.2) + expect(s4(0.25)).toBe(0.25) + expect(s4(0.25, true)).toBe(0.25) + expect(s5(0.25)).toBe(1 / 3) + expect(s5(0.25, true)).toBe(1 / 3) + }) + }) + }) + + describe('Stepper', () => { + it('has a done() method', () => { + const stepper = new Stepper() + expect(stepper).toEqual(any(Stepper)) + expect(stepper.done()).toBe(false) + }) + }) + + describe('Ease', () => { + describe('()', () => { + it('wraps the default easing function by default', () => { + const ease = new Ease() + expect(ease.ease).toBe(easing[defaults.timeline.ease]) + }) + + it('wraps an easing function found in "easing"', () => { + const ease = new Ease('-') + expect(ease.ease).toBe(easing['-']) + }) + + it('wraps a a custom easing function', () => { + const ease = new Ease(easing['-']) + expect(ease.ease).toBe(easing['-']) + }) + }) + + describe('step()', () => { + it('provides an eased value to a position', () => { + let ease = new Ease(easing['-']) + expect(ease.step(2, 4, 0.5)).toBe(3) + + ease = new Ease(() => 3) + expect(ease.step(2, 4, 0.5)).toBe(8) + + ease = new Ease() + expect(ease.step(2, 4, 0.5)).toBeCloseTo(3.414, 3) + }) + + it('jumps to "to" value at pos 1 if from is not a number', () => { + const ease = new Ease('-') + expect(ease.step('Hallo', 'Welt', 0.999)).toBe('Hallo') + expect(ease.step('Hallo', 'Welt', 1)).toBe('Welt') + }) + }) + }) + + describe('Controller', () => { + describe('()', () => { + it('constructs a controller with the given stepper function set', () => { + const spy = createSpy() + const controller = new Controller(spy) + expect(controller).toEqual(any(Controller)) + expect(controller.stepper).toBe(spy) + }) + }) + + describe('step()', () => { + it('runs the stepper with current value, target value, dt and context', () => { + const spy = createSpy().and.returnValue('foo') + const controller = new Controller(spy) + expect(controller.step(10, 20, 30, 'bar')).toBe('foo') + expect(spy).toHaveBeenCalledWith(10, 20, 30, 'bar') + }) + }) + + describe('done()', () => { + it('returns given values "done" property', () => { + const spy = createSpy() + const controller = new Controller(spy) + expect(controller.done({ done: 'yes' })).toBe('yes') + }) + }) + }) + + describe('Spring', () => { + describe('()', () => { + it('creates a spring with default duration and overshoot', () => { + const spring = new Spring() + expect(spring).toEqual(any(Spring)) + expect(spring.duration()).toBe(500) + expect(spring.overshoot()).toBe(0) + }) + + it('creates a spring with given duration and overshoot', () => { + const spring = new Spring(100, 10) + expect(spring).toEqual(any(Spring)) + expect(spring.duration()).toBe(100) + expect(spring.overshoot()).toBe(10) + }) + }) + + describe('duration()', () => { + it('gets and sets a new duration for the spring controller', () => { + const spring = new Spring().duration(100) + expect(spring.duration()).toBe(100) + }) + }) + + describe('overshoot()', () => { + it('gets and sets a new overshoot for the spring controller', () => { + const spring = new Spring().overshoot(10) + expect(spring.overshoot()).toBe(10) + }) + }) + + describe('step()', () => { + it('calculates the new spring position', () => { + const spring = new Spring() + expect(spring.step(0, 100, 16, {})).toBeCloseTo(0.793, 3) + }) + + it('returns current if current is a string', () => { + const spring = new Spring() + expect(spring.step('Hallo', 'Welt', 16, {})).toBe('Hallo') + }) + + it('returns current if dt is 0', () => { + const spring = new Spring() + expect(spring.step(0, 100, 0, {})).toBe(0) + }) + + it('is done if dt is infinity and returns target', () => { + const spring = new Spring() + const context = {} + expect(spring.step(0, 100, Infinity, context)).toBe(100) + expect(spring.done(context)).toBe(true) + }) + + it('uses dt of 16 if it is over 100', () => { + const spring = new Spring() + expect(spring.step(0, 100, 101, {})).toBe(spring.step(0, 100, 16, {})) + }) + }) + }) + + describe('PID', () => { + describe('()', () => { + it('creates a PID controller with default values', () => { + const pid = new PID() + expect(pid).toEqual(any(PID)) + expect(pid.p()).toBe(0.1) + expect(pid.i()).toBe(0.01) + expect(pid.d()).toBe(0) + expect(pid.windup()).toBe(1000) + }) + + it('creates a PID controller with given values', () => { + const pid = new PID(1, 2, 3, 4) + expect(pid).toEqual(any(PID)) + expect(pid.p()).toBe(1) + expect(pid.i()).toBe(2) + expect(pid.d()).toBe(3) + expect(pid.windup()).toBe(4) + }) + }) + + describe('p()', () => { + it('gets and sets the p parameter of the controller', () => { + const pid = new PID().p(100) + expect(pid.p()).toBe(100) + }) + }) + + describe('i()', () => { + it('gets and sets the i parameter of the controller', () => { + const pid = new PID().i(100) + expect(pid.i()).toBe(100) + }) + }) + + describe('d()', () => { + it('gets and sets the d parameter of the controller', () => { + const pid = new PID().d(100) + expect(pid.d()).toBe(100) + }) + }) + + describe('windup()', () => { + it('gets and sets the windup parameter of the controller', () => { + const pid = new PID().windup(100) + expect(pid.windup()).toBe(100) + }) + }) + + describe('step()', () => { + it('returns current if current is a string', () => { + const pid = new PID() + expect(pid.step('Hallo', 'Welt', 16, {})).toBe('Hallo') + }) + + it('returns current if dt is 0', () => { + const pid = new PID() + expect(pid.step(0, 100, 0, {})).toBe(0) + }) + + it('is done if dt is infinity and returns target', () => { + const pid = new PID() + const context = {} + expect(pid.step(0, 100, Infinity, context)).toBe(100) + expect(pid.done(context)).toBe(true) + }) + + it('calculates a new value', () => { + const pid = new PID() + expect(pid.step(0, 100, 16, {})).toBe(20) + }) + + it('uses antiwindup to restrict i power', () => { + const pid = new PID(0, 5, 0, 100) + expect(pid.step(0, 100, 1000, {})).toBe(500) + }) + + it('does not use antiwindup if disabled', () => { + const pid = new PID(0, 5, 0, false) + expect(pid.step(0, 100, 1000, {})).toBe(500000) + }) + }) + }) +}) diff --git a/spec/spec/animation/Morphable.js b/spec/spec/animation/Morphable.js new file mode 100644 index 000000000..d5d313ad3 --- /dev/null +++ b/spec/spec/animation/Morphable.js @@ -0,0 +1,565 @@ +/* globals describe, expect, it, jasmine */ + +import { + Morphable, + NonMorphable, + ObjectBag, + Color, + Box, + Matrix, + PointArray, + PathArray, + TransformBag, + Number as SVGNumber, + Array as SVGArray +} from '../../../src/main.js' +import { Stepper, easing, Ease } from '../../../src/animation/Controller.js' + +const { objectContaining, arrayContaining, any } = jasmine + +describe('Morphable.js', () => { + describe('()', () => { + it('sets a default stepper', () => { + const morpher = new Morphable() + expect(morpher.stepper().ease).toBe(easing['-']) + }) + + it('sets the passed stepper', () => { + const ease = new Ease() + const morpher = new Morphable(ease) + expect(morpher.stepper()).toBe(ease) + }) + }) + + describe('constructors', () => { + it('Morphable with SVGNumber', () => { + const morpher = new Morphable().from(10).to(5) + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(SVGNumber) + expect(morpher.at(0.5)).toEqual(any(SVGNumber)) + expect(morpher.at(0.5).valueOf()).toBe(7.5) + }) + + it('Morphable with String', () => { + const morpher = new Morphable().from('foo').to('bar') + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(NonMorphable) + expect(morpher.at(0.5)).toEqual(any(NonMorphable)) + expect(morpher.at(0.5).valueOf()).toBe('foo') + expect(morpher.at(1).valueOf()).toBe('bar') + }) + + it('Morphable with Object', () => { + const morpher = new Morphable().from({ a: 5, b: 10 }).to({ a: 10, b: 20 }) + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(ObjectBag) + expect(morpher.at(0.5)).toEqual(any(Object)) + expect(morpher.at(0.5).valueOf()).toEqual( + objectContaining({ a: new SVGNumber(7.5), b: new SVGNumber(15) }) + ) + }) + + it('Morphable from object containing css values', () => { + const morpher = new Morphable() + .from({ opacity: '0', 'stroke-width': '10px' }) + .to({ opacity: 1, 'stroke-width': 20 }) + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(ObjectBag) + expect(morpher.at(0.5)).toEqual(any(Object)) + expect(morpher.at(0.5).valueOf()).toEqual( + objectContaining({ + opacity: new SVGNumber(0.5), + 'stroke-width': new SVGNumber('15px') + }) + ) + }) + + it('Creates a morphable out of an SVGNumber', () => { + const morpher = new SVGNumber(5).to(10) + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(SVGNumber) + expect(morpher.at(0.5)).toEqual(any(SVGNumber)) + expect(morpher.at(0.5).valueOf()).toBe(7.5) + }) + + it('Creates a morphable out of an Color', () => { + const morpher = new Color('#fff').to('#000') + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(Color) + expect(morpher.at(0.5)).toEqual(any(Color)) + expect(morpher.at(0.5).toHex()).toBe('#808080') + }) + + it('Creates a morphable out of an Box', () => { + const morpher = new Box(1, 2, 3, 4).to([5, 6, 7, 8]) + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(Box) + expect(morpher.at(0.5)).toEqual(any(Box)) + expect(morpher.at(0.5)).toEqual( + objectContaining({ x: 3, y: 4, width: 5, height: 6 }) + ) + }) + + it('Creates a morphable out of an Matrix', () => { + const morpher = new Matrix(1, 2, 3, 4, 5, 6).to([3, 4, 5, 6, 7, 8]) + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(Matrix) + expect(morpher.at(0.5)).toEqual(any(Matrix)) + expect(morpher.at(0.5)).toEqual( + objectContaining(new Matrix(2, 3, 4, 5, 6, 7)) + ) + }) + + it('Creates a morphable out of an SVGArray', () => { + const morpher = new SVGArray([1, 2, 3, 4, 5, 6]).to([3, 4, 5, 6, 7, 8]) + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(SVGArray) + expect(morpher.at(0.5)).toEqual(any(SVGArray)) + expect(morpher.at(0.5).toArray()).toEqual( + arrayContaining([2, 3, 4, 5, 6, 7]) + ) + }) + + it('Creates a morphable out of an PointArray', () => { + const morpher = new PointArray([1, 2, 3, 4, 5, 6]).to([3, 4, 5, 6, 7, 8]) + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(PointArray) + expect(morpher.at(0.5)).toEqual(any(PointArray)) + expect(morpher.at(0.5).toArray()).toEqual( + arrayContaining([2, 3, 4, 5, 6, 7]) + ) + }) + + it('Creates a morphable out of an PathArray', () => { + const morpher = new PathArray(['M', 1, 2, 'L', 3, 4, 'L', 5, 6]).to([ + 'M', + 3, + 4, + 'L', + 5, + 6, + 'L', + 7, + 8 + ]) + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(PathArray) + expect(morpher.at(0.5)).toEqual(any(PathArray)) + expect(morpher.at(0.5).toArray()).toEqual( + arrayContaining(['M', 2, 3, 'L', 4, 5, 'L', 6, 7]) + ) + }) + + it('creates a morphable from unmorphable types', () => { + const morpher = new Morphable().from('Hallo').to('Welt') + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(NonMorphable) + expect(morpher.at(0.5)).toEqual(any(NonMorphable)) + expect(morpher.at(0.5).valueOf()).toBe('Hallo') + expect(morpher.at(1).valueOf()).toBe('Welt') + }) + + it('Creates a morphable out of an TransformBag', () => { + const morpher = new TransformBag({ rotate: 0, translateX: 0 }).to({ + rotate: 50, + translateX: 20 + }) + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(TransformBag) + expect(morpher.at(0.5)).toEqual(any(TransformBag)) + + expect(morpher.at(0.5)).toEqual( + objectContaining({ rotate: 25, translateX: 10 }) + ) + }) + + it('Creates a morphable out of an ObjectBag', () => { + const morpher = new ObjectBag({ a: 5, b: 10 }).to({ a: 10, b: 20 }) + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(ObjectBag) + expect(morpher.at(0.5)).toEqual(any(Object)) + expect(morpher.at(0.5).valueOf()).toEqual( + objectContaining({ a: new SVGNumber(7.5), b: new SVGNumber(15) }) + ) + }) + + it('creates a morphable from a color string', () => { + let morpher = new Morphable().from('#fff').to('#000') + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(Color) + expect(morpher.at(0.5)).toEqual(any(Color)) + expect(morpher.at(0.5).toHex()).toBe('#808080') + + morpher = new Morphable().from('rgb(255,255,255)').to('rgb(0,0,0)') + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(Color) + expect(morpher.at(0.5)).toEqual(any(Color)) + expect(morpher.at(0.5).toHex()).toBe('#808080') + }) + + it('creates a morphable from path string', () => { + const morpher = new Morphable().from('M 0 0 L 10 10').to('M 0 0 L 20 20') + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(PathArray) + expect(morpher.at(0.5)).toEqual(any(PathArray)) + expect(morpher.at(0.5).toString()).toBe('M0 0L15 15 ') + }) + + it('creates a morphable from number string', () => { + let morpher = new Morphable().from('10').to('20') + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(SVGNumber) + expect(morpher.at(0.5)).toEqual(any(SVGNumber)) + expect(morpher.at(0.5).toString()).toBe('15') + + morpher = new Morphable().from('10px').to('20px') + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(SVGNumber) + expect(morpher.at(0.5)).toEqual(any(SVGNumber)) + expect(morpher.at(0.5).toString()).toBe('15px') + }) + + it('creates a morphable from delimited string', () => { + const morpher = new Morphable().from(' 0 1, 2 , 3 ').to('4,5,6,7') + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(SVGArray) + expect(morpher.at(0.5)).toEqual(any(SVGArray)) + expect(morpher.at(0.5)).toEqual([2, 3, 4, 5]) + }) + + it('creates a morphable from an array', () => { + const morpher = new Morphable().from([0, 1, 2, 3]).to([4, 5, 6, 7]) + + expect(morpher).toEqual(any(Morphable)) + expect(morpher.type()).toBe(SVGArray) + expect(morpher.at(0.5)).toEqual(any(SVGArray)) + expect(morpher.at(0.5)).toEqual([2, 3, 4, 5]) + }) + + it('converts the to-color to the from-type', () => { + const morpher = new Color('#fff').to(new Color(1, 2, 3, 'hsl')) + expect(new Color(morpher.from()).space).toBe('rgb') + expect(morpher.at(0.5).space).toBe('rgb') + }) + + it('converts the from-color to the to-type', () => { + const morpher = new Morphable().to(new Color(1, 2, 3, 'hsl')).from('#fff') + expect(new Color(morpher.from()).space).toBe('hsl') + expect(morpher.at(0.5).space).toBe('hsl') + }) + }) + + describe('from()', () => { + it('sets the type of the runner', () => { + const morpher = new Morphable().from(5) + expect(morpher.type()).toBe(SVGNumber) + }) + + it('sets the from attribute to an array representation of the morphable type', () => { + const morpher = new Morphable().from(5) + expect(morpher.from()).toEqual(arrayContaining([5])) + }) + }) + + describe('type()', () => { + it('sets the type of the runner', () => { + const morpher = new Morphable().type(SVGNumber) + expect(morpher._type).toBe(SVGNumber) + }) + + it('gets the type of the runner', () => { + const morpher = new Morphable().type(SVGNumber) + expect(morpher.type()).toBe(SVGNumber) + }) + }) + + describe('to()', () => { + it('sets the type of the runner', () => { + const morpher = new Morphable().to(5) + expect(morpher.type()).toBe(SVGNumber) + }) + + it('sets the from attribute to an array representation of the morphable type', () => { + const morpher = new Morphable().to(5) + expect(morpher.to()).toEqual(arrayContaining([5])) + }) + }) + + describe('stepper()', () => { + it('sets and gets the stepper of the Morphable', () => { + const stepper = new Stepper() + const morpher = new Morphable().stepper(stepper) + expect(morpher.stepper()).toBe(stepper) + }) + }) + + describe('NonMorphable', () => { + describe('()', () => { + it('wraps any type into a NonMorphable from an array', () => { + const non = new NonMorphable([5]) + expect(non.valueOf()).toBe(5) + }) + + it('wraps any type into a NonMorphable from any type', () => { + expect(new NonMorphable(5).valueOf()).toBe(5) + expect(new NonMorphable('Hello').valueOf()).toBe('Hello') + }) + }) + + describe('toArray()', () => { + it('returns array representation of NonMorphable', () => { + expect(new NonMorphable(5).toArray()).toEqual([5]) + expect(new NonMorphable('Hello').toArray()).toEqual(['Hello']) + }) + }) + }) + + describe('TransformBag', () => { + describe('()', () => { + it('creates an object which holds transformations for morphing by passing array', () => { + const bag = new TransformBag([0, 1, 2, 3, 4, 5, 6, 7]) + expect(bag.toArray()).toEqual([0, 1, 2, 3, 4, 5, 6, 7]) + }) + + it('creates an object which holds transformations for morphing by passing object', () => { + const bag = new TransformBag({ + scaleX: 0, + scaleY: 1, + shear: 2, + rotate: 3, + translateX: 4, + translateY: 5, + originX: 6, + originY: 7 + }) + + expect(bag.toArray()).toEqual([0, 1, 2, 3, 4, 5, 6, 7]) + }) + }) + + describe('toArray()', () => { + it('creates an array out of the transform values', () => { + const bag = new TransformBag([0, 1, 2, 3, 4, 5, 6, 7]) + expect(bag.toArray()).toEqual([0, 1, 2, 3, 4, 5, 6, 7]) + }) + }) + }) + + describe('ObjectBag', () => { + describe('()', () => { + it('wraps an object into a morphable object by passing an array', () => { + const bag = new ObjectBag([ + 'foo', + SVGNumber, + 2, + 1, + '', + 'bar', + SVGNumber, + 2, + 2, + '', + 'baz', + SVGNumber, + 2, + 3, + '' + ]) + expect(bag.values).toEqual([ + 'foo', + SVGNumber, + 2, + 1, + '', + 'bar', + SVGNumber, + 2, + 2, + '', + 'baz', + SVGNumber, + 2, + 3, + '' + ]) + }) + + it('wraps an object into a morphable object by passing an object', () => { + const bag = new ObjectBag({ foo: 1, bar: 2, baz: 3 }) + expect(bag.values).toEqual([ + 'bar', + SVGNumber, + 2, + 2, + '', + 'baz', + SVGNumber, + 2, + 3, + '', + 'foo', + SVGNumber, + 2, + 1, + '' + ]) + }) + + it('wraps an object with morphable values in an ObjectBag', () => { + const bag = new ObjectBag({ fill: new Color(), bar: 2 }) + expect(bag.values).toEqual([ + 'bar', + SVGNumber, + 2, + 2, + '', + 'fill', + Color, + 5, + 0, + 0, + 0, + 0, + 'rgb' + ]) + }) + + it('wraps an array with morphable representation in an ObjectBag', () => { + const bag = new ObjectBag([ + 'bar', + SVGNumber, + 2, + 2, + '', + 'fill', + Color, + 5, + 0, + 0, + 0, + 0, + 'rgb' + ]) + expect(bag.toArray()).toEqual([ + 'bar', + SVGNumber, + 2, + 2, + '', + 'fill', + Color, + 5, + 0, + 0, + 0, + 0, + 'rgb' + ]) + }) + }) + + describe('toArray()', () => { + it('creates an array out of the object', () => { + const bag = new ObjectBag({ foo: 1, bar: 2, baz: 3 }) + expect(bag.toArray()).toEqual([ + 'bar', + SVGNumber, + 2, + 2, + '', + 'baz', + SVGNumber, + 2, + 3, + '', + 'foo', + SVGNumber, + 2, + 1, + '' + ]) + }) + + it('creates a flattened array out of the object with morphable values', () => { + const bag = new ObjectBag({ fill: new Color(), bar: 2 }) + expect(bag.toArray()).toEqual([ + 'bar', + SVGNumber, + 2, + 2, + '', + 'fill', + Color, + 5, + 0, + 0, + 0, + 0, + 'rgb' + ]) + }) + }) + + describe('valueOf()', () => { + it('creates morphable objects from the stored values', () => { + const bag = new ObjectBag({ foo: 1, bar: 2, baz: 3 }) + expect(bag.valueOf()).toEqual({ + foo: new SVGNumber(1), + bar: new SVGNumber(2), + baz: new SVGNumber(3) + }) + }) + + it('creates also morphable objects from the stored values', () => { + const bag = new ObjectBag({ fill: new Color(), bar: 2 }) + expect(bag.valueOf()).toEqual({ + fill: objectContaining(new Color()), + bar: new SVGNumber(2) + }) + }) + }) + + describe('align()', () => { + it('aligns color spaces between two object bags', () => { + const bag1 = new ObjectBag({ x: 1, y: '#fff' }) + const bag2 = new ObjectBag({ x: 2, y: new Color().hsl() }) + bag1.align(bag2.toArray()) + expect(bag1.toArray()).toEqual([ + 'x', + SVGNumber, + 2, + 1, + '', + 'y', + Color, + 5, + 0, + 0, + 100, + 0, + 'hsl' + ]) + }) + }) + }) +}) diff --git a/spec/spec/animation/Queue.js b/spec/spec/animation/Queue.js new file mode 100644 index 000000000..b405b6258 --- /dev/null +++ b/spec/spec/animation/Queue.js @@ -0,0 +1,107 @@ +/* globals describe, expect, it */ + +import { Queue } from '../../../src/main.js' + +describe('Queue.js', function () { + describe('first ()', function () { + it('returns null if no item in the queue', function () { + var queue = new Queue() + expect(queue.first()).toEqual(null) + }) + + it('returns the first value in the queue', function () { + var queue = new Queue() + queue.push(1) + expect(queue.first()).toBe(1) + queue.push(2) + expect(queue.first()).toBe(1) + }) + }) + + describe('last ()', function () { + it('returns null if no item in the queue', function () { + var queue = new Queue() + expect(queue.last()).toEqual(null) + }) + + it('returns the last value added', function () { + var queue = new Queue() + queue.push(1) + expect(queue.last()).toBe(1) + queue.push(2) + expect(queue.last()).toBe(2) + }) + }) + + describe('push ()', function () { + it('adds an element to the end of the queue', function () { + var queue = new Queue() + queue.push(1) + queue.push(2) + queue.push(3) + + expect(queue.first()).toBe(1) + expect(queue.last()).toBe(3) + }) + + it('adds an item to the end of the queue', function () { + var queue = new Queue() + queue.push(1) + const item = queue.push(2) + queue.push(3) + queue.remove(item) + queue.push(item) + + expect(queue.first()).toBe(1) + expect(queue.last()).toBe(2) + }) + }) + + describe('remove ()', function () { + it('removes the given item from the queue', function () { + var queue = new Queue() + queue.push(1) + queue.push(2) + var item = queue.push(3) + + queue.remove(item) + + expect(queue.last()).toBe(2) + expect(queue.first()).toBe(1) + }) + + it('removes the given item from the queue', function () { + var queue = new Queue() + var item = queue.push(1) + queue.push(2) + queue.push(3) + + queue.remove(item) + + expect(queue.last()).toBe(3) + expect(queue.first()).toBe(2) + }) + }) + + describe('shift ()', function () { + it('returns nothing if queue is empty', function () { + var queue = new Queue() + var val = queue.shift() + expect(val).toBeFalsy() + }) + + it('returns the first item of the queue and removes it', function () { + var queue = new Queue() + queue.push(1) + queue.push(2) + queue.push(3) + + var val = queue.shift() + + expect(queue.last()).toBe(3) + expect(queue.first()).toBe(2) + + expect(val).toBe(1) + }) + }) +}) diff --git a/spec/spec/animation/Runner.js b/spec/spec/animation/Runner.js new file mode 100644 index 000000000..2325fd5d8 --- /dev/null +++ b/spec/spec/animation/Runner.js @@ -0,0 +1,2083 @@ +/* globals describe, expect, it, beforeEach, afterEach, spyOn, jasmine */ + +import { + Runner, + defaults, + Ease, + Controller, + SVG, + Timeline, + Rect, + Morphable, + Animator, + Queue, + Matrix, + Color, + Box, + Polygon, + PointArray +} from '../../../src/main.js' +import { FakeRunner, RunnerArray } from '../../../src/animation/Runner.js' +import { getWindow } from '../../../src/utils/window.js' +import SVGNumber from '../../../src/types/SVGNumber.js' + +const { any, createSpy, objectContaining, arrayContaining } = jasmine + +describe('Runner.js', () => { + describe('Runner', () => { + var initFn = createSpy('initFn') + var runFn = createSpy('runFn') + + beforeEach(() => { + jasmine.RequestAnimationFrame.install(getWindow()) + Animator.timeouts = new Queue() + Animator.frames = new Queue() + Animator.immediates = new Queue() + Animator.nextDraw = null + initFn.calls.reset() + runFn.calls.reset() + }) + + afterEach(() => { + jasmine.RequestAnimationFrame.uninstall(getWindow()) + }) + + describe('sanitise()', () => { + it('can handle all form of input', () => { + var fn = Runner.sanitise + + expect(fn(200, 200, 'now')).toEqual( + objectContaining({ + duration: 200, + delay: 200, + when: 'now', + times: 1, + wait: 0, + swing: false + }) + ) + + expect(fn(200, 200)).toEqual( + objectContaining({ + duration: 200, + delay: 200, + when: 'last', + times: 1, + wait: 0, + swing: false + }) + ) + + expect(fn(200)).toEqual( + objectContaining({ + duration: 200, + delay: defaults.timeline.delay, + when: 'last', + times: 1, + wait: 0, + swing: false + }) + ) + + expect(fn(runFn)).toEqual( + objectContaining({ + duration: runFn, + delay: defaults.timeline.delay, + when: 'last', + times: 1, + wait: 0, + swing: false + }) + ) + + expect(fn({ delay: 200 })).toEqual( + objectContaining({ + duration: defaults.timeline.duration, + delay: 200, + when: 'last', + times: 1, + wait: 0, + swing: false + }) + ) + + expect( + fn({ times: 3, delay: 200, when: 'now', swing: true, wait: 200 }) + ).toEqual( + objectContaining({ + duration: defaults.timeline.duration, + delay: 200, + when: 'now', + times: 3, + wait: 200, + swing: true + }) + ) + }) + }) + + describe('())', () => { + it('creates a runner with defaults', () => { + var runner = new Runner() + expect(runner instanceof Runner).toBe(true) + expect(runner._duration).toBe(defaults.timeline.duration) + expect(runner._stepper instanceof Ease).toBe(true) + }) + + it('creates a runner with duration set', () => { + var runner = new Runner(1000) + expect(runner instanceof Runner).toBe(true) + expect(runner._duration).toBe(1000) + expect(runner._stepper instanceof Ease).toBe(true) + }) + + it('creates a runner with controller set', () => { + var runner = new Runner(runFn) + expect(runner instanceof Runner).toBe(true) + expect(runner._duration).toBeFalsy() + expect(runner._stepper instanceof Controller).toBe(true) + }) + }) + + describe('queue()', () => { + it('adds another closure to the runner', () => { + var runner = new Runner() + runner.queue(initFn, runFn, true) + + expect(runner._queue[0]).toEqual( + objectContaining({ + initialiser: initFn, + initialised: false, + runner: runFn, + finished: false + }) + ) + }) + }) + + describe('step()', () => { + it('returns itself', () => { + var runner = new Runner() + expect(runner.step()).toBe(runner) + }) + + it('does nothing when not active', () => { + const runner = new Runner().active(false) + const frozen = Object.freeze(runner) + expect(frozen.step()).toEqual(runner) + }) + + it('calls initFn once and runFn at every step', () => { + var runner = new Runner() + runner.queue(initFn, runFn, false) + + runner.step() + expect(initFn).toHaveBeenCalled() + expect(runFn).toHaveBeenCalled() + + runner.step() + expect(initFn.calls.count()).toBe(1) + expect(runFn.calls.count()).toBe(2) + }) + + it('calls initFn on every step if its declarative', () => { + var runner = new Runner(new Controller()) + runner.queue(initFn, runFn, true) + + runner.step() + expect(initFn).toHaveBeenCalled() + expect(runFn).toHaveBeenCalled() + + runner.step() + expect(initFn.calls.count()).toBe(2) + expect(runFn.calls.count()).toBe(2) + }) + + function getLoop(r) { + var loopDuration = r._duration + r._wait + var loopsDone = Math.floor(r._time / loopDuration) + return loopsDone + } + + // step in time + it('steps forward a certain time', () => { + var spy = createSpy('stepper') + var r = new Runner(1000).loop(10, false, 100) + r.queue(null, spy) + + r.step(300) // should be 0.3s + expect(spy).toHaveBeenCalledWith(0.3) + expect(getLoop(r)).toBe(0) + + r.step(300) // should be 0.6s + expect(spy).toHaveBeenCalledWith(0.6) + expect(getLoop(r)).toBe(0) + + r.step(600) // should be 0.1s + expect(spy).toHaveBeenCalledWith(0.1) + expect(getLoop(r)).toBe(1) + + r.step(-300) // should be 0.9s + expect(spy).toHaveBeenCalledWith(0.9) + expect(getLoop(r)).toBe(0) + + r.step(2000) // should be 0.7s + expect(spy).toHaveBeenCalledWith(0.7) + expect(getLoop(r)).toBe(2) + + r.step(-2000) // should be 0.9s + expect(spy).toHaveBeenCalledWith(0.9) + expect(getLoop(r)).toBe(0) + }) + + it('handles dts which are bigger than the animation time', () => { + var runner = new Runner(1000) + runner.queue(initFn, runFn, true) + + runner.step(1100) + expect(initFn).toHaveBeenCalled() + expect(runFn).toHaveBeenCalledWith(1) + }) + + describe('looping', () => { + describe('without wait', () => { + describe('unreversed', () => { + describe('nonswinging', () => { + it('does behave correctly at the end of an even loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(6, false) + runner.queue(null, spy) + + runner.step(5750) + expect(spy).toHaveBeenCalledWith(0.75) + runner.step(250) + expect(spy).toHaveBeenCalledWith(1) + }) + + it('does behave correctly at the end of an uneven loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(5, false) + runner.queue(null, spy) + + runner.step(4750) + expect(spy).toHaveBeenCalledWith(0.75) + runner.step(250) + expect(spy).toHaveBeenCalledWith(1) + }) + }) + + describe('swinging', () => { + it('does behave correctly at the end of an even loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(6, true) + runner.queue(null, spy) + + runner.step(5750) + expect(spy).toHaveBeenCalledWith(0.25) + runner.step(250) + expect(spy).toHaveBeenCalledWith(0) + }) + + it('does behave correctly at the end of an uneven loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(5, true) + runner.queue(null, spy) + + runner.step(4750) + expect(spy).toHaveBeenCalledWith(0.75) + runner.step(250) + expect(spy).toHaveBeenCalledWith(1) + }) + }) + }) + + describe('reversed', () => { + describe('nonswinging', () => { + it('does behave correctly at the end of an even loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(6, false).reverse() + runner.queue(null, spy) + + runner.step(5750) + expect(spy).toHaveBeenCalledWith(0.25) + runner.step(250) + expect(spy).toHaveBeenCalledWith(0) + }) + + it('does behave correctly at the end of an uneven loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(5, false).reverse() + runner.queue(null, spy) + + runner.step(4750) + expect(spy).toHaveBeenCalledWith(0.25) + runner.step(250) + expect(spy).toHaveBeenCalledWith(0) + }) + }) + + describe('swinging', () => { + it('does behave correctly at the end of an even loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(6, true).reverse() + runner.queue(null, spy) + + runner.step(5750) + expect(spy).toHaveBeenCalledWith(0.75) + runner.step(250) + expect(spy).toHaveBeenCalledWith(1) + }) + + it('does behave correctly at the end of an uneven loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(5, true).reverse() + runner.queue(null, spy) + + runner.step(4750) + expect(spy).toHaveBeenCalledWith(0.25) + runner.step(250) + expect(spy).toHaveBeenCalledWith(0) + }) + }) + }) + }) + + describe('with wait', () => { + describe('unreversed', () => { + describe('nonswinging', () => { + it('does behave correctly at the end of an even loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(6, false, 100) + runner.queue(null, spy) + + runner.step(5450) + expect(spy).toHaveBeenCalledWith(1) + spy.calls.reset() + + runner.step(800) + expect(spy).toHaveBeenCalledWith(0.75) + runner.step(250) + expect(spy).toHaveBeenCalledWith(1) + }) + + it('does behave correctly at the end of an uneven loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(5, false, 100) + runner.queue(null, spy) + + runner.step(4350) + expect(spy).toHaveBeenCalledWith(1) + spy.calls.reset() + + runner.step(800) + expect(spy).toHaveBeenCalledWith(0.75) + runner.step(250) + expect(spy).toHaveBeenCalledWith(1) + }) + }) + + describe('swinging', () => { + it('does behave correctly at the end of an even loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(6, true, 100) + runner.queue(null, spy) + + runner.step(5450) + expect(spy).toHaveBeenCalledWith(1) + spy.calls.reset() + + runner.step(800) + expect(spy).toHaveBeenCalledWith(0.25) + runner.step(250) + expect(spy).toHaveBeenCalledWith(0) + }) + + it('does behave correctly at the end of an uneven loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(5, true, 100) + runner.queue(null, spy) + + runner.step(4350) + expect(spy).toHaveBeenCalledWith(0) + spy.calls.reset() + + runner.step(800) + expect(spy).toHaveBeenCalledWith(0.75) + + runner.step(250) + expect(spy).toHaveBeenCalledWith(1) + }) + }) + }) + + describe('reversed', () => { + describe('nonswinging', () => { + it('does behave correctly at the end of an even loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(6, false, 100).reverse() + runner.queue(null, spy) + + runner.step(5450) + expect(spy).toHaveBeenCalledWith(0) + spy.calls.reset() + + runner.step(800) + expect(spy).toHaveBeenCalledWith(0.25) + runner.step(250) + expect(spy).toHaveBeenCalledWith(0) + }) + + it('does behave correctly at the end of an uneven loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(5, false, 100).reverse() + runner.queue(null, spy) + + runner.step(4350) + expect(spy).toHaveBeenCalledWith(0) + spy.calls.reset() + + runner.step(800) + expect(spy).toHaveBeenCalledWith(0.25) + runner.step(250) + expect(spy).toHaveBeenCalledWith(0) + }) + }) + + describe('swinging', () => { + it('does behave correctly at the end of an even loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(6, true, 100).reverse() + runner.queue(null, spy) + + runner.step(5450) + expect(spy).toHaveBeenCalledWith(0) + spy.calls.reset() + + runner.step(800) + expect(spy).toHaveBeenCalledWith(0.75) + runner.step(250) + expect(spy).toHaveBeenCalledWith(1) + }) + + it('does behave correctly at the end of an uneven loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(5, true, 100).reverse() + runner.queue(null, spy) + + runner.step(4350) + expect(spy).toHaveBeenCalledWith(1) + spy.calls.reset() + + runner.step(800) + expect(spy).toHaveBeenCalledWith(0.25) + runner.step(250) + expect(spy).toHaveBeenCalledWith(0) + }) + }) + }) + }) + }) + }) + + describe('active()', () => { + it('acts as a getter without parameters', () => { + var runner = new Runner() + expect(runner.active()).toBe(true) + }) + + it('disables the runner when false is passed', () => { + var runner = new Runner() + expect(runner.active(false)).toBe(runner) + expect(runner.active()).toBe(false) + }) + + it('enables the runner when true is passed', () => { + var runner = new Runner() + expect(runner.active(false)).toBe(runner) + expect(runner.active(true)).toBe(runner) + expect(runner.active()).toBe(true) + }) + }) + + describe('duration()', () => { + it('return the full duration of the runner including all loops and waits', () => { + var runner = new Runner(800).loop(10, true, 200) + expect(runner.duration()).toBe(9800) + }) + }) + + describe('loop()', () => { + it('makes this runner looping', () => { + var runner = new Runner(1000).loop(5) + expect(runner.duration()).toBe(5000) + }) + + it('makes this runner indefinitey by passing true', () => { + var runner = new Runner(1000).loop(true) + expect(runner.duration()).toBe(Infinity) + }) + + it('makes this runner indefinitey by passing nothing', () => { + var runner = new Runner(1000).loop() + expect(runner.duration()).toBe(Infinity) + }) + }) + + describe('time()', () => { + it('returns itself', () => { + var runner = new Runner() + expect(runner.time(0)).toBe(runner) + }) + + it('acts as a getter with no parameter passed', () => { + var runner = new Runner() + expect(runner.time()).toBe(0) + }) + + it('reschedules the runner to a new time', () => { + var runner = new Runner() + runner.time(10) + + expect(runner.time()).toBe(10) + }) + + it('calls step to reschedule', () => { + var runner = new Runner() + spyOn(runner, 'step') + runner.time(10) + + expect(runner.step).toHaveBeenCalledWith(10) + }) + }) + + describe('loops()', () => { + it('get the loops of a runner', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).queue(null, spy) + + runner.step(300) + expect(spy).toHaveBeenCalledWith(0.3) + + expect(runner.loops()).toBe(0.3) + }) + it('sets the loops of the runner', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).queue(null, spy) + + expect(runner.loops(0.5).loops()).toBe(0.5) + expect(spy).toHaveBeenCalledWith(0.5) + + expect(runner.loops(0.1).loops()).toBe(0.1) + expect(spy).toHaveBeenCalledWith(0.1) + + expect(runner.loops(1.5).loops()).toBe(1) + expect(spy).toHaveBeenCalledWith(1) + }) + it('sets the loops of the runner in a loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(5, true, 500).queue(null, spy) + + expect(runner.loops(1.3).loops()).toBe(1.3) + expect(spy).toHaveBeenCalledWith(0.7) + + expect(runner.loops(0.3).loops()).toBe(0.3) + }) + }) + + describe('progress()', () => { + it('gets the progress of a runner', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).queue(null, spy) + + runner.step(300) + expect(spy).toHaveBeenCalledWith(0.3) + + expect(runner.progress()).toBe(0.3) + }) + + it('gets the progress of a runner when looping', () => { + var spy = createSpy('stepper') + var runner = new Runner(800).queue(null, spy).loop(10, false, 200) // duration should be 9800 + + // middle of animation, in the middle of wait time + runner.step(4900) + expect(runner.progress()).toBe(0.5) + expect(spy).toHaveBeenCalledWith(1) + + // start of next loop + runner.step(100) + expect(spy).toHaveBeenCalledWith(0) + + // move 400 into current loop which is 0.5 progress + // the progress value is 5400 / 9800 + runner.step(400) + expect(spy).toHaveBeenCalledWith(0.5) + expect(runner.progress()).toBe(5400 / 9800) + }) + + it('sets the progress of a runner', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).queue(null, spy) + + expect(runner.progress(0.5).progress()).toBe(0.5) + expect(spy).toHaveBeenCalledWith(0.5) + }) + + it('sets the progress of a runner when looping', () => { + var spy = createSpy('stepper') + var runner = new Runner(800).queue(null, spy).loop(10, false, 200) + + // progress 0.5 somewhere in the middle of wait time + expect(runner.progress(0.5).progress()).toBe(0.5) + expect(spy).toHaveBeenCalledWith(1) + + // start of next loop + runner.step(100) + expect(spy).toHaveBeenCalledWith(0) + + // should move 0.5 into the next loop + expect(runner.progress(5400 / 9800).progress()).toBe(5400 / 9800) + expect(spy.calls.mostRecent().args[0]).toBeCloseTo(0.5) + }) + }) + + describe('position()', () => { + it('gets the position of a runner', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).queue(null, spy) + + runner.step(300) + expect(spy).toHaveBeenCalledWith(0.3) + + expect(runner.position()).toBe(0.3) + }) + + it('gets the position of a runner when looping', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(5, true, 100).queue(null, spy) + + runner.step(1200) + expect(spy).toHaveBeenCalledWith(0.9) + + expect(runner.position()).toBe(0.9) + }) + + it('sets the position of a runner', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).queue(null, spy) + + expect(runner.position(0.5).position()).toBe(0.5) + expect(spy).toHaveBeenCalledWith(0.5) + }) + + it('sets the position of a runner in a loop', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).loop(5, true, 100).queue(null, spy) + + runner.step(1200) + expect(runner.position(0.4).position()).toBe(0.4) + expect(spy).toHaveBeenCalledWith(0.4) + + expect(runner.position(0).position()).toBe(0) + expect(spy).toHaveBeenCalledWith(0) + + expect(runner.position(1).position()).toBe(1) + expect(spy).toHaveBeenCalledWith(1) + }) + }) + + describe('element()', () => { + it('returns the element bound to this runner if any', () => { + var runner1 = new Runner() + expect(runner1.element()).toBe(null) + + var element = SVG('') + var runner2 = element.animate() + expect(runner2.element()).toBe(element) + }) + + it('sets an element to be bound to the runner', () => { + var runner = new Runner() + var element = SVG('') + expect(runner.element(element)).toBe(runner) + expect(runner.element()).toBe(element) + }) + }) + + describe('timeline()', () => { + it('returns the timeline bound to this runner if any', () => { + var runner1 = new Runner() + expect(runner1.element()).toBe(null) + + var element = SVG('') + var runner2 = element.animate() + expect(runner2.timeline()).toBe(element.timeline()) + }) + + it('sets a timeline to be bound to the runner', () => { + var runner = new Runner() + var timeline = new Timeline() + expect(runner.timeline(timeline)).toBe(runner) + expect(runner.timeline()).toBe(timeline) + }) + }) + + describe('schedule()', () => { + it('schedules the runner on a timeline', () => { + var runner = new Runner() + var timeline = new Timeline() + var spy = spyOn(timeline, 'schedule').and.callThrough() + + expect(runner.schedule(timeline, 200, 'now')).toBe(runner) + expect(runner.timeline()).toBe(timeline) + expect(spy).toHaveBeenCalledWith(runner, 200, 'now') + }) + + it('schedules the runner on its own timeline', () => { + var runner = new Runner() + var timeline = new Timeline() + var spy = spyOn(timeline, 'schedule') + runner.timeline(timeline) + + expect(runner.schedule(200, 'now')).toBe(runner) + expect(runner.timeline()).toBe(timeline) + expect(spy).toHaveBeenCalledWith(runner, 200, 'now') + }) + + it('throws if no timeline is given', () => { + var runner = new Runner() + expect(() => runner.schedule(200, 'now')).toThrowError( + 'Runner cannot be scheduled without timeline' + ) + }) + }) + + describe('unschedule()', () => { + it('unschedules this runner from its timeline', () => { + var runner = new Runner() + var timeline = new Timeline() + var spy = spyOn(timeline, 'unschedule').and.callThrough() + + expect(runner.schedule(timeline, 200, 'now')).toBe(runner) + expect(runner.unschedule()).toBe(runner) + expect(spy).toHaveBeenCalledWith(runner) + expect(runner.timeline()).toBe(null) + }) + }) + + describe('animate()', () => { + it('creates a new runner scheduled after the first', () => { + var runner = new Runner(1000) + var timeline = new Timeline() + + runner.schedule(timeline) + + var runner2 = runner.animate(500, 1000) + + var t = timeline.time() + + expect(runner2.timeline()).toBe(timeline) + expect(runner2.time()).toBe(0) + + expect(timeline.schedule()).toEqual( + arrayContaining([ + objectContaining({ + start: t, + duration: 1000, + end: t + 1000, + runner: runner + }), + objectContaining({ + start: t + 2000, + duration: 500, + end: t + 2500, + runner: runner2 + }) + ]) + ) + }) + + it('reuses timeline and element of current runner', () => { + const element = new Rect() + const timeline = new Timeline() + const runner = new Runner().element(element).timeline(timeline) + const after = runner.animate() + expect(after.timeline()).toBe(timeline) + expect(after.element()).toBe(element) + }) + + it('does not reuse element if not set', () => { + const timeline = new Timeline() + const runner = new Runner().timeline(timeline) + const after = runner.animate() + expect(after.element()).toBe(null) + }) + }) + + describe('delay()', () => { + it('calls animate with delay parameters', () => { + var runner = new Runner(1000) + spyOn(runner, 'animate') + + runner.delay(500) + expect(runner.animate).toHaveBeenCalledWith(0, 500) + }) + }) + + describe('during()', () => { + it('returns itself', () => { + var runner = new Runner() + expect(runner.during(runFn)).toBe(runner) + }) + + it('calls queue passing only a function to call on every step', () => { + var runner = new Runner() + spyOn(runner, 'queue') + runner.during(runFn) + + expect(runner.queue).toHaveBeenCalledWith(null, runFn) + }) + }) + + describe('after()', () => { + it('returns itself', () => { + var runner = new Runner() + expect(runner.after(runFn)).toBe(runner) + }) + + it('binds a function to the after event', () => { + var runner = new Runner() + spyOn(runner, 'on') + runner.after(runFn) + + expect(runner.on).toHaveBeenCalledWith('finished', runFn) + }) + }) + + describe('finish()', () => { + it('returns itself', () => { + var runner = new Runner() + expect(runner.finish()).toBe(runner) + }) + + it('calls step with Infinity as argument', () => { + var runner = new Runner() + spyOn(runner, 'step') + runner.finish() + + expect(runner.step).toHaveBeenCalledWith(Infinity) + }) + }) + + describe('reverse()', () => { + it('returns itself', () => { + var runner = new Runner() + expect(runner.reverse()).toBe(runner) + }) + + it('reverses the runner', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).reverse().queue(null, spy) + runner.step(750) + expect(spy).toHaveBeenCalledWith(0.25) + }) + + it('reverses the runner when true is passed', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).reverse(true).queue(null, spy) + runner.step(750) + expect(spy).toHaveBeenCalledWith(0.25) + }) + + it('unreverses the runner when true is passed', () => { + var spy = createSpy('stepper') + var runner = new Runner(1000).reverse(false).queue(null, spy) + runner.step(750) + expect(spy).toHaveBeenCalledWith(0.75) + }) + }) + + describe('ease()', () => { + it('returns itself', () => { + var runner = new Runner() + expect(runner.ease(() => {})).toBe(runner) + }) + + it('creates an easing Controller from the easing function', () => { + var runner = new Runner() + runner.ease(() => {}) + + expect(runner._stepper instanceof Ease).toBe(true) + }) + }) + + describe('reset()', () => { + it('resets the runner by setting it to time 0', () => { + var runner = new Runner().step(16) + expect(runner.time()).toBe(16) + expect(runner.reset()).toBe(runner) + expect(runner.time()).toBe(0) + }) + + it('does not reset if already reset', () => { + var runner = Object.freeze(new Runner().reset()) + expect(runner.reset()).toBe(runner) + }) + }) + + describe('private Methods', () => { + describe('_rememberMorpher()', () => { + it('adds a morpher for a method to the runner', () => { + const runner = new Runner() + const morpher = new Morphable() + runner._rememberMorpher('move', morpher) + expect(runner._history.move).toEqual({ morpher, caller: undefined }) + }) + + it('resumes the timeline in case this runner uses a controller', () => { + const timeline = new Timeline() + const spy = spyOn(timeline, 'play') + const runner = new Runner(new Controller(() => 0)).timeline(timeline) + const morpher = new Morphable() + runner._rememberMorpher('move', morpher) + expect(spy).toHaveBeenCalled() + }) + }) + + describe('_tryRetarget()', () => { + it('tries to retarget a morpher for the animation and returns true', () => { + const rect = new Rect().move(0, 0) + const runner = rect.animate().move(10, 10) + jasmine.RequestAnimationFrame.tick(16) + expect(runner._tryRetarget('x', 20)).toBe(true) + expect(runner._history.x.morpher.to()).toEqual([20, '']) + }) + + it('throws away the morpher if it was not initialized yet and returns false', () => { + const rect = new Rect().move(0, 0) + const runner = rect.animate().move(10, 10) + // In that case tryRetarget is not successful + expect(runner._tryRetarget('x', 20)).toBe(false) + }) + + it('does nothing if method was not found', () => { + const rect = new Rect().move(0, 0) + const runner = rect.animate().move(10, 10) + jasmine.RequestAnimationFrame.tick(16) + // In that case tryRetarget is not successful + expect(runner._tryRetarget('foo', 20)).toBe(false) + }) + + it('does only work with controller for transformations and uses retarget function when retargeting transformations', () => { + const rect = new Rect() + const runner = rect + .animate(new Controller(() => 0)) + .transform({ translate: [10, 10] }) + jasmine.RequestAnimationFrame.tick(16) + // In that case tryRetarget is not successful + expect( + runner._tryRetarget('transform', { translate: [20, 20] }) + ).toBe(true) + }) + + it('starts the timeline if retarget was successful', () => { + const timeline = new Timeline() + const rect = new Rect().move(0, 0).timeline(timeline) + const runner = rect.animate().move(10, 10) + jasmine.RequestAnimationFrame.tick(16) + const spy = spyOn(timeline, 'play') + expect(runner._tryRetarget('x', 20)).toBe(true) + expect(runner._history.x.morpher.to()).toEqual([20, '']) + expect(spy).toHaveBeenCalledTimes(1) + }) + }) + + describe('_initialise', () => { + it('does nothing if false is passed', () => { + const runner = Object.freeze(new Runner()) + expect(runner._initialise(false)).toBe(undefined) + }) + + it('does nothing if true is passed and runner is not declarative', () => { + const runner = Object.freeze(new Runner()) + expect(runner._initialise(true)).toBe(undefined) + }) + + it('calls the initializer function on the queue when runner is declarative', () => { + const runner = new Runner(() => 0).queue(initFn, runFn) + runner._initialise() + expect(initFn).toHaveBeenCalledTimes(1) + }) + + it('calls the initializer function on the queue when true is passed and runner is not declarative', () => { + const runner = new Runner().queue(initFn, runFn) + runner._initialise(true) + expect(initFn).toHaveBeenCalledTimes(1) + }) + + it('does nothing if function is already initialized', () => { + const runner = new Runner().queue(initFn, runFn) + runner._initialise(true) + runner._initialise(true) + expect(initFn).toHaveBeenCalledTimes(1) + }) + }) + + describe('_run()', () => { + it('runs each queued function for the position or dt given', () => { + const runner = new Runner().queue(initFn, runFn) + runner._run(16) + expect(runFn).toHaveBeenCalledWith(16) + }) + + it('returns true if all runners converged', () => { + const spy = createSpy().and.returnValue(true) + const runner = new Runner().queue(initFn, spy) + expect(runner._run(16)).toBe(true) + }) + + it('returns true if all runners finished', () => { + const spy = createSpy().and.returnValue(true) + const runner = new Runner(100).queue(initFn, spy) + runner._run(200) + expect(runner._run(1)).toBe(true) + }) + }) + + describe('addTransform()', () => { + it('adds a transformation by multiplying', () => { + const runner = new Runner() + runner.addTransform({ translate: [10, 10] }) + expect(runner.transforms).toEqual(new Matrix(1, 0, 0, 1, 10, 10)) + }) + }) + + describe('clearTransform()', () => { + it('resets the transformations to identity', () => { + const runner = new Runner() + runner.addTransform({ translate: [10, 10] }) + runner.clearTransform() + expect(runner.transforms).toEqual(new Matrix()) + }) + }) + + describe('clearTransformsFromQueue', () => { + it('deletes all functions from the queue which are transformations', () => { + const runner = new Runner().queue(initFn, runFn) + runner.transform({ translate: [10, 20] }) + runner.clearTransformsFromQueue() + expect(runner._queue.length).toBe(1) + }) + }) + }) + + describe('Element', () => { + describe('animate()', () => { + it('creates a runner with the element set and schedules it on the timeline', () => { + var element = SVG('') + var runner = element.animate() + expect(runner instanceof Runner) + expect(runner.element()).toBe(element) + expect(runner.timeline()).toBe(element.timeline()) + expect(element.timeline().getLastRunnerInfo().runner).toBe(runner) + }) + }) + + describe('delay()', () => { + it('calls animate with correct parameters', () => { + var element = SVG('') + + spyOn(element, 'animate') + element.delay(100, 'now') + expect(element.animate).toHaveBeenCalledWith(0, 100, 'now') + }) + }) + + describe('_clearTransformRunnersBefore()', () => { + it('calls clearBefore on the runner array', () => { + const rect = new Rect() + rect._prepareRunner() + const spy = spyOn(rect._transformationRunners, 'clearBefore') + rect._clearTransformRunnersBefore({ id: 1 }) + expect(spy).toHaveBeenCalledWith(1) + }) + }) + + describe('_currentTransform()', () => { + it('calculates the current transformation of this element', () => { + const rect = new Rect() + rect._prepareRunner() + const runner1 = new Runner().addTransform({ translate: [10, 20] }) + const runner2 = new Runner().addTransform({ rotate: 45 }) + const runner3 = new Runner().addTransform({ translate: [10, 20] }) + + rect._addRunner(runner1) + rect._addRunner(runner2) + rect._addRunner(runner3) + expect(rect._currentTransform(runner3)).toEqual( + new Matrix({ translate: [10, 20] }).rotate(45).translate(10, 20) + ) + }) + }) + + describe('_addRunner()', () => { + it('adds a runner to the runner array of this element', () => { + const rect = new Rect() + rect._prepareRunner() + const spy = spyOn(rect._transformationRunners, 'add') + const runner = new Runner() + rect._addRunner(runner) + expect(spy).toHaveBeenCalledWith(runner) + }) + }) + + describe('_prepareRunner()', () => { + it('adds a runner array to the element', () => { + const rect = new Rect() + expect(rect._transformationRunners).toBe(undefined) + rect._prepareRunner() + expect(rect._transformationRunners).toEqual(any(RunnerArray)) + }) + + it('only adds it if no animation is in progress', () => { + const rect = new Rect() + expect(rect._transformationRunners).toBe(undefined) + rect._prepareRunner() + const arr = rect._transformationRunners + rect._frameId = 1 + rect._prepareRunner() + expect(rect._transformationRunners).toBe(arr) + }) + }) + }) + + describe('methods', () => { + describe('attr()', () => { + it('relays to styleAttr with "attr" as parameter', () => { + const runner = new Runner() + const spy = spyOn(runner, 'styleAttr') + runner.attr(1, 2) + expect(spy).toHaveBeenCalledWith('attr', 1, 2) + }) + }) + + describe('css()', () => { + it('relays to styleAttr with "css" as parameter', () => { + const runner = new Runner() + const spy = spyOn(runner, 'styleAttr') + runner.css(1, 2) + expect(spy).toHaveBeenCalledWith('css', 1, 2) + }) + }) + + describe('styleAttr()', () => { + it('adds a morpher for attr', () => { + const runner = new Runner() + runner.styleAttr('attr', 'x', 5) + expect(runner._history.attr.morpher).toEqual(any(Morphable)) + expect(runner._history.attr.morpher.to()).toEqual([ + 'x', + SVGNumber, + 2, + 5, + '' + ]) + }) + + it('adds a morpher for css', () => { + const runner = new Runner() + runner.styleAttr('css', 'x', 5) + expect(runner._history.css.morpher).toEqual(any(Morphable)) + expect(runner._history.css.morpher.to()).toEqual([ + 'x', + SVGNumber, + 2, + 5, + '' + ]) + }) + + it('adds init and run fn for execution when runner runs', () => { + const element = new Rect().move(0, 0) + const runner = new Runner(100).ease('-').element(element) + runner.styleAttr('attr', 'x', 5) + runner.step(50) + expect(runner._history.attr.morpher.from()).toEqual([ + 'x', + SVGNumber, + 2, + 0, + '' + ]) + expect(runner._history.attr.morpher.to()).toEqual([ + 'x', + SVGNumber, + 2, + 5, + '' + ]) + expect(element.x()).toBe(2.5) + }) + + it('it also works when the object contains other morphable values', () => { + const element = new Rect().fill('#fff').stroke('#000') + const runner = new Runner(100).ease('-').element(element) + runner.styleAttr('attr', { fill: '#000', stroke: new Color('#fff') }) + runner.step(50) + expect(runner._history.attr.morpher.from()).toEqual([ + 'fill', + Color, + 5, + 255, + 255, + 255, + 0, + 'rgb', + 'stroke', + Color, + 5, + 0, + 0, + 0, + 0, + 'rgb' + ]) + + expect(runner._history.attr.morpher.to()).toEqual([ + 'fill', + Color, + 5, + 0, + 0, + 0, + 0, + 'rgb', + 'stroke', + Color, + 5, + 255, + 255, + 255, + 0, + 'rgb' + ]) + const result = runner._history.attr.morpher.at(0.5).valueOf() + expect(result.fill).toEqual(any(Color)) + expect(result.stroke).toEqual(any(Color)) + expect(result.fill.toArray()).toEqual([127.5, 127.5, 127.5, 0, 'rgb']) + expect(result.stroke.toArray()).toEqual([ + 127.5, + 127.5, + 127.5, + 0, + 'rgb' + ]) + }) + + it('it changes color space', () => { + const element = new Rect().fill('#fff') + const runner = new Runner(100).ease('-').element(element) + runner.styleAttr('attr', { fill: new Color(100, 12, 12, 'hsl') }) + runner.step(50) + expect(runner._history.attr.morpher.from()).toEqual([ + 'fill', + Color, + 5, + 0, + 0, + 100, + 0, + 'hsl' + ]) + + expect(runner._history.attr.morpher.to()).toEqual([ + 'fill', + Color, + 5, + 100, + 12, + 12, + 0, + 'hsl' + ]) + const result = runner._history.attr.morpher.at(0.5).valueOf() + expect(result.fill).toEqual(any(Color)) + expect(result.fill.toArray()).toEqual([50, 6, 56, 0, 'hsl']) + expect(element.fill()).toBe('#969388') + }) + + it('retargets if called two times with new key', () => { + const element = new Rect().fill('#fff') + const runner = new Runner(100).ease('-').element(element) + runner.styleAttr('attr', { fill: new Color(100, 12, 12, 'hsl') }) + runner.step(50) + expect(element.fill()).toBe('#969388') + runner.styleAttr('attr', { + fill: new Color(100, 50, 50, 'hsl'), + x: 50 + }) + runner.step(25) + expect(element.fill()).toBe('#b1c37c') + expect(element.x()).toBe(37.5) + }) + + it('retargets if called two times without new key', () => { + const element = new Rect().fill('#fff') + const runner = new Runner(100).ease('-').element(element) + runner.styleAttr('attr', { fill: new Color(100, 12, 12, 'hsl') }) + runner.step(50) + expect(element.fill()).toBe('#969388') + runner.styleAttr('attr', { fill: new Color(100, 50, 50, 'hsl') }) + runner.step(25) + expect(element.fill()).toBe('#b1c37c') + }) + }) + + function closeTo(number, precision = 2) { + return { + /* + * The asymmetricMatch function is required, and must return a boolean. + */ + asymmetricMatch: function (compareTo) { + const factor = 10 ** precision + return ( + Math.round(~~(compareTo * factor) / factor) === + Math.round(~~(number * factor) / factor) + ) + }, + + /* + * The jasmineToString method is used in the Jasmine pretty printer, and will + * be seen by the user in the message when a test fails. + */ + jasmineToString: function () { + return ( + '' + ) + } + } + } + + function equal(obj) { + return { + /* + * The asymmetricMatch function is required, and must return a boolean. + */ + asymmetricMatch: function (compareTo) { + if (!(compareTo instanceof obj.constructor)) return false + + const keys = Object.keys(obj) + const difference = Object.keys(compareTo).filter( + (el) => !keys.includes(el) + ) + + if (difference.length) return false + + for (const key in obj) { + if (obj[key] !== compareTo[key]) return false + } + + return true + }, + + /* + * The jasmineToString method is used in the Jasmine pretty printer, and will + * be seen by the user in the message when a test fails. + */ + jasmineToString: function () { + return '' + } + } + } + + describe('zoom()', () => { + it('adds a zoom morpher to the queue', () => { + const element = SVG().size(100, 100).viewbox(0, 0, 100, 100) + const runner = new Runner(100).ease('-').element(element) + runner.zoom(2, { x: 0, y: 0 }) + runner.step(50) + expect(runner._history.zoom.morpher.from()).toEqual([1, '']) + expect(runner._history.zoom.morpher.to()).toEqual([2, '']) + + expect(element.zoom()).toBeCloseTo(1.5, 10) + expect(element.viewbox().toArray()).toEqual([ + 0, + 0, + closeTo(66.666, 3), + closeTo(66.666, 3) + ]) + }) + + it('retargets if called twice', () => { + const element = SVG().size(100, 100).viewbox(0, 0, 100, 100) + const runner = new Runner(100).ease('-').element(element) + runner.zoom(2, { x: 0, y: 0 }) + runner.step(50) + runner.zoom(4, { x: 0, y: 0 }) + expect(runner._history.zoom.morpher.from()).toEqual([1, '']) + expect(runner._history.zoom.morpher.to()).toEqual([4, '']) + + runner.step(25) + expect(element.zoom()).toBeCloseTo(3.25, 10) + expect(element.viewbox().toArray()).toEqual([ + 0, + 0, + closeTo(30.769, 3), + closeTo(30.769, 3) + ]) + }) + }) + + describe('transform()', () => { + it('does not retarget for non-declarative transformations', () => { + const runner = new Runner() + const spy = spyOn(runner, '_tryRetarget') + runner.transform({ translate: [10, 20] }) + expect(spy).not.toHaveBeenCalled() + }) + + it('does not retarget for relative transformations', () => { + const runner = new Runner(new Controller(() => 0)) + const spy = spyOn(runner, '_tryRetarget') + runner.transform({ translate: [10, 20] }, true) + expect(spy).not.toHaveBeenCalled() + }) + + it('does retarget for absolute declarative transformations', () => { + const runner = new Runner(new Controller(() => 0)) + const spy = spyOn(runner, '_tryRetarget') + runner.transform({ translate: [10, 20] }) + expect(spy).toHaveBeenCalled() + }) + + it('calls queue with isTransform=true', () => { + const runner = new Runner() + const spy = spyOn(runner, 'queue') + runner.transform({ translate: [10, 20] }) + expect(spy).toHaveBeenCalledWith( + any(Function), + any(Function), + any(Function), + true + ) + }) + + it('steps an affine transformation correctly', () => { + const element = new Rect() + const runner = new Runner(100).ease('-').element(element) + runner.transform({ translate: [10, 20], scale: 2, rotate: 90 }) + runner.step(50) + // transform sets an immediate callback to apply all merged transforms + // when every runner had the chance to add its bit of transforms + jasmine.RequestAnimationFrame.tick(1) + expect(element.matrix().decompose()).toEqual( + objectContaining({ + translateX: 5, + translateY: 10, + scaleX: closeTo(1.5, 10), + scaleY: closeTo(1.5), + rotate: closeTo(45, 10) + }) + ) + }) + + it('retargets an affine transformation correctly', () => { + const element = new Rect() + const runner = new Runner(() => 1).element(element) + runner.transform({ translate: [10, 20], scale: 2, rotate: 90 }) + runner.step(50) + runner.transform({ scale: 2 }) + + // transform sets its to-target to the morpher in the initialisation step + // because it depends on the from-target. Declaritive animation run the init-step + // on every frame. That is why we step here to see the effect of our retargeting + runner.step(25) + + expect(runner._history.transform.morpher.to()).toEqual([ + 2, 2, 0, 0, 0, 0, 0, 0 + ]) + }) + + it('retargets an affine transformation correctly and sets new origin', () => { + const element = new Rect() + const runner = new Runner(() => 1).element(element) + runner.transform({ translate: [10, 20], scale: 2, rotate: 90 }) + runner.step(50) + runner.transform({ scale: 2, origin: [10, 10] }) + + // transform sets its to-target to the morpher in the initialisation step + // because it depends on the from-target. Declaritive animation run the init-step + // on every frame. That is why we step here to see the effect of our retargeting + runner.step(25) + + expect(runner._history.transform.morpher.to()).toEqual([ + 2, 2, 0, 0, 0, 0, 10, 10 + ]) + }) + + it('steps multiple relative animations correctly', () => { + const element = new Rect() + const runner = new Runner(100).ease('-').element(element) + runner.translate(10, 20).scale(2).rotate(45) + runner.step(50) + // transform sets an immediate callback to apply all merged transforms + // when every runner had the chance to add its bit of transforms + jasmine.RequestAnimationFrame.tick(1) + + // The origin is transformed with every + expect(element.matrix()).toEqual( + new Matrix().translate(5, 10).scale(1.5, 5, 10).rotate(22.5, 5, 10) + ) + }) + + it('steps multiple relative animations correctly from multiple runners', () => { + const element = new Rect() + const runner1 = new Runner(100).ease('-').element(element) + const runner2 = new Runner(100).ease('-').element(element) + const runner3 = new Runner(100).ease('-').element(element) + runner1.translate(10, 20) + runner2.scale(2) + runner3.rotate(45) + runner1.step(50) + runner2.step(50) + runner3.step(50) + // transform sets an immediate callback to apply all merged transforms + // when every runner had the chance to add its bit of transforms + jasmine.RequestAnimationFrame.tick(1) + + // The origin is transformed with every + expect(element.matrix()).toEqual( + new Matrix().translate(5, 10).scale(1.5, 5, 10).rotate(22.5, 5, 10) + ) + }) + + it('absolute transformations correctly overwrite relatives', () => { + const element = new Rect() + const runner1 = new Runner(100).ease('-').element(element) + const runner2 = new Runner(100).ease('-').element(element) + const runner3 = new Runner(100).ease('-').element(element) + runner1.translate(10, 20) + runner2.transform({ scale: 2 }) + runner3.rotate(45) + runner1.step(50) + runner2.step(50) + runner3.step(50) + // transform sets an immediate callback to apply all merged transforms + // when every runner had the chance to add its bit of transforms + jasmine.RequestAnimationFrame.tick(1) + + expect(runner1._queue.length).toBe(0) + + // The origin is transformed with every + expect(element.matrix()).toEqual(new Matrix().scale(1.5).rotate(22.5)) + }) + + it('correctly animates matrices directly', () => { + const element = new Rect() + const runner = new Runner(100).ease('-').element(element) + runner.transform(new Matrix({ rotate: 90 })) + runner.step(50) + // transform sets an immediate callback to apply all merged transforms + // when every runner had the chance to add its bit of transforms + jasmine.RequestAnimationFrame.tick(1) + + // The origin is transformed with every + expect(element.matrix()).toEqual( + new Matrix(0.5, 0.5, -0.5, 0.5, 0, 0) + ) + }) + + it('correctly animates matrices affine', () => { + const element = new Rect() + const runner = new Runner(100).ease('-').element(element) + runner.transform( + Object.assign({ affine: true }, new Matrix({ rotate: 90 })) + ) + runner.step(50) + // transform sets an immediate callback to apply all merged transforms + // when every runner had the chance to add its bit of transforms + jasmine.RequestAnimationFrame.tick(1) + + // The origin is transformed with every + expect(element.matrix()).toEqual(new Matrix({ rotate: 45 })) + }) + + it('correctly animates matrices affine by passing third parameter', () => { + const element = new Rect() + const runner = new Runner(100).ease('-').element(element) + runner.transform(new Matrix({ rotate: 90 }), true, true) + runner.step(50) + // transform sets an immediate callback to apply all merged transforms + // when every runner had the chance to add its bit of transforms + jasmine.RequestAnimationFrame.tick(1) + + // The origin is transformed with every + expect(element.matrix()).toEqual(new Matrix({ rotate: 45 })) + }) + + it('correctly animates a declarative relative rotation', () => { + const element = new Rect() + const runner = new Runner(() => 1).element(element) + runner.transform({ rotate: 90 }, true) + runner.step(16) + jasmine.RequestAnimationFrame.tick(1) + runner.step(16) + jasmine.RequestAnimationFrame.tick(1) + expect(element.matrix()).not.toEqual(new Matrix()) + }) + }) + + describe('x()', () => { + it('queues a numer', () => { + const runner = new Runner() + const spy = spyOn(runner, '_queueNumber') + runner.x(10) + expect(spy).toHaveBeenCalledWith('x', 10) + }) + }) + + describe('y()', () => { + it('queues a numer', () => { + const runner = new Runner() + const spy = spyOn(runner, '_queueNumber') + runner.y(10) + expect(spy).toHaveBeenCalledWith('y', 10) + }) + }) + + describe('dx()', () => { + it('queues a numer', () => { + const runner = new Runner() + const spy = spyOn(runner, '_queueNumberDelta') + runner.dx(10) + expect(spy).toHaveBeenCalledWith('x', 10) + }) + + it('uses a delta of 0 by default', () => { + const runner = new Runner() + const spy = spyOn(runner, '_queueNumberDelta') + runner.dx() + expect(spy).toHaveBeenCalledWith('x', 0) + }) + }) + + describe('dy()', () => { + it('queues a number', () => { + const runner = new Runner() + const spy = spyOn(runner, '_queueNumberDelta') + runner.dy(10) + expect(spy).toHaveBeenCalledWith('y', 10) + }) + + it('uses a delta of 0 by default', () => { + const runner = new Runner() + const spy = spyOn(runner, '_queueNumberDelta') + runner.dy() + expect(spy).toHaveBeenCalledWith('y', 0) + }) + }) + + describe('dmove()', () => { + it('calls dx and dy', () => { + const runner = new Runner() + const spy1 = spyOn(runner, 'dx').and.returnValue(runner) + const spy2 = spyOn(runner, 'dy').and.returnValue(runner) + runner.dmove(10, 20) + expect(spy1).toHaveBeenCalledWith(10) + expect(spy2).toHaveBeenCalledWith(20) + }) + }) + + describe('_queueNumberDelta', () => { + it('queues a morpher of type SVGNumber', () => { + const element = new Rect().x(10) + const runner = new Runner(100).ease('-').element(element) + runner._queueNumberDelta('x', 10) + runner.step(50) + expect(runner._history.x.morpher.type()).toEqual(SVGNumber) + expect(runner._history.x.morpher.from()).toEqual([10, '']) + expect(runner._history.x.morpher.to()).toEqual([20, '']) + + expect(element.x()).toBe(15) + }) + + it('retargets correctly', () => { + const element = new Rect().x(10) + const runner = new Runner(100).ease('-').element(element) + runner._queueNumberDelta('x', 10) + runner.step(25) + runner._queueNumberDelta('x', 20) + + expect(runner._history.x.morpher.to()).toEqual([30, '']) + + runner.step(25) + expect(element.x()).toBe(20) + }) + }) + + describe('_queueObject', () => { + it('queues a morphable object', () => { + const element = new Rect().x(10) + const runner = new Runner(100).ease('-').element(element) + runner._queueObject('x', new SVGNumber(20)) + runner.step(50) + expect(runner._history.x.morpher.type()).toEqual(SVGNumber) + expect(runner._history.x.morpher.from()).toEqual([10, '']) + expect(runner._history.x.morpher.to()).toEqual([20, '']) + + expect(element.x()).toBe(15) + }) + + it('queues a morphable primitive', () => { + const element = new Rect().fill('#000') + const runner = new Runner(100).ease('-').element(element) + runner._queueObject('fill', '#fff') + runner.step(50) + expect(runner._history.fill.morpher.type()).toEqual(Color) + + expect(element.fill()).toBe('#808080') + }) + + it('retargets correctly', () => { + const element = new Rect().x(10) + const runner = new Runner(100).ease('-').element(element) + runner._queueObject('x', 20) + + runner.step(25) + + runner._queueObject('x', 30) + runner.step(25) + expect(element.x()).toBe(20) + }) + }) + + describe('_queueNumber', () => { + it('queues an SVGNumber with _queueObject', () => { + const runner = new Runner() + const spy = spyOn(runner, '_queueObject') + runner._queueNumber('x', 10) + expect(spy).toHaveBeenCalledWith('x', equal(new SVGNumber(10))) + }) + }) + + describe('cy()', () => { + it('queues a numer', () => { + const runner = new Runner() + const spy = spyOn(runner, '_queueNumber') + runner.cy(10) + expect(spy).toHaveBeenCalledWith('cy', 10) + }) + }) + + describe('cx()', () => { + it('queues a numer', () => { + const runner = new Runner() + const spy = spyOn(runner, '_queueNumber') + runner.cx(10) + expect(spy).toHaveBeenCalledWith('cx', 10) + }) + }) + + describe('move()', () => { + it('calls x and y', () => { + const runner = new Runner() + const spy1 = spyOn(runner, 'x').and.returnValue(runner) + const spy2 = spyOn(runner, 'y').and.returnValue(runner) + runner.move(10, 20) + expect(spy1).toHaveBeenCalledWith(10) + expect(spy2).toHaveBeenCalledWith(20) + }) + }) + + describe('center()', () => { + it('calls cx and cy', () => { + const runner = new Runner() + const spy1 = spyOn(runner, 'cx').and.returnValue(runner) + const spy2 = spyOn(runner, 'cy').and.returnValue(runner) + runner.center(10, 20) + expect(spy1).toHaveBeenCalledWith(10) + expect(spy2).toHaveBeenCalledWith(20) + }) + }) + + describe('size()', () => { + it('calls width and height', () => { + const runner = new Runner() + const spy1 = spyOn(runner, 'width').and.returnValue(runner) + const spy2 = spyOn(runner, 'height').and.returnValue(runner) + runner.size(10, 20) + expect(spy1).toHaveBeenCalledWith(10) + expect(spy2).toHaveBeenCalledWith(20) + }) + + it('figures out height if only width given', () => { + const element = new Rect().size(10, 10) + const runner = new Runner().element(element) + const spy1 = spyOn(runner, 'width').and.returnValue(runner) + const spy2 = spyOn(runner, 'height').and.returnValue(runner) + runner.size(20) + expect(spy1).toHaveBeenCalledWith(20) + expect(spy2).toHaveBeenCalledWith(20) + }) + + it('figures out width if only height given', () => { + const element = new Rect().size(10, 10) + const runner = new Runner().element(element) + const spy1 = spyOn(runner, 'width').and.returnValue(runner) + const spy2 = spyOn(runner, 'height').and.returnValue(runner) + runner.size(null, 20) + expect(spy1).toHaveBeenCalledWith(20) + expect(spy2).toHaveBeenCalledWith(20) + }) + }) + + describe('width()', () => { + it('queues a numer', () => { + const runner = new Runner() + const spy = spyOn(runner, '_queueNumber') + runner.width(10) + expect(spy).toHaveBeenCalledWith('width', 10) + }) + }) + + describe('height()', () => { + it('queues a numer', () => { + const runner = new Runner() + const spy = spyOn(runner, '_queueNumber') + runner.height(10) + expect(spy).toHaveBeenCalledWith('height', 10) + }) + }) + + describe('plot()', () => { + it('queues a morphable array', () => { + const element = new Polygon().plot([10, 10, 20, 20]) + const runner = new Runner(100).ease('-').element(element) + runner.plot(20, 20, 30, 30) + runner.step(50) + expect(runner._history.plot.morpher.from()).toEqual([10, 10, 20, 20]) + expect(runner._history.plot.morpher.to()).toEqual([20, 20, 30, 30]) + expect(element.array()).toEqual(new PointArray([15, 15, 25, 25])) + }) + + it('retargets correctly', () => { + const element = new Polygon().plot([10, 10, 20, 20]) + const runner = new Runner(100).ease('-').element(element) + runner.plot(20, 20, 30, 30) + runner.step(25) + runner.plot(30, 30, 40, 40) + runner.step(25) + expect(runner._history.plot.morpher.from()).toEqual([10, 10, 20, 20]) + expect(runner._history.plot.morpher.to()).toEqual([30, 30, 40, 40]) + expect(element.array()).toEqual(new PointArray([20, 20, 30, 30])) + }) + }) + + describe('leading()', () => { + it('queues a numer', () => { + const runner = new Runner() + const spy = spyOn(runner, '_queueNumber') + runner.leading(10) + expect(spy).toHaveBeenCalledWith('leading', 10) + }) + }) + + describe('viewbox()', () => { + it('queues a numer', () => { + const runner = new Runner() + const spy = spyOn(runner, '_queueObject') + runner.viewbox(10, 10, 100, 100) + expect(spy).toHaveBeenCalledWith( + 'viewbox', + equal(new Box(10, 10, 100, 100)) + ) + }) + }) + + describe('update()', () => { + it('relays to attr call', () => { + const runner = new Runner() + const spy = spyOn(runner, 'attr') + runner.update(0.5, '#fff', 1) + expect(spy).toHaveBeenCalledWith('offset', 0.5) + expect(spy).toHaveBeenCalledWith('stop-color', '#fff') + expect(spy).toHaveBeenCalledWith('stop-opacity', 1) + }) + }) + }) + }) + + describe('FakeRunner', () => { + describe('()', () => { + it('creates a new FakeRunner with a new matrix which is always done', () => { + const runner = new FakeRunner() + expect(runner.transforms).toEqual(new Matrix()) + expect(runner.id).toBe(-1) + expect(runner.done).toBe(true) + }) + }) + + describe('mergeWith()', () => { + it('merges the transformations of a runner with another and returns a FakeRunner', () => { + const fake = new FakeRunner() + const runner = new Runner().addTransform({ translate: [10, 20] }) + const newRunner = fake.mergeWith(runner) + expect(newRunner).toEqual(any(FakeRunner)) + expect(newRunner.transforms).toEqual( + new Matrix({ translate: [10, 20] }) + ) + }) + }) + }) + + describe('RunnerArray', () => { + describe('add()', () => { + it('adds a runner to the runner array', () => { + const runner = new Runner() + const arr = new RunnerArray() + arr.add(runner) + expect(arr.length()).toBe(1) + }) + + it('does not add the same runner twice', () => { + const runner = new Runner() + const arr = new RunnerArray() + arr.add(runner) + arr.add(runner) + expect(arr.length()).toBe(1) + }) + }) + + describe('getByID()', () => { + it('returns a runner by its id', () => { + const runner = new Runner() + const arr = new RunnerArray() + arr.add(runner) + expect(arr.getByID(runner.id)).toBe(runner) + }) + }) + + describe('remove()', () => { + it('removes a runner by its id', () => { + const runner = new Runner() + const arr = new RunnerArray() + arr.add(runner) + arr.remove(runner.id) + expect(arr.length()).toBe(0) + }) + }) + + describe('merge()', () => { + it('merges all runners which are done', () => { + const runner1 = new Runner().addTransform({ translate: [10, 20] }) + const runner2 = new Runner().addTransform({ rotate: 45 }) + const runner3 = new Runner().addTransform({ translate: [10, 20] }) + const arr = new RunnerArray() + arr.add(runner1).add(runner2).add(runner3) + runner1.done = true + runner2.done = true + runner3.done = true + arr.merge() + expect(arr.runners[0]).toEqual(any(FakeRunner)) + expect(arr.runners[0].transforms).toEqual( + new Matrix({ translate: [10, 20] }).rotate(45).translate(10, 20) + ) + }) + + it('skips runners which are not done', () => { + const runner1 = new Runner().addTransform({ translate: [10, 20] }) + const runner2 = new Runner().addTransform({ rotate: 45 }) + const runner3 = new Runner().addTransform({ rotate: 45 }) + const runner4 = new Runner().addTransform({ translate: [10, 20] }) + const runner5 = new Runner().addTransform({ rotate: 45 }) + const arr = new RunnerArray() + arr.add(runner1).add(runner2).add(runner3).add(runner4).add(runner5) + runner1.done = true + runner2.done = true + runner3.done = false + runner4.done = true + runner5.done = true + arr.merge() + expect(arr.runners[0]).toEqual(any(FakeRunner)) + expect(arr.runners[0].transforms).toEqual( + new Matrix({ translate: [10, 20] }).rotate(45) + ) + + expect(arr.runners[2]).toEqual(any(FakeRunner)) + expect(arr.runners[2].transforms).toEqual( + new Matrix({ translate: [10, 20] }).rotate(45) + ) + + expect(arr.runners[1]).toBe(runner3) + }) + + it('skips runners which have a timeline and are scheduled on that timeline', () => { + const runner1 = new Runner().addTransform({ translate: [10, 20] }) + const runner2 = new Runner().addTransform({ rotate: 45 }) + const runner3 = new Runner().addTransform({ rotate: 45 }) + const runner4 = new Runner().addTransform({ translate: [10, 20] }) + const runner5 = new Runner().addTransform({ rotate: 45 }) + const arr = new RunnerArray() + arr.add(runner1).add(runner2).add(runner3).add(runner4).add(runner5) + runner1.done = true + runner2.done = true + runner3.done = true + runner4.done = true + runner5.done = true + + runner3.schedule(new Timeline()) + arr.merge() + expect(arr.runners[0]).toEqual(any(FakeRunner)) + expect(arr.runners[0].transforms).toEqual( + new Matrix({ translate: [10, 20] }).rotate(45) + ) + + expect(arr.runners[2]).toEqual(any(FakeRunner)) + expect(arr.runners[2].transforms).toEqual( + new Matrix({ translate: [10, 20] }).rotate(45) + ) + + expect(arr.runners[1]).toBe(runner3) + }) + }) + + describe('edit()', () => { + it('replaces one runner with another', () => { + const arr = new RunnerArray() + const runner1 = new Runner() + const runner2 = new Runner() + arr.add(runner1) + arr.edit(runner1.id, runner2) + expect(arr.length()).toBe(1) + expect(arr.runners[0]).toBe(runner2) + }) + }) + + describe('length()', () => { + it('returns the number of runners in the array', () => { + const arr = new RunnerArray().add(new Runner()).add(new Runner()) + expect(arr.length()).toBe(2) + }) + }) + + describe('clearBefore', () => { + it('removes all runners before a certain runner', () => { + const runner1 = new Runner() + const runner2 = new Runner() + const runner3 = new Runner() + const runner4 = new Runner() + const runner5 = new Runner() + const arr = new RunnerArray() + arr.add(runner1).add(runner2).add(runner3).add(runner4).add(runner5) + arr.clearBefore(runner3.id) + expect(arr.length()).toBe(4) + expect(arr.runners).toEqual([ + any(FakeRunner), + runner3, + runner4, + runner5 + ]) + }) + }) + }) +}) diff --git a/spec/spec/animation/Timeline.js b/spec/spec/animation/Timeline.js new file mode 100644 index 000000000..cbb40f815 --- /dev/null +++ b/spec/spec/animation/Timeline.js @@ -0,0 +1,509 @@ +/* globals describe, expect, it, beforeEach, afterEach, spyOn, container, jasmine */ + +import { + Timeline, + SVG, + Runner, + Animator, + Queue, + Rect +} from '../../../src/main.js' +import { getWindow } from '../../../src/utils/window.js' + +const { createSpy, any } = jasmine + +describe('Timeline.js', () => { + beforeEach(() => { + jasmine.RequestAnimationFrame.install(getWindow()) + Animator.timeouts = new Queue() + Animator.frames = new Queue() + Animator.immediates = new Queue() + Animator.nextDraw = null + }) + + afterEach(() => { + getWindow().cancelAnimationFrame(Animator.nextDraw) + jasmine.RequestAnimationFrame.uninstall(getWindow()) + }) + + describe('()', () => { + it('creates a new Timeline with a default timesource', () => { + const timeline = new Timeline() + expect(timeline.source()).toEqual(any(Function)) + }) + + it('creates a new Timeline with the passed timesource', () => { + const source = createSpy() + const timeline = new Timeline(source) + expect(timeline.source()).toBe(source) + }) + }) + + describe('schedule()', () => { + it('schedules a runner at the start of the queue with a default delay of 0', () => { + const timeline = new Timeline() + const runner = new Runner(1000) + timeline.schedule(runner) + expect(timeline._runners[0].start).toEqual(0) + }) + + it('sets a reference of the timeline to the runner', () => { + const timeline = new Timeline() + const runner = new Runner(1000) + timeline.schedule(runner) + expect(runner.timeline()).toBe(timeline) + }) + + it('schedules after when no when is past', () => { + const timeline = new Timeline().schedule(new Runner(1000)) + const runner = new Runner(1000) + timeline.schedule(runner) + expect(timeline._runners[1].start).toBe(1000) + }) + + it('schedules after when when is last', () => { + const timeline = new Timeline().schedule(new Runner(1000)) + const runner = new Runner(1000) + timeline.schedule(runner, 0, 'last') + expect(timeline._runners[1].start).toBe(1000) + }) + + it('schedules after when when is after', () => { + const timeline = new Timeline().schedule(new Runner(1000)) + const runner = new Runner(1000) + timeline.schedule(runner, 0, 'after') + expect(timeline._runners[1].start).toBe(1000) + }) + + it('starts the animation right away when there is no runner to schedule after and when is after', () => { + const timeline = new Timeline().time(100) + const runner = new Runner(1000) + timeline.schedule(runner, 0, 'after') + expect(timeline._runners[0].start).toBe(100) + }) + + it('schedules with start of the last runner when when is with-last', () => { + const timeline = new Timeline().schedule(new Runner(1000), 200) + const runner = new Runner(1000) + timeline.schedule(runner, 0, 'with-last') + expect(timeline._runners[1].start).toBe(200) + }) + + it('starts the animation right away when there is no runner to schedule after and when is after', () => { + const timeline = new Timeline().time(100) + const runner = new Runner(1000) + timeline.schedule(runner, 0, 'with-last') + expect(timeline._runners[0].start).toBe(100) + }) + + it('respects passed delay', () => { + const timeline = new Timeline().schedule(new Runner(1000), 1000) + const runner = new Runner(1000) + timeline.schedule(runner, 0, 'after') + expect(timeline._runners[1].start).toBe(2000) + }) + + it('schedules the runner absolutely with absolute', () => { + const timeline = new Timeline().schedule(new Runner(1000)) + const runner = new Runner(1000) + timeline.schedule(runner, 0, 'absolute') + expect(timeline._runners[1].start).toBe(0) + }) + + it('schedules the runner absolutely with start', () => { + const timeline = new Timeline().schedule(new Runner(1000)) + const runner = new Runner(1000) + timeline.schedule(runner, 0, 'start') + expect(timeline._runners[1].start).toBe(0) + }) + + it('schedules the runner relatively to old start with relative', () => { + const timeline = new Timeline() + const runner = new Runner(1000) + timeline.schedule(runner, 100).schedule(runner, 100, 'relative') + expect(timeline._runners[0].start).toBe(200) + }) + + it('schedules the runner as absolute if this runner was not on the timeline', () => { + const timeline = new Timeline() + const runner = new Runner(1000) + timeline.schedule(runner, 100, 'relative') + expect(timeline._runners[0].start).toBe(100) + }) + + it('throws if when is not supported', () => { + const timeline = new Timeline().schedule(new Runner(1000), 1000) + const runner = new Runner(1000) + expect(() => timeline.schedule(runner, 0, 'not supported')).toThrowError( + 'Invalid value for the "when" parameter' + ) + }) + + it('uses persist value of the runner of present', () => { + const timeline = new Timeline() + const runner = new Runner(1000).persist(100) + timeline.schedule(runner) + expect(timeline._runners[0].persist).toBe(100) + }) + }) + + describe('unschedule()', () => { + it('removes a runner from the timeline', () => { + const timeline = new Timeline() + const runner = new Runner(1000) + timeline.schedule(runner).unschedule(runner) + expect(runner.timeline()).toBe(null) + expect(timeline._runners).toEqual([]) + }) + }) + + describe('getRunnerInfoById()', () => { + it('gets a runner by its id from the timeline', () => { + const timeline = new Timeline() + const runner = new Runner(1000) + expect( + timeline.schedule(runner).getRunnerInfoById(runner.id).runner + ).toBe(runner) + }) + + it('returns null of runner not found', () => { + const timeline = new Timeline() + const runner = new Runner(1000) + expect(timeline.getRunnerInfoById(runner.id)).toBe(null) + }) + }) + + describe('getLastRunnerInfo()', () => { + it('gets a runner by its id from the timeline', () => { + const timeline = new Timeline().schedule(new Runner(1000)) + const runner = new Runner(1000) + expect(timeline.schedule(runner).getLastRunnerInfo().runner).toBe(runner) + }) + }) + + describe('getEndTime()', () => { + it('returns the end time of the runner which started last', () => { + const timeline = new Timeline() + const runner = new Runner(1000) + const runner2 = new Runner(100) + timeline.schedule(runner).schedule(runner2, 500, 'start') + expect(timeline.getEndTime()).toBe(600) + }) + + it('returns the timeline time if no runner is scheduled', () => { + const timeline = new Timeline().time(100) + expect(timeline.getEndTime()).toBe(100) + }) + }) + + describe('getEndTimeOfTimeline', () => { + it('returns 0 if no runners are scheduled', () => { + const timeline = new Timeline() + const endTime = timeline.getEndTimeOfTimeline() + expect(endTime).toEqual(0) + }) + + it('returns the time all runners are finished', () => { + const timeline = new Timeline() + const runner = new Runner(1000) + const runner2 = new Runner(100) + timeline.schedule(runner).schedule(runner2, 500, 'start') + expect(timeline.getEndTimeOfTimeline()).toBe(1000) + }) + }) + + describe('finish - issue #964', () => { + let canvas + + beforeEach(() => { + canvas = SVG().addTo(container) + }) + + it('places all elements at the right position - single runner', () => { + const timeline = new Timeline() + + const rect = canvas.rect(20, 20) + rect.timeline(timeline) + rect.animate().move(100, 200) + + timeline.finish() + expect(rect.x()).toEqual(100) + expect(rect.y()).toEqual(200) + }) + + it('places all elements at the right position - runner that finishes latest is in first position', () => { + const timeline = new Timeline() + + const rect1 = canvas.rect(10, 10) + rect1.timeline(timeline) + + const rect2 = canvas.rect(10, 10) + rect2.timeline(timeline) + + const rect3 = canvas.rect(10, 10) + rect3.timeline(timeline) + + rect1.animate(2000, 0, 'now').move(100, 200) + rect2.animate(1000, 0, 'now').move(100, 200) + rect3.animate(1000, 500, 'now').move(100, 200) + + timeline.finish() + + expect(rect1.x()).toEqual(100) + expect(rect1.y()).toEqual(200) + + expect(rect2.x()).toEqual(100) + expect(rect2.y()).toEqual(200) + + expect(rect3.x()).toEqual(100) + expect(rect3.y()).toEqual(200) + }) + + it('places all elements at the right position - runner that finishes latest is in middle position', () => { + const timeline = new Timeline() + + const rect1 = canvas.rect(10, 10) + rect1.timeline(timeline) + + const rect2 = canvas.rect(10, 10) + rect2.timeline(timeline) + + const rect3 = canvas.rect(10, 10) + rect3.timeline(timeline) + + rect2.animate(1000, 0, 'now').move(100, 200) + rect1.animate(2000, 0, 'now').move(100, 200) + rect3.animate(1000, 500, 'now').move(100, 200) + + timeline.finish() + + expect(rect1.x()).toEqual(100) + expect(rect1.y()).toEqual(200) + + expect(rect2.x()).toEqual(100) + expect(rect2.y()).toEqual(200) + + expect(rect3.x()).toEqual(100) + expect(rect3.y()).toEqual(200) + }) + + it('places all elements at the right position - runner that finishes latest is in last position', () => { + const timeline = new Timeline() + + const rect1 = canvas.rect(10, 10) + rect1.timeline(timeline) + + const rect2 = canvas.rect(10, 10) + rect2.timeline(timeline) + + const rect3 = canvas.rect(10, 10) + rect3.timeline(timeline) + + rect2.animate(1000, 0, 'now').move(100, 200) + rect3.animate(1000, 500, 'now').move(100, 200) + rect1.animate(2000, 0, 'now').move(100, 200) + + timeline.finish() + + expect(rect1.x()).toEqual(100) + expect(rect1.y()).toEqual(200) + + expect(rect2.x()).toEqual(100) + expect(rect2.y()).toEqual(200) + + expect(rect3.x()).toEqual(100) + expect(rect3.y()).toEqual(200) + }) + }) + + describe('updateTime()', () => { + it('sets the time to the current time', () => { + const timeline = new Timeline(() => 200).play() + expect(timeline._lastSourceTime).toBe(200) + }) + }) + + describe('stop()', () => { + it('sets the time to 0 and pauses the timeline', () => { + const timeline = new Timeline().time(100) + expect(timeline.stop().time()).toBe(0) + expect(timeline._paused).toBe(true) + }) + }) + + describe('speed()', () => { + it('gets or sets the speed of the timeline', () => { + const timeline = new Timeline().speed(2) + expect(timeline.speed()).toBe(2) + }) + }) + + describe('reverse()', () => { + it('reverses the timeline with no parameter given', () => { + const timeline = new Timeline().speed(2) + const spy = spyOn(timeline, 'speed').and.callThrough() + timeline.reverse() + expect(spy).toHaveBeenCalledWith(-2) + timeline.reverse() + expect(spy).toHaveBeenCalledWith(2) + }) + + it('reverses the timeline when true was passed', () => { + const timeline = new Timeline().speed(2) + const spy = spyOn(timeline, 'speed').and.callThrough() + timeline.reverse(true) + expect(spy).toHaveBeenCalledWith(-2) + }) + + it('plays normal direction when false was passed', () => { + const timeline = new Timeline().speed(-2) + const spy = spyOn(timeline, 'speed').and.callThrough() + timeline.reverse(false) + expect(spy).toHaveBeenCalledWith(2) + }) + }) + + describe('seek()', () => { + it('seeks the time by a given delta', () => { + const timeline = new Timeline().time(100).seek(200) + expect(timeline.time()).toBe(300) + }) + }) + + describe('time()', () => { + it('gets and sets the current time of the timeline', () => { + const timeline = new Timeline().time(100) + expect(timeline.time()).toBe(100) + }) + }) + + describe('persist()', () => { + it('gets and sets the persist property of the timeline', () => { + const timeline = new Timeline().persist(true) + expect(timeline.persist()).toBe(true) + }) + }) + + describe('source()', () => { + it('gets or sets the time source of the timeline', () => { + const source = () => 200 + const timeline = new Timeline().source(source) + expect(timeline.source()).toBe(source) + }) + }) + + describe('_stepFn', () => { + it('does a step in the timeline and runs all runners', () => { + const timeline = new Timeline() + const runner = new Runner(1000) + timeline.schedule(runner).play() // we have to play because its synchronous here + jasmine.RequestAnimationFrame.tick(16) + expect(runner.time()).toBe(16) + }) + + it('doenst run runners which start later', () => { + const timeline = new Timeline() + const runner = new Runner(1000) + timeline.schedule(runner, 100).play() // we have to play because its synchronous here + jasmine.RequestAnimationFrame.tick(16) + expect(runner.time()).toBe(0) + }) + + it('reset runner if timeline was seeked backwards', () => { + const timeline = new Timeline() + const runner = new Runner(1000) + timeline.schedule(runner) + const spy = spyOn(runner, 'reset').and.callThrough() + jasmine.RequestAnimationFrame.tick(1000) + timeline.seek(-1000) + expect(runner.time()).toBe(0) + expect(spy).toHaveBeenCalled() + }) + + it('does not run runners if they are not active', () => { + const timeline = new Timeline() + const runner = new Runner(1000).active(false) + timeline.schedule(runner).play() // we have to play because its synchronous here + jasmine.RequestAnimationFrame.tick(16) + expect(runner.time()).toBe(0) + }) + + it('unschedules runner if its finished', () => { + const timeline = new Timeline() + const runner = new Runner(1000) + timeline.schedule(runner).play() // we have to play because its synchronous here + jasmine.RequestAnimationFrame.tick(1000) + jasmine.RequestAnimationFrame.tick(1) + expect(runner.time()).toBe(1001) + expect(timeline.getRunnerInfoById(runner.id)).toBe(null) + }) + + it('does not unschedule if runner is persistent forever', () => { + const timeline = new Timeline() + const runner = new Runner(1000).persist(true) + timeline.schedule(runner).play() // we have to play because its synchronous here + jasmine.RequestAnimationFrame.tick(1000) + jasmine.RequestAnimationFrame.tick(1) + expect(runner.time()).toBe(1001) + expect(timeline.getRunnerInfoById(runner.id)).not.toBe(null) + }) + + it('does not unschedule if runner is persistent for a certain time', () => { + const timeline = new Timeline() + const runner = new Runner(1000).persist(100) + timeline.schedule(runner).play() // we have to play because its synchronous here + jasmine.RequestAnimationFrame.tick(1000) + jasmine.RequestAnimationFrame.tick(1) + expect(runner.time()).toBe(1001) + expect(timeline.getRunnerInfoById(runner.id)).not.toBe(null) + }) + + it('fires finish if no runners left', () => { + const spy = createSpy() + const timeline = new Timeline().on('finished', spy) + const runner = new Runner(1000) + spy.calls.reset() + timeline.schedule(runner).play() // we have to play because its synchronous here + jasmine.RequestAnimationFrame.tick(1000) + jasmine.RequestAnimationFrame.tick(1) + expect(spy).toHaveBeenCalled() + }) + + it('continues if there are still runners left from us when going back in time', () => { + const spy = createSpy() + const timeline = new Timeline() + .on('finished', spy) + .time(1200) + .reverse(true) + const runner = new Runner(1000) + spy.calls.reset() + timeline.schedule(runner, 0).play() // we have to play because its synchronous here + jasmine.RequestAnimationFrame.tick(1) + expect(spy).not.toHaveBeenCalled() + }) + + it('finishes if time is backwards and 0', () => { + const spy = createSpy() + const timeline = new Timeline().on('finished', spy).reverse(true) + const runner = new Runner(1000) + spy.calls.reset() + timeline.schedule(runner, 0).play() // we have to play because its synchronous here + jasmine.RequestAnimationFrame.tick(1) + expect(spy).toHaveBeenCalled() + }) + }) + + describe('Element', () => { + describe('timeline()', () => { + it('sets and gets the timeline of the element', () => { + const timeline = new Timeline() + const rect = new Rect().timeline(timeline) + expect(rect.timeline()).toBe(timeline) + }) + + it('creates a timeline on the fly when getting it', () => { + expect(new Rect().timeline()).toEqual(any(Timeline)) + }) + }) + }) +}) diff --git a/spec/spec/arrange.js b/spec/spec/arrange.js deleted file mode 100644 index 3424b2a30..000000000 --- a/spec/spec/arrange.js +++ /dev/null @@ -1,185 +0,0 @@ -describe('Arrange', function() { - var e1, e2, e3 - - beforeEach(function() { - draw.clear() - - e1 = draw.rect(100,100).move(10,10).attr('id', 'e1') - e2 = draw.ellipse(100,100).move(20,20).attr('id', 'e2') - e3 = draw.line(0,0,100,100).move(30,30).attr('id', 'e3') - }) - - describe('siblings()', function() { - it('returns all siblings of targeted element', function() { - expect(e1.siblings().length).toBe(3+parserInDoc) - expect(parser.concat([e1,e2,e3])).toEqual(e2.siblings()) - }) - }) - - describe('position()', function() { - it('returns the index position within it\'s parent', function() { - expect(e1.siblings().length).toBe(3+parserInDoc) - expect(e1.position()).toBe(0+parserInDoc) - expect(e2.position()).toBe(1+parserInDoc) - expect(e3.position()).toBe(2+parserInDoc) - }) - }) - - describe('next()', function() { - it('returns the next sibling within the parent element', function() { - expect(e1.next()).toBe(e2) - expect(e2.next()).toBe(e3) - expect(e3.next()).toBe(undefined) - }) - }) - - describe('previous()', function() { - it('returns the previous sibling within the parent element', function() { - expect(e1.previous()).toBe(parser[0]) - expect(e2.previous()).toBe(e1) - expect(e3.previous()).toBe(e2) - }) - }) - - describe('forward()', function() { - it('returns the element itself', function() { - expect(e1.forward()).toBe(e1) - }) - it('moves the element one step forward within its parent', function() { - e1.forward() - expect(e1.position()).toBe(1+parserInDoc) - expect(e2.position()).toBe(0+parserInDoc) - expect(e3.position()).toBe(2+parserInDoc) - }) - it('keeps the last element at the same position', function() { - e3.forward() - expect(e3.position()).toBe(2+parserInDoc) - }) - it('keeps the defs on top of the stack', function() { - draw.defs() - e3.forward() - expect(draw.node.childNodes[2+parserInDoc]).toBe(e3.node) - expect(draw.node.childNodes[3+parserInDoc]).toBe(draw.defs().node) - }) - }) - - describe('backward()', function() { - it('returns the element itself', function() { - if(parserInDoc){ - expect(parser[0].backward()).toBe(parser[0]) - }else{ - expect(e1.backward()).toBe(e1) - } - }) - it('moves the element one step backwards within its parent', function() { - e3.backward() - expect(e1.position()).toBe(0+parserInDoc) - expect(e2.position()).toBe(2+parserInDoc) - expect(e3.position()).toBe(1+parserInDoc) - }) - it('keeps the first element at the same position', function() { - e3.backward() - expect(e1.position()).toBe(0+parserInDoc) - }) - }) - - describe('front()', function() { - it('returns the element itself', function() { - expect(e3.front()).toBe(e3) - }) - it('moves the element to the top of the stack within its parent', function() { - e1.front() - expect(e1.position()).toBe(2+parserInDoc) - expect(e2.position()).toBe(0+parserInDoc) - expect(e3.position()).toBe(1+parserInDoc) - }) - it('keeps the last element at the same position', function() { - e3.front() - expect(e3.position()).toBe(2+parserInDoc) - }) - it('keeps the defs on top of the stack', function() { - e1.front() - expect(draw.node.childNodes[2+parserInDoc]).toBe(e1.node) - expect(draw.node.childNodes[3+parserInDoc]).toBe(draw.defs().node) - }) - }) - - describe('back()', function() { - it('returns the element itself', function() { - expect(e3.back()).toBe(e3) - }) - it('moves the element to the bottom of the stack within its parent', function() { - e3.back() - expect(e1.position()).toBe(1+parserInDoc) - expect(e2.position()).toBe(2+parserInDoc) - expect(e3.position()).toBe(0) - }) - it('keeps the first element at the same position', function() { - e1.back() - expect(e1.position()).toBe(0) - }) - }) - - describe('before()', function() { - it('returns the targeted element itself', function() { - expect(e3.before(e1)).toBe(e3) - }) - it('inserts a given element before the targeted element', function() { - e3.before(e1) - expect(e1.position()).toBe(1+parserInDoc) - expect(e2.position()).toBe(0+parserInDoc) - expect(e3.position()).toBe(2+parserInDoc) - }) - it('moves elements between containers', function() { - var group = draw.group() - , e4 = group.rect(80,120) - , e5 = group.rect(80,120) - , e6 = group.rect(80,120) - - e2.before(e5) - expect(e1.position()).toBe(0+parserInDoc) - expect(e2.position()).toBe(2+parserInDoc) - expect(e3.position()).toBe(3+parserInDoc) - expect(e5.position()).toBe(1+parserInDoc) - }) - }) - - describe('after()', function() { - it('returns the targeted element itself', function() { - expect(e3.after(e1)).toBe(e3) - }) - it('inserts a given element after the targeted element', function() { - e3.after(e1) - expect(e1.position()).toBe(2+parserInDoc) - expect(e2.position()).toBe(0+parserInDoc) - expect(e3.position()).toBe(1+parserInDoc) - }) - it('moves elements between containers', function() { - var group = draw.group() - , e4 = group.rect(80,120) - , e5 = group.rect(80,120) - , e6 = group.rect(80,120) - - e2.after(e5) - expect(e1.position()).toBe(0+parserInDoc) - expect(e2.position()).toBe(1+parserInDoc) - expect(e3.position()).toBe(3+parserInDoc) - expect(e5.position()).toBe(2+parserInDoc) - }) - }) - -}) - - - - - - - - - - - - - - diff --git a/spec/spec/array.js b/spec/spec/array.js deleted file mode 100644 index 7530ea16d..000000000 --- a/spec/spec/array.js +++ /dev/null @@ -1,404 +0,0 @@ -describe('Array', function () { - var array, arr1, arr2 - - it('parses a matrix array correctly to string', function() { - array = new SVG.Array([ .343, .669, .119, 0, 0 - , .249, -.626, .130, 0, 0 - , .172, .334, .111, 0, 0 - , .000, .000, .000, 1, -0 ]) - - expect(array + '').toBe('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') - }) - it('parses space seperated string and converts it to array', function() { - expect((new SVG.Array('1 2 3 4')).value).toEqual([1,2,3,4]) - }) - it('parses comma seperated string and converts it to array', function() { - expect((new SVG.Array('1,2,3,4')).value).toEqual([1,2,3,4]) - }) - describe('reverse()', function() { - it('reverses the array', function() { - array = new SVG.Array([1 ,2 ,3, 4, 5]).reverse() - expect(array.value).toEqual([5, 4, 3, 2, 1]) - }) - it('returns itself', function() { - array = new SVG.Array() - expect(array.reverse()).toBe(array) - }) - }) - describe('clone()', function() { - it('creates a deep clone of the array', function() { - array = new SVG.Array([1, 2, 3, 4, 5]) - - clone = array.clone() - - expect(array).toEqual(clone) - expect(array).not.toBe(clone) - - array = new SVG.Array([[1,2], [3, 4], [5]]) - clone = array.clone() - - expect(array).toEqual(array) - for(var i = 0, len = array.value.length; i; ++i){ - expect(array[i]).not.toBe(clone[i]) - } - }) - it('also works with PointArray', function() { - array = new SVG.PointArray([1,2,3,4,5,6]) - clone = array.clone() - - expect(array).toEqual(clone) - expect(array).not.toBe(clone) - - for(var i = 0, len = array.value.length; i; ++i){ - expect(array[i]).not.toBe(clone[i]) - } - }) - it('also works with PathArray', function() { - array = new SVG.PathArray([['M',1,2],['L',3,4],['L',5,6]]) - clone = array.clone() - - expect(array).toEqual(clone) - expect(array).not.toBe(clone) - - for(var i = 0, len = array.value.length; i; ++i){ - expect(array[i]).not.toBe(clone[i]) - } - }) - }) - describe('morph()', function() { - it('adds entries so that destination array has equal length', function() { - - arr1 = new SVG.Array([1,2,3,4,5]) - arr2 = new SVG.Array([1,2,3,4]) - - arr1.morph(arr2) - - expect(arr1.destination.length).toBe(arr1.value.length) - }) - it('does the same the other way round', function() { - - arr1 = new SVG.Array([1,2,3,4]) - arr2 = new SVG.Array([1,2,3,4,5]) - - arr1.morph(arr2) - - expect(arr1.destination.length).toBe(arr1.value.length) - }) - }) - describe('settle()', function() { - it('cleans up any duplicate value', function() { - array = new SVG.Array([1,2,3,4,5,4,3,2,1]) - expect(array.settle().sort()).toEqual([1,2,3,4,5].sort()) - }) - }) - describe('at()', function() { - beforeEach(function() { - arr1 = new SVG.Array([1,2,3,4]) - arr2 = new SVG.Array([2,3,4,5]) - }) - - it('returns a new array instance', function() { - arr1.morph(arr2) - - start = arr1.at(0) - end = arr1.at(1) - - expect(start instanceof SVG.Array).toBeTruthy() - expect(start).not.toBe(arr1) - - expect(end instanceof SVG.Array).toBeTruthy() - expect(end).not.toBe(arr2) - }) - it('morphs all values of the array', function() { - arr1.morph(arr2) - expect(arr1.at(0.5).value).toEqual([1.5, 2.5, 3.5, 4.5]) - }) - it('returns itself if no destination was specified', function() { - expect(arr1.at(0.5)).toBe(arr1) - }) - }) -}) - - -describe('PointArray', function () { - it('parses a string to a point array', function() { - var array = new SVG.PointArray('0,1 -.05,7.95 1000.0001,-200.222') - - expect(array.valueOf()).toEqual([[0, 1], [-0.05, 7.95], [1000.0001, -200.222]]) - }) - it('parses a points array correctly to string', function() { - var array = new SVG.PointArray([[0,.15], [-100,-3.141592654], [50,100]]) - - expect(array + '').toBe('0,0.15 -100,-3.141592654 50,100') - }) - it('parses a flat array of x/y coordinates to a point array', function() { - var array = new SVG.PointArray([1,4, 5,68, 12,24]) - - expect(array.value).toEqual([[1,4], [5,68], [12,24]]) - }) - it('parses points with space delimitered x/y coordinates', function() { - var array = new SVG.PointArray('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') - - expect(array + '').toBe('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') - }) - it('parses points with comma delimitered x/y coordinates', function() { - var array = new SVG.PointArray('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') - - expect(array + '').toBe('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') - }) - it('parses points with comma and space delimitered x/y coordinates', function() { - var array = new SVG.PointArray('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') - - expect(array + '').toBe('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') - }) - it('parses points with space and comma delimitered x/y coordinates', function() { - var array = new SVG.PointArray('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') - - expect(array + '').toBe('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') - }) - it('parses points with redundant spaces at the end', function() { - var array = new SVG.PointArray('2176.6,1708.8 2176.4,1755.8 2245.8,1801.5 2297,1787.8 ') - - expect(array + '').toBe('2176.6,1708.8 2176.4,1755.8 2245.8,1801.5 2297,1787.8') - }) - it('parses points with space delimitered x/y coordinates - even with leading or trailing space', function() { - var array = new SVG.PointArray(' 1 2 3 4 ') - - expect(array + '').toBe('1,2 3,4') - }) - it('parses odd number of points with space delimitered x/y coordinates and silently remove the odd point', function() { - // this is according to spec: https://svgwg.org/svg2-draft/shapes.html#DataTypePoints - - var array = new SVG.PointArray('1 2 3') - - expect(array + '').toBe('1,2') - }) - it('parses odd number of points in a flat array of x/y coordinates and silently remove the odd point', function() { - // this is according to spec: https://svgwg.org/svg2-draft/shapes.html#DataTypePoints - - var array = new SVG.PointArray([1, 2, 3]) - - expect(array.value).toEqual([[1,2]]) - }) - - describe('size()', function() { - it('correctly sizes the points over the whole area', function() { - var array = new SVG.PointArray([10, 10, 20, 20, 30, 30]) - expect(array.size(60, 60).valueOf()).toEqual([[10,10], [40, 40], [70, 70]]) - }) - - it('let coordinates untouched when width/height is zero', function() { - var array = new SVG.PointArray([10, 10, 10, 20, 10, 30]) - expect(array.size(60, 60).valueOf()).toEqual([[10,10], [10, 40], [10, 70]]) - - array = new SVG.PointArray([10, 10, 20, 10, 30, 10]) - expect(array.size(60, 60).valueOf()).toEqual([[10,10], [40, 10], [70, 10]]) - }) - - }) - - describe('at()', function() { - var arr1, arr2 - - beforeEach(function() { - arr1 = new SVG.PointArray([[1,2],[3,4]]) - arr2 = new SVG.Array([[2,3],[4,5]]) - }) - - it('returns a new array instance', function() { - arr1.morph(arr2) - - start = arr1.at(0) - end = arr1.at(1) - - expect(start instanceof SVG.PointArray).toBeTruthy() - expect(start).not.toBe(arr1) - - expect(end instanceof SVG.PointArray).toBeTruthy() - expect(end).not.toBe(arr2) - }) - it('morphs all values of the array', function() { - arr1.morph(arr2) - expect(arr1.at(0.5).value).toEqual([[1.5, 2.5], [3.5, 4.5]]) - }) - it('returns itself if no destination was specified', function() { - expect(arr1.at(0.5)).toBe(arr1) - }) - }) -}) - -describe('PathArray', function () { - var p1, p2, p3, p4, p5, p6, p7 - - beforeEach(function() { - p1 = new SVG.PathArray('m10 10 h 80 v 80 h -80 l 300 400 z') - p2 = new SVG.PathArray('m10 80 c 40 10 65 10 95 80 s 150 150 180 80 t 300 300 q 52 10 95 80 z') - p3 = new SVG.PathArray('m80 80 A 45 45, 0, 0, 0, 125 125 L 125 80 z') - p4 = new SVG.PathArray('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') - p5 = new SVG.PathArray('M10 10-45-30.5.5 .89L2e-2.5.5.5-.5C.5.5.5.5.5.5L-3-4z') - p6 = new SVG.PathArray('m 0,0 0,3189 2209,0 0,-3189 -2209,0 z m 154,154 1901,0 0,2881 -1901,0 0,-2881 z') - - }) - - it('converts to absolute values', function() { - expect(p1.toString()).toBe('M10 10H90V90H10L310 490Z ') - expect(p2.toString()).toBe('M10 80C50 90 75 90 105 160S255 310 285 240T585 540Q637 550 680 620Z ') - expect(p3.toString()).toBe('M80 80A45 45 0 0 0 125 125L125 80Z ') - expect(p4.toString()).toBe('M215.458 245.23C215.458 245.23 292.861 245.23 309.73199999999997 245.23S405 216.451 405 138.054S329.581 15 287.9 15C246.21999999999997 15 147.97599999999997 15 117.21199999999999 15C86.45 15 15 60.65 15 134.084C15 207.518 111.259 246.221 129.122 246.221C146.984 246.221 215.458 245.23 215.458 245.23Z ') - expect(p6.toString()).toBe('M0 0L0 3189L2209 3189L2209 0L0 0ZM154 154L2055 154L2055 3035L154 3035L154 154Z ') - }) - - it('parses difficult syntax correctly', function() { - expect(p5.toString()).toBe('M10 10L-45 -30.5L0.5 0.89L0.02 0.5L0.5 -0.5C0.5 0.5 0.5 0.5 0.5 0.5L-3 -4Z ') - }) - - it('parses flat arrays correctly', function() { - p6 = new SVG.PathArray([ 'M', 0, 0, 'L', 100, 100, 'z' ]) - expect(p6.toString()).toBe('M0 0L100 100Z ') - }) - - it('parses nested arrays correctly', function() { - p7 = new SVG.PathArray([ ['M', 0, 0], ['L', 100, 100], ['z'] ]) - expect(p7.toString()).toBe('M0 0L100 100Z ') - }) - - // this test is designed to cover a certain line but it doesnt work because of #608 - it('returns the valueOf when PathArray is given', function() { - var p = new SVG.PathArray('m10 10 h 80 v 80 h -80 l 300 400 z') - - expect((new SVG.PathArray(p)).value).toEqual(p.value) - }) - - it('can handle all formats which can be used', function() { - // when no command is specified after move, line is used automatically (specs say so) - expect(new SVG.PathArray('M10 10 80 80 30 30 Z').toString()).toBe('M10 10L80 80L30 30Z ') - - // parsing can handle 0.5.3.3.2 stuff - expect(new SVG.PathArray('M10 10L.5.5.3.3Z').toString()).toBe('M10 10L0.5 0.5L0.3 0.3Z ') - }) - - describe('move()', function() { - it('moves all points in a straight path', function() { - expect(p1.move(100,200).toString()).toBe('M100 200H180V280H100L400 680Z ') - }) - it('moves all points in a curved path', function() { - expect(p2.move(100,200).toString()).toBe('M100 200C140 210 165 210 195 280S345 430 375 360T675 660Q727 670 770 740Z ') - }) - it('moves all points in a arc path', function() { - expect(p3.move(100,200).toString()).toBe('M100 200A45 45 0 0 0 145 245L145 200Z ') - }) - }) - - describe('size()', function() { - it('resizes all points in a straight path', function() { - expect(p1.size(600,200).toString()).toBe('M10 10H170V43.333333333333336H10L610 210Z ') - }) - it('resizes all points in a curved path', function() { - expect(p2.size(600,200).toString()).toBe('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 ') - }) - it('resizes all points in a arc path', function() { - var expected = [ - ['M', 80, 80], - ['A', 600, 200, 0, 0, 0, 680, 280], - ['L', 680, 80], - ['Z'] - ] - - var toBeTested = p3.size(600,200).value - for(var i in toBeTested) { - expect(toBeTested[i].shift().toUpperCase()).toBe(expected[i].shift().toUpperCase()) - for(var j in toBeTested[i]) { - expect(toBeTested[i][j]).toBeCloseTo(expected[i][j]) - } - } - }) - }) - - describe('equalCommands()', function() { - it('return true if the passed path array use the same commands', function() { - var pathArray1 = new SVG.PathArray('m -1500,-478 a 292,195 0 0 1 262,205 l -565,319 c 0,0 -134,-374 51,-251 185,122 251,-273 251,-273 z') - , pathArray2 = new SVG.PathArray('m -680, 527 a 292,195 0 0 1 262,205 l -565,319 c 0,0 -134,-374 51,-251 185,122 251,-273 251,-273 z') - - expect(pathArray1.equalCommands(pathArray2)).toBe(true) - }) - it('return false if the passed path array does not use the same commands', function() { - var pathArray1 = new SVG.PathArray('m -1500,-478 a 292,195 0 0 1 262,205 l -565,319 c 0,0 -134,-374 51,-251 185,122 251,-273 251,-273 z') - , pathArray2 = new SVG.PathArray('m - 663, 521 c 147,178 118,-25 245,210 l -565,319 c 0,0 -134,-374 51,-251 185,122 268,-278 268,-278 z') - - expect(pathArray1.equalCommands(pathArray2)).toBe(false) - }) - }) - - describe('morph()', function() { - it('should set the attribute destination to the passed path array when it have the same comands as this path array', function() { - var pathArray1 = new SVG.PathArray('m -1500,-478 a 292,195 0 0 1 262,205 l -565,319 c 0,0 -134,-374 51,-251 185,122 251,-273 251,-273 z') - , pathArray2 = new SVG.PathArray('m -680, 527 a 292,195 0 0 1 262,205 l -565,319 c 0,0 -134,-374 51,-251 185,122 251,-273 251,-273 z') - - pathArray1.morph(pathArray2) - expect(pathArray1.destination).toEqual(pathArray2) - }) - it('should set the attribute destination to null when the passed path array does not have the same comands as this path array', function() { - var pathArray1 = new SVG.PathArray('m -1500,-478 a 292,195 0 0 1 262,205 l -565,319 c 0,0 -134,-374 51,-251 185,122 251,-273 251,-273 z') - , pathArray2 = new SVG.PathArray('m - 663, 521 c 147,178 118,-25 245,210 l -565,319 c 0,0 -134,-374 51,-251 185,122 268,-278 268,-278 z') - - pathArray1.morph(pathArray2) - expect(pathArray1.destination).toBeNull() - }) - }) - - describe('at()', function() { - it('returns a morphed path array at a given position', function() { - var pathArray1 = new SVG.PathArray("M 63 25 A 15 15 0 0 1 73 40 A 15 15 0 0 1 61 53 C 49 36 50 59 50 59 L 33 55 Z") - , pathArray2 = new SVG.PathArray("M 132 40 A 15 15 0 0 1 141 54 A 15 15 0 0 1 130 67 C 118 51 119 73 119 73 L 103 69 Z") - , morphedPathArray = pathArray1.morph(pathArray2).at(0.5) - , sourceArray = pathArray1.value, destinationArray = pathArray1.destination.value - , morphedArray = morphedPathArray.value - , i, il, j, jl - - expect(morphedArray.length).toBe(sourceArray.length) - - // For all the commands - for(i = 0, il = sourceArray.length; i < il; i++) { - // Expect the current command to be the same - expect(morphedArray[i][0]).toBe(sourceArray[i][0]) - expect(morphedArray[i].length).toBe(sourceArray[i].length) - - // For all the parameters of the current command - for(j = 1, jl = sourceArray[i].length; j < jl; j++) { - expect(morphedArray[i][j]).toBe((sourceArray[i][j] + destinationArray[i][j]) / 2) - } - } - }) - it('should interpolate flags and booleans as fractions between zero and one, with any non-zero value considered to be a value of one/true', function() { - // Only the Elliptical arc command use flags, it has the following form: - // A rx ry x-axis-rotation large-arc-flag sweep-flag x y - var pathArray1 = new SVG.PathArray('M 13 13 A 25 37 0 0 1 43 25') - , pathArray2 = new SVG.PathArray('M 101 55 A 25 37 0 1 0 130 67') - , morphedPathArray - - pathArray1.morph(pathArray2) - - // The morphedPathArray.value contain 2 commands: [['M', ...], ['A', ...]] - // Elliptical arc command in a path array followed by corresponding indexes: - // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] - // 0 1 2 3 4 5 6 7 - morphedPathArray = pathArray1.at(0) - expect(morphedPathArray.value[1][4]).toBe(0) - expect(morphedPathArray.value[1][5]).toBe(1) - - morphedPathArray = pathArray1.at(0.5) - expect(morphedPathArray.value[1][4]).toBe(1) - expect(morphedPathArray.value[1][5]).toBe(1) - - morphedPathArray = pathArray1.at(1) - expect(morphedPathArray.value[1][4]).toBe(1) - expect(morphedPathArray.value[1][5]).toBe(0) - }) - it('return itself if the destination attribute is null', function(){ - var pathArray = new SVG.PathArray('M 13 13 A 25 37 0 0 1 43 25') - pathArray.destination = null - expect(pathArray.at(0.45)).toBe(pathArray) - }) - }) - -}) diff --git a/spec/spec/bare.js b/spec/spec/bare.js deleted file mode 100644 index 0488bbd83..000000000 --- a/spec/spec/bare.js +++ /dev/null @@ -1,41 +0,0 @@ -describe('Bare', function() { - - describe('element()', function() { - var element - - beforeEach(function() { - element = draw.element('rect') - }) - - it('creates an instance of SVG.Bare', function() { - expect(element instanceof SVG.Bare).toBeTruthy() - }) - it('creates element in called parent', function() { - expect(element.parent()).toBe(draw) - }) - it('inherits from given parent', function() { - expect(draw.element('g', SVG.Container).rect).toBeTruthy() - expect(draw.element('g', SVG.Container).group).toBeTruthy() - }) - }) - - describe('words()', function() { - it('inserts plain text in a node', function() { - var element = draw.element('title').words('These are some words.').id(null) - var result = element.svg() - expect( - result == 'These are some words.' - || result == 'These are some words.' - ).toBe(true) - }) - it('removes all nodes before adding words', function() { - var element = draw.element('title').words('These are some words.').id(null) - element.words('These are some words.') - var result = element.svg() - expect( - result == 'These are some words.' - || result == 'These are some words.' - ).toBe(true) - }) - }) -}) diff --git a/spec/spec/boxes.js b/spec/spec/boxes.js deleted file mode 100644 index b3c9685d3..000000000 --- a/spec/spec/boxes.js +++ /dev/null @@ -1,233 +0,0 @@ -describe('Box', function() { - it('creates a new instance without passing anything', function() { - var box = new SVG.Box - - expect(box instanceof SVG.Box).toBe(true) - expect(box).toEqual(jasmine.objectContaining({ - x:0, y:0, cx:0, cy:0, width:0, height:0 - })) - }) - - it('creates a new instance with 4 arguments given', function() { - var box = new SVG.Box(10, 20, 100, 50) - - expect(box instanceof SVG.Box).toBe(true) - expect(box).toEqual(jasmine.objectContaining({ - x:10, y:20, cx:60, cy:45, width:100, height:50 - })) - }) - - it('creates a new instance with object given', function() { - var box = new SVG.Box({x:10, y:20, width: 100, height:50}) - - expect(box instanceof SVG.Box).toBe(true) - expect(box).toEqual(jasmine.objectContaining({ - x:10, y:20, cx:60, cy:45, width:100, height:50 - })) - }) - - describe('merge()', function() { - it('merges various bounding boxes', function() { - var box1 = new SVG.Box(50, 50, 100, 100) - var box2 = new SVG.Box(300, 400, 100, 100) - var box3 = new SVG.Box(500, 100, 100, 100) - var merged = box1.merge(box2).merge(box3) - - expect(merged).toEqual(jasmine.objectContaining({ - x: 50, y: 50, cx: 325, cy: 275, width: 550, height: 450 - })) - }) - it('returns a new instance', function() { - var box1 = new SVG.Box(50, 50, 100, 100) - var box2 = new SVG.Box(300, 400, 100, 100) - var merged = box1.merge(box2) - expect(box1).not.toBe(merged) - expect(box2).not.toBe(merged) - - expect(merged instanceof SVG.Box).toBe(true) - }) - }) - - describe('transform()', function() { - it('transforms the box with given matrix', function() { - var box1 = new SVG.Box(50, 50, 100, 100).transform(new SVG.Matrix(1,0,0,1,20,20)) - var box2 = new SVG.Box(50, 50, 100, 100).transform(new SVG.Matrix(2,0,0,2,0,0)) - var box3 = new SVG.Box(-200, -200, 100, 100).transform(new SVG.Matrix(1,0,0,1,-20,-20)) - - expect(box1).toEqual(jasmine.objectContaining({ - x: 70, y: 70, cx: 120, cy: 120, width: 100, height: 100 - })) - - expect(box2).toEqual(jasmine.objectContaining({ - x: 100, y: 100, cx: 200, cy: 200, width: 200, height: 200 - })) - - expect(box3).toEqual(jasmine.objectContaining({ - x: -220, y: -220, cx: -170, cy: -170, width: 100, height: 100 - })) - }) - }) -}) - -describe('BBox', function() { - - afterEach(function() { - draw.clear() - }) - - it('creates a new instance from an element', function() { - var rect = draw.rect(100, 100).move(100, 25) - var box = new SVG.BBox(rect) - - expect(box).toEqual(jasmine.objectContaining({ - x: 100, y: 25, cx: 150, cy: 75, width: 100, height: 100 - })) - }) - - describe('merge()', function() { - it('returns an instance of SVG.BBox', function() { - var box1 = new SVG.BBox(50, 50, 100, 100) - var box2 = new SVG.BBox(300, 400, 100, 100) - var merged = box1.merge(box2) - - expect(merged instanceof SVG.BBox).toBe(true) - }) - }) - -}) - -describe('TBox', function() { - - afterEach(function() { - draw.clear() - }) - - it('should map to RBox and be removed in 3.x', function() { - var rect = draw.rect(100, 100).move(100, 25).stroke({width:0}) - var tbox = rect.tbox() - - expect(tbox.x).toBe(100) - expect(tbox.y).toBeCloseTo(25) - - rect.transform({ scale: 1.5 }) - tbox = rect.tbox() - expect(tbox.x).toBe(75) - expect(tbox.y).toBe(0) - - rect.transform({ skewX: 5 }) - tbox = rect.tbox() - expect(tbox.x|0).toBe(68) - expect(tbox.y|0).toBe(0) - }) - -}) - -describe('RBox', function() { - - afterEach(function() { - draw.clear() - }) - - it('creates a new instance from an element', function() { - var rect = draw.rect(100, 100).move(100, 25).stroke({width:0}) - var box = new SVG.RBox(rect).transform(rect.doc().screenCTM().inverse()).addOffset() - expect(window.roundBox(box)).toEqual(jasmine.objectContaining({ - x: 100, y: 25, cx: 150, cy: 75, width: 100, height: 100 - })) - }) - - describe('merge()', function() { - it('returns an instance of SVG.RBox', function() { - var box1 = new SVG.RBox(50, 50, 100, 100) - var box2 = new SVG.RBox(300, 400, 100, 100) - var merged = box1.merge(box2) - - expect(merged instanceof SVG.RBox).toBe(true) - }) - }) -}) - -describe('Boxes', function() { - var rect, nested, offset - - beforeEach(function() { - offset = draw.screenCTM() - draw.viewbox(100,100, 200, 200) - nested = draw.nested().size(200, 200).move(100,100).viewbox(0, 0, 100, 100) - rect = nested.rect(50, 180).stroke({width:0}).move(25, 90).scale(2, 0, 0).transform({x:10, y:10}, true) - }) - afterEach(function() { - draw.clear().attr('viewBox', null) - }) - - describe('bbox()', function() { - it('returns an instance of SVG.BBox', function() { - expect(rect.bbox() instanceof SVG.BBox).toBeTruthy() - }) - it('matches the size of the target element, ignoring transformations', function() { - var box = rect.bbox() - - expect(box).toEqual(jasmine.objectContaining({ - x: 25, y: 90, cx: 50, cy: 180, width: 50, height: 180 - })) - }) - it('returns a box even if the element is not in the dom', function() { - var line = new SVG.Line().plot(0, 0, 50, 50) - var box = line.bbox() - - expect(box).toEqual(jasmine.objectContaining({ - x: 0, y: 0, width: 50, height: 50 - })) - - expect('Should not result into infinite loop').toBe('Should not result into infinite loop') - }) - it('returns a box even if the element is not in the dom and invisible', function() { - var line = new SVG.Line().plot(0, 0, 50, 50).hide() - var box = line.bbox() - - expect(box).toEqual(jasmine.objectContaining({ - x: 0, y: 0, width: 50, height: 50 - })) - - expect('Should not result into infinite loop').toBe('Should not result into infinite loop') - }) - }) - - describe('rbox()', function() { - it('returns an instance of SVG.RBox', function() { - expect(rect.rbox() instanceof SVG.RBox).toBeTruthy() - }) - - it('returns the elements box in absolute screen coordinates by default', function() { - var box = rect.rbox() - - expect(window.roundBox(box)).toEqual(jasmine.objectContaining(window.roundBox({ - x: 70 + offset.e, y: 200 + offset.f, width: 100, height: 360 - }))) - - }) - - it('returns the elements box in coordinates of given element (doc)', function() { - var box = rect.rbox(draw) - - expect(window.roundBox(box)).toEqual(jasmine.objectContaining({ - x: 240, y: 500, width: 200, height: 720 - })) - }) - - it('returns the elements box in coordinates of given element (nested)', function() { - var box = rect.rbox(nested) - - expect(window.roundBox(box)).toEqual(jasmine.objectContaining({ - x: 70, y: 200, width: 100, height: 360 - })) - }) - }) - -}) - - - - - - diff --git a/spec/spec/circle.js b/spec/spec/circle.js deleted file mode 100644 index fc66e0c93..000000000 --- a/spec/spec/circle.js +++ /dev/null @@ -1,177 +0,0 @@ -describe('Circle', function() { - var circle - - beforeEach(function() { - circle = draw.circle(240) - }) - - afterEach(function() { - draw.clear() - }) - - describe('x()', function() { - it('returns the value of x without an argument', function() { - expect(circle.x()).toBe(0) - }) - it('sets the value of x with the first argument', function() { - circle.x(123) - var box = circle.bbox() - expect(box.x).toBeCloseTo(123) - }) - }) - - describe('y()', function() { - it('returns the value of y without an argument', function() { - expect(circle.y()).toBe(0) - }) - it('sets the value of cy with the first argument', function() { - circle.y(345) - var box = circle.bbox() - expect(box.y).toBe(345) - }) - }) - - describe('cx()', function() { - it('returns the value of cx without an argument', function() { - expect(circle.cx()).toBe(120) - }) - it('sets the value of cx with the first argument', function() { - circle.cx(123) - var box = circle.bbox() - expect(box.cx).toBe(123) - }) - }) - - describe('cy()', function() { - it('returns the value of cy without an argument', function() { - expect(circle.cy()).toBe(120) - }) - it('sets the value of cy with the first argument', function() { - circle.cy(345) - var box = circle.bbox() - expect(box.cy).toBe(345) - }) - }) - - describe('radius()', function() { - it('sets the r attribute with the first argument', function() { - circle.radius(10) - expect(circle.node.getAttribute('r')).toBe('10') - }) - }) - - describe('rx()', function() { - it('sets the r attribute with the first argument', function() { - circle.rx(11) - expect(circle.node.getAttribute('r')).toBe('11') - }) - it('gets the r attribute without and argument', function() { - circle.rx() - expect(circle.node.getAttribute('r')).toBe('120') - }) - }) - - describe('ry()', function() { - it('sets the r attribute with the first argument', function() { - circle.ry(12) - expect(circle.node.getAttribute('r')).toBe('12') - }) - it('gets the r attribute without and argument', function() { - circle.ry() - expect(circle.node.getAttribute('r')).toBe('120') - }) - }) - - describe('move()', function() { - it('sets the x and y position', function() { - circle.move(123, 456) - var box = circle.bbox() - expect(box.x).toBeCloseTo(123) - expect(box.y).toBe(456) - }) - }) - - describe('dx()', function() { - it('moves the x positon of the element relative to the current position', function() { - circle.move(50, 60) - circle.dx(100) - expect(circle.node.getAttribute('cx')).toBe('270') - }) - }) - - describe('dy()', function() { - it('moves the y positon of the element relative to the current position', function() { - circle.move(50, 60) - circle.dy(120) - expect(circle.node.getAttribute('cy')).toBe('300') - }) - }) - - describe('dmove()', function() { - it('moves the x and y positon of the element relative to the current position', function() { - circle.move(50,60) - circle.dmove(80, 25) - expect(circle.node.getAttribute('cx')).toBe('250') - expect(circle.node.getAttribute('cy')).toBe('205') - }) - }) - - describe('center()', function() { - it('sets the cx and cy position', function() { - circle.center(321,567) - var box = circle.bbox() - expect(box.cx).toBe(321) - expect(box.cy).toBe(567) - }) - }) - - describe('width()', function() { - it('sets the width and height of the element', function() { - circle.width(82) - expect(circle.node.getAttribute('r')).toBe('41') - }) - it('gets the width and height of the element if the argument is null', function() { - expect((circle.width() / 2).toString()).toBe(circle.node.getAttribute('r')) - }) - }) - - describe('height()', function() { - it('sets the height and width of the element', function() { - circle.height(1236) - expect(circle.node.getAttribute('r')).toBe('618') - }) - it('gets the height and width of the element if the argument is null', function() { - expect((circle.height() / 2).toString()).toBe(circle.node.getAttribute('r')) - }) - }) - - describe('size()', function() { - it('defines the r of the element', function() { - circle.size(987) - expect(circle.node.getAttribute('r')).toBe((987 / 2).toString()) - }) - }) - - describe('scale()', function() { - it('should scale the element universally with one argument', function() { - var box = circle.scale(2).rbox() - - expect(box.width).toBe(circle.attr('r') * 2 * 2) - expect(box.height).toBe(circle.attr('r') * 2 * 2) - }) - it('should scale the element over individual x and y axes with two arguments', function() { - var box = circle.scale(2, 3.5).rbox() - - expect(box.width).toBe(circle.attr('r') * 2 * 2) - expect(box.height).toBe(circle.attr('r') * 2 * 3.5) - }) - }) - - describe('translate()', function() { - it('sets the translation of an element', function() { - circle.transform({ x: 12, y: 12 }) - expect(window.matrixStringToArray(circle.node.getAttribute('transform'))).toEqual([1,0,0,1,12,12]) - }) - }) - -}) diff --git a/spec/spec/clip.js b/spec/spec/clip.js deleted file mode 100644 index c641c4c25..000000000 --- a/spec/spec/clip.js +++ /dev/null @@ -1,62 +0,0 @@ -describe('ClipPath', function() { - var rect, circle - - beforeEach(function() { - rect = draw.rect(100,100) - circle = draw.circle(100).move(50, 50) - rect.clipWith(circle) - }) - - afterEach(function() { - draw.clear() - }) - - it('moves the clipping element to a new clip node', function() { - expect(circle.parent() instanceof SVG.ClipPath).toBe(true) - }) - - it('creates the clip node in the defs node', function() { - expect(circle.parent().parent()).toBe(draw.defs()) - }) - - it('sets the "clip-path" attribute on the cliped element with the clip id', function() { - expect(rect.attr('clip-path')).toBe('url("#' + circle.parent().attr('id') + '")') - }) - - it('references the clip element in the masked element', function() { - expect(rect.clipper).toBe(circle.parent()) - }) - - it('references the clipped element in the clipPath target list', function() { - expect(rect.clipper.targets.indexOf(rect) > -1).toBe(true) - }) - - it('reuses clip element when clip was given', function() { - var clip = rect.clipper - expect(draw.rect(100,100).clipWith(clip).clipper).toBe(clip) - }) - - it('unclips all clipped elements when being removed', function() { - rect.clipper.remove() - expect(rect.attr('clip-path')).toBe(undefined) - }) - - describe('unclip()', function() { - - it('clears the "clip-path" attribute on the clipped element', function() { - rect.unclip() - expect(rect.attr('clip-path')).toBe(undefined) - }) - - it('removes the reference to the clipping element', function() { - rect.unclip() - expect(rect.clipper).toBe(undefined) - }) - - it('returns the clipPath element', function() { - expect(rect.unclip()).toBe(rect) - }) - - }) - -}) \ No newline at end of file diff --git a/spec/spec/color.js b/spec/spec/color.js deleted file mode 100644 index 1e8654463..000000000 --- a/spec/spec/color.js +++ /dev/null @@ -1,86 +0,0 @@ -describe('Color', function() { - var color - - beforeEach(function() { - color = new SVG.Color({ r: 0, g: 102, b: 255 }) - }) - - it('correclty parses a rgb string', function() { - color = new SVG.Color('rgb(255,0,128)') - expect(color.r).toBe(255) - expect(color.g).toBe(0) - expect(color.b).toBe(128) - }) - - it('correclty parses a 3 digit hex string', function() { - color = new SVG.Color('#f06') - expect(color.r).toBe(255) - expect(color.g).toBe(0) - expect(color.b).toBe(102) - }) - - it('correclty parses a 6 digit hex string', function() { - color = new SVG.Color('#0066ff') - expect(color.r).toBe(0) - expect(color.g).toBe(102) - expect(color.b).toBe(255) - }) - - describe('toHex()', function() { - it('returns a hex color', function() { - expect(color.toHex()).toBe('#0066ff') - }) - }) - - describe('toRgb()', function() { - it('returns a rgb string color', function() { - expect(color.toRgb()).toBe('rgb(0,102,255)') - }) - }) - - describe('brightness()', function() { - it('returns the percieved brightness value of a color', function() { - expect(color.brightness()).toBe(0.346) - }) - }) - - describe('morph()', function() { - it('prepares the color for morphing', function() { - var destination = new SVG.Color - color.morph(destination) - expect(color.destination).toEqual(destination) - }) - }) - - describe('at()', function() { - it('morphes color to a given position', function() { - var destination = new SVG.Color - var morphed = color.morph(destination).at(0.5) - expect(morphed.r).toBe(0) - expect(morphed.g).toBe(51) - expect(morphed.b).toBe(127) - }) - - it('morphes color to 1 with higher values', function() { - var destination = new SVG.Color('#fff') - var morphed = color.morph(destination).at(2) - expect(morphed.r).toBe(255) - expect(morphed.g).toBe(255) - expect(morphed.b).toBe(255) - }) - - it('morphes color to 0 with lower values', function() { - var destination = new SVG.Color('#fff') - var morphed = color.morph(destination).at(-3) - expect(morphed.r).toBe(0) - expect(morphed.g).toBe(102) - expect(morphed.b).toBe(255) - }) - - it('returns itself when no destination specified', function() { - expect(color.at(0.5)).toBe(color) - }) - }) - -}) - diff --git a/spec/spec/container.js b/spec/spec/container.js deleted file mode 100644 index b9fabe7df..000000000 --- a/spec/spec/container.js +++ /dev/null @@ -1,362 +0,0 @@ -describe('Container', function() { - - beforeEach(function() { - draw.clear() - }) - - describe('rect()', function() { - it('should increase children by 1', function() { - var initial = draw.children().length - draw.rect(100,100) - expect(draw.children().length).toBe(initial + 1) - }) - it('should create a rect', function() { - expect(draw.rect(100,100).type).toBe('rect') - }) - it('should create an instance of SVG.Rect', function() { - expect(draw.rect(100,100) instanceof SVG.Rect).toBe(true) - }) - it('should be an instance of SVG.Shape', function() { - expect(draw.rect(100,100) instanceof SVG.Shape).toBe(true) - }) - it('should be an instance of SVG.Element', function() { - expect(draw.rect(100,100) instanceof SVG.Element).toBe(true) - }) - }) - - describe('ellipse()', function() { - it('should increase children by 1', function() { - var initial = draw.children().length - draw.ellipse(100,100) - expect(draw.children().length).toBe(initial + 1) - }) - it('should create an ellipse', function() { - expect(draw.ellipse(100,100).type).toBe('ellipse') - }) - it('should create an instance of SVG.Ellipse', function() { - expect(draw.ellipse(100,100) instanceof SVG.Ellipse).toBe(true) - }) - it('should be an instance of SVG.Shape', function() { - expect(draw.ellipse(100,100) instanceof SVG.Shape).toBe(true) - }) - it('should be an instance of SVG.Element', function() { - expect(draw.ellipse(100,100) instanceof SVG.Element).toBe(true) - }) - }) - - describe('circle()', function() { - it('should increase children by 1', function() { - var initial = draw.children().length - draw.circle(100) - expect(draw.children().length).toBe(initial + 1) - }) - it('should create an circle', function() { - expect(draw.circle(100).type).toBe('circle') - }) - it('should create an instance of SVG.Circle', function() { - expect(draw.circle(100) instanceof SVG.Circle).toBe(true) - }) - it('should be an instance of SVG.Shape', function() { - expect(draw.circle(100) instanceof SVG.Shape).toBe(true) - }) - it('should be an instance of SVG.Element', function() { - expect(draw.circle(100) instanceof SVG.Element).toBe(true) - }) - }) - - describe('line()', function() { - it('should increase children by 1', function() { - var initial = draw.children().length - draw.line(0,100,100,0) - expect(draw.children().length).toBe(initial + 1) - }) - it('should create a line', function() { - expect(draw.line(0,100,100,0).type).toBe('line') - }) - it('should create an instance of SVG.Line', function() { - expect(draw.line(0,100,100,0) instanceof SVG.Line).toBe(true) - }) - it('should be an instance of SVG.Shape', function() { - expect(draw.line(0,100,100,0) instanceof SVG.Shape).toBe(true) - }) - it('should be an instance of SVG.Element', function() { - expect(draw.line(0,100,100,0) instanceof SVG.Element).toBe(true) - }) - }) - - describe('polyline()', function() { - it('should increase children by 1', function() { - var initial = draw.children().length - draw.polyline('0,0 100,0 100,100 0,100') - expect(draw.children().length).toBe(initial + 1) - }) - it('should create a polyline', function() { - expect(draw.polyline('0,0 100,0 100,100 0,100').type).toBe('polyline') - }) - it('should be an instance of SVG.Polyline', function() { - expect(draw.polyline('0,0 100,0 100,100 0,100') instanceof SVG.Polyline).toBe(true) - }) - it('should be an instance of SVG.Shape', function() { - expect(draw.polyline('0,0 100,0 100,100 0,100') instanceof SVG.Shape).toBe(true) - }) - it('should be an instance of SVG.Element', function() { - expect(draw.polyline('0,0 100,0 100,100 0,100') instanceof SVG.Element).toBe(true) - }) - }) - - describe('polygon()', function() { - it('should increase children by 1', function() { - var initial = draw.children().length - draw.polygon('0,0 100,0 100,100 0,100') - expect(draw.children().length).toBe(initial + 1) - }) - it('should create a polygon', function() { - expect(draw.polygon('0,0 100,0 100,100 0,100').type).toBe('polygon') - }) - it('should be an instance of SVG.Polygon', function() { - expect(draw.polygon('0,0 100,0 100,100 0,100') instanceof SVG.Polygon).toBe(true) - }) - it('should be an instance of SVG.Shape', function() { - expect(draw.polygon('0,0 100,0 100,100 0,100') instanceof SVG.Shape).toBe(true) - }) - it('should be an instance of SVG.Element', function() { - expect(draw.polygon('0,0 100,0 100,100 0,100') instanceof SVG.Element).toBe(true) - }) - }) - - describe('path()', function() { - it('should increase children by 1', function() { - var initial = draw.children().length - draw.path(svgPath) - expect(draw.children().length).toBe(initial + 1) - }) - it('should create a path', function() { - expect(draw.path(svgPath).type).toBe('path') - }) - it('should be an instance of SVG.Path', function() { - expect(draw.path(svgPath) instanceof SVG.Path).toBe(true) - }) - it('should be an instance of SVG.Shape', function() { - expect(draw.path(svgPath) instanceof SVG.Shape).toBe(true) - }) - it('should be an instance of SVG.Element', function() { - expect(draw.path(svgPath) instanceof SVG.Element).toBe(true) - }) - }) - - describe('image()', function() { - it('should increase children by 1', function() { - var initial = draw.children().length - draw.image(imageUrl, 100, 100) - expect(draw.children().length).toBe(initial + 1) - }) - it('should create a rect', function() { - expect(draw.image(imageUrl, 100, 100).type).toBe('image') - }) - it('should create an instance of SVG.Rect', function() { - expect(draw.image(imageUrl, 100, 100) instanceof SVG.Image).toBe(true) - }) - it('should be an instance of SVG.Shape', function() { - expect(draw.image(imageUrl, 100, 100) instanceof SVG.Shape).toBe(true) - }) - it('should be an instance of SVG.Element', function() { - expect(draw.image(imageUrl, 100, 100) instanceof SVG.Element).toBe(true) - }) - }) - - describe('text()', function() { - it('increases children by 1', function() { - var initial = draw.children().length - draw.text(loremIpsum) - expect(draw.children().length).toBe(initial + 1) - }) - it('creates a text element', function() { - expect(draw.text(loremIpsum).type).toBe('text') - }) - it('creates an instance of SVG.Rect', function() { - expect(draw.text(loremIpsum) instanceof SVG.Text).toBe(true) - }) - it('is an instance of SVG.Shape', function() { - expect(draw.text(loremIpsum) instanceof SVG.Shape).toBe(true) - }) - it('is an instance of SVG.Element', function() { - expect(draw.text(loremIpsum) instanceof SVG.Element).toBe(true) - }) - }) - - describe('plain()', function() { - it('increases children by 1', function() { - var initial = draw.children().length - draw.plain(loremIpsum) - expect(draw.children().length).toBe(initial + 1) - }) - it('creates a plain element', function() { - expect(draw.plain(loremIpsum).type).toBe('text') - }) - it('creates an instance of SVG.Rect', function() { - expect(draw.plain(loremIpsum) instanceof SVG.Text).toBe(true) - }) - it('is an instance of SVG.Shape', function() { - expect(draw.plain(loremIpsum) instanceof SVG.Shape).toBe(true) - }) - it('is an instance of SVG.Element', function() { - expect(draw.plain(loremIpsum) instanceof SVG.Element).toBe(true) - }) - }) - - describe('clear()', function() { - it('removes all children except the parser if present', function() { - draw.rect(100,100) - draw.clear() - expect(draw.children().length).toBe(parserInDoc) - }) - it('creates a new defs node', function() { - var oldDefs = draw.defs() - draw.rect(100,100).maskWith(draw.circle(100, 100)) - draw.clear() - expect(draw.defs()).not.toBe(oldDefs) - }) - it('clears all children in the defs node', function() { - draw.rect(100,100).maskWith(draw.circle(100, 100)) - draw.clear() - expect(draw.defs().children().length).toBe(0) - }) - }) - - describe('each()', function() { - it('should iterate over all children', function() { - var children = [] - - draw.rect(100,100) - draw.ellipse(100, 100) - draw.polygon() - - draw.each(function() { - children.push(this.type) - }) - expect(children).toEqual((parserInDoc ? [parser[0].type] : []).concat(['rect', 'ellipse', 'polygon'])) - }) - it('should only include the its own children', function() { - var children = [] - , group = draw.group() - - draw.rect(100,200) - draw.circle(300) - - group.rect(100,100) - group.ellipse(100, 100) - group.polygon() - - group.each(function() { - children.push(this) - }) - - expect(children).toEqual(group.children()) - }) - it('should traverse recursively when set to deep', function() { - var children = [] - , group = draw.group() - - draw.rect(100,200) - draw.circle(300) - - group.rect(100,100) - group.ellipse(100, 100) - group.polygon() - - draw.each(function() { - children.push(this) - }, true) - - expect(children.length).toEqual(draw.children().length + group.children().length + (parserInDoc ? parser[0].children().length : 0)) - }) - }) - - describe('get()', function() { - it('gets an element at a given index', function() { - draw.clear() - var rect = draw.rect(100,100) - var circle = draw.circle(100) - var line = draw.line(0,0,100,100) - expect(draw.get(0+parserInDoc)).toBe(rect) - expect(draw.get(1+parserInDoc)).toBe(circle) - expect(draw.get(2+parserInDoc)).toBe(line) - expect(draw.get(3+parserInDoc)).toBeNull() - }) - }) - - describe('first()', function() { - it('gets the first child', function() { - draw.clear() - var rect = draw.rect(100,100) - var circle = draw.circle(100) - var line = draw.line(0,0,100,100) - expect(draw.first()).toBe(parserInDoc ? parser[0] : rect) - }) - }) - - describe('last()', function() { - it('gets the last child', function() { - draw.clear() - var rect = draw.rect(100,100) - var circle = draw.circle(100) - var line = draw.line(0,0,100,100) - expect(draw.last()).toBe(line) - }) - }) - - describe('has()', function() { - it('determines if a given element is a child of the parent', function() { - var rect = draw.rect(100,100) - var circle = draw.circle(100) - var group = draw.group() - var line = group.line(0,0,100,100) - expect(draw.has(rect)).toBe(true) - expect(draw.has(circle)).toBe(true) - expect(draw.has(group)).toBe(true) - expect(draw.has(line)).toBe(false) - expect(group.has(line)).toBe(true) - }) - }) - - describe('index()', function() { - it('determines the index of given element', function() { - var rect = draw.rect(100,100) - var circle = draw.circle(100) - var group = draw.group() - var line = group.line(0,0,100,100) - expect(draw.index(rect)).toBe(0+parserInDoc) - expect(draw.index(circle)).toBe(1+parserInDoc) - expect(draw.index(group)).toBe(2+parserInDoc) - expect(draw.index(line)).toBe(-1) - expect(group.index(line)).toBe(0) - }) - }) - - describe('parent()', function() { - it('returns the parent element instance', function() { - var rect = draw.rect(100,100) - expect(rect.parent()).toBe(rect.node.parentNode.instance) - }) - }) - - describe('defs()', function() { - it('returns the defs from the svg', function() { - var g = draw.group() - expect(g.defs()).toBe(draw.doc().defs()) - expect(g.defs() instanceof SVG.Defs).toBeTruthy() - }) - }) - -}) - - - - - - - - - - - diff --git a/spec/spec/defs.js b/spec/spec/defs.js deleted file mode 100644 index 5e5da08ac..000000000 --- a/spec/spec/defs.js +++ /dev/null @@ -1,12 +0,0 @@ -describe('Defs', function() { - var defs - - beforeEach(function() { - defs = draw.defs() - }) - - it('creates an instance of SVG.Defs', function() { - expect(defs instanceof SVG.Defs).toBeTruthy() - }) - -}) \ No newline at end of file diff --git a/spec/spec/doc.js b/spec/spec/doc.js deleted file mode 100644 index 17bcb28d2..000000000 --- a/spec/spec/doc.js +++ /dev/null @@ -1,51 +0,0 @@ -describe('Doc', function() { - - describe('create()', function(){ - it('doenst alter size when adopting width SVG()', function() { - var svg = SVG('inlineSVG') - expect(svg.width()).toBe(0) - expect(svg.height()).toBe(0) - }) - }) - - it('is an instance of SVG.Container', function() { - expect(draw instanceof SVG.Container).toBe(true) - }) - - it('is an instance of SVG.Doc', function() { - expect(draw instanceof SVG.Doc).toBe(true) - }) - - it('returns itself as Doc', function() { - expect(draw.doc()).toBe(draw) - }) - - it('has a defs element', function() { - expect(draw.defs() instanceof SVG.Defs).toBe(true) - }) - - describe('defs()', function() { - it('returns defs element', function(){ - expect(draw.defs()).toBe(draw._defs) - }) - it('references parent node', function(){ - expect(draw.defs().parent()).toBe(draw) - }) - }) - - describe('remove()', function() { - it('removes the doc from the dom only if doc is not root element', function() { - var cnt = window.document.querySelectorAll('svg').length - draw.remove() - if(parserInDoc){ - expect(window.document.querySelectorAll('svg').length).toBe(cnt) - }else{ - expect(window.document.querySelectorAll('svg').length).toBe(cnt-1) - } - - draw = SVG(drawing).size(100,100); - expect(window.document.querySelectorAll('svg').length).toBe(cnt) - }) - }) - -}) diff --git a/spec/spec/easing.js b/spec/spec/easing.js deleted file mode 100644 index 04690ac14..000000000 --- a/spec/spec/easing.js +++ /dev/null @@ -1,22 +0,0 @@ -describe('SVG.easing', function() { - var easedValues = { - '-':0.5, - '<>':0.5, - '>':0.7071, - '<':0.2929, - } - - ;['-', '<>', '<', '>'].forEach(function(el) { - describe(el, function() { - it('is 0 at 0', function() { - expect(SVG.easing[el](0)).toBe(0) - }) - it('is 1 at 1', function() { - expect(Math.round(SVG.easing[el](1)*1000)/1000).toBe(1) // we need to round cause for some reason at some point 1==0.999999999 - }) - it('is eased at 0.5', function() { - expect(SVG.easing[el](0.5)).toBeCloseTo(easedValues[el]) - }) - }) - }) -}) diff --git a/spec/spec/element.js b/spec/spec/element.js deleted file mode 100644 index 28f30dd98..000000000 --- a/spec/spec/element.js +++ /dev/null @@ -1,980 +0,0 @@ -describe('Element', function() { - - beforeEach(function() { - draw.clear() - draw.attr('viewBox', null) - }) - - it('should create a circular reference on the node', function() { - var rect = draw.rect(100,100) - expect(rect.node.instance).toBe(rect) - }) - - describe('native()', function() { - it('returns the node reference', function() { - var rect = draw.rect(100,100) - expect(rect.native()).toBe(rect.node) - }) - }) - - describe('attr()', function() { - var rect - - beforeEach(function() { - rect = draw.rect(100,100) - }) - - afterEach(function() { - rect.remove() - draw.defs().select('pattern').each(function() { this.remove() }) - }) - - it('sets one attribute when two arguments are given', function() { - rect.attr('fill', '#ff0066') - expect(rect.node.getAttribute('fill')).toBe('#ff0066') - }) - it('sets various attributes when an object is given', function() { - rect.attr({ fill: '#00ff66', stroke: '#ff2233', 'stroke-width': 10 }) - expect(rect.node.getAttribute('fill')).toBe('#00ff66') - expect(rect.node.getAttribute('stroke')).toBe('#ff2233') - expect(rect.node.getAttribute('stroke-width')).toBe('10') - }) - it('gets the value of the string value given as first argument', function() { - rect.attr('fill', '#ff0066') - expect(rect.attr('fill')).toEqual('#ff0066') - }) - it('gets an object with all attributes without any arguments', function() { - rect.attr({ fill: '#00ff66', stroke: '#ff2233' }) - var attr = rect.attr() - expect(attr.fill).toBe('#00ff66') - expect(attr.stroke).toBe('#ff2233') - }) - it('removes an attribute if the second argument is explicitly set to null', function() { - rect.attr('stroke-width', 10) - expect(rect.node.getAttribute('stroke-width')).toBe('10') - rect.attr('stroke-width', null) - expect(rect.node.getAttribute('stroke-width')).toBe(null) - }) - it('correctly parses numeric values as a getter', function() { - rect.attr('stroke-width', 11) - expect(rect.node.getAttribute('stroke-width')).toBe('11') - expect(rect.attr('stroke-width')).toBe(11) - }) - it('correctly parses negative numeric values as a getter', function() { - rect.attr('x', -120) - expect(rect.node.getAttribute('x')).toBe('-120') - expect(rect.attr('x')).toBe(-120) - }) - it('falls back on default values if attribute is not present', function() { - expect(rect.attr('stroke-linejoin')).toBe('miter') - }) - it('gets the "style" attribute as a string', function() { - rect.style('cursor', 'pointer') - expect(rect.node.style.cursor).toBe('pointer') - }) - it('sets the style attribute correctly', function() { - rect.attr('style', 'cursor:move;') - expect(rect.node.style.cursor).toBe('move') - }) - it('acts as a global getter when no arguments are given', function() { - rect.fill('#ff0066') - expect(rect.attr().fill).toBe('#ff0066') - }) - it('correctly parses numeric values as a global getter', function() { - rect.stroke({ width: 20 }) - expect(rect.attr()['stroke-width']).toBe(20) - }) - it('correctly parses negative numeric values as a global getter', function() { - rect.x(-30) - expect(rect.attr().x).toBe(-30) - }) - it('leaves unit values alone as a global getter', function() { - rect.attr('x', '69%') - expect(rect.attr().x).toBe('69%') - }) - it('creates an image in defs when image path is specified for fill', function() { - rect.attr('fill', imageUrl) - expect(draw.defs().select('pattern').length()).toBe(1) - expect(draw.defs().select('pattern image').length()).toBe(1) - expect(draw.defs().select('pattern image').first().src).toBe(imageUrl) - }) - it('creates pattern in defs when image object is specified for fill', function() { - rect.attr('fill', new SVG.Image().load(imageUrl)) - expect(draw.defs().select('pattern').length()).toBe(1) - expect(draw.defs().select('pattern image').length()).toBe(1) - expect(draw.defs().select('pattern image').first().src).toBe(imageUrl) - }) - it('correctly creates SVG.Array if array given', function() { - rect.attr('something', [2,3,4]) - expect(rect.attr('something')).toBe('2 3 4') - }) - it('redirects to the leading() method when setting leading', function() { - var text = draw.text(loremIpsum) - spyOn(text, 'leading') - - text.attr('leading', 2) - expect(text.leading).toHaveBeenCalled() - text.remove() - }) - }) - - describe('id()', function() { - var rect - - beforeEach(function() { - rect = draw.rect(100,100) - }) - - it('gets the value if the id attribute without an argument', function() { - expect(rect.id()).toBe(rect.attr('id')) - }) - it('sets the value of the id', function() { - rect.id('new_id') - expect(rect.attr('id')).toBe('new_id') - }) - }) - - describe('style()', function() { - it('sets the style with key and value arguments', function() { - var rect = draw.rect(100,100).style('cursor', 'crosshair') - expect(window.stripped(rect.node.style.cssText)).toBe('cursor:crosshair') - }) - it('sets multiple styles with an object as the first argument', function() { - var rect = draw.rect(100,100).style({ cursor: 'help', display: 'block' }) - expect(window.stripped(rect.node.style.cssText)).toMatch(/cursor:help/) - expect(window.stripped(rect.node.style.cssText)).toMatch(/display:block/) - expect(window.stripped(rect.node.style.cssText).length).toBe(('display:block;cursor:help').length) - }) - it('sets multiple styles with a css string as the first argument', function() { - var rect = draw.rect(100,100).style('cursor: help; display: block;') - expect(window.stripped(rect.node.style.cssText)).toMatch(/cursor:help/) - expect(window.stripped(rect.node.style.cssText)).toMatch(/display:block/) - expect(window.stripped(rect.node.style.cssText).length).toBe(('display:block;cursor:help').length) - }) - it('gets a style with a string key as the fists argument', function() { - var rect = draw.rect(100,100).style({ cursor: 'progress', display: 'block' }) - expect(rect.style('cursor')).toBe('progress') - }) - it('gets the full css string with no argument', function() { - var rect = draw.rect(100,100).style({ cursor: 's-resize', display: 'none' }) - expect(window.stripped(rect.style())).toMatch(/display:none/) - expect(window.stripped(rect.style())).toMatch(/cursor:s-resize/) - expect(window.stripped(rect.style()).length).toBe(('cursor:s-resize;display:none').length) - }) - it('removes a style if the value is an empty string', function() { - var rect = draw.rect(100,100).style({ cursor: 'n-resize', display: '' }) - expect(window.stripped(rect.style())).toBe('cursor:n-resize') - }) - it('removes a style if the value explicitly set to null', function() { - var rect = draw.rect(100,100).style('cursor', 'w-resize') - expect(window.stripped(rect.style())).toBe('cursor:w-resize') - rect.style('cursor', null) - expect(rect.style()).toBe('') - }) - }) - - describe('transform()', function() { - var rect, ctm - - beforeEach(function() { - rect = draw.rect(100,100) - }) - - it('gets the current transformations', function() { - expect(rect.transform()).toEqual(new SVG.Matrix(rect).extract()) - }) - it('sets the translation of and element', function() { - rect.transform({ x: 10, y: 11 }) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([1,0,0,1,10,11]) - }) - it('performs an absolute translation', function() { - rect.transform({ x: 10, y: 11 }).transform({ x: 20, y: 21 }) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([1,0,0,1,20,21]) - }) - it('performs a relative translation when relative is set to true', function() { - rect.transform({ x: 10, y: 11 }).transform({ x: 20, y: 21, relative: true }) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([1,0,0,1,30,32]) - }) - it('performs a relative translation with relative flag', function() { - rect.transform({ x: 10, y: 11 }).transform({ x: 20, y: 21 }, true) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([1,0,0,1,30,32]) - }) - it('sets the scaleX and scaleY of an element', function() { - rect.transform({ scaleX: 0.5, scaleY: 2 }) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([0.5,0,0,2,25,-50]) - }) - it('performs a uniform scale with scale given', function() { - rect.transform({ scale: 3 }) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([3,0,0,3,-100,-100]) - }) - it('also works with only skaleX', function() { - rect.transform({ scaleX: 3 }) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([3,0,0,1,-100,0]) - }) - it('also works with only skaleY', function() { - rect.transform({ scaleY: 3 }) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([1,0,0,3,0,-100]) - }) - - it('performs an absolute scale by default', function() { - rect.transform({ scale: 3 }).transform({ scale: 0.5 }) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([0.5,0,0,0.5,25,25]) - }) - it('performs a relative scale with a relative flag', function() { - rect.transform({ scaleX: 0.5, scaleY: 2 }).transform({ scaleX: 3, scaleY: 4 }, true) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([1.5,0,0,8,-25,-350]) - }) - it('sets the skewX of an element with center on the element', function() { - ctm = rect.transform({ skewX: 10 }).ctm() - expect(ctm.a).toBe(1) - expect(ctm.b).toBe(0) - expect(ctm.c).toBeCloseTo(0.17632698070846498) - expect(ctm.d).toBe(1) - expect(ctm.e).toBeCloseTo(-8.81634903542325) - expect(ctm.f).toBe(0) - }) - it('sets the skewX of an element with given center', function() { - ctm = rect.transform({ skewX: 10, cx: 0, cy: 0 }).ctm() - expect(ctm.a).toBe(1) - expect(ctm.b).toBe(0) - expect(ctm.c).toBeCloseTo(0.17632698070846498) - expect(ctm.d).toBe(1) - expect(ctm.e).toBe(0) - expect(ctm.f).toBe(0) - }) - it('sets the skewY of an element', function() { - ctm = rect.transform({ skewY: -10, cx: 0, cy: 0 }).ctm() - expect(ctm.a).toBe(1) - expect(ctm.b).toBeCloseTo(-0.17632698070846498) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(1) - expect(ctm.e).toBe(0) - expect(ctm.f).toBe(0) - }) - it('sets the skewX and skewY of an element', function() { - ctm = rect.transform({ skewX: 10, skewY: -10, cx: 0, cy: 0 }).ctm() - expect(ctm.a).toBe(1) - expect(ctm.b).toBeCloseTo(-0.17632698070846498) - expect(ctm.c).toBeCloseTo(0.17632698070846498) - expect(ctm.d).toBe(1) - expect(ctm.e).toBe(0) - expect(ctm.f).toBe(0) - }) - it('performs a uniform skew with skew given', function() { - ctm = rect.transform({ skew: 5, cx: 0, cy: 0 }).ctm() - expect(ctm.a).toBe(1) - expect(ctm.b).toBeCloseTo(0.08748866352592401) - expect(ctm.c).toBeCloseTo(0.08748866352592401) - expect(ctm.d).toBe(1) - expect(ctm.e).toBe(0) - expect(ctm.f).toBe(0) - }) - it('rotates the element around its centre if no rotation point is given', function() { - ctm = rect.center(100, 100).transform({ rotation: 45 }).ctm() - expect(ctm.a).toBeCloseTo(0.7071068286895752) - expect(ctm.b).toBeCloseTo(0.7071068286895752) - expect(ctm.c).toBeCloseTo(-0.7071068286895752) - expect(ctm.d).toBeCloseTo(0.7071068286895752) - expect(ctm.e).toBeCloseTo(100) - expect(ctm.f).toBeCloseTo(-41.421356201171875) - expect(rect.transform('rotation')).toBe(45) - }) - it('rotates the element around the given rotation point', function() { - ctm = rect.transform({ rotation: 55, cx: 80, cy:2 }).ctm() - expect(ctm.a).toBeCloseTo(0.5735765099525452) - expect(ctm.b).toBeCloseTo(0.8191521167755127) - expect(ctm.c).toBeCloseTo(-0.8191521167755127) - expect(ctm.d).toBeCloseTo(0.5735765099525452) - expect(ctm.e).toBeCloseTo(35.75218963623047) - expect(ctm.f).toBeCloseTo(-64.67931365966797) - }) - it('transforms element using a matrix', function() { - rect.transform({ a: 0.5, c: 0.5 }) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([0.5,0,0.5,1,0,0]) - }) - it('transforms relative using a matrix', function() { - rect.transform({ a: 0.5, c: 0.5 }).transform(new SVG.Matrix({ e: 20, f: 20 }), true) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([0.5,0,0.5,1,20,20]) - }) - it('flips the element on x axis', function() { - rect.transform({ flip: 'x' }) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([-1,0,0,1,100,0]) - }) - it('flips the element on x axis with offset', function() { - rect.transform({ flip: 'x', offset: 20 }) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([-1,0,0,1,40,0]) - }) - it('flips the element on y axis with offset', function() { - rect.transform({ flip: 'y', offset: 20 }) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([1,0,0,-1,0,40]) - }) - it('flips the element on both axis with offset', function() { - rect.transform({ flip: 'both', offset: 20 }) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([-1,0,0,-1,40,40]) - }) - it('flips the element on both axis', function() { - rect.transform({ flip: 'both' }) - expect(window.matrixStringToArray(rect.node.getAttribute('transform'))).toEqual([-1,0,0,-1,100,100]) - }) - }) - - describe('untransform()', function() { - var circle - - beforeEach(function() { - circle = draw.circle(100).translate(50, 100) - }) - - it('removes the transform attribute', function() { - expect(window.matrixStringToArray(circle.node.getAttribute('transform'))).toEqual([1,0,0,1,50,100]) - circle.untransform() - expect(circle.node.getAttribute('transform')).toBeNull() - }) - it('resets the current transform matix', function() { - expect(circle.ctm()).toEqual(new SVG.Matrix(1,0,0,1,50,100)) - circle.untransform() - expect(circle.ctm()).toEqual(new SVG.Matrix) - }) - }) - - describe('matrixify', function() { - var rect - - beforeEach(function() { - rect = draw.rect(100, 100) - }) - - it('allow individual transform definitions to be separated by whitespace', function(){ - // One space - rect.attr('transform', 'translate(20) translate(20)') - expect(rect.matrixify().toString()).toBe('matrix(1,0,0,1,40,0)') - - // More than one space - rect.attr('transform', 'translate(20) translate(-60)') - expect(rect.matrixify().toString()).toBe('matrix(1,0,0,1,-40,0)') - }) - - it('allow individual transform definitions to be separated by a comma', function(){ - rect.attr('transform', 'translate(20,16),translate(20)') - expect(rect.matrixify().toString()).toBe('matrix(1,0,0,1,40,16)') - }) - - it('allow individual transform definitions to be separated by whitespace and a comma', function(){ - // Spaces before the comma - rect.attr('transform', 'translate(20,16) ,translate(20)') - expect(rect.matrixify().toString()).toBe('matrix(1,0,0,1,40,16)') - - // Spaces after the comma - rect.attr('transform', 'translate(12), translate(10,14)') - expect(rect.matrixify().toString()).toBe('matrix(1,0,0,1,22,14)') - - // Spaces before and after the comma - rect.attr('transform', 'translate(24,14) , translate(36,6)') - expect(rect.matrixify().toString()).toBe('matrix(1,0,0,1,60,20)') - }) - }) - - describe('toParent()', function() { - var nested, g1, g2, rect1 - - beforeEach(function() { - nested = draw.nested() - g1 = nested.group().translate(20, 20) - g2 = g1.group().translate(100, 100) - rect1 = g2.rect(100,100).scale(2) - rect2 = nested.rect(100,100).scale(0.5) - }) - - afterEach(function() { - draw.clear() - }) - - it('returns itself when given parent and it is the same', function() { - expect(g2.toParent(g2)).toBe(g2) - }) - - it('moves the element to other parent while maintaining the same visal representation', function() { - expect(rect1.toParent(nested).transform()).toEqual(jasmine.objectContaining({ - a:2, b:0, c:0, d:2, e:70, f:70 - })) - expect(rect1.parent()).toEqual(nested) - expect(rect2.toParent(g2).transform()).toEqual(jasmine.objectContaining({ - a:0.5, b:0, c:0, d:0.5, e:-95, f:-95 - })) - expect(rect2.parent()).toEqual(g2) - }) - }) - - describe('toDoc()', function() { - var nested, g1, g2, rect - - beforeEach(function() { - rect = draw.rect(100,100) - spyOn(rect, 'toParent') - }) - - afterEach(function() { - draw.clear() - }) - - it('redirects to toParent(doc)', function() { - rect.toDoc() - expect(rect.toParent).toHaveBeenCalledWith(rect.doc()) - }) - }) - - describe('ungroup()', function() { - var nested, g1, g2, rect1 - - beforeEach(function() { - draw.defs() - nested = draw.nested() - g1 = nested.group().translate(20, 20) - g2 = g1.group().translate(100, 100) - rect1 = g2.rect(100,100).scale(2) - rect2 = g1.rect(100,100).scale(0.5) - }) - - afterEach(function() { - draw.clear() - }) - - it('returns itself when depths is 0 or this is SVG.Defs', function() { - expect(draw.defs().ungroup()).toBe(draw.defs()) - expect(g1.ungroup(null, 0)).toBe(g1) - }) - - it('breaks up all container and move the elements to the parent', function() { - g1.ungroup() - expect(rect1.parent()).toBe(nested) - expect(rect2.parent()).toBe(nested) - - expect(g1.node.parentNode).toBeFalsy() - expect(g2.node.parentNode).toBeFalsy() - - expect(rect1.transform()).toEqual(jasmine.objectContaining({ - a:2, b:0, c:0, d:2, e:70, f:70 - })) - expect(rect2.transform()).toEqual(jasmine.objectContaining({ - a:0.5, b:0, c:0, d:0.5, e:45, f:45 - })) - }) - - it('ungroups everything to the doc root when called on SVG.Doc / does not ungroup defs/parser', function() { - draw.ungroup() - expect(rect1.parent()).toBe(draw) - expect(rect2.parent()).toBe(draw) - - expect(g1.node.parentNode).toBeFalsy() - expect(g1.node.parentNode).toBeFalsy() - expect(nested.node.parentNode).toBeFalsy() - - expect(rect1.transform()).toEqual(jasmine.objectContaining({ - a:2, b:0, c:0, d:2, e:70, f:70 - })) - expect(rect2.transform()).toEqual(jasmine.objectContaining({ - a:0.5, b:0, c:0, d:0.5, e:45, f:45 - })) - - expect(draw.children().length).toBe(3+parserInDoc) // 2 * rect + defs - }) - }) - - describe('flatten()', function() { - it('redirects the call to ungroup()', function() { - spyOn(draw, 'ungroup') - draw.flatten() - expect(draw.ungroup).toHaveBeenCalled() - }) - }) - - describe('ctm()', function() { - var rect - - beforeEach(function() { - rect = draw.rect(100, 100) - }) - - it('gets the current transform matrix of the element', function() { - rect.translate(10, 20) - expect(rect.ctm().toString()).toBe('matrix(1,0,0,1,10,20)') - }) - it('returns an instance of SVG.Matrix', function() { - expect(rect.ctm() instanceof SVG.Matrix).toBeTruthy() - }) - }) - - describe('data()', function() { - it('sets a data attribute and convert value to json', function() { - var rect = draw.rect(100,100).data('test', 'value') - expect(rect.node.getAttribute('data-test')).toBe('value') - }) - it('sets a data attribute and not convert value to json if flagged raw', function() { - var rect = draw.rect(100,100).data('test', 'value', true) - expect(rect.node.getAttribute('data-test')).toBe('value') - }) - it('sets multiple data attributes and convert values to json when an object is passed', function() { - var rect = draw.rect(100,100).data({ - forbidden: 'fruit' - , multiple: { - values: 'in' - , an: 'object' - } - }) - expect(rect.node.getAttribute('data-forbidden')).toBe('fruit') - expect(rect.node.getAttribute('data-multiple')).toEqual('{"values":"in","an":"object"}') - }) - it('gets data value if only one argument is passed', function() { - var rect = draw.rect(100,100).data('test', 101) - expect(rect.data('test')).toBe(101) - }) - it('gets the raw value when value is no valid json', function() { - var rect = draw.rect(100,100).data('test', '{["sd":12}]', true) - expect(rect.data('test')).toBe('{["sd":12}]') - }) - it('removes data when null given', function() { - var rect = draw.rect(100,100).data('test', '{"sd":12}', true) - expect(rect.data('test', null).attr('data-test')).toBeFalsy() - }) - it('maintains data type for a number', function() { - var rect = draw.rect(100,100).data('test', 101) - expect(typeof rect.data('test')).toBe('number') - }) - it('maintains data type for an object', function() { - var rect = draw.rect(100,100).data('test', { string: 'value', array: [1,2,3] }) - expect(typeof rect.data('test')).toBe('object') - expect(Array.isArray(rect.data('test').array)).toBe(true) - }) - }) - - describe('remove()', function() { - it('removes an element and return it', function() { - var rect = draw.rect(100,100) - expect(rect.remove()).toBe(rect) - }) - it('removes an element from its parent', function() { - var rect = draw.rect(100,100) - rect.remove() - expect(draw.has(rect)).toBe(false) - }) - }) - - describe('addTo()', function() { - it('adds an element to a given parent and returns itself', function() { - var rect = draw.rect(100,100) - , group = draw.group() - - expect(rect.addTo(group)).toBe(rect) - expect(rect.parent()).toBe(group) - }) - }) - - describe('putIn()', function() { - it('adds an element to a given parent and returns parent', function() { - var rect = draw.rect(100,100) - , group = draw.group() - - expect(rect.putIn(group)).toBe(group) - expect(rect.parent()).toBe(group) - }) - }) - - describe('rbox()', function() { - it('returns an instance of SVG.RBox', function() { - var rect = draw.rect(100,100) - expect(rect.rbox() instanceof SVG.RBox).toBe(true) - }) - it('returns the correct rectangular box', function() { - // stroke has to be set in order to get the correct result when calling getBoundingClientRect in IE11 - var rect = draw.size(200, 150).viewbox(0, 0, 200, 150).rect(105, 210).move(2, 12).stroke({width:0}) - var box = rect.rbox(draw) - expect(box.x).toBeCloseTo(2) - expect(box.y).toBeCloseTo(12) - expect(box.cx).toBeCloseTo(54.5) - expect(box.cy).toBeCloseTo(117) - expect(box.width).toBeCloseTo(105) - expect(box.height).toBeCloseTo(210) - }) - }) - - describe('doc()', function() { - it('returns the parent document', function() { - var rect = draw.rect(100,100) - expect(rect.doc()).toBe(draw) - }) - }) - - describe('parent()', function() { - it('contains the parent svg', function() { - var rect = draw.rect(100,100) - expect(rect.parent()).toBe(draw) - }) - it('contains the parent group when in a group', function() { - var group = draw.group() - , rect = group.rect(100,100) - expect(rect.parent()).toBe(group) - }) - it('contains the parent which matches type', function() { - var group = draw.group() - , rect = group.rect(100,100) - expect(rect.parent(SVG.Doc)).toBe(draw) - }) - it('contains the parent which matches selector', function() { - var group1 = draw.group().addClass('test') - , group2 = group1.group() - , rect = group2.rect(100,100) - expect(rect.parent('.test')).toBe(group1) - }) - }) - - describe('parents()', function() { - it('returns array of parent up to but not including the dom element filtered by type', function() { - var group1 = draw.group().addClass('test') - , group2 = group1.group() - , rect = group2.rect(100,100) - - expect(rect.parents('.test')[0]).toBe(group1) - expect(rect.parents(SVG.G)[0]).toBe(group2) - expect(rect.parents(SVG.G)[1]).toBe(group1) - expect(rect.parents().length).toBe(3) - }) - }) - - describe('clone()', function() { - var rect, group, circle - - beforeEach(function() { - rect = draw.rect(100,100).center(321,567).fill('#f06') - group = draw.group().add(rect) - circle = group.circle(100) - }) - - it('makes an exact copy of the element', function() { - clone = rect.clone() - expect(clone.attr('id', null).attr()).toEqual(rect.attr('id', null).attr()) - }) - it('assigns a new id to the cloned element', function() { - clone = rect.clone() - expect(clone.attr('id')).not.toBe(rect.attr('id')) - }) - it('copies all child nodes as well', function() { - clone = group.clone() - expect(clone.children().length).toBe(group.children().length) - }) - it('assigns a new id to cloned child elements', function() { - clone = group.clone() - expect(clone.attr('id')).not.toEqual(group.attr('id')) - expect(clone.get(0).attr('id')).not.toBe(group.get(0).attr('id')) - expect(clone.get(1).attr('id')).not.toBe(group.get(1).attr('id')) - }) - it('inserts the clone after the cloned element', function() { - clone = rect.clone() - expect(rect.next()).toBe(clone) - }) - it('inserts the clone in the specified parent', function() { - var g = draw.group() - clone = rect.clone(g) - expect(g.get(0)).toBe(clone) - }) - it('deep copies over dom data', function() { - group.dom = {'foo':'bar'} - rect.dom = {'foo':'baz'} - clone = group.clone() - expect(clone.dom.foo).toBe('bar') - expect(clone.get(0).dom.foo).toBe('baz') - }) - }) - - describe('toString()', function() { - it('returns the element id', function() { - var rect = draw.rect(100,100).center(321,567).fill('#f06') - expect(rect + '').toBe(rect.attr('id')) - }) - }) - - describe('replace()', function() { - it('replaces the original element by another given element', function() { - var rect = draw.rect(100,100).center(321,567).fill('#f06') - var circle = draw.circle(200) - var rectIndex = draw.children().indexOf(rect) - - rect.replace(circle) - - expect(rectIndex).toBe(draw.children().indexOf(circle)) - }) - it('removes the original element', function() { - var rect = draw.rect(100,100).center(321,567).fill('#f06') - - rect.replace(draw.circle(200)) - - expect(draw.has(rect)).toBe(false) - }) - it('returns the new element', function() { - var circle = draw.circle(200) - var element = draw.rect(100,100).center(321,567).fill('#f06').replace(circle) - - expect(element).toBe(circle) - }) - }) - - describe('classes()', function() { - it('returns an array of classes on the node', function() { - var element = draw.rect(100,100) - element.node.setAttribute('class', 'one two') - expect(element.classes()).toEqual(['one', 'two']) - }) - }) - - describe('hasClass()', function() { - it('returns true if the node has the class', function() { - var element = draw.rect(100,100) - element.node.setAttribute('class', 'one') - expect(element.hasClass('one')).toBeTruthy() - }) - - it('returns false if the node does not have the class', function() { - var element = draw.rect(100,100) - element.node.setAttribute('class', 'one') - expect(element.hasClass('two')).toBeFalsy() - }) - }) - - describe('addClass()', function() { - it('adds the class to the node', function() { - var element = draw.rect(100,100) - element.addClass('one') - expect(element.hasClass('one')).toBeTruthy() - }) - - it('does not add duplicate classes', function() { - var element = draw.rect(100,100) - element.addClass('one') - element.addClass('one') - expect(element.node.getAttribute('class')).toEqual('one') - }) - - it('returns the svg instance', function() { - var element = draw.rect(100,100) - expect(element.addClass('one')).toEqual(element) - }) - }) - - describe('removeClass()', function() { - it('removes the class from the node when the class exists', function() { - var element = draw.rect(100,100) - element.addClass('one') - element.removeClass('one') - expect(element.hasClass('one')).toBeFalsy() - }) - - it('does nothing when the class does not exist', function() { - var element = draw.rect(100,100) - element.removeClass('one') - expect(element.hasClass('one')).toBeFalsy() - }) - - it('returns the element', function() { - var element = draw.rect(100,100) - expect(element.removeClass('one')).toEqual(element) - }) - }) - - describe('toggleClass()', function() { - it('adds the class when it does not already exist', function(){ - var element = draw.rect(100,100) - element.toggleClass('one') - expect(element.hasClass('one')).toBeTruthy() - }) - it('removes the class when it already exists', function(){ - var element = draw.rect(100,100) - element.addClass('one') - element.toggleClass('one') - expect(element.hasClass('one')).toBeFalsy() - }) - it('returns the svg instance', function() { - var element = draw.rect(100,100) - expect(element.toggleClass('one')).toEqual(element) - }) - }) - - describe('reference()', function() { - it('gets a referenced element from a given attribute', function() { - var rect = draw.defs().rect(100, 100) - , use = draw.use(rect) - , mark = draw.marker(10, 10) - , path = draw.path(svgPath).marker('end', mark) - - expect(use.reference('href')).toBe(rect) - expect(path.reference('marker-end')).toBe(mark) - }) - }) - - describe('svg()', function() { - describe('without an argument', function() { - it('returns full raw svg when called on the main svg doc', function() { - draw.size(100,100).rect(100,100).id(null) - draw.circle(100).fill('#f06').id(null) - - var toBeTested = draw.svg() - - // Test for different browsers namely Firefox and Chrome - expect( - // IE - toBeTested === '' - - // Firefox - || toBeTested === '' - - // svgdom - || toBeTested === '' - ).toBeTruthy() - - }) - it('returns partial raw svg when called on a sub group', function() { - var group = draw.group().id(null) - group.rect(100,100).id(null) - group.circle(100).fill('#f06').id(null) - - var toBeTested = group.svg() - - expect( - toBeTested === '' - || toBeTested === '' - || toBeTested === '' - ).toBeTruthy() - }) - it('returns a single element when called on an element', function() { - var group = draw.group().id(null) - group.rect(100,100).id(null) - var circle = group.circle(100).fill('#f06').id(null) - var toBeTested = circle.svg() - - expect( - toBeTested === '' - || toBeTested === '' - || toBeTested === '' - ).toBeTruthy() - }) - }) - describe('with raw svg given', function() { - it('imports a full svg document', function() { - draw.svg('') - - expect(draw.get(0+parserInDoc).type).toBe('svg') - expect(draw.get(0+parserInDoc).children().length).toBe(2) - expect(draw.get(0+parserInDoc).get(0).type).toBe('rect') - expect(draw.get(0+parserInDoc).get(1).type).toBe('circle') - expect(draw.get(0+parserInDoc).get(1).attr('fill')).toBe('#ff0066') - }) - it('imports partial svg content', function() { - draw.svg('') - expect(draw.get(0+parserInDoc).type).toBe('g') - expect(draw.get(0+parserInDoc).get(0).type).toBe('rect') - expect(draw.get(0+parserInDoc).get(1).type).toBe('circle') - expect(draw.get(0+parserInDoc).get(1).attr('fill')).toBe('#ff0066') - }) - it('does not import on single elements, even with an argument it acts as a getter', function() { - var rect = draw.rect(100,100).id(null) - , result = rect.svg('') - - expect( - result === '' - || result === '' - || result === '' - ).toBeTruthy() - }) - }) - }) - - describe('writeDataToDom()', function() { - it('set all properties in el.dom to the svgjs:data attribute', function(){ - var rect = draw.rect(100,100) - rect.dom.foo = 'bar' - rect.dom.number = new SVG.Number('3px') - - rect.writeDataToDom() - - expect(rect.attr('svgjs:data')).toBe('{"foo":"bar","number":"3px"}') - }) - it('recursively dumps the data', function() { - var g = draw.group() - rect = g.rect(100,100) - g.dom.foo = 'bar' - rect.dom.number = new SVG.Number('3px') - - g.writeDataToDom() - - expect(g.attr('svgjs:data')).toBe('{"foo":"bar"}') - expect(rect.attr('svgjs:data')).toBe('{"number":"3px"}') - }) - it('uses lines() instead of each() when dealing with text', function() { - var text = draw.text('Hello\nWorld') - text.writeDataToDom() - expect(text.attr('svgjs:data')).toBe('{"leading":"1.3"}') - expect(text.lines().first().attr('svgjs:data')).toBe('{"newLined":true}') - }) - }) - - describe('setData()', function() { - it('read all data from the svgjs:data attribute and assign it to el.dom', function(){ - var rect = draw.rect(100,100) - - rect.attr('svgjs:data', '{"foo":"bar","number":"3px"}') - rect.setData(JSON.parse(rect.attr('svgjs:data'))) - - expect(rect.dom.foo).toBe('bar') - expect(rect.dom.number).toBe('3px') - }) - }) - - describe('point()', function() { - it('creates a point from screen coordinates transformed in the elements space', function(){ - var rect = draw.rect(100,100) - - var m = draw.node.getScreenCTM() - // alert([m.a, m.a, m.c, m.d, m.e, m.f].join(', ')) - - var translation = {x: m.e, y: m.f} - var pos = {x: 2, y:5} - - expect(rect.point(pos.x, pos.y).x).toBeCloseTo(pos.x - translation.x) - expect(rect.point(pos.x, pos.y).y).toBeCloseTo(pos.y - translation.y) - }) - }) - - describe('inside()', function() { - it('checks whether the given point inside the bounding box of the element', function() { - var rect = draw.rect(100,100) - expect(rect.inside(50,50)).toBeTruthy() - expect(rect.inside(150,150)).toBeFalsy() - }) - }) - describe('show()', function() { - it('sets display property to ""', function() { - var rect = draw.rect(100,100).show() - expect(rect.style('display')).toBe('') - }) - }) - describe('hide()', function() { - it('sets display property to none', function() { - var rect = draw.rect(100,100).hide() - expect(rect.style('display')).toBe('none') - }) - }) - describe('visible()', function() { - it('checks if element is hidden or not', function() { - var rect = draw.rect(100,100).hide() - expect(rect.visible()).toBeFalsy() - rect.show() - expect(rect.visible()).toBeTruthy() - }) - }) - describe('is()', function() { - it('checks if element is instance of a certain kind', function() { - var rect = draw.rect(100,100) - expect(rect.is(SVG.Rect)).toBeTruthy() - expect(rect.is(SVG.Element)).toBeTruthy() - expect(rect.is(SVG.Parent)).toBeFalsy() - }) - }) -}) diff --git a/spec/spec/elements/A.js b/spec/spec/elements/A.js new file mode 100644 index 000000000..5fcbe87d2 --- /dev/null +++ b/spec/spec/elements/A.js @@ -0,0 +1,118 @@ +/* globals describe, expect, it, jasmine */ + +import { A, G, Rect } from '../../../src/main.js' + +const { any } = jasmine + +const url = 'https://svgjs.dev' +describe('A.js', () => { + describe('()', () => { + it('creates a new object of type A', () => { + expect(new A()).toEqual(any(A)) + }) + + it('sets passed attributes on the element', () => { + expect(new A({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('to()', () => { + it('creates xlink:href attribute', () => { + const link = new A() + link.to(url) + expect(link.attr('href')).toBe(url) + }) + }) + + describe('target()', () => { + it('creates target attribute', () => { + const link = new A() + link.target('_blank') + expect(link.attr('target')).toBe('_blank') + }) + }) + + describe('Container', () => { + describe('link()', () => { + it('creates a link with given url', () => { + const group = new G() + const link = group.link(url) + expect(link.attr('href')).toBe(url) + expect(link).toEqual(any(A)) + }) + }) + }) + + describe('Element', () => { + describe('linker()', () => { + it('returns the instance of the link of a linked element', () => { + const link = new A().to(url) + const rect = link.rect(100, 100) + + expect(rect.linker()).toBe(link) + }) + + it('returns null if no link is found', () => { + const group = new G() + const rect = group.rect(100, 100) + + expect(rect.linker()).toBe(null) + }) + + it('returns null when el is not in dom at all', () => { + const group = new G() + expect(group.linker()).toBe(null) + }) + }) + + describe('unlink()', () => { + it('returns itself', () => { + const group = new G() + expect(group.unlink()).toBe(group) + }) + + it('removes the link', () => { + const group = new G() + const link = group.link(url) + const rect = link.rect(100, 100) + + expect(rect.unlink().parent()).toBe(group) + expect(link.parent()).toBe(null) + }) + + it("removes also the link when link wasn't in document", () => { + const link = new A().to(url) + const rect = link.rect(100, 100) + + expect(rect.unlink().parent()).toBe(null) + expect(link.parent()).toBe(null) + }) + }) + + describe('linkTo()', () => { + it('wraps the called element in a link with given url', () => { + const rect = new Rect() + rect.linkTo(url) + expect(rect.linker()).toEqual(any(A)) + expect(rect.linker().attr('href')).toBe(url) + }) + + it('wraps the called element in a link with given block', () => { + const rect = new Rect() + rect.linkTo(function (link) { + link.to(url).target('_blank') + }) + expect(rect.linker().attr('href')).toBe(url) + expect(rect.linker().attr('target')).toBe('_blank') + }) + + it('reuses existing link if possible', () => { + const rect = new Rect() + rect.linkTo(url) + const link = rect.linker() + rect.linkTo(url + '/something') + expect(rect.linker()).toBe(link) + }) + }) + }) +}) diff --git a/spec/spec/elements/Circle.js b/spec/spec/elements/Circle.js new file mode 100644 index 000000000..0b339f190 --- /dev/null +++ b/spec/spec/elements/Circle.js @@ -0,0 +1,73 @@ +/* globals describe, expect, it, beforeEach, spyOn, jasmine */ + +import { Circle, G } from '../../../src/main.js' + +const { any, objectContaining } = jasmine + +describe('Circle.js', () => { + let circle + + beforeEach(() => { + circle = new Circle() + }) + + describe('()', () => { + it('creates a new object of type Circle', () => { + expect(new Circle()).toEqual(any(Circle)) + }) + + it('sets passed attributes on the element', () => { + expect(new Circle({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('radius()', () => { + it('calls attr with r', () => { + const spy = spyOn(circle, 'attr').and.callThrough() + circle.radius(123) + expect(spy).toHaveBeenCalledWith('r', 123) + }) + }) + + describe('rx()', () => { + it('calls attr with r', () => { + const spy = spyOn(circle, 'attr') + circle.rx(123) + expect(spy).toHaveBeenCalledWith('r', 123) + }) + }) + + describe('ry()', () => { + it('calls rx', () => { + const spy = spyOn(circle, 'rx') + circle.ry(123) + expect(spy).toHaveBeenCalledWith(123) + }) + }) + + describe('size()', () => { + it('calls radius with half of the size', () => { + const spy = spyOn(circle, 'radius') + circle.size(100) + expect(spy).toHaveBeenCalledWith(objectContaining({ value: 50 })) + }) + }) + + describe('Container', () => { + describe('circle()', () => { + it('creates a circle with given size', () => { + const group = new G() + const circle = group.circle(50) + expect(circle.attr('r')).toBe(25) + expect(circle).toEqual(any(Circle)) + }) + + it('defaults to zero size', () => { + const group = new G() + const circle = group.circle() + expect(circle.attr('r')).toBe(0) + expect(circle).toEqual(any(Circle)) + }) + }) + }) +}) diff --git a/spec/spec/elements/ClipPath.js b/spec/spec/elements/ClipPath.js new file mode 100644 index 000000000..5061a1001 --- /dev/null +++ b/spec/spec/elements/ClipPath.js @@ -0,0 +1,94 @@ +/* globals describe, expect, it, spyOn, jasmine, container */ + +import { ClipPath, SVG, Container, Rect } from '../../../src/main.js' + +const { any } = jasmine + +describe('ClipPath.js', () => { + describe('()', () => { + it('creates a new object of type ClipPath', () => { + expect(new ClipPath()).toEqual(any(ClipPath)) + }) + + it('sets passed attributes on the element', () => { + expect(new ClipPath({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('remove()', () => { + it('unclips all targets', () => { + const canvas = SVG().addTo(container) + const clip = canvas.clip() + const rect = canvas.rect(100, 100).clipWith(clip) + expect(clip.remove()).toBe(clip) + expect(rect.clipper()).toBe(null) + }) + + it('calls remove on parent class', () => { + const clip = new ClipPath() + const spy = spyOn(Container.prototype, 'remove') + clip.remove() + expect(spy).toHaveBeenCalled() + }) + }) + + describe('targets()', () => { + it('gets all targets of this clipPath', () => { + const canvas = SVG().addTo(container) + const clip = canvas.clip() + const rect = canvas.rect(100, 100).clipWith(clip) + expect(clip.targets()).toEqual([rect]) + }) + }) + + describe('Container', () => { + describe('clip()', () => { + it('creates a clipPath in the defs', () => { + const canvas = SVG() + const clip = canvas.clip() + expect(clip).toEqual(any(ClipPath)) + expect(canvas.defs().children()).toEqual([clip]) + }) + }) + }) + + describe('Element', () => { + describe('clipper()', () => { + it('returns the instance of ClipPath the current element is clipped with', () => { + const canvas = SVG().addTo(container) + const clip = canvas.clip() + const rect = canvas.rect(100, 100).clipWith(clip) + expect(rect.clipper()).toEqual(clip) + }) + + it('returns null if no clipPath was found', () => { + expect(new Rect().clipper()).toBe(null) + }) + }) + + describe('clipWith()', () => { + it('sets the clip-path attribute on the element to the id of the clipPath', () => { + const clip = new ClipPath().id('foo') + const rect = new Rect().clipWith(clip) + expect(rect.attr('clip-path')).toBe('url(#foo)') + }) + + it('creates a clipPath and appends the passed element to it to clip current element', () => { + const canvas = SVG().addTo(container) + const circle = canvas.circle(40) + const rect = canvas.rect(100, 100).clipWith(circle) + expect(circle.parent()).toEqual(any(ClipPath)) + expect(rect.attr('clip-path')).toBe(`url(#${circle.parent().id()})`) + }) + }) + + describe('unclip()', () => { + it('sets the clip-target attribute to null and returns itself', () => { + const clip = new ClipPath().id('foo') + const rect = new Rect().clipWith(clip) + expect(rect.unclip()).toBe(rect) + expect(rect.attr('clip-path')).toBe(undefined) + }) + }) + }) +}) diff --git a/spec/spec/elements/Container.js b/spec/spec/elements/Container.js new file mode 100644 index 000000000..f9741edec --- /dev/null +++ b/spec/spec/elements/Container.js @@ -0,0 +1,153 @@ +/* globals describe, expect, it, beforeEach, jasmine, container */ + +import { Container, create, SVG } from '../../../src/main.js' + +const { any } = jasmine + +describe('Container.js', () => { + describe('()', () => { + it('creates a new object of type Container', () => { + expect(new Container(create('g'))).toEqual(any(Container)) + }) + }) + + let canvas + let rect1 + let group1 + let rect2 + let circle1 + let group2 + let circle2 + let group3 + let line1 + let line2 + let circle3 + let group4 + let rect3 + + beforeEach(() => { + canvas = SVG().addTo(container) + rect1 = canvas.rect(100, 100).id('rect1') + group1 = canvas.group().id('group1') + rect2 = group1.rect(100, 100).id('rect2') + circle1 = group1.circle(50).id('circle1') + group2 = group1.group().id('group2') + circle2 = group2.circle(50).id('circle2') + group3 = group2.group().id('group3') + line1 = group3.line(1, 1, 2, 2).id('line1') + line2 = group3.line(1, 1, 2, 2).id('line2') + circle3 = group2.circle(50).id('circle3') + group4 = canvas.group().id('group4') + rect3 = group4.rect(100, 100).id('rect3') + + /* should be: + canvas + rect1 + group1 + rect2 + circle1 + group2 + circle2 + group3 + line1 + line2 + circle3 + group4 + rect3 + */ + }) + + describe('flatten()', () => { + it('flattens the whole document when called on the root', () => { + canvas.flatten() + + expect(canvas.children()).toEqual([ + rect1, + rect2, + circle1, + circle2, + line1, + line2, + circle3, + rect3 + ]) + }) + + it('flattens a group and places all children into its parent when called on a group - 1', () => { + group1.flatten() + + /* now should be: + canvas + rect1 + group1 + rect2 + circle1 + circle2 + line1 + line2 + circle3 + group4 + rect3 + */ + + expect(canvas.children()).toEqual([rect1, group1, group4]) + expect(group1.children()).toEqual([ + rect2, + circle1, + circle2, + line1, + line2, + circle3 + ]) + }) + + it('flattens a group and places all children into its parent when called on a group - 2', () => { + group2.flatten() + + /* now should be: + canvas + rect1 + group1 + rect2 + circle1 + group2 + circle2 + line1 + line2 + circle3 + group4 + rect3 + */ + + expect(group2.children()).toEqual([circle2, line1, line2, circle3]) + }) + }) + + describe('ungroup()', () => { + it('ungroups a group and inserts all children in the correct order in the parent parent of the group', () => { + group1.ungroup() + + expect(canvas.children()).toEqual([rect1, rect2, circle1, group2, group4]) + + group4.ungroup() + + expect(canvas.children()).toEqual([rect1, rect2, circle1, group2, rect3]) + }) + + it('ungroups a group into another group and appends the elements to the other group', () => { + group1.ungroup(group4) + expect(group4.children()).toEqual([rect3, rect2, circle1, group2]) + }) + + it('ungroups a group into another group at the specified position', () => { + group2.ungroup(group1, 1) + expect(group1.children()).toEqual([ + rect2, + circle2, + group3, + circle3, + circle1 + ]) + }) + }) +}) diff --git a/spec/spec/elements/Defs.js b/spec/spec/elements/Defs.js new file mode 100644 index 000000000..ef6c29029 --- /dev/null +++ b/spec/spec/elements/Defs.js @@ -0,0 +1,31 @@ +/* globals describe, expect, it, jasmine */ + +import { Defs } from '../../../src/main.js' + +const { any } = jasmine + +describe('Defs.js', () => { + describe('()', () => { + it('creates a new object of type Defs', () => { + expect(new Defs()).toEqual(any(Defs)) + }) + + it('sets passed attributes on the element', () => { + expect(new Defs({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('flatten()', () => { + it('does nothing and returns itself', () => { + const defs = Object.freeze(new Defs()) + expect(defs.flatten()).toBe(defs) + }) + }) + + describe('ungroup()', () => { + it('does nothing and returns itself', () => { + const defs = Object.freeze(new Defs()) + expect(defs.ungroup()).toBe(defs) + }) + }) +}) diff --git a/spec/spec/elements/Dom.js b/spec/spec/elements/Dom.js new file mode 100644 index 000000000..00879d084 --- /dev/null +++ b/spec/spec/elements/Dom.js @@ -0,0 +1,762 @@ +/* globals describe, expect, it, beforeEach, spyOn, jasmine, container */ + +import { + SVG, + G, + Rect, + Svg, + Dom, + List, + Fragment, + Circle, + Tspan, + create +} from '../../../src/main.js' +import { getWindow } from '../../../src/utils/window.js' +import { svg, html } from '../../../src/modules/core/namespaces.js' +const { any, createSpy, objectContaining } = jasmine + +describe('Dom.js', function () { + describe('()', () => { + it('creates a new object of type Dom', () => { + const rect = new Rect() + expect(new Dom(rect.node)).toEqual(any(Dom)) + }) + + it('sets passed attributes on the element', () => { + const rect = new Rect() + expect(new Dom(rect.node, { id: 'foo' }).id()).toBe('foo') + }) + + it('references the passed node on the instance', () => { + const rect = new Rect() + expect(new Dom(rect.node).node).toBe(rect.node) + }) + + it('sets the type according to the nodename', () => { + const rect = new Rect() + expect(new Dom(rect.node).type).toBe(rect.node.nodeName) + }) + }) + + describe('add()', () => { + it('adds an element as child to the end with no second argument given', () => { + const g = new G() + g.add(new Rect()) + const rect = new Rect() + g.add(rect) + expect(g.children().length).toBe(2) + expect(g.get(1)).toBe(rect) + }) + + it('adds an element at the specified position with second argument given', () => { + const g = new G() + g.add(new Rect()) + g.add(new Rect()) + const rect = new Rect() + g.add(rect, 1) + expect(g.children().length).toBe(3) + expect(g.get(1)).toBe(rect) + }) + + it('does nothing if element is already the element at that position', () => { + const g = new G() + g.rect(100, 100) + const rect = g.rect(100, 100) + g.add(rect, 1) + expect(g.get(1)).toBe(rect) + }) + + it('handles svg strings', () => { + const g = new G() + g.add('') + expect(g.children().length).toBe(1) + expect(g.get(0)).toEqual(any(Rect)) + }) + + it('handles query selectors', () => { + const canvas = SVG().addTo(container) + const rect = canvas.rect(100, 100).addClass('test') + const g = canvas.group() + g.add('.test') + expect(g.children().length).toBe(1) + expect(g.get(0)).toBe(rect) + }) + + it('handles a node', () => { + const g = new G() + const node = create('rect') + g.add(node) + expect(g.children().length).toBe(1) + expect(g.get(0)).toEqual(any(Rect)) + }) + }) + + describe('addTo()', () => { + it('returns the current element', () => { + const g = new G() + const rect = new Rect() + expect(rect.addTo(g)).toBe(rect) + }) + + it('puts an element into another element', () => { + const g = new G() + const rect = new Rect() + const spy = spyOn(g, 'put') + rect.addTo(g, 0) + expect(spy).toHaveBeenCalledWith(rect, 0) + }) + + it('works with svg strings', () => { + const rect = new Rect() + rect.addTo('') + expect(rect.parent()).toEqual(any(G)) + }) + + it('works with query selector', () => { + const canvas = SVG().addTo(container) + const rect = canvas.rect(100, 100) + const g = canvas.group().addClass('test') + rect.addTo('.test') + expect(g.children().length).toBe(1) + expect(g.get(0)).toBe(rect) + }) + }) + + describe('children()', () => { + it('returns a List of all children', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100, 100) + const children = g.children() + expect(children).toEqual([rect, circle]) + expect(children).toEqual(any(List)) + }) + }) + + describe('clear()', () => { + it('returns the current element', () => { + const g = new G() + g.rect(100, 100) + g.circle(100, 100) + expect(g.clear()).toBe(g) + }) + + it('removes all children from an element', () => { + const g = new G() + g.rect(100, 100) + g.circle(100, 100) + g.clear() + expect(g.children()).toEqual([]) + }) + }) + + describe('clone()', () => { + it('clones the current element and returns it', () => { + const rect = new Rect() + const clone = rect.clone() + expect(rect).not.toBe(clone) + expect(clone).toEqual(any(Rect)) + expect(clone.type).toBe(rect.type) + }) + + it('also clones the children by default', () => { + const group = new G() + const rect = group.rect(100, 100) + const clone = group.clone() + expect(clone.get(0)).not.toBe(rect) + expect(clone.get(0)).toEqual(any(Rect)) + }) + + it('does not clone the children when passing false', () => { + const group = new G() + group.rect(100, 100) + const clone = group.clone(false) + expect(clone.children()).toEqual([]) + }) + + it('assigns a new id to the element and to child elements by default', () => { + const group = new G().id('group') + const rect = group.rect(100, 100).id('rect') + const clone = group.clone() + expect(clone.get(0).id()).not.toBe(rect.id()) + expect(clone.id()).not.toBe(group.id()) + }) + + it('does not assign a new id to the element and to child elements', () => { + const group = new G().id('group') + const rect = group.rect(100, 100).id('rect') + const clone = group.clone(true, false) + expect(clone.get(0).id()).toBe(rect.id()) + expect(clone.id()).toBe(group.id()) + }) + + it('returns an instance of the same class the method was called on', () => { + const rect = new Dom(create('rect')) + expect(rect.constructor).toBe(Dom) + expect(rect.clone().constructor).toBe(Dom) + }) + }) + + describe('each()', () => { + it('iterates over all children and executes the passed function on then', () => { + const group = new G() + const group2 = group.group() + const circle = group.circle(100, 100) + const spy = createSpy('each') + group.each(spy) + + expect(spy.calls.all()).toEqual([ + objectContaining({ object: group2, args: [0, [group2, circle]] }), + objectContaining({ object: circle, args: [1, [group2, circle]] }) + ]) + }) + + it('iterates over all children recursively and executes the passed function on then when deep is true', () => { + const group = new G() + const group2 = group.group() + const rect = group2.rect(100, 100) + const circle = group.circle(100, 100) + const spy = createSpy('each') + group.each(spy, true) + + expect(spy.calls.all()).toEqual([ + objectContaining({ object: group2, args: [0, [group2, circle]] }), + objectContaining({ object: rect, args: [0, [rect]] }), + objectContaining({ object: circle, args: [1, [group2, circle]] }) + ]) + }) + }) + + describe('element()', () => { + it('creates an element of given type and appends it to the current element', () => { + const g = new G() + const el = g.element('title') + expect(el).toEqual(any(Dom)) + expect(el.type).toBe('title') + }) + + it('sets the specified attributes passed as second argument', () => { + const g = new G() + const el = g.element('title', { id: 'foo' }) + expect(el.id()).toBe('foo') + }) + }) + + describe('first()', () => { + it('returns the first child', () => { + const g = new G() + const rect = g.rect(100, 100) + g.circle(100, 100) + expect(g.first()).toBe(rect) + }) + + it('returns null if no first child exists', () => { + expect(new G().first()).toBe(null) + }) + }) + + describe('get()', () => { + it('returns the child at the given position', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100, 100) + expect(g.get(0)).toBe(rect) + expect(g.get(1)).toBe(circle) + }) + }) + + describe('getEventHolder()', () => { + it('returns the node because it holds all events on the object', () => { + const dom = new Dom({}) + expect(dom.getEventHolder()).toBe(dom.node) + }) + }) + + describe('getEventTarget()', () => { + it('returns the node because it is the target of the event', () => { + const dom = new Dom({}) + expect(dom.getEventTarget()).toBe(dom.node) + }) + }) + + describe('has()', () => { + it('returns true if the element has the passed element as child', () => { + const g = new G() + const rect = g.rect(100, 100) + expect(g.has(rect)).toBe(true) + }) + + it("returns false if the element hasn't the passed element as child", () => { + const g = new G() + const rect = new Rect() + expect(g.has(rect)).toBe(false) + }) + }) + + describe('html()', () => { + it('calls xml with the html namespace', () => { + const group = new G() + const spy = spyOn(group, 'xml') + group.html('') + expect(spy).toHaveBeenCalledWith('', undefined, html) + }) + }) + + describe('id()', () => { + it('returns current element when called as setter', () => { + const g = new G() + expect(g.id('asd')).toBe(g) + }) + + it('sets the id with argument given', () => { + expect(new G().id('foo').node.id).toBe('foo') + }) + + it('gets the id when no argument given', () => { + const g = new G({ id: 'foo' }) + expect(g.id()).toBe('foo') + }) + + it('generates an id on getting if none is set', () => { + const g = new G() + expect(g.node.id).toBe('') + g.id() + expect(g.node.id).not.toBe('') + }) + }) + + describe('index()', () => { + it('gets the position of the passed child', () => { + const g = new G() + g.rect(100, 100) + const rect = g.rect(100, 100) + expect(g.index(rect)).toBe(1) + }) + + it('returns -1 if element is no child', () => { + const g = new G() + const rect = new Rect() + expect(g.index(rect)).toBe(-1) + }) + }) + + describe('last()', () => { + it('gets the last child of the element', () => { + const g = new G() + g.rect(100, 100) + const rect = g.rect(100, 100) + expect(g.last()).toBe(rect) + }) + + it('returns null if no last child exists', () => { + expect(new G().last()).toBe(null) + }) + }) + + describe('parent()', () => { + var canvas, rect, group1, group2 + + beforeEach(function () { + canvas = SVG().addTo(container) + group1 = canvas.group().addClass('test') + group2 = group1.group() + rect = group2.rect(100, 100) + }) + + it('returns the svg parent with no argument given', () => { + expect(rect.parent()).toBe(group2) + }) + + it('returns the closest parent with the correct type', () => { + expect(rect.parent(Svg)).toBe(canvas) + }) + + it('returns the closest parent matching the selector', () => { + expect(rect.parent('.test')).toBe(group1) + }) + + it('returns null if it cannot find a parent matching the argument', () => { + expect(rect.parent('.not-there')).toBe(null) + }) + + it('returns null if it cannot find a parent matching the argument in a #document-fragment', () => { + const fragment = getWindow().document.createDocumentFragment() + const svg = new Svg().addTo(fragment) + const rect = svg.rect(100, 100) + expect(rect.parent('.not-there')).toBe(null) + }) + + it('returns Dom if parent is #document-fragment', () => { + const fragment = getWindow().document.createDocumentFragment() + const svg = new Svg().addTo(fragment) + expect(svg.parent()).toEqual(any(Dom)) + }) + + it('returns html parents, too', () => { + expect(canvas.parent().node).toBe(container) + }) + }) + + describe('put()', () => { + it('calls add() but returns the added element instead', () => { + const g = new G() + const rect = new Rect() + const spy = spyOn(g, 'add').and.callThrough() + expect(g.put(rect, 0)).toBe(rect) + expect(spy).toHaveBeenCalledWith(rect, 0) + }) + + it('creates object from svg string', () => { + const g = new G() + const rect = '' + const spy = spyOn(g, 'add').and.callThrough() + const ret = g.put(rect, 0) + expect(ret).toEqual(any(Rect)) + expect(spy).toHaveBeenCalledWith(ret, 0) + }) + + it('works with a query selector', () => { + const canvas = SVG().addTo(container) + const rect = canvas.rect().addClass('test') + const g = canvas.group() + const spy = spyOn(g, 'add').and.callThrough() + const ret = g.put('.test', 0) + expect(ret).toEqual(rect) + expect(spy).toHaveBeenCalledWith(rect, 0) + }) + }) + + describe('putIn()', () => { + it('calls add on the given parent', () => { + const g = new G() + const rect = new Rect() + const spy = spyOn(g, 'add') + rect.putIn(g, 0) + expect(spy).toHaveBeenCalledWith(rect, 0) + }) + + it('returns the passed element', () => { + const g = new G() + const rect = new Rect() + expect(rect.putIn(g, 0)).toBe(g) + }) + + it('returns an instance when svg string given', () => { + const g = '' + const rect = new Rect() + const ret = rect.putIn(g) + expect(ret).toEqual(any(G)) + expect(ret.children()).toEqual([rect]) + }) + + it('works with a query selector', () => { + const canvas = SVG().addTo(container) + const g = canvas.group().addClass('test') + const rect = canvas.rect(100, 100) + const ret = rect.putIn('.test') + expect(ret).toBe(g) + expect(g.children()).toEqual([rect]) + }) + }) + + describe('remove()', () => { + it('returns the removed element', () => { + const canvas = SVG().addTo(container) + const rect = canvas.rect(100, 100) + expect(rect.remove()).toBe(rect) + }) + + it('removes the element from the parent', () => { + const canvas = SVG().addTo(container) + const rect = canvas.rect(100, 100) + expect(canvas.children()).toEqual([rect]) + rect.remove() + expect(canvas.children()).toEqual([]) + }) + + it('is a noop when element is not attached to the dom', () => { + const rect = new Rect() + expect(rect.remove()).toBe(rect) + }) + + it('also works when direct child of document-fragment', () => { + const fragment = new Fragment() + const rect = fragment.rect(100, 100) + expect(fragment.children()).toEqual([rect]) + expect(rect.remove()).toBe(rect) + expect(fragment.children()).toEqual([]) + }) + }) + + describe('removeElement()', () => { + it('returns itself', () => { + const g = new G() + const rect = g.rect(100, 100) + expect(g.removeElement(rect)).toBe(g) + }) + + it('removes the given child', () => { + const g = new G() + const rect = g.rect(100, 100) + expect(g.removeElement(rect).children()).toEqual([]) + }) + + it('throws if the given element is not a child', () => { + const g = new G() + const rect = new Rect() + try { + g.removeElement(rect) + } catch (e) { + expect(e).toEqual(objectContaining({ code: 8 })) + } + }) + }) + + describe('replace()', () => { + it('returns the new element', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = new Circle() + expect(rect.replace(circle)).toBe(circle) + }) + + it('replaces the child at the correct position', () => { + const g = new G() + const rect1 = g.rect(100, 100) + const rect2 = g.rect(100, 100) + const rect3 = g.rect(100, 100) + const circle = new Circle() + rect2.replace(circle) + expect(g.children()).toEqual([rect1, circle, rect3]) + }) + + it('also works without a parent', () => { + const rect = new Rect() + const circle = new Circle() + expect(rect.replace(circle)).toBe(circle) + }) + }) + + describe('round()', () => { + it('rounds all attributes whose values are numbers to two decimals by default', () => { + const rect = new Rect({ id: 'foo', x: 10.678, y: 3, width: 123.456 }) + expect(rect.round().attr()).toEqual({ + id: 'foo', + x: 10.68, + y: 3, + width: 123.46 + }) + }) + + it('rounds all attributes whose values are numbers to the passed precision', () => { + const rect = new Rect({ id: 'foo', x: 10.678, y: 3, width: 123.456 }) + expect(rect.round(1).attr()).toEqual({ + id: 'foo', + x: 10.7, + y: 3, + width: 123.5 + }) + }) + + it('rounds the given attributes whose values are numbers to the passed precision', () => { + const rect = new Rect({ id: 'foo', x: 10.678, y: 3, width: 123.456 }) + expect(rect.round(1, ['id', 'x']).attr()).toEqual({ + id: 'foo', + x: 10.7, + y: 3, + width: 123.456 + }) + }) + }) + + describe('svg()', () => { + it('calls xml with the svg namespace', () => { + const group = new G() + const spy = spyOn(group, 'xml') + group.svg('') + expect(spy).toHaveBeenCalledWith('', undefined, svg) + }) + }) + + describe('toString()', () => { + it('calls id() and returns its result', () => { + const rect = new Rect({ id: 'foo' }) + const spy = spyOn(rect, 'id').and.callThrough() + expect(rect.toString()).toBe('foo') + expect(spy).toHaveBeenCalled() + }) + }) + + describe('words', () => { + it('sets the nodes textContent to the given value', () => { + const tspan = new Tspan().words('Hello World') + expect(tspan.text()).toBe('Hello World') + }) + }) + + describe('wrap()', function () { + var canvas + var rect + + beforeEach(function () { + canvas = SVG() + rect = canvas.rect(100, 100) + }) + + it('returns the current element', function () { + expect(rect.wrap(new G())).toBe(rect) + }) + + it('wraps the passed element around the current element', function () { + var g = new G() + expect(rect.wrap(g).parent()).toBe(g) + expect(g.parent()).toBe(canvas) + }) + + it('wraps also when element is not in the dom', () => { + var g = new G() + var rect = new Rect() + expect(rect.wrap(g).parent()).toBe(g) + expect(g.parent()).toBe(null) + }) + + it('inserts at the correct position', () => { + canvas.rect(100, 100) + rect = canvas.rect(100, 100) + var position = rect.position() + var g = new G() + expect(rect.wrap(g).parent().position()).toBe(position) + }) + + it('allows to pass an svg string as element', () => { + rect.wrap('') + expect(rect.parent()).toEqual(any(G)) + expect(rect.parent().parent()).toBe(canvas) + }) + + it('allows to pass an svg string as element', () => { + rect.wrap('') + expect(rect.parent()).toEqual(any(G)) + expect(rect.parent().parent()).toBe(canvas) + }) + + it('allows to pass an svg string as element when element not in the dom', () => { + var rect = new Rect() + rect.wrap(SVG('')) + expect(rect.parent()).toEqual(any(G)) + expect(rect.parent().parent()).toBe(null) + }) + + it('allows to pass an svg node as element', () => { + const node = create('g') + rect.wrap(node) + expect(rect.parent()).toEqual(any(G)) + expect(rect.parent().node).toBe(node) + expect(rect.parent().parent()).toBe(canvas) + }) + }) + + describe('xml()', () => { + describe('as setter', () => { + it('returns itself', () => { + const g = new G() + expect(g.xml('', undefined, svg)).toBe(g) + }) + + it('imports a single element', () => { + const g = new G().xml('', undefined, svg) + expect(g.children()).toEqual([any(Rect)]) + expect(g.children()[0].node.namespaceURI).toBe(svg) + }) + + it('imports multiple elements', () => { + const g = new G().xml('', undefined, svg) + expect(g.children()).toEqual([any(Rect), any(Circle)]) + }) + + it('replaces the current element with the imported elements with outerHtml = true', () => { + const canvas = new Svg() + const g = canvas.group() + g.xml('', true, svg) + expect(canvas.children()).toEqual([any(Rect), any(Circle)]) + }) + + it('returns the parent when outerHtml = true', () => { + const canvas = new Svg() + const g = canvas.group() + expect(g.xml('', true, svg)).toBe(canvas) + expect(canvas.children()).toEqual([any(Rect), any(Circle)]) + }) + + it('works without a parent', () => { + const canvas = new Svg() + expect(canvas.xml('', undefined, svg)).toBe(canvas) + }) + }) + + describe('as getter', () => { + let canvas, group, rect + + beforeEach(() => { + canvas = new Svg().removeNamespace() + group = canvas.group() + rect = group.rect(123.456, 234.567) + }) + + it('returns the svg string of the element by default', () => { + expect(rect.xml(), svg).toBe( + '' + ) + expect(canvas.xml(), svg).toBe( + '' + ) + }) + + it('returns the innerHtml when outerHtml = false', () => { + expect(rect.xml(false, svg)).toBe('') + expect(canvas.xml(false, svg)).toBe( + '' + ) + }) + + it('runs a function on every exported node', () => { + expect(rect.xml((el) => el.round(1))).toBe( + '' + ) + }) + + it('runs a function on every exported node and replaces node with returned node if return value is not falsy', () => { + expect(rect.xml(() => new Circle(), svg)).toBe('') + expect(canvas.xml(() => new G(), svg)).toBe('') // outer was replaced by an empty g + expect( + canvas.xml((el) => { + if (el instanceof Rect) return new Circle() + if (el instanceof Svg) el.removeNamespace() + }, svg) + ).toBe('') + }) + + it('runs a function on every exported node and removes node if return value is false', () => { + expect(group.xml(() => false, svg)).toBe('') + expect(canvas.xml(() => false, svg)).toBe('') + expect( + canvas.xml((el) => { + if (el instanceof Svg) { + el.removeNamespace() + } else { + return false + } + }, svg) + ).toBe('') + }) + + it('runs a function on every inner node and exports it when outerHtml = false', () => { + expect(canvas.xml(() => false, false, svg)).toBe('') + expect(canvas.xml(() => undefined, false, svg)).toBe( + '' + ) + }) + }) + }) +}) diff --git a/spec/spec/elements/Element.js b/spec/spec/elements/Element.js new file mode 100644 index 000000000..93cee3b0f --- /dev/null +++ b/spec/spec/elements/Element.js @@ -0,0 +1,320 @@ +/* globals describe, expect, it, beforeEach, spyOn, jasmine, container */ + +import { Element, create, Rect, G, SVG, Text } from '../../../src/main.js' +const { any, objectContaining } = jasmine + +describe('Element.js', function () { + let element + + beforeEach(() => { + element = new Element(create('rect')) + }) + + describe('()', () => { + it('creates a new object of type Element', () => { + expect(element).toEqual(any(Element)) + }) + + it('sets passed attributes on the element', () => { + expect(new Element(create('rect'), { id: 'foo' }).id()).toBe('foo') + }) + + it('references the instance on the passed node', () => { + expect(element.node.instance).toBe(element) + }) + + it('sets the dom property to an empty object', () => { + expect(element.dom).toEqual({}) + }) + + it('hydrates the dom property with data found in the dom', () => { + element.dom = { foo: 'bar' } + element.writeDataToDom() + expect(new Element(element.node).dom).toEqual({ foo: 'bar' }) + }) + + it('falls back to empty object when attribute is null', () => { + element.node.setAttribute('data-svg', 'null') + expect(new Element(element.node).dom).toEqual({}) + }) + + it('uses old svgjs:data attribute if present', () => { + element.node.setAttribute('svgjs:data', '{"foo":"bar"}') + expect(new Element(element.node).dom).toEqual({ foo: 'bar' }) + }) + }) + + describe('center()', () => { + it('calls cx and cy with passed parameters and returns itself', () => { + const spyCx = spyOn(element, 'cx').and.callThrough() + const spyCy = spyOn(element, 'cy').and.callThrough() + expect(element.center(1, 2)).toBe(element) + expect(spyCx).toHaveBeenCalledWith(1) + expect(spyCy).toHaveBeenCalledWith(2) + }) + }) + + describe('cx()', () => { + it('gets the elements center along the x axis', () => { + element.attr({ x: 10, width: 100 }) + expect(element.cx()).toBe(60) + }) + + it('centers the element along the x axis and returns itself', () => { + element.attr({ x: 10, width: 100 }) + expect(element.cx(100)).toBe(element) + expect(element.attr('x')).toBe(50) + }) + }) + + describe('cy()', () => { + it('gets the elements center along the y axis', () => { + element.attr({ y: 10, height: 100 }) + expect(element.cy()).toBe(60) + }) + + it('centers the element along the y axis and returns itself', () => { + element.attr({ y: 10, height: 100 }) + expect(element.cy(100)).toBe(element) + expect(element.attr('y')).toBe(50) + }) + }) + + describe('defs()', () => { + it('returns null if detached', () => { + expect(new Rect().defs()).toBe(null) + expect(new G().put(new Rect()).defs()).toBe(null) + }) + + it('calls defs on root node', () => { + const canvas = SVG() + const rect = canvas.rect(100, 100) + const spy = spyOn(canvas, 'defs').and.callThrough() + expect(rect.defs()).toBe(canvas.defs()) + expect(spy.calls.count()).toBe(2) + }) + }) + + describe('dmove()', () => { + it('calls dx and dy with passed parameters and returns itself', () => { + const spyDx = spyOn(element, 'dx').and.callThrough() + const spyDy = spyOn(element, 'dy').and.callThrough() + expect(element.dmove(1, 2)).toBe(element) + expect(spyDx).toHaveBeenCalledWith(1) + expect(spyDy).toHaveBeenCalledWith(2) + }) + }) + + describe('dx()', () => { + it('moves by zero by default', () => { + element.attr({ x: 10, width: 100 }) + expect(element.dx().x()).toBe(10) + }) + + it('moves the element along the x axis relatively and returns itself', () => { + element.attr({ x: 10, width: 100 }) + expect(element.dx(100)).toBe(element) + expect(element.attr('x')).toBe(110) + }) + }) + + describe('dy()', () => { + it('moves by zero by default', () => { + element.attr({ y: 10, height: 100 }) + expect(element.dy().y()).toBe(10) + }) + + it('moves the element along the x axis relatively and returns itself', () => { + element.attr({ y: 10, height: 100 }) + expect(element.dy(100)).toBe(element) + expect(element.attr('y')).toBe(110) + }) + }) + + describe('root()', () => { + it('returns the root of this element', () => { + const canvas = SVG() + const rect = canvas.rect() + expect(rect.root()).toBe(canvas) + }) + + it('returns null if element is detached', () => { + expect(new G().put(new Rect()).root()).toBe(null) + }) + }) + + describe('getEventHolder()', () => { + it('returns itself', () => { + expect(element.getEventHolder()).toBe(element) + }) + }) + + describe('height()', () => { + it('calls attr with height', () => { + const spy = spyOn(element, 'attr') + element.height(123) + expect(spy).toHaveBeenCalledWith('height', 123) + }) + }) + + describe('move()', () => { + it('calls x and y with passed parameters and returns itself', () => { + const spyx = spyOn(element, 'x').and.callThrough() + const spyy = spyOn(element, 'y').and.callThrough() + expect(element.move(1, 2)).toBe(element) + expect(spyx).toHaveBeenCalledWith(1) + expect(spyy).toHaveBeenCalledWith(2) + }) + }) + + describe('parents()', () => { + it('returns array of parents until the passed element or root svg', () => { + const canvas = SVG().addTo(container) + const _groupA = canvas.group().addClass('test') + const group1 = canvas.group().addClass('test') + const group2 = group1.group() + const group3 = group2.group() + const rect = group3.rect(100, 100) + + expect(rect.parents('.test')).toEqual([group3, group2, group1]) + expect(rect.parents(group2)).toEqual([group3, group2]) + expect(rect.parents(group1).length).toBe(3) + expect(rect.parents()).toEqual([group3, group2, group1, canvas]) + }) + + it('returns array of parents until the closest matching parent', () => { + const canvas = SVG().addTo(container) + const _groupA = canvas.group().addClass('test') + const group1 = canvas.group().addClass('test') + const group2 = group1.group().addClass('test').addClass('foo') + const group3 = group2.group().addClass('foo') + const rect = group3.rect(100, 100) + + expect(rect.parents('.test')).toEqual([group3, group2]) + expect(rect.parents('.foo')).toEqual([group3]) + expect(rect.parents('.test:not(.foo)')).toEqual([group3, group2, group1]) + }) + + it('returns null if the passed element is not an ancestor', () => { + const canvas = SVG().addTo(container) + const groupA = canvas.group().addClass('test') + const group1 = canvas.group() + const group2 = group1.group() + const group3 = group2.group() + const rect = group3.rect(100, 100) + + expect(rect.parents('.does-not-exist')).toEqual(null) + expect(rect.parents('.test')).toEqual(null) + expect(rect.parents(groupA)).toEqual(null) + }) + }) + + describe('reference()', () => { + it('gets a referenced element from a given attribute', () => { + const canvas = SVG().addTo(container) + const rect = canvas.defs().rect(100, 100) + const use = canvas.use(rect) + const mark = canvas.marker(10, 10) + const path = canvas.path('M0 0 50 50').marker('end', mark) + + expect(use.reference('href')).toBe(rect) + expect(path.reference('marker-end')).toBe(mark) + expect(rect.reference('width')).toBe(null) + }) + }) + + describe('setData()', () => { + it('sets the given data to the dom property and returns itself', () => { + expect(element.setData({ foo: 'bar' })).toBe(element) + expect(element.dom).toEqual({ foo: 'bar' }) + }) + }) + + describe('size()', () => { + it('calls width and height with passed parameters and returns itself', () => { + const spyWidth = spyOn(element, 'width').and.callThrough() + const spyHeight = spyOn(element, 'height').and.callThrough() + expect(element.size(1, 2)).toBe(element) + expect(spyWidth).toHaveBeenCalledWith(objectContaining({ value: 1 })) + expect(spyHeight).toHaveBeenCalledWith(objectContaining({ value: 2 })) + }) + + it('changes height proportionally if null', () => { + const canvas = SVG().addTo(container) + const element = canvas.rect(100, 100) + const spyWidth = spyOn(element, 'width').and.callThrough() + const spyHeight = spyOn(element, 'height').and.callThrough() + expect(element.size(200, null)).toBe(element) + expect(spyWidth).toHaveBeenCalledWith(objectContaining({ value: 200 })) + expect(spyHeight).toHaveBeenCalledWith(objectContaining({ value: 200 })) + }) + + it('changes width proportionally if null', () => { + const canvas = SVG().addTo(container) + const element = canvas.rect(100, 100) + const spyWidth = spyOn(element, 'width').and.callThrough() + const spyHeight = spyOn(element, 'height').and.callThrough() + expect(element.size(null, 200)).toBe(element) + expect(spyWidth).toHaveBeenCalledWith(objectContaining({ value: 200 })) + expect(spyHeight).toHaveBeenCalledWith(objectContaining({ value: 200 })) + }) + }) + + describe('width()', () => { + it('calls attr with width', () => { + const spy = spyOn(element, 'attr') + element.width(123) + expect(spy).toHaveBeenCalledWith('width', 123) + }) + }) + + describe('writeDataToDom()', () => { + it('removes previously set data', () => { + element.node.setAttribute('data-svgjs', JSON.stringify({ foo: 'bar' })) + element.writeDataToDom() + expect(element.node.getAttribute('data-svgjs')).toBe(null) + }) + + it('writes data from the dom property into the dom', () => { + element.dom = { foo: 'bar' } + element.writeDataToDom() + expect(element.node.getAttribute('data-svgjs')).toBe( + JSON.stringify({ foo: 'bar' }) + ) + }) + + it('recursively calls writeDataToDom on all children', () => { + const g = new G() + const rect = g.rect(100, 100) + const spy = spyOn(rect, 'writeDataToDom') + g.writeDataToDom() + expect(spy).toHaveBeenCalled() + }) + + it('filters out default data', () => { + const node1 = new Text() + const node2 = new Text() + node2.dom.foo = 'bar' + node1.writeDataToDom() + node2.writeDataToDom() + expect(node1.node.getAttribute('data-svgjs')).toBe(null) + expect(node2.node.getAttribute('data-svgjs')).toBe('{"foo":"bar"}') + }) + }) + + describe('x()', () => { + it('calls attr with x', () => { + const spy = spyOn(element, 'attr') + element.x(123) + expect(spy).toHaveBeenCalledWith('x', 123) + }) + }) + + describe('y()', () => { + it('calls attr with y', () => { + const spy = spyOn(element, 'attr') + element.y(123) + expect(spy).toHaveBeenCalledWith('y', 123) + }) + }) +}) diff --git a/spec/spec/elements/Ellipse.js b/spec/spec/elements/Ellipse.js new file mode 100644 index 000000000..217266928 --- /dev/null +++ b/spec/spec/elements/Ellipse.js @@ -0,0 +1,76 @@ +/* globals describe, expect, it, spyOn, jasmine, container */ + +import { Ellipse, SVG, G } from '../../../src/main.js' + +const { any, objectContaining } = jasmine + +describe('Ellipse.js', () => { + describe('()', () => { + it('creates a new object of type Ellipse', () => { + expect(new Ellipse()).toEqual(any(Ellipse)) + }) + + it('sets passed attributes on the element', () => { + expect(new Ellipse({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('size()', () => { + it('calls rx and ry with passed parameters and returns itself', () => { + const ellipse = new Ellipse() + const spyrx = spyOn(ellipse, 'rx').and.callThrough() + const spyry = spyOn(ellipse, 'ry').and.callThrough() + expect(ellipse.size(4, 2)).toBe(ellipse) + expect(spyrx).toHaveBeenCalledWith(objectContaining({ value: 2 })) + expect(spyry).toHaveBeenCalledWith(objectContaining({ value: 1 })) + }) + + it('changes ry proportionally if null', () => { + const canvas = SVG().addTo(container) + const ellipse = canvas.ellipse(100, 100) + const spyrx = spyOn(ellipse, 'rx').and.callThrough() + const spyry = spyOn(ellipse, 'ry').and.callThrough() + expect(ellipse.size(200, null)).toBe(ellipse) + expect(spyrx).toHaveBeenCalledWith(objectContaining({ value: 100 })) + expect(spyry).toHaveBeenCalledWith(objectContaining({ value: 100 })) + }) + + it('changes rx proportionally if null', () => { + const canvas = SVG().addTo(container) + const ellipse = canvas.ellipse(100, 100) + const spyrx = spyOn(ellipse, 'rx').and.callThrough() + const spyry = spyOn(ellipse, 'ry').and.callThrough() + expect(ellipse.size(null, 200)).toBe(ellipse) + expect(spyrx).toHaveBeenCalledWith(objectContaining({ value: 100 })) + expect(spyry).toHaveBeenCalledWith(objectContaining({ value: 100 })) + }) + }) + + describe('Container', () => { + describe('ellipse()', () => { + it('creates a ellipse with given size', () => { + const group = new G() + const ellipse = group.ellipse(50, 50) + expect(ellipse.attr('rx')).toBe(25) + expect(ellipse.attr('ry')).toBe(25) + expect(ellipse).toEqual(any(Ellipse)) + }) + + it('defaults to same radius with one argument', () => { + const group = new G() + const ellipse = group.ellipse(50) + expect(ellipse.attr('rx')).toBe(25) + expect(ellipse.attr('ry')).toBe(25) + expect(ellipse).toEqual(any(Ellipse)) + }) + + it('defaults to zero radius with no argument', () => { + const group = new G() + const ellipse = group.ellipse() + expect(ellipse.attr('rx')).toBe(0) + expect(ellipse.attr('ry')).toBe(0) + expect(ellipse).toEqual(any(Ellipse)) + }) + }) + }) +}) diff --git a/spec/spec/elements/ForeignObject.js b/spec/spec/elements/ForeignObject.js new file mode 100644 index 000000000..0040d04da --- /dev/null +++ b/spec/spec/elements/ForeignObject.js @@ -0,0 +1,47 @@ +/* globals describe, expect, it, jasmine */ + +import { makeInstance, ForeignObject } from '../../../src/main.js' + +const { any } = jasmine + +describe('ForeignObject.js', () => { + describe('()', () => { + it('creates a new object of type ForeignObject', () => { + expect(new ForeignObject()).toEqual(any(ForeignObject)) + }) + + it('sets passed attributes on the element', () => { + expect(new ForeignObject({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('Container', () => { + describe('foreignObject()', () => { + it('creates a foreignObject in the container', () => { + const canvas = makeInstance().addTo('#canvas') + const foreignObject = canvas.foreignObject() + expect(foreignObject).toEqual(any(ForeignObject)) + expect(foreignObject.parent()).toBe(canvas) + }) + + it('sets width and height correctly on construction', () => { + const canvas = makeInstance().addTo('#canvas') + const foreignObject = canvas.foreignObject(100, 200) + expect(foreignObject.width()).toBe(100) + expect(foreignObject.height()).toBe(200) + }) + }) + }) + + describe('Element methods', () => { + it('is usable with Elements methods such as height() and width()', () => { + const canvas = makeInstance().addTo('#canvas') + const foreignObject = canvas.foreignObject() + foreignObject.width(100).height(200).x(10).y(20) + expect(foreignObject.width()).toBe(100) + expect(foreignObject.height()).toBe(200) + expect(foreignObject.x()).toBe(10) + expect(foreignObject.y()).toBe(20) + }) + }) +}) diff --git a/spec/spec/elements/Fragment.js b/spec/spec/elements/Fragment.js new file mode 100644 index 000000000..dfac3d0df --- /dev/null +++ b/spec/spec/elements/Fragment.js @@ -0,0 +1,64 @@ +/* globals describe, expect, it, spyOn, jasmine */ + +import { Fragment, Dom } from '../../../src/main.js' +import { getWindow } from '../../../src/utils/window.js' +import { svg } from '../../../src/modules/core/namespaces.js' + +const { any } = jasmine + +describe('Fragment.js', () => { + describe('()', () => { + it('creates a new object of type Fragment', () => { + expect(new Fragment()).toEqual(any(Fragment)) + }) + + it('uses passed node instead of creating', () => { + const fragment = getWindow().document.createDocumentFragment() + expect(new Fragment(fragment).node).toBe(fragment) + }) + + it('has all Container methods available', () => { + const frag = new Fragment() + const rect = frag.rect(100, 100) + + expect(frag.children()).toEqual([rect]) + }) + }) + + describe('xml()', () => { + describe('as setter', () => { + it('calls parent method with outerHtml = false', () => { + const frag = new Fragment() + const spy = spyOn(Dom.prototype, 'xml').and.callThrough() + frag.xml('', true, svg) + expect(spy).toHaveBeenCalledWith('', false, svg) + }) + }) + + describe('as getter', () => { + it('calls parent method with outerHtml = false - 1', () => { + const frag = new Fragment() + const group = frag.group() + group.rect(123.456, 234.567) + const spy = spyOn(Dom.prototype, 'xml').and.callThrough() + + expect(frag.xml(false, svg)).toBe( + '' + ) + expect(spy).toHaveBeenCalledWith(false, svg) + }) + + it('calls parent method with outerHtml = false - 2', () => { + const frag = new Fragment() + const group = frag.group() + group.rect(123.456, 234.567) + const spy = spyOn(Dom.prototype, 'xml').and.callThrough() + + expect(frag.xml(true, svg)).toBe( + '' + ) + expect(spy).toHaveBeenCalledWith(false, svg) + }) + }) + }) +}) diff --git a/spec/spec/elements/G.js b/spec/spec/elements/G.js new file mode 100644 index 000000000..b46e23227 --- /dev/null +++ b/spec/spec/elements/G.js @@ -0,0 +1,28 @@ +/* globals describe, expect, it, jasmine, container */ + +import { G, SVG } from '../../../src/main.js' + +const { any } = jasmine + +describe('G.js', () => { + describe('()', () => { + it('creates a new object of type G', () => { + expect(new G()).toEqual(any(G)) + }) + + it('sets passed attributes on the element', () => { + expect(new G({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('Container', () => { + describe('group()', () => { + it('creates a group in the container', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + expect(g).toEqual(any(G)) + expect(g.parent()).toBe(canvas) + }) + }) + }) +}) diff --git a/spec/spec/elements/Gradient.js b/spec/spec/elements/Gradient.js new file mode 100644 index 000000000..41b896e9d --- /dev/null +++ b/spec/spec/elements/Gradient.js @@ -0,0 +1,109 @@ +/* globals describe, expect, it, spyOn, jasmine, container */ + +import { Gradient, SVG, Container } from '../../../src/main.js' + +const { any, objectContaining, createSpy } = jasmine + +describe('Gradient.js', () => { + describe('()', () => { + it('creates a new object of type LinearGradient', () => { + const gradient = new Gradient('linear') + expect(gradient).toEqual(any(Gradient)) + expect(gradient.type).toBe('linearGradient') + }) + + it('creates a new object of type RadialGradient', () => { + const gradient = new Gradient('radial') + expect(gradient).toEqual(any(Gradient)) + expect(gradient.type).toBe('radialGradient') + }) + + it('sets passed attributes on the element', () => { + expect(new Gradient('linear', { id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('attr()', () => { + it('relays to parents attr method for any call except transformation', () => { + const gradient = new Gradient('linear') + const spy = spyOn(Container.prototype, 'attr') + gradient.attr(1, 2, 3) + gradient.attr('transform', 2, 3) + + expect(spy).toHaveBeenCalledWith(1, 2, 3) + expect(spy).toHaveBeenCalledWith('gradientTransform', 2, 3) + }) + }) + + describe('bbox()', () => { + it('returns an empty box', () => { + expect(new Gradient('linear').bbox().isNulled()).toBe(true) + }) + }) + + describe('targets()', () => { + it('gets all targets of this gradient', () => { + const canvas = SVG().addTo(container) + const gradient = canvas.gradient('linear') + const rect = canvas.rect(100, 100).fill(gradient) + expect(gradient.targets()).toEqual([rect]) + }) + }) + + describe('toString()', () => { + it('calls url() and returns the result', () => { + const gradient = new Gradient('linear') + expect(gradient.toString()).toBe(gradient.url()) + }) + }) + + describe('update()', () => { + it('clears the element', () => { + const gradient = new Gradient('linear') + gradient.stop(0.1, '#fff') + expect(gradient.update().children()).toEqual([]) + }) + + it('executes a function in the context of the gradient', () => { + const gradient = new Gradient('linear') + const spy = createSpy('gradient') + gradient.update(spy) + expect(spy.calls.all()).toEqual([ + objectContaining({ object: gradient, args: [gradient] }) + ]) + }) + }) + + describe('url()', () => { + it('returns url(#id)', () => { + const gradient = new Gradient('linear').id('foo') + expect(gradient.url()).toBe('url(#foo)') + }) + }) + + describe('Container', () => { + it('relays the call to defs', () => { + const canvas = new SVG() + const defs = canvas.defs() + const spy = spyOn(defs, 'gradient').and.callThrough() + const spy2 = createSpy('gradient') + + canvas.gradient('linear', spy2) + expect(spy).toHaveBeenCalledWith('linear', spy2) + expect(spy2).toHaveBeenCalled() + }) + }) + + describe('Defs', () => { + it('creates a pattern in the defs', () => { + const canvas = new SVG() + const defs = canvas.defs() + const spy = createSpy('gradient') + const gradient = defs.gradient('linear', spy) + expect(gradient).toEqual(any(Gradient)) + expect(gradient.type).toBe('linearGradient') + expect(defs.children()).toEqual([gradient]) + expect(spy).toHaveBeenCalled() + }) + }) +}) diff --git a/spec/spec/elements/Image.js b/spec/spec/elements/Image.js new file mode 100644 index 000000000..2e92e13f5 --- /dev/null +++ b/spec/spec/elements/Image.js @@ -0,0 +1,123 @@ +/* globals describe, expect, it, jasmine */ + +import { Image, Pattern, SVG } from '../../../src/main.js' +import { getWindow } from '../../../src/utils/window.js' + +const { any, objectContaining, createSpy } = jasmine + +const url = 'spec/fixtures/pixel.png' +describe('Image.js', () => { + describe('()', () => { + it('creates a new object of type Image', () => { + expect(new Image()).toEqual(any(Image)) + }) + + it('sets passed attributes on the element', () => { + expect(new Image({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('load()', () => { + it('is a noop when url is falsy and returns itself', () => { + const image = Object.freeze(new Image()) + expect(image.load()).toBe(image) + }) + + it('executes a callback when the image is loaded', (done) => { + const spy = createSpy('image', (e) => { + expect(e.target.complete).toBe(true) + expect(spy.calls.all()).toEqual([ + objectContaining({ object: image, args: [any(getWindow().Event)] }) + ]) + done() + }).and.callThrough() + + const image = new Image().load(url, spy) + }) + + it('errors when image cant be loaded', () => { + // cant test this because of jasmine timeouts and browser disconnects + }) + + // it('sets the width and height of the image automatically', () => { + // const image = new Image('spec/fixtures/pixel.png') + // }) + + it('should set width and height automatically if no size is given', (done) => { + const image = new Image().load(url, () => { + expect(image.attr('height')).toBe(1) + expect(image.attr('width')).toBe(1) + done() + }) + }) + + it('should not change with and height when size already set', (done) => { + const image = new Image() + .load(url, () => { + expect(image.attr('height')).toBe(100) + expect(image.attr('width')).toBe(100) + done() + }) + .size(100, 100) + }) + + it('changes size of pattern to image size if parent is pattern and size is 0', (done) => { + const pattern = new Pattern().size(0, 0) + new Image() + .load(url, () => { + expect(pattern.attr('height')).toBe(100) + expect(pattern.attr('width')).toBe(100) + done() + }) + .size(100, 100) + .addTo(pattern) + }) + + it('does not change size of pattern if pattern has a size set', (done) => { + const pattern = new Pattern().size(50, 50) + new Image() + .load(url, () => { + expect(pattern.attr('height')).toBe(50) + expect(pattern.attr('width')).toBe(50) + done() + }) + .size(100, 100) + .addTo(pattern) + }) + }) + + describe('Container', () => { + describe('image()', () => { + it('creates image in the container', () => { + const canvas = SVG() + const image = canvas.image(url) + expect(image).toBe(image) + expect(canvas.children()).toEqual([image]) + }) + }) + }) + + describe('attribute hook', () => { + it('creates a pattern in defs when value is an image and puts image there', () => { + const canvas = SVG() + const image = new Image() + canvas.rect(100, 100).attr('something', image) + expect(canvas.defs().children()).toEqual([any(Pattern)]) + expect(canvas.defs().findOne('image')).toBe(image) + }) + + it('creates an image from image path in defs with pattern when attr is fill', () => { + const canvas = SVG() + canvas.rect(100, 100).attr('fill', url) + expect(canvas.defs().children()).toEqual([any(Pattern)]) + expect(canvas.defs().findOne('image').attr('href')).toBe(url) + }) + + it('creates an image from image path in defs with pattern when attr is stroke', () => { + const canvas = SVG() + canvas.rect(100, 100).attr('stroke', url) + expect(canvas.defs().children()).toEqual([any(Pattern)]) + expect(canvas.defs().findOne('image').attr('href')).toBe(url) + }) + }) +}) diff --git a/spec/spec/elements/Line.js b/spec/spec/elements/Line.js new file mode 100644 index 000000000..48a02bf8a --- /dev/null +++ b/spec/spec/elements/Line.js @@ -0,0 +1,172 @@ +/* globals describe, expect, it, beforeEach, spyOn, jasmine, container */ + +import { Line, PointArray, SVG, G } from '../../../src/main.js' + +const { any, objectContaining } = jasmine + +describe('Line.js', () => { + let line + + beforeEach(() => { + line = new Line() + }) + + describe('()', () => { + it('creates a new object of type Line', () => { + expect(new Line()).toEqual(any(Line)) + }) + + it('sets passed attributes on the element', () => { + expect(new Line({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('array()', () => { + it('returns a PointArray containing the points of the line', () => { + const array = line.plot(1, 2, 3, 4).array() + expect(array).toEqual(any(PointArray)) + expect(array).toEqual([ + [1, 2], + [3, 4] + ]) + }) + }) + + describe('move()', () => { + it('returns itself', () => { + expect(line.move(0, 0)).toBe(line) + }) + + it('moves the line along x and y axis', () => { + const canvas = SVG().addTo(container) + const line = canvas.line(1, 2, 3, 4) + line.move(50, 50) + expect(line.bbox()).toEqual( + objectContaining({ + x: 50, + y: 50, + width: 2, + height: 2 + }) + ) + }) + }) + + describe('plot()', () => { + it('relays to array() as getter', () => { + const spy = spyOn(line, 'array') + line.plot() + expect(spy).toHaveBeenCalled() + }) + + it('calls attr with line attributes when 4 parameters given', () => { + const spy = spyOn(line, 'attr') + line.plot(1, 2, 3, 4) + expect(spy).toHaveBeenCalledWith({ x1: 1, y1: 2, x2: 3, y2: 4 }) + }) + + it('calls attr with line attributes when string given', () => { + const spy = spyOn(line, 'attr') + line.plot('1, 2, 3, 4') + expect(spy).toHaveBeenCalledWith({ x1: 1, y1: 2, x2: 3, y2: 4 }) + }) + + it('calls attr with line attributes when array given', () => { + const spy = spyOn(line, 'attr') + line.plot([1, 2, 3, 4]) + expect(spy).toHaveBeenCalledWith({ x1: 1, y1: 2, x2: 3, y2: 4 }) + }) + + it('calls attr with line attributes when multi array given', () => { + const spy = spyOn(line, 'attr') + line.plot([ + [1, 2], + [3, 4] + ]) + expect(spy).toHaveBeenCalledWith({ x1: 1, y1: 2, x2: 3, y2: 4 }) + }) + + it('calls attr with line attributes when PointArray given', () => { + const spy = spyOn(line, 'attr') + line.plot( + new PointArray([ + [1, 2], + [3, 4] + ]) + ) + expect(spy).toHaveBeenCalledWith({ x1: 1, y1: 2, x2: 3, y2: 4 }) + }) + }) + + describe('size()', () => { + it('returns itself', () => { + expect(line.size(50, 50)).toBe(line) + }) + + it('sets the size of the line', () => { + const canvas = SVG().addTo(container) + const line = canvas.line(1, 2, 3, 4) + line.size(50, 50) + expect(line.bbox()).toEqual( + objectContaining({ + width: 50, + height: 50, + x: 1, + y: 2 + }) + ) + }) + + it('changes height proportionally', () => { + const canvas = SVG().addTo(container) + const line = canvas.line(1, 2, 3, 4) + line.size(50, null) + expect(line.bbox()).toEqual( + objectContaining({ + width: 50, + height: 50, + x: 1, + y: 2 + }) + ) + }) + + it('changes width proportionally', () => { + const canvas = SVG().addTo(container) + const line = canvas.line(1, 2, 3, 4) + line.size(null, 50) + expect(line.bbox()).toEqual( + objectContaining({ + width: 50, + height: 50, + x: 1, + y: 2 + }) + ) + }) + }) + + describe('Container', () => { + describe('line()', () => { + it('creates a line with given points', () => { + const group = new G() + const line = group.line(1, 2, 3, 4) + expect(line.array()).toEqual([ + [1, 2], + [3, 4] + ]) + expect(line).toEqual(any(Line)) + }) + + it('defaults to zero line', () => { + const group = new G() + const line = group.line() + expect(line.array()).toEqual([ + [0, 0], + [0, 0] + ]) + expect(line).toEqual(any(Line)) + }) + }) + }) +}) diff --git a/spec/spec/elements/Marker.js b/spec/spec/elements/Marker.js new file mode 100644 index 000000000..cddfc5008 --- /dev/null +++ b/spec/spec/elements/Marker.js @@ -0,0 +1,177 @@ +/* globals describe, expect, it, beforeEach, jasmine, container */ + +import { Marker, SVG, Defs } from '../../../src/main.js' + +const { any } = jasmine + +describe('Marker.js', function () { + describe('()', () => { + it('creates a new object of type Marker', () => { + expect(new Marker()).toEqual(any(Marker)) + }) + + it('sets passed attributes on the element', () => { + expect(new Marker({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('width()', () => { + it('sets the markerWidth attribute', () => { + const marker = new Marker().width(100) + expect(marker.attr('markerWidth')).toBe(100) + }) + }) + + describe('height()', () => { + it('sets the markerHeight attribute', () => { + const marker = new Marker().height(100) + expect(marker.attr('markerHeight')).toBe(100) + }) + }) + + describe('orient()', () => { + it('sets the orient attribute', () => { + const marker = new Marker().orient('auto') + expect(marker.attr('orient')).toBe('auto') + }) + }) + + describe('ref()', () => { + it('sets refX and refY attribute', () => { + const marker = new Marker().ref(10, 20) + expect(marker.attr('refX')).toBe(10) + expect(marker.attr('refY')).toBe(20) + }) + }) + + describe('update()', () => { + it('updates the marker', () => { + const marker = new Marker() + marker.rect(100, 100) + marker.update(function (m) { + m.rect(100, 100) + expect(this).toBe(marker) + expect(m).toBe(marker) + }) + expect(marker.children().length).toBe(1) + }) + }) + + describe('toString()', () => { + it('returns the url identifier for this marker', () => { + const marker = new Marker() + expect(marker.toString()).toBe('url(#' + marker.id() + ')') + }) + }) + + describe('Container', () => { + var canvas + var group + + beforeEach(() => { + canvas = SVG() + group = canvas.group() + }) + + describe('marker()', () => { + it('creates an instance of Marker', () => { + const marker = group.marker(10, 12) + expect(marker instanceof Marker).toBeTrue() + }) + + it('creates marker in defs', () => { + const marker = group.marker(10, 12) + expect(marker.parent() instanceof Defs).toBeTruthy() + }) + + it('sets the refX to half of the given width and height', () => { + const marker = group.marker(10, 12) + expect(marker.node.getAttribute('refX')).toBe('5') + expect(marker.node.getAttribute('refY')).toBe('6') + }) + }) + }) + + describe('Defs', () => { + describe('marker()', () => { + it('creates a marker in the defs and sets all attributes', () => { + const canvas = SVG() + const defs = canvas.defs() + const marker = defs.marker(10, 12) + expect(marker.attr('refX')).toBe(5) + expect(marker.attr('refY')).toBe(6) + expect(marker.attr('markerWidth')).toBe(10) + expect(marker.attr('markerHeight')).toBe(12) + expect(marker.attr('viewBox')).toBe('0 0 10 12') + expect(marker.attr('orient')).toBe('auto') + expect(marker).toEqual(any(Marker)) + expect(defs.children()).toEqual([marker]) + }) + }) + }) + + describe('marker', () => { + var path, marker, canvas + + beforeEach(() => { + // because we use `reference` here we need to put it into the live dom + canvas = new SVG().addTo(container) + path = canvas.path( + 'M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100' + ) + }) + + it('creates an instance of Marker', () => { + path.marker('mid', 10, 12, function (add) { + add.rect(10, 12) + + this.ref(5, 6) + }) + + expect(path.reference('marker-mid').children().length).toBe(1) + expect(path.reference('marker-mid').attr('refX')).toBe(5) + expect(path.reference('marker-mid') instanceof Marker).toBeTruthy() + }) + + describe('marker()', () => { + it('returns the target element', () => { + expect(path.marker('start', 10, 12)).toBe(path) + }) + + it('creates a marker and applies it to the marker-start attribute', () => { + path.marker('start', 10, 12) + marker = path.reference('marker-start') + + expect(path.node.getAttribute('marker-start')).toBe(marker.toString()) + }) + + it('creates a marker and applies it to the marker-mid attribute', () => { + path.marker('mid', 10, 12) + marker = path.reference('marker-mid') + + expect(path.node.getAttribute('marker-mid')).toBe(marker.toString()) + }) + + it('creates a marker and applies it to the marker-end attribute', () => { + path.marker('end', 10, 12) + marker = path.reference('marker-end') + + expect(path.node.getAttribute('marker-end')).toBe(marker.toString()) + }) + + it('creates a marker and applies it to the marker-end attribute', () => { + path.marker('all', 10, 12) + marker = path.reference('marker') + + expect(path.node.getAttribute('marker')).toBe(marker.toString()) + }) + + it('accepts an instance of an existing marker element as the second argument', () => { + marker = new Marker().size(11, 11) + path.marker('mid', marker) + + expect(path.node.getAttribute('marker-mid')).toBe(marker.toString()) + }) + }) + }) +}) diff --git a/spec/spec/elements/Mask.js b/spec/spec/elements/Mask.js new file mode 100644 index 000000000..08a1aaa4b --- /dev/null +++ b/spec/spec/elements/Mask.js @@ -0,0 +1,94 @@ +/* globals describe, expect, it, spyOn, jasmine, container */ + +import { Mask, SVG, Container, Rect } from '../../../src/main.js' + +const { any } = jasmine + +describe('Mask.js', () => { + describe('()', () => { + it('creates a new object of type Mask', () => { + expect(new Mask()).toEqual(any(Mask)) + }) + + it('sets passed attributes on the element', () => { + expect(new Mask({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('remove()', () => { + it('unmasks all targets', () => { + const canvas = SVG().addTo(container) + const mask = canvas.mask() + const rect = canvas.rect(100, 100).maskWith(mask) + expect(mask.remove()).toBe(mask) + expect(rect.masker()).toBe(null) + }) + + it('calls remove on parent class', () => { + const mask = new Mask() + const spy = spyOn(Container.prototype, 'remove') + mask.remove() + expect(spy).toHaveBeenCalled() + }) + }) + + describe('targets()', () => { + it('gets all targets of this maskPath', () => { + const canvas = SVG().addTo(container) + const mask = canvas.mask() + const rect = canvas.rect(100, 100).maskWith(mask) + expect(mask.targets()).toEqual([rect]) + }) + }) + + describe('Container', () => { + describe('mask()', () => { + it('creates a maskPath in the defs', () => { + const canvas = SVG() + const mask = canvas.mask() + expect(mask).toEqual(any(Mask)) + expect(canvas.defs().children()).toEqual([mask]) + }) + }) + }) + + describe('Element', () => { + describe('masker()', () => { + it('returns the instance of Mask the current element is masked with', () => { + const canvas = SVG().addTo(container) + const mask = canvas.mask() + const rect = canvas.rect(100, 100).maskWith(mask) + expect(rect.masker()).toEqual(mask) + }) + + it('returns null if no maskPath was found', () => { + expect(new Rect().masker()).toBe(null) + }) + }) + + describe('maskWith()', () => { + it('sets the mask attribute on the element to the id of the maskPath', () => { + const mask = new Mask().id('foo') + const rect = new Rect().maskWith(mask) + expect(rect.attr('mask')).toBe('url(#foo)') + }) + + it('creates a maskPath and appends the passed element to it to mask current element', () => { + const canvas = SVG().addTo(container) + const circle = canvas.circle(40) + const rect = canvas.rect(100, 100).maskWith(circle) + expect(circle.parent()).toEqual(any(Mask)) + expect(rect.attr('mask')).toBe(`url(#${circle.parent().id()})`) + }) + }) + + describe('unmask()', () => { + it('sets the mask-target attribute to null and returns itself', () => { + const mask = new Mask().id('foo') + const rect = new Rect().maskWith(mask) + expect(rect.unmask()).toBe(rect) + expect(rect.attr('mask')).toBe(undefined) + }) + }) + }) +}) diff --git a/spec/spec/elements/Path.js b/spec/spec/elements/Path.js new file mode 100644 index 000000000..08105bc95 --- /dev/null +++ b/spec/spec/elements/Path.js @@ -0,0 +1,219 @@ +/* globals describe, expect, beforeEach, it, spyOn, jasmine, container */ + +import { Path, SVG, PathArray } from '../../../src/main.js' + +const { any, objectContaining } = jasmine + +describe('Path.js', () => { + let path + + beforeEach(() => { + path = new Path() + }) + + describe('()', () => { + it('creates a new object of type Path', () => { + expect(new Path()).toEqual(any(Path)) + }) + + it('sets passed attributes on the element', () => { + expect(new Path({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('array()', () => { + it('returns the underlying PathArray', () => { + const array = path.plot('M1 2 3 4').array() + expect(array).toEqual(any(PathArray)) + expect(array).toEqual([ + ['M', 1, 2], + ['L', 3, 4] + ]) + }) + }) + + describe('clear()', () => { + it('clears the array cache and returns itself', () => { + const array = path.plot('M1 2 3 4').array() + expect(path.clear()).toBe(path) + expect(array).not.toBe(path._array) + }) + }) + + describe('height()', () => { + it('gets the height of the path', () => { + const canvas = SVG().addTo(container) + const path = canvas.path('M0 0 50, 50') + expect(path.height()).toBe(50) + }) + + it('sets the height of the path and returns itself', () => { + const canvas = SVG().addTo(container) + const path = canvas.path('M0 0 50, 50') + expect(path.height(100)).toBe(path) + expect(path.height()).toBe(100) + }) + }) + + describe('move()', () => { + it('returns itself', () => { + expect(path.move(0, 0)).toBe(path) + }) + + it('moves the path along x and y axis', () => { + const canvas = SVG().addTo(container) + const path = canvas.path('M0 0 50, 50') + path.move(50, 50) + expect(path.bbox()).toEqual( + objectContaining({ + x: 50, + y: 50, + width: 50, + height: 50 + }) + ) + }) + }) + + describe('plot()', () => { + it('relays to array() as getter', () => { + const spy = spyOn(path, 'array') + path.plot() + expect(spy).toHaveBeenCalled() + }) + + it('works by passing a string', () => { + const spy = spyOn(path, 'attr') + path.plot('M0 0 50 50') + expect(spy).toHaveBeenCalledWith('d', 'M0 0 50 50') + }) + + it('works with flat array', () => { + const spy = spyOn(path, 'attr') + path.plot(['M', 0, 0, 'L', 50, 50]) + expect(spy).toHaveBeenCalledWith('d', [ + ['M', 0, 0], + ['L', 50, 50] + ]) + }) + + it('works with multi array', () => { + const spy = spyOn(path, 'attr') + path.plot([ + ['M', 0, 0], + ['L', 50, 50] + ]) + expect(spy).toHaveBeenCalledWith('d', [ + ['M', 0, 0], + ['L', 50, 50] + ]) + }) + + it('works with PathArray', () => { + const spy = spyOn(path, 'attr') + path.plot( + new PathArray([ + ['M', 0, 0], + ['L', 50, 50] + ]) + ) + expect(spy).toHaveBeenCalledWith('d', [ + ['M', 0, 0], + ['L', 50, 50] + ]) + }) + }) + + describe('size()', () => { + it('returns itself', () => { + expect(path.size(50, 50)).toBe(path) + }) + + it('sets the size of the path', () => { + const canvas = SVG().addTo(container) + const path = canvas.path('M0 0 50, 50') + path.size(100, 100) + expect(path.bbox()).toEqual( + objectContaining({ + width: 100, + height: 100, + x: 0, + y: 0 + }) + ) + }) + + it('changes height proportionally', () => { + const canvas = SVG().addTo(container) + const path = canvas.path('M0 0 50, 50') + path.size(100, null) + expect(path.bbox()).toEqual( + objectContaining({ + width: 100, + height: 100, + x: 0, + y: 0 + }) + ) + }) + + it('changes width proportionally', () => { + const canvas = SVG().addTo(container) + const path = canvas.path('M0 0 50, 50') + path.size(null, 100) + expect(path.bbox()).toEqual( + objectContaining({ + width: 100, + height: 100, + x: 0, + y: 0 + }) + ) + }) + }) + + describe('width()', () => { + it('gets the width of the path', () => { + const canvas = SVG().addTo(container) + const path = canvas.path('M0 0 50, 50') + expect(path.width()).toBe(50) + }) + + it('sets the width of the path and returns itself', () => { + const canvas = SVG().addTo(container) + const path = canvas.path('M0 0 50, 50') + expect(path.width(100)).toBe(path) + expect(path.width()).toBe(100) + }) + }) + + describe('x()', () => { + it('gets the x position of the path', () => { + const canvas = SVG().addTo(container) + const path = canvas.path('M10 10 50, 50') + expect(path.x()).toBe(10) + }) + + it('sets the x position of the path and returns itself', () => { + const canvas = SVG().addTo(container) + const path = canvas.path('M0 0 50, 50') + expect(path.x(100)).toBe(path) + expect(path.x()).toBe(100) + }) + }) + + describe('y()', () => { + it('gets the y position of the path', () => { + const canvas = SVG().addTo(container) + const path = canvas.path('M10 10 50, 50') + expect(path.y()).toBe(10) + }) + + it('sets the y position of the path and returns itself', () => { + const canvas = SVG().addTo(container) + const path = canvas.path('M0 0 50, 50') + expect(path.y(100)).toBe(path) + expect(path.y()).toBe(100) + }) + }) +}) diff --git a/spec/spec/elements/Pattern.js b/spec/spec/elements/Pattern.js new file mode 100644 index 000000000..edc98413e --- /dev/null +++ b/spec/spec/elements/Pattern.js @@ -0,0 +1,107 @@ +/* globals describe, expect, it, spyOn, jasmine, container */ + +import { Pattern, SVG, Container } from '../../../src/main.js' + +const { any, objectContaining, createSpy } = jasmine + +describe('Pattern.js', () => { + describe('()', () => { + it('creates a new object of type Pattern', () => { + const pattern = new Pattern() + expect(pattern).toEqual(any(Pattern)) + }) + + it('sets passed attributes on the element', () => { + expect(new Pattern({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('attr()', () => { + it('relays to parents attr method for any call except transformation', () => { + const pattern = new Pattern() + const spy = spyOn(Container.prototype, 'attr') + pattern.attr(1, 2, 3) + pattern.attr('transform', 2, 3) + + expect(spy).toHaveBeenCalledWith(1, 2, 3) + expect(spy).toHaveBeenCalledWith('patternTransform', 2, 3) + }) + }) + + describe('bbox()', () => { + it('returns an empty box', () => { + expect(new Pattern().bbox().isNulled()).toBe(true) + }) + }) + + describe('targets()', () => { + it('gets all targets of this pattern', () => { + const canvas = SVG().addTo(container) + const pattern = canvas.pattern() + const rect = canvas.rect(100, 100).fill(pattern) + expect(pattern.targets()).toEqual([rect]) + }) + }) + + describe('toString()', () => { + it('calls url() and returns the result', () => { + const pattern = new Pattern() + expect(pattern.toString()).toBe(pattern.url()) + }) + }) + + describe('update()', () => { + it('clears the element', () => { + const pattern = new Pattern() + pattern.rect(100, 100) + expect(pattern.update().children()).toEqual([]) + }) + + it('executes a function in the context of the pattern', () => { + const pattern = new Pattern() + const spy = createSpy('pattern') + pattern.update(spy) + expect(spy.calls.all()).toEqual([ + objectContaining({ object: pattern, args: [pattern] }) + ]) + }) + }) + + describe('url()', () => { + it('returns url(#id)', () => { + const pattern = new Pattern().id('foo') + expect(pattern.url()).toBe('url(#foo)') + }) + }) + + describe('Container', () => { + it('relays the call to defs', () => { + const canvas = new SVG() + const defs = canvas.defs() + const spy = spyOn(defs, 'pattern').and.callThrough() + const spy2 = createSpy('pattern') + + canvas.pattern(100, 100, spy2) + expect(spy).toHaveBeenCalledWith(100, 100, spy2) + expect(spy2).toHaveBeenCalled() + }) + }) + + describe('Defs', () => { + it('creates a pattern in the defs and sets its size and position', () => { + const canvas = new SVG() + const defs = canvas.defs() + const spy = createSpy('pattern') + const pattern = defs.pattern(100, 100, spy) + expect(pattern).toEqual(any(Pattern)) + expect(defs.children()).toEqual([pattern]) + expect(spy).toHaveBeenCalled() + expect(pattern.attr(['x', 'y', 'width', 'height'])).toEqual({ + x: 0, + y: 0, + width: 100, + height: 100 + }) + }) + }) +}) diff --git a/spec/spec/elements/Polygon.js b/spec/spec/elements/Polygon.js new file mode 100644 index 000000000..872aa3278 --- /dev/null +++ b/spec/spec/elements/Polygon.js @@ -0,0 +1,38 @@ +/* globals describe, expect, it, jasmine */ + +import { Polygon, G } from '../../../src/main.js' + +const { any } = jasmine + +describe('Polygon.js', () => { + describe('()', () => { + it('creates a new object of type Polygon', () => { + expect(new Polygon()).toEqual(any(Polygon)) + }) + + it('sets passed attributes on the element', () => { + expect(new Polygon({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('Container', () => { + describe('polygon()', () => { + it('creates a polygon with given points', () => { + const group = new G() + const polygon = group.polygon([1, 2, 3, 4]) + expect(polygon.array()).toEqual([ + [1, 2], + [3, 4] + ]) + expect(polygon).toEqual(any(Polygon)) + }) + }) + + it('creates a polygon with one point by default', () => { + const group = new G() + const polygon = group.polygon() + expect(polygon.array()).toEqual([[0, 0]]) + expect(polygon).toEqual(any(Polygon)) + }) + }) +}) diff --git a/spec/spec/elements/Polyline.js b/spec/spec/elements/Polyline.js new file mode 100644 index 000000000..e4a571f1a --- /dev/null +++ b/spec/spec/elements/Polyline.js @@ -0,0 +1,38 @@ +/* globals describe, expect, it, jasmine */ + +import { Polyline, G } from '../../../src/main.js' + +const { any } = jasmine + +describe('Polyline.js', () => { + describe('()', () => { + it('creates a new object of type Polyline', () => { + expect(new Polyline()).toEqual(any(Polyline)) + }) + + it('sets passed attributes on the element', () => { + expect(new Polyline({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('Container', () => { + describe('polyline()', () => { + it('creates a polyline with given points', () => { + const group = new G() + const polyline = group.polyline([1, 2, 3, 4]) + expect(polyline.array()).toEqual([ + [1, 2], + [3, 4] + ]) + expect(polyline).toEqual(any(Polyline)) + }) + + it('creates a polyline with one point by default', () => { + const group = new G() + const polyline = group.polyline() + expect(polyline.array()).toEqual([[0, 0]]) + expect(polyline).toEqual(any(Polyline)) + }) + }) + }) +}) diff --git a/spec/spec/elements/Rect.js b/spec/spec/elements/Rect.js new file mode 100644 index 000000000..20b511780 --- /dev/null +++ b/spec/spec/elements/Rect.js @@ -0,0 +1,31 @@ +/* globals describe, expect, it, jasmine */ + +import { Rect, G } from '../../../src/main.js' + +const { any } = jasmine + +describe('Rect.js', () => { + describe('()', () => { + it('creates a new object of type Rect', () => { + expect(new Rect()).toEqual(any(Rect)) + }) + + it('sets passed attributes on the element', () => { + expect(new Rect({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('Container', () => { + describe('rect()', () => { + it('creates a rect with given size', () => { + const group = new G() + const rect = group.rect(100, 100) + expect(rect.attr(['width', 'height'])).toEqual({ + width: 100, + height: 100 + }) + expect(rect).toEqual(any(Rect)) + }) + }) + }) +}) diff --git a/spec/spec/elements/Shape.js b/spec/spec/elements/Shape.js new file mode 100644 index 000000000..3d1976e14 --- /dev/null +++ b/spec/spec/elements/Shape.js @@ -0,0 +1,17 @@ +/* globals describe, expect, it, jasmine */ + +import { Shape, create } from '../../../src/main.js' + +const { any } = jasmine + +describe('Rect.js', () => { + describe('()', () => { + it('creates a new object of type Shape', () => { + expect(new Shape(create('rect'))).toEqual(any(Shape)) + }) + + it('sets passed attributes on the element', () => { + expect(new Shape(create('rect'), { id: 'foo' }).id()).toBe('foo') + }) + }) +}) diff --git a/spec/spec/elements/Stop.js b/spec/spec/elements/Stop.js new file mode 100644 index 000000000..e58cb2423 --- /dev/null +++ b/spec/spec/elements/Stop.js @@ -0,0 +1,80 @@ +/* globals describe, expect, it, jasmine */ + +import { Stop, Gradient } from '../../../src/main.js' + +const { any } = jasmine + +describe('Stop.js', () => { + describe('()', () => { + it('creates a new object of type Stop', () => { + expect(new Stop()).toEqual(any(Stop)) + }) + + it('sets passed attributes on the element', () => { + expect(new Stop({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('update()', () => { + it('sets offset, color and opacity with 3 arguments given', () => { + const stop = new Stop() + stop.update(0.1, '#ffffff', 0.5) + expect(stop.attr('offset')).toBe(0.1) + expect(stop.attr('stop-color')).toBe('#ffffff') + expect(stop.attr('stop-opacity')).toBe(0.5) + }) + + it('sets offset, color and opacity with object given', () => { + const stop = new Stop() + stop.update({ offset: 0.1, color: '#ffffff', opacity: 0.5 }) + expect(stop.attr('offset')).toBe(0.1) + expect(stop.attr('stop-color')).toBe('#ffffff') + expect(stop.attr('stop-opacity')).toBe(0.5) + }) + + it('sets efault values if not all supplied', () => { + let stop = new Stop() + stop.update({ offset: 0.1 }) + expect(stop.attr('offset')).toBe(0.1) + expect(stop.attr('stop-color')).toBe('#000000') + expect(stop.attr('stop-opacity')).toBe(1) + + stop = new Stop() + stop.update({ color: '#ffffff' }) + expect(stop.attr('offset')).toBe(0) + expect(stop.attr('stop-color')).toBe('#ffffff') + expect(stop.attr('stop-opacity')).toBe(1) + + stop = new Stop() + stop.update({ opacity: 0.5 }) + expect(stop.attr('offset')).toBe(0) + expect(stop.attr('stop-color')).toBe('#000000') + expect(stop.attr('stop-opacity')).toBe(0.5) + }) + }) + + describe('Gradient', () => { + describe('stop()', () => { + it('creates a stop in the gradient with 3 arguments', () => { + const gradient = new Gradient('linear') + const stop = gradient.stop(0.1, '#ffffff', 0.5) + expect(stop).toEqual(any(Stop)) + expect(stop.attr('offset')).toBe(0.1) + expect(stop.attr('stop-color')).toBe('#ffffff') + expect(stop.attr('stop-opacity')).toBe(0.5) + }) + + it('creates stop in the gradient with object given', () => { + const gradient = new Gradient('linear') + const stop = gradient.stop({ + offset: 0.1, + color: '#ffffff', + opacity: 0.5 + }) + expect(stop.attr('offset')).toBe(0.1) + expect(stop.attr('stop-color')).toBe('#ffffff') + expect(stop.attr('stop-opacity')).toBe(0.5) + }) + }) + }) +}) diff --git a/spec/spec/elements/Style.js b/spec/spec/elements/Style.js new file mode 100644 index 000000000..fd587a18b --- /dev/null +++ b/spec/spec/elements/Style.js @@ -0,0 +1,91 @@ +/* globals describe, expect, it, jasmine */ + +import { Style, G } from '../../../src/main.js' + +const { any } = jasmine + +describe('Style.js', () => { + describe('()', () => { + it('creates a new object of type Style', () => { + expect(new Style()).toEqual(any(Style)) + }) + + it('sets passed attributes on the element', () => { + expect(new Style({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('addText()', () => { + it('appends a string to the current textContent and returns itself', () => { + const style = new Style() + expect(style.addText('foo').node.textContent).toBe('foo') + expect(style.addText('bar').node.textContent).toBe('foobar') + expect(style.addText('foobar')).toBe(style) + }) + + it('appends an empty string if nothing passed', () => { + const style = new Style() + expect(style.addText().node.textContent).toBe('') + }) + }) + + describe('font()', () => { + it('adds a font-face rule to load a custom font and returns itself', () => { + const style = new Style() + expect(style.font('fontName', 'url')).toBe(style) + expect(style.node.textContent).toBe( + '@font-face{font-family:fontName;src:url;}' + ) + }) + + it('adds extra parameters if wanted', () => { + const style = new Style() + style.font('fontName', 'url', { foo: 'bar' }) + expect(style.node.textContent).toBe( + '@font-face{font-family:fontName;src:url;foo:bar;}' + ) + }) + }) + + describe('rule()', () => { + it('adds a css rule', () => { + const style = new Style() + expect(style.rule('#id', { fontSize: 15 })).toBe(style) + expect(style.node.textContent).toBe('#id{font-size:15;}') + }) + + it('adds only selector when no obj was given', () => { + const style = new Style() + style.rule('#id') + expect(style.node.textContent).toBe('#id') + }) + + it('adds nothing if no selector was given', () => { + const style = new Style() + style.rule() + expect(style.node.textContent).toBe('') + }) + }) + + describe('Container', () => { + describe('style()', () => { + it('creates a style element in the container and adds a rule', () => { + const g = new G() + const style = g.style('#id', { fontSize: 15 }) + expect(style).toEqual(any(Style)) + expect(style.node.textContent).toBe('#id{font-size:15;}') + }) + }) + + describe('fontface()', () => { + it('creates a style element in the container and adds a font-face rule', () => { + const g = new G() + const style = g.fontface('fontName', 'url', { foo: 'bar' }) + expect(style).toEqual(any(Style)) + expect(style.node.textContent).toBe( + '@font-face{font-family:fontName;src:url;foo:bar;}' + ) + }) + }) + }) +}) diff --git a/spec/spec/elements/Svg.js b/spec/spec/elements/Svg.js new file mode 100644 index 000000000..ebe3b520b --- /dev/null +++ b/spec/spec/elements/Svg.js @@ -0,0 +1,153 @@ +/* globals describe, expect, it, jasmine, container */ + +import { Svg, SVG, Defs } from '../../../src/main.js' +import { svg as ns, xlink } from '../../../src/modules/core/namespaces.js' +import { getWindow } from '../../../src/utils/window.js' + +const { any } = jasmine + +describe('Svg.js', () => { + describe('()', () => { + it('creates a new object of type Svg', () => { + expect(new Svg()).toEqual(any(Svg)) + }) + + it('sets passed attributes on the element', () => { + expect(new Svg({ id: 'foo' }).id()).toBe('foo') + }) + + it('creates namespaces on creation', () => { + const svg = new Svg() + + expect(svg.attr('xmlns')).toBe(ns) + expect(svg.attr('version')).toBe(1.1) + expect(svg.attr('xmlns:xlink')).toBe(xlink) + }) + }) + + describe('defs()', () => { + it('returns the defs if its the root svg', () => { + const svg = new Svg() + const defs = new Defs().addTo(svg) + expect(svg.defs()).toBe(defs) + }) + + it('returns the defs if its not the root svg', () => { + const svg = new Svg() + const defs = new Defs().addTo(svg) + const nested = new Svg().addTo(svg) + expect(nested.defs()).toBe(defs) + }) + + it('creates the defs if not found', () => { + const svg = new SVG() + + expect(svg.findOne('defs')).toBe(null) + + const defs = svg.defs() + + expect(svg.findOne('defs')).toBe(defs) + }) + }) + + describe('namespace()', () => { + it('returns itself', () => { + const svg = SVG('') + expect(svg.namespace()).toBe(svg) + }) + + it('creates the namespace attributes on the svg', () => { + const svg = SVG('') + + expect(svg.attr('xmlns')).toBe(undefined) + + svg.namespace() + + expect(svg.attr('xmlns')).toBe(ns) + expect(svg.attr('version')).toBe(1.1) + expect(svg.attr('xmlns:xlink')).toBe(xlink) + }) + }) + + describe('isRoot()', () => { + it('returns true if svg is the root svg', () => { + const canvas = SVG().addTo(container) + expect(canvas.isRoot()).toBe(true) + }) + + it('returns true if its detached from the dom', () => { + const svg = new Svg() + expect(svg.isRoot()).toBe(true) + }) + + it('returns true if its the root child of the document', () => { + // cannot be tested here + }) + + it('returns false if its the child of a document-fragment', () => { + const fragment = getWindow().document.createDocumentFragment() + const svg = new Svg().addTo(fragment) + expect(svg.isRoot()).toBe(false) + }) + + it('returns false if its a child of another svg element', () => { + const svg = new Svg() + const nested = new Svg().addTo(svg) + expect(nested.isRoot()).toBe(false) + }) + }) + + describe('removeNamespace()', () => { + it('returns itself', () => { + const svg = new Svg() + expect(svg.removeNamespace()).toBe(svg) + }) + + it('removes the namespace attributes from the svg element', () => { + const svg = new Svg() + + expect(svg.attr('xmlns')).toBe(ns) + + svg.removeNamespace() + + expect(svg.attr('xmlns')).toBe(undefined) + expect(svg.attr('version')).toBe(undefined) + expect(svg.attr('xmlns:xlink')).toBe(undefined) + expect(svg.attr('xmlns:svgjs')).toBe(undefined) + }) + }) + + describe('root()', () => { + it('returns itself if its the root svg', () => { + const svg = new Svg() + expect(svg.root()).toBe(svg) + }) + + it('returns the actual root if its not the root svg', () => { + const svg = new Svg() + const nested = new Svg().addTo(svg) + expect(nested.root()).toBe(svg) + }) + }) + + describe('Container', () => { + describe('nested()', () => { + it('creates an svg element in the container', () => { + const svg = new Svg() + const nested = svg.nested() + expect(nested).toEqual(any(Svg)) + expect(nested.parent()).toBe(svg) + }) + + it('has no namespaces set', () => { + const svg = new Svg() + const nested = svg.nested() + + expect(nested.attr('xmlns')).toBe(undefined) + expect(nested.attr('version')).toBe(undefined) + expect(nested.attr('xmlns:xlink')).toBe(undefined) + expect(nested.attr('xmlns:svgjs')).toBe(undefined) + }) + }) + }) +}) diff --git a/spec/spec/elements/Symbol.js b/spec/spec/elements/Symbol.js new file mode 100644 index 000000000..6d9c17eac --- /dev/null +++ b/spec/spec/elements/Symbol.js @@ -0,0 +1,28 @@ +/* globals describe, expect, it, jasmine */ + +import { Symbol, G } from '../../../src/main.js' + +const { any } = jasmine + +describe('Symbol.js', () => { + describe('()', () => { + it('creates a new object of type Symbol', () => { + expect(new Symbol()).toEqual(any(Symbol)) + }) + + it('sets passed attributes on the element', () => { + expect(new Symbol({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('Container', () => { + describe('symbol()', () => { + it('creates a symbol in the container', () => { + const g = new G() + const symbol = g.symbol() + expect(symbol).toEqual(any(Symbol)) + expect(g.children()).toEqual([symbol]) + }) + }) + }) +}) diff --git a/spec/spec/elements/Text.js b/spec/spec/elements/Text.js new file mode 100644 index 000000000..28802a294 --- /dev/null +++ b/spec/spec/elements/Text.js @@ -0,0 +1,188 @@ +/* globals describe, expect, it, spyOn jasmine, container */ + +import { + Text, + Number as SVGNumber, + SVG, + G, + Path, + TextPath, + Svg +} from '../../../src/main.js' + +const { any } = jasmine + +describe('Text.js', () => { + describe('()', () => { + it('creates a new object of type Text', () => { + expect(new Text()).toEqual(any(Text)) + }) + + it('sets passed attributes on the element', () => { + expect(new Text({ id: 'foo' }).id()).toBe('foo') + }) + + it('recovers leading data from dom', () => { + const svg = new Svg().namespace() + svg.text('').leading(3) + const newSvg = SVG(svg.svg()) + expect(newSvg.findOne('text').leading().valueOf()).toBe(3) + }) + }) + + describe('text()', () => { + it('sets the text content of the tspan and returns itself', () => { + const text = new Text() + expect(text.text('Hello World')).toBe(text) + expect(text.node.textContent).toBe('Hello World') + }) + + it('creates tspans for every line', () => { + const text = new Text().text('Hello World\nHow is it\ngoing') + expect(text.children().length).toBe(3) + expect(text.get(0).node.textContent).toBe('Hello World') + expect(text.get(1).node.textContent).toBe('How is it') + expect(text.get(2).node.textContent).toBe('going') + }) + + it('increases dy after empty line', () => { + const canvas = SVG().addTo(container) + const text = canvas.text('Hello World\n\nHow is it\ngoing') + expect(text.children().length).toBe(4) + expect(text.get(0).node.textContent).toBe('Hello World') + expect(text.get(1).node.textContent).toBe('') + expect(text.get(2).node.textContent).toBe('How is it') + expect(text.get(3).node.textContent).toBe('going') + expect(text.get(2).dy()).toBe(text.get(3).dy() * 2) + }) + + it('returns the correct text with newlines', () => { + const text = new Text().text('Hello World\nHow is it\ngoing') + expect(text.text()).toBe('Hello World\nHow is it\ngoing') + }) + + it('returns the correct text with newlines and skips textPaths and descriptive elements', () => { + const path = new Path() + const text = new Text() + const textPath = text.text('Hello World\nHow is it\ngoing').path(path) + textPath.children().addTo(text) + text.add(new TextPath(), 3) + text.add(SVG('MyText')) + + expect(text.text()).toBe('Hello World\nHow is it\ngoing') + }) + + it('executes passed block', () => { + const text = new Text() + text.text(function (t) { + t.tspan('Hello World').newLine() + t.tspan('How is it').newLine() + t.tspan('going').newLine() + expect(this).toBe(text) + expect(t).toBe(text) + }) + expect(text.text()).toBe('Hello World\nHow is it\ngoing') + }) + + it('triggers rebuild', () => { + const text = new Text() + const spy = spyOn(text, 'rebuild') + text.text('foo') + expect(spy).toHaveBeenCalled() + }) + }) + + describe('leading()', () => { + it('returns the leading value of the text without an argument', () => { + const text = new Text() + expect(text.leading() instanceof SVGNumber) + expect(text.leading().valueOf()).toBe(1.3) + }) + + it('sets the leading value of the text with the first argument', () => { + const text = new Text() + expect(text.leading(1.5).dom.leading.valueOf()).toBe(1.5) + }) + }) + + describe('rebuild()', () => { + it('disables the rebuild if called with false', () => { + const text = new Text() + expect(text.rebuild(false)._rebuild).toBeFalse() + }) + + it('enables the rebuild if called with true', () => { + const text = new Text() + expect(text.rebuild(true)._rebuild).toBeTrue() + }) + + it('rebuilds the text without an argument given', () => { + const canvas = SVG().addTo(container) + const text = new Text().addTo(canvas) + text.text((t) => { + t.tspan('Hello World').newLine() + t.tspan('How is it').newLine() + t.tspan('going').newLine() + t.add('My Text') + }) + + const dy = text.get(1).dy() + text.leading(1.7) + expect(dy).not.toBe(text.get(1).dy()) + }) + }) + + describe('setData()', () => { + it('read all data from the svgjs:data attribute and assign it to el.dom', () => { + const text = new Text() + text.attr('svgjs:data', '{"foo":"bar","leading":"3px"}') + text.setData(JSON.parse(text.attr('svgjs:data'))) + + expect(text.dom.foo).toBe('bar') + expect(text.dom.leading instanceof SVGNumber).toBeTruthy() + expect(text.dom.leading.value).toBe(3) + expect(text.dom.leading.unit).toBe('px') + }) + + it('uses a leading of 1.3 when no leading is set or 0', () => { + const text = new Text() + text.setData({ leading: 0 }) + + expect(text.dom.leading.value).toBe(1.3) + }) + }) + + describe('Container', () => { + describe('text()', () => { + it('creates a text element with lines', () => { + const group = new G() + const text = group.text('Hello World\nHow is it\ngoing') + expect(text).toEqual(any(Text)) + expect(text.text()).toBe('Hello World\nHow is it\ngoing') + }) + + it('defaults to empty string', () => { + const group = new G() + const text = group.text() + expect(text).toEqual(any(Text)) + expect(text.text()).toBe('') + }) + }) + + describe('plain()', () => { + it('creates plain text', () => { + const group = new G() + const text = group.plain('A piece') + expect(text).toEqual(any(Text)) + expect(text.node.childNodes[0].data).toBe('A piece') + }) + + it('defaults to empty string', () => { + const group = new G() + const text = group.plain() + expect(text).toEqual(any(Text)) + expect(text.node.childNodes[0].data).toBe('') + }) + }) + }) +}) diff --git a/spec/spec/elements/TextPath.js b/spec/spec/elements/TextPath.js new file mode 100644 index 000000000..5e85bdbc9 --- /dev/null +++ b/spec/spec/elements/TextPath.js @@ -0,0 +1,152 @@ +/* globals describe, expect, it, beforeEach, jasmine, container */ + +import { Text, SVG, TextPath, Path } from '../../../src/main.js' + +const { any } = jasmine + +describe('TextPath.js', () => { + var canvas, text, path + var txt = 'We go up, then we go down, then up again' + var data = + 'M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100' + + beforeEach(() => { + canvas = new SVG().addTo(container) + text = canvas.text(txt) + path = canvas.path(data) + }) + + describe('()', () => { + it('creates a new object of type TextPath', () => { + expect(new TextPath()).toEqual(any(TextPath)) + }) + + it('sets passed attributes on the element', () => { + expect(new TextPath({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('track()', () => { + it('returns the referenced path instance', () => { + const textPath = text.path(path) + expect(textPath.track()).toBe(path) + }) + }) + + describe('array()', () => { + it('returns the path array of the underlying path', () => { + expect(text.path(path).array()).toEqual(path.array()) + }) + + it('returns null if there is no underlying path', () => { + const textPath = new TextPath() + expect(textPath.array()).toBe(null) + }) + }) + + describe('plot()', () => { + it('changes the array of the underlying path', () => { + expect(text.path().plot(path.array()).array()).toEqual(path.array()) + }) + + it('return the path array of the underlying path when no arguments is passed', () => { + const textPath = text.path(path) + expect(textPath.plot()).toBe(textPath.array()) + expect(textPath.plot()).not.toBe(null) + }) + + it('does nothing if no path is attached as track', () => { + const textPath = Object.freeze(new TextPath()) + expect(textPath.plot('M0 0')).toBe(textPath) + }) + }) + + describe('Container', () => { + describe('textPath()', () => { + it('creates a textPath from string text and string path', () => { + const textPath = canvas.textPath(txt, data) + expect(textPath).toEqual(any(TextPath)) + expect(textPath.parent()).toEqual(any(Text)) + expect(textPath.track()).toEqual(any(Path)) + expect(textPath.track().parent()).toBe(canvas.defs()) + }) + + it('creates a textPath from Text and Path', () => { + const textPath = canvas.textPath(text, path) + expect(textPath.parent()).toEqual(text) + expect(textPath.track()).toEqual(path) + }) + + it('passes the text into textPath and not text', () => { + const tspan = text.first() + const textPath = canvas.textPath(text, path) + expect(textPath.first()).toBe(tspan) + expect(text.first()).toBe(textPath) + }) + }) + }) + + describe('Text', () => { + describe('path()', () => { + it('returns an instance of TextPath', () => { + expect(text.path(data)).toEqual(any(TextPath)) + }) + + it('creates a textPath node in the text element', () => { + text.path(data) + expect(text.node.querySelector('textPath')).not.toBe(null) + }) + + it('references the passed path', () => { + const textPath = text.path(path) + expect(textPath.reference('href')).toBe(path) + }) + + it('imports all nodes from the text by default', () => { + const children = text.children() + const textPath = text.path(path) + expect(textPath.children()).toEqual(children) + }) + + it('does not import all nodes from the text when second parameter false', () => { + const textPath = text.path(path, false) + expect(textPath.children()).toEqual([]) + }) + }) + + describe('textPath()', () => { + it('returns the textPath element of this text', () => { + const textPath = text.path(path) + expect(text.textPath()).toBe(textPath) + }) + }) + }) + + describe('Path', () => { + describe('text()', () => { + it('returns an instance of TextPath', () => { + expect(path.text(txt)).toEqual(any(TextPath)) + }) + + it('creates a text with textPath node and inserts it after the path', () => { + var textPath = path.text(txt) + expect(textPath.parent()).toEqual(any(Text)) + expect(SVG(path.node.nextSibling)).toBe(textPath.parent()) + }) + + it('transplants the node from text to textPath', () => { + const nodesInText = [].slice.call(text.node.childNodes) + var textPath = path.text(text) + const nodesInTextPath = [].slice.call(textPath.node.childNodes) + expect(nodesInText).toEqual(nodesInTextPath) + }) + }) + + describe('targets', () => { + it('returns all elements referencing this path with href', () => { + const textPath = text.path(path) + expect(path.targets()).toEqual([textPath]) + }) + }) + }) +}) diff --git a/spec/spec/elements/Tspan.js b/spec/spec/elements/Tspan.js new file mode 100644 index 000000000..879267c73 --- /dev/null +++ b/spec/spec/elements/Tspan.js @@ -0,0 +1,138 @@ +/* globals describe, expect, it, jasmine, container */ + +import { Tspan, Text, Number as SVGNumber, SVG } from '../../../src/main.js' +import { getWindow } from '../../../src/utils/window.js' + +const { any } = jasmine + +describe('Tspan.js', () => { + describe('()', () => { + it('creates a new object of type Tspan', () => { + expect(new Tspan()).toEqual(any(Tspan)) + }) + + it('sets passed attributes on the element', () => { + expect(new Tspan({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('text()', () => { + it('sets the text content of the tspan and returns itself', () => { + const tspan = new Tspan() + expect(tspan.text('Hello World')).toBe(tspan) + expect(tspan.node.textContent).toBe('Hello World') + }) + + it('returns the textContent of the tspan', () => { + const tspan = new Tspan().text('Hello World') + expect(tspan.text()).toBe('Hello World') + }) + + it('adds a newline when this tspan is a newline', () => { + const tspan = new Tspan().text('Hello World').newLine() + expect(tspan.text()).toBe('Hello World\n') + }) + + it('executes a function in the context of the tspan', () => { + const tspan = new Tspan() + tspan.text(function (t) { + expect(this).toBe(tspan) + expect(t).toBe(tspan) + }) + }) + }) + + describe('dx()', () => { + it('sets the dx attribute and returns itself', () => { + const tspan = new Tspan() + expect(tspan.dx(20)).toBe(tspan) + expect(tspan.attr('dx')).toBe(20) + }) + + it('returns the dx attribute', () => { + const tspan = new Tspan().dx(20) + expect(tspan.dx()).toBe(20) + }) + }) + + describe('dy()', () => { + it('sets the dy attribute and returns itself', () => { + const tspan = new Tspan() + expect(tspan.dy(20)).toBe(tspan) + expect(tspan.attr('dy')).toBe(20) + }) + + it('returns the dy attribute', () => { + const tspan = new Tspan().dy(20) + expect(tspan.dy()).toBe(20) + }) + }) + + describe('newLine()', () => { + it('works without text parent', () => { + // should not fail + const tspan = new Tspan().newLine() + expect(tspan.dom.newLined).toBeTrue() + }) + + it('returns itself', () => { + const tspan = new Tspan() + expect(tspan.newLine()).toBe(tspan) + }) + + it('marks the tspan as a newline', () => { + const tspan = new Tspan().wrap(new Text()).newLine() + expect(tspan.dom.newLined).toBeTrue() + }) + + it('sets dy to zero of first line', () => { + const text = new Text() + const first = text.tspan('First Line').newLine() + expect(first.dy()).toBe(0) + }) + + it('sets dy corresponding to line and leading', () => { + const canvas = SVG().addTo(container) + const text = new Text().leading(2).build(true).addTo(canvas) + text.tspan('First Line').newLine() + text.tspan('Second Line').newLine() + const third = text.tspan('Third Line').newLine() + + const fontSize = getWindow() + .getComputedStyle(third.node) + .getPropertyValue('font-size') + const dy = 2 * new SVGNumber(fontSize) + expect(third.dy()).toBe(dy) + }) + }) + + describe('Tspan', () => { + describe('tspan()', () => { + it('creates a tspan in a text', () => { + const text = new Text() + const tspan = text.tspan() + expect(tspan).toEqual(any(Tspan)) + expect(tspan.parent()).toBe(text) + }) + + it('creates a tspan in a tspan', () => { + const tspan1 = new Tspan() + const tspan2 = tspan1.tspan() + expect(tspan2).toEqual(any(Tspan)) + expect(tspan2.parent()).toBe(tspan1) + }) + }) + }) + + describe('Text', () => { + describe('newLine()', () => { + it('creates a tspan and calls newLine() on it', () => { + const text = new Text() + const tspan = text.newLine() + expect(tspan).toEqual(any(Tspan)) + expect(tspan.parent()).toBe(text) + expect(tspan.dom.newLined).toBeTrue() + }) + }) + }) +}) diff --git a/spec/spec/elements/Use.js b/spec/spec/elements/Use.js new file mode 100644 index 000000000..b1b446428 --- /dev/null +++ b/spec/spec/elements/Use.js @@ -0,0 +1,42 @@ +/* globals describe, expect, it, jasmine, container */ + +import { Use, Rect, SVG } from '../../../src/main.js' + +const { any } = jasmine + +describe('Use.js', () => { + describe('()', () => { + it('creates a new object of type Use', () => { + expect(new Use()).toEqual(any(Use)) + }) + + it('sets passed attributes on the element', () => { + expect(new Use({ id: 'foo' }).id()).toBe('foo') + }) + }) + + describe('use()', () => { + it('links an element', () => { + const rect = new Rect() + const use = new Use().use(rect) + expect(use.attr('href')).toBe('#' + rect.id()) + }) + + it('links an element from a different file', () => { + const use = new Use().use('id', 'file') + expect(use.attr('href')).toBe('file#id') + }) + }) + + describe('Container', () => { + describe('use()', () => { + it('creates a use element linked to the given element', () => { + const canvas = new SVG().addTo(container) + const rect = canvas.rect(100, 100) + const use = canvas.use(rect) + expect(use.attr('href')).toBe('#' + rect.id()) + expect(use.reference('href')).toBe(rect) + }) + }) + }) +}) diff --git a/spec/spec/ellipse.js b/spec/spec/ellipse.js deleted file mode 100644 index f6aa2719c..000000000 --- a/spec/spec/ellipse.js +++ /dev/null @@ -1,187 +0,0 @@ -describe('Ellipse', function() { - var ellipse - - beforeEach(function() { - ellipse = draw.ellipse(240,90) - }) - - afterEach(function() { - draw.clear() - }) - - describe('x()', function() { - it('returns the value of x without an argument', function() { - expect(ellipse.x()).toBe(0) - }) - it('sets the value of x with the first argument', function() { - ellipse.x(123) - var box = ellipse.bbox() - expect(box.x).toBeCloseTo(123) - }) - }) - - describe('y()', function() { - it('returns the value of y without an argument', function() { - expect(ellipse.y()).toBe(0) - }) - it('sets the value of cy with the first argument', function() { - ellipse.y(345) - var box = ellipse.bbox() - expect(box.y).toBe(345) - }) - }) - - describe('cx()', function() { - it('returns the value of cx without an argument', function() { - expect(ellipse.cx()).toBe(120) - }) - it('sets the value of cx with the first argument', function() { - ellipse.cx(123) - var box = ellipse.bbox() - expect(box.cx).toBe(123) - }) - }) - - describe('cy()', function() { - it('returns the value of cy without an argument', function() { - expect(ellipse.cy()).toBe(45) - }) - it('sets the value of cy with the first argument', function() { - ellipse.cy(345) - var box = ellipse.bbox() - expect(box.cy).toBe(345) - }) - }) - - describe('radius()', function() { - it('sets the rx and ry', function() { - ellipse.radius(10, 20) - expect(ellipse.node.getAttribute('rx')).toBe('10') - expect(ellipse.node.getAttribute('ry')).toBe('20') - }) - it('sets the rx and ry if only rx given', function() { - ellipse.radius(30) - expect(ellipse.node.getAttribute('rx')).toBe('30') - expect(ellipse.node.getAttribute('ry')).toBe('30') - }) - it('sets the rx and ry value correctly when given 0', function() { - ellipse.radius(11, 0) - expect(ellipse.node.getAttribute('rx')).toBe('11') - expect(ellipse.node.getAttribute('ry')).toBe('0') - }) - }) - - describe('move()', function() { - it('sets the x and y position', function() { - ellipse.move(123, 456) - var box = ellipse.bbox() - expect(box.x).toBeCloseTo(123) - expect(box.y).toBeCloseTo(456) - }) - }) - - describe('dx()', function() { - it('moves the x positon of the element relative to the current position', function() { - ellipse.move(50, 60) - ellipse.dx(100) - expect(ellipse.node.getAttribute('cx')).toBe('270') - }) - }) - - describe('dy()', function() { - it('moves the y positon of the element relative to the current position', function() { - ellipse.move(50, 60) - ellipse.dy(120) - expect(ellipse.node.getAttribute('cy')).toBe('225') - }) - }) - - describe('dmove()', function() { - it('moves the x and y positon of the element relative to the current position', function() { - ellipse.move(50,60) - ellipse.dmove(80, 25) - expect(ellipse.node.getAttribute('cx')).toBe('250') - expect(ellipse.node.getAttribute('cy')).toBe('130') - }) - }) - - describe('center()', function() { - it('sets the cx and cy position', function() { - ellipse.center(321,567) - var box = ellipse.bbox() - expect(box.cx).toBe(321) - expect(box.cy).toBe(567) - }) - }) - - describe('width()', function() { - it('sets the width of the element', function() { - ellipse.width(82) - expect(ellipse.node.getAttribute('rx')).toBe('41') - }) - it('gets the width of the element if the argument is null', function() { - expect((ellipse.width() / 2).toString()).toBe(ellipse.node.getAttribute('rx')) - }) - }) - - describe('height()', function() { - it('sets the height of the element', function() { - ellipse.height(1236) - expect(ellipse.node.getAttribute('ry')).toBe('618') - }) - it('gets the height of the element if the argument is null', function() { - expect((ellipse.height() / 2).toString()).toBe(ellipse.node.getAttribute('ry')) - }) - }) - - describe('size()', function() { - it('defines the rx and ry of the element', function() { - ellipse.size(987,654) - expect(ellipse.node.getAttribute('rx')).toBe((987 / 2).toString()) - expect(ellipse.node.getAttribute('ry')).toBe((654 / 2).toString()) - }) - it('defines the width and height proportionally with only the width value given', function() { - var box = ellipse.bbox() - ellipse.size(500) - expect(ellipse.width()).toBe(500) - expect(ellipse.width() / ellipse.height()).toBe(box.width / box.height) - }) - it('defines the width and height proportionally with only the height value given', function() { - var box = ellipse.bbox() - ellipse.size(null, 525) - expect(ellipse.height()).toBe(525) - expect(ellipse.width() / ellipse.height()).toBe(box.width / box.height) - }) - }) - - describe('scale()', function() { - it('should scale the element universally with one argument', function() { - var box = ellipse.scale(2).rbox() - - expect(box.width).toBe(ellipse.attr('rx') * 2 * 2) - expect(box.height).toBe(ellipse.attr('ry') * 2 * 2) - }) - it('should scale the element over individual x and y axes with two arguments', function() { - var box = ellipse.scale(2, 3.5).rbox() - - expect(box.width).toBe(ellipse.attr('rx') * 2 * 2) - expect(box.height).toBe(ellipse.attr('ry') * 2 * 3.5) - }) - }) - - describe('translate()', function() { - it('sets the translation of an element', function() { - ellipse.transform({ x: 12, y: 12 }) - expect(ellipse.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') - }) - }) - -}) - - - - - - - - diff --git a/spec/spec/event.js b/spec/spec/event.js deleted file mode 100644 index 0b9fe5b5f..000000000 --- a/spec/spec/event.js +++ /dev/null @@ -1,495 +0,0 @@ -describe('Event', function() { - var rect, context - , toast = null - , fruitsInDetail = null, - action = function(e) { - toast = 'ready' - context = this - fruitsInDetail = e.detail || null - } - - beforeEach(function() { - rect = draw.rect(100, 100) - spyOn(SVG,'on').and.callThrough() - }) - - afterEach(function() { - toast = context = null - }) - - if (!this.isTouchDevice) { - - describe('click()', function() { - it('attaches an onclick event to the node of the element', function() { - rect.click(action) - expect(SVG.on).toHaveBeenCalledWith(rect.node, 'click', action) - }) - it('fires the event on click', function() { - rect.click(action).fire('click') - expect(toast).toBe('ready') - }) - it('applies the element as context', function() { - rect.click(action).fire('click') - expect(context).toBe(rect) - }) - it('returns the called element', function() { - expect(rect.click(action)).toBe(rect) - }) - }) - - describe('dblclick()', function() { - it('attaches an ondblclick event to the node of the element', function() { - rect.dblclick(action) - expect(SVG.on).toHaveBeenCalledWith(rect.node, 'dblclick', action) - }) - it('fires the event on dblclick', function() { - rect.dblclick(action).fire('dblclick') - expect(toast).toBe('ready') - }) - it('applies the element as context', function() { - rect.dblclick(action).fire('dblclick') - expect(context).toBe(rect) - }) - it('returns the called element', function() { - expect(rect.dblclick(action)).toBe(rect) - }) - }) - - describe('mousedown()', function() { - it('attaches an onmousedown event to the node of the element', function() { - rect.mousedown(action) - expect(SVG.on).toHaveBeenCalledWith(rect.node, 'mousedown', action) - }) - it('fires the event on mousedown', function() { - rect.mousedown(action).fire('mousedown') - expect(toast).toBe('ready') - }) - it('applies the element as context', function() { - rect.mousedown(action).fire('mousedown') - expect(context).toBe(rect) - }) - it('returns the called element', function() { - expect(rect.mousedown(action)).toBe(rect) - }) - }) - - describe('mouseup()', function() { - it('attaches an onmouseup event to the node of the element', function() { - rect.mouseup(action) - expect(SVG.on).toHaveBeenCalledWith(rect.node, 'mouseup', action) - }) - it('fires the event on mouseup', function() { - rect.mouseup(action).fire('mouseup') - expect(toast).toBe('ready') - }) - it('applies the element as context', function() { - rect.mouseup(action).fire('mouseup') - expect(context).toBe(rect) - }) - it('returns the called element', function() { - expect(rect.mouseup(action)).toBe(rect) - }) - }) - - describe('mouseover()', function() { - it('attaches an onmouseover event to the node of the element', function() { - rect.mouseover(action) - expect(SVG.on).toHaveBeenCalledWith(rect.node, 'mouseover', action) - }) - it('fires the event on mouseover', function() { - rect.mouseover(action).fire('mouseover') - expect(toast).toBe('ready') - }) - it('applies the element as context', function() { - rect.mouseover(action).fire('mouseover') - expect(context).toBe(rect) - }) - it('returns the called element', function() { - expect(rect.mouseover(action)).toBe(rect) - }) - }) - - describe('mouseout()', function() { - it('attaches an onmouseout event to the node of the element', function() { - rect.mouseout(action) - expect(SVG.on).toHaveBeenCalledWith(rect.node, 'mouseout', action) - }) - it('fires the event on mouseout', function() { - rect.mouseout(action).fire('mouseout') - expect(toast).toBe('ready') - }) - it('applies the element as context', function() { - rect.mouseout(action).fire('mouseout') - expect(context).toBe(rect) - }) - it('returns the called element', function() { - expect(rect.mouseout(action)).toBe(rect) - }) - }) - - describe('mousemove()', function() { - it('attaches an onmousemove event to the node of the element', function() { - rect.mousemove(action) - expect(SVG.on).toHaveBeenCalledWith(rect.node, 'mousemove', action) - }) - it('fires the event on mousemove', function() { - rect.mousemove(action).fire('mousemove') - expect(toast).toBe('ready') - }) - it('applies the element as context', function() { - rect.mousemove(action).fire('mousemove') - expect(context).toBe(rect) - }) - it('returns the called element', function() { - expect(rect.mousemove(action)).toBe(rect) - }) - }) - - /*describe('mouseenter()', function() { - it('attaches an onmouseenter event to the node of the element', function() { - expect(typeof rect.node.onmouseenter).not.toBe('function') - rect.mouseenter(action) - expect(typeof rect.node.onmouseenter).toBe('function') - }) - it('fires the event on mouseenter', function() { - rect.mouseenter(action).fire('mouseenter') - expect(toast).toBe('ready') - }) - it('applies the element as context', function() { - rect.mouseenter(action).fire('mouseenter') - expect(context).toBe(rect) - }) - it('returns the called element', function() { - expect(rect.mouseenter(action)).toBe(rect) - }) - }) - - describe('mouseleave()', function() { - it('attaches an onmouseleave event to the node of the element', function() { - expect(typeof rect.node.onmouseleave).not.toBe('function') - rect.mouseleave(action) - expect(typeof rect.node.onmouseleave).toBe('function') - }) - it('fires the event on mouseleave', function() { - rect.mouseleave(action).fire('mouseleave') - expect(toast).toBe('ready') - }) - it('applies the element as context', function() { - rect.mouseleave(action).fire('mouseleave') - expect(context).toBe(rect) - }) - it('returns the called element', function() { - expect(rect.mouseleave(action)).toBe(rect) - }) - })*/ - - } else { - - describe('touchstart()', function() { - it('attaches an ontouchstart event to the node of the element', function() { - rect.touchstart(action) - expect(SVG.on).toHaveBeenCalledWith(rect.node, 'touchstart', action) - }) - it('fires the event on touchstart', function() { - rect.touchstart(action).fire('touchstart') - expect(toast).toBe('ready') - }) - it('applies the element as context', function() { - rect.touchstart(action).fire('touchstart') - expect(context).toBe(rect) - }) - it('returns the called element', function() { - expect(rect.touchstart(action)).toBe(rect) - }) - }) - - describe('touchmove()', function() { - it('attaches an ontouchmove event to the node of the element', function() { - rect.touchmove(action) - expect(SVG.on).toHaveBeenCalledWith(rect.node, 'touchmove', action) - }) - it('fires the event on touchmove', function() { - rect.touchmove(action).fire('touchmove') - expect(toast).toBe('ready') - }) - it('applies the element as context', function() { - rect.touchmove(action).fire('touchmove') - expect(context).toBe(rect) - }) - it('returns the called element', function() { - expect(rect.touchmove(action)).toBe(rect) - }) - }) - - describe('touchleave()', function() { - it('attaches an ontouchleave event to the node of the element', function() { - rect.touchleave(action) - expect(SVG.on).toHaveBeenCalledWith(rect.node, 'touchleave', action) - }) - it('fires the event on touchleave', function() { - rect.touchleave(action).fire('touchleave') - expect(toast).toBe('ready') - }) - it('applies the element as context', function() { - rect.touchleave(action).fire('touchleave') - expect(context).toBe(rect) - }) - it('returns the called element', function() { - expect(rect.touchleave(action)).toBe(rect) - }) - }) - - describe('touchend()', function() { - it('attaches an ontouchend event to the node of the element', function() { - rect.touchend(action) - expect(SVG.on).toHaveBeenCalledWith(rect.node, 'touchend', action) - }) - it('fires the event on touchend', function() { - rect.touchend(action).fire('touchend') - expect(toast).toBe('ready') - }) - it('applies the element as context', function() { - rect.touchend(action).fire('touchend') - expect(context).toBe(rect) - }) - it('returns the called element', function() { - expect(rect.touchend(action)).toBe(rect) - }) - }) - - describe('touchcancel()', function() { - it('attaches an ontouchcancel event to the node of the element', function() { - rect.touchcancel(action) - expect(SVG.on).toHaveBeenCalledWith(rect.node, 'touchcancel', action) - }) - it('fires the event on touchcancel', function() { - rect.touchcancel(action).fire('touchcancel') - expect(toast).toBe('ready') - }) - it('applies the element as context', function() { - rect.touchcancel(action).fire('touchcancel') - expect(context).toBe(rect) - }) - it('returns the called element', function() { - expect(rect.touchcancel(action)).toBe(rect) - }) - }) - - } - - - describe('on()', function() { - - it('attaches an event to the element', function() { - rect.on('event', action).fire('event') - expect(toast).toBe('ready') - }) - it('attaches an event to a non svg element', function() { - var el = document.createElement('div') - SVG.on(el, 'event', action) - el.dispatchEvent(new window.CustomEvent('event')) - expect(toast).toBe('ready') - SVG.off(el, 'event', action) - }) - it('attaches multiple handlers on different element', function() { - var listenerCnt = SVG.listeners.length - - var rect2 = draw.rect(100,100); - var rect3 = draw.rect(100,100); - - rect.on('event', action) - rect2.on('event', action) - rect3.on('event', function(){ butter = 'melting' }) - rect3.on('event', action) - - expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['*']).length).toBe(1) // 1 listener on rect - expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect2.node)]['event']['*']).length).toBe(1) // 1 listener on rect2 - expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect3.node)]['event']['*']).length).toBe(2) // 2 listener on rect3 - - expect(SVG.listeners.length).toBe(listenerCnt + 3) // added listeners on 3 different elements - }) - if('attaches a handler to a namespaced event', function(){ - var listenerCnt = SVG.listeners.length - - var rect2 = draw.rect(100,100); - var rect3 = draw.rect(100,100); - - rect.on('event.namespace1', action) - rect2.on('event.namespace2', action) - rect3.on('event.namespace3', function(){ butter = 'melting' }) - rect3.on('event', action) - - expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['*'])).toBeUndefined() // no global listener on rect - expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['namespace1']).length).toBe( 1) // 1 namespaced listener on rect - expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect2.node)]['event']['namespace2']).length).toBe(1) // 1 namespaced listener on rect - expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect3.node)]['event']['*']).length).toBe(1) // 1 gobal listener on rect3 - expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect3.node)]['event']['namespace3']).length).toBe(1) // 1 namespaced listener on rect3 - expect(SVG.listeners.length).toBe(listenerCnt + 3) // added listeners on 3 different elements - }) - it('applies the element as context', function() { - rect.on('event', action).fire('event') - expect(context).toBe(rect) - }) - it('applies given object as context', function() { - rect.on('event', action, this).fire('event') - expect(context).toBe(this) - }) - it('stores the listener for future reference', function() { - rect.on('event', action) - expect(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['*'][action._svgjsListenerId]).not.toBeUndefined() - }) - it('returns the called element', function() { - expect(rect.on('event', action)).toBe(rect) - }) - }) - - describe('off()', function() { - var butter = null - - beforeEach(function() { - butter = null - }) - - it('detaches a specific event listener, all other still working', function() { - rect2 = draw.rect(100,100); - rect3 = draw.rect(100,100); - - rect.on('event', action) - rect2.on('event', action) - rect3.on('event', function(){ butter = 'melting' }) - - rect.off('event', action) - - expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['*']).length).toBe(0) - - rect.fire('event') - expect(toast).toBeNull() - - rect2.fire('event') - expect(toast).toBe('ready') - - rect3.fire('event') - expect(butter).toBe('melting') - - expect(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['*'][action]).toBeUndefined() - }) - it('detaches a specific namespaced event listener, all other still working', function() { - rect2 = draw.rect(100,100); - rect3 = draw.rect(100,100); - - rect.on('event.namespace', action) - rect2.on('event.namespace', action) - rect3.on('event.namespace', function(){ butter = 'melting' }) - - rect.off('event.namespace', action) - - expect(Object.keys(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['namespace']).length).toBe(0) - - rect.fire('event') - expect(toast).toBeNull() - - rect2.fire('event') - expect(toast).toBe('ready') - - rect3.fire('event') - expect(butter).toBe('melting') - - expect(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']['namespace'][action]).toBeUndefined() - }) - it('detaches all listeners for a specific namespace', function() { - rect.on('event', action) - rect.on('event.namespace', function() { butter = 'melting'; }) - rect.off('.namespace') - - rect.fire('event') - expect(toast).toBe('ready') - expect(butter).toBeNull() - }) - it('detaches all listeners for an event without a listener given', function() { - rect.on('event', action) - rect.on('event.namespace', function() { butter = 'melting'; }) - rect.off('event') - - rect.fire('event') - expect(toast).toBeNull() - expect(butter).toBeNull() - expect(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]['event']).toBeUndefined() - }) - it('detaches all listeners without an argument', function() { - rect.on('event', action) - rect.on('click', function() { butter = 'melting' }) - rect.off() - rect.fire('event') - rect.fire('click') - expect(toast).toBeNull() - expect(butter).toBeNull() - expect(SVG.listeners[SVG.handlerMap.indexOf(rect.node)]).toBeUndefined() - }) - it('returns the called element', function() { - expect(rect.off('event', action)).toBe(rect) - }) - it('does not throw when event is removed which was already removed with a global off', function() { - var undefined - - rect.on('event', action) - rect.off() - try{ - rect.off('event') - }catch(e){ - expect('Should not error out').toBe(true) - } - - expect(SVG.handlerMap[SVG.handlerMap.indexOf(rect.node)]).toBe(undefined) - }) - }) - - describe('fire()', function() { - - beforeEach(function() { - rect.on('event', action) - }) - - it('fires an event for the element', function() { - expect(toast).toBeNull() - rect.fire('event') - expect(toast).toBe('ready') - expect(fruitsInDetail).toBe(null) - }) - it('returns the called element', function() { - expect(rect.fire('event')).toBe(rect) - }) - it('fires event with additional data', function() { - expect(fruitsInDetail).toBeNull() - rect.fire('event', {apple:1}) - expect(fruitsInDetail).not.toBe(null) - expect(fruitsInDetail.apple).toBe(1) - }) - it('fires my own event', function() { - toast = null - rect.fire(new window.CustomEvent('event')) - expect(toast).toBe('ready') - }) - it('makes the event cancelable', function() { - rect.on('event', function(e) { - e.preventDefault() - }) - rect.fire('event') - expect(rect._event.defaultPrevented).toBe(true) - }) - }) - - describe('event()', function() { - it('returns null when no event was fired', function() { - expect(rect.event()).toBe(null) - }) - it('returns the last fired event', function() { - var event = new window.CustomEvent('foo') - rect.fire(event) - expect(rect.event()).toBe(event) - - event = new window.CustomEvent('bar') - rect.fire(event) - expect(rect.event()).toBe(event) - }) - }) -}) diff --git a/spec/spec/fx.js b/spec/spec/fx.js deleted file mode 100644 index b1f009164..000000000 --- a/spec/spec/fx.js +++ /dev/null @@ -1,2864 +0,0 @@ -describe('FX', function() { - var rect, fx, undefined; - - beforeEach(function() { - rect = draw.rect(100,100).move(100,100) - fx = rect.animate(500) - - jasmine.clock().install() - jasmine.clock().mockDate() // This freeze the Date - }) - - afterEach(function() { - jasmine.clock().uninstall() - - fx.stop(false, true) - }) - - - it('creates an instance of SVG.FX and sets parameter', function() { - expect(fx instanceof SVG.FX).toBe(true) - expect(fx._target).toBe(rect) - expect(fx.absPos).toBe(0) - expect(fx.pos).toBe(0) - expect(fx.lastPos).toBe(0) - expect(fx.paused).toBe(false) - expect(fx.active).toBe(false) - expect(fx._speed).toBe(1) - expect(fx.situations).toEqual([]) - expect(fx.situation.init).toBe(false) - expect(fx.situation.reversed).toBe(false) - expect(fx.situation.duration).toBe(500) - expect(fx.situation.delay).toBe(0) - expect(fx.situation.loops).toBe(false) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.animations).toEqual({}) - expect(fx.situation.attrs).toEqual({}) - expect(fx.situation.styles).toEqual({}) - expect(fx.situation.transforms).toEqual([]) - expect(fx.situation.once).toEqual({}) - }) - - describe('animate()', function () { - it('set duration, ease and delay of the new situation to their default value when they are not passed', function() { - var defaultDuration = 1000 - , defaultEase = SVG.easing['-'] - , defaultDelay = 0 - , lastSituation = fx.animate().last() - - expect(lastSituation.duration).toBe(defaultDuration) - expect(lastSituation.ease).toBe(defaultEase) - expect(lastSituation.delay).toBe(defaultDelay) - }) - - it('use the passed values to set duration, ease and delay of the new situation', function() { - var duration = 14502 - , ease = '>' - , delay = 450 - , lastSituation = fx.animate(duration, ease, delay).last() - - expect(lastSituation.duration).toBe(duration) - expect(lastSituation.ease).toBe(SVG.easing[ease]) - expect(lastSituation.delay).toBe(delay) - }) - - it('allow duration, ease and delay to be passed in an object', function() { - var o = { - duration: 7892 - , ease: '<' - , delay: 1145 - } - , lastSituation = fx.animate(o).last() - - expect(lastSituation.duration).toBe(o.duration) - expect(lastSituation.ease).toBe(SVG.easing[o.ease]) - expect(lastSituation.delay).toBe(o.delay) - }) - - it('allow ease to be a custom function', function () { - var customEase = function() {} - , lastSituation = fx.animate({ease: customEase}).last() - - expect(lastSituation.ease).toBe(customEase) - }) - }) - - describe('target()', function(){ - it('returns the current fx object with no argument given', function(){ - expect(fx.target()).toBe(rect) - }) - - it('changes the target of the animation when parameter given', function(){ - var c = draw.circle(5) - expect(fx.target(c).target()).toBe(c) - }) - }) - - - describe('timeToAbsPos()', function() { - it('converts a timestamp to an absolute progress', function() { - expect(fx.timeToAbsPos( fx.situation.start + fx.situation.duration*0.5 )).toBe(0.5) - }) - - it('should take speed into consideration', function() { - var spd - - spd = 4 - fx.speed(spd) - expect(fx.timeToAbsPos( fx.situation.start + (fx.situation.duration/spd)*0.5 )).toBe(0.5) - - spd = 0.5 - fx.speed(spd) - expect(fx.timeToAbsPos( fx.situation.start + (fx.situation.duration/spd)*0.25 )).toBe(0.25) - }) - }) - - - describe('absPosToTime()', function() { - it('converts an absolute progress to a timestamp', function() { - expect(fx.absPosToTime(0.5)).toBe( fx.situation.start + fx.situation.duration*0.5 ) - }) - - it('should take speed into consideration', function() { - var spd - - spd = 4 - fx.speed(spd) - expect(fx.absPosToTime(0.5)).toBe( fx.situation.start + (fx.situation.duration/spd)*0.5 ) - - spd = 0.5 - fx.speed(spd) - expect(fx.absPosToTime(0.25)).toBe( fx.situation.start + (fx.situation.duration/spd)*0.25 ) - }) - }) - - - describe('atStart()', function () { - it('sets the animation at the start', function() { - // When the animation is running forward, the start position is 0 - fx.pos = 0.5 - expect(fx.atStart().pos).toBe(0) - - // When the animation is running backward, the start position is 1 - fx.pos = 0.5 - expect(fx.reverse(true).atStart().pos).toBe(1) - }) - - it('sets the animation at the start, before any loops', function() { - fx.loop(true) - - // When the animation is running forward, the start position is 0 - fx.at(3.7, true) - expect(fx.absPos).toBe(3.7) - expect(fx.pos).toBeCloseTo(0.7) - expect(fx.situation.loop).toBe(3) - - fx.atStart() - expect(fx.absPos).toBe(0) - expect(fx.pos).toBe(0) - expect(fx.situation.loop).toBe(0) - - // When the animation is running backward, the start position is 1 - fx.reverse(true).at(2.14, true) - expect(fx.absPos).toBe(2.14) - expect(fx.pos).toBeCloseTo(1 - 0.14) - expect(fx.situation.loop).toBe(2) - expect(fx.situation.reversed).toBe(true) - - fx.atStart() - expect(fx.absPos).toBe(0) - expect(fx.pos).toBe(1) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.reversed).toBe(true) - }) - - it('sets the animation at the start, before any loops when reversing is true', function() { - fx.loop(true, true) // Set reversing to true - - // When the animation is running forward, the start position is 0 - fx.at(11.21, true) - expect(fx.absPos).toBe(11.21) - expect(fx.pos).toBeCloseTo(1 - 0.21) - expect(fx.situation.loop).toBe(11) - expect(fx.situation.reversed).toBe(true) - - fx.atStart() - expect(fx.absPos).toBe(0) - expect(fx.pos).toBe(0) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.reversed).toBe(false) - - // When the animation is running backward, the start position is 1 - fx.reverse(true).at(14.10, true) - expect(fx.absPos).toBe(14.10) - expect(fx.pos).toBeCloseTo(1 - 0.10) - expect(fx.situation.loop).toBe(14) - expect(fx.situation.reversed).toBe(true) - - fx.atStart() - expect(fx.absPos).toBe(0) - expect(fx.pos).toBe(1) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.reversed).toBe(true) - }) - }) - - - describe('atEnd()', function () { - it('sets the animation at the end', function() { - // When the animation is running forward, the end position is 1 - fx.pos = 0.5 - expect(fx.atEnd().pos).toBe(1) - expect(fx.situation).toBeNull() - - // Recreate an animation since the other one was ended - fx.animate() - - // When the animation is running backward, the end position is 0 - fx.pos = 0.5 - expect(fx.reverse(true).atEnd().pos).toBe(0) - expect(fx.situation).toBeNull() - }) - - it('sets the animation at the end, after all loops', function() { - var loops - - // When the animation is running forward, the end position is 1 - loops = 12 - fx.loop(loops).start().step() - expect(fx.absPos).toBe(0) - expect(fx.pos).toBe(0) - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(loops) - - fx.atEnd() - expect(fx.absPos).toBe(loops) - expect(fx.pos).toBe(1) - expect(fx.active).toBe(false) - expect(fx.situation).toBeNull() - - // Recreate an animation since the other one was ended - fx.animate() - - - // When the animation is running backward, the end position is 0 - loops = 21 - fx.reverse(true).loop(loops).start().step() - expect(fx.absPos).toBe(0) - expect(fx.pos).toBe(1) - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(loops) - expect(fx.situation.reversed).toBe(true) - - fx.atEnd() - expect(fx.absPos).toBe(loops) - expect(fx.pos).toBe(0) - expect(fx.active).toBe(false) - expect(fx.situation).toBeNull() - }) - - it('sets the animation at the end, after all loops when reversing is true', function() { - var loops - - // When reversing is true, the end position is 0 when loops is even and - // 1 when loops is odd - - // The animation is running forward - loops = 6 - fx.loop(loops, true).start().step() - expect(fx.absPos).toBe(0) - expect(fx.pos).toBe(0) - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(loops) - expect(fx.situation.reversed).toBe(false) - - fx.atEnd() - expect(fx.absPos).toBe(loops) - expect(fx.pos).toBe(0) // End position is 0 because loops is even - expect(fx.active).toBe(false) - expect(fx.situation).toBeNull() - - // Recreate an animation since the other one was ended - fx.animate() - - // When reversing is true and the animation is running backward, - // the end position is 1 when loops is even and 0 when loops is odd - - // The animation is running backward - loops = 3 - fx.reverse(true).loop(loops, true).start().step() - expect(fx.absPos).toBe(0) - expect(fx.pos).toBe(1) - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(loops) - expect(fx.situation.reversed).toBe(true) - - fx.atEnd() - expect(fx.absPos).toBe(loops) - expect(fx.pos).toBe(0) // End position is 0 because loops is odd - expect(fx.active).toBe(false) - expect(fx.situation).toBeNull() - }) - - it('sets the animation at the end of the current iteration when in an infinite loop', function () { - // When the animation is running forward, the end position is 1 - fx.loop(true).start().step() - expect(fx.absPos).toBe(0) - expect(fx.pos).toBe(0) - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(true) - - // Should be halfway through iteration 10 - jasmine.clock().tick(500 * 10 + 250) - fx.step() - expect(fx.absPos).toBe(10.5) - expect(fx.pos).toBe(0.5) - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(10) - expect(fx.situation.loops).toBe(true) - - fx.atEnd() - expect(fx.absPos).toBe(11) - expect(fx.pos).toBe(1) - expect(fx.active).toBe(false) - expect(fx.situation).toBeNull() - - // Recreate an animation since the other one was ended - fx.animate(500) - - // When the animation is running backward, the end position is 0 - fx.reverse(true).loop(true).start().step() - expect(fx.absPos).toBe(0) - expect(fx.pos).toBe(1) - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(true) - expect(fx.situation.reversed).toBe(true) - - // Should be halfway through iteration 21 - jasmine.clock().tick(500 * 21 + 250) - fx.step() - expect(fx.absPos).toBe(21.5) - expect(fx.pos).toBe(0.5) - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(21) - expect(fx.situation.loops).toBe(true) - - fx.atEnd() - expect(fx.absPos).toBe(22) - expect(fx.pos).toBe(0) - expect(fx.active).toBe(false) - expect(fx.situation).toBeNull() - }) - - - it('sets the animation at the end of the current iteration when in an infinite loop and reversing is true', function () { - // When reversing is true, the end position is 1 when ending on an even - // iteration and 0 when ending on an odd iteration as illustrated below: - - // 0 Iteration 1 - // |--------------0------------->| - // |<-------------1--------------| - // |--------------2------------->| - // |<-------------3--------------| - // ... - - - // The animation is running forward - fx.loop(true, true).start().step() - expect(fx.absPos).toBe(0) - expect(fx.pos).toBe(0) - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(true) - - // Should be halfway through iteration 11 - jasmine.clock().tick(500 * 11 + 250) - fx.step() - expect(fx.absPos).toBe(11.5) - expect(fx.pos).toBe(0.5) - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(11) - expect(fx.situation.loops).toBe(true) - - fx.atEnd() - expect(fx.absPos).toBe(12) - expect(fx.pos).toBe(0) // End position is 0 because ended on a odd iteration - expect(fx.active).toBe(false) - expect(fx.situation).toBeNull() - - // Recreate an animation since the other one was ended - fx.animate(500) - - // When reversing is true and the animation is running backward, - // the end position is 0 when ending on an even iteration and - // 1 when ending on an odd iteration as illustrated below: - - // 0 Iteration 1 - // |<-------------0--------------| - // |--------------1------------->| - // |<-------------2--------------| - // |--------------3------------->| - // ... - - // The animation is running backward - fx.reverse(true).loop(true).start().step() - expect(fx.absPos).toBe(0) - expect(fx.pos).toBe(1) - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(true) - expect(fx.situation.reversed).toBe(true) - - // Should be halfway through iteration 42 - jasmine.clock().tick(500 * 42 + 250) - fx.step() - expect(fx.absPos).toBe(42.5) - expect(fx.pos).toBe(0.5) - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(42) - expect(fx.situation.loops).toBe(true) - - fx.atEnd() - expect(fx.absPos).toBe(43) - expect(fx.pos).toBe(0) // End position is 0 because ended on an even iteration - expect(fx.active).toBe(false) - expect(fx.situation).toBeNull() - }) - }) - - - describe('at()', function() { - it('sets the progress to the specified position', function() { - var pos - - // Animation running forward - pos = 0.5 - expect(fx.at(pos).pos).toBe(pos) - expect(fx.situation.start).toBe(+new Date - fx.situation.duration * pos) - - // Animation running backward - pos = 0.4 - expect(fx.reverse(true).at(pos).pos).toBe(pos) - expect(fx.situation.start).toBe(+new Date - fx.situation.duration * (1-pos)) - }) - - it('should convert a position to an absolute position', function () { - var pos, loop, absPos - - fx.loop(true) - - // Animation running forward - pos = 0.7 - loop = 4 - absPos = pos+loop - fx.situation.loop = loop - expect(fx.at(pos).absPos).toBe(absPos) - expect(fx.situation.start).toBe(+new Date - fx.situation.duration * absPos) - - // Animation running backward - pos = 0.23 - loop = 9 - absPos = (1-pos)+loop - fx.situation.loop = loop - fx.situation.reversed = true - expect(fx.at(pos).absPos).toBe(absPos) - expect(fx.situation.start).toBe(+new Date - fx.situation.duration * absPos) - - }) - - it('should end the animation when the end position is passed', function() { - var pos - - fx.start() - expect(fx.active).toBe(true) - expect(fx.situation).not.toBeNull() - - // When running forward, the end position is 1 - pos = 1 - expect(fx.at(pos).pos).toBe(pos) - expect(fx.active).toBe(false) - expect(fx.situation).toBeNull() - - // Recreate an animation since the other one was ended - fx.animate().start() - expect(fx.active).toBe(true) - expect(fx.situation).not.toBeNull() - - // When running backward, the end position is 0 - pos = 0 - expect(fx.reverse(true).at(pos).pos).toBe(pos) - expect(fx.active).toBe(false) - expect(fx.situation).toBeNull() - }) - - it('correct the passed position when it is out of [0,1] and the animation is not looping', function () { - var pos - - pos = -0.7 - expect(fx.at(pos).pos).toBe(0) - - pos = 1.3 - expect(fx.at(pos).pos).toBe(1) - - // Recreate an animation since the other one was ended - fx.animate() - - // Should work even when animation is running backward - pos = 1.3 - expect(fx.reverse(true).at(pos).pos).toBe(1) - - pos = -0.7 - expect(fx.reverse(true).at(pos).pos).toBe(0) - }) - - it('should, when the animation is looping and the passed position is out of [0,1], use the integer part of postion to update the loop counter and set position to its fractional part', function(){ - var loop, pos, posFrac, posInt - - // Without the reverse flag - fx.loop(10) - expect(fx.situation.loops).toBe(10) - expect(fx.situation.loop).toBe(loop = 0) - - pos = 1.3 - posFrac = pos % 1 - posInt = pos - posFrac - expect(fx.at(pos).pos).toBeCloseTo(posFrac) - expect(fx.situation.loop).toBe(loop += posInt) - - pos = 7.723 - posFrac = pos % 1 - posInt = pos - posFrac - expect(fx.at(pos).pos).toBeCloseTo(posFrac) - expect(fx.situation.loop).toBe(loop += posInt) - - // In this case, pos is above the remaining number of loops, so we expect - // the position to be set to 1 and the animation to be ended - pos = 4.3 - posFrac = pos % 1 - posInt = pos - posFrac - expect(fx.at(pos).pos).toBe(1) - expect(fx.situation).toBeNull() - - // Recreate an animation since the other one was ended - fx.animate() - - // With the reverse flag, the position is reversed each time loop is odd - fx.loop(10, true) - expect(fx.situation.loops).toBe(10) - expect(fx.situation.loop).toBe(loop = 0) - expect(fx.situation.reversed).toBe(false) - - pos = 3.3 - posFrac = pos % 1 - posInt = pos - posFrac - expect(fx.at(pos).pos).toBeCloseTo(1-posFrac) // Animation is reversed because 0+3 is odd - expect(fx.situation.loop).toBe(loop += posInt) - expect(fx.situation.reversed).toBe(true) - - // When the passed position is below 0, the integer part of position is - // substracted from 1, so, in this case, -0.6 has 1 as is integer part - // This is necessary so we can add something to the loop counter - pos = -0.645 - posFrac = (1-pos) % 1 - posInt = (1-pos) - posFrac - expect(fx.at(pos).pos).toBeCloseTo(posFrac) - expect(fx.situation.loop).toBe(loop += posInt) - expect(fx.situation.reversed).toBe(false) - - // In this case, pos is above the remaining number of loop, so we expect - // the position to be set to 0 (since we end reversed) and the animation to - // be ended - pos = 7.2 - posFrac = pos % 1 - posInt = pos - posFrac - expect(fx.at(pos).pos).toBe(0) - expect(fx.situation).toBeNull() - }) - - it('should, when the animation is in a infinite loop and the passed position is out of [0,1], use the integer part of postion to update the loop counter and set position to its fractional part', function(){ - var loop, pos, posFrac, posInt - - // Without the reverse flag - fx.loop(true) - expect(fx.situation.loops).toBe(true) - expect(fx.situation.loop).toBe(loop = 0) - - pos = 10.34 - posFrac = pos % 1 - posInt = pos - posFrac - expect(fx.at(pos).pos).toBeCloseTo(posFrac) - expect(fx.situation.loop).toBe(loop += posInt) - - // With the reverse flag, the position is reversed each time loop is odd - fx.loop(true, true) - expect(fx.situation.loops).toBe(true) - expect(fx.situation.loop).toBe(loop = 0) - expect(fx.situation.reversed).toBe(false) - - pos = 3.3 - posFrac = pos % 1 - posInt = pos - posFrac - expect(fx.at(pos).pos).toBeCloseTo(1-posFrac) // Animation is reversed because 3+0 is odd - expect(fx.situation.loop).toBe(loop += posInt) - expect(fx.situation.reversed).toBe(true) - - pos = -8.41 - posFrac = (1-pos) % 1 - posInt = (1-pos) - posFrac - expect(fx.at(pos).pos).toBeCloseTo(posFrac) - expect(fx.situation.loop).toBe(loop += posInt) - expect(fx.situation.reversed).toBe(false) - }) - - it('should take speed into consideration', function() { - var dur, spd - - dur = fx.situation.duration - - spd = 4 - fx.speed(spd).at(0) - expect(fx.situation.finish-fx.situation.start).toBe(dur/spd) - - spd = 5 - fx.speed(spd).at(0.15) - expect(fx.situation.finish-fx.situation.start).toBe(dur/spd) - - spd = 0.25 - fx.speed(spd).at(0.75) - expect(fx.situation.finish-fx.situation.start).toBe(dur/spd) - - spd = 0.5 - fx.speed(spd).at(0.83) - expect(fx.situation.finish-fx.situation.start).toBe(dur/spd) - }) - - it('should consider the first parameter as an absolute position when the second parameter is true', function() { - var absPos - - fx.loop(true) - - absPos = 3.2 - expect(fx.at(absPos, true).absPos).toBe(absPos) - - absPos = -4.27 - expect(fx.at(absPos, true).absPos).toBe(absPos) - - absPos = 0 - expect(fx.at(absPos, true).absPos).toBe(absPos) - - absPos = 1 - expect(fx.at(absPos, true).absPos).toBe(absPos) - }) - }) - - - describe('start()', function(){ - it('starts the animation', function() { - fx.start() - expect(fx.active).toBe(true) - - jasmine.clock().tick(200) - fx.step() // Call step to update the animation - - expect(fx.pos).toBeGreaterThan(0) - }) - - it('should take speed into consideration', function() { - var dur = 500 - , delay = 300 - , spd = 4 - - - fx.stop().animate(dur, '-', delay).speed(spd).start() - expect(fx.situation.finish - new Date).toBe(delay/spd + dur/spd) - }) - - it('should do the delay', function() { - fx.situation.delay = 1000 - expect(fx.start().active).toBe(true) - - jasmine.clock().tick(501) - fx.step() // Call step to update the animation - expect(fx.active).toBe(true) - - jasmine.clock().tick(501) - fx.step() // Call step to update the animation - expect(fx.active).toBe(true) - - jasmine.clock().tick(501) - fx.step() // Call step to update the animation - expect(fx.active).toBe(false) - }) - }) - - describe('delay()', function() { - it('should push an empty situation with its duration attribute set to the duration of the delay', function() { - var delay = 8300 - fx.delay(delay) - expect(fx.situations[0].duration).toBe(delay) - }) - }) - - - describe('pause()', function() { - it('pause the animation', function() { - expect(fx.pause().paused).toBe(true) - }) - }) - - describe('play()', function() { - it('returns itself when animation not paused', function() { - expect(fx.paused).toBe(false) - expect(fx.play()).toBe(fx) - }) - - it('unpause the animation', function() { - var start = fx.start().pause().situation.start - - jasmine.clock().tick(200) - - expect(fx.situation.start).toBe(start) - expect(fx.play().paused).toBe(false) - expect(fx.situation.start).not.toBe(start) - }) - - it('should not change the position when the animation is unpaused while it is set to run backward', function(){ - var pos = 0.4 - - expect(fx.reverse(true).at(pos).pause().play().pos).toBe(pos) - }) - - it('should be able to unpause the delay', function () { - fx.stop().animate(500, '-', 300).start().step() - expect(fx.pos).toBe(0) - expect(fx.absPos).toBeCloseTo(-0.6) - - // At this point, we should have an animation of 500 ms with a delay of - // 300 ms that should be running. - - jasmine.clock().tick(150) - - // Should be halfway through the delay - fx.step() - expect(fx.pos).toBe(0) - expect(fx.absPos).toBe(-0.3) - - expect(fx.pause().paused).toBe(true) // Pause the delay - - jasmine.clock().tick(150) - - // Unpause, should still be halfway through the delay - expect(fx.play().paused).toBe(false) - expect(fx.pos).toBe(0) - expect(fx.absPos).toBe(-0.3) - - jasmine.clock().tick(150) - - // Delay should be done - fx.step() - expect(fx.pos).toBe(0) - expect(fx.absPos).toBe(0) - - jasmine.clock().tick(500) - - // Animation and delay should be done - fx.step() - expect(fx.active).toBe(false) - expect(fx.pos).toBe(1) - expect(fx.absPos).toBe(1) - }) - }) - - - describe('speed()', function() { - it('set the speed of the animation', function(){ - var dur, spd - - dur = fx.situation.duration - - spd = 2 - fx.speed(spd) - expect(fx._speed).toBe(spd) - expect(fx.situation.finish-fx.situation.start).toBe(dur/spd) - - spd = 0.5 - fx.speed(spd) - expect(fx._speed).toBe(spd) - expect(fx.situation.finish-fx.situation.start).toBe(dur/spd) - - spd = 2 - fx.at(0.2).speed(spd) - expect(fx._speed).toBe(spd) - expect(fx.situation.finish-fx.situation.start).toBe(dur/spd) - - spd = 1 - fx.speed(spd) - expect(fx._speed).toBe(spd) - expect(fx.situation.finish-fx.situation.start).toBe(dur) - }) - - it('should not change the position when the animation is run backward', function(){ - var pos = 0.4 - - expect(fx.reverse(true).at(pos).speed(2).pos).toBe(pos) - }) - - it('return the current speed with no argument given', function(){ - var spd - - spd = 2 - fx._speed = spd - expect(fx.speed()).toBe(spd) - - spd = 0.5 - fx._speed = spd - expect(fx.speed()).toBe(spd) - - spd = 1 - fx._speed = spd - expect(fx.speed()).toBe(spd) - }) - - it('pause the animation when a speed of 0 is passed', function(){ - var spd = fx._speed - - expect(fx.speed(0)).toBe(fx) - expect(fx._speed).toBe(spd) - expect(fx.paused).toBe(true) - }) - - it('should affect all animations in the queue', function(){ - fx.speed(2).animate(300) - expect(fx.situations.length).not.toBe(0) - expect(fx.pos).not.toBe(1) - - // At this point, there should be 2 animations in the queue to be played: - // the one of 500ms that is added before every test and the one of 300ms - // we just added. Normally, it would take 800ms before both of these - // animations are done, but because we set the speed to 2, it should - // only take 400ms to do both animations. - fx.start().step() - - jasmine.clock().tick(250) - - // Should be playing the second animation - fx.step() - expect(fx.active).toBe(true) - expect(fx.situations.length).toBe(0) - expect(fx.pos).not.toBe(1) - - jasmine.clock().tick(150) // 400ms have passed - - // All animations should be done - fx.step() - expect(fx.active).toBe(false) - expect(fx.situations.length).toBe(0) - expect(fx.pos).toBe(1) - }) - - it('should affect the delay', function() { - fx.stop().animate(500, '-', 300).start().step() - expect(fx.pos).toBe(0) - expect(fx.absPos).toBeCloseTo(-0.6) - - fx.speed(2) - expect(fx.pos).toBe(0) - expect(fx.absPos).toBeCloseTo(-0.6) - - // At this point, we should have an animation of 500 ms with a delay of - // 300 ms that should be running. Normally, it would take 800 ms for the - // animation and its delay to complete, but because the speed is set to 2 - // , it should only take 400ms - - jasmine.clock().tick(75) - - // Should be halfway through the delay - fx.step() - expect(fx.pos).toBe(0) - expect(fx.absPos).toBe(-0.3) - - jasmine.clock().tick(75) - - // Delay should be done - fx.step() - expect(fx.pos).toBe(0) - expect(fx.absPos).toBe(0) - - jasmine.clock().tick(250) - - // Animation and delay should be done - fx.step() - expect(fx.active).toBe(false) - expect(fx.pos).toBe(1) - expect(fx.absPos).toBe(1) - }) - }) - - - describe('reverse()', function() { - it('toggles the direction of the animation without a parameter', function() { - expect(fx.reverse().situation.reversed).toBe(true) - }) - it('sets the direction to backwards with true given', function() { - expect(fx.reverse(true).situation.reversed).toBe(true) - }) - it('sets the direction to forwards with false given', function() { - expect(fx.reverse(false).situation.reversed).toBe(false) - }) - }) - - - describe('queue()', function() { - it('can add a situation to the queue', function() { - var situation = new SVG.Situation({duration: 1000, delay: 0, ease: SVG.easing['-']}) - - fx.queue(situation) - expect(fx.situations[0]).toBe(situation) - }) - - it('can add a function to the queue', function() { - var f = function(){} - - fx.queue(f) - expect(fx.situations[0]).toBe(f) - }) - - it('should set the situation attribute before pushing something in the situations queue', function(){ - var situation = new SVG.Situation({duration: 1000, delay: 0, ease: SVG.easing['-']}) - - // Clear the animation that is created before each test - fx.stop() - - expect(fx.situation).toBeNull() - expect(fx.situations.length).toBe(0) - fx.queue(situation) - expect(fx.situation).toBe(situation) - expect(fx.situations.length).toBe(0) - }) - }) - - - describe('dequeue()', function() { - it('should pull the next situtation from the queue', function() { - var situation = new SVG.Situation({duration: 1000, delay: 0, ease: SVG.easing['-']}) - - fx.queue(situation) - expect(fx.situtation).not.toBe(situation) - expect(fx.situations[0]).toBe(situation) - - fx.dequeue() - expect(fx.situation).toBe(situation) - expect(fx.situations.length).toBe(0) - }) - - it('initialize the animation pulled from the queue to its start position', function() { - // When the animation is forward, the start position is 0 - fx.animate() - fx.pos = 0.5 - expect(fx.dequeue().pos).toBe(0) - - // When the animation backward, the start position is 1 - fx.animate().reverse(true) - fx.pos = 0.5 - expect(fx.dequeue().pos).toBe(1) - }) - - it('when the first element of the queue is a function, it should execute it', function() { - var called = false - - fx.queue(function(){ - called = true - expect(this).toBe(fx) - this.dequeue() - }).dequeue() - - expect(called).toBe(true) - }) - - it('should stop the currently running animation when there is one', function() { - fx.start() - expect(fx.active).toBe(true) - fx.queue(function() { - expect(this.active).toBe(false) - this.dequeue() - }) - fx.dequeue() - }) - }) - - - describe('stop()', function() { - it('stops the animation immediately without a parameter', function() { - fx.animate(500).start() - expect(fx.stop().situation).toBeNull() - expect(fx.active).toBe(false) - expect(fx.situations.length).toBe(1) - }) - it('stops the animation immediately and fullfill it if first parameter true', function() { - fx.animate(500).start() - expect(fx.stop(true).situation).toBeNull() - expect(fx.active).toBe(false) - expect(fx.pos).toBe(1) - expect(fx.situations.length).toBe(1) - }) - it('stops the animation immediately and remove all items from queue when second parameter true', function() { - fx.animate(500).start() - expect(fx.stop(false, true).situation).toBeNull() - expect(fx.active).toBe(false) - expect(fx.situations.length).toBe(0) - }) - }) - - - describe('reset()', function() { - it('resets the element to the state it was when the current animation was started', function() { - var loops = 4 - , situation = fx.situation - - // These settings make the animations run backward - fx.situation.loop = 2 - fx.situation.loops = loops - fx.situation.reversed = true - fx.pos = 0.5 - fx.absPos = 2.5 - - fx.reset() - - expect(fx.situation).toBe(situation) - expect(fx.situation.loops).toBe(loops) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.reversed).toBe(true) // True because the animation is backward - expect(fx.pos).toBe(1) - expect(fx.absPos).toBe(0) - }) - }) - - - describe('finish()', function() { - it('finish the whole animation by fullfilling every single one', function() { - fx.animate(500) - expect(fx.finish().pos).toBe(1) - expect(fx.situations.length).toBe(0) - expect(fx.situation).toBeNull() - }) - }) - - - describe('progress()', function() { - it('returns the current position', function() { - expect(fx.progress()).toBe(0) - expect(fx.progress()).toBe(fx.pos) - }) - it('returns the current position as eased value if fist argument is true', function() { - var anim = draw.rect(100,100).animate(500,'>').start() - expect(anim.progress(true)).toBe(0) - - anim.at(0.25) - expect(anim.progress(true)).toBeCloseTo(anim.situation.ease(0.25)) - }) - }) - - - describe('after()', function() { - it('adds a callback which is called when the current animation is finished', function() { - var called = false - - fx.start().after(function(situation){ - expect(fx.situation).toBe(situation) - expect(fx.pos).toBe(1) - called = true - }) - - jasmine.clock().tick(500) - fx.step() - expect(called).toBe(true) - }) - }) - - - describe('afterAll()', function() { - it('adds a callback which is called when all animations are finished', function() { - var called = false - - fx.animate(150).animate(125).start().afterAll(function(){ - expect(fx.pos).toBe(1) - expect(fx.situations.length).toBe(0) - called = true - }) - - expect(fx.situations.length).toBe(2) - - // End of the first animation - jasmine.clock().tick(500) - fx.step() - expect(fx.situations.length).toBe(1) - expect(called).toBe(false) - - // End of the second animation - jasmine.clock().tick(150) - fx.step() - expect(fx.situations.length).toBe(0) - expect(called).toBe(false) - - // End of the third and last animation - jasmine.clock().tick(125) - fx.step() - expect(fx.situation).toBeNull() - expect(called).toBe(true) - }) - }) - - - describe('during()', function() { - it('adds a callback which is called on every animation step', function() { - var called = 0 - - fx.start().during(function(pos, morph, eased, situation){ - - expect(fx.situation).toBe(situation) - - switch(++called) { - case 1: - expect(pos).toBeCloseTo(0.25) - break - - case 2: - expect(pos).toBeCloseTo(0.5) - break - - case 3: - expect(pos).toBeCloseTo(0.65) - break - - case 4: - expect(pos).toBe(1) - break - } - - expect(morph(0, 100)).toBeCloseTo(pos*100) - - }) - - jasmine.clock().tick(125) - fx.step() - expect(called).toBe(1) - - jasmine.clock().tick(125) // 250 ms have passed - fx.step() - expect(called).toBe(2) - - jasmine.clock().tick(75) // 325 ms have passed - fx.step() - expect(called).toBe(3) - - jasmine.clock().tick(175) // 500 ms have passed - fx.step() - expect(called).toBe(4) - }) - }) - - - describe('duringAll()', function() { - it('adds a callback which is called on every animation step for the whole chain', function() { - - fx.finish() - rect.off('.fx') - - fx.animate(500).start().animate(500) - - var sit = null - - var pos1 = false - var pos2 = false - - fx.duringAll(function(pos, morph, eased, situation){ - - if(pos1){ - pos1 = false - sit = situation - expect(this.fx.pos).toBeCloseTo(0.6) - } - - if(pos2){ - pos2 = null - expect(situation).not.toBe(sit) - expect(this.fx.pos).toBeCloseTo(0.75) - } - }) - - pos1 = true - jasmine.clock().tick(300) - fx.step() - - jasmine.clock().tick(200) // End of the first animation - fx.step() - - pos2 = true - jasmine.clock().tick(375) - fx.step() - - if(pos1 || pos2) { - fail('Not enough situations called') - } - }) - }) - - - describe('once()', function() { - it('adds a callback which is called once at the specified position', function() { - var called = false - - fx.start().once(0.5, function(pos, eased){ - called = true - expect(pos).toBeCloseTo(0.5) - }) - - jasmine.clock().tick(125) - fx.step() - expect(called).toBe(false) - - jasmine.clock().tick(125) // 250 ms have passed - fx.step() - expect(called).toBe(true) - }) - - it('adds the callback on the last situation', function () { - var callback = function () {} - - fx.animate(500).animate(500).once(0.5, callback) - expect(fx.situation.once['0.5']).toBeUndefined() - expect(fx.situations[0].once['0.5']).toBeUndefined() - expect(fx.situations[1].once['0.5']).toBe(callback) - }) - }) - - - describe('loop()', function() { - it('should create an eternal loop when no arguments are given', function() { - var time = 10523, dur = fx.situation.duration - - fx.loop() - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(true) - expect(fx.pos).toBe(0) - expect(fx.absPos).toBe(0) - - fx.start().step() - jasmine.clock().tick(time) - fx.step() - - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe( Math.floor(time/dur) ) - expect(fx.situation.loops).toBe(true) - expect(fx.pos).toBeCloseTo((time/dur) % 1) - expect(fx.absPos).toBeCloseTo(time/dur) - }) - - it('should create an eternal loop when the first argument is true', function() { - var time = 850452, dur = fx.situation.duration - - fx.loop(true) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(true) - expect(fx.pos).toBe(0) - expect(fx.absPos).toBe(0) - - fx.start().step() - jasmine.clock().tick(time) - fx.step() - - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe( Math.floor(time/dur) ) - expect(fx.situation.loops).toBe(true) - expect(fx.pos).toBeCloseTo((time/dur) % 1) - expect(fx.absPos).toBeCloseTo(time/dur) - }) - - it('should loop for the specified number of times', function() { - var time = 0, dur = fx.situation.duration - - fx.loop(3) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(3) - expect(fx.pos).toBe(0) - expect(fx.absPos).toBe(0) - - fx.start().step() - jasmine.clock().tick(200) - time = 200 - - fx.step() - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(3) - expect(fx.pos).toBeCloseTo((time/dur) % 1) - expect(fx.absPos).toBeCloseTo(time/dur) - - jasmine.clock().tick(550) - time += 550 // time at 750 - - fx.step() - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(1) - expect(fx.situation.loops).toBe(3) - expect(fx.pos).toBeCloseTo((time/dur) % 1) - expect(fx.absPos).toBeCloseTo(time/dur) - - jasmine.clock().tick(570) - time += 570 // time at 1320 - - fx.step() - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(2) - expect(fx.situation.loops).toBe(3) - expect(fx.pos).toBeCloseTo((time/dur) % 1) - expect(fx.absPos).toBeCloseTo(time/dur) - - jasmine.clock().tick(180) - time += 180 // time at 1500 - - fx.step() - expect(fx.active).toBe(false) - expect(fx.situation).toBeNull() - expect(fx.pos).toBe(1) - expect(fx.absPos).toBe(3) - }) - - it('should go from beginning to end and start over again (0->1.0->1.0->1.) by default', function() { - var time = 0, dur = fx.situation.duration - - fx.loop(2) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(2) - expect(fx.situation.reversing).toBe(false) - expect(fx.situation.reversed).toBe(false) - expect(fx.pos).toBe(0) - expect(fx.absPos).toBe(0) - - fx.start().step() - jasmine.clock().tick(325) - time = 325 - - fx.step() - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(2) - expect(fx.situation.reversing).toBe(false) - expect(fx.situation.reversed).toBe(false) - expect(fx.pos).toBeCloseTo((time/dur) % 1) - expect(fx.absPos).toBeCloseTo(time/dur) - - jasmine.clock().tick(575) - time += 575 // time at 900 - - fx.step() - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(1) - expect(fx.situation.loops).toBe(2) - expect(fx.situation.reversing).toBe(false) - expect(fx.situation.reversed).toBe(false) - expect(fx.pos).toBeCloseTo((time/dur) % 1) - expect(fx.absPos).toBeCloseTo(time/dur) - - jasmine.clock().tick(200) - time += 200 // time at 1100 - - fx.step() - expect(fx.active).toBe(false) - expect(fx.situation).toBeNull() - expect(fx.pos).toBe(1) - expect(fx.absPos).toBe(2) - }) - - it('should be completely reversed before starting over (0->1->0->1->0->1.) when the reverse flag is passed', function() { - var time = 0, dur = fx.situation.duration - - fx.loop(2, true) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(2) - expect(fx.situation.reversing).toBe(true) - expect(fx.situation.reversed).toBe(false) - expect(fx.pos).toBe(0) - expect(fx.absPos).toBe(0) - - fx.start().step() - jasmine.clock().tick(325) - time = 325 - - fx.step() - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(2) - expect(fx.situation.reversing).toBe(true) - expect(fx.situation.reversed).toBe(false) - expect(fx.pos).toBeCloseTo((time/dur) % 1) - expect(fx.absPos).toBeCloseTo(time/dur) - - jasmine.clock().tick(575) - time += 575 // time at 900 - - fx.step() - expect(fx.active).toBe(true) - expect(fx.situation.loop).toBe(1) - expect(fx.situation.loops).toBe(2) - expect(fx.situation.reversing).toBe(true) - expect(fx.situation.reversed).toBe(true) - expect(fx.pos).toBeCloseTo(1 - (time/dur) % 1) - expect(fx.absPos).toBeCloseTo(time/dur) - - jasmine.clock().tick(200) - time += 200 // time at 1100 - - fx.step() - expect(fx.active).toBe(false) - expect(fx.situation).toBeNull() - expect(fx.pos).toBe(0) - expect(fx.absPos).toBe(2) - }) - - it('should be applied on the last situation', function() { - fx.loop(5) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(5) - expect(fx.situation.reversing).toBe(false) - - fx.animate().loop(3, true) - expect(fx.situation.loop).toBe(0) - expect(fx.situation.loops).toBe(5) - expect(fx.situation.reversing).toBe(false) - - var c = fx.last() - expect(c.loop).toBe(0) - expect(c.loops).toBe(3) - expect(c.reversing).toBe(true) - }) - - it('should be possible to call it with false as the first argument', function() { - fx.situation.loops = true - fx.loop(false) - expect(fx.situation.loops).toBe(false) - }) - }) - - - describe('step()', function() { - it('should not recalculate the absolute position if the first parameter is true', function() { - var absPos - - // We shift start to help us see if the absolute position get recalculated - // If it get recalculated, the result would be 0.5 - fx.situation.start -= 250 - - absPos = 0.4 - fx.absPos = absPos - expect(fx.step(true).absPos).toBe(absPos) - - absPos = 0 - fx.absPos = absPos - expect(fx.step(true).absPos).toBe(absPos) - - absPos = -3.7 - fx.absPos = absPos - expect(fx.step(true).absPos).toBe(absPos) - - absPos = 1 - fx.absPos = absPos - expect(fx.step(true).absPos).toBe(absPos) - }) - - it('should not allow an absolute position to be above the end', function() { - var absPos, loops - - // With no loops, absolute position should not go above 1 - absPos = 4.26 - fx.absPos = absPos - expect(fx.step(true).absPos).toBe(1) - expect(fx.situation).toBeNull() - - fx.animate() // Recreate an animation since the other one was ended - - // With loops, absolute position should not go above loops - loops = 4 - absPos = 7.42 - fx.absPos = absPos - expect(fx.loop(loops).step(true).absPos).toBe(loops) - expect(fx.situation).toBeNull() - }) - - describe('when converting an absolute position to a position', function() { - it('should, when the absolute position is below the maximum number of loops, use the integer part of the absolute position to set the loop counter and use its fractional part to set the position', function(){ - var absPos, absPosFrac, absPosInt, loops - - // Without the reverse flag - loops = 12 - absPos = 4.52 - absPosInt = Math.floor(absPos) - absPosFrac = absPos - absPosInt - fx.absPos = absPos - fx.loop(loops).step(true) - expect(fx.pos).toBe(absPosFrac) - expect(fx.situation.loop).toBe(absPosInt) - - fx.stop().animate() - - loops = true - absPos = 2.57 - absPosInt = Math.floor(absPos) - absPosFrac = absPos - absPosInt - fx.absPos = absPos - fx.loop(loops).step(true) - expect(fx.pos).toBe(absPosFrac) - expect(fx.situation.loop).toBe(absPosInt) - - fx.stop().animate() - - // With the reverse flag, the position is reversed at each odd loop - loops = 412 - absPos = 6.14 - absPosInt = Math.floor(absPos) - absPosFrac = absPos - absPosInt - fx.absPos = absPos - fx.loop(loops, true).step(true) - expect(fx.pos).toBe(absPosFrac) - expect(fx.situation.loop).toBe(absPosInt) - expect(fx.situation.reversed).toBe(false) - - fx.stop().animate() - - loops = true - absPos = 5.12 - absPosInt = Math.floor(absPos) - absPosFrac = absPos - absPosInt - fx.absPos = absPos - fx.loop(loops, true).step(true) - expect(fx.pos).toBe(1-absPosFrac) // Odd loop, so it is reversed - expect(fx.situation.loop).toBe(absPosInt) - expect(fx.situation.reversed).toBe(true) - - fx.stop().animate() - - // When the animation is set to run backward, it is the opposite, the position is reversed at each even loop - loops = 14 - absPos = 8.46 - absPosInt = Math.floor(absPos) - absPosFrac = absPos - absPosInt - fx.absPos = absPos - fx.reverse(true).loop(loops, true).step(true) - expect(fx.pos).toBe(1-absPosFrac) // Even loop, so it is reversed - expect(fx.situation.loop).toBe(absPosInt) - expect(fx.situation.reversed).toBe(true) - - fx.stop().animate() - - loops = true - absPos = 3.12 - absPosInt = Math.floor(absPos) - absPosFrac = absPos - absPosInt - fx.absPos = absPos - fx.reverse(true).loop(loops, true).step(true) - expect(fx.pos).toBe(absPosFrac) - expect(fx.situation.loop).toBe(absPosInt) - expect(fx.situation.reversed).toBe(false) - }) - - it('should, when the absolute position is above or equal to the the maximum number of loops, set the position to its end value and end the animation', function() { - var absPos, loops - - // Without the reverse flag, the end value of position is 1 - loops = 6 - absPos = 13.52 - fx.absPos = absPos - fx.loop(loops).step(true) - expect(fx.pos).toBe(1) - expect(fx.situation).toBeNull() - - fx.animate() // Recreate an animation since the other one was ended - - loops = false - absPos = 146.22 - fx.absPos = absPos - fx.loop(loops).step(true) - expect(fx.pos).toBe(1) - expect(fx.situation).toBeNull() - - fx.animate() // Recreate an animation since the other one was ended - - // With the reverse flag, the end value of position is 0 when loops is even and 1 when loops is an odd number or false - loops = 6 - absPos = 6 - fx.absPos = absPos - fx.loop(loops, true).step(true) - expect(fx.pos).toBe(0) // Even loops - expect(fx.situation).toBeNull() - - fx.animate() // Recreate an animation since the other one was ended - - loops = false - absPos = 4.47 - fx.absPos = absPos - fx.loop(loops, true).step(true) - expect(fx.pos).toBe(1) // 1 since loops is false - expect(fx.situation).toBeNull() - - fx.animate() // Recreate an animation since the other one was ended - - // When the animation is set to run backward, it is the opposite, the end value of position is 1 when loops is even and 0 when loops is an odd number or false - loops = 8 - absPos = 12.65 - fx.absPos = absPos - fx.reverse(true).loop(loops, true).step(true) - expect(fx.pos).toBe(1) // Even loops - expect(fx.situation).toBeNull() - - fx.animate() // Recreate an animation since the other one was ended - - loops = 11 - absPos = 12.41 - fx.absPos = absPos - fx.reverse(true).loop(loops, true).step(true) - expect(fx.pos).toBe(0) // Odd loops - expect(fx.situation).toBeNull() - }) - - it('should set the position to its start value when the absolute position is below 0', function() { - var absPos - - // When the animation is not set to run backward the start value is 0 - absPos = -2.27 - fx.loop(7) - fx.situation.loop = 3 - fx.absPos = absPos - fx.step(true) - expect(fx.pos).toBe(0) - expect(fx.absPos).toBe(absPos) - expect(fx.situation.loop).toBe(0) - - fx.stop().animate() - - // When the animation is set to run backward the start value is 1 - absPos = -4.12 - fx.absPos = absPos - fx.reverse(true).step(true) - expect(fx.pos).toBe(1) - expect(fx.absPos).toBe(absPos) - }) - - it('should, when looping with the reverse flag, toggle reversed only when the difference between the new value of loop counter and its old value is odd', function() { - // The new value of the loop counter is the integer part of absPos - - fx.loop(9, true) - expect(fx.situation.loop).toBe(0) - expect(fx.pos).toBe(0) - expect(fx.situation.reversed).toBe(false) - - fx.absPos = 3 - fx.step(true) - expect(fx.situation.reversed).toBe(true) // (3-0) is odd - - fx.absPos = 1 - fx.step(true) - expect(fx.situation.reversed).toBe(true) // (1-3) is even - - fx.absPos = 6 - fx.step(true) - expect(fx.situation.reversed).toBe(false) // (6-1) is odd - - fx.absPos = 9 - fx.step(true) - expect(fx.situation).toBeNull() - expect(fx.pos).toBe(1) // It should end not reversed, which mean the position is expected to be 1 - // ((9-1)-6) is even, the -1 is because we do not want reversed to be toggled after the last loop - }) - }) - - - it('should not throw an error when stop is called in a during callback', function () { - fx.move(100,100).start() - fx.during(function () {this.stop()}) - expect(fx.step.bind(fx)).not.toThrow() - }) - - it('should not throw an error when finish is called in a during callback', function () { - fx.move(100,100).start() - fx.during(function () {this.finish()}) - expect(fx.step.bind(fx)).not.toThrow() - }) - - it('should not set active to false if the afterAll callback add situations to the situations queue', function () { - fx.afterAll(function(){this.animate(500).move(0,0)}) - - jasmine.clock().tick(500) - fx.step() - expect(fx.active).toBe(true) - expect(fx.situation).not.toBeNull() - expect(fx.situations.length).toBe(0) - - jasmine.clock().tick(500) - fx.step() - expect(fx.active).toBe(false) - expect(fx.situation).toBeNull() - expect(fx.situations.length).toBe(0) - }) - }) - - - it('animates the x/y-attr', function() { - var called = false - - fx.move(200,200).after(function(){ - - expect(rect.x()).toBe(200) - expect(rect.y()).toBe(200) - called = true - - }) - - jasmine.clock().tick(250) - fx.step() - expect(rect.x()).toBeGreaterThan(100) - expect(rect.y()).toBeGreaterThan(100) - - jasmine.clock().tick(250) - fx.step() - expect(called).toBe(true) - }) - - it('animates matrix', function() { - var ctm, called = false - - fx.transform({a:0.8, b:0.4, c:-0.15, d:0.7, e: 90.3, f: 27.07}).after(function(){ - - var ctm = rect.ctm() - expect(ctm.a).toBeCloseTo(0.8) - expect(ctm.b).toBeCloseTo(0.4) - expect(ctm.c).toBeCloseTo(-0.15) - expect(ctm.d).toBeCloseTo(0.7) - expect(ctm.e).toBeCloseTo(90.3) - expect(ctm.f).toBeCloseTo(27.07) - called = true - - }) - - jasmine.clock().tick(250) - fx.step() - ctm = rect.ctm() - expect(ctm.a).toBeLessThan(1) - expect(ctm.b).toBeGreaterThan(0) - expect(ctm.c).toBeLessThan(0) - expect(ctm.d).toBeGreaterThan(0) - expect(ctm.e).toBeGreaterThan(0) - expect(ctm.f).toBeGreaterThan(0) - - jasmine.clock().tick(250) - fx.step() - expect(called).toBe(true) - }) - - it('animate a scale transform using the passed center point when there is already a transform in place', function(){ - var ctm - - // When no ceter point is passed to the method scale, it use the center of the element as the center point - - rect.scale(2) // The transform in place - - fx.scale(0.5) - jasmine.clock().tick(500) // Have the animation reach its end - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(0.5) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(0.5) - expect(ctm.e).toBe(75) - expect(ctm.f).toBe(75) - }) - - it('animate a flip(x) transform', function() { - var ctm - - fx.transform({flip: 'x'}).start() - - jasmine.clock().tick(125) // Have the animation be 1/4 of the way (not halfway as usual because of a bug in the node method getCTM on Firefox) - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(0.5) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(1) - expect(ctm.e).toBe(75) - expect(ctm.f).toBe(0) - - jasmine.clock().tick(475) // Have the animation reach its end - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(-1) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(1) - expect(ctm.e).toBe(300) - expect(ctm.f).toBe(0) - }) - - it('animate a flip(x) transform with an offset', function() { - var ctm - - fx.transform({flip: 'x', offset: 20}).start() - - jasmine.clock().tick(125) // Have the animation be 1/4 of the way (not halfway as usual because of a bug in the node method getCTM on Firefox) - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(0.5) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(1) - expect(ctm.e).toBe(10) - expect(ctm.f).toBe(0) - - jasmine.clock().tick(475) // Have the animation reach its end - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(-1) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(1) - expect(ctm.e).toBe(40) - expect(ctm.f).toBe(0) - }) - - it('animate a flip(y) transform', function() { - var ctm - - fx.transform({flip: 'y'}).start() - - jasmine.clock().tick(125) // Have the animation be 1/4 of the way (not halfway as usual because of a bug in the node method getCTM on Firefox) - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(1) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(0.5) - expect(ctm.e).toBe(0) - expect(ctm.f).toBe(75) - - jasmine.clock().tick(475) // Have the animation reach its end - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(1) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(-1) - expect(ctm.e).toBe(0) - expect(ctm.f).toBe(300) - }) - - it('animate a flip(y) transform with an offset', function() { - var ctm - - fx.transform({flip: 'y', offset: 20}).start() - - jasmine.clock().tick(125) // Have the animation be 1/4 of the way (not halfway as usual because of a bug in the node method getCTM on Firefox) - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(1) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(0.5) - expect(ctm.e).toBe(0) - expect(ctm.f).toBe(10) - - jasmine.clock().tick(475) // Have the animation reach its end - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(1) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(-1) - expect(ctm.e).toBe(0) - expect(ctm.f).toBe(40) - }) - - it('animate a flip() transform', function() { - var ctm - - fx.transform({flip: 'both'}).start() - - jasmine.clock().tick(125) // Have the animation be 1/4 of the way (not halfway as usual because of a bug in the node method getCTM on Firefox) - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(0.5) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(0.5) - expect(ctm.e).toBe(75) - expect(ctm.f).toBe(75) - - jasmine.clock().tick(475) // Have the animation reach its end - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(-1) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(-1) - expect(ctm.e).toBe(300) - expect(ctm.f).toBe(300) - }) - - it('animate a flip() transform with an offset', function() { - var ctm - - fx.transform({flip: 'both', offset: 20}).start() - - jasmine.clock().tick(125) // Have the animation be 1/4 of the way (not halfway as usual because of a bug in the node method getCTM on Firefox) - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(0.5) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(0.5) - expect(ctm.e).toBe(10) - expect(ctm.f).toBe(10) - - jasmine.clock().tick(475) // Have the animation reach its end - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(-1) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(-1) - expect(ctm.e).toBe(40) - expect(ctm.f).toBe(40) - }) - - it('animate relative matrix transform', function(){ - var ctm - - fx.transform(new SVG.Matrix().scale(2,0,0), true) - - jasmine.clock().tick(250) // Have the animation be half way - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(1.5) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(1.5) - expect(ctm.e).toBe(0) - expect(ctm.f).toBe(0) - - jasmine.clock().tick(250) // Have the animation reach its end - fx.step() - - ctm = rect.ctm() - expect(ctm.a).toBe(2) - expect(ctm.b).toBe(0) - expect(ctm.c).toBe(0) - expect(ctm.d).toBe(2) - expect(ctm.e).toBe(0) - expect(ctm.f).toBe(0) - }) - - describe('when animating plots', function() { - it('should allow plot animations to be chained', function() { - var pathString1 = 'M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80' - , pathString2 = 'M10 80 C 40 150, 65 150, 95 80 S 150 10, 180 80' - , path = draw.path(pathString1) - , morph - - fx = path.animate(1000).plot(pathString2).animate(1000).plot(pathString1) - morph = new SVG.PathArray(pathString1).morph(pathString2) - - fx.start() - expect(path.array()).toEqual(morph.at(0)) - - jasmine.clock().tick(500) // Have the first animation be half way - fx.step() - expect(path.array()).toEqual(morph.at(0.5)) - - jasmine.clock().tick(500) // Have the first animation reach its end - fx.step() - expect(path.array()).toEqual(morph.at(1)) - morph = new SVG.PathArray(pathString2).morph(pathString1) - expect(path.array()).toEqual(morph.at(0)) - - jasmine.clock().tick(500) // Have the second animation be half way - fx.step() - expect(path.array()).toEqual(morph.at(0.5)) - - jasmine.clock().tick(500) // Have the second animation reach its end - fx.step() - expect(path.array()).toEqual(morph.at(1)) - }) - - it('should allow plot to be called on a polyline', function() { - var startValue = [[0,0], [100,50], [50,100], [150,50], [200,50]] - , endValue = [[0,0], [100,50], [50,100], [150,50], [200,50], [250,100], [300,50], [350,50]] - , morph = new SVG.PointArray(startValue).morph(endValue) - , polyline = draw.polyline(startValue) - - fx = polyline.animate(3000).plot(endValue) - - fx.start() - expect(polyline.array()).toEqual(morph.at(0)) - - jasmine.clock().tick(1500) // Have the animation be half way - fx.step() - expect(polyline.array()).toEqual(morph.at(0.5)) - - jasmine.clock().tick(1500) // Have the animation reach its end - fx.step() - expect(polyline.array()).toEqual(morph.at(1)) - }) - - it('should allow plot to be called on a polygon', function() { - var startValue = [[0,0], [100,50], [50,100], [150,50], [200,50]] - , endValue = [[0,0], [100,50], [50,100], [150,50], [200,50], [250,100], [300,50], [350,50]] - , morph = new SVG.PointArray(startValue).morph(endValue) - , polygon = draw.polygon(startValue) - - fx = polygon.animate(3000).plot(endValue) - - fx.start() - expect(polygon.array()).toEqual(morph.at(0)) - - jasmine.clock().tick(1500) // Have the animation be half way - fx.step() - expect(polygon.array()).toEqual(morph.at(0.5)) - - jasmine.clock().tick(1500) // Have the animation reach its end - fx.step() - expect(polygon.array()).toEqual(morph.at(1)) - }) - - it('should allow plot to be called on a path', function() { - var startValue = new SVG.PathArray('M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80') - , endValue = new SVG.PathArray('M10 80 C 40 150, 65 150, 95 80 S 150 10, 180 80') - , morph = new SVG.PathArray(startValue).morph(endValue) - , path = draw.path(startValue) - - fx = path.animate(2000).plot(endValue) - - fx.start() - expect(path.array()).toEqual(morph.at(0)) - - jasmine.clock().tick(1000) // Have the animation be half way - fx.step() - expect(path.array()).toEqual(morph.at(0.5)) - - jasmine.clock().tick(1000) // Have the animation reach its end - fx.step() - expect(path.array()).toEqual(morph.at(1)) - }) - - it('should allow plot to be called on a textpath', function() { - var startValue = new SVG.PathArray('M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80') - , endValue = new SVG.PathArray('M10 80 C 40 150, 65 150, 95 80 S 150 10, 180 80') - , morph = new SVG.PathArray(startValue).morph(endValue) - - var text = draw.text(function(add) { - add.tspan("We go up and down, then we go down, then up again") - }) - - fx = text.path(startValue).animate(500).plot(endValue) - - fx.start() - expect(text.array()).toEqual(morph.at(0)) - - jasmine.clock().tick(250) // Have the animation be half way - fx.step() - expect(text.array()).toEqual(morph.at(0.5)) - - jasmine.clock().tick(250) // Have the animation reach its end - fx.step() - expect(text.array()).toEqual(morph.at(1)) - }) - - it('should allow plot to be called on a line', function() { - var startValue = '0,0 100,150' - , endValue = [[50,30], [120,250]] - , morph = new SVG.PointArray(startValue).morph(endValue) - , line = draw.line(startValue) - - fx = line.animate(3000).plot(endValue) - - fx.start() - expect(line.array()).toEqual(morph.at(0)) - - jasmine.clock().tick(1500) // Have the animation be half way - fx.step() - expect(line.array()).toEqual(morph.at(0.5)) - - jasmine.clock().tick(1500) // Have the animation reach its end - fx.step() - expect(line.array()).toEqual(morph.at(1)) - }) - - it('should allow plot to be called with 4 parameters on a line', function () { - var startPointArray = new SVG.PointArray('0,0 100,150') - , endPointArray = new SVG.PointArray([[50,30], [120,250]]) - , morph = new SVG.PointArray(startPointArray).morph(endPointArray) - , a - - a = startPointArray.value - var line = draw.line(a[0][0], a[0][1], a[1][0], a[1][1]) - - a = endPointArray.value - fx = line.animate(3000).plot(a[0][0], a[0][1], a[1][0], a[1][1]) - - fx.start() - expect(line.array()).toEqual(morph.at(0)) - - jasmine.clock().tick(1500) // Have the animation be half way - fx.step() - expect(line.array()).toEqual(morph.at(0.5)) - - jasmine.clock().tick(1500) // Have the animation reach its end - fx.step() - expect(line.array()).toEqual(morph.at(1)) - }) - }) - - - describe('when animating attributes', function() { - it('should be possible to animate numeric attributes', function () { - var startValue = 0 - , endValue = 150 - , morph = new SVG.Number(startValue).morph(endValue) - - var text = draw.text(function(add) { - add.tspan('We go ') - add.tspan('up').fill('#f09').dy(-40) - add.tspan(', then we go down, then up again').dy(40) - }) - - var path = 'M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100' - - text.path(path).font({ size: 42.5, family: 'Verdana' }) - - text.textPath().attr('startOffset', startValue) - fx = text.textPath().animate(1000).attr('startOffset', endValue) - - fx.start() - expect(text.textPath().attr('startOffset')).toBe(morph.at(0).value) - - jasmine.clock().tick(500) // Have the animation be half way - fx.step() - expect(text.textPath().attr('startOffset')).toBe(morph.at(0.5).value) - - jasmine.clock().tick(500) // Have the animation reach its end - fx.step() - expect(text.textPath().attr('startOffset')).toBe(morph.at(1).value) - }) - - it('should be possible to animate non-numeric attributes', function () { - var startValue = 'butt' - , endValue = 'round' - , line = draw.line('0,0 100,150').attr('stroke-linecap', startValue) - - fx = line.animate(3000).attr('stroke-linecap', endValue) - - fx.start() - expect(line.attr('stroke-linecap')).toBe(startValue) - - jasmine.clock().tick(1500) // Have the animation be half way - fx.step() - expect(line.attr('stroke-linecap')).toBe(startValue) - - jasmine.clock().tick(1500) // Have the animation reach its end - fx.step() - expect(line.attr('stroke-linecap')).toBe(endValue) - }) - - it('should be possible to animate color attributes by using SVG.Color', function() { - var startValue = 'rgb(42,251,100)' - , endValue = 'rgb(10,80,175)' - , morph = new SVG.Color(startValue).morph(endValue) - - rect.attr('fill', startValue) - fx.attr('fill', endValue) - - fx.start() - expect(rect.attr('fill')).toBe(morph.at(0).toString()) - - jasmine.clock().tick(250) // Have the animation be half way - fx.step() - expect(rect.attr('fill')).toBe(morph.at(0.5).toString()) - - jasmine.clock().tick(250) // Have the animation reach its end - fx.step() - expect(rect.attr('fill')).toBe(morph.at(1).toString()) - }) - - it('should be possible to pass percentage strings to numeric attributes', function () { - var startValue = '0%' - , endValue = '80%' - , morph = new SVG.Number(startValue).morph(endValue) - - var text = draw.text(function(add) { - add.tspan('We go ') - add.tspan('up').fill('#f09').dy(-40) - add.tspan(', then we go down, then up again').dy(40) - }) - - var path = 'M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100' - - text.path(path).font({ size: 42.5, family: 'Verdana' }) - - text.textPath().attr('startOffset', startValue) - fx = text.textPath().animate(1000).attr('startOffset', endValue) - - fx.start() - expect(text.textPath().attr('startOffset')).toBe(morph.at(0).toString()) - - jasmine.clock().tick(500) // Have the animation be half way - fx.step() - expect(text.textPath().attr('startOffset')).toBe(morph.at(0.5).toString()) - - jasmine.clock().tick(500) // Have the animation reach its end - fx.step() - expect(text.textPath().attr('startOffset')).toBe(morph.at(1).toString()) - }) - - it('should allow 0 to be specified without unit', function () { - // This code snippet come from issue #552 - - var gradient = draw.gradient('linear', function(stop) { - s1 = stop.at(0, '#33235b') - s2 = stop.at(0.5, '#E97639') - s3 = stop.at(1, '#33235b') - }) - - var r1, r2; - var fill = draw.pattern('300%', '100%', function(add) { - r1 = add.rect('150%', '100%').fill(gradient) - r2 = add.rect('150%', '100%').fill(gradient) - }); - fill.attr({patternUnits: 'userSpaceOnUse'}) - - r1.attr('x', 0).animate('0.5s').attr('x', '150%') - r2.attr('x', '-150%').animate('0.5s').attr('x', 0) - - var text = draw.text('Manifesto').move('50%', '50%').fill(fill) - text.font({ - size: 70 - , anchor: 'middle' - , leading: 1 - }) - - r1.fx.start() - r2.fx.start() - - jasmine.clock().tick(250) // Have the animation be half way - r1.fx.step() - r2.fx.step() - expect(r1.attr('x')).toBe('75%') - expect(r2.attr('x')).toBe('-75%') - - jasmine.clock().tick(250) // Have the animation reach its end - r1.fx.step() - r2.fx.step() - expect(r1.attr('x')).toBe('150%') - expect(r2.attr('x')).toBe('0%') - }) - }) - - - describe('when animating styles', function() { - it('should be possible to animate numeric styles', function () { - var startValue = 0 - , endValue = 5 - , morph = new SVG.Number(startValue).morph(endValue) - - rect.style('stroke-width', startValue) - fx.style('stroke-width', endValue) - - fx.start() - expect(rect.style('stroke-width')).toBe(morph.at(0).toString()) - - jasmine.clock().tick(250) // Have the animation be half way - fx.step() - expect(rect.style('stroke-width')).toBe(morph.at(0.5).toString()) - - jasmine.clock().tick(250) // Have the animation reach its end - fx.step() - expect(rect.style('stroke-width')).toBe(morph.at(1).toString()) - }) - - it('should be possible to animate non-numeric styles', function () { - var startValue = 'butt' - , endValue = 'round' - , line = draw.line('0,0 100,150').style('stroke-linecap', startValue) - - fx = line.animate(3000).style('stroke-linecap', endValue) - - fx.start() - expect(line.style('stroke-linecap')).toBe(startValue) - - jasmine.clock().tick(1500) // Have the animation be half way - fx.step() - expect(line.style('stroke-linecap')).toBe(startValue) - - jasmine.clock().tick(1500) // Have the animation reach its end - fx.step() - expect(line.style('stroke-linecap')).toBe(endValue) - }) - - it('should be possible to animate color styles by using SVG.Color', function() { - var startValue = '#81DE01' - , endValue = '#B1835D' - , morph = new SVG.Color(startValue).morph(endValue) - - rect.style('fill', startValue) - fx.style('fill', endValue) - - - fx.start() - // When setting a style color, it get saved as a rgb() string even if it was passed as an hex code - // The style rgb string has spaces while the one returned by SVG.Color do not as show bellow - // CSS: rgb(255, 255, 255) SVG.Color: rgb(255,255,255) - // The space in the style rbg string are removed so they can be equal - expect(rect.style('fill').replace(/\s+/g, '')).toBe(morph.at(0).toRgb()) - - jasmine.clock().tick(250) // Have the animation be half way - fx.step() - expect(rect.style('fill').replace(/ /g, '')).toBe(morph.at(0.5).toRgb()) - - jasmine.clock().tick(250) // Have the animation reach its end - fx.step() - expect(rect.style('fill').replace(/ /g, '')).toBe(morph.at(1).toRgb()) - }) - - it('should be possible to pass percentage strings to numeric styles', function () { - var startValue = '0%' - , endValue = '5%' - , morph = new SVG.Number(startValue).morph(endValue) - - rect.style('stroke-width', startValue) - fx.style('stroke-width', endValue) - - fx.start() - expect(rect.style('stroke-width')).toBe(morph.at(0).toString()) - - jasmine.clock().tick(250) // Have the animation be half way - fx.step() - expect(rect.style('stroke-width')).toBe(morph.at(0.5).toString()) - - jasmine.clock().tick(250) // Have the animation reach its end - fx.step() - expect(rect.style('stroke-width')).toBe(morph.at(1).toString()) - }) - - it('should allow 0 to be specified without a unit', function () { - var r1 = draw.rect(100,100).move(200,200) - , r2 = draw.rect(100,100).move(400,400) - - r1.style('stroke-width', '100%').animate(500).style('stroke-width', 0) - r2.style('stroke-width', 0).animate(500).style('stroke-width', '100%') - - r1.fx.start() - r2.fx.start() - expect(r1.style('stroke-width')).toBe('100%') - expect(r2.style('stroke-width')).toBe('0%') - - jasmine.clock().tick(250) // Have the animation be half way - r1.fx.step() - r2.fx.step() - expect(r1.style('stroke-width')).toBe('50%') - expect(r2.style('stroke-width')).toBe('50%') - - jasmine.clock().tick(250) // Have the animation reach its end - r1.fx.step() - r2.fx.step() - expect(r1.style('stroke-width')).toBe('0%') - expect(r2.style('stroke-width')).toBe('100%') - }) - }) - - - describe('add()', function() { - it('adds to animations obj by default', function() { - fx.add('x', new SVG.Number(20)) - expect(fx.situation.animations.x.value).toBe(20) - }) - - it('adds to specified obj', function() { - fx.add('x', new SVG.Number(20), 'animations') - fx.add('x', new SVG.Number(20), 'attrs') - fx.add('x', new SVG.Number(20), 'styles') - expect(fx.situation.animations.x.value).toBe(20) - expect(fx.situation.attrs.x.value).toBe(20) - expect(fx.situation.styles.x.value).toBe(20) - }) - }) - - describe('attr()', function() { - it('should allow an object to be passed', function() { - spyOn(fx, 'attr').and.callThrough() - fx.attr({ - x: 20, - y: 20 - }) - - expect(fx.attr).toHaveBeenCalledWith('x', 20) - expect(fx.attr).toHaveBeenCalledWith('y', 20) - }) - - it('should call add() with attrs as method', function() { - spyOn(fx, 'add') - fx.attr('x', 20) - expect(fx.add).toHaveBeenCalledWith('x', 20, 'attrs') - }) - }) - - describe('style()', function() { - it('should allow an object to be passed', function() { - spyOn(fx, 'style').and.callThrough() - fx.style({ - x: 20, - y: 20 - }) - - expect(fx.style).toHaveBeenCalledWith('x', 20) - expect(fx.style).toHaveBeenCalledWith('y', 20) - }) - - it('should call add() with styles as method', function() { - spyOn(fx, 'add') - fx.style('x', 20) - expect(fx.add).toHaveBeenCalledWith('x', 20, 'styles') - }) - }) - - describe('x() / y()', function() { - it('should add an entry to the animations obj', function() { - spyOn(fx, 'add') - fx.x(20) - fx.y(20) - - expect(fx.add).toHaveBeenCalledWith('x', jasmine.objectContaining({value:20})) - expect(fx.add).toHaveBeenCalledWith('y', jasmine.objectContaining({value:20})) - }) - - it('allows relative move with relative flag set', function() { - spyOn(fx, 'add') - fx.x(20, true) - fx.y(20, true) - - expect(fx.add).toHaveBeenCalledWith('x', jasmine.objectContaining({value:20, relative:true })) - expect(fx.add).toHaveBeenCalledWith('y', jasmine.objectContaining({value:20, relative:true })) - }) - - it('redirects to transform when target is a group', function() { - var group = draw.group() - , fx = group.animate(500) - - spyOn(fx, 'transform') - - fx.x(20) - fx.y(20) - - expect(fx.transform).toHaveBeenCalledWith({x: 20}, undefined) - expect(fx.transform).toHaveBeenCalledWith({y: 20}, undefined) - }) - - it('redirects to transform when target is a group with relative flag set', function() { - var group = draw.group() - , fx = group.animate(500) - - spyOn(fx, 'transform') - - fx.x(20, true) - fx.y(20, true) - - expect(fx.transform).toHaveBeenCalledWith({x: 20}, true) - expect(fx.transform).toHaveBeenCalledWith({y: 20}, true) - }) - }) - - describe('cx() / cy()', function() { - it('should call add with method and argument', function() { - spyOn(fx, 'add') - fx.cx(20) - fx.cy(20) - - expect(fx.add).toHaveBeenCalledWith('cx', jasmine.objectContaining({value:20})) - expect(fx.add).toHaveBeenCalledWith('cy', jasmine.objectContaining({value:20})) - }) - }) - - describe('move()', function() { - it('should redirect call to x() and y()', function() { - spyOn(fx, 'x').and.callThrough() - spyOn(fx, 'y').and.callThrough() - fx.move(20, 20) - - expect(fx.x).toHaveBeenCalledWith(20) - expect(fx.y).toHaveBeenCalledWith(20) - }) - }) - - describe('center()', function() { - it('should redirect call to cx() and cy()', function() { - spyOn(fx, 'cx').and.callThrough() - spyOn(fx, 'cy').and.callThrough() - fx.center(20, 20) - - expect(fx.cx).toHaveBeenCalledWith(20) - expect(fx.cy).toHaveBeenCalledWith(20) - }) - }) - - describe('size()', function() { - it('should set font-size with attr() when called on a text', function() { - var text = draw.text('Hello World') - , fx = text.animate(500) - - spyOn(fx, 'attr') - fx.size(20) - expect(fx.attr).toHaveBeenCalledWith('font-size', 20) - }) - - it('should set width and height with add()', function() { - spyOn(fx, 'add').and.callThrough() - fx.size(20, 20) - - expect(fx.add).toHaveBeenCalledWith('width', jasmine.objectContaining({value:20})) - expect(fx.add).toHaveBeenCalledWith('height', jasmine.objectContaining({value:20})) - }) - - it('should calculate proportional size when only height or width is given', function() { - spyOn(fx, 'add').and.callThrough() - fx.size(40, null) - fx.size(null, 60) - - expect(fx.add).toHaveBeenCalledWith('width', jasmine.objectContaining({value:40})) - expect(fx.add).toHaveBeenCalledWith('height', jasmine.objectContaining({value:40})) - - expect(fx.add).toHaveBeenCalledWith('width', jasmine.objectContaining({value:60})) - expect(fx.add).toHaveBeenCalledWith('height', jasmine.objectContaining({value:60})) - }) - }) - - describe('width()', function() { - it('should set width with add()', function() { - spyOn(fx, 'add').and.callThrough() - fx.width(20) - expect(fx.add).toHaveBeenCalledWith('width', jasmine.objectContaining({value:20})) - }) - - it('should animate the width attribute', function() { - fx.width(200) - expect(rect.width()).toBe(100) - - jasmine.clock().tick(250) - fx.step() - expect(rect.width()).toBe(150) - - jasmine.clock().tick(250) - fx.step() - expect(rect.width()).toBe(200) - }) - }) - - describe('height()', function() { - it('should set height with add()', function() { - spyOn(fx, 'add').and.callThrough() - fx.height(20) - expect(fx.add).toHaveBeenCalledWith('height', jasmine.objectContaining({value:20})) - }) - - it('should animate the height attribute', function() { - fx.height(200) - expect(rect.height()).toBe(100) - - jasmine.clock().tick(250) - fx.step() - expect(rect.height()).toBe(150) - - jasmine.clock().tick(250) - fx.step() - expect(rect.height()).toBe(200) - }) - }) - - describe('plot()', function() { - it('should call add with plot as method', function() { - var polyline = draw.polyline('10 10 20 20 30 10 50 20') - , fx = polyline.animate(500) - - spyOn(fx, 'add') - fx.plot('5 5 30 29 40 19 12 30') - expect(fx.add).toHaveBeenCalledWith('plot', new SVG.PointArray('5 5 30 29 40 19 12 30')) - }) - - it('also accept parameter list', function() { - var line = draw.line('10 10 20 20') - , fx = line.animate(500) - - spyOn(fx, 'add') - fx.plot(5, 5, 10, 10) - expect(fx.add).toHaveBeenCalledWith('plot', new SVG.PointArray([5, 5, 10, 10])) - }) - }) - - describe('leading()', function() { - it('should call add with method and argument', function() { - var text = draw.text('Hello World') - , fx = text.animate(500) - spyOn(fx, 'add') - fx.leading(3) - - expect(fx.add).toHaveBeenCalledWith('leading', jasmine.objectContaining({value:3})) - }) - - it('does nothiing when not called on text', function() { - spyOn(fx, 'add') - fx.leading(3) - expect(fx.add).not.toHaveBeenCalled() - }) - }) - - describe('viewbox()', function() { - it('should call add with method and argument', function() { - var nested = draw.nested() - , fx = nested.animate(500) - spyOn(fx, 'add') - fx.viewbox(1,2,3,4) - - expect(fx.add).toHaveBeenCalledWith('viewbox', jasmine.objectContaining({x:1, y:2, width:3, height:4})) - }) - - it('does nothing when not called on SVG.Container', function() { - spyOn(fx, 'add') - fx.viewbox(1,2,3,4) - expect(fx.add).not.toHaveBeenCalled() - }) - }) - - describe('update()', function() { - it('should convert call with 3 arguments to call with obj', function() { - var stop = new SVG.Stop() - , fx = stop.animate() - spyOn(fx, 'update').and.callThrough() - fx.update(1,'#ccc',0.5) - - expect(fx.update).toHaveBeenCalledWith({offset: 1, color: '#ccc', opacity: 0.5}) - }) - - it('calls add with method argument and attrs as type', function() { - var stop = new SVG.Stop() - , fx = stop.animate() - spyOn(fx, 'add') - fx.update({offset: 1, color: '#ccc', opacity: 0.5}) - - expect(fx.add).toHaveBeenCalledWith('stop-opacity', 0.5, 'attrs') - expect(fx.add).toHaveBeenCalledWith('stop-color', '#ccc', 'attrs') - expect(fx.add).toHaveBeenCalledWith('offset', 1, 'attrs') - }) - - it('does nothing when not called on SVG.Stop', function() { - spyOn(fx, 'add') - fx.update({offset: 1, color: '#ccc', opacity: 0.5}) - expect(fx.add).not.toHaveBeenCalled() - }) - }) - - describe('transform()', function() { - it('returns itself when no valid transformation was found', function() { - expect(fx.transform({})).toBe(fx) - }) - it('gets the current transforms', function() { - expect(fx.transform()).toEqual(new SVG.Matrix(rect).extract()) - }) - it('gets a certain transformation if used with an argument', function() { - expect(fx.transform('x')).toEqual(0) - }) - it('adds an entry to transforms when matrix given', function() { - var matrix = new SVG.Matrix(1,2,3,4,5,6) - fx.transform(matrix) - expect(fx.situation.transforms[0]).toEqual(jasmine.objectContaining(matrix)) - }) - it('sets relative flag when given', function() { - var matrix = new SVG.Matrix(1,2,3,4,5,6) - fx.transform(matrix, true) - expect(fx.situation.transforms[0]).toEqual(jasmine.objectContaining(matrix)) - expect(fx.situation.transforms[0].relative).toBe(true) - }) - it('adds an entry to transforms when rotation given', function() { - fx.transform({rotation: 30, cx:0, cy:0}) - expect(fx.situation.transforms[0]).toEqual(jasmine.objectContaining(new SVG.Rotate(30, 0, 0))) - }) - it('adds an entry to transforms when scale given', function() { - fx.transform({scale: 2, cx:0, cy:0}) - expect(fx.situation.transforms[0]).toEqual(jasmine.objectContaining(new SVG.Scale(2, 2, 0, 0))) - }) - it('adds an entry to transforms when scaleX given', function() { - fx.transform({scaleX: 2, cx:0, cy:0}) - expect(fx.situation.transforms[0]).toEqual(jasmine.objectContaining(new SVG.Scale(2, 1, 0, 0))) - }) - it('adds an entry to transforms when scaleY given', function() { - fx.transform({scaleY: 2, cx:0, cy:0}) - expect(fx.situation.transforms[0]).toEqual(jasmine.objectContaining(new SVG.Scale(1, 2, 0, 0))) - }) - it('adds an entry to transforms when skewX given', function() { - fx.transform({skewX: 2, cx:0, cy:0}) - expect(fx.situation.transforms[0]).toEqual(jasmine.objectContaining(new SVG.Skew(2, 0, 0, 0))) - }) - it('adds an entry to transforms when skewY given', function() { - fx.transform({skewY: 2, cx:0, cy:0}) - expect(fx.situation.transforms[0]).toEqual(jasmine.objectContaining(new SVG.Skew(0, 2, 0, 0))) - }) - it('adds an entry to transforms when flip x given', function() { - fx.transform({flip: 'x'}) - expect(fx.situation.transforms[0]).toEqual(jasmine.objectContaining((new SVG.Matrix()).flip('x', 150))) - }) - it('adds an entry to transforms when flip x with offset given', function() { - fx.transform({flip: 'x', offset: 100}) - expect(fx.situation.transforms[0]).toEqual(jasmine.objectContaining((new SVG.Matrix()).flip('x', 100))) - }) - it('adds an entry to transforms when flip y given', function() { - fx.transform({flip: 'y'}) - expect(fx.situation.transforms[0]).toEqual(jasmine.objectContaining((new SVG.Matrix()).flip('y', 150))) - }) - it('adds an entry to transforms when x given', function() { - fx.transform({x:20}) - expect(fx.situation.transforms[0]).toEqual(jasmine.objectContaining(new SVG.Translate(20, undefined))) - }) - it('adds an entry to transforms when y given', function() { - fx.transform({y:20}) - expect(fx.situation.transforms[0]).toEqual(jasmine.objectContaining(new SVG.Translate(undefined, 20))) - }) - }) - - /* shortcuts for animation */ - describe('animate()', function() { - it('creates a new fx instance on the element', function() { - var rect = draw.rect(100,100) - rect.animate(100) - expect(rect.fx instanceof SVG.FX).toBeTruthy() - }) - - it('redirects the call to fx.animate()', function() { - spyOn(fx, 'animate') - rect.animate() - expect(fx.animate).toHaveBeenCalled() - }) - }) - - describe('delay()', function() { - it('creates a new fx instance on the element', function() { - var rect = draw.rect(100,100) - rect.delay(100) - expect(rect.fx instanceof SVG.FX).toBeTruthy() - }) - - it('redirects the call to fx.delay()', function() { - spyOn(fx, 'delay') - rect.delay(5) - expect(fx.delay).toHaveBeenCalled() - }) - }) - - describe('stop()', function() { - it('redirects the call to fx.stop()', function() { - spyOn(fx, 'stop') - rect.stop() - expect(fx.stop).toHaveBeenCalled() - }) - }) - - describe('finish()', function() { - it('redirects the call to fx.finish()', function() { - spyOn(fx, 'finish') - rect.finish() - expect(fx.finish).toHaveBeenCalled() - }) - }) - - describe('pause()', function() { - it('redirects the call to fx.pause()', function() { - spyOn(fx, 'pause') - rect.pause() - expect(fx.pause).toHaveBeenCalled() - }) - }) - - describe('play()', function() { - it('redirects the call to fx.play()', function() { - spyOn(fx, 'play') - rect.play() - expect(fx.play).toHaveBeenCalled() - }) - }) - - describe('speed()', function() { - it('redirects the call to fx.speed() as getter', function() { - spyOn(fx, 'speed') - rect.speed() - expect(fx.speed).toHaveBeenCalled() - }) - - it('redirects the call to fx.speed() as setter', function() { - spyOn(fx, 'speed').and.callThrough() - expect(rect.speed(5)).toBe(rect) - expect(fx.speed).toHaveBeenCalled() - }) - }) -}) - -describe('SVG.MorphObj', function() { - it('accepts color strings and converts them to SVG.Color', function() { - var obj = new SVG.MorphObj('#000', '#fff') - expect(obj instanceof SVG.Color).toBeTruthy() - - obj = new SVG.MorphObj('rgb(0,0,0)', 'rgb(255,255,255)') - expect(obj instanceof SVG.Color).toBeTruthy() - }) - - it('accepts numbers and converts them to SVG.Number', function() { - var obj = new SVG.MorphObj('0', '10') - expect(obj instanceof SVG.Number).toBeTruthy() - - var obj = new SVG.MorphObj(0, 10) - expect(obj instanceof SVG.Number).toBeTruthy() - }) - - it('accepts any other values', function() { - var obj = new SVG.MorphObj('Hello', 'World') - - expect(obj.value).toBe('Hello') - expect(obj.destination).toBe('World') - }) - - it('morphes unmorphable objects with plain morphing', function() { - var obj = new SVG.MorphObj('Hello', 'World') - - expect(obj.at(0,0)).toBe('Hello') - expect(obj.at(0.5,0.5)).toBe('Hello') - expect(obj.at(1,1)).toBe('World') - }) - - it('converts to its value when casted', function() { - var obj = new SVG.MorphObj('Hello', 'World') - expect(obj.valueOf()).toBe('Hello') - expect(obj + 'World').toBe('HelloWorld') - }) -}) diff --git a/spec/spec/gradient.js b/spec/spec/gradient.js deleted file mode 100644 index 3fb74f9e2..000000000 --- a/spec/spec/gradient.js +++ /dev/null @@ -1,151 +0,0 @@ -describe('Gradient', function() { - var rect, gradient - - beforeEach(function() { - rect = draw.rect(100,100) - gradient = draw.gradient('linear', function(stop) { - stop.at({ offset: 0, color: '#333', opacity: 1 }) - stop.at({ offset: 1, color: '#fff', opacity: 1 }) - }) - radial = draw.gradient('radial', function(stop) { - stop.at({ offset: 0, color: '#333', opacity: 1 }) - stop.at({ offset: 1, color: '#fff', opacity: 1 }) - }) - }) - - afterEach(function() { - rect.remove() - gradient.remove() - }) - - it('is an instance of SVG.Gradient', function() { - expect(gradient instanceof SVG.Gradient).toBe(true) - }) - - it('allows creation of a new gradient without block', function() { - gradient = draw.gradient('linear') - expect(gradient.children().length).toBe(0) - }) - - describe('fill()', function() { - it('returns the id of the gradient wrapped in url()', function() { - expect(gradient.fill()).toBe('url(#' + gradient.attr('id') + ')') - }) - }) - - describe('from()', function() { - it('sets fx and fy attribute for radial gradients', function() { - radial.from(7, 10) - expect(radial.attr('fx')).toBe(7) - expect(radial.attr('fy')).toBe(10) - }) - it('sets x1 and y1 attribute for linear gradients', function() { - gradient.from(7, 10) - expect(gradient.attr('x1')).toBe(7) - expect(gradient.attr('y1')).toBe(10) - }) - }) - - describe('to()', function() { - it('sets cx and cy attribute for radial gradients', function() { - radial.to(75, 105) - expect(radial.attr('cx')).toBe(75) - expect(radial.attr('cy')).toBe(105) - }) - it('sets x2 and y2 attribute for linear gradients', function() { - gradient.to(75, 105) - expect(gradient.attr('x2')).toBe(75) - expect(gradient.attr('y2')).toBe(105) - }) - }) - - describe('attr()', function() { - it('will catch transform attribues and convert them to gradientTransform', function() { - expect(gradient.translate(100,100).attr('gradientTransform')).toBe('matrix(1,0,0,1,100,100)') - }) - }) - - describe('toString()', function() { - it('returns the id of the gradient wrapped in url()', function() { - expect(gradient + '').toBe('url(#' + gradient.attr('id') + ')') - }) - it('is called when instance is passed as an attribute value', function() { - rect.attr('fill', gradient) - expect(rect.attr('fill')).toBe('url(#' + gradient.attr('id') + ')') - }) - }) - - describe('input values', function() { - var s1, s2 - - it('accepts floats', function() { - gradient = draw.gradient('linear', function(stop) { - s1 = stop.at({ offset: 0.12, color: '#333', opacity: 1 }) - s2 = stop.at({ offset: 0.93, color: '#fff', opacity: 1 }) - }) - expect(s1.attr('offset')).toBe(0.12) - expect(s2.attr('offset')).toBe(0.93) - }) - it('accepts string floats', function() { - gradient = draw.gradient('linear', function(stop) { - s1 = stop.at({ offset: '0.13', color: '#333', opacity: 1 }) - s2 = stop.at({ offset: '0.92', color: '#fff', opacity: 1 }) - }) - expect(s1.attr('offset')).toBe(0.13) - expect(s2.attr('offset')).toBe(0.92) - }) - it('accept percentages', function() { - gradient = draw.gradient('linear', function(stop) { - s1 = stop.at({ offset: '14%', color: '#333', opacity: 1 }) - s2 = stop.at({ offset: '91%', color: '#fff', opacity: 1 }) - }) - expect(s1.attr('offset')).toBe('14%') - expect(s2.attr('offset')).toBe('91%') - }) - }) - - describe('update()', function() { - - it('removes all existing children first', function() { - gradient = draw.gradient('linear', function(stop) { - s1 = stop.at({ offset: 0.12, color: '#333', opacity: 1 }) - s2 = stop.at({ offset: 0.93, color: '#fff', opacity: 1 }) - }) - expect(gradient.children().length).toBe(2) - gradient.update(function(stop) { - s1 = stop.at({ offset: 0.33, color: '#666', opacity: 1 }) - s2 = stop.at({ offset: 1, color: '#000', opacity: 1 }) - }) - expect(gradient.children().length).toBe(2) - }) - - it('accepts multiple aruments on fixed positions', function() { - gradient = draw.gradient('linear', function(stop) { - s1 = stop.at(0.11, '#333') - s2 = stop.at(0.94, '#fff', 0.5) - }) - expect(gradient.children().length).toBe(2) - expect(s1.attr('offset')).toBe(0.11) - expect(s1.attr('stop-color')).toBe('#333333') - expect(s2.attr('offset')).toBe(0.94) - expect(s2.attr('stop-color')).toBe('#ffffff') - expect(s2.attr('stop-opacity')).toBe(0.5) - }) - - }) - - describe('get()', function() { - - it('returns the stop at a given index', function() { - gradient = draw.gradient('linear', function(stop) { - s1 = stop.at({ offset: 0.12, color: '#333', opacity: 1 }) - s2 = stop.at({ offset: 0.93, color: '#fff', opacity: 1 }) - }) - expect(gradient.get(0)).toBe(s1) - expect(gradient.get(1)).toBe(s2) - expect(gradient.get(2)).toBeNull() - }) - - }) - -}) diff --git a/spec/spec/group.js b/spec/spec/group.js deleted file mode 100644 index 4d5ff3655..000000000 --- a/spec/spec/group.js +++ /dev/null @@ -1,116 +0,0 @@ -describe('Group', function() { - var group - - beforeEach(function() { - group = draw.group().move(50, 50) - group.rect(100,100) - }) - - afterEach(function() { - draw.clear() - }) - - describe('x()', function() { - it('returns the value of x without an argument', function() { - expect(group.x()).toBe(50) - }) - it('sets the value of x with the first argument', function() { - group.x(123) - var box = group.gbox() - expect(box.x).toBe(123) - }) - it('sets the value of x correctly when called multiple times', function() { - group.x(10).x(100).x(13) - var box = group.gbox() - expect(box.x).toBe(13) - }) - it('sets the value of x correctly when the first argument is a string number', function(){ - group.x('123') - var box = group.gbox() - expect(box.x).toBe(123) - }) - }) - - describe('y()', function() { - it('returns the value of y without an argument', function() { - expect(group.y()).toBe(50) - }) - it('sets the value of y with the first argument', function() { - group.y(345) - var box = group.gbox() - expect(box.y).toBe(345) - }) - it('sets the value of y correctly when called multiple times', function() { - group.y(1).y(10).y(15) - var box = group.gbox() - expect(box.y).toBe(15) - }) - it('sets the value of y correctly when the first argument is a string number', function(){ - group.y('124') - var box = group.gbox() - expect(box.y).toBe(124) - }) - }) - - describe('cx()', function() { - it('returns the value of cx without an argument', function() { - expect(group.cx()).toBe(100) - }) - it('sets the value of cx with the first argument', function() { - group.cx(123) - var box = group.gbox() - expect(box.cx).toBe(123) - }) - }) - - describe('cy()', function() { - it('returns the value of cy without an argument', function() { - expect(group.cy()).toBe(100) - }) - it('sets the value of cy with the first argument', function() { - group.cy(345) - var box = group.gbox() - expect(box.cy).toBe(345) - }) - }) - - describe('move()', function() { - it('sets the x and y position', function() { - group.move(123,456) - expect(group.node.getAttribute('transform')).toBe('matrix(1,0,0,1,123,456)') - }) - }) - - describe('center()', function() { - it('sets the cx and cy position', function() { - group.center(321,567) - var box = group.gbox() - expect(box.cx).toBe(321) - expect(box.cy).toBe(567) - }) - }) - - describe('dx()', function() { - it('moves the x positon of the element relative to the current position', function() { - group.move(50,60) - group.dx(100) - expect(group.node.getAttribute('transform')).toBe('matrix(1,0,0,1,150,60)') - }) - }) - - describe('dy()', function() { - it('moves the y positon of the element relative to the current position', function() { - group.move(50,60) - group.dy(120) - expect(group.node.getAttribute('transform')).toBe('matrix(1,0,0,1,50,180)') - }) - }) - - describe('dmove()', function() { - it('moves the x and y positon of the element relative to the current position', function() { - group.move(50, 60) - group.dmove(80, 25) - expect(group.node.getAttribute('transform')).toBe('matrix(1,0,0,1,130,85)') - }) - }) -}) diff --git a/spec/spec/helper.js b/spec/spec/helper.js deleted file mode 100644 index 025c9dc05..000000000 --- a/spec/spec/helper.js +++ /dev/null @@ -1,177 +0,0 @@ -// create canavs -//var drawing, window = window, document = document, SVG = SVG - -parserInDoc = false - -if(typeof exports === 'object'){ - window = require('svgdom') - SVG = require('../../dist/svg.js') - document = window.document - drawing = document.documentElement - imageUrl = 'spec/fixtures/pixel.png' - parserInDoc = true - - function tag(name, attrs, children) { - var el = document.createElement(name) - for(var i in attrs){ - el.setAttribute(i, attrs[i]) - } - - for(var i in children){ - if(typeof children[i] == 'string') - children[i] = document.createTextNode(children[i]) - - el.appendChild(children[i]) - } - - return el - } - - // create fixtures in svgdom - var el = tag('svg', { - height:0, - width:0, - id:'inlineSVG' - },[ - tag('defs', {}, [ - tag('linearGradient', {}, [ - tag('stop', {offset: '5%', 'stop-color': 'green'}), - tag('stop', {offset: '95%', 'stop-color': 'gold'}), - ]), - tag('radialGradient', {}, [ - tag('stop', {offset: '5%', 'stop-color': 'green'}), - tag('stop', {offset: '95%', 'stop-color': 'gold'}), - ]) - ]), - tag('desc', {}, ['Some description']), - tag('path', { - id: 'lineAB', - d: 'M 100 350 l 150 -300', - stroke: 'red', - 'stroke-width': '3', - fill: 'none' - }), - tag('path', { - id: 'lineBC', - d: 'M 250 50 l 150 300', - stroke: 'red', - 'stroke-width': '3', - fill: 'none' - }), - tag('path', { - d: 'M 175 200 l 150 0', - stroke: 'green', - 'stroke-width': '3', - fill: 'none' - }), - tag('path', { - d: 'M 100 350 q 150 -300 300 0', - stroke: 'blue', - 'stroke-width': '5', - fill: 'none' - }), - tag('g', { - stroke: 'black', - 'stroke-width': '2', - fill: 'black', - id: 'pointGroup' - },[ - tag('circle', { - id: 'pointA', - cx: '100', - cy: '350', - r: '3', - }), - tag('circle', { - id: 'pointB', - cx: '250', - cy: '50', - r: '50', - }), - tag('circle', { - id: 'pointC', - cx: '400', - cy: '350', - r: '50', - }) - ]), - tag('g', { - 'font-size': '30', - font: 'sans-serif', - fill: 'black', - stroke: 'none', - 'text-anchor': 'middle', - id: 'labelGroup' - },[ - tag('text', { - x: '100', - y: '350', - dy: '-30', - }, ['A']), - tag('text', { - x: '250', - y: '50', - dy: '-10', - }, ['B']), - tag('text', { - x: '400', - y: '350', - dx: '30', - }, ['C']) - ]), - tag('polygon', {points: '200,10 250,190 160,210'}), - tag('polyline', {points: '20,20 40,25 60,40 80,120 120,140 200,180'}) - ]) - - document.appendChild(el) - -}else{ - drawing = document.createElement('div') - document.getElementsByTagName('body')[0].appendChild(drawing) - imageUrl = 'fixtures/pixel.png' -} - -parserInDoc |= 0 -drawing.id = 'drawing' -draw = SVG(drawing).size(100,100) - -parser = parserInDoc ? [SVG.parser.draw.instance] : [] - -// raw path data -svgPath = 'M88.006,61.994c3.203,0,6.216-1.248,8.481-3.514C98.752,56.215,100,53.203,100,50c0-3.204-1.248-6.216-3.513-8.481 c-2.266-2.265-5.278-3.513-8.481-3.513c-2.687,0-5.237,0.877-7.327,2.496h-7.746l5.479-5.479 c5.891-0.757,10.457-5.803,10.457-11.896c0-6.614-5.381-11.995-11.994-11.995c-6.093,0-11.14,4.567-11.896,10.457l-5.479,5.479 v-7.747c1.618-2.089,2.495-4.641,2.495-7.327c0-3.204-1.247-6.216-3.513-8.481C56.216,1.248,53.204,0,50,0 c-3.204,0-6.216,1.248-8.481,3.513c-2.265,2.265-3.513,5.277-3.513,8.481c0,2.686,0.877,5.237,2.495,7.327v7.747l-5.479-5.479 c-0.757-5.89-5.803-10.457-11.896-10.457c-6.614,0-11.995,5.381-11.995,11.995c0,6.093,4.567,11.139,10.458,11.896l5.479,5.479 h-7.747c-2.089-1.619-4.641-2.496-7.327-2.496c-3.204,0-6.216,1.248-8.481,3.513C1.248,43.784,0,46.796,0,50 c0,3.203,1.248,6.216,3.513,8.48c2.265,2.266,5.277,3.514,8.481,3.514c2.686,0,5.237-0.877,7.327-2.496h7.747l-5.479,5.479 c-5.891,0.757-10.458,5.804-10.458,11.896c0,6.614,5.381,11.994,11.995,11.994c6.093,0,11.139-4.566,11.896-10.457l5.479-5.479 v7.749c-3.63,4.7-3.291,11.497,1.018,15.806C43.784,98.752,46.796,100,50,100c3.204,0,6.216-1.248,8.481-3.514 c4.309-4.309,4.647-11.105,1.018-15.806v-7.749l5.479,5.479c0.757,5.891,5.804,10.457,11.896,10.457 c6.613,0,11.994-5.38,11.994-11.994c0-6.093-4.566-11.14-10.457-11.896l-5.479-5.479h7.746 C82.769,61.117,85.319,61.994,88.006,61.994z M76.874,68.354c4.705,0,8.52,3.814,8.52,8.521c0,4.705-3.814,8.52-8.52,8.52 s-8.52-3.814-8.52-8.52l-12.33-12.33V81.98c3.327,3.328,3.327,8.723,0,12.049c-3.327,3.328-8.722,3.328-12.049,0 c-3.327-3.326-3.327-8.721,0-12.049V64.544l-12.33,12.33c0,4.705-3.814,8.52-8.52,8.52s-8.52-3.814-8.52-8.52 c0-4.706,3.814-8.521,8.52-8.521l12.33-12.33H18.019c-3.327,3.328-8.722,3.328-12.049,0c-3.327-3.326-3.327-8.721,0-12.048 s8.722-3.327,12.049,0h17.438l-12.33-12.33c-4.706,0-8.52-3.814-8.52-8.52c0-4.706,3.814-8.52,8.52-8.52s8.52,3.814,8.52,8.52 l12.33,12.33V18.019c-3.327-3.327-3.327-8.722,0-12.049s8.722-3.327,12.049,0s3.327,8.722,0,12.049v17.438l12.33-12.33 c0-4.706,3.814-8.52,8.52-8.52s8.52,3.814,8.52,8.52c0,4.705-3.814,8.52-8.52,8.52l-12.33,12.33h17.438 c3.327-3.327,8.722-3.327,12.049,0s3.327,8.722,0,12.048c-3.327,3.328-8.722,3.328-12.049,0H64.544L76.874,68.354z' - -// image url - - -// lorem ipsum text -loremIpsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras sodales\n imperdiet auctor. Nunc ultrices lectus at erat dictum pharetra\n elementum ante posuere. Duis turpis risus, blandit nec elementum et,\n posuere eget lacus. Aliquam et risus magna, eu aliquet nibh. Fusce\n consequat mi quis purus varius sagittis euismod urna interdum.\n Curabitur aliquet orci quis felis semper vulputate. Vestibulum ac nisi\n magna, id dictum diam. Proin sed metus vel magna blandit\n sodales. Pellentesque at neque ultricies nunc euismod rutrum ut in\n lorem. Mauris euismod tellus in tellus tempus interdum. Phasellus\n mattis sapien et leo feugiat dictum. Vestibulum at volutpat velit.' - -beforeEach(function(){ - // test for touch device - this.isTouchDevice = 'ontouchstart' in document.documentElement -}) - -// strip spaces from result -window.stripped = function(string) { - string = string.replace(/\s+/g, '') - if(string.slice(-1) == ';') string = string.slice(0, -1) - return string -} - -// This is needed because of IE11 which uses space as a delimiter in matrix -window.matrixStringToArray = function(source){ - return source - .replace(/matrix\(|\)/, '') - .split(SVG.regex.delimiter) - .map(parseFloat) -} - -// This is needed because of IE11 creating values like 2.99999 when calculating a transformed box -window.roundBox = function(box) { - return new SVG.Box( - Math.round(box.x), - Math.round(box.y), - Math.round(box.width), - Math.round(box.height) - ) -} diff --git a/spec/spec/hyperlink.js b/spec/spec/hyperlink.js deleted file mode 100644 index affef6d84..000000000 --- a/spec/spec/hyperlink.js +++ /dev/null @@ -1,61 +0,0 @@ -describe('Hyperlink', function() { - var link - , url = 'http://svgjs.com' - - beforeEach(function() { - link = draw.link(url) - link.rect(100,100) - }) - - afterEach(function() { - draw.clear() - }) - - it('creates a link', function() { - expect(link.attr('href')).toBe(url) - }) - - describe('to()', function() { - it('creates xlink:href attribute', function() { - link.to('http://apple.com') - expect(link.attr('href')).toBe('http://apple.com') - }) - }) - - describe('show()', function() { - it('creates xlink:show attribute', function() { - link.show('replace') - expect(link.attr('show')).toBe('replace') - }) - }) - - describe('target()', function() { - it('creates target attribute', function() { - link.target('_blank') - expect(link.attr('target')).toBe('_blank') - }) - }) - - describe('SVG.Element', function() { - var element - - beforeEach(function() { - element = draw.rect(100,100) - }) - - describe('linkTo()', function() { - it('wraps the called element in a link with given url', function() { - element.linkTo(url) - expect(element.parent().attr('href')).toBe(url) - }) - it('wraps the called element in a link with given block', function() { - element.linkTo(function(link) { - link.to(url).target('_blank') - }) - expect(element.parent().attr('href')).toBe(url) - expect(element.parent().attr('target')).toBe('_blank') - }) - }) - }) - -}) \ No newline at end of file diff --git a/spec/spec/image.js b/spec/spec/image.js deleted file mode 100644 index 4b51a9b96..000000000 --- a/spec/spec/image.js +++ /dev/null @@ -1,226 +0,0 @@ -describe('Image', function() { - var image - - beforeEach(function() { - image = draw.image(imageUrl, 100, 100) - }) - - afterEach(function() { - draw.clear() - }) - - - describe('()', function() { - it('should set width and height automatically if no size is given', function(done) { - image = draw.image(imageUrl).loaded(function() { - expect(image.node.getAttribute('height')).toBe('1') - expect(image.node.getAttribute('width')).toBe('1') - done() - }) - }) - it('should set width and height if size is given', function(done) { - image = draw.image(imageUrl, 100, 100).loaded(function() { - expect(image.node.getAttribute('height')).toBe('100') - expect(image.node.getAttribute('width')).toBe('100') - done() - }) - }) - it('returns itself when no url given', function() { - var img = new SVG.Image() - expect(img.load()).toBe(img) - }) - }) - - describe('loaded()', function() { - beforeEach(function(done) { - loadCb = {cb: function(){ done() }} - errorCb = jasmine.createSpy('errorCb') - spyOn(loadCb, 'cb').and.callThrough() - image = draw.image(imageUrl, 100, 100).loaded(loadCb.cb).error(errorCb) - }) - - it('should set the load callback', function() { - expect(image._loaded).toBe(loadCb.cb) - }) - it('executes the load callback', function() { - expect(loadCb.cb).toHaveBeenCalledWith({ - width: 1, - height: 1, - ratio: 1, - url: jasmine.any(String) - }) - }) - it('does not execute the error callback', function() { - expect(errorCb).not.toHaveBeenCalled() - }) - }) - - describe('error()', function() { - beforeEach(function(done) { - loadCb = jasmine.createSpy('loadCb') - errorCb = {cb: function(){ done() }} - spyOn(errorCb, 'cb').and.callThrough() - image = draw.image('not_existant.jpg', 100, 100).loaded(loadCb).error(errorCb.cb) - }) - - it('should set the error callback', function() { - expect(image._error).toBe(errorCb.cb) - }) - it('executes the error callback', function() { - expect(errorCb.cb).toHaveBeenCalledWith(jasmine.any(window.Event)) - }) - it('does not execute the load callback', function() { - expect(loadCb).not.toHaveBeenCalled() - }) - }) - - - describe('x()', function() { - it('should return the value of x without an argument', function() { - expect(image.x()).toBe(0) - }) - it('should set the value of x with the first argument', function() { - image.x(123) - var box = image.bbox() - expect(box.x).toBe(123) - }) - }) - - describe('y()', function() { - it('should return the value of y without an argument', function() { - expect(image.y()).toBe(0) - }) - it('should set the value of y with the first argument', function() { - image.y(345) - var box = image.bbox() - expect(box.y).toBe(345) - }) - }) - - describe('cx()', function() { - it('should return the value of cx without an argument', function() { - expect(image.cx()).toBe(50) - }) - it('should set the value of cx with the first argument', function() { - image.cx(123) - var box = image.bbox() - expect(box.cx).toBe(123) - }) - }) - - describe('cy()', function() { - it('should return the value of cy without an argument', function() { - expect(image.cy()).toBe(50) - }) - it('should set the value of cy with the first argument', function() { - image.cy(345) - var box = image.bbox() - expect(box.cy).toBe(345) - }) - }) - - describe('move()', function() { - it('should set the x and y position', function() { - image.move(123,456) - expect(image.node.getAttribute('x')).toBe('123') - expect(image.node.getAttribute('y')).toBe('456') - }) - }) - - describe('dx()', function() { - it('moves the x positon of the element relative to the current position', function() { - image.move(50,60) - image.dx(100) - expect(image.node.getAttribute('x')).toBe('150') - }) - }) - - describe('dy()', function() { - it('moves the y positon of the element relative to the current position', function() { - image.move(50,60) - image.dy(120) - expect(image.node.getAttribute('y')).toBe('180') - }) - }) - - describe('dmove()', function() { - it('moves the x and y positon of the element relative to the current position', function() { - image.move(50,60) - image.dmove(80, 25) - expect(image.node.getAttribute('x')).toBe('130') - expect(image.node.getAttribute('y')).toBe('85') - }) - }) - - describe('center()', function() { - it('should set the cx and cy position', function() { - image.center(321,567) - var box = image.bbox() - expect(box.cx).toBe(321) - expect(box.cy).toBe(567) - }) - }) - - describe('width()', function() { - it('sets the width of the element', function() { - image.width(789) - expect(image.node.getAttribute('width')).toBe('789') - }) - it('gets the width of the element if the argument is null', function() { - expect(image.width().toString()).toBe(image.node.getAttribute('width')) - }) - }) - - describe('height()', function() { - it('sets the height of the element', function() { - image.height(1236) - expect(image.node.getAttribute('height')).toBe('1236') - }) - it('gets the height of the element if the argument is null', function() { - expect(image.height().toString()).toBe(image.node.getAttribute('height')) - }) - }) - - describe('size()', function() { - it('should define the width and height of the element', function() { - image.size(987,654) - expect(image.node.getAttribute('width')).toBe('987') - expect(image.node.getAttribute('height')).toBe('654') - }) - it('defines the width and height proportionally with only the width value given', function() { - var box = image.bbox() - image.size(500) - expect(image.width()).toBe(500) - expect(image.width() / image.height()).toBe(box.width / box.height) - }) - it('defines the width and height proportionally with only the height value given', function() { - var box = image.bbox() - image.size(null, 525) - expect(image.height()).toBe(525) - expect(image.width() / image.height()).toBe(box.width / box.height) - }) - }) - - describe('scale()', function() { - it('should scale the element universally with one argument', function() { - var box = image.scale(2).rbox() - - expect(box.width).toBe(image.attr('width') * 2) - expect(box.height).toBe(image.attr('height') * 2) - }) - it('should scale the element over individual x and y axes with two arguments', function() { - var box = image.scale(2, 3.5).rbox() - - expect(box.width).toBe(image.attr('width') * 2) - expect(box.height).toBe(image.attr('height') * 3.5) - }) - }) - - describe('translate()', function() { - it('should set the translation of an element', function() { - image.transform({ x: 12, y: 12 }) - expect(image.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') - }) - }) - -}) diff --git a/spec/spec/line.js b/spec/spec/line.js deleted file mode 100644 index 219043d96..000000000 --- a/spec/spec/line.js +++ /dev/null @@ -1,244 +0,0 @@ -describe('Line', function() { - var line - - beforeEach(function() { - line = draw.line(0,100,100,0) - }) - - afterEach(function() { - draw.clear() - }) - - // #487 - describe('()', function(){ - it('will take an array as input', function(){ - line = draw.line([[0,100],[100,0]]) - var attrs = line.attr() - expect(attrs.x1).toBe(0) - expect(attrs.y1).toBe(100) - expect(attrs.x2).toBe(100) - expect(attrs.y2).toBe(0) - }) - - it('falls back to a line with its two points at [0,0] without an argument', function() { - line = draw.line() - var attrs = line.attr() - expect(attrs.x1).toBe(0) - expect(attrs.y1).toBe(0) - expect(attrs.x2).toBe(0) - expect(attrs.y2).toBe(0) - }) - }) - - describe('x()', function() { - it('should return the value of x without an argument', function() { - expect(line.x()).toBe(0) - }) - it('should set the value of x with the first argument', function() { - line.x(123) - var box = line.bbox() - expect(box.x).toBe(123) - }) - }) - - describe('y()', function() { - it('should return the value of y without an argument', function() { - expect(line.y()).toBe(0) - }) - it('should set the value of y with the first argument', function() { - line.y(345) - var box = line.bbox() - expect(box.y).toBe(345) - }) - }) - - describe('cx()', function() { - it('should return the value of cx without an argument', function() { - expect(line.cx()).toBe(50) - }) - it('should set the value of cx with the first argument', function() { - line.cx(123) - var box = line.bbox() - expect(box.cx).toBe(123) - }) - }) - - describe('cy()', function() { - it('should return the value of cy without an argument', function() { - expect(line.cy()).toBe(50) - }) - it('should set the value of cy with the first argument', function() { - line.cy(345) - var box = line.bbox() - expect(box.cy).toBe(345) - }) - }) - - describe('move()', function() { - it('should set the x and y position', function() { - line.move(123,456) - var box = line.bbox() - expect(box.x).toBe(123) - expect(box.y + box.height).toBe(556) - expect(box.x + box.width).toBe(223) - expect(box.y).toBe(456) - }) - }) - - describe('dx()', function() { - it('moves the x positon of the element relative to the current position', function() { - line.move(50,60) - line.dx(100) - var box = line.bbox() - expect(box.x).toBe(150) - expect(box.y + box.height).toBe(160) - expect(box.x + box.width).toBe(250) - expect(box.y).toBe(60) - }) - }) - - describe('dy()', function() { - it('moves the y positon of the element relative to the current position', function() { - line.move(50, 60) - line.dy(120) - var box = line.bbox() - expect(box.x).toBe(50) - expect(box.y + box.height).toBe(280) - expect(box.x + box.width).toBe(150) - expect(box.y).toBe(180) - }) - }) - - describe('dmove()', function() { - it('moves the x and y positon of the element relative to the current position', function() { - line.move(50,60) - line.dmove(80, 25) - var box = line.bbox() - expect(box.x).toBe(130) - expect(box.y + box.height).toBe(185) - expect(box.x + box.width).toBe(230) - expect(box.y).toBe(85) - }) - }) - - describe('center()', function() { - it('should set the cx and cy position', function() { - line.center(321,567) - var box = line.bbox() - expect(box.x).toBe(271) - expect(box.y + box.height).toBe(617) - expect(box.x + box.width).toBe(371) - expect(box.y).toBe(517) - }) - }) - - describe('width()', function() { - it('sets the width of the element', function() { - line.width(400) - var box = line.bbox() - expect(box.x).toBe(0) - expect(box.x + box.width).toBe(400) - }) - it('get the width of the element without argument', function() { - line.width(123) - var box = line.bbox() - expect(line.width()).toBe(box.width) - }) - }) - - describe('height()', function() { - it('sets the height of the element', function() { - line.height(300) - var box = line.bbox() - expect(box.y).toBe(0) - expect(box.y + box.height).toBe(300) - }) - it('gets the height of the element without argument', function() { - line.height(456) - var box = line.bbox() - expect(line.height()).toBe(box.height) - }) - }) - - describe('size()', function() { - it('should define the width and height of the element', function() { - line.size(987,654) - var box = line.bbox() - expect(box.x).toBe(0) - expect(box.y + box.height).toBe(654) - expect(box.x + box.width).toBe(987) - expect(box.y).toBe(0) - }) - it('defines the width and height proportionally with only the width value given', function() { - var box = line.bbox() - line.size(500) - expect(line.width()).toBe(500) - expect(line.width() / line.height()).toBe(box.width / box.height) - }) - it('defines the width and height proportionally with only the height value given', function() { - var box = line.bbox() - line.size(null, 525) - expect(line.height()).toBe(525) - expect(line.width() / line.height()).toBe(box.width / box.height) - }) - }) - - describe('scale()', function() { - it('should scale the element universally with one argument', function() { - var box1 = line.rbox() - , box2 = line.scale(2).rbox() - - expect(box2.width).toBe(box1.width * 2) - expect(box2.height).toBe(box1.height * 2) - }) - it('should scale the element over individual x and y axes with two arguments', function() { - var box1 = line.rbox() - , box2 = line.scale(2,3.5).rbox() - - expect(box2.width).toBe(box1.width * 2) - expect(box2.height).toBe(box1.height * 3.5) - }) - }) - - describe('translate()', function() { - it('should set the translation of an element', function() { - line.transform({ x: 12, y: 12 }) - expect(line.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') - }) - }) - - describe('plot()', function() { - it('should update the start and end points', function() { - line.plot(100,200,300,400) - var box = line.bbox() - expect(box.x).toBe(100) - expect(box.y).toBe(200) - expect(box.x + box.width).toBe(300) - expect(box.y + box.height).toBe(400) - }) - it('change the x1,y1,x2,y2 attributes of the underlying line node when a string is passed', function() { - expect(line.plot('100,50 200,10')).toBe(line) - - var attrs = line.attr() - expect(attrs.x1).toBe(100) - expect(attrs.y1).toBe(50) - expect(attrs.x2).toBe(200) - expect(attrs.y2).toBe(10) - }) - it('change the x1,y1,x2,y2 attributes of the underlying line node when 4 numbers are passed', function() { - expect(line.plot(45, 24, 220, 300)).toBe(line) - - var attrs = line.attr() - expect(attrs.x1).toBe(45) - expect(attrs.y1).toBe(24) - expect(attrs.x2).toBe(220) - expect(attrs.y2).toBe(300) - }) - it('return the coordinates in a point array when no arguments are passed', function () { - var attrs = line.attr() - , pointArray = new SVG.PointArray([[attrs.x1, attrs.y1], [attrs.x2, attrs.y2]]) - - expect(line.plot()).toEqual(pointArray) - }) - }) -}) diff --git a/spec/spec/marker.js b/spec/spec/marker.js deleted file mode 100644 index 7b902d4d8..000000000 --- a/spec/spec/marker.js +++ /dev/null @@ -1,89 +0,0 @@ -describe('Marker', function() { - - describe('on a container element', function() { - var marker - - beforeEach(function() { - marker = draw.marker(10, 12, function(add) { - add.rect(10, 12) - - this.ref(5, 6) - }) - }) - - it('creates an instance of SVG.Marker', function() { - expect(marker instanceof SVG.Marker).toBeTruthy() - }) - - it('creates marker in defs', function() { - expect(marker.parent() instanceof SVG.Defs).toBeTruthy() - }) - - describe('marker()', function() { - it('returns the marker element', function() { - expect(marker = draw.marker(10, 12)).toBe(marker) - }) - it('sets the refX to half of the given width', function() { - marker = draw.marker(10, 12) - expect(marker.node.getAttribute('refX')).toBe('5') - }) - it('sets the refY to half of the given height', function() { - marker = draw.marker(13, 15) - expect(marker.node.getAttribute('refY')).toBe('7.5') - }) - }) - - }) - - describe('on a target path', function() { - var path, marker - - beforeEach(function() { - path = draw.path('M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100') - - path.marker('mid', 10, 12, function(add) { - add.rect(10, 12) - - this.ref(5, 6) - }) - - marker = path.marker('mid', 10, 10) - }) - - it('creates an instance of SVG.Marker', function() { - expect(path.reference('marker-mid') instanceof SVG.Marker).toBeTruthy() - }) - - describe('marker()', function() { - it('returns the target element', function() { - expect(path.marker('start', 10, 12)).toBe(path) - }) - it('creates a marker and applies it to the marker-start attribute', function() { - path.marker('start', 10, 12) - marker = path.reference('marker-start') - - expect(path.node.getAttribute('marker-start')).toBe(marker.toString()) - }) - it('creates a marker and applies it to the marker-mid attribute', function() { - path.marker('mid', 10, 12) - marker = path.reference('marker-mid') - - expect(path.node.getAttribute('marker-mid')).toBe(marker.toString()) - }) - it('creates a marker and applies it to the marker-end attribute', function() { - path.marker('end', 10, 12) - marker = path.reference('marker-end') - - expect(path.node.getAttribute('marker-end')).toBe(marker.toString()) - }) - it('accepts an instance of an existing marker element as the second argument', function() { - marker = draw.marker(11, 11) - path.marker('mid', marker) - - expect(path.node.getAttribute('marker-mid')).toBe(marker.toString()) - }) - }) - }) - - -}) \ No newline at end of file diff --git a/spec/spec/mask.js b/spec/spec/mask.js deleted file mode 100644 index a7cd6d1ee..000000000 --- a/spec/spec/mask.js +++ /dev/null @@ -1,62 +0,0 @@ -describe('Mask', function() { - var rect, circle - - beforeEach(function() { - rect = draw.rect(100,100) - circle = draw.circle(100).move(50, 50).fill('#fff') - rect.maskWith(circle) - }) - - afterEach(function() { - draw.clear() - }) - - it('moves the masking element to a new mask node', function() { - expect(circle.parent() instanceof SVG.Mask).toBe(true) - }) - - it('creates the mask node in the defs node', function() { - expect(circle.parent().parent()).toBe(draw.defs()) - }) - - it('sets the "mask" attribute on the masked element with the mask id', function() { - expect(rect.attr('mask')).toBe('url("#' + circle.parent().attr('id') + '")') - }) - - it('references the mask element in the masked element', function() { - expect(rect.masker).toBe(circle.parent()) - }) - - it('references the masked element in the mask target list', function() { - expect(rect.masker.targets.indexOf(rect) > -1).toBe(true) - }) - - it('reuses mask element when mask was given', function() { - var mask = rect.masker - expect(draw.rect(100,100).maskWith(mask).masker).toBe(mask) - }) - - it('unmasks all masked elements when being removed', function() { - rect.masker.remove() - expect(rect.attr('mask')).toBe(undefined) - }) - - describe('unmask()', function() { - - it('clears the "mask" attribute on the masked element', function() { - rect.unmask() - expect(rect.attr('mask')).toBe(undefined) - }) - - it('removes the reference to the masking element', function() { - rect.unmask() - expect(rect.masker).toBe(undefined) - }) - - it('returns the element itslef', function() { - expect(rect.unmask()).toBe(rect) - }) - - }) - -}) \ No newline at end of file diff --git a/spec/spec/matrix.js b/spec/spec/matrix.js deleted file mode 100644 index 0816f6637..000000000 --- a/spec/spec/matrix.js +++ /dev/null @@ -1,471 +0,0 @@ -describe('Matrix', function() { - var matrix - - describe('initialization', function() { - - describe('without a source', function() { - - beforeEach(function() { - matrix = new SVG.Matrix - }) - - it('creates a new matrix with default values', function() { - expect(matrix.a).toBe(1) - expect(matrix.b).toBe(0) - expect(matrix.c).toBe(0) - expect(matrix.d).toBe(1) - expect(matrix.e).toBe(0) - expect(matrix.f).toBe(0) - }) - - describe('extract()', function() { - var extract - - beforeEach(function() { - extract = matrix.extract() - }) - - it('parses translation values', function() { - expect(extract.x).toBe(0) - expect(extract.y).toBe(0) - }) - it('parses skew values', function() { - expect(extract.skewX).toBe(0) - expect(extract.skewY).toBe(0) - }) - it('parses scale values', function() { - expect(extract.scaleX).toBe(1) - expect(extract.scaleY).toBe(1) - }) - it('parses rotatoin value', function() { - expect(extract.rotation).toBe(0) - }) - }) - - describe('toString()' , function() { - it('exports correctly to a string', function() { - expect(matrix.toString()).toBe('matrix(1,0,0,1,0,0)') - }) - }) - }) - - describe('with an element given', function() { - var rect - - beforeEach(function() { - rect = draw.rect(100, 100) - .transform({ rotation: -10 }, true) - .transform({ x: 40, y: 50 }, true) - .transform({ scale: 2 }, true) - - matrix = new SVG.Matrix(rect) - }) - - it('parses the current transform matrix from an element', function() { - expect(matrix.a).toBeCloseTo(1.9696155786514282) - expect(matrix.b).toBeCloseTo(-0.3472963869571686) - expect(matrix.c).toBeCloseTo(0.3472963869571686) - expect(matrix.d).toBeCloseTo(1.9696155786514282) - expect(matrix.e).toBeCloseTo(-17.770875930786133) - expect(matrix.f).toBeCloseTo(11.178505897521973) - }) - - describe('extract()', function() { - - it('parses translation values', function() { - var extract = new SVG.Matrix(draw.rect(100, 100).translate(40, 50)).extract() - expect(extract.x).toBeCloseTo(40) - expect(extract.y).toBeCloseTo(50) - }) - it('parses skewX value', function() { - var extract = new SVG.Matrix(draw.rect(100, 100).skew(25, 0)).extract() - expect(extract.skewX).toBeCloseTo(25) - }) - it('parses skewY value', function() { - var extract = new SVG.Matrix(draw.rect(100, 100).skew(0, 20)).extract() - expect(extract.skewY).toBeCloseTo(20) - }) - it('parses scale values', function() { - var extract = new SVG.Matrix(draw.rect(100, 100).scale(2, 3)).extract() - expect(extract.scaleX).toBeCloseTo(2) - expect(extract.scaleY).toBeCloseTo(3) - }) - it('parses rotatoin value', function() { - var extract = new SVG.Matrix(draw.rect(100, 100).rotate(-100)).extract() - expect(extract.rotation).toBeCloseTo(-100) - }) - - }) - - }) - - describe('with a string given', function() { - it('parses the string value correctly', function() { - var matrix = new SVG.Matrix('2, 0, 0, 2, 100, 50') - - expect(matrix.a).toBe(2) - expect(matrix.b).toBe(0) - expect(matrix.c).toBe(0) - expect(matrix.d).toBe(2) - expect(matrix.e).toBe(100) - expect(matrix.f).toBe(50) - }) - }) - - describe('with an array given', function() { - it('parses the array correctly', function() { - var matrix = new SVG.Matrix([2, 0, 0, 2, 100, 50]) - - expect(matrix.a).toBe(2) - expect(matrix.b).toBe(0) - expect(matrix.c).toBe(0) - expect(matrix.d).toBe(2) - expect(matrix.e).toBe(100) - expect(matrix.f).toBe(50) - }) - }) - - describe('with an object given', function() { - it('parses the object correctly', function() { - var matrix = new SVG.Matrix({a:2, b:0, c:0, d:2, e:100, f:50}) - - expect(matrix.a).toBe(2) - expect(matrix.b).toBe(0) - expect(matrix.c).toBe(0) - expect(matrix.d).toBe(2) - expect(matrix.e).toBe(100) - expect(matrix.f).toBe(50) - }) - }) - - describe('with 6 arguments given', function() { - it('parses the arguments correctly', function() { - var matrix = new SVG.Matrix(2, 0, 0, 2, 100, 50) - - expect(matrix.a).toBe(2) - expect(matrix.b).toBe(0) - expect(matrix.c).toBe(0) - expect(matrix.d).toBe(2) - expect(matrix.e).toBe(100) - expect(matrix.f).toBe(50) - }) - }) - - }) - - describe('clone()', function() { - it('returns a clone of the matrix', function() { - var matrix = new SVG.Matrix(2, 0, 0, 5, 0, 0) - , clone = matrix.clone() - expect(matrix).not.toBe(clone) - for(var i in 'abcdef') { - expect(matrix[i]).toEqual(clone[i]) - } - }) - }) - - describe('morph()', function() { - it('stores a given matrix for morphing', function() { - var matrix1 = new SVG.Matrix(2, 0, 0, 5, 0, 0) - , matrix2 = new SVG.Matrix(1, 0, 0, 1, 4, 3) - - matrix1.morph(matrix2) - - expect(matrix1.destination).toEqual(matrix2) - }) - it('stores a clone, not the given matrix itself', function() { - var matrix1 = new SVG.Matrix(2, 0, 0, 5, 0, 0) - , matrix2 = new SVG.Matrix(1, 0, 0, 1, 4, 3) - - matrix1.morph(matrix2) - - expect(matrix1.destination).not.toBe(matrix2) - }) - }) - - describe('at()', function() { - it('returns a morphed array at a given position', function() { - var matrix1 = new SVG.Matrix(2, 0, 0, 5, 0, 0) - , matrix2 = new SVG.Matrix(1, 0, 0, 1, 4, 3) - , matrix3 = matrix1.morph(matrix2).at(0.5) - - expect(matrix1.toString()).toBe('matrix(2,0,0,5,0,0)') - expect(matrix2.toString()).toBe('matrix(1,0,0,1,4,3)') - expect(matrix3.toString()).toBe('matrix(1.5,0,0,3,2,1.5)') - }) - it('returns itself when no destination specified', function() { - var matrix = new SVG.Matrix(2, 0, 0, 5, 0, 0) - expect(matrix.at(0.5)).toBe(matrix) - }) - }) - - describe('multiply()', function() { - it('multiplies two matices', function() { - var matrix1 = new SVG.Matrix(2, 0, 0, 5, 0, 0) - , matrix2 = new SVG.Matrix(1, 0, 0, 1, 4, 3) - , matrix3 = matrix1.multiply(matrix2) - - expect(matrix1.toString()).toBe('matrix(2,0,0,5,0,0)') - expect(matrix2.toString()).toBe('matrix(1,0,0,1,4,3)') - expect(matrix3.toString()).toBe('matrix(2,0,0,5,8,15)') - }) - it('accepts matrices in any form', function() { - var matrix1 = new SVG.Matrix(2, 0, 0, 5, 0, 0) - , matrix2 = matrix1.multiply('1,0,0,1,4,3') - - expect(matrix1.toString()).toBe('matrix(2,0,0,5,0,0)') - expect(matrix2.toString()).toBe('matrix(2,0,0,5,8,15)') - }) - }) - - describe('inverse()', function() { - it('inverses matrix', function() { - - var matrix1 = new SVG.Matrix(2, 0, 0, 5, 4, 3) - , matrix2 = matrix1.inverse() - , abcdef = [0.5,0,0,0.2,-2,-0.6] - - expect(matrix1.toString()).toBe('matrix(2,0,0,5,4,3)') - - for(var i in 'abcdef') { - expect(matrix2['abcdef'[i]]).toBeCloseTo(abcdef[i]) - } - }) - }) - - describe('translate()', function() { - it('translates matrix by given x and y values', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).translate(10, 12.5) - - expect(matrix.e).toBe(14) - expect(matrix.f).toBe(15.5) - }) - }) - - describe('scale()', function() { - it('performs a uniformal scale with one value', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).scale(3) - - expect(matrix.a).toBe(3) - expect(matrix.d).toBe(3) - expect(matrix.e).toBe(4) - expect(matrix.f).toBe(3) - }) - it('performs a non-uniformal scale with two values', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).scale(2.5, 3.5) - - expect(matrix.a).toBe(2.5) - expect(matrix.d).toBe(3.5) - expect(matrix.e).toBe(4) - expect(matrix.f).toBe(3) - }) - it('performs a uniformal scale at a given center point with three values', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).scale(3, 150, 100) - - expect(matrix.a).toBe(3) - expect(matrix.d).toBe(3) - expect(matrix.e).toBe(-296) - expect(matrix.f).toBe(-197) - }) - it('performs a non-uniformal scale at a given center point with four values', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).scale(3, 2, 150, 100) - - expect(matrix.a).toBe(3) - expect(matrix.d).toBe(2) - expect(matrix.e).toBe(-296) - expect(matrix.f).toBe(-97) - }) - }) - - describe('rotate()', function() { - it('performs a rotation with one argument', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).rotate(30) - - expect(matrix.a).toBeCloseTo(0.8660254037844387) - expect(matrix.d).toBeCloseTo(0.8660254037844387) - expect(matrix.e).toBe(4) - expect(matrix.f).toBe(3) - }) - it('performs a rotation on a given point with three arguments', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).rotate(30, 150, 100) - - expect(matrix.a).toBeCloseTo(0.8660254037844387) - expect(matrix.d).toBeCloseTo(0.8660254037844387) - expect(matrix.e).toBeCloseTo(74.0961894323342) - expect(matrix.f).toBeCloseTo(-58.60254037844388) - }) - }) - - describe('flip()', function() { - describe('with x given', function() { - it('performs a flip over the horizontal axis with one argument', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).flip('x') - - expect(matrix.a).toBe(-1) - expect(matrix.d).toBe(1) - expect(matrix.e).toBe(4) - expect(matrix.f).toBe(3) - }) - it('performs a flip over the horizontal axis over a given point with two arguments', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).flip('x', 150) - - expect(matrix.a).toBe(-1) - expect(matrix.d).toBe(1) - expect(matrix.e).toBe(304) - expect(matrix.f).toBe(3) - }) - }) - describe('with y given', function() { - it('performs a flip over the vertical axis with one argument', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).flip('y') - - expect(matrix.a).toBe(1) - expect(matrix.d).toBe(-1) - expect(matrix.e).toBe(4) - expect(matrix.f).toBe(3) - }) - it('performs a flip over the vertical axis over a given point with two arguments', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).flip('y', 100) - - expect(matrix.a).toBe(1) - expect(matrix.d).toBe(-1) - expect(matrix.e).toBe(4) - expect(matrix.f).toBe(203) - }) - }) - describe('with no axis given', function() { - it('performs a flip over the horizontal and vertical axis with no argument', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).flip() - - expect(matrix.a).toBe(-1) - expect(matrix.d).toBe(-1) - expect(matrix.e).toBe(4) - expect(matrix.f).toBe(3) - }) - it('performs a flip over the horizontal and vertical axis over a given point with one argument that represent both coordinates', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).flip(100) - - expect(matrix.a).toBe(-1) - expect(matrix.d).toBe(-1) - expect(matrix.e).toBe(204) - expect(matrix.f).toBe(203) - }) - it('performs a flip over the horizontal and vertical axis over a given point with two arguments', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).flip(50, 100) - - expect(matrix.a).toBe(-1) - expect(matrix.d).toBe(-1) - expect(matrix.e).toBe(104) - expect(matrix.f).toBe(203) - }) - }) - }) - - describe('skew()', function() { - it('performs a uniformal skew with one value', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).skew(14) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBeCloseTo(0.24932800284318) - expect(matrix.c).toBeCloseTo(0.24932800284318) - expect(matrix.d).toBe(1) - expect(matrix.e).toBe(4) - expect(matrix.f).toBe(3) - }) - it('performs a non-uniformal skew with two values', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).skew(8, 5) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBeCloseTo(0.087488663525924) - expect(matrix.c).toBeCloseTo(0.14054083470239) - expect(matrix.d).toBe(1) - expect(matrix.e).toBe(4) - expect(matrix.f).toBe(3) - }) - it('performs a uniformal skew at a given center point with three values', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).skew(3, 150, 100) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBeCloseTo(0.052407779283041) - expect(matrix.c).toBeCloseTo(0.052407779283041) - expect(matrix.d).toBe(1) - expect(matrix.e).toBeCloseTo(-1.2407779283) - expect(matrix.f).toBeCloseTo(-4.8611668924562) - }) - it('performs a non-uniformal skew at a given center point with four values', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).skew(9, 7, 150, 100) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBeCloseTo(0.1227845609029) - expect(matrix.c).toBeCloseTo(0.15838444032454) - expect(matrix.d).toBe(1) - expect(matrix.e).toBeCloseTo(-11.83844403245) - expect(matrix.f).toBeCloseTo(-15.417684135435) - }) - it('can be chained', function(){ - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).skew(9, 7).skew(20, 40) - - expect(matrix.a).toBeCloseTo(1.1329003254605) - expect(matrix.b).toBeCloseTo(0.96188419208018) - expect(matrix.c).toBeCloseTo(0.52235467459074) - expect(matrix.d).toBeCloseTo(1.0446899253961) - expect(matrix.e).toBe(4) - expect(matrix.f).toBe(3) - }) - }) - - describe('skewX', function(){ - it('performs a skew along the x axis with one value', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).skewX(12) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBe(0) - expect(matrix.c).toBeCloseTo(0.21255656167002) - expect(matrix.d).toBe(1) - expect(matrix.e).toBe(4) - expect(matrix.f).toBe(3) - }) - - it('performs a skew along the x axis at a given center point with three values', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).skewX(5, 150, 100) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBe(0) - expect(matrix.c).toBeCloseTo(0.087488663525924) - expect(matrix.d).toBe(1) - expect(matrix.e).toBeCloseTo(-4.74886635259) - expect(matrix.f).toBe(3) - }) - }) - - describe('skewY', function(){ - it('performs a skew along the y axis with one value', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).skewY(12) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBeCloseTo(0.21255656167002) - expect(matrix.c).toBe(0) - expect(matrix.d).toBe(1) - expect(matrix.e).toBe(4) - expect(matrix.f).toBe(3) - }) - - it('performs a skew along the y axis at a given center point with three values', function() { - var matrix = new SVG.Matrix(1, 0, 0, 1, 4, 3).skewY(5, 150, 100) - - expect(matrix.a).toBe(1) - expect(matrix.b).toBeCloseTo(0.087488663525924) - expect(matrix.c).toBe(0) - expect(matrix.d).toBe(1) - expect(matrix.e).toBe(4) - expect(matrix.f).toBeCloseTo(-10.123299528889) - }) - }) - - describe('native()', function() { - it('returns the node reference', function() { - expect(new SVG.Matrix().native() instanceof window.SVGMatrix).toBeTruthy() - }) - }) - -}) diff --git a/spec/spec/memory.js b/spec/spec/memory.js deleted file mode 100644 index 32773a090..000000000 --- a/spec/spec/memory.js +++ /dev/null @@ -1,58 +0,0 @@ -describe('Memory', function () { - var rect, circle - - beforeEach(function() { - rect = draw.rect(100,120) - circle = draw.circle(100) - }) - - afterEach(function() { - draw.clear() - }) - - describe('remember()', function() { - it('accepts an object with values', function() { - rect.remember({ some: {cool:'and',nested:'stuff',foo:5} }) - expect(rect.remember('some').foo).toBe(5) - }) - it('accepts key / value arguments', function() { - rect.remember('fill', rect.attr('fill')) - rect.fill('#f09') - expect(rect.remember('fill')).toBe('#000000') - }) - it('acts as a getter with one string argument', function() { - rect.remember('opacity', 0.85) - expect(rect.remember('opacity')).toBe(0.85) - }) - it('saves values to individual objects', function() { - rect.remember('opacity', 0.85) - circle.remember('opacity', 0.5) - expect(rect.remember('opacity')).toBe(0.85) - expect(circle.remember('opacity')).toBe(0.5) - }) - }) - - describe('forget()', function() { - it('deletes a given memory', function() { - rect.remember({ grass: 'is green', one: 1 }) - rect.forget('grass') - expect(rect.remember('grass')).toBe(undefined) - expect(rect.remember('one')).toBe(1) - }) - it('accepts multiple arguments as different memories', function() { - rect.remember({ grass: 'might be purple', two: 2, sea: true }) - rect.forget('grass', 'sea') - expect(rect.remember('grass')).toBe(undefined) - expect(rect.remember('sea')).toBe(undefined) - expect(rect.remember('two')).toBe(2) - }) - it('clears the whole memory without arguments', function() { - rect.remember({ grass: 'is never pink', three: 3, tree: true }) - rect.forget() - expect(rect.remember('grass')).toBe(undefined) - expect(rect.remember('tree')).toBe(undefined) - expect(rect.remember('three')).toBe(undefined) - }) - }) - -}) \ No newline at end of file diff --git a/spec/spec/modules/core/attr.js b/spec/spec/modules/core/attr.js new file mode 100644 index 000000000..9a52c563c --- /dev/null +++ b/spec/spec/modules/core/attr.js @@ -0,0 +1,133 @@ +/* globals describe, expect, it, beforeEach, spyOn, jasmine */ + +import { Element, create, Text, Rect } from '../../../../src/main.js' +import { registerAttrHook } from '../../../../src/modules/core/attr.js' + +const { objectContaining } = jasmine + +describe('attr.js', () => { + describe('attr()', () => { + let element + + beforeEach(() => { + element = new Element(create('rect')) + }) + + it('returns itself as setter', () => { + expect(element.attr('fill', '#ff0066')).toBe(element) + }) + + it('sets one attribute when two arguments are given', () => { + element.attr('fill', '#ff0066') + expect(element.node.getAttribute('fill')).toBe('#ff0066') + }) + + it('sets various attributes when an object is given', () => { + element.attr({ fill: '#00ff66', stroke: '#ff2233', 'stroke-width': 10 }) + expect(element.node.getAttribute('fill')).toBe('#00ff66') + expect(element.node.getAttribute('stroke')).toBe('#ff2233') + expect(element.node.getAttribute('stroke-width')).toBe('10') + }) + + it('gets the value of the string value given as first argument', () => { + element.attr('fill', '#ff0066') + expect(element.attr('fill')).toEqual('#ff0066') + }) + + it('gets an object with all attributes without any arguments', () => { + element.attr({ fill: '#00ff66', stroke: '#ff2233' }) + var attr = element.attr() + expect(attr.fill).toBe('#00ff66') + expect(attr.stroke).toBe('#ff2233') + }) + + it('removes an attribute if the second argument is explicitly set to null', () => { + element.attr('stroke-width', 10) + expect(element.node.getAttribute('stroke-width')).toBe('10') + element.attr('stroke-width', null) + expect(element.node.getAttribute('stroke-width')).toBe(null) + }) + + it('correctly parses numeric values as a getter', () => { + element.attr('stroke-width', 11) + expect(element.node.getAttribute('stroke-width')).toBe('11') + expect(element.attr('stroke-width')).toBe(11) + }) + + it('correctly parses negative numeric values as a getter', () => { + element.attr('x', -120) + expect(element.node.getAttribute('x')).toBe('-120') + expect(element.attr('x')).toBe(-120) + }) + + it('falls back on default values if attribute is not present', () => { + expect(element.attr('stroke-linejoin')).toBe('miter') + }) + + it('gets the "style" attribute as a string', () => { + element.css('cursor', 'pointer') + expect(element.attr('style')).toBe('cursor: pointer;') + }) + + it('sets the style attribute correctly', () => { + element.attr('style', 'cursor:move;') + expect(element.node.style.cursor).toBe('move') + }) + + it('acts as getter for an array of values passed', () => { + element.attr({ + x: 1, + y: 2, + width: 20, + 'fill-opacity': 0.5 + }) + + const ret = element.attr(['x', 'fill-opacity']) + + expect(ret).toEqual({ x: 1, 'fill-opacity': 0.5 }) + }) + + it('correctly creates SVG.Array if array given', () => { + element.attr('something', [2, 3, 4]) + expect(element.attr('something')).toBe('2 3 4') + }) + + it('redirects to the leading() method when setting leading', () => { + const text = new Text().text('Hello World') + const spy = spyOn(text, 'leading') + + text.attr('leading', 2) + expect(spy).toHaveBeenCalledWith(objectContaining({ value: 2 })) + }) + + it('ignores leading if no leading method is available', () => { + const frozen = Object.freeze(element) + expect(frozen.attr('leading', 2)).toBe(frozen) + }) + + it('only applies transforms color values if the attribute is designed to take a color as input', () => { + const rect = new Rect().attr('id', '#ff0') + + expect(rect.attr('id')).toBe('#ff0') + }) + + it('executes registered hooks', () => { + registerAttrHook((attr, val, el) => { + if (el.node.id === 'somethingVeryRandom' && attr === 'name') { + throw new Error('This hook should only be executed in one test') + } + return val + }) + + element.id('somethingVeryRandom') + + const throwingFn = () => { + element.attr('name', 'Bob') + } + + expect(throwingFn).toThrowError( + 'This hook should only be executed in one test' + ) + }) + }) +}) diff --git a/spec/spec/modules/core/circled.js b/spec/spec/modules/core/circled.js new file mode 100644 index 000000000..95217c044 --- /dev/null +++ b/spec/spec/modules/core/circled.js @@ -0,0 +1,97 @@ +/* globals describe, expect, it, beforeEach, spyOn, jasmine, container */ + +import { Ellipse, SVG } from '../../../../src/main.js' + +const { objectContaining } = jasmine + +describe('circled.js', () => { + let element + + beforeEach(() => { + element = new Ellipse().size(50, 50) + }) + + describe('rx()', () => { + it('calls attribute with rx and returns itself', () => { + const spy = spyOn(element, 'attr').and.callThrough() + expect(element.rx(50)).toBe(element) + expect(spy).toHaveBeenCalledWith('rx', 50) + }) + }) + + describe('ry()', () => { + it('calls attribute with ry and returns itself', () => { + const spy = spyOn(element, 'attr').and.callThrough() + expect(element.ry(50)).toBe(element) + expect(spy).toHaveBeenCalledWith('ry', 50) + }) + }) + + describe('x()', () => { + it('sets x position and returns itself', () => { + element = SVG().addTo(container).ellipse(50, 50) + expect(element.x(50)).toBe(element) + expect(element.bbox().x).toBe(50) + }) + + it('gets the x position', () => { + element.x(50) + expect(element.x()).toBe(50) + }) + }) + + describe('y()', () => { + it('sets y position and returns itself', () => { + element = SVG().addTo(container).ellipse(50, 50) + expect(element.y(50)).toBe(element) + expect(element.bbox().y).toBe(50) + }) + + it('gets the y position', () => { + element.y(50) + expect(element.y()).toBe(50) + }) + }) + + describe('cx()', () => { + it('calls attribute with cx and returns itself', () => { + const spy = spyOn(element, 'attr').and.callThrough() + expect(element.cx(50)).toBe(element) + expect(spy).toHaveBeenCalledWith('cx', 50) + }) + }) + + describe('cy()', () => { + it('calls attribute with cy and returns itself', () => { + const spy = spyOn(element, 'attr').and.callThrough() + expect(element.cy(50)).toBe(element) + expect(spy).toHaveBeenCalledWith('cy', 50) + }) + }) + + describe('width()', () => { + it('sets rx by half the given width', () => { + const spy = spyOn(element, 'rx').and.callThrough() + expect(element.width(50)).toBe(element) + expect(spy).toHaveBeenCalledWith(objectContaining({ value: 25 })) + }) + + it('gets the width of the element', () => { + element.width(100) + expect(element.width()).toBe(100) + }) + }) + + describe('height()', () => { + it('sets ry by half the given height', () => { + const spy = spyOn(element, 'ry').and.callThrough() + expect(element.height(50)).toBe(element) + expect(spy).toHaveBeenCalledWith(objectContaining({ value: 25 })) + }) + + it('gets the height of the element', () => { + element.height(100) + expect(element.height()).toBe(100) + }) + }) +}) diff --git a/spec/spec/modules/core/containerGeometry.js b/spec/spec/modules/core/containerGeometry.js new file mode 100644 index 000000000..2bb2fcaf3 --- /dev/null +++ b/spec/spec/modules/core/containerGeometry.js @@ -0,0 +1,350 @@ +/* globals describe, expect, it, jasmine, spyOn, container */ + +import { Box, create, Element, G, Rect, SVG } from '../../../../src/main.js' + +const { any, objectContaining } = jasmine + +describe('containerGeometry.js', () => { + describe('dmove()', () => { + it('moves the bbox of the group by a certain amount (1)', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + + g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) + g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) + + g.dmove(10, 10) + + const box = g.bbox() + expect(box).toEqual( + objectContaining({ + x: 20, + y: 30, + width: box.width, + height: box.height + }) + ) + }) + + it('moves the bbox of the group by a certain amount (2)', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + + g.rect(400, 200).move(123, 312).rotate(34).skew(12) + g.rect(100, 50).move(11, 43).translate(123, 32).skew(-12) + g.rect(400, 200).rotate(90) + g.group().rotate(23).group().skew(32).rect(100, 40).skew(11).rotate(12) + + const oldBox = g.bbox() + + g.dmove(10, 10) + + const newBox = g.bbox() + + expect(newBox.x).toBeCloseTo(oldBox.x + 10, 4) + expect(newBox.y).toBeCloseTo(oldBox.y + 10, 4) + expect(newBox.w).toBeCloseTo(oldBox.w, 4) + expect(newBox.h).toBeCloseTo(oldBox.h, 4) + }) + + it('moves nested svgs in a group correctly when calling dmove', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + const s1 = g.nested().size(100, 100).move(100, 100) + s1.rect(100, 100).move(50, 50).rotate(10) + + const oldBox = g.bbox() + + g.dmove(10, 10) + + const newBox = g.bbox() + + expect(newBox.x).toBeCloseTo(oldBox.x + 10, 4) + expect(newBox.y).toBeCloseTo(oldBox.y + 10, 4) + expect(newBox.w).toBeCloseTo(oldBox.w, 4) + expect(newBox.h).toBeCloseTo(oldBox.h, 4) + }) + + it('moves nested svgs in a group correctly when calling dmove (2)', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + const s1 = g.nested().move(100, 100) + s1.rect(100, 100).move(50, 50).rotate(10) + + const oldBox = g.bbox() + + g.dmove(10, 10) + + const newBox = g.bbox() + + expect(newBox.x).toBeCloseTo(oldBox.x + 10, 4) + expect(newBox.y).toBeCloseTo(oldBox.y + 10, 4) + expect(newBox.w).toBeCloseTo(oldBox.w, 4) + expect(newBox.h).toBeCloseTo(oldBox.h, 4) + }) + + it('moves nested svgs in a group correctly when calling dmove (3)', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + const s1 = g.nested() + s1.rect(100, 100).move(50, 50).rotate(10) + + const oldBox = g.bbox() + + g.dmove(10, 10) + + const newBox = g.bbox() + + expect(newBox.x).toBeCloseTo(oldBox.x + 10, 4) + expect(newBox.y).toBeCloseTo(oldBox.y + 10, 4) + expect(newBox.w).toBeCloseTo(oldBox.w, 4) + expect(newBox.h).toBeCloseTo(oldBox.h, 4) + }) + + it('it does not fail when hitting elements without bbox', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + + g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) + g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) + g.add(new Element(create('title'))) + + const fn = () => g.dmove(10, 10) + expect(fn).not.toThrowError() + + const box = g.bbox() + expect(box).toEqual( + objectContaining({ + x: 20, + y: 30, + width: box.width, + height: box.height + }) + ) + }) + }) + + describe('dx()', () => { + it('calls dmove with dy=0 and returns itself', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + const spy = spyOn(g, 'dmove').and.callThrough() + expect(g.dx(10)).toBe(g) + expect(spy).toHaveBeenCalledWith(10, 0) + }) + }) + + describe('dy()', () => { + it('calls dmove with dx=0 and returns itself', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + const spy = spyOn(g, 'dmove').and.callThrough() + expect(g.dy(10)).toBe(g) + expect(spy).toHaveBeenCalledWith(0, 10) + }) + }) + + describe('move()', () => { + it('calls dmove() with the correct difference', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + g.rect(100, 200).move(111, 223) + + spyOn(g, 'dmove') + + g.move(100, 150) + expect(g.dmove).toHaveBeenCalledWith(-11, -73) + }) + + it('defaults to x=0 and y=0', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + g.rect(100, 200).move(111, 223) + + spyOn(g, 'dmove') + + g.move() + expect(g.dmove).toHaveBeenCalledWith(-111, -223) + }) + }) + + describe('x()', () => { + it('gets the x value of the bbox', () => { + const canvas = SVG().addTo(container) + + const g = new G() + g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) + g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) + + g.addTo(canvas) + + expect(g.x()).toBe(g.bbox().x) + expect(g.x()).toBe(10) + }) + it('calls move with the parameter as x', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + g.rect(100, 200).move(111, 223) + + spyOn(g, 'move') + + g.x(100) + expect(g.move).toHaveBeenCalledWith(100, g.bbox().y, any(Box)) + }) + }) + + describe('y()', () => { + it('gets the y value of the bbox', () => { + const canvas = SVG().addTo(container) + + const g = new G() + g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) + g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) + + g.addTo(canvas) + + expect(g.y()).toBe(g.bbox().y) + expect(g.y()).toBe(20) + }) + + it('calls move with the parameter as y', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + g.rect(100, 200).move(111, 223) + + spyOn(g, 'move') + + g.y(100) + expect(g.move).toHaveBeenCalledWith(g.bbox().x, 100, any(Box)) + }) + }) + + describe('size()', () => { + it('changes the dimensions of the bbox (1)', () => { + const canvas = SVG().addTo(container) + + const g = new G() + g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) + g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) + + g.addTo(canvas) + + const oldBox = g.bbox() + + expect(g.size(100, 100)).toBe(g) + + const newBox = g.bbox() + + expect(newBox.x).toBeCloseTo(oldBox.x, 4) + expect(newBox.y).toBeCloseTo(oldBox.y, 4) + expect(newBox.w).toBeCloseTo(100, 4) + expect(newBox.h).toBeCloseTo(100, 4) + + const rbox1 = g.children()[0].rbox() + const rbox2 = g.children()[1].rbox() + + expect(rbox1.width).toBeCloseTo(90.9, 1) + expect(Math.floor(rbox2.width * 10) / 10).toBeCloseTo(63.6, 1) // Browsers have different opinion on this one (chrome: 63.6, ff: 63.7) + + expect(rbox1.x).toBeCloseTo(10, 1) + expect(rbox2.x).toBeCloseTo(46.4, 1) + expect(rbox1.height).toBeCloseTo(85.7, 1) + expect(rbox2.height).toBeCloseTo(71.4, 1) + expect(rbox1.y).toBeCloseTo(20, 1) + expect(rbox2.y).toBeCloseTo(48.6, 1) + }) + + it('changes the dimensions of the bbox (2)', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + + g.rect(400, 200).move(123, 312).rotate(34).skew(12) + g.rect(100, 50).move(11, 43).translate(123, 32).skew(-12) + g.rect(400, 200).rotate(90) + g.group().rotate(23).group().skew(32).rect(100, 40).skew(11).rotate(12) + + const oldBox = g.bbox() + + g.size(100, 100) + + const newBox = g.bbox() + + expect(newBox.x).toBeCloseTo(oldBox.x, 4) + expect(newBox.y).toBeCloseTo(oldBox.y, 4) + expect(newBox.w).toBeCloseTo(100, 4) + expect(newBox.h).toBeCloseTo(100, 4) + }) + }) + + describe('width()', () => { + it('gets the width value of the bbox', () => { + const canvas = SVG().addTo(container) + + const g = new G() + g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) + g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) + + g.addTo(canvas) + + expect(g.width()).toBe(g.bbox().width) + expect(g.width()).toBe(110) + }) + it('sets the width value of the bbox by moving all children', () => { + const canvas = SVG().addTo(container) + + const g = new G() + g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) + g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) + + g.addTo(canvas) + + expect(g.width(100)).toBe(g) + expect(g.bbox().width).toBe(100) + + const rbox1 = g.children()[0].rbox() + const rbox2 = g.children()[1].rbox() + + expect(rbox1.width).toBeCloseTo(90.9, 1) + expect(Math.floor(rbox2.width * 10) / 10).toBeCloseTo(63.6, 1) // Browsers have different opinion on this one (chrome: 63.6, ff: 63.7) + + expect(rbox1.x).toBeCloseTo(10, 3) + expect(rbox2.x).toBeCloseTo(46.4, 1) + }) + }) + + describe('height()', () => { + it('gets the height value of the bbox', () => { + const canvas = SVG().addTo(container) + + const g = new G() + g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) + g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) + + g.addTo(canvas) + + expect(g.height()).toBe(g.bbox().height) + expect(g.height()).toBe(140) + }) + it('sets the height value of the bbox by moving all children', () => { + const canvas = SVG().addTo(container) + + const g = new G() + g.add(new Rect({ width: 100, height: 120, x: 10, y: 20 })) + g.add(new Rect({ width: 70, height: 100, x: 50, y: 60 })) + + g.addTo(canvas) + + expect(g.height(100)).toBe(g) + expect(g.bbox().height).toBeCloseTo(100, 3) + + const rbox1 = g.children()[0].rbox() + const rbox2 = g.children()[1].rbox() + + expect(rbox1.height).toBeCloseTo(85.7, 1) + expect(rbox2.height).toBeCloseTo(71.4, 1) + + expect(rbox1.y).toBeCloseTo(20, 3) + expect(rbox2.y).toBeCloseTo(48.6, 1) + }) + }) +}) diff --git a/spec/spec/modules/core/event.js b/spec/spec/modules/core/event.js new file mode 100644 index 000000000..e25959398 --- /dev/null +++ b/spec/spec/modules/core/event.js @@ -0,0 +1,226 @@ +/* globals describe, expect, it, spyOn, jasmine */ +import { + windowEvents, + getEvents, + getEventTarget, + clearEvents, + dispatch, + on, + off +} from '../../../../src/modules/core/event.js' +import { getWindow } from '../../../../src/utils/window.js' +import { EventTarget, SVG } from '../../../../src/main.js' + +const { any, createSpy } = jasmine + +describe('event.js', () => { + describe('getEvents()', () => { + it('returns the instance events for an EventTarget', () => { + const eventTarget = new EventTarget() + eventTarget.events = 'Test' + const events = getEvents(eventTarget) + expect(events).toBe('Test') + }) + + it('accesses windowEvents if instance is window', () => { + windowEvents.events = 'bla' + const events = getEvents(SVG(getWindow())) + expect(events).toBe(windowEvents.events) + }) + }) + + describe('getEventTarget()', () => { + it('calls getEventTarget() on the instance', () => { + const eventTarget = new EventTarget() + const spy = spyOn(eventTarget, 'getEventTarget') + getEventTarget(eventTarget) + expect(spy).toHaveBeenCalled() + }) + }) + + describe('clearEvents()', () => { + it('sets events to an empty object', () => { + const eventTarget = new EventTarget() + eventTarget.events = 'Test' + clearEvents(eventTarget) + expect(eventTarget.events).toEqual({}) + }) + + it('does not do anything if no event object is found on the instance', () => { + const eventTarget = new EventTarget() + delete eventTarget.events + clearEvents(eventTarget) + expect(eventTarget.events).toBe(undefined) + }) + }) + + describe('on()', () => { + it('binds an event to an EventTarget', () => { + const eventTarget = new EventTarget() + const spy = createSpy('spy') + on(eventTarget, 'event', spy) + dispatch(eventTarget, 'event') + expect(spy).toHaveBeenCalledWith(any(getWindow().CustomEvent)) + }) + + it('binds to multiple events with space or comma separated string', () => { + const eventTarget = new EventTarget() + const spy = createSpy('spy') + on(eventTarget, 'event1 event2, event3', spy) + dispatch(eventTarget, 'event1') + dispatch(eventTarget, 'event2') + dispatch(eventTarget, 'event3') + expect(spy).toHaveBeenCalledTimes(3) + }) + + it('binds to multiple events passed as array', () => { + const eventTarget = new EventTarget() + const spy = createSpy('spy') + on(eventTarget, ['event1', 'event2', 'event3'], spy) + dispatch(eventTarget, 'event1') + dispatch(eventTarget, 'event2') + dispatch(eventTarget, 'event3') + expect(spy).toHaveBeenCalledTimes(3) + }) + + it('binds a namespaced event of form event.namespace', () => { + const eventTarget = new EventTarget() + const spy = createSpy('spy') + on(eventTarget, 'event.namespace', spy) + dispatch(eventTarget, 'event') + expect(spy).toHaveBeenCalledWith(any(getWindow().CustomEvent)) + }) + }) + + describe('off()', () => { + it('unbinds an event of an EventTarget', () => { + const eventTarget = new EventTarget() + const spy = createSpy('spy') + on(eventTarget, 'event', spy) + dispatch(eventTarget, 'event') + off(eventTarget, 'event', spy) + dispatch(eventTarget, 'event') + expect(spy).toHaveBeenCalledTimes(1) + }) + + it('unbinds multiple events with space or comma separated string', () => { + const eventTarget = new EventTarget() + const spy = createSpy('spy') + on(eventTarget, 'event1 event2, event3', spy) + dispatch(eventTarget, 'event1') + dispatch(eventTarget, 'event2') + dispatch(eventTarget, 'event3') + off(eventTarget, 'event1 event2, event3', spy) + dispatch(eventTarget, 'event1') + dispatch(eventTarget, 'event2') + dispatch(eventTarget, 'event3') + expect(spy).toHaveBeenCalledTimes(3) + }) + + it('unbinds multiple events with space or comma separated string', () => { + const eventTarget = new EventTarget() + const spy = createSpy('spy') + on(eventTarget, ['event1', 'event2', 'event3'], spy) + dispatch(eventTarget, 'event1') + dispatch(eventTarget, 'event2') + dispatch(eventTarget, 'event3') + off(eventTarget, ['event1', 'event2', 'event3'], spy) + dispatch(eventTarget, 'event1') + dispatch(eventTarget, 'event2') + dispatch(eventTarget, 'event3') + expect(spy).toHaveBeenCalledTimes(3) + }) + + it('unbinds a namespaced event', () => { + const eventTarget = new EventTarget() + const spy = createSpy('spy') + on(eventTarget, 'event.namespace', spy) + dispatch(eventTarget, 'event') + off(eventTarget, 'event.namespace', spy) + dispatch(eventTarget, 'event') + expect(spy).toHaveBeenCalledTimes(1) + }) + + it('unbinds all events including namespaced ones when only event is passed', () => { + const eventTarget = new EventTarget() + const spy = createSpy('spy') + on(eventTarget, ['event1.ns1', 'event2.ns2', 'event3'], spy) + dispatch(eventTarget, 'event1') + dispatch(eventTarget, 'event2') + dispatch(eventTarget, 'event3') + off(eventTarget, ['event1', 'event2', 'event3']) + dispatch(eventTarget, 'event1') + dispatch(eventTarget, 'event2') + dispatch(eventTarget, 'event3') + expect(spy).toHaveBeenCalledTimes(3) + }) + + it('unbinds with namespace only', () => { + const eventTarget = new EventTarget() + const spy1 = createSpy('spy1') + const spy2 = createSpy('spy2') + const spy3 = createSpy('spy3') + on(eventTarget, 'event1.ns1', spy1) + on(eventTarget, 'event2.ns1', spy2) + on(eventTarget, 'event3.ns2', spy3) + dispatch(eventTarget, 'event1') + dispatch(eventTarget, 'event2') + dispatch(eventTarget, 'event3') + off(eventTarget, '.ns1') + dispatch(eventTarget, 'event1') + dispatch(eventTarget, 'event2') + dispatch(eventTarget, 'event3') + expect(spy1).toHaveBeenCalledTimes(1) + expect(spy2).toHaveBeenCalledTimes(1) + expect(spy3).toHaveBeenCalledTimes(2) + }) + + it('unbinds all events when called without event', () => { + const eventTarget = new EventTarget() + const spy1 = createSpy('spy1') + const spy2 = createSpy('spy2') + const spy3 = createSpy('spy3') + on(eventTarget, 'event1.ns1', spy1) + on(eventTarget, 'event2.ns1', spy2) + on(eventTarget, 'event3.ns2', spy3) + dispatch(eventTarget, 'event1') + dispatch(eventTarget, 'event2') + dispatch(eventTarget, 'event3') + off(eventTarget) + dispatch(eventTarget, 'event1') + dispatch(eventTarget, 'event2') + dispatch(eventTarget, 'event3') + expect(spy1).toHaveBeenCalledTimes(1) + expect(spy2).toHaveBeenCalledTimes(1) + expect(spy3).toHaveBeenCalledTimes(1) + }) + }) + + describe('dispatch()', () => { + it('dispatches a custom event on the EventTarget by calling dispatchEvent()', () => { + const eventTarget = new EventTarget() + const spy = spyOn(eventTarget, 'dispatchEvent') + const event = dispatch( + eventTarget, + 'event', + { some: 'data' }, + { cancelable: false } + ) + expect(event).toEqual(any(getWindow().CustomEvent)) + expect(spy).toHaveBeenCalledWith(event) + expect(event.detail).toEqual({ some: 'data' }) + expect(event.cancelable).toBe(false) + }) + + it('dispatches the passed event directly', () => { + const eventTarget = new EventTarget() + const spy = spyOn(eventTarget, 'dispatchEvent') + + const CustomEvent = getWindow().CustomEvent + const event1 = new CustomEvent('event', { detail: { some: 'data' } }) + const event2 = dispatch(eventTarget, event1) + expect(event1).toBe(event2) + expect(spy).toHaveBeenCalledWith(event1) + }) + }) +}) diff --git a/spec/spec/modules/core/gradiented.js b/spec/spec/modules/core/gradiented.js new file mode 100644 index 000000000..7b2c22788 --- /dev/null +++ b/spec/spec/modules/core/gradiented.js @@ -0,0 +1,37 @@ +/* globals describe, expect, it */ + +import { Gradient } from '../../../../src/main.js' + +describe('gradiented.js', () => { + describe('from()', () => { + it('sets fx and fy for radial gradients and returns itself', () => { + const gradient = new Gradient('radial') + expect(gradient.from(10, 20)).toBe(gradient) + expect(gradient.attr('fx')).toBe(10) + expect(gradient.attr('fy')).toBe(20) + }) + + it('sets x1 and y1 for linear gradients and returns itself', () => { + const gradient = new Gradient('linear') + expect(gradient.from(10, 20)).toBe(gradient) + expect(gradient.attr('x1')).toBe(10) + expect(gradient.attr('y1')).toBe(20) + }) + }) + + describe('to()', () => { + it('sets cx and cy for radial gradients and returns itself', () => { + const gradient = new Gradient('radial') + expect(gradient.to(10, 20)).toBe(gradient) + expect(gradient.attr('cx')).toBe(10) + expect(gradient.attr('cy')).toBe(20) + }) + + it('sets x2 and y2 for linear gradients and returns itself', () => { + const gradient = new Gradient('linear') + expect(gradient.to(10, 20)).toBe(gradient) + expect(gradient.attr('x2')).toBe(10) + expect(gradient.attr('y2')).toBe(20) + }) + }) +}) diff --git a/spec/spec/modules/core/parser.js b/spec/spec/modules/core/parser.js new file mode 100644 index 000000000..c7acc4eb0 --- /dev/null +++ b/spec/spec/modules/core/parser.js @@ -0,0 +1,28 @@ +/* globals describe, expect, it */ + +import parser from '../../../../src/modules/core/parser.js' +import { getWindow } from '../../../../src/utils/window.js' + +describe('parser.js', () => { + describe('parser()', () => { + it('returns an object with svg and path', () => { + const nodes = parser() + expect(nodes.path).toBeDefined() + expect(nodes.svg).toBeDefined() + }) + + it('creates an svg node in the dom', () => { + expect(getWindow().document.querySelector('svg')).toBe(null) + const nodes = parser() + expect(getWindow().document.querySelector('svg')).toBe(nodes.svg.node) + }) + + it('reuses parser instance when it was removed', () => { + const nodes = parser() + nodes.svg.remove() + const nodes2 = parser() + expect(nodes.svg).toBe(nodes2.svg) + expect(nodes.path).toBe(nodes2.path) + }) + }) +}) diff --git a/spec/spec/modules/core/pointed.js b/spec/spec/modules/core/pointed.js new file mode 100644 index 000000000..77bf2d31e --- /dev/null +++ b/spec/spec/modules/core/pointed.js @@ -0,0 +1,62 @@ +/* globals describe, expect, it, beforeEach, container */ + +import { SVG } from '../../../../src/main.js' + +describe('pointed.js', () => { + let canvas, lines + + beforeEach(() => { + canvas = SVG().addTo(container) + const line = canvas.line(1, 2, 3, 4) + const polygon = canvas.polygon([1, 2, 3, 4]) + const polyline = canvas.polyline([1, 2, 3, 4]) + lines = { line, polygon, polyline } + }) + ;['line', 'polygon', 'polyline'].forEach((l) => { + describe('for ' + l, () => { + describe('x()', () => { + it('sets the x value of the ' + l + 'and returns itself', () => { + expect(lines[l].x(50)).toBe(lines[l]) + expect(lines[l].bbox().x).toBe(50) + }) + + it('gets the x value of the ' + l, () => { + expect(lines[l].x(50).x()).toBe(50) + }) + }) + + describe('y()', () => { + it('sets the y value of the ' + l + 'and returns itself', () => { + expect(lines[l].y(50)).toBe(lines[l]) + expect(lines[l].bbox().y).toBe(50) + }) + + it('gets the y value of the ' + l, () => { + expect(lines[l].y(50).y()).toBe(50) + }) + }) + + describe('width()', () => { + it('sets the width of the ' + l + 'and returns itself', () => { + expect(lines[l].width(50)).toBe(lines[l]) + expect(lines[l].bbox().width).toBe(50) + }) + + it('gets the width of the ' + l, () => { + expect(lines[l].width(50).width()).toBe(50) + }) + }) + + describe('height()', () => { + it('sets the height of the ' + l + 'and returns itself', () => { + expect(lines[l].height(50)).toBe(lines[l]) + expect(lines[l].bbox().height).toBe(50) + }) + + it('gets the height of the ' + l, () => { + expect(lines[l].height(50).height()).toBe(50) + }) + }) + }) + }) +}) diff --git a/spec/spec/modules/core/poly.js b/spec/spec/modules/core/poly.js new file mode 100644 index 000000000..e4c3bed59 --- /dev/null +++ b/spec/spec/modules/core/poly.js @@ -0,0 +1,149 @@ +/* globals describe, expect, beforeEach, it, spyOn, jasmine, container */ + +import { Polygon, SVG, PointArray } from '../../../../src/main.js' + +const { any, objectContaining } = jasmine + +describe('Polygon.js', () => { + let poly + + beforeEach(() => { + poly = new Polygon() + }) + + describe('array()', () => { + it('returns the underlying PointArray', () => { + const array = poly.plot('1 2 3 4').array() + expect(array).toEqual(any(PointArray)) + expect(array).toEqual([ + [1, 2], + [3, 4] + ]) + }) + }) + + describe('clear()', () => { + it('clears the array cache and returns itself', () => { + const array = poly.plot('1 2 3 4').array() + expect(poly.clear()).toBe(poly) + expect(array).not.toBe(poly._array) + }) + }) + + describe('move()', () => { + it('returns itself', () => { + expect(poly.move(0, 0)).toBe(poly) + }) + + it('moves the poly along x and y axis', () => { + const canvas = SVG().addTo(container) + const poly = canvas.polygon('0 0 50 50') + poly.move(50, 50) + expect(poly.bbox()).toEqual( + objectContaining({ + x: 50, + y: 50, + width: 50, + height: 50 + }) + ) + }) + }) + + describe('plot()', () => { + it('relays to array() as getter', () => { + const spy = spyOn(poly, 'array') + poly.plot() + expect(spy).toHaveBeenCalled() + }) + + it('works by passing a string', () => { + const spy = spyOn(poly, 'attr') + poly.plot('1 2 3 4') + expect(spy).toHaveBeenCalledWith('points', '1 2 3 4') + }) + + it('works with flat array', () => { + const spy = spyOn(poly, 'attr') + poly.plot([1, 2, 3, 4]) + expect(spy).toHaveBeenCalledWith('points', [ + [1, 2], + [3, 4] + ]) + }) + + it('works with multi array', () => { + const spy = spyOn(poly, 'attr') + poly.plot([ + [1, 2], + [3, 4] + ]) + expect(spy).toHaveBeenCalledWith('points', [ + [1, 2], + [3, 4] + ]) + }) + + it('works with PointArray', () => { + const spy = spyOn(poly, 'attr') + poly.plot( + new PointArray([ + [1, 2], + [3, 4] + ]) + ) + expect(spy).toHaveBeenCalledWith('points', [ + [1, 2], + [3, 4] + ]) + }) + }) + + describe('size()', () => { + it('returns itself', () => { + expect(poly.size(50, 50)).toBe(poly) + }) + + it('sets the size of the poly', () => { + const canvas = SVG().addTo(container) + const poly = canvas.polygon('0 0 50 50') + poly.size(100, 100) + expect(poly.bbox()).toEqual( + objectContaining({ + width: 100, + height: 100, + x: 0, + y: 0 + }) + ) + }) + + it('changes height proportionally', () => { + const canvas = SVG().addTo(container) + const poly = canvas.polygon('0 0 50 50') + poly.size(100, null) + expect(poly.bbox()).toEqual( + objectContaining({ + width: 100, + height: 100, + x: 0, + y: 0 + }) + ) + }) + + it('changes width proportionally', () => { + const canvas = SVG().addTo(container) + const poly = canvas.polygon('0 0 50 50') + poly.size(null, 100) + expect(poly.bbox()).toEqual( + objectContaining({ + width: 100, + height: 100, + x: 0, + y: 0 + }) + ) + }) + }) +}) diff --git a/spec/spec/modules/core/regex.js b/spec/spec/modules/core/regex.js new file mode 100644 index 000000000..37f774c59 --- /dev/null +++ b/spec/spec/modules/core/regex.js @@ -0,0 +1,228 @@ +/* globals describe, expect, it */ + +import { regex } from '../../../../src/main.js' + +describe('regex.js', () => { + describe('numberAndUnit', () => { + it('matches number and unit 12px', () => { + const match = '12px'.match(regex.numberAndUnit) + expect(match[1]).toBe('12') + expect(match[5]).toBe('px') + }) + + it('matches number and unit 12', () => { + const match = '12'.match(regex.numberAndUnit) + expect(match[1]).toBe('12') + expect(match[5]).toBe('') + }) + + it('matches number and unit 12%', () => { + const match = '12%'.match(regex.numberAndUnit) + expect(match[1]).toBe('12') + expect(match[5]).toBe('%') + }) + + it('matches number and unit -12%', () => { + const match = '-12%'.match(regex.numberAndUnit) + expect(match[1]).toBe('-12') + expect(match[5]).toBe('%') + }) + + it('matches number and unit -12.123%', () => { + const match = '-12.123%'.match(regex.numberAndUnit) + expect(match[1]).toBe('-12.123') + expect(match[5]).toBe('%') + }) + + it('matches number and unit -12.123e12%', () => { + const match = '-12.123e12%'.match(regex.numberAndUnit) + expect(match[1]).toBe('-12.123e12') + expect(match[5]).toBe('%') + }) + }) + + describe('hex', () => { + it('matches a 6 digit hex', () => { + const match = '#123456'.match(regex.hex) + expect(match[1]).toBe('12') + expect(match[2]).toBe('34') + expect(match[3]).toBe('56') + }) + + /* it('does not matches without #', () => { + const match = '123456'.match(regex.hex) + expect(match).toBe(null) + }) */ + + it('does not matches other then 0-f #', () => { + const match = '#09afhz'.match(regex.hex) + expect(match).toBe(null) + }) + + it('does not matches non full hex', () => { + const match = '#aaa'.match(regex.hex) + expect(match).toBe(null) + }) + }) + + describe('rgb', () => { + it('matches rgb values of rgb(...) command', () => { + const match = 'rgb(12,34,56)'.match(regex.rgb) + expect(match[1]).toBe('12') + expect(match[2]).toBe('34') + expect(match[3]).toBe('56') + }) + + it('does not match in the wrong format', () => { + expect('rgb( 12 , 34 , 56)'.match(regex.rgb)).toBe(null) + expect('12,34,56'.match(regex.rgb)).toBe(null) + expect('(12,34,56)'.match(regex.rgb)).toBe(null) + expect('rgb(aa,34,56)'.match(regex.rgb)).toBe(null) + expect('rgb(12,34)'.match(regex.rgb)).toBe(null) + }) + }) + + describe('reference', () => { + it('matches a reference', () => { + const match = '#soMe_cRazy-1_id'.match(regex.reference) + expect(match[1]).toBe('#soMe_cRazy-1_id') + }) + + it('tries to match malformed references', () => { + const match = '#some_crazy%-1_id'.match(regex.reference) + expect(match[0]).toBe('#some_crazy') + }) + }) + + describe('transforms', () => { + it('splits a transform chain', () => { + const split = + 'rotate(34) translate(1,2), translate(1 , 3),rotate(12) , something(1,2,3)'.split( + regex.transforms + ) + expect(split).toEqual([ + 'rotate(34', + 'translate(1,2', + 'translate(1 , 3', + 'rotate(12', + 'something(1,2,3', + '' + ]) + }) + }) + + describe('whitespace', () => { + it('replaces all whitespaces', () => { + expect(' \n \r \t '.replace(regex.whitespace, ' ')).toBe( + ' ' + ) + }) + }) + + describe('isHex', () => { + it('returns true when testing hex values', () => { + expect(regex.isHex.test('#123')).toBe(true) + expect(regex.isHex.test('#abc')).toBe(true) + expect(regex.isHex.test('#123456')).toBe(true) + expect(regex.isHex.test('#abcdef')).toBe(true) + expect(regex.isHex.test('#16fde9')).toBe(true) + }) + + it('returns false when testing non hex values', () => { + expect(regex.isHex.test('#12')).toBe(false) + expect(regex.isHex.test('abc')).toBe(false) + expect(regex.isHex.test('#1234563')).toBe(false) + expect(regex.isHex.test('#kasdhs')).toBe(false) + expect(regex.isHex.test('#abcd')).toBe(false) + }) + }) + + describe('isRgb', () => { + it('returns true when testing rgb values', () => { + expect(regex.isRgb.test('rgb(1,2,3)')).toBe(true) + expect(regex.isRgb.test('rgb( 3, 1,3)')).toBe(true) + }) + + it('returns false when testing non rgb values', () => { + expect(regex.isRgb.test('hsl(1,2,3)')).toBe(false) + expect(regex.isRgb.test('#123')).toBe(false) + expect(regex.isRgb.test('something')).toBe(false) + }) + }) + + describe('isBlank', () => { + it('returns true if something is blank', () => { + expect(regex.isBlank.test('')).toBe(true) + expect(regex.isBlank.test(' ')).toBe(true) + expect(regex.isBlank.test('\n')).toBe(true) + expect(regex.isBlank.test('\r')).toBe(true) + expect(regex.isBlank.test('\t')).toBe(true) + expect(regex.isBlank.test(' \n\r\t')).toBe(true) + }) + + it('returns false if something is not blank', () => { + expect(regex.isBlank.test('a')).toBe(false) + expect(regex.isBlank.test('1')).toBe(false) + }) + }) + + describe('isNumber', () => { + it('returns true if something is a number', () => { + expect(regex.isNumber.test('123')).toBe(true) + expect(regex.isNumber.test('-123')).toBe(true) + expect(regex.isNumber.test('-12.3')).toBe(true) + expect(regex.isNumber.test('-12.3e12')).toBe(true) + expect(regex.isNumber.test('-12.3e-12')).toBe(true) + expect(regex.isNumber.test('+12.3e-12')).toBe(true) + expect(regex.isNumber.test('+12.3E-12')).toBe(true) + }) + + it('returns false if something is not a number', () => { + expect(regex.isNumber.test('a')).toBe(false) + expect(regex.isNumber.test('-a')).toBe(false) + expect(regex.isNumber.test('-12a')).toBe(false) + expect(regex.isNumber.test('-12.3a12')).toBe(false) + expect(regex.isNumber.test('-12.3e-1a')).toBe(false) + expect(regex.isNumber.test('12.12.12')).toBe(false) + expect(regex.isNumber.test('12.12e12.3')).toBe(false) + expect(regex.isNumber.test('12.12e12e4')).toBe(false) + }) + }) + + describe('isImage', () => { + it('returns true if something is an image filename', () => { + expect(regex.isImage.test('a.jpg')).toBe(true) + expect(regex.isImage.test('a.jpeg')).toBe(true) + expect(regex.isImage.test('a.png')).toBe(true) + expect(regex.isImage.test('a.gif')).toBe(true) + expect(regex.isImage.test('a.svg')).toBe(true) + }) + + it('returns false if something is not an image filename', () => { + expect(regex.isImage.test('a.abc')).toBe(false) + expect(regex.isImage.test('a.txt')).toBe(false) + expect(regex.isImage.test('a.doc')).toBe(false) + }) + }) + + describe('delimiter', () => { + it('splits at whitespace and comma', () => { + const split = '1,2 3 , 4 5,, 6'.split(regex.delimiter) + expect(split).toEqual(['1', '2', '3', '4', '5', '6']) + }) + }) + + describe('isPathLetter', () => { + it('returns true if something is a path letter', () => { + 'MLHVCSQTAZmlhvcsqtaz'.split('').forEach((l) => { + expect(regex.isPathLetter.test(l)).toBe(true) + }) + }) + + it('returns false if something is not path letter', () => { + '123biuBIU$%&'.split('').forEach((l) => { + expect(regex.isPathLetter.test(l)).toBe(false) + }) + }) + }) +}) diff --git a/spec/spec/modules/core/selector.js b/spec/spec/modules/core/selector.js new file mode 100644 index 000000000..8729b6192 --- /dev/null +++ b/spec/spec/modules/core/selector.js @@ -0,0 +1,60 @@ +/* globals describe, expect, it, container */ + +import { find, SVG, G } from '../../../../src/main.js' +import { getWindow } from '../../../../src/utils/window.js' + +describe('selector.js', () => { + describe('baseFind()', () => { + it('finds all elements of a selector in the document', () => { + const div = SVG('
', true).id('foo').addTo(container) + const span = SVG('', true).addClass('bar').addTo(div) + const span2 = SVG('', true).addTo(div) + + expect(find('#canvas').map((el) => el.node)).toEqual([container]) + expect(find('span')).toEqual([span, span2]) + expect(find('#foo')).toEqual([div]) + expect(find('.bar')).toEqual([span]) + }) + + it('finds all elements of a selector scoped to an element', () => { + const div = SVG('
', true).id('foo').addTo(container) + + expect(find('#canvas', getWindow().document)[0].node).toBe(container) + expect(find('#foo', container)).toEqual([div]) + expect(find('#canvas', div.node)).toEqual([]) + }) + }) + + describe('Dom', () => { + describe('find()', () => { + it('finds all elements matching the selector in this element', () => { + const g1 = new G() + const g2 = new G().addTo(g1).id('foo') + const g3 = new G().addTo(g1).addClass('bar') + const g4 = new G().addTo(g2) + const g5 = new G().addTo(g3) + + expect(g1.find('g')).toEqual([g2, g4, g3, g5]) + expect(g2.find('g')).toEqual([g4]) + expect(g1.find('#foo')).toEqual([g2]) + expect(g2.find('#foo')).toEqual([]) + expect(g1.find('.bar')).toEqual([g3]) + }) + }) + + describe('findOne()', () => { + it('finds an element in this element', () => { + const g1 = new G() + const g2 = new G().addTo(g1).id('foo') + const g3 = new G().addTo(g1).addClass('bar') + const g4 = new G().addTo(g2) + + expect(g1.findOne('g')).toBe(g2) + expect(g2.findOne('g')).toBe(g4) + expect(g1.findOne('#foo')).toBe(g2) + expect(g2.findOne('#foo')).toBe(null) + expect(g1.findOne('.bar')).toBe(g3) + }) + }) + }) +}) diff --git a/spec/spec/modules/core/textable.js b/spec/spec/modules/core/textable.js new file mode 100644 index 000000000..8135c6a13 --- /dev/null +++ b/spec/spec/modules/core/textable.js @@ -0,0 +1,352 @@ +/* globals describe, expect, it, beforeEach, spyOn, container */ + +import { SVG, Box, Tspan } from '../../../../src/main.js' + +describe('textable.js', () => { + var canvas, text, tspan + + beforeEach(() => { + canvas = SVG().addTo(container) + text = canvas.text('Hello World\nIn two lines') + tspan = text.get(0) + }) + + describe('x()', () => { + it('returns the value of x without an argument on a text', () => { + expect(text.x(0).x()).toBe(0) + }) + + it('sets the x value of the bbox with the first argument on a text', () => { + text.x(123) + expect(text.bbox().x).toBe(123) + }) + + it('sets the value of all lines', () => { + text.x(200) + text.each(function () { + expect(this.x()).toBe(text.x()) + }) + }) + + it('returns the value of x without an argument on a tspan', () => { + expect(tspan.x(10).x()).toBe(10) + }) + + it('sets the x value of the bbox with the first argument on a tspan', () => { + tspan.x(123) + expect(tspan.bbox().x).toBe(123) + }) + }) + + describe('y()', () => { + it('returns the value of y without an argument on a text', () => { + expect(text.y(0).y()).toBe(0) + }) + + it('sets the y value of the bbox with the first argument on a text', () => { + text.y(123) + expect(text.bbox().y).toBe(123) + }) + + it('sets the y position of first line', () => { + text.y(200) + expect(text.first().y()).toBe(text.y()) + }) + + it('returns the value of y without an argument on a tspan', () => { + expect(tspan.y(10).y()).toBe(10) + }) + + it('sets the y value of the bbox with the first argument on a tspan', () => { + tspan.y(123) + expect(tspan.bbox().y).toBe(123) + }) + }) + + describe('move()', () => { + it('calls x() and y() with parameters on text', () => { + const spyX = spyOn(text, 'x').and.callThrough() + const spyY = spyOn(text, 'y').and.callThrough() + const box = new Box() + text.move(1, 2, box) + expect(spyX).toHaveBeenCalledWith(1, box) + expect(spyY).toHaveBeenCalledWith(2, box) + }) + + it('calls x() and y() with parameters on tspan', () => { + const spyX = spyOn(tspan, 'x').and.callThrough() + const spyY = spyOn(tspan, 'y').and.callThrough() + const box = new Box() + tspan.move(1, 2, box) + expect(spyX).toHaveBeenCalledWith(1, box) + expect(spyY).toHaveBeenCalledWith(2, box) + }) + }) + + describe('ax()', () => { + it('sets the value of x with a percent value with Text', () => { + text.ax('40%') + expect(text.node.getAttribute('x')).toBe('40%') + }) + + it('returns the value of x when x is a percentual value with Text', () => { + expect(text.ax('40%').ax()).toBe('40%') + }) + + it('sets the value of x with a percent value with Tspan', () => { + tspan.ax('40%') + expect(tspan.node.getAttribute('x')).toBe('40%') + }) + + it('returns the value of x when x is a percentual value with Tspan', () => { + tspan.ax('40%') + expect(tspan.ax()).toBe('40%') + }) + }) + + describe('ay()', () => { + it('sets the value of y with a percent value with Text', () => { + text.ay('40%') + expect(text.node.getAttribute('y')).toBe('40%') + }) + + it('returns the value of y when y is a percentual value with Tspan', () => { + expect(text.ay('45%').ay()).toBe('45%') + }) + + it('sets the value of y with a percent value with Text', () => { + tspan.ay('40%') + expect(tspan.node.getAttribute('y')).toBe('40%') + }) + + it('returns the value of y when y is a percentual value with Tspan', () => { + tspan.ay('40%') + expect(tspan.ay()).toBe('40%') + }) + }) + + describe('move()', () => { + it('calls ax() and ay() with parameters on text', () => { + const spyX = spyOn(text, 'ax').and.callThrough() + const spyY = spyOn(text, 'ay').and.callThrough() + text.amove(1, 2) + expect(spyX).toHaveBeenCalledWith(1) + expect(spyY).toHaveBeenCalledWith(2) + }) + + it('calls ax() and ay() with parameters on tspan', () => { + const spyX = spyOn(tspan, 'ax').and.callThrough() + const spyY = spyOn(tspan, 'ay').and.callThrough() + tspan.amove(1, 2) + expect(spyX).toHaveBeenCalledWith(1) + expect(spyY).toHaveBeenCalledWith(2) + }) + }) + + // this is a hackish. It should not be neccessary to use toBeCloseTo but bbox with text is a thing... + describe('cx()', () => { + it('returns the value of cx without an argument with Text', () => { + var box = text.bbox() + expect(text.cx()).toBeCloseTo(box.x + box.width / 2) + }) + + it('sets the value of cx with the first argument with Text', () => { + text.cx(123) + var box = text.bbox() + expect(box.cx).toBeCloseTo(box.x + box.width / 2) + }) + + it('returns the value of cx without an argument with Tspan', () => { + var box = tspan.bbox() + expect(tspan.cx()).toBeCloseTo(box.x + box.width / 2) + }) + + it('sets the value of cx with the first argument with Tspan', () => { + tspan.cx(123) + var box = tspan.bbox() + expect(box.cx).toBeCloseTo(box.x + box.width / 2) + }) + }) + + describe('cy()', () => { + it('returns the value of cy without an argument with Tspan', () => { + var box = tspan.bbox() + expect(tspan.cy()).toBe(box.cy) + }) + + it('sets the value of cy with the first argument with Tspan', () => { + tspan.cy(345) + var box = tspan.bbox() + expect(Math.round(box.cy * 10) / 10).toBe(345) + }) + + it('returns the value of cy without an argument with Tspan', () => { + var box = tspan.bbox() + expect(tspan.cy()).toBe(box.cy) + }) + + it('sets the value of cy with the first argument with Tspan', () => { + tspan.cy(345) + var box = tspan.bbox() + expect(Math.round(box.cy * 10) / 10).toBe(345) + }) + }) + + describe('center()', () => { + it('calls cx() and cy() with parameters on Text', () => { + const spyX = spyOn(text, 'cx').and.callThrough() + const spyY = spyOn(text, 'cy').and.callThrough() + const box = new Box() + text.center(1, 2, box) + expect(spyX).toHaveBeenCalledWith(1, box) + expect(spyY).toHaveBeenCalledWith(2, box) + }) + + it('calls cx() and cy() with parameters on Tspan', () => { + const spyX = spyOn(tspan, 'cx').and.callThrough() + const spyY = spyOn(tspan, 'cy').and.callThrough() + const box = new Box() + tspan.center(1, 2, box) + expect(spyX).toHaveBeenCalledWith(1, box) + expect(spyY).toHaveBeenCalledWith(2, box) + }) + }) + + describe('plain()', () => { + it('adds content without a tspan with Text', () => { + text.plain('It is a bear!') + expect(text.node.childNodes[0].nodeType).toBe(3) + expect(text.node.childNodes[0].data).toBe('It is a bear!') + }) + + it('clears content before adding new content with Text', () => { + text.plain('It is not a bear!') + expect(text.node.childNodes.length).toBe(1) + expect(text.node.childNodes[0].data).toBe('It is not a bear!') + }) + + it('restores the content from the dom with Text', () => { + text.plain('Just plain text!') + expect(text.text()).toBe('Just plain text!') + }) + + it('adds content without a tspan with Tspan', () => { + tspan.plain('It is a bear!') + expect(tspan.node.childNodes[0].nodeType).toBe(3) + expect(tspan.node.childNodes[0].data).toBe('It is a bear!') + }) + + it('clears content before adding new content with Tspan', () => { + tspan.plain('It is not a bear!') + expect(tspan.node.childNodes.length).toBe(1) + expect(tspan.node.childNodes[0].data).toBe('It is not a bear!') + }) + + it('restores the content from the dom with Tspan', () => { + // We create a new Tspan here because the one used before was part of text creation + // and therefore is marked as newline and that is not what we want to test + const tspan = new Tspan().plain('Just plain text!') + expect(tspan.text()).toBe('Just plain text!') + }) + }) + + describe('length()', () => { + it('gets total length of text', () => { + text.text(function (add) { + add.tspan('The first.') + add.tspan('The second.') + add.tspan('The third.') + }) + expect(text.length()).toBeCloseTo( + text.get(0).length() + text.get(1).length() + text.get(2).length(), + 3 + ) + }) + + it('gets total length of tspan', () => { + tspan.text(function (add) { + add.tspan('The first.') + add.tspan('The second.') + add.tspan('The third.') + }) + expect(tspan.length()).toBeCloseTo( + tspan.get(0).length() + tspan.get(1).length() + tspan.get(2).length(), + 3 + ) + }) + }) + + describe('build()', () => { + it('enables adding multiple plain text nodes when given true for Text', () => { + text.clear().build(true) + text.plain('A great piece!') + text.plain('Another great piece!') + expect(text.node.childNodes[0].data).toBe('A great piece!') + expect(text.node.childNodes[1].data).toBe('Another great piece!') + }) + + it('enables adding multiple tspan nodes when given true for Text', () => { + text.clear().build(true) + text.tspan('A great piece!') + text.tspan('Another great piece!') + expect(text.node.childNodes[0].childNodes[0].data).toBe('A great piece!') + expect(text.node.childNodes[1].childNodes[0].data).toBe( + 'Another great piece!' + ) + }) + + it('disables adding multiple plain text nodes when given false for Text', () => { + text.clear().build(true) + text.plain('A great piece!') + text.build(false).plain('Another great piece!') + expect(text.node.childNodes[0].data).toBe('Another great piece!') + expect(text.node.childNodes[1]).toBe(undefined) + }) + + it('disables adding multiple tspan nodes when given false for Text', () => { + text.clear().build(true) + text.tspan('A great piece!') + text.build(false).tspan('Another great piece!') + expect(text.node.childNodes[0].childNodes[0].data).toBe( + 'Another great piece!' + ) + expect(text.node.childNodes[1]).toBe(undefined) + }) + + it('enables adding multiple plain text nodes when given true for Tspan', () => { + tspan.clear().build(true) + tspan.plain('A great piece!') + tspan.plain('Another great piece!') + expect(tspan.node.childNodes[0].data).toBe('A great piece!') + expect(tspan.node.childNodes[1].data).toBe('Another great piece!') + }) + + it('enables adding multiple text nodes when given true for Tspan', () => { + tspan.clear().build(true) + tspan.tspan('A great piece!') + tspan.tspan('Another great piece!') + expect(tspan.node.childNodes[0].childNodes[0].data).toBe('A great piece!') + expect(tspan.node.childNodes[1].childNodes[0].data).toBe( + 'Another great piece!' + ) + }) + + it('disables adding multiple plain text nodes when given false for Tspan', () => { + tspan.clear().build(true) + tspan.plain('A great piece!') + tspan.build(false).plain('Another great piece!') + expect(tspan.node.childNodes[0].data).toBe('Another great piece!') + expect(tspan.node.childNodes[1]).toBe(undefined) + }) + + it('disables adding multiple tspan nodes when given false for Tspan', () => { + tspan.clear().build(true) + tspan.tspan('A great piece!') + tspan.build(false).tspan('Another great piece!') + expect(tspan.node.childNodes[0].childNodes[0].data).toBe( + 'Another great piece!' + ) + expect(tspan.node.childNodes[1]).toBe(undefined) + }) + }) +}) diff --git a/spec/spec/modules/optional/arrange.js b/spec/spec/modules/optional/arrange.js new file mode 100644 index 000000000..c665aa516 --- /dev/null +++ b/spec/spec/modules/optional/arrange.js @@ -0,0 +1,235 @@ +/* globals describe, expect, it */ + +import { G, Line } from '../../../../src/main.js' + +describe('arrange.js', () => { + describe('Dom', () => { + describe('siblings()', () => { + it('returns all siblings including the node itself', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + const line = g.line(1, 2, 3, 4) + + expect(circle.siblings()).toEqual([rect, circle, line]) + }) + }) + + describe('position()', () => { + it('returns the position in the parent', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + const line = g.line(1, 2, 3, 4) + + expect(rect.position()).toBe(0) + expect(circle.position()).toBe(1) + expect(line.position()).toBe(2) + }) + }) + + describe('next()', () => { + it('returns the next sibling', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + + expect(rect.next()).toBe(circle) + }) + + it('returns undefined if there is no sibling', () => { + const g = new G() + const rect = g.rect(100, 100) + + expect(rect.next()).toBe(undefined) + }) + }) + + describe('prev()', () => { + it('returns the next sibling', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + + expect(circle.prev()).toBe(rect) + }) + + it('returns undefined if there is no sibling', () => { + const g = new G() + const rect = g.rect(100, 100) + + expect(rect.prev()).toBe(undefined) + }) + }) + + describe('forward()', () => { + it('returns itself', () => { + const g = new G() + const rect = g.rect(100, 100) + expect(rect.forward()).toBe(rect) + }) + + it('moves an element one step forward', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + const line = g.line(1, 2, 3, 4) + + rect.forward() + + expect(g.children()).toEqual([circle, rect, line]) + }) + + it('does nothing when the element is already the last one', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + const line = g.line(1, 2, 3, 4) + + line.forward() + + expect(g.children()).toEqual([rect, circle, line]) + }) + }) + + describe('backward()', () => { + it('returns itself', () => { + const g = new G() + const rect = g.rect(100, 100) + expect(rect.backward()).toBe(rect) + }) + + it('moves an element one step backward', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + const line = g.line(1, 2, 3, 4) + + line.backward() + + expect(g.children()).toEqual([rect, line, circle]) + }) + + it('does nothing when the element is already the first one', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + const line = g.line(1, 2, 3, 4) + + rect.backward() + + expect(g.children()).toEqual([rect, circle, line]) + }) + }) + + describe('front()', () => { + it('returns itself', () => { + const g = new G() + const rect = g.rect(100, 100) + expect(rect.front()).toBe(rect) + }) + + it('moves an element to the front', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + const line = g.line(1, 2, 3, 4) + + rect.front() + + expect(g.children()).toEqual([circle, line, rect]) + }) + + it('does nothing when the element is already the last one', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + const line = g.line(1, 2, 3, 4) + + line.front() + + expect(g.children()).toEqual([rect, circle, line]) + }) + }) + + describe('back()', () => { + it('returns itself', () => { + const g = new G() + const rect = g.rect(100, 100) + expect(rect.back()).toBe(rect) + }) + + it('moves an element to the back', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + const line = g.line(1, 2, 3, 4) + + line.back() + + expect(g.children()).toEqual([line, rect, circle]) + }) + + it('does nothing when the element is already the first one', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + const line = g.line(1, 2, 3, 4) + + rect.back() + + expect(g.children()).toEqual([rect, circle, line]) + }) + }) + + describe('before()', () => { + it('inserts an element before this one', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + + const line = new Line() + circle.before(line) + + expect(g.children()).toEqual([rect, line, circle]) + }) + }) + + describe('after()', () => { + it('inserts an element after this one', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + + const line = new Line() + rect.after(line) + + expect(g.children()).toEqual([rect, line, circle]) + }) + }) + + describe('insertBefore()', () => { + it('inserts the current element before another one', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + + const line = new Line().insertBefore(circle) + + expect(g.children()).toEqual([rect, line, circle]) + }) + }) + + describe('insertAfter()', () => { + it('inserts the current element after another one', () => { + const g = new G() + const rect = g.rect(100, 100) + const circle = g.circle(100) + + const line = new Line().insertAfter(rect) + + expect(g.children()).toEqual([rect, line, circle]) + }) + }) + }) +}) diff --git a/spec/spec/modules/optional/class.js b/spec/spec/modules/optional/class.js new file mode 100644 index 000000000..461c14fed --- /dev/null +++ b/spec/spec/modules/optional/class.js @@ -0,0 +1,92 @@ +/* globals describe, expect, it */ + +import { Rect } from '../../../../src/main.js' + +describe('class.js', () => { + describe('Dom', () => { + describe('classes()', () => { + it('returns all classes on an element', () => { + const rect = new Rect({ class: 'myClass myClass2' }) + expect(rect.classes()).toEqual(['myClass', 'myClass2']) + }) + + it('returns an empty array if no class on the element', () => { + const rect = new Rect() + expect(rect.classes()).toEqual([]) + }) + }) + + describe('hasClass()', () => { + it('returns true if a class is present on the element', () => { + const rect = new Rect({ class: 'myClass myClass2' }) + expect(rect.hasClass('myClass')).toBe(true) + }) + + it('returns false if a class is not present on the element', () => { + const rect = new Rect({ class: 'myClass myClass2' }) + expect(rect.hasClass('myClass3')).toBe(false) + }) + }) + + describe('addClass()', () => { + it('returns itself', () => { + const rect = new Rect({ class: 'myClass myClass2' }) + expect(rect.addClass('myClass3')).toBe(rect) + }) + + it('adds a class to the element', () => { + const rect = new Rect({ class: 'myClass myClass2' }).addClass( + 'myClass3' + ) + expect(rect.classes()).toEqual(['myClass', 'myClass2', 'myClass3']) + }) + + it('does nothing if class already present on the element', () => { + const rect = new Rect({ class: 'myClass myClass2' }).addClass('myClass') + expect(rect.classes()).toEqual(['myClass', 'myClass2']) + }) + }) + + describe('removeClass()', () => { + it('returns itself', () => { + const rect = new Rect({ class: 'myClass myClass2' }) + expect(rect.removeClass('myClass3')).toBe(rect) + }) + + it('removes a class from the element', () => { + const rect = new Rect({ class: 'myClass myClass2' }).removeClass( + 'myClass2' + ) + expect(rect.classes()).toEqual(['myClass']) + }) + + it('does nothing if class is not present on the element', () => { + const rect = new Rect({ class: 'myClass myClass2' }).removeClass( + 'myClass3' + ) + expect(rect.classes()).toEqual(['myClass', 'myClass2']) + }) + }) + + describe('toggleClass()', () => { + it('returns itself', () => { + const rect = new Rect({ class: 'myClass myClass2' }) + expect(rect.toggleClass('myClass3')).toBe(rect) + }) + + it('removes a class from the element when its present', () => { + const rect = new Rect({ class: 'myClass myClass2' }).toggleClass( + 'myClass2' + ) + expect(rect.classes()).toEqual(['myClass']) + }) + + it('adds a class to the element if its not present', () => { + const rect = new Rect({ class: 'myClass myClass2' }).toggleClass( + 'myClass3' + ) + expect(rect.classes()).toEqual(['myClass', 'myClass2', 'myClass3']) + }) + }) + }) +}) diff --git a/spec/spec/modules/optional/css.js b/spec/spec/modules/optional/css.js new file mode 100644 index 000000000..a0fa701c0 --- /dev/null +++ b/spec/spec/modules/optional/css.js @@ -0,0 +1,160 @@ +/* globals describe, expect, it */ + +import { Rect } from '../../../../src/main.js' + +describe('css.js', () => { + describe('Dom', () => { + describe('css()', () => { + describe('as getter', () => { + it('returns all css as object', () => { + const rect = new Rect({ + style: 'fill: none; outline-width: 1px; stroke: none' + }) + expect(rect.css()).toEqual({ + fill: 'none', + 'outline-width': '1px', + stroke: 'none' + }) + }) + + it('returns an object with selected css properties', () => { + const rect = new Rect({ + style: 'fill: none; outline-width: 1px; stroke: none' + }) + expect(rect.css(['fill', 'stroke'])).toEqual({ + fill: 'none', + stroke: 'none' + }) + }) + + it('returns a single property with property name given', () => { + const rect = new Rect({ + style: 'fill: none; outline-width: 1px; stroke: none' + }) + expect(rect.css('fill')).toBe('none') + }) + + it('correctly returns css vars', () => { + const rect = new Rect({ + style: '--foo: red;' + }) + expect(rect.css('--foo')).toBe('red') + }) + + it('returns undefined if css property is not set', () => { + const rect = new Rect({ + style: 'fill: none; outline-width: 1px; stroke: none' + }) + expect(rect.css('outline-color')).toBe('') + }) + }) + + describe('as setter', () => { + it('returns itself', () => { + const rect = new Rect({ + style: 'fill: none; outline-width: 1px; stroke: none' + }) + expect(rect.css('fill', 'black')).toBe(rect) + }) + + it('adds a css property', () => { + const rect = new Rect({ + style: 'fill: none; outline-width: 1px; stroke: none' + }) + expect(rect.css('stroke-width', '2px').css('stroke-width')).toBe( + '2px' + ) + }) + + it('changes a css property', () => { + const rect = new Rect({ + style: 'fill: none; outline-width: 1px; stroke: none' + }) + expect(rect.css('fill', 'black').css('fill')).toBe('black') + }) + + it('sets an object of properties', () => { + const rect = new Rect() + expect(rect.css({ fill: 'none', stroke: 'none' }).css()).toEqual({ + fill: 'none', + stroke: 'none' + }) + }) + + it('removes property if empty string is passed as value', () => { + const rect = new Rect({ + style: 'fill: none; outline-width: 1px; stroke: none' + }) + expect(rect.css('fill', '').css('fill')).toBe('') + }) + + it('removes property if null is passed as value', () => { + const rect = new Rect({ + style: 'fill: none; outline-width: 1px; stroke: none' + }) + expect(rect.css('fill', null).css('fill')).toBe('') + }) + + it('removes property if null is passed as part of object', () => { + const rect = new Rect({ + style: 'fill: none; outline-width: 1px; stroke: none' + }) + expect(rect.css({ fill: null, stroke: 'black' }).css('fill')).toBe('') + }) + + it('allows single set of css vars', () => { + const rect = new Rect().css('--foo', 'red').css('--foo', 'green') + expect(rect.css()).toEqual({ + '--foo': 'green' + }) + }) + + it('allows multiple set of css vars via object', () => { + const rect = new Rect().css({ '--foo': 'red', '--bar': 'green' }) + + expect(rect.css()).toEqual({ + '--foo': 'red', + '--bar': 'green' + }) + }) + }) + }) + + describe('show()', () => { + it('returns itself', () => { + const rect = new Rect() + expect(rect.show()).toBe(rect) + }) + + it('removes the display property', () => { + const rect = new Rect().hide() + expect(rect.show().css('display')).toBe('') + }) + }) + + describe('hide()', () => { + it('returns itself', () => { + const rect = new Rect() + expect(rect.hide()).toBe(rect) + }) + + it('sets the css display property to none', () => { + const rect = new Rect() + expect(rect.hide().css('display')).toBe('none') + }) + }) + + describe('visible()', () => { + it('returns true if display is not none', () => { + const rect = new Rect() + expect(rect.show().visible()).toBe(true) + expect(rect.css('display', 'block').visible()).toBe(true) + }) + + it('returns false if display is none', () => { + const rect = new Rect() + expect(rect.hide().visible()).toBe(false) + }) + }) + }) +}) diff --git a/spec/spec/modules/optional/data.js b/spec/spec/modules/optional/data.js new file mode 100644 index 000000000..5265dd16e --- /dev/null +++ b/spec/spec/modules/optional/data.js @@ -0,0 +1,129 @@ +/* globals describe, expect, it */ + +import { Rect } from '../../../../src/main.js' + +describe('data.js', () => { + describe('Dom', () => { + describe('data()', () => { + describe('as getter', () => { + it('returns all data as object', () => { + const rect = new Rect({ + 'data-fill': 'none', + 'data-outline-width': '1px', + 'data-stroke': 'none' + }) + expect(rect.data()).toEqual({ + fill: 'none', + 'outline-width': '1px', + stroke: 'none' + }) + }) + + it('returns an object with selected data properties', () => { + const rect = new Rect({ + 'data-fill': 'none', + 'data-outline-width': '1px', + 'data-stroke': 'none' + }) + expect(rect.data(['fill', 'stroke'])).toEqual({ + fill: 'none', + stroke: 'none' + }) + }) + + it('returns a single property with property name given', () => { + const rect = new Rect({ + 'data-fill': 'none', + 'data-outline-width': '1px', + 'data-stroke': 'none' + }) + expect(rect.data('fill')).toBe('none') + }) + + it('returns undefined if data property is not set', () => { + const rect = new Rect({ + 'data-fill': 'none', + 'data-outline-width': '1px', + 'data-stroke': 'none' + }) + expect(rect.data('outline-color')).toBe(undefined) + }) + }) + + describe('as setter', () => { + it('returns itself', () => { + const rect = new Rect({ + 'data-fill': 'none', + 'data-outline-width': '1px', + 'data-stroke': 'none' + }) + expect(rect.data('fill', 'black')).toBe(rect) + }) + + it('adds a data property', () => { + const rect = new Rect({ + 'data-fill': 'none', + 'data-outline-width': '1px', + 'data-stroke': 'none' + }) + expect(rect.data('stroke-width', '2px').data('stroke-width')).toBe( + '2px' + ) + }) + + it('changes a data property', () => { + const rect = new Rect({ + 'data-fill': 'none', + 'data-outline-width': '1px', + 'data-stroke': 'none' + }) + expect(rect.data('fill', 'black').data('fill')).toBe('black') + }) + + it('sets an object of properties', () => { + const rect = new Rect() + expect(rect.data({ fill: 'none', stroke: 'none' }).data()).toEqual({ + fill: 'none', + stroke: 'none' + }) + }) + + it('removes property if null is passed as value', () => { + const rect = new Rect({ + 'data-fill': 'none', + 'data-outline-width': '1px', + 'data-stroke': 'none' + }) + expect(rect.data('fill', null).data('fill')).toBe(undefined) + }) + + it('removes property if null is passed as part of object', () => { + const rect = new Rect({ + 'data-fill': 'none', + 'data-outline-width': '1px', + 'data-stroke': 'none' + }) + expect(rect.data({ fill: null, stroke: 'black' }).data('fill')).toBe( + undefined + ) + }) + + it('converts everything except number and strings to JSON', () => { + const rect = new Rect() + expect(rect.data('fill', { some: 'object' }).attr('data-fill')).toBe( + JSON.stringify({ some: 'object' }) + ) + expect(rect.data('fill', 5).attr('data-fill')).toBe(5) + expect(rect.data('fill', 'string').attr('data-fill')).toBe('string') + }) + + it('does not convert to json with third parameter true', () => { + const rect = new Rect() + expect( + rect.data('fill', { some: 'object' }, true).attr('data-fill') + ).toBe({}.toString()) + }) + }) + }) + }) +}) diff --git a/spec/spec/modules/optional/memory.js b/spec/spec/modules/optional/memory.js new file mode 100644 index 000000000..747207848 --- /dev/null +++ b/spec/spec/modules/optional/memory.js @@ -0,0 +1,113 @@ +/* globals describe, expect, it */ + +import { Rect } from '../../../../src/main.js' + +describe('memory.js', () => { + describe('Dom', () => { + describe('memory()', () => { + it('returns all memory as object', () => { + const rect = new Rect().remember({ + fill: 'none', + 'outline-width': '1px', + stroke: 'none' + }) + expect(rect.memory()).toEqual({ + fill: 'none', + 'outline-width': '1px', + stroke: 'none' + }) + }) + }) + + describe('remember()', () => { + describe('as getter', () => { + it('returns a single property with property name given', () => { + const rect = new Rect().remember({ + fill: 'none', + 'outline-width': '1px', + stroke: 'none' + }) + expect(rect.remember('fill')).toBe('none') + }) + + it('returns undefined if memory property is not set', () => { + const rect = new Rect().remember({ + fill: 'none', + 'outline-width': '1px', + stroke: 'none' + }) + expect(rect.remember('outline-color')).toBe(undefined) + }) + }) + + describe('as setter', () => { + it('returns itself', () => { + const rect = new Rect().remember({ + fill: 'none', + 'outline-width': '1px', + stroke: 'none' + }) + expect(rect.remember('fill', 'black')).toBe(rect) + }) + + it('adds a memory property', () => { + const rect = new Rect().remember({ + fill: 'none', + 'outline-width': '1px', + stroke: 'none' + }) + expect( + rect.remember('stroke-width', '2px').remember('stroke-width') + ).toBe('2px') + }) + + it('changes a memory property', () => { + const rect = new Rect().remember({ + fill: 'none', + 'outline-width': '1px', + stroke: 'none' + }) + expect(rect.remember('fill', 'black').remember('fill')).toBe('black') + }) + + it('sets an object of properties', () => { + const rect = new Rect() + expect( + rect.remember({ fill: 'none', stroke: 'none' }).memory() + ).toEqual({ fill: 'none', stroke: 'none' }) + }) + }) + }) + + describe('forget()', () => { + it('removes property', () => { + const rect = new Rect().remember({ + fill: 'none', + 'outline-width': '1px', + stroke: 'none' + }) + expect(rect.forget('fill').remember('fill')).toBe(undefined) + }) + + it('removes multiple properties', () => { + const rect = new Rect().remember({ + fill: 'none', + 'outline-width': '1px', + stroke: 'none' + }) + expect(rect.forget('fill', 'stroke').memory()).toEqual({ + 'outline-width': '1px' + }) + }) + + it('erases the whole object with nothing passed', () => { + const rect = new Rect().remember({ + fill: 'none', + 'outline-width': '1px', + stroke: 'none' + }) + expect(rect.forget().memory()).toEqual({}) + }) + }) + }) +}) diff --git a/spec/spec/modules/optional/sugar.js b/spec/spec/modules/optional/sugar.js new file mode 100644 index 000000000..74cef6267 --- /dev/null +++ b/spec/spec/modules/optional/sugar.js @@ -0,0 +1,464 @@ +/* globals describe, expect, it, beforeEach, spyOn, container */ + +import { Rect, SVG, Matrix, Ellipse, Gradient } from '../../../../src/main.js' + +describe('sugar.js', () => { + describe('Element/Runner', () => { + describe('fill()', () => { + describe('as setter', () => { + it('returns itself', () => { + const rect = new Rect() + expect(rect.fill('black')).toBe(rect) + }) + + it('sets a fill color', () => { + const rect = new Rect() + expect(rect.fill('black').attr('fill')).toBe('black') + }) + + it('sets a fill pattern when pattern given', () => { + const canvas = SVG().addTo(container) + const pattern = canvas.pattern() + const rect = canvas.rect(100, 100) + expect(rect.fill(pattern).attr('fill')).toBe(pattern.url()) + }) + + it('sets a fill pattern when image given', () => { + const canvas = SVG().addTo(container) + const image = canvas.image('spec/fictures/pixel.png') + const rect = canvas.rect(100, 100) + expect(rect.fill(image).attr('fill')).toBe(image.parent().url()) + }) + + it('sets an object of fill properties', () => { + const rect = new Rect() + expect( + rect + .fill({ + color: 'black', + opacity: 0.5, + rule: 'even-odd' + }) + .attr() + ).toEqual({ + fill: 'black', + 'fill-opacity': 0.5, + 'fill-rule': 'even-odd' + }) + }) + }) + + describe('as getter', () => { + it('returns fill color', () => { + const rect = new Rect().fill('black') + expect(rect.fill()).toBe('black') + }) + + it('returns default fill color if nothing is set', () => { + const rect = new Rect() + expect(rect.fill()).toBe('#000000') + }) + }) + }) + + describe('stroke()', () => { + describe('as setter', () => { + it('returns itself', () => { + const rect = new Rect() + expect(rect.stroke('black')).toBe(rect) + }) + + it('sets a stroke color', () => { + const rect = new Rect() + expect(rect.stroke('black').attr('stroke')).toBe('black') + }) + + it('sets a stroke pattern when pattern given', () => { + const canvas = SVG().addTo(container) + const pattern = canvas.pattern() + const rect = canvas.rect(100, 100) + expect(rect.stroke(pattern).attr('stroke')).toBe(pattern.url()) + }) + + it('sets a stroke pattern when image given', () => { + const canvas = SVG().addTo(container) + const image = canvas.image('spec/fictures/pixel.png') + const rect = canvas.rect(100, 100) + expect(rect.stroke(image).attr('stroke')).toBe(image.parent().url()) + }) + + it('sets an object of stroke properties', () => { + const rect = new Rect() + expect( + rect + .stroke({ + color: 'black', + width: 2, + opacity: 0.5, + linecap: 'butt', + linejoin: 'miter', + miterlimit: 10, + dasharray: '2 2', + dashoffset: 15 + }) + .attr() + ).toEqual({ + stroke: 'black', + 'stroke-width': 2, + 'stroke-opacity': 0.5, + 'stroke-linecap': 'butt', + 'stroke-linejoin': 'miter', + 'stroke-miterlimit': 10, + 'stroke-dasharray': '2 2', + 'stroke-dashoffset': 15 + }) + }) + + it('sets stroke dasharray with array passed', () => { + const rect = new Rect().stroke({ dasharray: [2, 2] }) + expect(rect.attr()).toEqual({ 'stroke-dasharray': '2 2' }) + }) + }) + + describe('as getter', () => { + it('returns stroke color', () => { + const rect = new Rect().stroke('black') + expect(rect.stroke()).toBe('black') + }) + + it('returns default stroke color if nothing is set', () => { + const rect = new Rect() + expect(rect.stroke()).toBe('#000000') + }) + }) + }) + + describe('matrix()', () => { + it('gets the matrix with no argument passed', () => { + const rect = new Rect().transform(new Matrix(1, 0, 1, 1, 1, 0)) + expect(rect.matrix()).toEqual(new Matrix(1, 0, 1, 1, 1, 0)) + }) + + it('sets the matrix if matrix given', () => { + const rect = new Rect().matrix(new Matrix(1, 0, 1, 1, 1, 0)) + expect(rect.matrix()).toEqual(new Matrix(1, 0, 1, 1, 1, 0)) + }) + + it('sets the matrix with 6 arguments given', () => { + const rect = new Rect().matrix(1, 0, 1, 1, 1, 0) + expect(rect.matrix()).toEqual(new Matrix(1, 0, 1, 1, 1, 0)) + }) + }) + + describe('rotate()', function () { + it('redirects to transform()', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.rotate(1, 2, 3) + expect(spy).toHaveBeenCalledWith({ rotate: 1, ox: 2, oy: 3 }, true) + }) + }) + + describe('skew()', function () { + it('redirects to transform() with no argument', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.skew() + expect(spy).toHaveBeenCalledWith( + { skew: [undefined, undefined], ox: undefined, oy: undefined }, + true + ) + }) + + it('redirects to transform() with one argument', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.skew(5) + expect(spy).toHaveBeenCalledWith( + { skew: 5, ox: undefined, oy: undefined }, + true + ) + }) + + it('redirects to transform() with two argument', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.skew(5, 6) + expect(spy).toHaveBeenCalledWith( + { skew: [5, 6], ox: undefined, oy: undefined }, + true + ) + }) + + it('redirects to transform() with three arguments', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.skew(5, 6, 7) + expect(spy).toHaveBeenCalledWith({ skew: 5, ox: 6, oy: 7 }, true) + }) + + it('redirects to transform() with four arguments', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.skew(5, 6, 7, 8) + expect(spy).toHaveBeenCalledWith({ skew: [5, 6], ox: 7, oy: 8 }, true) + }) + }) + + describe('shear', () => { + it('redirects to transform()', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.shear(1, 2, 3) + expect(spy).toHaveBeenCalledWith({ shear: 1, ox: 2, oy: 3 }, true) + }) + }) + + describe('scale()', function () { + it('redirects to transform() with no argument', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.scale() + expect(spy).toHaveBeenCalledWith( + { scale: [undefined, undefined], ox: undefined, oy: undefined }, + true + ) + }) + + it('redirects to transform() with one argument', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.scale(5) + expect(spy).toHaveBeenCalledWith( + { scale: 5, ox: undefined, oy: undefined }, + true + ) + }) + + it('redirects to transform() with two argument', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.scale(5, 6) + expect(spy).toHaveBeenCalledWith( + { scale: [5, 6], ox: undefined, oy: undefined }, + true + ) + }) + + it('redirects to transform() with three arguments', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.scale(5, 6, 7) + expect(spy).toHaveBeenCalledWith({ scale: 5, ox: 6, oy: 7 }, true) + }) + + it('redirects to transform() with four arguments', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.scale(5, 6, 7, 8) + expect(spy).toHaveBeenCalledWith({ scale: [5, 6], ox: 7, oy: 8 }, true) + }) + }) + + describe('translate()', function () { + it('redirects to transform()', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.translate(1, 2) + expect(spy).toHaveBeenCalledWith({ translate: [1, 2] }, true) + }) + }) + + describe('relative()', () => { + it('redirects to transform()', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.relative(1, 2) + expect(spy).toHaveBeenCalledWith({ relative: [1, 2] }, true) + }) + }) + + describe('flip()', function () { + it('redirects to transform()', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.flip('x', 2) + expect(spy).toHaveBeenCalledWith({ flip: 'x', origin: 2 }, true) + }) + + it('sets flip to "both" when calling without anything', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.flip() + expect(spy).toHaveBeenCalledWith( + { flip: 'both', origin: 'center' }, + true + ) + }) + + // this works because only x and y are valid flip values. Everything else flips on both axis + it('sets flip to both and origin to number when called with origin only', function () { + const rect = new Rect() + const spy = spyOn(rect, 'transform') + rect.flip(5) + expect(spy).toHaveBeenCalledWith({ flip: 'both', origin: 5 }, true) + }) + }) + + describe('opacity()', function () { + it('redirects to attr() directly', function () { + const rect = new Rect() + const spy = spyOn(rect, 'attr') + rect.opacity(0.5) + expect(spy).toHaveBeenCalledWith('opacity', 0.5) + }) + }) + + describe('font()', function () { + const txt = 'Some text' + let canvas, text + + beforeEach(() => { + canvas = SVG().addTo(container) + text = canvas.text(txt) + }) + + it('sets leading when given', function () { + const spy = spyOn(text, 'leading') + text.font({ leading: 3 }) + expect(spy).toHaveBeenCalledWith(3) + }) + + it('sets text-anchor when anchor given', function () { + const spy = spyOn(text, 'attr') + text.font({ anchor: 'start' }) + expect(spy).toHaveBeenCalledWith('text-anchor', 'start') + }) + + it('sets all font properties via attr()', function () { + const spy = spyOn(text, 'attr') + text.font({ + size: 20, + family: 'Verdana', + weight: 'bold', + stretch: 'wider', + variant: 'small-caps', + style: 'italic' + }) + expect(spy).toHaveBeenCalledWith('font-size', 20) + expect(spy).toHaveBeenCalledWith('font-family', 'Verdana') + expect(spy).toHaveBeenCalledWith('font-weight', 'bold') + expect(spy).toHaveBeenCalledWith('font-stretch', 'wider') + expect(spy).toHaveBeenCalledWith('font-variant', 'small-caps') + expect(spy).toHaveBeenCalledWith('font-style', 'italic') + }) + + it('redirects all other stuff directly to attr()', function () { + const spy = spyOn(text, 'attr') + text.font({ + foo: 'bar', + bar: 'baz' + }) + expect(spy).toHaveBeenCalledWith('foo', 'bar') + expect(spy).toHaveBeenCalledWith('bar', 'baz') + }) + + it('sets key value pair when called with 2 parameters', function () { + const spy = spyOn(text, 'attr') + text.font('size', 20) + expect(spy).toHaveBeenCalledWith('font-size', 20) + }) + + it('gets value if called with one parameter', function () { + const spy = spyOn(text, 'attr') + text.font('size') + expect(spy).toHaveBeenCalledWith('font-size', undefined) + }) + }) + }) + + describe('radius()', () => { + describe('Rect', () => { + it('sets rx and ry on the rectangle', () => { + const rect = new Rect().radius(5, 10) + expect(rect.attr()).toEqual({ rx: 5, ry: 10 }) + }) + }) + + describe('Ellipse', () => { + it('sets rx and ry on the rectangle', () => { + const rect = new Ellipse().radius(5, 10) + expect(rect.attr()).toEqual({ rx: 5, ry: 10 }) + }) + }) + + describe('radialGradient', () => { + it('sets rx and ry on the rectangle', () => { + const rect = new Gradient('radial').radius(5) + expect(rect.attr()).toEqual({ r: 5 }) + }) + }) + }) + + describe('Path', () => { + describe('length', () => { + it('returns the full length of the path', () => { + const canvas = SVG().addTo(container) + const path = canvas.path('M0 0 L 0 5') + expect(path.length()).toBe(5) + }) + }) + + describe('pointAt', () => { + it('returns a point at a specific length', () => { + const canvas = SVG().addTo(container) + const path = canvas.path('M0 0 L 0 5') + const point = path.pointAt(3) + + expect(point.x).toBeCloseTo(0) // chrome has rounding issues -.- + expect(point.y).toBe(3) + }) + }) + }) + + describe('events', () => { + ;[ + 'click', + 'dblclick', + 'mousedown', + 'mouseup', + 'mouseover', + 'mouseout', + 'mousemove', + 'mouseenter', + 'mouseleave', + 'touchstart', + 'touchmove', + 'touchleave', + 'touchend', + 'touchcancel' + ].forEach((ev) => { + describe(ev, () => { + it('calls on() with the eventname set to ' + ev, () => { + const rect = new Rect() + const spy = spyOn(rect, 'on') + const fn = () => {} + rect[ev](fn) + expect(spy).toHaveBeenCalledWith(ev, fn) + }) + + it( + 'calls off() with the eventname set to ' + + ev + + ' when null is passed as second argument', + () => { + const rect = new Rect() + const spy = spyOn(rect, 'off') + rect[ev](null) + expect(spy).toHaveBeenCalledWith(ev) + } + ) + }) + }) + }) +}) diff --git a/spec/spec/modules/optional/transform.js b/spec/spec/modules/optional/transform.js new file mode 100644 index 000000000..41d403ca0 --- /dev/null +++ b/spec/spec/modules/optional/transform.js @@ -0,0 +1,156 @@ +/* globals describe, expect, it, spyOn, container */ + +import { Rect, Matrix, SVG } from '../../../../src/main.js' + +describe('transform.js', () => { + describe('untransform()', () => { + it('returns itself', () => { + const rect = new Rect() + expect(rect.untransform()).toBe(rect) + }) + + it('deletes the transform attribute', () => { + const rect = new Rect() + expect(rect.untransform().attr('transform')).toBe(undefined) + }) + }) + + describe('matrixify()', () => { + it('reduces all transformations of the transform list into one matrix - 1', () => { + const rect = new Rect().attr('transform', 'matrix(1, 0, 1, 1, 0, 1)') + expect(rect.matrixify()).toEqual(new Matrix(1, 0, 1, 1, 0, 1)) + }) + + it('reduces all transformations of the transform list into one matrix - 2', () => { + const rect = new Rect().attr('transform', 'translate(10, 20) rotate(45)') + expect(rect.matrixify()).toEqual( + new Matrix().rotate(45).translate(10, 20) + ) + }) + + it('reduces all transformations of the transform list into one matrix - 3', () => { + const rect = new Rect().attr( + 'transform', + 'translate(10, 20) rotate(45) skew(1,2) skewX(10) skewY(20)' + ) + expect(rect.matrixify()).toEqual( + new Matrix().skewY(20).skewX(10).skew(1, 2).rotate(45).translate(10, 20) + ) + }) + }) + + describe('toParent()', () => { + it('returns itself', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + const rect = g.rect(100, 100) + expect(rect.toParent(canvas)).toBe(rect) + }) + + it('does nothing if the parent is the the current element', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + const parent = g.parent() + const position = g.position() + g.toParent(g) + expect(g.parent()).toBe(parent) + expect(g.position()).toBe(position) + }) + + it('moves an element to a different container without changing its visual representation - 1', () => { + const canvas = SVG().addTo(container) + const g = canvas.group().matrix(1, 0, 1, 1, 0, 1) + const rect = g.rect(100, 100) + rect.toParent(canvas) + expect(rect.matrix()).toEqual(new Matrix(1, 0, 1, 1, 0, 1)) + expect(rect.parent()).toBe(canvas) + }) + + it('moves an element to a different container without changing its visual representation - 2', () => { + const canvas = SVG().addTo(container) + const g = canvas.group().translate(10, 20) + const rect = g.rect(100, 100) + const g2 = canvas.group().rotate(10) + rect.toParent(g2) + const actual = rect.matrix() + const expected = new Matrix().translate(10, 20).rotate(-10) + + // funny enough the dom seems to shorten the floats and precision gets lost + ;[...'abcdef'].forEach((prop) => + expect(actual[prop]).toBeCloseTo(expected[prop], 5) + ) + }) + + it('inserts the element at the specified position', () => { + const canvas = SVG().addTo(container) + const g = canvas.group() + const rect = g.rect(100, 100) + canvas.rect(100, 100) + canvas.rect(100, 100) + expect(rect.toParent(canvas, 2).position()).toBe(2) + }) + }) + + describe('toRoot()', () => { + it('calls toParent() with root node', () => { + const canvas = SVG().addTo(container) + const g = canvas.group().matrix(1, 0, 1, 1, 0, 1) + const rect = g.rect(100, 100) + const spy = spyOn(rect, 'toParent') + rect.toRoot(3) + expect(spy).toHaveBeenCalledWith(canvas, 3) + }) + }) + + describe('transform()', () => { + it('acts as full getter with no argument', () => { + const rect = new Rect().attr('transform', 'translate(10, 20) rotate(45)') + const actual = rect.transform() + const expected = new Matrix().rotate(45).translate(10, 20).decompose() + + expect(actual).toEqual(expected) + }) + + it('returns a single transformation value when string was passed', () => { + const rect = new Rect().attr('transform', 'translate(10, 20) rotate(45)') + expect(rect.transform('rotate')).toBe(45) + expect(rect.transform('translateX')).toBe(10) + expect(rect.transform('translateY')).toBe(20) + }) + + it('sets the transformation with an object', () => { + const rect = new Rect().transform({ rotate: 45, translate: [10, 20] }) + expect(rect.transform('rotate')).toBe(45) + expect(rect.transform('translateX')).toBe(10) + expect(rect.transform('translateY')).toBe(20) + }) + + it('performs a relative transformation with flag=true', () => { + const rect = new Rect() + .transform({ rotate: 45, translate: [10, 20] }) + .transform({ rotate: 10 }, true) + expect(rect.transform('rotate')).toBeCloseTo(55, 5) // rounding errors + expect(rect.transform('translateX')).toBe(10) + expect(rect.transform('translateY')).toBe(20) + }) + + it('performs a relative transformation with flag=other matrix', () => { + const rect = new Rect() + .transform({ rotate: 45, translate: [10, 20] }) + .transform({ rotate: 10 }, new Matrix().rotate(30)) + expect(rect.transform('rotate')).toBeCloseTo(40, 5) // rounding errors + expect(rect.transform('translateX')).toBe(0) + expect(rect.transform('translateY')).toBe(0) + }) + + it('performs a relative transformation with flag=other element', () => { + const referenceElement = new Rect().transform({ rotate: 30 }) + const rect = new Rect() + .transform({ rotate: 45, translate: [10, 20] }) + .transform({ rotate: 10 }, referenceElement) + expect(rect.transform('rotate')).toBeCloseTo(40, 5) // rounding errors + expect(rect.transform('translateX')).toBe(0) + expect(rect.transform('translateY')).toBe(0) + }) + }) +}) diff --git a/spec/spec/nested.js b/spec/spec/nested.js deleted file mode 100644 index 311388098..000000000 --- a/spec/spec/nested.js +++ /dev/null @@ -1,13 +0,0 @@ -describe('Nested', function() { - - afterEach(function() { - draw.clear() - }) - - describe('()', function() { - it('creates a nested svg of type SVG.Nested', function() { - expect(draw.nested() instanceof SVG.Nested).toBeTruthy() - }) - }) - -}) diff --git a/spec/spec/number.js b/spec/spec/number.js deleted file mode 100644 index 58c14bd70..000000000 --- a/spec/spec/number.js +++ /dev/null @@ -1,245 +0,0 @@ -describe('Number', function() { - var number - - beforeEach(function() { - number = new SVG.Number - }) - - describe('new', function() { - it('is zero', function() { - expect(number.value).toBe(0) - }) - it('has a blank unit', function() { - expect(number.unit).toBe('') - }) - it('accepts the unit as a second argument', function() { - number = new SVG.Number(30, '%') - expect(number.value).toBe(30) - expect(number.unit).toBe('%') - }) - it('parses a pixel value', function() { - number = new SVG.Number('20px') - expect(number.value).toBe(20) - expect(number.unit).toBe('px') - }) - it('parses a percent value', function() { - number = new SVG.Number('99%') - expect(number.value).toBe(0.99) - expect(number.unit).toBe('%') - }) - it('parses a seconds value', function() { - number = new SVG.Number('2s') - expect(number.value).toBe(2000) - expect(number.unit).toBe('s') - }) - it('parses a negative percent value', function() { - number = new SVG.Number('-89%') - expect(number.value).toBe(-0.89) - expect(number.unit).toBe('%') - }) - it('falls back to 0 if given value is NaN', function() { - number = new SVG.Number(NaN) - expect(number.value).toBe(0) - }) - it('falls back to maximum value if given number is positive infinite', function() { - number = new SVG.Number(1.7976931348623157E+10308) - expect(number.value).toBe(3.4e+38) - }) - it('falls back to minimum value if given number is negative infinite', function() { - number = new SVG.Number(-1.7976931348623157E+10308) - expect(number.value).toBe(-3.4e+38) - }) - }) - - describe('toString()', function() { - it('converts the number to a string', function() { - expect(number.toString()).toBe('0') - }) - it('appends the unit', function() { - number.value = 1.21 - number.unit = 'px' - expect(number.toString()).toBe('1.21px') - }) - it('converts percent values properly', function() { - number.value = 1.36 - number.unit = '%' - expect(number.toString()).toBe('136%') - }) - it('converts second values properly', function() { - number.value = 2500 - number.unit = 's' - expect(number.toString()).toBe('2.5s') - }) - }) - - describe('valueOf()', function() { - it('returns a numeric value for default units', function() { - expect(typeof number.valueOf()).toBe('number') - number = new SVG.Number('12') - expect(typeof number.valueOf()).toBe('number') - number = new SVG.Number(13) - expect(typeof number.valueOf()).toBe('number') - }) - it('returns a numeric value for pixel units', function() { - number = new SVG.Number('10px') - expect(typeof number.valueOf()).toBe('number') - }) - it('returns a numeric value for percent units', function() { - number = new SVG.Number('20%') - expect(typeof number.valueOf()).toBe('number') - }) - it('converts to a primitive when multiplying', function() { - number.value = 80 - expect(number * 4).toBe(320) - }) - }) - - describe('to()', function() { - beforeEach(function() { - number = number.plus(4) - }) - it('returns a new instance', function() { - expect(number.to('em')).not.toBe(number) - expect(number.to('em') instanceof SVG.Number).toBeTruthy() - }) - it('changes the unit value', function() { - number = number.to('%') - expect(number.unit).toBe('%') - }) - it('changes the output value', function() { - var oldNumber = number.valueOf() - number = number.to('%') - expect(number.toString()).toBe('400%') - }) - }) - - describe('plus()', function() { - it('returns a new instance', function() { - expect(number.plus(4.5)).not.toBe(number) - expect(number.plus(4.5) instanceof SVG.Number).toBeTruthy() - }) - it('adds a given number', function() { - expect(number.plus(3.5).valueOf()).toBe(3.5) - }) - it('adds a given percentage value', function() { - expect(number.plus('225%').valueOf()).toBe(2.25) - }) - it('adds a given pixel value', function() { - expect(number.plus('83px').valueOf()).toBe(83) - }) - it('use the unit of this number as the unit of the returned number by default', function (){ - expect(new SVG.Number('12s').plus('3%').unit).toBe('s') - }) - it('use the unit of the passed number as the unit of the returned number when this number as no unit', function() { - expect(number.plus('15%').unit).toBe('%') - }) - }) - - describe('minus()', function() { - it('subtracts a given number', function() { - expect(number.minus(3.7).valueOf()).toBe(-3.7) - }) - it('subtracts a given percentage value', function() { - expect(number.minus('223%').valueOf()).toBe(-2.23) - }) - it('subtracts a given pixel value', function() { - expect(number.minus('85px').valueOf()).toBe(-85) - }) - it('use the unit of this number as the unit of the returned number by default', function (){ - expect(new SVG.Number('12s').minus('3%').unit).toBe('s') - }) - it('use the unit of the passed number as the unit of the returned number when this number as no unit', function() { - expect(number.minus('15%').unit).toBe('%') - }) - }) - - describe('times()', function() { - beforeEach(function() { - number = number.plus(4) - }) - it('multiplies with a given number', function() { - expect(number.times(3).valueOf()).toBe(12) - }) - it('multiplies with a given percentage value', function() { - expect(number.times('110%').valueOf()).toBe(4.4) - }) - it('multiplies with a given pixel value', function() { - expect(number.times('85px').valueOf()).toBe(340) - }) - it('use the unit of this number as the unit of the returned number by default', function (){ - expect(new SVG.Number('12s').times('3%').unit).toBe('s') - }) - it('use the unit of the passed number as the unit of the returned number when this number as no unit', function() { - expect(number.times('15%').unit).toBe('%') - }) - }) - - describe('divide()', function() { - beforeEach(function() { - number = number.plus(90) - }) - it('divides by a given number', function() { - expect(number.divide(3).valueOf()).toBe(30) - }) - it('divides by a given percentage value', function() { - expect(number.divide('3000%').valueOf()).toBe(3) - }) - it('divides by a given pixel value', function() { - expect(number.divide('45px').valueOf()).toBe(2) - }) - it('use the unit of this number as the unit of the returned number by default', function (){ - expect(new SVG.Number('12s').divide('3%').unit).toBe('s') - }) - it('use the unit of the passed number as the unit of the returned number when this number as no unit', function() { - expect(number.divide('15%').unit).toBe('%') - }) - }) - - describe('morph()', function() { - it('returns itself', function() { - expect(number.morph(new SVG.Number)).toBe(number) - }) - it('prepares the color for morphing', function() { - var destination = new SVG.Number - number.morph(destination) - expect(number.destination).toEqual(destination) - }) - it('if the passed object as a relative attribute set to true, destination is relative to the current value', function() { - var n1 = new SVG.Number(3) - , n2 = new SVG.Number(7) - - n2.relative = true - n1.morph(n2) - expect(n1.destination.value).toBe(n1.value + n2.value) - }) - }) - - describe('at()', function() { - it('returns a new instance', function() { - var destination = new SVG.Number(200) - var morphed = number.morph(destination).at(0.4) - expect(morphed).not.toBe(number) - expect(morphed).not.toBe(destination) - }) - it('morphes number to a given position', function() { - var destination = new SVG.Number(200) - var morphed = number.morph(destination).at(0.4) - expect(morphed.valueOf()).toBe(80) - }) - it('morphes number to a given percentage position', function() { - var destination = new SVG.Number('100%') - var morphed = number.morph(destination).at(0.72) - expect(morphed.toString()).toBe('72%') - }) - it('use the unit of the destination number as the unit of the returned number by default', function() { - expect(new SVG.Number('100s').morph('50%').at(0.5).unit).toBe('%') - }) - it('use the unit of this number as the unit of the returned number when the destination number as no unit', function() { - expect(expect(new SVG.Number('100s').morph(50).at(0.5).unit).toBe('s')) - }) - it('returns itself when no destination specified', function() { - expect(number.at(0.5)).toBe(number) - }) - }) - -}) diff --git a/spec/spec/path.js b/spec/spec/path.js deleted file mode 100644 index f5b99ca3b..000000000 --- a/spec/spec/path.js +++ /dev/null @@ -1,252 +0,0 @@ -describe('Path', function() { - var path - - beforeEach(function() { - path = draw.path(svgPath) - }) - - afterEach(function() { - draw.clear() - }) - - describe('()', function() { - it('falls back to a single point without an argument', function() { - path = draw.path() - expect(path.attr('d')).toBe('M0 0 ') - }) - }) - - describe('array()', function() { - it('returns an instance of SVG.PathArray', function() { - expect(path.array() instanceof SVG.PathArray).toBeTruthy() - }) - it('returns the value stored in the private variable _array', function() { - expect(path.array()).toBe(path._array) - }) - }) - - describe('x()', function() { - it('returns the value of x without an argument', function() { - expect(path.x()).toBe(0) - }) - it('sets the value of x with the first argument', function() { - path.x(123) - var box = path.bbox() - expect(box.x).toBe(123) - }) - }) - - describe('y()', function() { - it('returns the value of y without an argument', function() { - expect(path.y()).toBe(0) - }) - it('sets the value of y with the first argument', function() { - path.y(345) - var box = path.bbox() - expect(box.y).toBe(345) - }) - }) - - describe('cx()', function() { - it('returns the value of cx without an argument', function() { - expect(path.cx()).toBe(50) - }) - it('sets the value of cx with the first argument', function() { - path.cx(123) - var box = path.bbox() - expect(box.cx).toBe(123) - }) - }) - - describe('cy()', function() { - it('returns the value of cy without an argument', function() { - expect(path.cy()).toBe(50) - }) - it('sets the value of cy with the first argument', function() { - path.cy(345) - var box = path.bbox() - expect(box.cy).toBe(345) - }) - }) - - describe('move()', function() { - it('sets the x and y position', function() { - path.move(123,456) - var box = path.bbox() - expect(box.x).toBe(123) - expect(box.y).toBe(456) - }) - it('sets the x and y position when scaled to half its size', function() { - path.scale(0.5, 0, 0).move(123,456) - var box = path.bbox() - expect(box.x).toBe(123) - expect(box.y).toBe(456) - }) - }) - - describe('dx()', function() { - it('moves the x positon of the element relative to the current position', function() { - path.move(50,60) - path.dx(100) - var box = path.bbox() - expect(box.x).toBe(150) - }) - }) - - describe('dy()', function() { - it('moves the y positon of the element relative to the current position', function() { - path.move(50, 60) - path.dy(120) - var box = path.bbox() - expect(box.y).toBe(180) - }) - }) - - describe('dmove()', function() { - it('moves the x and y positon of the element relative to the current position', function() { - path.move(50,60) - path.dmove(80, 25) - var box = path.bbox() - expect(box.x).toBe(130) - expect(box.y).toBe(85) - }) - }) - - describe('center()', function() { - it('sets the cx and cy position', function() { - path.center(321,567) - var box = path.bbox() - expect(box.x).toBe(271) - expect(box.y).toBe(517) - }) - }) - - describe('width()', function() { - it('sets the width of the element', function() { - path.width(234) - var box = path.bbox() - expect(box.width).toBeCloseTo(234) - }) - it('gets the width of the element without an argument', function() { - path.width(456) - expect(path.width()).toBeCloseTo(456) - }) - }) - - describe('height()', function() { - it('sets the height of the element', function() { - path.height(654) - var box = path.bbox() - expect(box.height).toBeCloseTo(654) - }) - it('gets the height of the element without an argument', function() { - path.height(321) - expect(path.height()).toBeCloseTo(321) - }) - }) - - describe('size()', function() { - it('defines the width and height of the element', function() { - path.size(987,654) - var box = path.bbox() - expect(box.width).toBeCloseTo(987) - expect(box.height).toBeCloseTo(654) - }) - it('defines the width and height proportionally with only the width value given', function() { - var box = path.bbox() - path.size(500) - expect(path.width()).toBeCloseTo(500) - expect(path.width() / path.height()).toBe(box.width / box.height) - }) - it('defines the width and height proportionally with only the height value given', function() { - var box = path.bbox() - path.size(null, 525) - expect(path.height()).toBe(525) - expect(path.width() / path.height()).toBeCloseTo(box.width / box.height) - }) - }) - - describe('scale()', function() { - it('should scale the element universally with one argument', function() { - var box1 = path.rbox() - , box2 = path.scale(2).rbox() - - expect(box1.width * 2).toBe(box2.width) - expect(box1.height * 2).toBe(box2.height) - }) - it('should scale the element over individual x and y axes with two arguments', function() { - var box1 = path.rbox() - , box2 = path.scale(2, 3.5).rbox() - - expect(box1.width * 2).toBe(box2.width) - expect(box1.height * 3.5).toBe(box2.height) - }) - }) - - describe('translate()', function() { - it('sets the translation of an element', function() { - path.transform({ x: 12, y: 12 }) - expect(path.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') - }) - }) - - describe('plot()', function() { - it('changes the d attribute of the underlying path node when a string is passed', function() { - var pathString = 'm 3,2 c 0,0 -0,13 8,14 L 5,4' - , pathArray = new SVG.PathArray(pathString) - - expect(path.plot(pathString)).toBe(path) - expect(path.attr('d')).toBe(pathString) - }) - it('clears the array cache when a value is passed', function() { - path = draw.path([ ['M', 50, 60], ['A', 60, 60, 0, 0, 0, 50, -60], ['z'] ]) - expect(path._array instanceof SVG.PathArray).toBeTruthy() - path.plot('m 3,2 c 0,0 -0,13 8,14 L 5,4') - expect(path._array).toBeUndefined() - }) - it('applies a given path string value as is', function() { - var pathString = 'm 3,2 c 0,0 -0,13 8,14 L 5,4' - - path = draw.path(pathString) - expect(path.attr('d')).toBe(pathString) - }) - it('does not parse and cache a given string value to SVG.PathArray', function() { - path = draw.path('m 3,2 c 0,0 -0,13 8,14 L 5,4') - expect(path._array).toBeUndefined() - }) - it('caches a given array value', function() { - path = draw.path([ ['M', 50, 60], ['A', 60, 60, 0, 0, 0, 50, -60], ['H', 100], ['L', 20, 30], ['Z'] ]) - expect(path._array instanceof SVG.PathArray).toBeTruthy() - }) - it('returns the path array when no arguments are passed', function () { - expect(path.plot()).toBe(path.array()) - }) - }) - - describe('clear()', function() { - it('clears the cached SVG.PathArray instance', function() { - path = draw.path(svgPath) - path.clear() - expect(path._array).toBeUndefined() - }) - }) - - describe('toString()', function() { - it('renders path array correctly to string', function() { - path = path.plot(['M', 50, 60, 'A', 60, 60, 0, 0, 0, 50, -60, 'H', 100, 'V', 100, 'L', 20, 30, 'C', 10, 20, 30, 40, 50, 60 ]) - expect(path.node.getAttribute('d')).toBe('M50 60A60 60 0 0 0 50 -60H100V100L20 30C10 20 30 40 50 60 ') - }) - }) - - describe('length()', function() { - it('gets the total length of the path', function() { - expect(path.length()).toBe(path.node.getTotalLength()) - }) - }) - - describe('pointAt()', function() { - it('gets a point at given length', function() { - expect(path.pointAt(100)).toEqual(path.node.getPointAtLength(100)) - }) - }) -}) diff --git a/spec/spec/pattern.js b/spec/spec/pattern.js deleted file mode 100644 index e12d11e1f..000000000 --- a/spec/spec/pattern.js +++ /dev/null @@ -1,69 +0,0 @@ -describe('Pattern', function() { - var rect, pattern - - beforeEach(function() { - rect = draw.rect(100,100) - pattern = draw.pattern(20, 30, function(add) { - add.rect(10,10).move(10,10) - add.circle(30) - }) - }) - - afterEach(function() { - rect.remove() - pattern.remove() - }) - - it('is an instance of SVG.Pattern', function() { - expect(pattern instanceof SVG.Pattern).toBe(true) - }) - - it('allows creation of a new gradient without block', function() { - pattern = draw.pattern(10,30) - expect(pattern.children().length).toBe(0) - }) - - describe('fill()', function() { - it('returns the id of the pattern wrapped in url()', function() { - expect(pattern.fill()).toBe('url(#' + pattern.attr('id') + ')') - }) - }) - - describe('attr()', function() { - it('will catch transform attribues and convert them to patternTransform', function() { - expect(pattern.translate(100,100).attr('patternTransform')).toBe('matrix(1,0,0,1,100,100)') - }) - }) - - describe('toString()', function() { - it('returns the id of the pattern wrapped in url()', function() { - expect(pattern + '').toBe('url(#' + pattern.attr('id') + ')') - }) - it('is called when instance is passed as an attribute value', function() { - rect.attr('fill', pattern) - expect(rect.attr('fill')).toBe('url(#' + pattern.attr('id') + ')') - }) - it('is called when instance is passed in a fill() method', function() { - rect.fill(pattern) - expect(rect.attr('fill')).toBe('url(#' + pattern.attr('id') + ')') - }) - }) - - describe('update()', function() { - - it('removes all existing children first', function() { - pattern = draw.pattern(30, 30, function(add) { - add.rect(10,10).move(10,10) - add.circle(30) - }) - expect(pattern.children().length).toBe(2) - pattern.update(function(add) { - add.rect(10,10).move(10,10) - add.circle(30) - }) - expect(pattern.children().length).toBe(2) - }) - - }) - -}) diff --git a/spec/spec/point.js b/spec/spec/point.js deleted file mode 100644 index 8eacb8452..000000000 --- a/spec/spec/point.js +++ /dev/null @@ -1,140 +0,0 @@ -describe('Point', function() { - var point - - describe('initialization', function() { - - describe('without a source', function() { - - beforeEach(function() { - point = new SVG.Point - }) - - it('creates a new point with default values', function() { - expect(point.x).toBe(0) - expect(point.y).toBe(0) - }) - - }) - - describe('with x and y given', function() { - it('creates a point with given values', function() { - var point = new SVG.Point(2,4) - - expect(point.x).toBe(2) - expect(point.y).toBe(4) - }) - }) - - describe('with only x given', function() { - it('creates a point using the given value for both x and y', function() { - var point = new SVG.Point(7) - - expect(point.x).toBe(7) - expect(point.y).toBe(7) - }) - }) - - describe('with array given', function() { - it('creates a point from array', function() { - var point = new SVG.Point([2,4]) - - expect(point.x).toBe(2) - expect(point.y).toBe(4) - }) - }) - - describe('with object given', function() { - it('creates a point from object', function() { - var point = new SVG.Point({x:2,y:4}) - - expect(point.x).toBe(2) - expect(point.y).toBe(4) - }) - }) - - describe('with SVG.Point given', function() { - it('creates a point from SVG.Point', function() { - var point = new SVG.Point(new SVG.Point(2,4)) - - expect(point.x).toBe(2) - expect(point.y).toBe(4) - }) - }) - - describe('with native SVGPoint given', function() { - it('creates a point from native SVGPoint', function() { - var point = new SVG.Point(new SVG.Point(2,4).native()) - - expect(point.x).toBe(2) - expect(point.y).toBe(4) - }) - }) - - }) - - describe('clone()', function() { - it('returns cloned point', function() { - var point1 = new SVG.Point(1,1) - , point2 = point1.clone() - - expect(point1).toEqual(point2) - expect(point1).not.toBe(point2) - }) - }) - - describe('morph()', function() { - it('stores a given point for morphing', function() { - var point1 = new SVG.Point(1,1) - , point2 = new SVG.Point(2,2) - - point1.morph(point2) - - expect(point1.destination).toEqual(point2) - }) - it('stores a clone, not the given matrix itself', function() { - var point1 = new SVG.Point(1,1) - , point2 = new SVG.Point(2,2) - - point1.morph(point2) - - expect(point1.destination).not.toBe(point2) - }) - it('allow passing the point by directly passing its coordinates', function() { - var point1 = new SVG.Point(1,1) - , point2 = new SVG.Point(2,2) - - point1.morph(point2.x, point2.y) - - expect(point1.destination).toEqual(point2) - }) - }) - - describe('at()', function() { - it('returns a morphed point at a given position', function() { - var point1 = new SVG.Point(1,1) - , point2 = new SVG.Point(2,2) - , point3 = point1.morph(point2).at(0.5) - - expect(point3).toEqual(new SVG.Point(1.5, 1.5)) - }) - it('returns itself when no destination specified', function() { - var point = new SVG.Point(1,1) - expect(point.at(0.4)).toBe(point) - }) - }) - - describe('transform()', function() { - it('returns a point transformed with given matrix', function() { - var point = new SVG.Point(1,5) - , matrix = new SVG.Matrix(0,0,1,0,0,1) - - expect(point.transform(matrix)).toEqual(new SVG.Point(5,1)) - }) - }) - - describe('native()', function() { - it('returns native SVGPoint', function() { - expect(new SVG.Point().native() instanceof window.SVGPoint).toBeTruthy() - }) - }) -}) diff --git a/spec/spec/polygon.js b/spec/spec/polygon.js deleted file mode 100644 index 789c3b9c9..000000000 --- a/spec/spec/polygon.js +++ /dev/null @@ -1,228 +0,0 @@ -describe('Polygon', function() { - var polygon - - beforeEach(function() { - polygon = draw.polygon('0,0 100,0 100,100 0,100') - }) - - afterEach(function() { - draw.clear() - }) - - describe('()', function(){ - it('falls back to a single point without an argument', function() { - polygon = draw.polygon() - expect(polygon.attr('points')).toBe('0,0') - }) - }) - - - describe('array()', function() { - it('returns an instance of SVG.PointArray', function() { - expect(polygon.array() instanceof SVG.PointArray).toBeTruthy() - }) - it('returns the value stored in the private variable _array', function() { - expect(polygon.array()).toBe(polygon._array) - }) - }) - - describe('x()', function() { - it('returns the value of x without an argument', function() { - expect(polygon.x()).toBe(0) - }) - it('sets the value of x with the first argument', function() { - polygon.x(123) - var box = polygon.bbox() - expect(box.x).toBe(123) - }) - }) - - describe('y()', function() { - it('returns the value of y without an argument', function() { - expect(polygon.y()).toBe(0) - }) - it('sets the value of y with the first argument', function() { - polygon.y(345) - var box = polygon.bbox() - expect(box.y).toBe(345) - }) - }) - - describe('cx()', function() { - it('returns the value of cx without an argument', function() { - expect(polygon.cx()).toBe(50) - }) - it('sets the value of cx with the first argument', function() { - polygon.cx(123) - var box = polygon.bbox() - expect(box.cx).toBe(123) - }) - }) - - describe('cy()', function() { - it('returns the value of cy without an argument', function() { - expect(polygon.cy()).toBe(50) - }) - it('sets the value of cy with the first argument', function() { - polygon.cy(345) - var box = polygon.bbox() - expect(box.cy).toBe(345) - }) - }) - - describe('move()', function() { - it('sets the x and y position', function() { - polygon.move(123,456) - var box = polygon.bbox() - expect(box.x).toBe(123) - expect(box.y).toBe(456) - }) - }) - - describe('dx()', function() { - it('moves the x positon of the element relative to the current position', function() { - polygon.move(50,60) - polygon.dx(100) - var box = polygon.bbox() - expect(box.x).toBe(150) - }) - }) - - describe('dy()', function() { - it('moves the y positon of the element relative to the current position', function() { - polygon.move(50, 60) - polygon.dy(120) - var box = polygon.bbox() - expect(box.y).toBe(180) - }) - }) - - describe('dmove()', function() { - it('moves the x and y positon of the element relative to the current position', function() { - polygon.move(50,60) - polygon.dmove(80, 25) - var box = polygon.bbox() - expect(box.x).toBe(130) - expect(box.y).toBe(85) - }) - }) - - describe('center()', function() { - it('sets the cx and cy position', function() { - polygon.center(321,567) - var box = polygon.bbox() - expect(box.x).toBe(271) - expect(box.y).toBe(517) - }) - }) - - describe('width()', function() { - it('sets the width and height of the element', function() { - polygon.width(987) - var box = polygon.bbox() - expect(box.width).toBeCloseTo(987) - }) - it('gets the width and height of the element without an argument', function() { - polygon.width(789) - expect(polygon.width()).toBeCloseTo(789) - }) - }) - - describe('height()', function() { - it('sets the height and height of the element', function() { - polygon.height(987) - var box = polygon.bbox() - expect(box.height).toBeCloseTo(987) - }) - it('gets the height and height of the element without an argument', function() { - polygon.height(789) - expect(polygon.height()).toBeCloseTo(789) - }) - }) - - describe('size()', function() { - it('should define the width and height of the element', function() { - polygon.size(987,654) - var box = polygon.bbox() - expect(box.width).toBeCloseTo(987) - expect(box.height).toBeCloseTo(654) - }) - it('defines the width and height proportionally with only the width value given', function() { - var box = polygon.bbox() - polygon.size(500) - expect(polygon.width()).toBe(500) - expect(polygon.width() / polygon.height()).toBe(box.width / box.height) - }) - it('defines the width and height proportionally with only the height value given', function() { - var box = polygon.bbox() - polygon.size(null, 525) - expect(polygon.height()).toBe(525) - expect(polygon.width() / polygon.height()).toBe(box.width / box.height) - }) - }) - - describe('scale()', function() { - it('should scale the element universally with one argument', function() { - var box1 = polygon.rbox() - , box2 = polygon.scale(2).rbox() - - expect(box2.width).toBe(box1.width * 2) - expect(box2.height).toBe(box1.height * 2) - }) - it('should scale the element over individual x and y axes with two arguments', function() { - var box1 = polygon.rbox() - , box2 = polygon.scale(2, 3.5).rbox() - - expect(box2.width).toBe(box1.width * 2) - expect(box2.height).toBe(box1.height * 3.5) - }) - }) - - describe('translate()', function() { - it('sets the translation of an element', function() { - polygon.transform({ x: 12, y: 12 }) - expect(polygon.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') - }) - }) - - describe('plot()', function() { - it('changes the points attribute of the underlying polygon node when a string is passed', function() { - var pointString = '100,50 75,20 200,100' - , pointArray = new SVG.PointArray(pointString) - - expect(polygon.plot(pointString)).toBe(polygon) - expect(polygon.attr('points')).toBe(pointArray.toString()) - }) - it('returns the point array when no arguments are passed', function () { - expect(polygon.plot()).toBe(polygon.array()) - }) - it('clears the array cache when a value is passed', function() { - polygon = draw.polygon([100,50,75,20,200,100]) - expect(polygon._array instanceof SVG.PointArray).toBeTruthy() - polygon.plot('100,50 75,20 200,100') - expect(polygon._array).toBeUndefined() - }) - it('applies a given polygon string value as is', function() { - var polyString = '100,50,75,20,200,100' - - polygon = draw.polygon(polyString) - expect(polygon.attr('points')).toBe(polyString) - }) - it('does not parse and cache a given string value to SVG.PointArray', function() { - polygon = draw.polygon('100,50 75,20 200,100') - expect(polygon._array).toBeUndefined() - }) - it('caches a given array value', function() { - polygon = draw.polygon([100,50,75,20,200,100]) - expect(polygon._array instanceof SVG.PointArray).toBeTruthy() - }) - }) - - describe('clear()', function() { - it('clears the cached SVG.PointArray instance', function() { - polygon = draw.polygon([100,50,75,20,200,100]) - polygon.clear() - expect(polygon._array).toBeUndefined() - }) - }) -}) diff --git a/spec/spec/polyline.js b/spec/spec/polyline.js deleted file mode 100644 index ca516bd63..000000000 --- a/spec/spec/polyline.js +++ /dev/null @@ -1,228 +0,0 @@ -describe('Polyline', function() { - var polyline - - beforeEach(function() { - polyline = draw.polyline('0,0 100,0 100,100 0,100') - }) - - afterEach(function() { - draw.clear() - }) - - describe('()', function(){ - it('falls back to a single point without an argument', function() { - polyline = draw.polyline() - expect(polyline.attr('points')).toBe('0,0') - }) - }) - - - describe('array()', function() { - it('returns an instance of SVG.PointArray', function() { - expect(polyline.array() instanceof SVG.PointArray).toBeTruthy() - }) - it('returns the value stored in the private variable _array', function() { - expect(polyline.array()).toBe(polyline._array) - }) - }) - - describe('x()', function() { - it('returns the value of x without an argument', function() { - expect(polyline.x()).toBe(0) - }) - it('sets the value of x with the first argument', function() { - polyline.x(123) - var box = polyline.bbox() - expect(box.x).toBe(123) - }) - }) - - describe('y()', function() { - it('returns the value of y without an argument', function() { - expect(polyline.y()).toBe(0) - }) - it('sets the value of y with the first argument', function() { - polyline.y(345) - var box = polyline.bbox() - expect(box.y).toBe(345) - }) - }) - - describe('cx()', function() { - it('returns the value of cx without an argument', function() { - expect(polyline.cx()).toBe(50) - }) - it('sets the value of cx with the first argument', function() { - polyline.cx(123) - var box = polyline.bbox() - expect(box.cx).toBe(123) - }) - }) - - describe('cy()', function() { - it('returns the value of cy without an argument', function() { - expect(polyline.cy()).toBe(50) - }) - it('sets the value of cy with the first argument', function() { - polyline.cy(345) - var box = polyline.bbox() - expect(box.cy).toBe(345) - }) - }) - - describe('move()', function() { - it('sets the x and y position', function() { - polyline.move(123,456) - var box = polyline.bbox() - expect(box.x).toBe(123) - expect(box.y).toBe(456) - }) - }) - - describe('dx()', function() { - it('moves the x positon of the element relative to the current position', function() { - polyline.move(50,60) - polyline.dx(100) - var box = polyline.bbox() - expect(box.x).toBe(150) - }) - }) - - describe('dy()', function() { - it('moves the y positon of the element relative to the current position', function() { - polyline.move(50, 60) - polyline.dy(120) - var box = polyline.bbox() - expect(box.y).toBe(180) - }) - }) - - describe('dmove()', function() { - it('moves the x and y positon of the element relative to the current position', function() { - polyline.move(50,60) - polyline.dmove(80, 25) - var box = polyline.bbox() - expect(box.x).toBe(130) - expect(box.y).toBe(85) - }) - }) - - describe('center()', function() { - it('sets the cx and cy position', function() { - polyline.center(321,567) - var box = polyline.bbox() - expect(box.x).toBe(271) - expect(box.y).toBe(517) - }) - }) - - describe('width()', function() { - it('sets the width and height of the element', function() { - polyline.width(987) - var box = polyline.bbox() - expect(box.width).toBeCloseTo(987, 1) - }) - it('gets the width and height of the element without an argument', function() { - polyline.width(789) - expect(polyline.width()).toBeCloseTo(789) - }) - }) - - describe('height()', function() { - it('sets the height and height of the element', function() { - polyline.height(987) - var box = polyline.bbox() - expect(box.height).toBeCloseTo(987) - }) - it('gets the height and height of the element without an argument', function() { - polyline.height(789) - expect(polyline.height()).toBeCloseTo(789) - }) - }) - - describe('size()', function() { - it('should define the width and height of the element', function() { - polyline.size(987,654) - var box = polyline.bbox() - expect(box.width).toBeCloseTo(987) - expect(box.height).toBeCloseTo(654) - }) - it('defines the width and height proportionally with only the width value given', function() { - var box = polyline.bbox() - polyline.size(500) - expect(polyline.width()).toBe(500) - expect(polyline.width() / polyline.height()).toBe(box.width / box.height) - }) - it('defines the width and height proportionally with only the height value given', function() { - var box = polyline.bbox() - polyline.size(null, 525) - expect(polyline.height()).toBe(525) - expect(polyline.width() / polyline.height()).toBe(box.width / box.height) - }) - }) - - describe('scale()', function() { - it('should scale the element universally with one argument', function() { - var box1 = polyline.rbox() - , box2 = polyline.scale(2).rbox() - - expect(box2.width).toBe(box1.width * 2) - expect(box2.height).toBe(box1.height * 2) - }) - it('should scale the element over individual x and y axes with two arguments', function() { - var box1 = polyline.rbox() - , box2 = polyline.scale(2, 3.5).rbox() - - expect(box2.width).toBe(box1.width * 2) - expect(box2.height).toBe(box1.height * 3.5) - }) - }) - - describe('translate()', function() { - it('sets the translation of an element', function() { - polyline.transform({ x: 12, y: 12 }) - expect(polyline.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') - }) - }) - - describe('plot()', function() { - it('change the points attribute of the underlying polyline node when a string is passed', function() { - var pointString = '100,50 75,20 200,100' - , pointArray = new SVG.PointArray(pointString) - - expect(polyline.plot(pointString)).toBe(polyline) - expect(polyline.attr('points')).toBe(pointArray.toString()) - }) - it('return the point array when no arguments are passed', function () { - expect(polyline.plot()).toBe(polyline.array()) - }) - it('clears the array cache when a value is passed', function() { - polyline = draw.polyline([100,50,75,20,200,100]) - expect(polyline._array instanceof SVG.PointArray).toBeTruthy() - polyline.plot('100,50 75,20 200,100') - expect(polyline._array).toBeUndefined() - }) - it('applies a given polyline string value as is', function() { - var polyString = '100,50,75,20,200,100' - - polyline = draw.polyline(polyString) - expect(polyline.attr('points')).toBe(polyString) - }) - it('does not parse and cache a given string value to SVG.PointArray', function() { - polyline = draw.polyline('100,50 75,20 200,100') - expect(polyline._array).toBeUndefined() - }) - it('caches a given array value', function() { - polyline = draw.polyline([100,50,75,20,200,100]) - expect(polyline._array instanceof SVG.PointArray).toBeTruthy() - }) - }) - - describe('clear()', function() { - it('clears the cached SVG.PointArray instance', function() { - polyline = draw.polyline([100,50,75,20,200,100]) - polyline.clear() - expect(polyline._array).toBeUndefined() - }) - }) -}) diff --git a/spec/spec/rect.js b/spec/spec/rect.js deleted file mode 100644 index 5ce65c0fc..000000000 --- a/spec/spec/rect.js +++ /dev/null @@ -1,179 +0,0 @@ -describe('Rect', function() { - var rect - - beforeEach(function() { - rect = draw.rect(220,100) - }) - - afterEach(function() { - draw.clear() - }) - - describe('x()', function() { - it('should return the value of x without an argument', function() { - expect(rect.x()).toBe(0) - }) - it('should set the value of x with the first argument', function() { - rect.x(123) - expect(rect.node.getAttribute('x')).toBe('123') - }) - }) - - describe('y()', function() { - it('should return the value of y without an argument', function() { - expect(rect.y()).toBe(0) - }) - it('should set the value of y with the first argument', function() { - rect.y(345) - expect(rect.node.getAttribute('y')).toBe('345') - }) - }) - - describe('cx()', function() { - it('should return the value of cx without an argument', function() { - expect(rect.cx()).toBe(110) - }) - it('should set the value of cx with the first argument', function() { - rect.cx(123) - var box = rect.bbox() - expect(box.cx).toBe(123) - }) - }) - - describe('cy()', function() { - it('should return the value of cy without an argument', function() { - expect(rect.cy()).toBe(50) - }) - it('should set the value of cy with the first argument', function() { - rect.cy(345) - var box = rect.bbox() - expect(box.cy).toBe(345) - }) - }) - - describe('radius()', function() { - it('should set the rx and ry', function() { - rect.radius(10,20) - expect(rect.node.getAttribute('rx')).toBe('10') - expect(rect.node.getAttribute('ry')).toBe('20') - }) - it('should set the rx and ry if only rx given', function() { - rect.radius(30) - expect(rect.node.getAttribute('rx')).toBe('30') - expect(rect.node.getAttribute('ry')).toBe('30') - }) - }) - - describe('move()', function() { - it('should set the x and y position', function() { - rect.move(123,456) - expect(rect.node.getAttribute('x')).toBe('123') - expect(rect.node.getAttribute('y')).toBe('456') - }) - }) - - describe('dx()', function() { - it('moves the x positon of the element relative to the current position', function() { - rect.move(50,60) - rect.dx(100) - expect(rect.node.getAttribute('x')).toBe('150') - }) - }) - - describe('dy()', function() { - it('moves the y positon of the element relative to the current position', function() { - rect.move(50,60) - rect.dy(120) - expect(rect.node.getAttribute('y')).toBe('180') - }) - }) - - describe('dmove()', function() { - it('moves the x and y positon of the element relative to the current position', function() { - rect.move(50,60) - rect.dmove(80, 25) - expect(rect.node.getAttribute('x')).toBe('130') - expect(rect.node.getAttribute('y')).toBe('85') - }) - }) - - describe('center()', function() { - it('should set the cx and cy position', function() { - rect.center(321,567) - var box = rect.bbox() - expect(box.cx).toBe(321) - expect(box.cy).toBe(567) - }) - }) - - describe('width()', function() { - it('sets the width of the element', function() { - rect.width(789) - expect(rect.node.getAttribute('width')).toBe('789') - }) - it('gets the width of the element if the argument is null', function() { - expect(rect.width().toString()).toBe(rect.node.getAttribute('width')) - }) - }) - - describe('height()', function() { - it('sets the height of the element', function() { - rect.height(1236) - expect(rect.node.getAttribute('height')).toBe('1236') - }) - it('gets the height of the element if the argument is null', function() { - expect(rect.height().toString()).toBe(rect.node.getAttribute('height')) - }) - }) - - describe('size()', function() { - it('should define the width and height of the element', function() { - rect.size(987,654) - expect(rect.node.getAttribute('width')).toBe('987') - expect(rect.node.getAttribute('height')).toBe('654') - }) - it('defines the width and height proportionally with only the width value given', function() { - var box = rect.bbox() - rect.size(500) - expect(rect.width()).toBe(500) - expect(rect.width() / rect.height()).toBe(box.width / box.height) - }) - it('defines the width and height proportionally with only the height value given', function() { - var box = rect.bbox() - rect.size(null, 525) - expect(rect.height()).toBe(525) - expect(rect.width() / rect.height()).toBe(box.width / box.height) - }) - }) - - describe('scale()', function() { - it('should scale the element universally with one argument', function() { - var box = rect.scale(2).rbox() - - expect(box.width).toBe(rect.attr('width') * 2) - expect(box.height).toBe(rect.attr('height') * 2) - }) - it('should scale the element over individual x and y axes with two arguments', function() { - var box = rect.scale(2, 3.5).rbox() - - expect(box.width).toBe(rect.attr('width') * 2) - expect(box.height).toBe(rect.attr('height') * 3.5) - }) - }) - - describe('translate()', function() { - it('should set the translation of an element', function() { - rect.transform({ x: 12, y: 12 }) - expect(rect.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') - }) - }) - -}) - - - - - - - - diff --git a/spec/spec/regex.js b/spec/spec/regex.js deleted file mode 100644 index 9a14bec74..000000000 --- a/spec/spec/regex.js +++ /dev/null @@ -1,92 +0,0 @@ -describe('Regex', function() { - - describe('matchers', function() { - - describe('numberAndUnit', function() { - var match - - it('is true with a positive unit value', function() { - match = ('10%').match(SVG.regex.numberAndUnit) - expect(match[1]).toBe('10') - expect(match[5]).toBe('%') - }) - it('is true with a negative unit value', function() { - match = ('-11%').match(SVG.regex.numberAndUnit) - expect(match[1]).toBe('-11') - expect(match[5]).toBe('%') - }) - it('is false with a positive unit value', function() { - match = ('NotAUnit').match(SVG.regex.numberAndUnit) - expect(match).toBeNull() - }) - - it('is true with a number', function() { - ["1", "-1", "+15", "1.55", ".5", "5.", "1.3e2", "1E-4", "1e+12"].forEach(function(s) { - expect(SVG.regex.numberAndUnit.test(s)).toBeTruthy() - }) - }) - it('is false with a faulty number', function() { - ["+-1", "1.2.3", "1+1", "1e4.5", ".5.", "1f5", "."].forEach(function(s) { - expect(SVG.regex.numberAndUnit.test(s)).toBeFalsy() - }) - }) - it('is true with a number with unit', function() { - ["1px", "-1em", "+15%", "1.55s", ".5pt", "5.deg", "1.3e2rad", "1E-4grad", "1e+12cm"].forEach(function(s) { - expect(SVG.regex.numberAndUnit.test(s)).toBeTruthy() - }) - }) - it('is false with a faulty number or wrong unit', function() { - ["1em1", "-1eq,5"].forEach(function(s) { - expect(SVG.regex.numberAndUnit.test(s)).toBeFalsy() - }) - }) - - }) - }) - - describe('testers', function() { - - describe('isHex', function() { - it('is true with a three based hex', function() { - expect(SVG.regex.isHex.test('#f09')).toBeTruthy() - }) - it('is true with a six based hex', function() { - expect(SVG.regex.isHex.test('#fe0198')).toBeTruthy() - }) - it('is false with a faulty hex', function() { - expect(SVG.regex.isHex.test('###')).toBeFalsy() - expect(SVG.regex.isHex.test('#0')).toBeFalsy() - expect(SVG.regex.isHex.test('f06')).toBeFalsy() - }) - }) - - describe('isRgb', function() { - it('is true with an rgb value', function() { - expect(SVG.regex.isRgb.test('rgb(255,66,100)')).toBeTruthy() - }) - it('is false with a non-rgb value', function() { - expect(SVG.regex.isRgb.test('hsb(255, 100, 100)')).toBeFalsy() - }) - }) - - describe('isNumber', function() { - - it('is true with a number', function() { - ["1", "-1", "+15", "1.55", ".5", "5.", "1.3e2", "1E-4", "1e+12"].forEach(function(s) { - expect(SVG.regex.isNumber.test(s)).toBeTruthy() - }) - }) - - it('is false with a faulty number', function() { - ["1a", "+-1", "1.2.3", "1+1", "1e4.5", ".5.", "1f5", "."].forEach(function(s) { - expect(SVG.regex.isNumber.test(s)).toBeFalsy() - }) - }) - - }) - - - - }) - -}) \ No newline at end of file diff --git a/spec/spec/selector.js b/spec/spec/selector.js deleted file mode 100644 index b28213d71..000000000 --- a/spec/spec/selector.js +++ /dev/null @@ -1,57 +0,0 @@ -describe('Selector', function() { - - describe('get()', function() { - it('gets an element\'s instance by id', function() { - var rect = draw.rect(111, 333) - - expect(SVG.get(rect.attr('id'))).toBe(rect) - }) - it('makes all the element\'s methods available', function() { - var element = draw.group() - , got = SVG.get(element.attr('id')) - - expect(got.attr()).toEqual(element.attr()) - }) - it('gets a referenced element by attribute value', function() { - var rect = draw.defs().rect(100, 100) - , use = draw.use(rect) - , mark = draw.marker(10, 10) - , path = draw.path(svgPath).marker('end', mark) - - expect(SVG.get(use.attr('href'))).toBe(rect) - expect(SVG.get(path.attr('marker-end'))).toBe(mark) - }) - }) - - describe('select()', function() { - var e1, e2, e3, e4 ,e5 - - beforeEach(function() { - e1 = draw.rect(100, 100).addClass('selectable-element') - e2 = draw.rect(100, 100).addClass('unselectable-element') - e3 = draw.rect(100, 100).addClass('selectable-element') - e4 = draw.rect(100, 100).addClass('unselectable-element') - e5 = draw.rect(100, 100).addClass('selectable-element') - }) - it('gets all elements with a given class name', function() { - expect(SVG.select('rect.selectable-element').valueOf()).toEqual([e1, e3, e5]) - }) - it('returns an instance of SVG.Set', function() { - expect(SVG.select('rect.selectable-element') instanceof SVG.Set).toBe(true) - }) - }) - - describe('Parent#select()', function() { - it('gets all elements with a given class name inside a given element', function() { - var group = draw.group() - , e1 = draw.rect(100, 100).addClass('selectable-element') - , e2 = draw.rect(100, 100).addClass('unselectable-element') - , e3 = group.rect(100, 100).addClass('selectable-element') - , e4 = draw.rect(100, 100).addClass('unselectable-element') - , e5 = group.rect(100, 100).addClass('selectable-element') - - expect(group.select('rect.selectable-element').valueOf()).toEqual([e3, e5]) - }) - }) - -}) \ No newline at end of file diff --git a/spec/spec/set.js b/spec/spec/set.js deleted file mode 100644 index 50c3126ad..000000000 --- a/spec/spec/set.js +++ /dev/null @@ -1,215 +0,0 @@ -describe('Set', function() { - var set, e1, e2, e3, e4, e5 - - beforeEach(function() { - draw.attr('viewBox', null) - set = draw.set() - e1 = draw.rect(100,100).attr('id', 'e1').move(200,250) - e2 = draw.ellipse(100,100).attr('id', 'e2') - e3 = draw.line(0,0,100,100).attr('id', 'e3') - e4 = draw.circle(50).attr('id', 'e4') - e5 = draw.polyline('0,0 10,20 30,50 80,100').attr('id', 'e5') - }) - - afterEach(function() { - draw.clear() - }) - - it('creates the set method on SVG.Container instances', function() { - expect(draw.set() instanceof SVG.Set).toBeTruthy() - }) - - it('creates a set with initial value', function() { - var members = [1, 2, 4] - - expect(draw.set(members).valueOf()).toBe(members) - }) - - describe('add()', function() { - it('returns the set instance', function() { - expect(set.add(e1)).toBe(set) - }) - it('stores given element', function() { - set.add(e1).add(e2).add(e3) - expect(set.valueOf()).toEqual([e1,e2,e3]) - expect(set.members.length).toBe(3) - }) - it('accepts multiple elements at once', function() { - set.add(e1, e2, e3, e4, e5) - expect(set.valueOf()).toEqual([e1, e2, e3, e4, e5]) - expect(set.members.length).toBe(5) - }) - }) - - describe('remove()', function() { - it('returns the set instance', function() { - set.add(e1) - expect(set.remove(e1)).toBe(set) - }) - it('removes given element', function() { - set.add(e1).add(e2).add(e3).remove(e2) - expect(set.valueOf()).toEqual([e1,e3]) - expect(set.members.length).toBe(2) - }) - }) - - describe('each()', function() { - it('returns the set instance', function() { - expect(set.each(function(){})).toBe(set) - }) - it('iterates over all members of the set', function() { - var ids = [] - set.add(e1).add(e2).add(e3) - - set.each(function() { - ids.push(this.attr('id')) - }) - - expect(ids.length).toBe(3) - expect(ids).toEqual(['e1','e2','e3']) - }) - }) - - describe('clear()', function() { - it('returns the set instance', function() { - expect(set.clear()).toBe(set) - }) - it('removes all members from set', function() { - set.add(e1).add(e2).add(e3).add(e4).add(e5).clear() - expect(set.members.length).toBe(0) - }) - }) - - describe('get()', function() { - it('returns member at given index', function() { - set.add(e1).add(e2).add(e3).add(e4).add(e5) - expect(set.get(2)).toBe(e3) - }) - }) - - describe('first()', function() { - it('returns first member', function() { - set.add(e1).add(e2).add(e3).add(e4).add(e5) - expect(set.first()).toBe(e1) - }) - }) - - describe('last()', function() { - it('returns last member', function() { - set.add(e1).add(e2).add(e3).add(e4).add(e5) - expect(set.last()).toBe(e5) - }) - }) - - describe('has()', function() { - it('checks if a given element is present in set', function() { - set.add(e1).add(e2).add(e3).add(e4).add(e5) - expect(set.has(e4)).toBeTruthy() - }) - }) - - describe('length()', function() { - it('gets the length of the set', function() { - set.add(e1).add(e2).add(e3).add(e4).add(e5) - expect(set.length()).toBe(5) - }) - }) - - describe('index()', function() { - it('returns the index of a given element within the set', function() { - set.add(e1).add(e2).add(e3).add(e5) - expect(set.index(e1)).toBe(0) - expect(set.index(e2)).toBe(1) - expect(set.index(e3)).toBe(2) - expect(set.index(e4)).toBe(-1) - expect(set.index(e5)).toBe(3) - }) - }) - - describe('valueOf()', function() { - it('returns the members array', function() { - set.add(e1) - expect(set.valueOf()).toBe(set.members) - }) - }) - - describe('bbox()', function() { - it('returns the bounding box of all elements', function() { - set.add(e1).add(e2).add(e3).add(e4).add(e5) - - var box = set.bbox() - - expect(box.x).toBeCloseTo(0) - expect(box.y).toBeCloseTo(0, 0) - expect(box.width).toBeCloseTo(300) - expect(box.height).toBeCloseTo(350) - }) - it('returns an instance of SVG.RBox', function() { - set.add(e1).add(e2).add(e3).add(e4).add(e5) - - expect(set.bbox() instanceof SVG.RBox).toBeTruthy() - }) - it('returns an empty bounding box wiht no members', function() { - var box = set.bbox() - - expect(box.x).toBe(0) - expect(box.y).toBe(0) - expect(box.width).toBe(0) - expect(box.height).toBe(0) - }) - }) - - describe('method alias', function() { - - describe('attr()', function() { - it('is applied to every member of the set', function() { - var fills = [] - - set.add(e1).add(e2).add(e3).add(e4).add(e5).attr('fill', '#ff0099') - set.each(function() { - fills.push(this.attr('fill')) - }) - - expect(fills).toEqual(['#ff0099','#ff0099','#ff0099','#ff0099','#ff0099']) - }) - }) - - }) - - describe('method inheritance', function() { - - beforeEach(function() { - SVG.extend(SVG.Element, { - orange: function() { - this.fill('#ff6600') - } - }) - }) - - it('inherits newly added element methods after initialisation', function() { - expect(typeof set.orange).toBe('function') - }) - - it('applies newly inherited methods properly to members', function() { - var fills = [] - - set.add(e1).add(e2).add(e3).add(e4).add(e5).orange() - set.each(function() { - fills.push(this.attr('fill')) - }) - - expect(fills).toEqual(['#ff6600','#ff6600','#ff6600','#ff6600','#ff6600']) - }) - - }) - -}) - - - - - - - - - diff --git a/spec/spec/sugar.js b/spec/spec/sugar.js deleted file mode 100644 index d55088694..000000000 --- a/spec/spec/sugar.js +++ /dev/null @@ -1,358 +0,0 @@ -describe('Sugar', function() { - - var rect - - beforeEach(function() { - draw.attr('viewBox', null) - }) - - afterEach(function() { - draw.clear() - }) - - describe('fill()', function() { - beforeEach(function() { - rect = draw.rect(100,100) - }) - - afterEach(function() { - rect.remove() - }) - - it('returns the node reference', function() { - expect(rect.fill('red')).toBe(rect) - }) - - it('sets the given value', function() { - expect(rect.fill('red').attr('fill')).toBe('red') - }) - - it('sets the given value with object given', function() { - rect.fill({color: 'red', opacity: 0.5, rule: 'odd'}) - expect(rect.attr('fill')).toBe('red') - expect(rect.attr('fill-opacity')).toBe(0.5) - expect(rect.attr('fill-rule')).toBe('odd') - }) - - it('is a nop with no argument given and returns node reference', function() { - rect.fill('red') - expect(rect.fill()).toBe(rect) - expect(rect.attr('fill')).toBe('red') - }) - }) - - describe('rotate()', function() { - var rect, spy, undefined - - beforeEach(function() { - rect = draw.rect(100,100) - spyOn(rect, 'transform') - }) - - afterEach(function() { - rect.remove() - rect.transform.calls.reset() - }) - - it('redirects to transform()', function() { - rect.rotate(1,2,3) - expect(rect.transform).toHaveBeenCalledWith({ rotation: 1, cx: 2, cy: 3 }) - }) - }) - - describe('skew()', function() { - var rect, spy, undefined - - beforeEach(function() { - rect = draw.rect(100,100) - spyOn(rect, 'transform') - }) - - afterEach(function() { - rect.remove() - rect.transform.calls.reset() - }) - - it('redirects to transform() with no argument', function() { - rect.skew() - expect(rect.transform).toHaveBeenCalledWith({ skewX: undefined, skewY: undefined, cx: undefined, cy: undefined }) - }) - - it('redirects to transform() with one argument', function() { - rect.skew(5) - expect(rect.transform).toHaveBeenCalledWith({ skew: 5, cx: undefined, cy: undefined }) - }) - - it('redirects to transform() with two argument', function() { - rect.skew(5, 6) - expect(rect.transform).toHaveBeenCalledWith({ skewX: 5, skewY: 6, cx: undefined, cy: undefined }) - }) - - it('redirects to transform() with three arguments', function() { - rect.skew(5, 6, 7) - expect(rect.transform).toHaveBeenCalledWith({ skew: 5, cx: 6, cy: 7 }) - }) - - it('redirects to transform() with four arguments', function() { - rect.skew(5, 6, 7, 8) - expect(rect.transform).toHaveBeenCalledWith({ skewX: 5, skewY: 6, cx: 7, cy: 8 }) - }) - }) - - describe('scale()', function() { - var rect, spy, undefined - - beforeEach(function() { - rect = draw.rect(100,100) - spyOn(rect, 'transform') - }) - - afterEach(function() { - rect.remove() - rect.transform.calls.reset() - }) - - it('redirects to transform() with no argument', function() { - rect.scale() - expect(rect.transform).toHaveBeenCalledWith({ scaleX: undefined, scaleY: undefined, cx: undefined, cy: undefined }) - }) - - it('redirects to transform() with one argument', function() { - rect.scale(5) - expect(rect.transform).toHaveBeenCalledWith({ scale: 5, cx: undefined, cy: undefined }) - }) - - it('redirects to transform() with two argument', function() { - rect.scale(5, 6) - expect(rect.transform).toHaveBeenCalledWith({ scaleX: 5, scaleY: 6, cx: undefined, cy: undefined }) - }) - - it('redirects to transform() with three arguments', function() { - rect.scale(5, 6, 7) - expect(rect.transform).toHaveBeenCalledWith({ scale: 5, cx: 6, cy: 7 }) - }) - - it('redirects to transform() with four arguments', function() { - rect.scale(5, 6, 7, 8) - expect(rect.transform).toHaveBeenCalledWith({ scaleX: 5, scaleY: 6, cx: 7, cy: 8 }) - }) - }) - - describe('translate()', function() { - var rect, spy, undefined - - beforeEach(function() { - rect = draw.rect(100,100) - spyOn(rect, 'transform') - }) - - afterEach(function() { - rect.remove() - rect.transform.calls.reset() - }) - - it('redirects to transform()', function() { - rect.translate(1,2) - expect(rect.transform).toHaveBeenCalledWith({ x: 1, y: 2 }) - }) - }) - - describe('flip()', function() { - var rect, spy, undefined - - beforeEach(function() { - rect = draw.rect(100,100) - spyOn(rect, 'transform') - }) - - afterEach(function() { - rect.remove() - rect.transform.calls.reset() - }) - - it('redirects to transform()', function() { - rect.flip('x',2) - expect(rect.transform).toHaveBeenCalledWith({ flip: 'x', offset: 2 }) - }) - - it('sets flip to "both" when calling without anything', function() { - rect.flip() - expect(rect.transform).toHaveBeenCalledWith({ flip: 'both', offset: undefined }) - }) - - // this works because only x and y are valid flip values. Evereything else flips on both axis - it('sets flip to number and offset to number when called with offset only', function() { - rect.flip(5) - expect(rect.transform).toHaveBeenCalledWith({ flip: 5, offset: 5 }) - }) - }) - - describe('matrix()', function() { - var rect, spy, undefined - - beforeEach(function() { - rect = draw.rect(100,100) - spyOn(rect, 'attr') - }) - - afterEach(function() { - rect.remove() - rect.attr.calls.reset() - }) - - it('redirects to attr() directly with one argument', function() { - rect.matrix([1,2,3,4,5,6]) - expect(rect.attr).toHaveBeenCalledWith('transform', new SVG.Matrix([1,2,3,4,5,6])) - }) - - it('redirects to attr() directly with 6 arguments', function() { - rect.matrix(1,2,3,4,5,6) - expect(rect.attr).toHaveBeenCalledWith('transform', new SVG.Matrix([1,2,3,4,5,6])) - }) - }) - - describe('opacity()', function() { - var rect, spy, undefined - - beforeEach(function() { - rect = draw.rect(100,100) - spyOn(rect, 'attr') - }) - - afterEach(function() { - rect.remove() - rect.attr.calls.reset() - }) - - it('redirects to attr() directly', function() { - rect.opacity(0.5) - expect(rect.attr).toHaveBeenCalledWith('opacity', 0.5) - }) - }) - - describe('dx() / dy()', function() { - var rect, spy, undefined - - beforeEach(function() { - rect = draw.rect(100,100) - spyOn(rect, 'x').and.callThrough() - spyOn(rect, 'y').and.callThrough() - }) - - afterEach(function() { - rect.remove() - rect.x.calls.reset() - rect.y.calls.reset() - }) - - it('redirects to x() / y() with adding the current value', function() { - rect.dx(5) - rect.dy(5) - expect(rect.x).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.Number('5')), true) - expect(rect.y).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.Number('5')), true) - }) - - it('allows to add a percentage value', function() { - rect.move('5%', '5%') - - rect.dx('5%') - rect.dy('5%') - - expect(rect.x).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.Number('10%')), true) - expect(rect.y).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.Number('10%')), true) - }) - - it('allows to add a percentage value when no x/y is set', function() { - rect.dx('5%') - rect.dy('5%') - - expect(rect.x).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.Number('5%')), true) - expect(rect.y).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.Number('5%')), true) - }) - }) - - describe('dmove()', function() { - var rect, spy, undefined - - beforeEach(function() { - rect = draw.rect(100,100) - spyOn(rect, 'dx').and.callThrough() - spyOn(rect, 'dy').and.callThrough() - }) - - afterEach(function() { - rect.remove() - rect.dx.calls.reset() - rect.dy.calls.reset() - }) - - it('redirects to dx() / dy() directly', function() { - rect.dmove(5,5) - expect(rect.dx).toHaveBeenCalledWith(5) - expect(rect.dy).toHaveBeenCalledWith(5) - }) - }) - - describe('font()', function() { - var text, spy, undefined - - beforeEach(function() { - text = draw.text(loremIpsum) - spyOn(text, 'leading') - spyOn(text, 'attr') - }) - - afterEach(function() { - text.remove() - text.leading.calls.reset() - text.attr.calls.reset() - }) - - it('sets leading when given', function() { - text.font({leading: 3}) - expect(text.leading).toHaveBeenCalledWith(3) - }) - - it('sets text-anchor when anchor given', function() { - text.font({anchor: 'start'}) - expect(text.attr).toHaveBeenCalledWith('text-anchor', 'start') - }) - - it('sets all font properties via attr()', function() { - text.font({ - size: 20, - family: 'Verdana', - weight: 'bold', - stretch: 'wider', - variant: 'small-caps', - style: 'italic' - }) - expect(text.attr).toHaveBeenCalledWith('font-size', 20) - expect(text.attr).toHaveBeenCalledWith('font-family', 'Verdana') - expect(text.attr).toHaveBeenCalledWith('font-weight', 'bold') - expect(text.attr).toHaveBeenCalledWith('font-stretch', 'wider') - expect(text.attr).toHaveBeenCalledWith('font-variant', 'small-caps') - expect(text.attr).toHaveBeenCalledWith('font-style', 'italic') - }) - - it('redirects all other stuff directly to attr()', function() { - text.font({ - foo:'bar', - bar:'baz' - }) - expect(text.attr).toHaveBeenCalledWith('foo', 'bar') - expect(text.attr).toHaveBeenCalledWith('bar', 'baz') - }) - - it('sets key value pair when called with 2 parameters', function() { - text.font('size', 20) - expect(text.attr).toHaveBeenCalledWith('font-size', 20) - }) - - it('gets value if called with one parameter', function() { - text.font('size') - expect(text.attr).toHaveBeenCalledWith('font-size', undefined) - }) - }) - -}) diff --git a/spec/spec/svg.js b/spec/spec/svg.js deleted file mode 100644 index 7738b0e58..000000000 --- a/spec/spec/svg.js +++ /dev/null @@ -1,115 +0,0 @@ -describe('SVG', function() { - - describe('()', function() { - var drawing, wrapper - - beforeEach(function() { - wrapper = document.createElement('svg') - document.documentElement.appendChild(wrapper) - drawing = SVG(wrapper) - }) - - afterEach(function() { - wrapper.parentNode.removeChild(wrapper) - }) - - it('creates a new svg drawing', function() { - expect(drawing.type).toBe('svg') - }) - it('creates an instance of SVG.Doc', function() { - expect(drawing instanceof SVG.Doc).toBe(true) - }) - - if(parserInDoc){ - it('sets no default size in svg documents', function() { - expect(drawing.width()).toBe(0) - expect(drawing.height()).toBe(0) - }) - }else{ - it('sets size to 100% in html documents', function() { - expect(drawing.width()).toBe('100%') - expect(drawing.height()).toBe('100%') - }) - } - }) - - describe('create()', function() { - it('creates an element with given node name and return it', function() { - var element = SVG.create('rect') - - expect(element.nodeName).toBe('rect') - }) - it('increases the global id sequence', function() { - var did = SVG.did - , element = SVG.create('rect') - - expect(did + 1).toBe(SVG.did) - }) - it('adds a unique id containing the node name', function() { - var did = SVG.did - , element = SVG.create('rect') - - expect(element.getAttribute('id')).toBe('SvgjsRect' + did) - }) - }) - - describe('extend()', function() { - it('adds all functions in the given object to the target object', function() { - SVG.extend(SVG.Rect, { - soft: function() { - return this.opacity(0.2) - } - }) - - expect(typeof SVG.Rect.prototype.soft).toBe('function') - expect(draw.rect(100,100).soft().attr('opacity')).toBe(0.2) - }) - it('accepts and extend multiple modules at once', function() { - SVG.extend(SVG.Rect, SVG.Ellipse, SVG.Path, { - soft: function() { - return this.opacity(0.5) - } - }) - - expect(typeof SVG.Rect.prototype.soft).toBe('function') - expect(draw.rect(100,100).soft().attr('opacity')).toBe(0.5) - expect(typeof SVG.Ellipse.prototype.soft).toBe('function') - expect(draw.ellipse(100,100).soft().attr('opacity')).toBe(0.5) - expect(typeof SVG.Path.prototype.soft).toBe('function') - expect(draw.path().soft().attr('opacity')).toBe(0.5) - }) - it('ignores non existant objects', function() { - SVG.extend(SVG.Rect, SVG.Bogus, { - soft: function() { - return this.opacity(0.3) - } - }) - - expect(typeof SVG.Rect.prototype.soft).toBe('function') - expect(draw.rect(100,100).soft().attr('opacity')).toBe(0.3) - expect(typeof SVG.Bogus).toBe('undefined') - }) - }) - - describe('prepare()', function() { - var drawing, wrapper, parser - - beforeEach(function() { - wrapper = document.createElement('svg') - document.documentElement.appendChild(wrapper) - drawing = SVG(wrapper) - }) - - it('creates a parser element when calling SVG()', function() { - expect(SVG.parser.draw.nodeName).toBe('svg') - }) - it('hides the parser', function() { - expect(window.stripped(SVG.parser.draw.getAttribute('style'))).toBe('overflow:hidden;top:-100%;left:-100%;position:absolute;opacity:0') - }) - it('holds polyline and path', function() { - expect(SVG.select('polyline', SVG.parser.draw).first().type).toBe('polyline') - expect(SVG.select('path', SVG.parser.draw).first().type).toBe('path') - }) - }) - -}) \ No newline at end of file diff --git a/spec/spec/symbol.js b/spec/spec/symbol.js deleted file mode 100644 index a4febbd5d..000000000 --- a/spec/spec/symbol.js +++ /dev/null @@ -1,16 +0,0 @@ -describe('Symbol', function() { - describe('()', function() { - var element - - beforeEach(function() { - element = draw.symbol() - }) - - it('creates an instance of SVG.Symbol', function() { - expect(element instanceof SVG.Symbol).toBeTruthy() - }) - it('is an instance of SVG.Container', function() { - expect(element instanceof SVG.Container).toBeTruthy() - }) - }) -}) \ No newline at end of file diff --git a/spec/spec/text.js b/spec/spec/text.js deleted file mode 100644 index 83b159640..000000000 --- a/spec/spec/text.js +++ /dev/null @@ -1,299 +0,0 @@ -// IMPORTANT!!! -// The native getBBox() on text elements isn't always accurate in the decimals. -// Therefore sometimes some rounding is used to make test work as expected. - -describe('Text', function() { - var text - - beforeEach(function() { - text = draw.text(loremIpsum).size(5) - }) - - afterEach(function() { - draw.clear() - }) - - describe('leading()', function() { - it('returns the leading value of the text without an argument', function() { - expect(text.leading() instanceof SVG.Number) - expect(text.leading().valueOf()).toBe(1.3) - }) - it('sets the leading value of the text with the first argument', function() { - expect(text.leading(1.5).dom.leading.valueOf()).toBe(1.5) - }) - }) - - describe('rebuild()', function() { - it('disables the rebuild if called with false', function() { - expect(text.rebuild(false)._rebuild).toBeFalsy() - }) - it('enables the rebuild if called with true', function() { - expect(text.rebuild(true)._rebuild).toBeTruthy() - }) - it('rebuilds the text without an argument given', function() { - var dy = text.lines().get(2).attr('dy') - text.leading(1.7) - expect(dy == text.lines().get(2).attr('dy')).toBeFalsy() - }) - }) - - describe('x()', function() { - it('returns the value of x without an argument', function() { - expect(text.x()).toBe(0) - }) - it('sets the value of x with the first argument', function() { - text.x(123) - expect(text.node.getAttribute('x')).toBeCloseTo(123) - }) - it('sets the value of all lines', function() { - text.x(200) - text.lines().each(function() { - expect(this.x()).toBe(200) - }) - }) - it('sets the value of y with a percent value', function() { - text.x('40%') - expect(text.node.getAttribute('x')).toBe('40%') - }) - it('returns the value of x when x is a percentual value', function() { - expect(text.x('45%').x()).toBe('45%') - }) - // Woow this test is old. The functionality not even implemented anymore - // Was a good feature. Maybe we add it back? - /*it('sets the value of x based on the anchor with the first argument', function() { - text.x(123, true) - var box = text.bbox() - expect(box.x).toBeCloseTo(123) - })*/ - }) - - describe('y()', function() { - it('returns the value of y without an argument', function() { - expect(text.y(0).y()).toBeCloseTo(0) - }) - it('returns the value of y when y is a percentual value', function() { - expect(text.y('45%').y()).toBe('45%') - }) - it('sets the value of y with the first argument', function() { - text.y(345) - var box = text.bbox() - expect(box.y).toBe(345) - }) - it('sets the value of y with a percent value', function() { - text.y('40%') - expect(text.node.getAttribute('y')).toBe('40%') - }) - }) - - describe('cx()', function() { - it('returns the value of cx without an argument', function() { - var box = text.bbox() - expect(text.cx()).toBeCloseTo(box.x + box.width / 2) - }) - it('sets the value of cx with the first argument', function() { - text.cx(123) - var box = text.bbox() - // this is a hack. it should be exactly 123 since you set it. But bbox with text is a thing... - expect(box.cx).toBeCloseTo(box.x + box.width/2) - }) - // not implemented anymore - /*it('sets the value of cx based on the anchor with the first argument', function() { - text.cx(123, true) - var box = text.bbox() - expect(box.cx).toBeCloseTo(123) - })*/ - }) - - describe('cy()', function() { - it('returns the value of cy without an argument', function() { - var box = text.bbox() - expect(text.cy()).toBe(box.cy) - }) - it('sets the value of cy with the first argument', function() { - text.cy(345) - var box = text.bbox() - expect(Math.round(box.cy * 10) / 10).toBe(345) - }) - }) - - describe('move()', function() { - it('sets the x and y position', function() { - text.move(123,456) - expect(text.node.getAttribute('x')).toBe('123') - expect(text.y()).toBeCloseTo(456) - }) - }) - - describe('center()', function() { - it('sets the cx and cy position', function() { - text.center(321, 567) - var box = text.bbox() - expect(+text.node.getAttribute('x') + box.width / 2).toBeCloseTo(321, 1) - expect(text.y() + box.height / 2).toBeCloseTo(567) - }) - }) - - describe('size()', function() { - it('should define the width and height of the element', function() { - text.size(50) - expect(text.attr('font-size').valueOf()).toBe(50) - }) - }) - - describe('translate()', function() { - it('sets the translation of an element', function() { - text.transform({ x: 12, y: 12 }) - expect(text.node.getAttribute('transform')).toBe('matrix(1,0,0,1,12,12)') - }) - }) - - describe('text()', function() { - it('adds content in a nested tspan', function() { - text.text('It is a bear!') - expect(text.node.childNodes[0].nodeType).toBe(1) - expect(text.node.childNodes[0].childNodes[0].data).toBe('It is a bear!') - }) - it('adds content in a nested tspan even with an empty string', function() { - text.text('') - expect(text.node.childNodes[0].nodeType).toBe(1) - expect(text.node.childNodes[0].childNodes[0].data).toBe('') - }) - it('creates multiple lines with a newline separated string', function() { - text.text('It is\nJUST\na bear!') - expect(text.node.childNodes.length).toBe(3) - }) - it('restores the content from the dom', function() { - text.text('It is\nJUST\na bear!') - expect(text.text()).toBe('It is\nJUST\na bear!') - }) - it('gets the given content of a text element without an argument', function() { - text.text('It is another bear!') - expect(text.node.childNodes[0].nodeType).toBe(1) - expect(text.text()).toMatch('It is another bear!') - }) - it('accepts a block as first arguments', function() { - text.text(function(add) { - add.tspan('mastaba') - add.plain('hut') - }) - expect(text.node.childNodes[0].nodeType).toBe(1) - expect(text.node.childNodes[0].childNodes[0].data).toBe('mastaba') - expect(text.node.childNodes[1].nodeType).toBe(3) - expect(text.node.childNodes[1].data).toBe('hut') - }) - }) - - describe('plain()', function() { - it('adds content without a tspan', function() { - text.plain('It is a bear!') - expect(text.node.childNodes[0].nodeType).toBe(3) - expect(text.node.childNodes[0].data).toBe('It is a bear!') - }) - it('clears content before adding new content', function() { - text.plain('It is not a bear!') - expect(text.node.childNodes.length).toBe(1) - expect(text.node.childNodes[0].data).toBe('It is not a bear!') - }) - it('restores the content from the dom', function() { - text.plain('Just plain text!') - expect(text.text()).toBe('Just plain text!') - }) - }) - - describe('tspan()', function() { - it('adds content in a tspan', function() { - text.tspan('It is a bear!') - expect(text.node.childNodes[0].nodeType).toBe(1) - expect(text.node.childNodes[0].childNodes[0].data).toBe('It is a bear!') - }) - it('clears content before adding new content', function() { - text.tspan('It is not a bear!') - expect(text.node.childNodes.length).toBe(1) - expect(text.node.childNodes[0].childNodes[0].data).toBe('It is not a bear!') - }) - }) - - describe('clear()', function() { - it('removes all content', function() { - text.text(function(add) { - add.tspan('The first.') - add.tspan('The second.') - add.tspan('The third.') - }) - expect(text.node.childNodes.length).toBe(3) - text.clear() - expect(text.node.childNodes.length).toBe(0) - }) - }) - - describe('lines()', function() { - it('gets an array of individual lines as an instance of SVG.Set', function() { - var l1, l2, l3 - text.text(function(add) { - l1 = add.tspan('The first.') - l2 = add.tspan('The second.') - l3 = add.tspan('The third.') - }) - expect(text.lines().length()).toBe(3) - expect(text.lines().get(0)).toBe(l1) - expect(text.lines().get(1)).toBe(l2) - expect(text.lines().get(2)).toBe(l3) - }) - }) - - describe('length()', function() { - it('gets total length of text', function() { - text.text(function(add) { - add.tspan('The first.') - add.tspan('The second.') - add.tspan('The third.') - }) - expect(text.length()).toBeCloseTo(text.lines().get(0).length() + text.lines().get(1).length() + text.lines().get(2).length(), 3) - }) - }) - - describe('build()', function() { - it('enables adding multiple plain text nodes when given true', function() { - text.clear().build(true) - text.plain('A great piece!') - text.plain('Another great piece!') - expect(text.node.childNodes[0].data).toBe('A great piece!') - expect(text.node.childNodes[1].data).toBe('Another great piece!') - }) - it('enables adding multiple tspan nodes when given true', function() { - text.clear().build(true) - text.tspan('A great piece!') - text.tspan('Another great piece!') - expect(text.node.childNodes[0].childNodes[0].data).toBe('A great piece!') - expect(text.node.childNodes[1].childNodes[0].data).toBe('Another great piece!') - }) - it('disables adding multiple plain text nodes when given false', function() { - text.clear().build(true) - text.plain('A great piece!') - text.build(false).plain('Another great piece!') - expect(text.node.childNodes[0].data).toBe('Another great piece!') - expect(text.node.childNodes[1]).toBe(undefined) - }) - it('disables adding multiple tspan nodes when given false', function() { - text.clear().build(true) - text.tspan('A great piece!') - text.build(false).tspan('Another great piece!') - expect(text.node.childNodes[0].childNodes[0].data).toBe('Another great piece!') - expect(text.node.childNodes[1]).toBe(undefined) - }) - }) - - describe('setData()', function() { - it('read all data from the svgjs:data attribute and assign it to el.dom', function(){ - - text.attr('svgjs:data', '{"foo":"bar","leading":"3px"}') - text.setData(JSON.parse(text.attr('svgjs:data'))) - - expect(text.dom.foo).toBe('bar') - expect(text.dom.leading instanceof SVG.Number).toBeTruthy() - expect(text.dom.leading.value).toBe(3) - expect(text.dom.leading.unit).toBe('px') - }) - }) - -}) diff --git a/spec/spec/textpath.js b/spec/spec/textpath.js deleted file mode 100644 index ee046ea9b..000000000 --- a/spec/spec/textpath.js +++ /dev/null @@ -1,62 +0,0 @@ -describe('TextPath', function() { - var text - , data = 'M 100 200 C 200 100 300 0 400 100 C 500 200 600 300 700 200 C 800 100 900 100 900 100' - - beforeEach(function() { - text = draw.text('We go up, then we go down, then up again') - }) - - afterEach(function() { - draw.clear() - }) - - describe('path()', function() { - it('returns the text element', function() { - expect(text.path(data)).toBe(text) - }) - it('creates a textPath node in the text element', function() { - text.path(data) - expect(text.node.firstChild.nodeName).toBe('textPath') - }) - }) - - describe('textPath()', function() { - it('creates a reference to the textPath', function() { - expect(text.path(data).textPath() instanceof SVG.TextPath).toBe(true) - }) - }) - - describe('track()', function() { - it('creates a reference to the path', function() { - expect(text.path(data).track() instanceof SVG.Path).toBe(true) - }) - }) - - describe('array()', function() { - it('return the path array of the underlying path', function() { - expect(text.path(data).array()).toEqual(new SVG.PathArray(data)) - }) - it('return null if there is no underlying path', function () { - expect(text.array()).toBe(null) - }) - }) - - describe('plot()', function() { - it('change the array of the underlying path when a string is passed', function() { - expect(text.path().plot(data)).toBe(text) - expect(text.array()).toEqual(new SVG.PathArray(data)) - }) - it('do nothing when a string is passed and there is no underlying path', function() { - expect(text.plot(data)).toBe(text) - expect(text.array()).toEqual(null) - }) - it('return the path array of the underlying path when no arguments is passed', function () { - text.path(data) - expect(text.plot()).toBe(text.array()) - expect(text.plot()).not.toBe(null) - }) - it('return null when no arguments is passed and there is no underlying path', function () { - expect(text.plot()).toBe(null) - }) - }) -}) diff --git a/spec/spec/transformations.js b/spec/spec/transformations.js deleted file mode 100644 index 3399981b8..000000000 --- a/spec/spec/transformations.js +++ /dev/null @@ -1,282 +0,0 @@ -describe('Transformations:', function() { - var translated, scaled, rotated, skewed - - beforeEach(function() { - translated = draw.rect(100,100).translate(100,100) - scaled = draw.rect(100,100).scale(2) - rotated = draw.rect(100,100).rotate(45, 50, 50) - skewed = draw.rect(100,100).skew(30) - }) - - /* SVG.Transformation is not tested because it is an abstract prototype */ - - describe('SVG.Transformation', function() { - it('marks the transformation as inversed when inverse flag given', function() { - var trans = new SVG.Transformation([], true) - expect(trans.inversed).toBeTruthy() - }) - }) - - describe('SVG.Translate', function() { - var trans - - beforeEach(function(){ - trans = new SVG.Translate(translated.transform()) - }) - - - it('creates an object of type SVG.Transformation', function() { - expect(trans instanceof SVG.Transformation).toBeTruthy() - }) - - it('uses transformedX and transformedY as arguments', function() { - expect(trans.arguments).toEqual(['transformedX', 'transformedY']) - }) - - it('s method is translate()', function() { - expect(trans.method).toEqual('translate') - }) - - it('sets the necessary parameters at creation', function() { - expect(trans.transformedX).toBe(100) - expect(trans.transformedY).toBe(100) - }) - - describe('undo', function() { - it('sets the undo matrix which can undo the translation', function() { - var extracted = (new SVG.Matrix(1,0,0,1,20,20)).extract() - trans.undo(extracted) - expect(trans._undo.toString()).toEqual('matrix(1,0,0,1,-20,-20)') - - var extracted = (new SVG.Matrix(10,0,0,10,20,20)).extract() - trans.undo(extracted) - expect(trans._undo.toString()).toEqual('matrix(1,0,0,1,-2,-2)') - - var extracted = (new SVG.Matrix(10,50,50,30,20,20)).extract() - trans.undo(extracted) - expect(trans._undo.e).toBeCloseTo(-extracted.transformedX) - expect(trans._undo.f).toBeCloseTo(-extracted.transformedY) - }) - }) - - describe('at', function() { - it('creates a matrix at a certain position', function() { - expect(trans.at(0.3).toString()).toEqual('matrix(1,0,0,1,30,30)') - }) - it('returns the inversed matrix from a specific position when created with inverse flag', function() { - expect((new SVG.Translate(translated.transform(), true)).at(0.3).toString()).toEqual('matrix(1,0,0,1,-30,-30)') - }) - it('returns the resulting transformation which has to be made to set an absolute translation', function() { - trans.undo(new SVG.Matrix(10,50,50,30,20,20).extract()) - - expect(trans.at(0.4).a).toEqual(1) - expect(trans.at(0.4).b).toEqual(0) - expect(trans.at(0.4).c).toEqual(0) - expect(trans.at(0.4).d).toEqual(1) - expect(trans.at(0.4).e).toBeCloseTo(100 * 0.4 + trans._undo.e * 0.4) - expect(trans.at(0.4).f).toBeCloseTo(100 * 0.4 + trans._undo.f * 0.4) - }) - }) - }) - - describe('SVG.Rotate', function() { - var trans - - beforeEach(function(){ - trans = new SVG.Rotate(45, 50, 50) - }) - - - it('creates an object of type SVG.Transformation', function() { - expect(trans instanceof SVG.Transformation).toBeTruthy() - }) - - it('uses rotation, cx and cy as arguments', function() { - expect(trans.arguments).toEqual(['rotation', 'cx', 'cy']) - }) - - it('s method is rotate()', function() { - expect(trans.method).toEqual('rotate') - }) - - it('sets the necessary parameters at creation', function() { - expect(trans.rotation).toBe(45) - expect(trans.cx).toBe(50) - expect(trans.cy).toBe(50) - }) - - describe('undo', function() { - it('sets an undo object which holds rotation', function() { - var extracted = (new SVG.Matrix(1,0,0,1,0,0)).rotate(20, 50, 50).extract() - trans.undo(extracted) - expect(trans._undo.rotation).toBeCloseTo(20) - }) - }) - - describe('at', function() { - it('creates a matrix at a certain position', function() { - expect(trans.at(0.3).toString()).toEqual((new SVG.Matrix()).rotate(0.3 * 45, 50, 50).toString()) - }) - it('returns the resulting transformation which has to be made to set an absolute translation', function() { - trans.undo((new SVG.Matrix()).rotate(20, 50, 50).extract()) - - expect(trans.at(0.4).a).toBeCloseTo(1,1) - expect(trans.at(0.4).b).toEqual(jasmine.any(Number)) - expect(trans.at(0.4).c).toEqual(jasmine.any(Number)) - expect(trans.at(0.4).d).toBeCloseTo(1,1) - expect(trans.at(0.4).e).toEqual(jasmine.any(Number)) - expect(trans.at(0.4).f).toEqual(jasmine.any(Number)) - }) - }) - }) - - - describe('SVG.Scale', function() { - var trans - - beforeEach(function(){ - trans = new SVG.Scale(2,2,50,50) - }) - - - it('creates an object of type SVG.Transformation', function() { - expect(trans instanceof SVG.Transformation).toBeTruthy() - }) - - it('uses scaleX, scaleY, cx and cy as arguments', function() { - expect(trans.arguments).toEqual(['scaleX', 'scaleY', 'cx', 'cy']) - }) - - it('s method is scale()', function() { - expect(trans.method).toEqual('scale') - }) - - it('sets the necessary parameters at creation', function() { - expect(trans.scaleX).toBe(2) - expect(trans.scaleY).toBe(2) - expect(trans.cx).toBe(50) - expect(trans.cy).toBe(50) - }) - - describe('undo', function() { - it('sets the undo matrix which can undo the translation', function() { - var extracted = (new SVG.Matrix(4,0,0,4,0,0)).extract() - trans.undo(extracted) - expect(trans._undo.toString()).toEqual('matrix(0.25,0,0,0.25,37.5,37.5)') - - var extracted = (new SVG.Matrix(10,0,0,10,20,20)).extract() - trans.undo(extracted) - expect(trans._undo.a).toBeCloseTo(1/extracted.scaleX) - expect(trans._undo.d).toBeCloseTo(1/extracted.scaleY) - expect(trans._undo.e).toBeCloseTo(45) - expect(trans._undo.f).toBeCloseTo(45) - - var extracted = (new SVG.Matrix(10,50,50,30,20,20)).extract() - trans.undo(extracted) - expect(trans._undo.a).toBeCloseTo(1/extracted.scaleX) - expect(trans._undo.d).toBeCloseTo(1/extracted.scaleY) - }) - }) - - describe('at', function() { - it('creates a matrix at a certain position', function() { - expect(trans.at(0.75).toString()).toEqual('matrix(1.75,0,0,1.75,-37.5,-37.5)') - }) - it('returns the inversed matrix from a specific position when created with inverse flag', function() { - var morphed = (new SVG.Scale(scaled.transform(2,2,50,50), true)).at(0.25) - - expect(morphed.a).toBeCloseTo(0.8) - expect(morphed.d).toBeCloseTo(0.8) - }) - it('returns the resulting transformation which has to be made to set an absolute translation', function() { - - var morphed = trans.undo((new SVG.Matrix(10,0,0,10,0,0)).extract()).at(0.5) - - expect(morphed.a).toBeCloseTo(0.6) - expect(morphed.b).toEqual(0) - expect(morphed.c).toEqual(0) - expect(morphed.d).toBeCloseTo(0.6) - expect(morphed.e).toBeCloseTo(20) - expect(morphed.f).toBeCloseTo(20) - }) - }) - }) - - describe('SVG.Skew', function() { - var trans - - beforeEach(function(){ - trans = new SVG.Skew(30,-30,50,50) - }) - - - it('creates an object of type SVG.Transformation', function() { - expect(trans instanceof SVG.Transformation).toBeTruthy() - }) - - it('uses scaleX, scaleY, cx and cy as arguments', function() { - expect(trans.arguments).toEqual(['skewX', 'skewY', 'cx', 'cy']) - }) - - it('s method is skew()', function() { - expect(trans.method).toEqual('skew') - }) - - it('sets the necessary parameters at creation', function() { - expect(trans.skewX).toBe(30) - expect(trans.skewY).toBe(-30) - expect(trans.cx).toBe(50) - expect(trans.cy).toBe(50) - }) - - describe('undo', function() { - it('sets the undo matrix which can undo the translation', function() { - var extracted = (new SVG.Matrix()).skew(90, 90, 0, 0).extract() - trans.undo(extracted) - expect(trans._undo.a).toBeCloseTo(0) - expect(trans._undo.b).toBeCloseTo(0) - expect(trans._undo.c).toBeCloseTo(0) - expect(trans._undo.d).toBeCloseTo(0) - expect(trans._undo.e).toBeCloseTo(50) - expect(trans._undo.f).toBeCloseTo(50) - - var extracted = (new SVG.Matrix(10,0,0,10,20,20)).extract() - trans.undo(extracted) - expect(trans._undo.a).toBeCloseTo(1) - expect(trans._undo.b).toBeCloseTo(0) - expect(trans._undo.c).toBeCloseTo(0) - expect(trans._undo.d).toBeCloseTo(1) - expect(trans._undo.e).toBeCloseTo(0) - expect(trans._undo.f).toBeCloseTo(0) - }) - }) - - describe('at', function() { - it('creates a matrix at a certain position', function() { - expect(trans.at(0.75)).toEqual((new SVG.Matrix()).morph((new SVG.Matrix()).skew(30, -30, 50, 50)).at(0.75)) - }) - it('returns the inversed matrix from a specific position when created with inverse flag', function() { - var morphed = (new SVG.Scale(skewed.transform(20,-20,50,50), true)).at(0.25) - - expect(morphed.a).toBeCloseTo(0.963) - expect(morphed.b).toBeCloseTo(0) - expect(morphed.c).toBeCloseTo(0) - expect(morphed.d).toBeCloseTo(0.963) - expect(morphed.e).toBeCloseTo(0) - expect(morphed.f).toBeCloseTo(0) - }) - it('returns the resulting transformation which has to be made to set an absolute translation', function() { - - var morphed = trans.undo((new SVG.Matrix(10,0,0,10,0,0)).skew(20, 30, 20, 10).extract()).at(0.5) - - expect(morphed.a).toBeCloseTo(1.266) - expect(morphed.b).toBeCloseTo(-0.7310) - expect(morphed.c).toBeCloseTo(0.1351) - expect(morphed.d).toBeCloseTo(0.9220) - expect(morphed.e).toBeCloseTo(-20.05593) - expect(morphed.f).toBeCloseTo(40.4468) - }) - }) - }) - -}) diff --git a/spec/spec/tspan.js b/spec/spec/tspan.js deleted file mode 100644 index d8ac4b1d7..000000000 --- a/spec/spec/tspan.js +++ /dev/null @@ -1,46 +0,0 @@ -describe('Tspan', function() { - var text, tspan - - beforeEach(function() { - text = draw.text(loremIpsum) - tspan = text.tspan('Hello World') - }) - - afterEach(function() { - draw.clear() - }) - - describe('newLine()', function() { - it('converts the tspan to a line', function() { - tspan = text.tspan('Hello World') - expect(tspan.newLine().dom.newLined).toBeTruthy() - }) - }) - - describe('text()', function() { - it('returns the text of the tspan without newline when not newlined', function() { - tspan = text.tspan('Hello World') - expect(tspan.text()).toBe('Hello World') - }) - it('returns the text of the tspan with newline when newlined', function() { - tspan = text.tspan('Hello World').newLine() - expect(tspan.text()).toBe('Hello World\n') - }) - it('calls the function when function given', function() { - var spy = jasmine.createSpy('dummy') - tspan = text.tspan('Hello World') - tspan.text(spy) - expect(spy).toHaveBeenCalledWith(tspan) - }) - }) - - describe('dx()', function() { - it('gets the dx value with no argument', function() { - tspan.attr('dx', 25) - expect(tspan.dx()).toBe(25) - }) - it('sets the dx value whith the first argument', function() { - expect(tspan.dx(25).attr('dx')).toBe(25) - }) - }) -}) diff --git a/spec/spec/types/Base.js b/spec/spec/types/Base.js new file mode 100644 index 000000000..9a5cf162b --- /dev/null +++ b/spec/spec/types/Base.js @@ -0,0 +1,11 @@ +/* globals describe, expect, it, jasmine */ + +import Base from '../../../src/types/Base.js' + +const { any } = jasmine + +describe('Base.js', () => { + it('creates a new object of type Base', () => { + expect(new Base()).toEqual(any(Base)) + }) +}) diff --git a/spec/spec/types/Box.js b/spec/spec/types/Box.js new file mode 100644 index 000000000..e0f13e110 --- /dev/null +++ b/spec/spec/types/Box.js @@ -0,0 +1,392 @@ +/* globals describe, expect, it, beforeEach, afterEach, spyOn, jasmine, container */ + +import { Box, Matrix, Rect, G, makeInstance as SVG } from '../../../src/main.js' +import { withWindow, getWindow } from '../../../src/utils/window.js' +import { isNulledBox, domContains } from '../../../src/types/Box.js' + +const { any, objectContaining } = jasmine + +// const getBody = () => { +// const win = getWindow() +// return win.document.body || win.document.documentElement +// } + +describe('Box.js', () => { + describe('isNulledBox', () => { + it('returns true if x, y, with and height is 0', () => { + expect(isNulledBox({ x: 0, y: 0, width: 0, height: 0 })).toBe(true) + }) + + it('returns false if one or more of x, y, with and height is not 0', () => { + expect(isNulledBox({ x: 0, y: 0, width: 0, height: 1 })).toBe(false) + expect(isNulledBox({ x: 0, y: 1, width: 0, height: 1 })).toBe(false) + }) + }) + + describe('domContains()', () => { + describe('with native function', () => { + it('returns true if node is in the dom', () => { + expect(domContains(container)).toBe(true) + }) + + it('returns false if node is not in the dom', () => { + const g = new G() + const rect = new Rect().addTo(g) + expect(domContains(rect.node)).toBe(false) + }) + }) + + describe('with polyfill', () => { + let containsBackup + beforeEach(() => { + containsBackup = getWindow().document.documentElement.contains + getWindow().document.documentElement.contains = null + }) + + afterEach(() => { + getWindow().document.documentElement.contains = containsBackup + }) + + it('returns true if node is in the dom', () => { + expect(domContains(container)).toBe(true) + }) + + it('returns false if node is not in the dom', () => { + const g = new G() + const rect = new Rect().addTo(g) + expect(domContains(rect.node)).toBe(false) + }) + }) + }) + + describe('Box', () => { + describe('()', () => { + it('creates a new Box with default attributes', () => { + const box = new Box() + expect(box).toEqual(any(Box)) + expect(box).toEqual( + objectContaining({ + width: 0, + height: 0, + x: 0, + y: 0, + w: 0, + h: 0, + cx: 0, + cy: 0, + x2: 0, + y2: 0 + }) + ) + }) + }) + + describe('init()', () => { + it('inits or reinits the box according to input', () => { + expect(new Box().init(1, 2, 3, 4).toArray()).toEqual([1, 2, 3, 4]) + }) + + it('works with array input', () => { + expect(new Box().init([1, 2, 3, 4]).toArray()).toEqual([1, 2, 3, 4]) + }) + + it('works with 3 arguments as input', () => { + expect(new Box().init(1, 2, 3, 4).toArray()).toEqual([1, 2, 3, 4]) + }) + + it('works with string input', () => { + expect(new Box().init('1,2,3,4').toArray()).toEqual([1, 2, 3, 4]) + }) + + it('creates a new box from parsed string with exponential values', function () { + expect(new Box().init('-1.12e1 1e-2 +2e2 +.3e+4').toArray()).toEqual([ + -11.2, 0.01, 200, 3000 + ]) + }) + + it('works with object input', () => { + expect( + new Box().init({ x: 1, y: 2, width: 3, height: 4 }).toArray() + ).toEqual([1, 2, 3, 4]) + }) + + it('calculates all derived values correctly', () => { + expect(new Box().init(2, 4, 6, 8)).toEqual( + objectContaining({ + cx: 5, + cy: 8, + x2: 8, + y2: 12, + w: 6, + h: 8 + }) + ) + }) + + it('can handle input with left instead of x and top instead of y', () => { + expect( + new Box().init({ left: 1, top: 2, width: 3, height: 4 }).toArray() + ).toEqual([1, 2, 3, 4]) + }) + }) + + describe('merge()', () => { + it('merges various bounding boxes', () => { + var box1 = new Box(50, 50, 100, 100) + var box2 = new Box(300, 400, 100, 100) + var box3 = new Box(500, 100, 100, 100) + var merged = box1.merge(box2).merge(box3) + + expect(merged.toArray()).toEqual([50, 50, 550, 450]) + }) + + it('returns a new instance', () => { + var box1 = new Box(50, 50, 100, 100) + var box2 = new Box(300, 400, 100, 100) + var merged = box1.merge(box2) + + expect(merged).toEqual(any(Box)) + }) + }) + + describe('transform()', () => { + it('transforms the box with given matrix', () => { + var box1 = new Box(50, 50, 100, 100).transform( + new Matrix(1, 0, 0, 1, 20, 20) + ) + var box2 = new Box(50, 50, 100, 100).transform( + new Matrix(2, 0, 0, 2, 0, 0) + ) + var box3 = new Box(-200, -200, 100, 100).transform( + new Matrix(1, 0, 0, 1, -20, -20) + ) + + expect(box1.toArray()).toEqual([70, 70, 100, 100]) + expect(box2.toArray()).toEqual([100, 100, 200, 200]) + expect(box3.toArray()).toEqual([-220, -220, 100, 100]) + }) + + it('also works with matrix like input', () => { + var box1 = new Box(50, 50, 100, 100).transform( + new Matrix(1, 0, 0, 1, 20, 20).toArray() + ) + var box2 = new Box(50, 50, 100, 100).transform( + new Matrix(2, 0, 0, 2, 0, 0).toArray() + ) + var box3 = new Box(-200, -200, 100, 100).transform( + new Matrix(1, 0, 0, 1, -20, -20).toArray() + ) + + expect(box1.toArray()).toEqual([70, 70, 100, 100]) + expect(box2.toArray()).toEqual([100, 100, 200, 200]) + expect(box3.toArray()).toEqual([-220, -220, 100, 100]) + }) + }) + + describe('addOffset()', () => { + it('returns a new instance', () => { + withWindow({ pageXOffset: 50, pageYOffset: 25 }, () => { + const box = new Box(100, 100, 100, 100) + const box2 = box.addOffset() + + expect(box2).toEqual(any(Box)) + expect(box2).not.toBe(box) + }) + }) + + it('adds the current page offset to the box', () => { + withWindow({ pageXOffset: 50, pageYOffset: 25 }, () => { + const box = new Box(100, 100, 100, 100).addOffset() + + expect(box.toArray()).toEqual([150, 125, 100, 100]) + }) + }) + }) + + describe('toString()', () => { + it('returns a string representation of the box', () => { + expect(new Box(1, 2, 3, 4).toString()).toBe('1 2 3 4') + }) + }) + + describe('toArray()', () => { + it('returns an array representation of the box', () => { + expect(new Box(1, 2, 3, 4).toArray()).toEqual([1, 2, 3, 4]) + }) + }) + + describe('isNulled()', () => { + it('checks if the box consists of only zeros', () => { + expect(new Box().isNulled()).toBe(true) + expect(new Box(1, 2, 3, 4).isNulled()).toBe(false) + }) + }) + }) + + describe('Element', () => { + describe('bbox()', () => { + it('returns the bounding box of the element', () => { + const canvas = SVG().addTo(container) + const rect = new Rect().size(100, 200).move(20, 30).addTo(canvas) + + expect(rect.bbox()).toEqual(any(Box)) + expect(rect.bbox().toArray()).toEqual([20, 30, 100, 200]) + }) + + it('returns the bounding box of the element even if the node is not in the dom', () => { + const rect = new Rect().size(100, 200).move(20, 30) + expect(rect.bbox().toArray()).toEqual([20, 30, 100, 200]) + }) + + it('throws when it is not possible to get a bbox', () => { + const spy = spyOn( + getWindow().SVGGraphicsElement.prototype, + 'getBBox' + ).and.callFake(() => { + throw new Error('No BBox for you') + }) + const rect = new Rect() + expect(() => rect.bbox()).toThrow() + expect(spy).toHaveBeenCalled() + }) + }) + + describe('rbox()', () => { + it('returns the BoundingClientRect of the element', () => { + const canvas = SVG().addTo(container) + const rect = new Rect() + .size(100, 200) + .move(20, 30) + .addTo(canvas) + .attr('transform', new Matrix({ scale: 2, translate: [40, 50] })) + + expect(rect.rbox()).toEqual(any(Box)) + expect(rect.rbox().toArray()).toEqual([80, 110, 200, 400]) + }) + + it('returns the rbox box of the element in the coordinate system of the passed element', () => { + const canvas = SVG().addTo(container) + const group = canvas.group().translate(1, 1) + const rect = new Rect() + .size(100, 200) + .move(20, 30) + .addTo(canvas) + .attr('transform', new Matrix({ scale: 2, translate: [40, 50] })) + + expect(rect.rbox(group)).toEqual(any(Box)) + expect(rect.rbox(group).toArray()).toEqual([79, 109, 200, 400]) + }) + + // svgdom actually only throws here because a new Rect without dimensions has no bounding box + // so in case you would create a rect with with and height this test would fail because + // svgdom actually can calculate an rbox for the element + // in that case we have to change the test like above so that the getBoundingClientRect call is mocked with a spy + it('throws when element is not in dom', () => { + expect(() => new Rect().rbox()).toThrow() + }) + }) + + describe('inside()', () => { + it('checks if a point is in the elements borders', () => { + const canvas = SVG().addTo(container) + const rect = canvas.rect(100, 100) + expect(rect.inside(50, 50)).toBe(true) + expect(rect.inside(101, 101)).toBe(false) + }) + }) + + describe('viewbox()', () => { + it('sets the viewbox of the element', () => { + const canvas = SVG().addTo(container).viewbox(10, 10, 200, 200) + expect(canvas.attr('viewBox')).toEqual('10 10 200 200') + }) + + it('gets the viewbox of the element', () => { + const canvas = SVG().addTo(container).viewbox(10, 10, 200, 200) + expect(canvas.viewbox()).toEqual(any(Box)) + expect(canvas.viewbox().toArray()).toEqual([10, 10, 200, 200]) + }) + }) + + describe('zoom()', () => { + it('zooms around the center by default', () => { + const canvas = SVG() + .size(100, 50) + .viewbox(0, 0, 100, 50) + .addTo(container) + .zoom(2) + expect(canvas.attr('viewBox')).toEqual('25 12.5 50 25') + }) + + it('zooms around a point', () => { + const canvas = SVG() + .size(100, 50) + .viewbox(0, 0, 100, 50) + .addTo(container) + .zoom(2, [0, 0]) + expect(canvas.attr('viewBox')).toEqual('0 0 50 25') + }) + + it('gets the zoom', () => { + // We use a nested here because its actually harder to get a width and height for a nested svg because clientHeight + // is not available + const svg = SVG() + .size(100, 50) + .addTo(container) + .nested() + .size(100, 50) + .viewbox(0, 0, 100, 50) + .zoom(2) + expect(svg.zoom()).toEqual(2) + }) + + it('gets the zoom with clientHeight', () => { + const svg = SVG() + .css({ width: '100px', height: '50px' }) + .addTo(container) + .viewbox(25, 12.5, 50, 25) + + const node = svg.node + + // svgdom doesn't support clientHeight + // so we mock it here + if (typeof node.clientHeight === 'undefined') { + node.clientHeight = 50 + node.clientWidth = 100 + } + + expect(svg.zoom()).toEqual(2) + }) + + it('throws an error if it is impossible to get an absolute value', () => { + const svg = SVG() + .size(100, 50) + .addTo(container) + .nested() + .viewbox(0, 0, 100, 50) + expect(() => svg.zoom()).toThrowError( + 'Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element' + ) + }) + + it('handles zoom level 0 which is - which basically sets the viewbox to a very high value', () => { + const svg = SVG() + .size(100, 50) + .viewbox(0, 0, 100, 50) + .addTo(container) + .zoom(0) + expect(svg.zoom()).toBeCloseTo(0, 10) + }) + + it('handles zoom level 0 and can recover from it', () => { + const svg = SVG() + .size(100, 50) + .viewbox(0, 0, 100, 50) + .addTo(container) + .zoom(0) + .zoom(1) + expect(svg.zoom()).toBe(1) + }) + }) + }) +}) diff --git a/spec/spec/types/Color.js b/spec/spec/types/Color.js new file mode 100644 index 000000000..4bbd626da --- /dev/null +++ b/spec/spec/types/Color.js @@ -0,0 +1,447 @@ +/* globals describe, expect, it, beforeEach, spyOn, jasmine */ + +import { Color } from '../../../src/main.js' + +const { any } = jasmine + +describe('Color.js', () => { + var color + + beforeEach(() => { + color = new Color({ r: 0, g: 102, b: 255 }) + }) + + describe('()', () => { + describe('constructs a color from an object in the correct color space', () => { + it('rgb', () => { + const color = new Color({ r: 255, g: 0, b: 128 }) + expect(color.r).toBe(255) + expect(color.g).toBe(0) + expect(color.b).toBe(128) + expect(color.space).toBe('rgb') + }) + + it('xyz', () => { + const color = new Color({ x: 255, y: 0, z: 128 }) + expect(color.x).toBe(255) + expect(color.y).toBe(0) + expect(color.z).toBe(128) + expect(color.space).toBe('xyz') + }) + + it('hsl', () => { + const color = new Color({ h: 255, s: 0, l: 128 }) + expect(color.h).toBe(255) + expect(color.s).toBe(0) + expect(color.l).toBe(128) + expect(color.space).toBe('hsl') + }) + + it('lab', () => { + const color = new Color({ l: 255, a: 0, b: 128 }) + expect(color.l).toBe(255) + expect(color.a).toBe(0) + expect(color.b).toBe(128) + expect(color.space).toBe('lab') + }) + + it('lch', () => { + const color = new Color({ l: 255, c: 0, h: 128 }) + expect(color.l).toBe(255) + expect(color.c).toBe(0) + expect(color.h).toBe(128) + expect(color.space).toBe('lch') + }) + + it('cmyk', () => { + const color2 = new Color({ c: 20, y: 15, m: 10, k: 5 }) + expect(color2.c).toBe(20) + expect(color2.m).toBe(10) + expect(color2.y).toBe(15) + expect(color2.k).toBe(5) + expect(color2.space).toBe('cmyk') + }) + + it('and default to rgb when unknown parameters are passed', () => { + const color = new Color({ h: 12, b: 10, l: 100 }) + expect(color.r).toBe(0) + expect(color.g).toBe(0) + expect(color.b).toBe(0) + expect(color.space).toBe('rgb') + }) + }) + + it('constructs a color from an array', () => { + const color = new Color([30, 24, 50]) + expect(color.r).toBe(30) + expect(color.g).toBe(24) + expect(color.b).toBe(50) + expect(color.space).toBe('rgb') + }) + + it('constructs a color from an array with space in array', () => { + const color = new Color([50, 50, 5, 'lab']) + expect(color.l).toBe(50) + expect(color.a).toBe(50) + expect(color.b).toBe(5) + expect(color.space).toBe('lab') + }) + + it('constructs a color from an array with space given', () => { + const color = new Color([50, 50, 5], 'lab') + expect(color.l).toBe(50) + expect(color.a).toBe(50) + expect(color.b).toBe(5) + expect(color.space).toBe('lab') + }) + + it('correclty parses an rgb string', () => { + const color = new Color('rgb(255,0,128)') + expect(color.r).toBe(255) + expect(color.g).toBe(0) + expect(color.b).toBe(128) + }) + + it('correclty parses a 3 digit hex string', () => { + color = new Color('#f06') + expect(color.r).toBe(255) + expect(color.g).toBe(0) + expect(color.b).toBe(102) + }) + + it('correclty parses a 6 digit hex string', () => { + color = new Color('#0066ff') + expect(color.r).toBe(0) + expect(color.g).toBe(102) + expect(color.b).toBe(255) + }) + + it('throws an error if unsupported string format was given', () => { + expect(() => new Color('#0066')).toThrowError( + "Unsupported string format, can't construct Color" + ) + }) + }) + + describe('input and output: Importing and exporting colors', () => { + describe('toHex()', () => { + it('returns a hex color', () => { + expect(color.toHex()).toBe('#0066ff') + }) + }) + + describe('toRgb()', () => { + it('returns a rgb string color', () => { + expect(color.toRgb()).toBe('rgb(0,102,255)') + }) + }) + }) + + describe('color spaces: The color spaces supported by our library', () => { + describe('lab()', () => { + it('can convert rgb to lab', () => { + const color = new Color(255, 0, 128) + const lab = color.lab() + expect(lab.l).toBeCloseTo(54.88, 1) + expect(lab.a).toBeCloseTo(84.55, 1) + expect(lab.b).toBeCloseTo(4.065, 1) + expect(lab.space).toBe('lab') + }) + + it('can convert from lab to rgb', () => { + const lab = new Color(54.88, 84.55, 4.065, 'lab') + const rgb = lab.rgb() + expect(rgb.r).toBeCloseTo(255, 0) + expect(rgb.g).toBeCloseTo(0, 0) + expect(rgb.b).toBeCloseTo(128, 0) + expect(rgb.space).toBe('rgb') + }) + + it('is invertable', () => { + const { r, g, b } = new Color(255, 0, 128).lab().rgb() + expect(r).toBeCloseTo(255, 0) + expect(g).toBeCloseTo(0, 0) + expect(b).toBeCloseTo(128, 0) + }) + + it('handles black', () => { + const color = new Color(0, 0, 0).lab().rgb() + expect(color.r).toBeCloseTo(0, 0) + expect(color.g).toBeCloseTo(0, 0) + expect(color.b).toBeCloseTo(0, 0) + expect(color.toHex()).toBe('#000000') + }) + + it('handles white', () => { + const color = new Color(255, 255, 255).lab().rgb() + expect(color.r).toBeCloseTo(255, 0) + expect(color.g).toBeCloseTo(255, 0) + expect(color.b).toBeCloseTo(255, 0) + expect(color.toHex()).toBe('#ffffff') + }) + }) + + describe('lch()', () => { + it('can convert rgb to lch', () => { + const color = new Color(255, 0, 128) + const lch = color.lch() + expect(lch.l).toBeCloseTo(54.88, 1) + expect(lch.c).toBeCloseTo(84.65, 1) + expect(lch.h).toBeCloseTo(2.75, 1) + expect(lch.space).toBe('lch') + }) + + it('can convert from lch to rgb', () => { + const lch = new Color(54.88, 84.65, 2.75, 'lch') + const rgb = lch.rgb() + expect(rgb.r).toBeCloseTo(255, 0) + expect(rgb.g).toBeCloseTo(0, 0) + expect(rgb.b).toBeCloseTo(128, 0) + expect(rgb.space).toBe('rgb') + }) + + it('is invertable', () => { + const { r, g, b } = new Color(255, 0, 128).lch().rgb() + expect(r).toBeCloseTo(255, 0) + expect(g).toBeCloseTo(0, 0) + expect(b).toBeCloseTo(128, 0) + }) + + it('handles black', () => { + const color = new Color(0, 0, 0).lch().rgb() + expect(color.r).toBeCloseTo(0, 0) + expect(color.g).toBeCloseTo(0, 0) + expect(color.b).toBeCloseTo(0, 0) + expect(color.toHex()).toBe('#000000') + }) + + it('handles white', () => { + const color = new Color(255, 255, 255).lch().rgb() + expect(color.r).toBeCloseTo(255, 0) + expect(color.g).toBeCloseTo(255, 0) + expect(color.b).toBeCloseTo(255, 0) + expect(color.toHex()).toBe('#ffffff') + }) + }) + + describe('hsl()', () => { + it('can convert from rgb to hsl', () => { + const color = new Color(255, 0, 128) + const hsl = color.hsl() + expect(hsl.h).toBeCloseTo(329.88, 1) + expect(hsl.s).toBeCloseTo(100, 1) + expect(hsl.l).toBeCloseTo(50, 1) + expect(hsl.space).toBe('hsl') + }) + + it('can convert from hsl to rgb', () => { + const hsl = new Color(329.88, 100, 50, 'hsl') + const rgb = hsl.rgb() + expect(rgb.r).toBeCloseTo(255, 0) + expect(rgb.g).toBeCloseTo(0, 0) + expect(rgb.b).toBeCloseTo(128, 0) + expect(rgb.space).toBe('rgb') + }) + + it('is invertable', () => { + const { r, g, b } = new Color(255, 0, 128).hsl().rgb() + expect(r).toBeCloseTo(255, 0) + expect(g).toBeCloseTo(0, 0) + expect(b).toBeCloseTo(128, 0) + }) + + it('handles black', () => { + const color = new Color(0, 0, 0).hsl().rgb() + expect(color.r).toBeCloseTo(0, 0) + expect(color.g).toBeCloseTo(0, 0) + expect(color.b).toBeCloseTo(0, 0) + expect(color.toHex()).toBe('#000000') + }) + + it('handles white', () => { + const color = new Color(255, 255, 255).hsl().rgb() + expect(color.r).toBeCloseTo(255, 0) + expect(color.g).toBeCloseTo(255, 0) + expect(color.b).toBeCloseTo(255, 0) + expect(color.toHex()).toBe('#ffffff') + }) + }) + + // This conversion is pretty lossy + // Especially g is super wrong (which should be 0 at testing and is >170) + // describe('xyz()', () => { + + // it('can convert from rgb to xyz', () => { + // const color = new Color(255, 0, 128) + // const xyz = color.xyz() + // expect(xyz.x).toBeCloseTo(0.780182, 6) + // expect(xyz.y).toBeCloseTo(0.611077, 6) + // expect(xyz.z).toBeCloseTo(0.590749, 6) + // expect(xyz.space).toBe('xyz') + // }) + + // it('can convert from xyz to rgb', () => { + // const xyz = new Color(0.780181754521692, 0.6110767760212142, 0.590748864329329, 'xyz') + // const rgb = xyz.rgb() + // expect(rgb.r).toBeCloseTo(255, 0) + // expect(rgb.g).toBeCloseTo(0, 0) + // expect(rgb.b).toBeCloseTo(128, 0) + // expect(rgb.space).toBe('rgb') + // }) + + // it('is invertable', () => { + // const { r, g, b } = new Color(255, 0, 128).xyz().rgb() + // expect(r).toBeCloseTo(255, 0) + // expect(g).toBeCloseTo(0, 0) + // expect(b).toBeCloseTo(128, 0) + // }) + + // it('handles black', () => { + // const color = new Color(0, 0, 0).xyz().rgb() + // expect(color.r).toBeCloseTo(0, 0) + // expect(color.g).toBeCloseTo(0, 0) + // expect(color.b).toBeCloseTo(0, 0) + // expect(color.toHex()).toBe('#000000') + // }) + + // it('handles white', () => { + // const color = new Color(255, 255, 255).xyz().rgb() + // expect(color.r).toBeCloseTo(255, 0) + // expect(color.g).toBeCloseTo(255, 0) + // expect(color.b).toBeCloseTo(255, 0) + // expect(color.toHex()).toBe('#ffffff') + // }) + // }) + + describe('cmyk()', () => { + it('can convert from rgb to cmyk', () => { + const color = new Color(255, 0, 128) + const cmyk = color.cmyk() + expect(cmyk.c).toBeCloseTo(0, 1) + expect(cmyk.m).toBeCloseTo(1, 1) + expect(cmyk.y).toBeCloseTo(0.49, 1) + expect(cmyk.k).toBeCloseTo(0, 1) + expect(cmyk.space).toBe('cmyk') + }) + + it('can convert from cmyk to rgb', () => { + const color = new Color(0, 1, 0.49, 0, 'cmyk') + const rgb = color.rgb() + expect(rgb.r).toBeCloseTo(255, -1) + expect(rgb.g).toBeCloseTo(0, -1) + expect(rgb.b).toBeCloseTo(128, -1) + expect(rgb.space).toBe('rgb') + }) + + it('is invertable', () => { + const { r, g, b } = new Color(255, 0, 128).cmyk().rgb() + expect(r).toBeCloseTo(255, 0) + expect(g).toBeCloseTo(0, 0) + expect(b).toBeCloseTo(128, 0) + }) + + it('handles black', () => { + const color = new Color(0, 0, 0).cmyk().rgb() + expect(color.r).toBeCloseTo(0, 0) + expect(color.g).toBeCloseTo(0, 0) + expect(color.b).toBeCloseTo(0, 0) + expect(color.toHex()).toBe('#000000') + }) + + it('handles white', () => { + const color = new Color(255, 255, 255).cmyk().rgb() + expect(color.r).toBeCloseTo(255, 0) + expect(color.g).toBeCloseTo(255, 0) + expect(color.b).toBeCloseTo(255, 0) + expect(color.toHex()).toBe('#ffffff') + }) + }) + }) + + describe('static methods', () => { + describe('random()', () => { + it('returns color', () => { + expect(Color.random()).toEqual(any(Color)) + }) + + it('returns color with mode=vibrant', () => { + expect(Color.random('vibrant')).toEqual(any(Color)) + }) + + it('returns color with mode=sine', () => { + expect(Color.random('sine')).toEqual(any(Color)) + }) + + it('returns color with mode=pastel', () => { + expect(Color.random('pastel')).toEqual(any(Color)) + }) + + it('returns color with mode=dark', () => { + expect(Color.random('dark')).toEqual(any(Color)) + }) + + it('returns color with mode=rgb', () => { + expect(Color.random('rgb')).toEqual(any(Color)) + }) + + it('returns color with mode=lab', () => { + expect(Color.random('lab')).toEqual(any(Color)) + }) + + it('returns color with mode=grey', () => { + expect(Color.random('grey')).toEqual(any(Color)) + }) + + it('throws an error if mode is unknown', () => { + expect(() => Color.random('foo')).toThrowError( + 'Unsupported random color mode' + ) + }) + }) + + describe('test()', () => { + it('returns false for non strings', () => { + expect(Color.test(1)).toBe(false) + }) + + it('returns true if a given string is a color - hex', () => { + expect(Color.test('#abc')).toBe(true) + expect(Color.test('#abcdef')).toBe(true) + }) + + it('returns true if a given string is a color - rgb', () => { + expect(Color.test('rgb(1,2,3)')).toBe(true) + }) + + it('returns false if a given string is a not a color', () => { + expect(Color.test('#1234')).toBe(false) + expect(Color.test('#Hallo Welt')).toBe(false) + }) + }) + + describe('isRgb()', () => { + it('returns true if object has all rgb properties set', () => { + expect(Color.isRgb({ r: 12, g: 45, b: 11, blub: true })).toBe(true) + }) + + it('returns false if object has not all rgb properties set or they are not numbers', () => { + expect(Color.isRgb({ r: 12, g: 45 })).toBe(false) + expect(Color.isRgb({ r: 12, g: 45, b: '1' })).toBe(false) + }) + }) + + describe('isColor', () => { + it('tests if given value is a color', () => { + const spy1 = spyOn(Color, 'isRgb') + const spy2 = spyOn(Color, 'test') + expect(Color.isColor(new Color())).toBe(true) + Color.isColor('#000') + const color = { r: 12, g: 45, b: 11 } + Color.isColor(color) + expect(spy1).toHaveBeenCalledWith('#000') + expect(spy1).toHaveBeenCalledWith(color) + expect(spy2).toHaveBeenCalledWith('#000') + }) + }) + }) +}) diff --git a/spec/spec/types/EventTarget.js b/spec/spec/types/EventTarget.js new file mode 100644 index 000000000..f19fe11cb --- /dev/null +++ b/spec/spec/types/EventTarget.js @@ -0,0 +1,143 @@ +/* globals describe, expect, it, spyOn, jasmine */ + +import { EventTarget } from '../../../src/main.js' +import { getWindow } from '../../../src/utils/window.js' + +const { any, objectContaining, createSpy } = jasmine + +const event = (name) => { + const CustomEvent = getWindow().CustomEvent + return new CustomEvent(name) +} + +describe('EventTarget.js', () => { + describe('()', () => { + it('creates a new object of type EventTarget', () => { + expect(new EventTarget()).toEqual(any(EventTarget)) + }) + }) + + describe('addEventListener()', () => { + it('is a noop', () => { + const target = new EventTarget() + const frozen = Object.freeze(target) + frozen.addEventListener() + }) + }) + + describe('dispatch()', () => { + it('eventually calls dispatchEvent on the target and returns the event', () => { + const target = new EventTarget() + const spy = spyOn(target, 'dispatchEvent').and.callThrough() + const options = { cancelable: false } + const event = target.dispatch('bla', 'foo', options) + expect(spy).toHaveBeenCalledWith(event) + expect(event).toEqual( + objectContaining({ type: 'bla', detail: 'foo', cancelable: false }) + ) + }) + }) + + describe('dispatchEvent()', () => { + it('returns true if no events are bound', () => { + const target = new EventTarget() + expect(target.dispatchEvent(event('event'))).toBe(true) + }) + + it('calls the handler for a bound event', () => { + const target = new EventTarget() + const spy = createSpy('event') + const ev = event('event') + target.on('event', spy) + const ret = target.dispatchEvent(ev) + expect(spy).toHaveBeenCalledWith(ev) + expect(ret).toBe(!ev.defaultPrevented) + }) + + it('returns negative default prevented', () => { + const target = new EventTarget() + const ev = event('event') + ev.preventDefault() + const ret = target.dispatchEvent(ev) + expect(ret).toBe(!ev.defaultPrevented) + }) + }) + + describe('fire()', () => { + it('calls dispatch and returns the element', () => { + const target = new EventTarget() + const spy = spyOn(target, 'dispatch') + expect(target.fire('event', 'foo', 'bar')).toBe(target) + expect(spy).toHaveBeenCalledWith('event', 'foo', 'bar') + }) + }) + + describe('getEventHolder()', () => { + it('returns itself', () => { + const target = new EventTarget() + expect(target.getEventHolder()).toBe(target) + }) + }) + + describe('getEventTarget()', () => { + it('returns itself', () => { + const target = new EventTarget() + expect(target.getEventTarget()).toBe(target) + }) + }) + + describe('off()', () => { + it('returns itself', () => { + const target = new EventTarget() + const spy = createSpy() + expect(target.on('event', spy)).toBe(target) + }) + + it('removes an event binding from the target', () => { + const target = new EventTarget() + const spy = createSpy() + target.on('event', spy) + target.dispatch('event') + expect(spy.calls.count()).toBe(1) + target.off('event', spy) + target.dispatch('event') + expect(spy.calls.count()).toBe(1) + }) + + it('removes an event binding with options from the target', () => { + const target = new EventTarget() + const spy = createSpy() + target.on('event', spy, undefined, { capture: true }) + target.dispatch('event') + expect(spy.calls.count()).toBe(1) + target.off('event', spy, { capture: true }) + target.dispatch('event') + expect(spy.calls.count()).toBe(1) + }) + }) + + describe('on()', () => { + it('returns itself', () => { + const target = new EventTarget() + const spy = createSpy() + expect(target.off('event', spy)).toBe(target) + }) + + it('adds an event binding to the target', () => { + const target = new EventTarget() + const spy = createSpy() + expect(spy.calls.count()).toBe(0) + target.on('event', spy) + target.dispatch('event') + expect(spy.calls.count()).toBe(1) + }) + }) + + describe('removeEventListener()', () => { + it('is a noop', () => { + const target = new EventTarget() + const frozen = Object.freeze(target) + frozen.removeEventListener() + }) + }) +}) diff --git a/spec/spec/types/List.js b/spec/spec/types/List.js new file mode 100644 index 000000000..9a44feb6f --- /dev/null +++ b/spec/spec/types/List.js @@ -0,0 +1,120 @@ +/* globals describe, expect, it, jasmine */ + +import { List } from '../../../src/main.js' + +const { any, createSpy, objectContaining } = jasmine + +describe('List.js', () => { + describe('()', () => { + it('creates a new List from Array', () => { + const list = new List([1, 2, 3]) + expect(list).toEqual(any(List)) + }) + + it('creates preallocated List like Array(1)', () => { + const list = new List(1) + expect(list.length).toBe(1) + }) + + it('is instance of Array', () => { + const list = new List([1, 2, 3]) + expect(list).toEqual(any(Array)) + }) + + it('allows index access', () => { + const list = new List([1, 2, 3]) + expect(list[1]).toBe(2) + }) + }) + + describe('each()', () => { + it('works like map but with context set to the element when a function is passed', () => { + const list = new List([1, 2, 3]).each((el) => el * 2) + expect(list).toEqual(any(List)) + expect(list).toEqual([2, 4, 6]) + + const spy = createSpy() + const obj = {} + const list2 = new List([obj]) + list2.each(spy) + expect(spy.calls.first()).toEqual( + objectContaining({ + object: obj, + args: [obj, 0, list2] + }) + ) + }) + + it('calls a method on every element in the list and passes arguments when a string is passed', () => { + const list = new List([10, 11, 12]) + expect(list.each('toString', 16)).toEqual(['a', 'b', 'c']) + }) + }) + + describe('toArray()', () => { + it('returns a plain array from the contents of the list', () => { + const list = new List([1, 2, 3]) + const arr = list.toArray() + expect(arr).toEqual(any(Array)) + expect(arr).not.toEqual(any(List)) + expect(arr).toEqual([1, 2, 3]) + }) + }) + + describe('static extend()', () => { + it('adds new method names to the List', () => { + List.extend(['fooBar']) + expect(new List().fooBar).toEqual(any(Function)) + + const obj = { fooBar: createSpy() } + new List([obj]).fooBar() + expect(obj.fooBar).toHaveBeenCalled() + + delete List.prototype.fooBar + }) + + it('keeps Array prototype names prefixed with $', () => { + // We're picking a function that we know isn't part of core svg.js + // If we implement an 'unshift' function at some point, change this to something else + if (List.prototype.hasOwnProperty('unshift')) { + fail( + 'List.unshift is already a function - change this test to use a different name!' + ) + return + } + + List.extend(['unshift']) + expect(new List().unshift).toEqual(any(Function)) + expect(new List().$unshift).toEqual(Array.prototype.unshift) + + // Check that it works! + const sourceArray = [ + { unshift: () => 1 }, + { unshift: () => 2 }, + { unshift: () => 3 } + ] + const list = new List(sourceArray) + + expect(list).toEqual(sourceArray) + expect(list.unshift(0)).toEqual([1, 2, 3]) + + expect(list.$unshift(0)).toEqual(4) + expect(list).toEqual([0].concat(sourceArray)) + + delete List.prototype.unshift + }) + + it('skips reserved names', () => { + const { constructor, each, toArray } = List.prototype + List.extend(['constructor', 'each', 'toArray']) + expect(List.prototype).toEqual( + objectContaining({ constructor, each, toArray }) + ) + }) + + it('skips private methods starting with an underscore', () => { + List.extend(['_private']) + expect(new List()._private).toBe(undefined) + }) + }) +}) diff --git a/spec/spec/types/Matrix.js b/spec/spec/types/Matrix.js new file mode 100644 index 000000000..36882a901 --- /dev/null +++ b/spec/spec/types/Matrix.js @@ -0,0 +1,604 @@ +/* globals describe, expect, it, spyOn, jasmine */ + +import { Matrix, Rect, SVG } from '../../../src/main.js' +import { getWindow } from '../../../src/utils/window.js' + +const { any, objectContaining } = jasmine + +describe('Matrix.js', () => { + const comp = { a: 2, b: 0, c: 0, d: 2, e: 100, f: 50 } + + describe('initialization', () => { + it('creates a new matrix with default values', () => { + const matrix = new Matrix() + expect(matrix).toEqual( + objectContaining({ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }) + ) + }) + + it('parses the current transform matrix from an element', () => { + const rect = new Rect().transform(comp) + const matrix = new Matrix(rect) + expect(matrix).toEqual(objectContaining(comp)) + }) + + it('parses a string value correctly', () => { + const matrix = new Matrix('2, 0, 0, 2, 100, 50') + expect(matrix).toEqual(objectContaining(comp)) + }) + + it('parses an array correctly', () => { + const matrix = new Matrix([2, 0, 0, 2, 100, 50]) + expect(matrix).toEqual(objectContaining(comp)) + }) + + it('parses an object correctly', () => { + const matrix = new Matrix(comp) + expect(matrix).toEqual(objectContaining(comp)) + }) + + it('parses a transform object correctly', () => { + const matrix = new Matrix({ scale: 2, translate: [100, 50] }) + expect(matrix).toEqual(objectContaining(comp)) + }) + + it('parses 6 arguments correctly', () => { + const matrix = new Matrix(2, 0, 0, 2, 100, 50) + expect(matrix).toEqual(objectContaining(comp)) + }) + + it('falls back to base if source is missing values', () => { + const matrix = new Matrix([]) + expect(matrix).toEqual(new Matrix()) + }) + }) + + describe('toString()', () => { + it('exports correctly to a string', () => { + expect(new Matrix().toString()).toBe('matrix(1,0,0,1,0,0)') + }) + }) + + describe('transform()', () => { + it('does simple left matrix multiplication if matrixlike object is passed', () => { + const matrix = new Matrix().transform(new Matrix().scale(2)) + expect(matrix).toEqual(new Matrix().lmultiplyO(new Matrix().scale(2))) + }) + + it('forces the origin to a specific place if position.x is passed', () => { + const matrix = new Matrix().transform({ px: 10 }) + expect(matrix.e).toBe(10) + }) + + it('forces the origin to a specific place if position.y is passed', () => { + const matrix = new Matrix().transform({ py: 10 }) + expect(matrix.f).toBe(10) + }) + }) + + describe('decompose()', () => { + it('decomposes a matrix properly', () => { + var matrix = new Matrix() + .scale(3, 2.5) + .shear(4) + .rotate(30) + .translate(20, 30) + var decomposed = matrix.decompose() + expect(decomposed.scaleX).toBeCloseTo(3) + expect(decomposed.scaleY).toBeCloseTo(2.5) + expect(decomposed.shear).toBeCloseTo(4) + expect(decomposed.rotate).toBeCloseTo(30) + expect(decomposed.translateX).toBeCloseTo(20) + expect(decomposed.translateY).toBeCloseTo(30) + }) + + it('can be recomposed to the same matrix', () => { + var matrix = new Matrix() + .scale(3, 2.5) + .shear(4) + .rotate(30) + .translate(20, 30) + var decomposed = matrix.decompose() + + // Get rid of the matrix values before recomposing with the matrix constructor + for (const prop in 'abcdef') delete decomposed[prop] + + var composed = new Matrix(decomposed) + expect(matrix.a).toBeCloseTo(composed.a) + expect(matrix.b).toBeCloseTo(composed.b) + expect(matrix.c).toBeCloseTo(composed.c) + expect(matrix.d).toBeCloseTo(composed.d) + expect(matrix.e).toBeCloseTo(composed.e) + expect(matrix.f).toBeCloseTo(composed.f) + }) + }) + + describe('clone()', () => { + it('returns a clone of the matrix', () => { + var matrix = new Matrix(2, 0, 0, 5, 0, 0) + var clone = matrix.clone() + expect(matrix).not.toBe(clone) + for (var i in 'abcdef') { + expect(matrix[i]).toEqual(clone[i]) + } + }) + }) + + describe('multiply()', () => { + it('multiplies two matrices', () => { + var matrix1 = new Matrix(1, 4, 2, 5, 3, 6) + var matrix2 = new Matrix(7, 8, 8, 7, 9, 6) + var matrix3 = matrix1.multiply(matrix2) + + expect(matrix1.toString()).toBe('matrix(1,4,2,5,3,6)') + expect(matrix2.toString()).toBe('matrix(7,8,8,7,9,6)') + expect(matrix3.toString()).toBe('matrix(23,68,22,67,24,72)') + }) + + it('accepts matrices in any form', () => { + var matrix1 = new Matrix(1, 4, 2, 5, 3, 6) + var matrix2 = matrix1.multiply('7,8,8,7,9,6') + + expect(matrix1.toString()).toBe('matrix(1,4,2,5,3,6)') + expect(matrix2.toString()).toBe('matrix(23,68,22,67,24,72)') + }) + }) + + describe('inverse()', () => { + it('inverses matrix', () => { + var matrix1 = new Matrix(2, 0, 0, 5, 4, 3) + var matrix2 = matrix1.inverse() + var abcdef = [0.5, 0, 0, 0.2, -2, -0.6] + + for (var i in 'abcdef') { + expect(matrix2['abcdef'[i]]).toBeCloseTo(abcdef[i]) + } + }) + + it('throws if matrix is not invertible', () => { + const matrix = new Matrix(0, 0, 0, 0, 0, 0) + expect(() => matrix.inverse()).toThrowError( + 'Cannot invert matrix(0,0,0,0,0,0)' + ) + }) + }) + + describe('translate()', () => { + it('translates matrix by given x and y values', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).translate(10, 12.5) + expect(matrix.e).toBe(14) + expect(matrix.f).toBe(15.5) + }) + + it('does nothing if you give it no x or y value', () => { + var matrix = new Matrix(1, 2, 3, 4, 5, 6).translate() + expect(matrix.e).toBe(5) + expect(matrix.f).toBe(6) + }) + }) + + describe('scale()', () => { + it('performs a uniformal scale with one value', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).scale(3) + + expect(matrix.a).toBe(3) + expect(matrix.d).toBe(3) + expect(matrix.e).toBe(4 * 3) + expect(matrix.f).toBe(3 * 3) + }) + + it('performs a non-uniformal scale with two values', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).scale(2.5, 3.5) + + expect(matrix.a).toBe(2.5) + expect(matrix.d).toBe(3.5) + expect(matrix.e).toBe(4 * 2.5) + expect(matrix.f).toBe(3 * 3.5) + }) + + it('performs a uniformal scale at a given center point with three values', () => { + var matrix = new Matrix(1, 3, 2, 3, 4, 3).scale(3, 2, 3) + + expect(matrix.a).toBe(3) + expect(matrix.b).toBe(9) + expect(matrix.c).toBe(6) + expect(matrix.d).toBe(9) + expect(matrix.e).toBe(8) + expect(matrix.f).toBe(3) + }) + + it('performs a non-uniformal scale at a given center point with four values', () => { + var matrix = new Matrix(1, 3, 2, 3, 4, 3).scale(3, 2, 2, 3) + + expect(matrix.a).toBe(3) + expect(matrix.b).toBe(6) + expect(matrix.c).toBe(6) + expect(matrix.d).toBe(6) + expect(matrix.e).toBe(8) + expect(matrix.f).toBe(3) + }) + }) + + describe('rotate()', () => { + it('performs a rotation with one argument', () => { + var matrix = new Matrix(1, 3, 2, 3, 4, 3).rotate(30) + + expect(matrix.a).toBeCloseTo(-0.6339746) + expect(matrix.b).toBeCloseTo(3.09807621) + expect(matrix.c).toBeCloseTo(0.23205081) + expect(matrix.d).toBeCloseTo(3.59807621) + expect(matrix.e).toBeCloseTo(1.96410162) + expect(matrix.f).toBeCloseTo(4.59807621) + }) + + it('performs a rotation around a given point with three arguments', () => { + var matrix = new Matrix(1, 3, 2, 3, 4, 3).rotate(30, 2, 3) + + expect(matrix.a).toBeCloseTo(-0.633974596216) + expect(matrix.b).toBeCloseTo(3.09807621135) + expect(matrix.c).toBeCloseTo(0.232050807569) + expect(matrix.d).toBeCloseTo(3.59807621135) + expect(matrix.e).toBeCloseTo(3.73205080757) + expect(matrix.f).toBeCloseTo(4.0) + }) + }) + + describe('flip()', () => { + describe('with x given', () => { + it('performs a flip over the horizontal axis with one argument', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).flip('x') + + expect(matrix.a).toBe(-1) + expect(matrix.d).toBe(1) + expect(matrix.e).toBe(-4) + expect(matrix.f).toBe(3) + }) + + it('performs a flip over the horizontal axis over a given point with two arguments', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).flip('x', 150) + + expect(matrix.a).toBe(-1) + expect(matrix.d).toBe(1) + expect(matrix.e).toBe(296) + expect(matrix.f).toBe(3) + }) + }) + + describe('with y given', () => { + it('performs a flip over the vertical axis with one argument', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).flip('y') + + expect(matrix.a).toBe(1) + expect(matrix.d).toBe(-1) + expect(matrix.e).toBe(4) + expect(matrix.f).toBe(-3) + }) + + it('performs a flip over the vertical axis over a given point with two arguments', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).flip('y', 100) + + expect(matrix.a).toBe(1) + expect(matrix.d).toBe(-1) + expect(matrix.e).toBe(4) + expect(matrix.f).toBe(197) + }) + }) + + describe('with no axis given', () => { + it('performs a flip over the horizontal and vertical axis with no argument', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).flip() + + expect(matrix.a).toBe(-1) + expect(matrix.d).toBe(-1) + expect(matrix.e).toBe(-4) + expect(matrix.f).toBe(-3) + }) + + it('performs a flip over the horizontal and vertical axis over a given point with one argument that represent both coordinates', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).flip(100) + + expect(matrix.a).toBe(-1) + expect(matrix.d).toBe(-1) + expect(matrix.e).toBe(196) + expect(matrix.f).toBe(197) + }) + + it('performs a flip over the horizontal and vertical axis over a given point with two arguments', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).flip(50, 100) + + expect(matrix.a).toBe(-1) + expect(matrix.d).toBe(-1) + expect(matrix.e).toBe(96) + expect(matrix.f).toBe(197) + }) + }) + }) + + describe('skew()', () => { + it('performs a uniformal skew with one value', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(30) + + expect(matrix.a).toBe(1) + expect(matrix.b).toBeCloseTo(0.57735026919) + expect(matrix.c).toBeCloseTo(0.57735026919) + expect(matrix.d).toBe(1) + expect(matrix.e).toBeCloseTo(5.73205080757) + expect(matrix.f).toBeCloseTo(5.30940107676) + }) + + it('performs a non-uniformal skew with two values', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(30, 20) + + expect(matrix.a).toBe(1) + expect(matrix.b).toBeCloseTo(0.363970234266) + expect(matrix.c).toBeCloseTo(0.57735026919) + expect(matrix.d).toBe(1) + expect(matrix.e).toBeCloseTo(5.73205080757) + expect(matrix.f).toBeCloseTo(4.45588093706) + }) + + it('performs a uniformal skew at a given center point with three values', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(30, 150, 100) + + expect(matrix.a).toBe(1) + expect(matrix.b).toBeCloseTo(0.57735026919) + expect(matrix.c).toBeCloseTo(0.57735026919) + expect(matrix.d).toBe(1) + expect(matrix.e).toBeCloseTo(-52.0029761114) + expect(matrix.f).toBeCloseTo(-81.2931393017) + }) + + it('performs a non-uniformal skew at a given center point with four values', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(30, 20, 150, 100) + + expect(matrix.a).toBe(1.0) + expect(matrix.b).toBeCloseTo(0.363970234266) + expect(matrix.c).toBeCloseTo(0.57735026919) + expect(matrix.d).toBe(1.0) + expect(matrix.e).toBeCloseTo(-52.0029761114) + expect(matrix.f).toBeCloseTo(-50.1396542029) + }) + + it('can be chained', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).skew(20, 30).skew(30, 20) + expect(matrix.a).toBeCloseTo(1.33333333333) + expect(matrix.b).toBeCloseTo(0.941320503456) + expect(matrix.c).toBeCloseTo(0.941320503456) + expect(matrix.d).toBeCloseTo(1.13247433143) + expect(matrix.e).toBeCloseTo(8.1572948437) + expect(matrix.f).toBeCloseTo(7.16270500812) + }) + }) + + describe('skewX', () => { + it('performs a skew along the x axis with one value', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).skewX(30) + + expect(matrix.a).toBe(1) + expect(matrix.b).toBe(0) + expect(matrix.c).toBeCloseTo(0.57735026919) + expect(matrix.d).toBe(1) + expect(matrix.e).toBeCloseTo(5.73205080757) + expect(matrix.f).toBe(3) + }) + + it('performs a skew along the x axis at a given center point with three values', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).skewX(30, 150, 100) + + expect(matrix.a).toBe(1) + expect(matrix.b).toBe(0) + expect(matrix.c).toBeCloseTo(0.57735026919) + expect(matrix.d).toBe(1) + expect(matrix.e).toBeCloseTo(-52.0029761114) + expect(matrix.f).toBe(3) + }) + }) + + describe('skewY', () => { + it('performs a skew along the y axis with one value', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).skewY(30) + + expect(matrix.a).toBe(1) + expect(matrix.b).toBeCloseTo(0.57735026919) + expect(matrix.c).toBe(0) + expect(matrix.d).toBe(1) + expect(matrix.e).toBe(4) + expect(matrix.f).toBeCloseTo(5.30940107676) + }) + + it('performs a skew along the y axis at a given center point with three values', () => { + var matrix = new Matrix(1, 0, 0, 1, 4, 3).skewY(30, 150, 100) + + expect(matrix.a).toBe(1) + expect(matrix.b).toBeCloseTo(0.57735026919) + expect(matrix.c).toBe(0) + expect(matrix.d).toBe(1) + expect(matrix.e).toBe(4) + expect(matrix.f).toBeCloseTo(-81.2931393017) + }) + }) + + describe('around()', () => { + it('performs a matrix operation around an origin by shifting the origin to 0,0', () => { + const matrix = new Matrix(1, 0, 0, 1, 0, 0).around( + 10, + 10, + new Matrix().scale(2) + ) + + expect(matrix).toEqual(new Matrix(2, 0, 0, 2, -10, -10)) + }) + + it('defaults to around center of 0,0', () => { + const matrix = new Matrix(1, 0, 0, 1, 0, 0).around( + 0, + 0, + new Matrix().scale(2) + ) + + expect(matrix).toEqual(new Matrix(2, 0, 0, 2, 0, 0)) + }) + }) + + describe('equals()', () => { + it('returns true if the same matrix is passed', () => { + const matrix = new Matrix() + expect(matrix.equals(matrix)).toBe(true) + }) + + it('returns true if the components match', () => { + const matrix = new Matrix() + expect(matrix.equals(matrix.clone())).toBe(true) + }) + + it('returns false if the components do not match', () => { + const matrix = new Matrix() + expect(matrix.equals(matrix.scale(2))).toBe(false) + }) + }) + + describe('valueOf()', () => { + it('returns an object containing the matrix components', () => { + const matrix = new Matrix().valueOf() + expect(matrix).not.toEqual(any(Matrix)) + expect(matrix).toEqual({ a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }) + }) + }) + + describe('toArray', () => { + it('converts matrix to array', () => { + const arr = new Matrix().toArray() + expect(arr).toEqual([1, 0, 0, 1, 0, 0]) + }) + }) + + describe('static', () => { + describe('fromArray()', () => { + it('creates a matrix like object from an array', () => { + const matrix = Matrix.fromArray([1, 2, 3, 4, 5, 6]) + expect(matrix).not.toEqual(any(Matrix)) + expect(matrix).toEqual(new Matrix(1, 2, 3, 4, 5, 6).valueOf()) + }) + }) + + describe('isMatrixLike', () => { + it('returns true if object contains all components', () => { + expect(Matrix.isMatrixLike(new Matrix())).toBe(true) + expect(Matrix.isMatrixLike(new Matrix().valueOf())).toBe(true) + expect(Matrix.isMatrixLike({ f: 0 })).toBe(true) + }) + + it('returns false if no component is found', () => { + expect(Matrix.isMatrixLike({ foo: 'bar' })).toBe(false) + }) + }) + + describe('formatTransforms()', () => { + it('formats all transform input varieties to a canonical form', () => { + expect( + Matrix.formatTransforms({ + flip: true, + skew: 5, + scale: 5, + originX: 5, + originY: 5, + positionX: 5, + positionY: 5, + translateX: 5, + translateY: 5, + relativeX: 5, + relativeY: 5 + }) + ).toEqual({ + scaleX: -5, + scaleY: -5, + skewX: 5, + skewY: 5, + shear: 0, + theta: 0, + rx: 5, + ry: 5, + tx: 5, + ty: 5, + ox: 5, + oy: 5, + px: 5, + py: 5 + }) + }) + + it('respects flip=x', () => { + expect( + Matrix.formatTransforms({ + flip: 'x', + scale: [1, 2], + skew: [1, 2] + }) + ).toEqual( + objectContaining({ scaleX: -1, scaleY: 2, skewX: 1, skewY: 2 }) + ) + }) + + it('respects flip=y', () => { + expect( + Matrix.formatTransforms({ + flip: 'y', + scaleX: 1, + scaleY: 2, + skewX: 1, + skewY: 2 + }) + ).toEqual( + objectContaining({ scaleX: 1, scaleY: -2, skewX: 1, skewY: 2 }) + ) + }) + + it('makes position NaN if not passed', () => { + expect( + Matrix.formatTransforms({ + flip: 'y', + scaleX: 1, + scaleY: 2, + skewX: 1, + skewY: 2 + }) + ).toEqual(objectContaining({ px: NaN, py: NaN })) + }) + }) + }) + + describe('Element', () => { + describe('ctm()', () => { + it('returns the native ctm wrapped into a matrix', () => { + const rect = new Rect() + const spy = spyOn(rect.node, 'getCTM') + rect.ctm() + expect(spy).toHaveBeenCalled() + }) + }) + + describe('screenCTM()', () => { + it('returns the native screenCTM wrapped into a matrix for a normal element', () => { + const rect = new Rect() + const spy = spyOn(rect.node, 'getScreenCTM') + rect.screenCTM() + expect(spy).toHaveBeenCalled() + }) + + it('does extra work for nested svgs because firefox needs it', () => { + const spy = spyOn( + getWindow().SVGGraphicsElement.prototype, + 'getScreenCTM' + ) + const svg = SVG().nested() + svg.screenCTM() + expect(spy).toHaveBeenCalled() + }) + + it('does not throw and returns identity matrix if element is not rendered', () => { + const canvas = SVG().viewbox(0, 0, 0, 0) + expect(canvas.screenCTM()).toEqual(new Matrix()) + }) + }) + }) +}) diff --git a/spec/spec/types/PathArray.js b/spec/spec/types/PathArray.js new file mode 100644 index 000000000..5c3991c67 --- /dev/null +++ b/spec/spec/types/PathArray.js @@ -0,0 +1,97 @@ +/* globals describe, expect, it, beforeEach */ + +import { PathArray, Box } from '../../../src/main.js' + +describe('PathArray.js', () => { + let p1, p2, p3 + + beforeEach(() => { + p1 = new PathArray('m10 10 h 80 v 80 h -80 l 300 400 z') + p2 = new PathArray( + 'm10 80 c 40 10 65 10 95 80 s 150 150 180 80 t 300 300 q 52 10 95 80 z' + ) + p3 = new PathArray('m80 80 A 45 45, 0, 0, 0, 125 125 L 125 80 z') + }) + + it('parses flat arrays correctly', () => { + const arr = new PathArray(['M', 0, 0, 'L', 100, 100, 'z']) + expect(arr.toString()).toBe('M0 0L100 100Z ') + }) + + it('parses nested arrays correctly', () => { + const arr = new PathArray([['M', 0, 0], ['L', 100, 100], ['z']]) + expect(arr.toString()).toBe('M0 0L100 100Z ') + }) + + // this test is designed to cover a certain line but it doesn't work because of #608 + it('returns the valueOf when PathArray is given', () => { + const p = new PathArray('m10 10 h 80 v 80 h -80 l 300 400 z') + + expect(new PathArray(p)).toEqual(p) + }) + + describe('move()', () => { + it('moves all points in a straight path', () => { + expect(p1.move(100, 200).toString()).toBe( + 'M100 200H180V280H100L400 680Z ' + ) + }) + + it('moves all points in a curved path', () => { + expect(p2.move(100, 200).toString()).toBe( + 'M100 200C140 210 165 210 195 280S345 430 375 360T675 660Q727 670 770 740Z ' + ) + }) + + it('moves all points in a arc path', () => { + expect(p3.move(100, 200).toString()).toBe( + 'M100 200A45 45 0 0 0 145 245L145 200Z ' + ) + }) + + it('does nothing if passed number is not a number', () => { + expect(p3.move()).toEqual(p3) + }) + }) + + describe('size()', () => { + it('resizes all points in a straight path', () => { + expect(p1.size(600, 200).toString()).toBe( + 'M10 10H170V43.333333333333336H10L610 210Z ' + ) + }) + + it('resizes all points in a curved path', () => { + expect(p2.size(600, 200).toString()).toBe( + '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 ' + ) + }) + + it('resizes all points in a arc path', () => { + const expected = [ + ['M', 80, 80], + ['A', 600, 200, 0, 0, 0, 680, 280], + ['L', 680, 80], + ['Z'] + ] + + const toBeTested = p3.size(600, 200) + + for (let i = toBeTested.length; i--; ) { + expect(toBeTested[i].shift().toUpperCase()).toBe( + expected[i].shift().toUpperCase() + ) + for (let j = toBeTested[i].length; j--; ) { + expect(toBeTested[i][j]).toBeCloseTo(expected[i][j]) + } + } + }) + }) + + describe('bbox()', () => { + it('calculates the bounding box of the PathArray', () => { + const box = new PathArray('M0 0 L 10 10').bbox() + expect(box).toEqual(new Box(0, 0, 10, 10)) + }) + }) +}) diff --git a/spec/spec/types/Point.js b/spec/spec/types/Point.js new file mode 100644 index 000000000..ff167b3ad --- /dev/null +++ b/spec/spec/types/Point.js @@ -0,0 +1,108 @@ +/* globals describe, expect, it, beforeEach, spyOn */ + +import { Point, Rect, Matrix } from '../../../src/main.js' + +describe('Point.js', () => { + var point + + describe('initialization', () => { + describe('without a source', () => { + beforeEach(() => { + point = new Point() + }) + + it('creates a new point with default values', () => { + expect(point.x).toBe(0) + expect(point.y).toBe(0) + }) + }) + + describe('with x and y given', () => { + it('creates a point with given values', () => { + var point = new Point(2, 4) + + expect(point.x).toBe(2) + expect(point.y).toBe(4) + }) + }) + + describe('with only x given', () => { + it('sets the y value to 0', () => { + var point = new Point(7) + + expect(point.x).toBe(7) + expect(point.y).toBe(0) + }) + }) + + describe('with array given', () => { + it('creates a point from array', () => { + var point = new Point([2, 4]) + + expect(point.x).toBe(2) + expect(point.y).toBe(4) + }) + }) + + describe('with object given', () => { + it('creates a point from object', () => { + var point = new Point({ x: 2, y: 4 }) + + expect(point.x).toBe(2) + expect(point.y).toBe(4) + }) + }) + + describe('with Point given', () => { + it('creates a point from Point', () => { + var point = new Point(new Point(2, 4)) + + expect(point.x).toBe(2) + expect(point.y).toBe(4) + }) + }) + }) + + describe('transform()', () => { + it('transforms a point with a matrix', () => { + expect( + new Point().transform(new Matrix({ translate: [10, 10] })) + ).toEqual(new Point(10, 10)) + }) + + it('transforms a point with a transformation object', () => { + expect(new Point().transform({ translate: [10, 10] })).toEqual( + new Point(10, 10) + ) + }) + }) + + describe('clone()', () => { + it('returns cloned point', () => { + var point1 = new Point(1, 1) + var point2 = point1.clone() + + expect(point1).toEqual(point2) + expect(point1).not.toBe(point2) + }) + }) + + describe('toArray()', () => { + it('creates an array representation of Point', () => { + const p = new Point(1, 2) + expect(p.toArray()).toEqual([1, 2]) + }) + }) + + describe('Element', () => { + describe('point()', () => { + it('transforms a screen point into the coordinate system of the element', () => { + const rect = new Rect() + spyOn(rect, 'screenCTM').and.callFake( + () => new Matrix(1, 0, 0, 1, 20, 20) + ) + expect(rect.point({ x: 10, y: 10 })).toEqual(new Point(-10, -10)) + }) + }) + }) +}) diff --git a/spec/spec/types/PointArray.js b/spec/spec/types/PointArray.js new file mode 100644 index 000000000..74cc21fb9 --- /dev/null +++ b/spec/spec/types/PointArray.js @@ -0,0 +1,177 @@ +/* globals describe, expect, it */ + +import { PointArray, Matrix, Point } from '../../../src/main.js' + +describe('PointArray.js', () => { + const squareString = '0,0 1,0 1,1 0,1' + + describe('()', () => { + it('parses a string to a point array', () => { + var array = new PointArray('0,1 -.05,7.95 1000.0001,-200.222') + expect(array.valueOf()).toEqual([ + [0, 1], + [-0.05, 7.95], + [1000.0001, -200.222] + ]) + }) + + it('parses a points array correctly to string', () => { + var array = new PointArray([ + [0, 0.15], + [-100, -3.141592654], + [50, 100] + ]) + expect(array + '').toBe('0,0.15 -100,-3.141592654 50,100') + }) + + it('parses a flat array of x/y coordinates to a point array', () => { + var array = new PointArray([1, 4, 5, 68, 12, 24]) + expect(array.valueOf()).toEqual([ + [1, 4], + [5, 68], + [12, 24] + ]) + }) + + it('parses points with space delimitered x/y coordinates', () => { + var array = new PointArray( + '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' + ) + expect(array + '').toBe( + '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' + ) + }) + + it('parses points with comma delimitered x/y coordinates', () => { + var array = new PointArray( + '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' + ) + expect(array + '').toBe( + '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' + ) + }) + + it('parses points with comma and space delimitered x/y coordinates', () => { + var array = new PointArray( + '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' + ) + expect(array + '').toBe( + '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' + ) + }) + + it('parses points with space and comma delimitered x/y coordinates', () => { + var array = new PointArray( + '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' + ) + expect(array + '').toBe( + '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' + ) + }) + + it('parses points with redundant spaces at the end', () => { + var array = new PointArray( + '2176.6,1708.8 2176.4,1755.8 2245.8,1801.5 2297,1787.8 ' + ) + expect(array + '').toBe( + '2176.6,1708.8 2176.4,1755.8 2245.8,1801.5 2297,1787.8' + ) + }) + + it('parses points with space delimitered x/y coordinates - even with leading or trailing space', () => { + var array = new PointArray(' 1 2 3 4 ') + expect(array + '').toBe('1,2 3,4') + }) + + it('parses odd number of points with space delimitered x/y coordinates and silently remove the odd point', () => { + // this is according to spec: https://svgwg.org/svg2-draft/shapes.html#DataTypePoints + var array = new PointArray('1 2 3') + expect(array + '').toBe('1,2') + }) + + it('parses odd number of points in a flat array of x/y coordinates and silently remove the odd point', () => { + // this is according to spec: https://svgwg.org/svg2-draft/shapes.html#DataTypePoints + var array = new PointArray([1, 2, 3]) + expect(array.valueOf()).toEqual([[1, 2]]) + }) + }) + + describe('move()', () => { + it('moves the whole array by the passed value', () => { + const arr = new PointArray([1, 2, 3, 4]).move(10, 10) + expect(arr.toArray()).toEqual([10, 10, 12, 12]) + }) + + it('does nothing if values not numbers', () => { + const arr = new PointArray([1, 2, 3, 4]).move() + expect(arr.toArray()).toEqual([1, 2, 3, 4]) + }) + }) + + describe('size()', () => { + it('correctly sizes the points over the whole area', () => { + var array = new PointArray([10, 10, 20, 20, 30, 30]) + expect(array.size(60, 60).valueOf()).toEqual([ + [10, 10], + [40, 40], + [70, 70] + ]) + }) + + it('let coordinates untouched when width/height is zero', () => { + var array = new PointArray([10, 10, 10, 20, 10, 30]) + expect(array.size(60, 60).valueOf()).toEqual([ + [10, 10], + [10, 40], + [10, 70] + ]) + + array = new PointArray([10, 10, 20, 10, 30, 10]) + expect(array.size(60, 60).valueOf()).toEqual([ + [10, 10], + [40, 10], + [70, 10] + ]) + }) + }) + + describe('toString()', () => { + it('converts to comma sepereated list', () => { + const square = new PointArray(squareString) + expect(square.toString()).toEqual(squareString) + }) + }) + + describe('toLine', () => { + it('returns an object which can be passed to a line as point attributes', () => { + const arr = new PointArray([1, 2, 3, 4]) + expect(arr.toLine()).toEqual({ x1: 1, y1: 2, x2: 3, y2: 4 }) + }) + }) + + describe('transform()', () => { + it('translates correctly', () => { + const square = new PointArray(squareString) + const translation = new Matrix({ translate: [2, 1] }) + const newSquare = square.transform(translation) + expect(newSquare.toString()).toEqual('2,1 3,1 3,2 2,2') + }) + + it('transforms like Point', () => { + const square = new PointArray(squareString) + const matrix = new Matrix(1, 2, 3, 4, 5, 6) + const newSquare = square.transform(matrix) + for (let i = 0; i < square.length; i++) { + const squarePoint = new Point(square[i]) + const newSquarePoint = new Point(newSquare[i]) + expect(squarePoint.transform(matrix)).toEqual(newSquarePoint) + } + }) + + it('works with transform object instead of matrix', () => { + const square = new PointArray(squareString) + const newSquare = square.transform({ translate: [2, 1] }) + expect(newSquare.toString()).toEqual('2,1 3,1 3,2 2,2') + }) + }) +}) diff --git a/spec/spec/types/SVGArray.js b/spec/spec/types/SVGArray.js new file mode 100644 index 000000000..35c4b8561 --- /dev/null +++ b/spec/spec/types/SVGArray.js @@ -0,0 +1,95 @@ +/* globals describe, expect, it, jasmine */ + +import { Array as SVGArray, PointArray, PathArray } from '../../../src/main.js' + +const { any } = jasmine + +describe('SVGArray.js', () => { + describe('()', () => { + it('preallocates memory if only number is passed', () => { + const arr = new SVGArray(1) + expect(arr.length).toBe(1) + }) + + it('parses a matrix array correctly to string', () => { + const array = new SVGArray([ + 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.0, 0.0, 1, -0 + ]) + + expect(array + '').toBe( + '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' + ) + }) + + it('parses space separated string and converts it to array', () => { + expect(new SVGArray('1 2 3 4').valueOf()).toEqual([1, 2, 3, 4]) + }) + + it('parses comma separated string and converts it to array', () => { + expect(new SVGArray('1,2,3,4').valueOf()).toEqual([1, 2, 3, 4]) + }) + }) + + describe('reverse()', () => { + it('reverses the array', () => { + const array = new SVGArray([1, 2, 3, 4, 5]).reverse() + expect(array.valueOf()).toEqual([5, 4, 3, 2, 1]) + }) + + it('returns itself', () => { + const array = new SVGArray() + expect(array.reverse()).toBe(array) + }) + }) + + describe('clone()', () => { + it('creates a shallow clone of the array', () => { + const array = new SVGArray([1, 2, 3, 4, 5]) + const clone = array.clone() + + expect(array).toEqual(clone) + expect(array).not.toBe(clone) + }) + + it('also works with PointArray (one depths clone)', () => { + const array = new PointArray([ + [1, 2], + [3, 4], + [5, 6] + ]) + const clone = array.clone() + + expect(array).toEqual(clone) + expect(array).not.toBe(clone) + + for (let i = array.length; i--; ) { + expect(array[i]).not.toBe(clone[i]) + } + }) + + it('also works with PathArray (one depths clone)', () => { + const array = new PathArray([ + ['M', 1, 2], + ['L', 3, 4], + ['L', 5, 6] + ]) + const clone = array.clone() + + expect(array).toEqual(clone) + expect(array).not.toBe(clone) + + for (let i = array.length; i--; ) { + expect(array[i]).not.toBe(clone[i]) + } + }) + }) + + describe('toSet()', () => { + it('creates a Set from the Array', () => { + const set = new SVGArray([1, 1, 2, 3]).toSet() + expect(set).toEqual(any(Set)) + expect(set).toEqual(new Set([1, 2, 3])) + }) + }) +}) diff --git a/spec/spec/types/SVGNumber.js b/spec/spec/types/SVGNumber.js new file mode 100644 index 000000000..200283356 --- /dev/null +++ b/spec/spec/types/SVGNumber.js @@ -0,0 +1,227 @@ +/* globals describe, expect, it, beforeEach, jasmine */ + +import { Number as SVGNumber } from '../../../src/main.js' + +const { any } = jasmine + +describe('Number.js', () => { + let number + + beforeEach(() => { + number = new SVGNumber() + }) + + describe('()', () => { + it('is zero', () => { + expect(number.value).toBe(0) + }) + + it('has a blank unit', () => { + expect(number.unit).toBe('') + }) + + it('accepts the unit as a second argument', () => { + number = new SVGNumber(30, '%') + expect(number.value).toBe(30) + expect(number.unit).toBe('%') + }) + + it('parses a pixel value', () => { + number = new SVGNumber('20px') + expect(number.value).toBe(20) + expect(number.unit).toBe('px') + }) + + it('parses a percent value', () => { + number = new SVGNumber('99%') + expect(number.value).toBe(0.99) + expect(number.unit).toBe('%') + }) + + it('parses a seconds value', () => { + number = new SVGNumber('2s') + expect(number.value).toBe(2000) + expect(number.unit).toBe('s') + }) + + it('parses a negative percent value', () => { + number = new SVGNumber('-89%') + expect(number.value).toBe(-0.89) + expect(number.unit).toBe('%') + }) + + it('falls back to 0 if given value is NaN', () => { + number = new SVGNumber(NaN) + expect(number.value).toBe(0) + }) + + it('falls back to maximum value if given number is positive infinite', () => { + // eslint-disable-next-line no-loss-of-precision + number = new SVGNumber(1.7976931348623157e10308) + expect(number.value).toBe(3.4e38) + }) + + it('falls back to minimum value if given number is negative infinite', () => { + // eslint-disable-next-line no-loss-of-precision + number = new SVGNumber(-1.7976931348623157e10308) + expect(number.value).toBe(-3.4e38) + }) + }) + + describe('toString()', () => { + it('converts the number to a string', () => { + expect(number.toString()).toBe('0') + }) + + it('appends the unit', () => { + number.value = 1.21 + number.unit = 'px' + expect(number.toString()).toBe('1.21px') + }) + + it('converts percent values properly', () => { + number.value = 1.36 + number.unit = '%' + expect(number.toString()).toBe('136%') + }) + + it('converts second values properly', () => { + number.value = 2500 + number.unit = 's' + expect(number.toString()).toBe('2.5s') + }) + }) + + describe('valueOf()', () => { + it('returns a numeric value for default units', () => { + expect(typeof number.valueOf()).toBe('number') + number = new SVGNumber('12') + expect(typeof number.valueOf()).toBe('number') + number = new SVGNumber(13) + expect(typeof number.valueOf()).toBe('number') + }) + + it('returns a numeric value for pixel units', () => { + number = new SVGNumber('10px') + expect(typeof number.valueOf()).toBe('number') + }) + + it('returns a numeric value for percent units', () => { + number = new SVGNumber('20%') + expect(typeof number.valueOf()).toBe('number') + }) + + it('converts to a primitive when multiplying', () => { + number.value = 80 + expect(number * 4).toBe(320) + }) + }) + + describe('plus()', () => { + it('returns a new instance', () => { + expect(number.plus(4.5)).not.toBe(number) + expect(number.plus(4.5)).toEqual(any(SVGNumber)) + }) + + it('adds a given number', () => { + expect(number.plus(3.5).valueOf()).toBe(3.5) + }) + + it('adds a given percentage value', () => { + expect(number.plus('225%').valueOf()).toBe(2.25) + }) + + it('adds a given pixel value', () => { + expect(number.plus('83px').valueOf()).toBe(83) + }) + + it('use the unit of this number as the unit of the returned number by default', () => { + expect(new SVGNumber('12s').plus('3%').unit).toBe('s') + }) + + it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => { + expect(number.plus('15%').unit).toBe('%') + }) + }) + + describe('minus()', () => { + it('subtracts a given number', () => { + expect(number.minus(3.7).valueOf()).toBe(-3.7) + }) + + it('subtracts a given percentage value', () => { + expect(number.minus('223%').valueOf()).toBe(-2.23) + }) + + it('subtracts a given pixel value', () => { + expect(number.minus('85px').valueOf()).toBe(-85) + }) + + it('use the unit of this number as the unit of the returned number by default', () => { + expect(new SVGNumber('12s').minus('3%').unit).toBe('s') + }) + + it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => { + expect(number.minus('15%').unit).toBe('%') + }) + }) + + describe('times()', () => { + beforeEach(() => { + number = number.plus(4) + }) + + it('multiplies with a given number', () => { + expect(number.times(3).valueOf()).toBe(12) + }) + + it('multiplies with a given percentage value', () => { + expect(number.times('110%').valueOf()).toBe(4.4) + }) + + it('multiplies with a given pixel value', () => { + expect(number.times('85px').valueOf()).toBe(340) + }) + + it('use the unit of this number as the unit of the returned number by default', () => { + expect(new SVGNumber('12s').times('3%').unit).toBe('s') + }) + + it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => { + expect(number.times('15%').unit).toBe('%') + }) + }) + + describe('divide()', () => { + beforeEach(() => { + number = number.plus(90) + }) + + it('divides by a given number', () => { + expect(number.divide(3).valueOf()).toBe(30) + }) + + it('divides by a given percentage value', () => { + expect(number.divide('3000%').valueOf()).toBe(3) + }) + + it('divides by a given pixel value', () => { + expect(number.divide('45px').valueOf()).toBe(2) + }) + + it('use the unit of this number as the unit of the returned number by default', () => { + expect(new SVGNumber('12s').divide('3%').unit).toBe('s') + }) + + it('use the unit of the passed number as the unit of the returned number when this number as no unit', () => { + expect(number.divide('15%').unit).toBe('%') + }) + }) + + describe('convert()', () => { + it('changes the unit of the number', () => { + const number = new SVGNumber('12px').convert('%') + expect(number.toString()).toBe('1200%') + }) + }) +}) diff --git a/spec/spec/use.js b/spec/spec/use.js deleted file mode 100644 index 0de5b39b2..000000000 --- a/spec/spec/use.js +++ /dev/null @@ -1,43 +0,0 @@ -describe('Use', function() { - var use - - describe('on a container element', function() { - var rect - - beforeEach(function() { - rect = draw.rect(100,100) - use = draw.use(rect) - }) - - it('creates an instance of SVG.Use', function() { - expect(use instanceof SVG.Use).toBe(true) - }) - - it('sets the target element id to its href attribute', function() { - expect(use.node.getAttributeNS(SVG.xlink, 'href')).toBe('#' + rect) - }) - - it('adopts the geometry of the target element', function() { - expect(use.bbox()).toEqual(rect.bbox()) - }) - }) - - describe('on an external path', function() { - var file = 'http://upload.wikimedia.org/wikipedia/commons/8/84/Example.svg' - , id = 'flowRoot1882' - - beforeEach(function() { - use = draw.use(id, file) - }) - - it('creates an instance of SVG.Use', function() { - expect(use instanceof SVG.Use).toBe(true) - }) - - it('sets the target element id and file path to its href attribute', function() { - expect(use.node.getAttributeNS(SVG.xlink, 'href')).toBe(file + '#' + id) - }) - - }) - -}) \ No newline at end of file diff --git a/spec/spec/utils.js b/spec/spec/utils.js deleted file mode 100644 index be8262ed7..000000000 --- a/spec/spec/utils.js +++ /dev/null @@ -1,10 +0,0 @@ -describe('SVG.utils', function() { - describe('degrees()', function() { - it('converts radiant to degrees', function() { - expect(SVG.utils.degrees(Math.PI)).toBe(180) - }) - it('maps to 0 - 360 degree only', function() { - expect(SVG.utils.degrees(2.5 * Math.PI)).toBe(90) - }) - }) -}) \ No newline at end of file diff --git a/spec/spec/utils/adopter.js b/spec/spec/utils/adopter.js new file mode 100644 index 000000000..b158be5b4 --- /dev/null +++ b/spec/spec/utils/adopter.js @@ -0,0 +1,283 @@ +/* globals describe, expect, it, beforeEach, afterEach, jasmine */ + +import { + create, + makeInstance, + nodeOrNew, + register, + getClass, + eid, + extend, + wrapWithAttrCheck, + Rect, + Element, + root, + G, + Gradient, + Dom, + Path, + Fragment +} from '../../../src/main.js' + +import { mockAdopt, assignNewId, adopt } from '../../../src/utils/adopter.js' +import { buildFixtures } from '../../helpers.js' +import { globals, getWindow } from '../../../src/utils/window.js' +import { svg } from '../../../src/modules/core/namespaces.js' + +const { any, createSpy, objectContaining } = jasmine + +describe('adopter.js', () => { + let Node + + beforeEach(() => { + Node = globals.window.Node + }) + + describe('create()', () => { + it('creates a node of the specified type', () => { + const rect = create('rect') + expect(rect).toEqual(any(Node)) + expect(rect.nodeName).toBe('rect') + }) + }) + + describe('makeInstance()', () => { + const adoptSpy = createSpy('adopt', adopt).and.callThrough() + + beforeEach(() => { + adoptSpy.calls.reset() + mockAdopt(adoptSpy) + }) + + afterEach(() => { + mockAdopt() + }) + + it('creates a root-object when no argument given', () => { + const doc = makeInstance() + + expect(doc).toEqual(any(getClass(root))) + expect(doc).toEqual(any(Element)) + }) + + it('returns a given svg.js object directly', () => { + const rect = new Rect() + const samerect = makeInstance(rect) + expect(rect).toBe(samerect) + }) + + it('creates an element from passed svg string', () => { + const rect = makeInstance('') + + expect(adoptSpy).toHaveBeenCalledWith(any(Node)) + expect(adoptSpy).toHaveBeenCalledWith( + objectContaining({ nodeName: 'rect' }) + ) + expect(rect).toEqual(any(Rect)) + expect(rect.parent()).toBe(null) + }) + + it('creates an element in the html namespace from passed html string', () => { + const div = makeInstance('
', true) + + expect(adoptSpy).toHaveBeenCalledWith(any(Node)) + expect(adoptSpy).toHaveBeenCalledWith( + objectContaining({ + nodeName: 'DIV', + namespaceURI: 'http://www.w3.org/1999/xhtml' + }) + ) + expect(div).toEqual(any(Dom)) + expect(div.parent()).toBe(null) + }) + + it('does not have its wrapper attached', () => { + const rect = makeInstance('') + expect(rect.parent()).toBe(null) + }) + + it('searches for element in dom if selector given', () => { + buildFixtures() + + const path = globals.window.document.getElementById('lineAB') + + const pathInst = makeInstance('#lineAB') + const noEl = makeInstance('#doesNotExist') + + expect(adoptSpy).toHaveBeenCalledWith(path) + expect(adoptSpy).toHaveBeenCalledWith(null) + expect(pathInst).toEqual(any(Path)) + expect(noEl).toBe(null) + }) + + it('calls adopt when passed a node', () => { + const rect = makeInstance(create('rect')) + + expect(adoptSpy).toHaveBeenCalledWith(any(Node)) + expect(adoptSpy).toHaveBeenCalledWith( + objectContaining({ nodeName: 'rect' }) + ) + expect(rect).toEqual(any(Rect)) + }) + }) + + describe('adopt()', () => { + it('returns null of passed node is null', () => { + expect(adopt(null)).toBe(null) + }) + + it('returns instance from node if present', () => { + const rect = new Rect() + expect(adopt(rect.node)).toBe(rect) + }) + + it('creates Fragment when document fragment is passed', () => { + const frag = getWindow().document.createDocumentFragment() + expect(adopt(frag)).toEqual(any(Fragment)) + }) + + it('creates instance when node without instance is passed', () => { + const rect = new Rect() + const node = rect.node + delete node.instance + expect(adopt(node)).toEqual(any(Rect)) + expect(adopt(node)).not.toBe(rect) + }) + + it('creates instance when node without instance is passed with gradients', () => { + const gradient = new Gradient('linear') + const node = gradient.node + delete node.instance + expect(adopt(node)).toEqual(any(Gradient)) + expect(adopt(node).type).toBe('linearGradient') + expect(adopt(node)).not.toBe(gradient) + }) + + it('creates Dom instances for unknown nodes', () => { + const div = getWindow().document.createElement('div') + expect(adopt(div)).toEqual(any(Dom)) + }) + }) + + describe('nodeOrNew()', () => { + it('creates a node of node argument is null', () => { + const rect = nodeOrNew('rect', null) + expect(rect).toEqual(any(Node)) + expect(rect.nodeName).toBe('rect') + }) + + it('returns the node if one is passed', () => { + const div = globals.window.document.createElement('div') + const node = nodeOrNew('something', div) + + // jasmine chucks on this when using the node directly + expect(node.outerHTML).toBe(div.outerHTML) + }) + + it('gracefully handles nodes that are not yet imported into the document', () => { + const otherDoc = globals.document.implementation.createDocument( + svg, + 'svg' + ) + const rect = otherDoc.createElementNS(svg, 'rect') + + const node = nodeOrNew('rect', rect) + + expect(node).toEqual(rect) + }) + }) + + describe('register()/getClass()', () => { + it('sets and gets a class from the class register', () => { + const A = class {} + register(A) + expect(getClass('A')).toBe(A) + }) + }) + + describe('eid()', () => { + it('returns a unique id', () => { + expect(eid('foo')).not.toBe(eid('foo')) + }) + }) + + describe('assignNewId()', () => { + it('assigns a new id if id is present on element', () => { + const rect = new Rect().id('foo') + assignNewId(rect.node) + expect(rect.id()).not.toBe('foo') + }) + + it('does not set id if no id is present on element', () => { + const rect = new Rect() + assignNewId(rect.node) + expect(rect.attr('id')).toBe(undefined) + }) + + it('recursively sets new ids on children', () => { + const group = new G().id('foo') + const rect = group.rect(100, 100).id('bar') + assignNewId(group.node) + expect(group.id()).not.toBe('foo') + expect(rect.id()).not.toBe('bar') + }) + }) + + describe('extend()', () => { + it('adds all functions in the given object to the target object', () => { + const A = class {} + + extend(A, { + test() { + this.prop = 'test' + return this + } + }) + + expect(typeof A.prototype.test).toBe('function') + expect(new A().test().prop).toBe('test') + }) + + it('accepts and extend multiple modules at once', () => { + const A = class {} + const B = class {} + const C = class {} + + extend([A, B, C], { + test() { + this.prop = 'test' + return this + } + }) + + expect(typeof A.prototype.test).toBe('function') + expect(new A().test().prop).toBe('test') + expect(typeof B.prototype.test).toBe('function') + expect(new B().test().prop).toBe('test') + expect(typeof C.prototype.test).toBe('function') + expect(new C().test().prop).toBe('test') + }) + }) + + describe('wrapWithAttrCheck()', () => { + it('wraps a function so that it calls an attr function if an object is passed', () => { + const attrSpy = createSpy('attr') + + const A = class {} + extend(A, { + test: wrapWithAttrCheck(function () { + this.prop = 'test' + return this + }), + attr: attrSpy + }) + + const obj = {} + + expect(new A().test().prop).toBe('test') + expect(attrSpy).not.toHaveBeenCalled() + new A().test(obj) + expect(attrSpy).toHaveBeenCalledWith(obj) + }) + }) +}) diff --git a/spec/spec/utils/methods.js b/spec/spec/utils/methods.js new file mode 100644 index 000000000..457a07841 --- /dev/null +++ b/spec/spec/utils/methods.js @@ -0,0 +1,34 @@ +/* globals describe, expect, it */ + +import { + registerMethods, + getMethodsFor, + getMethodNames +} from '../../../src/utils/methods.js' + +describe('methods.js', () => { + describe('registerMethods() / getMethodsFor() / addMethodNames / getMethodNames()', () => { + it('adds methods for a given type of classes with object given', () => { + const foo = { + func1: () => {} + } + registerMethods({ foo }) + + expect(getMethodsFor('foo')).toEqual(foo) + }) + + it('adds methods for a given type of classes with 2 parameters given', () => { + const foo = { + func1: () => {} + } + registerMethods('foo', foo) + + expect(getMethodsFor('foo')).toEqual(foo) + }) + + it('adds a method name', () => { + registerMethods({ bar: { func2: () => {} } }) + expect(getMethodNames()).toContain('func2') + }) + }) +}) diff --git a/spec/spec/utils/pathParser.js b/spec/spec/utils/pathParser.js new file mode 100644 index 000000000..fdf10c7c8 --- /dev/null +++ b/spec/spec/utils/pathParser.js @@ -0,0 +1,170 @@ +/* globals describe expect it */ + +import { pathParser } from '../../../src/utils/pathParser.js' + +describe('pathParser.js', () => { + describe('pathParser()', () => { + it('parses all paths correctly', () => { + expect(pathParser('M2,0a2 2 0 00-2 2a2 2 0 002 2a.5.5 0 011 0z')).toEqual( + [ + ['M', 2, 0], + ['A', 2, 2, 0, 0, 0, 0, 2], + ['A', 2, 2, 0, 0, 0, 2, 4], + ['A', 0.5, 0.5, 0, 0, 1, 3, 4], + ['Z'] + ] + ) + + expect(pathParser('M2,0a2 2 0 00-2 2a2 2 0 002 2a.5.5 0 111 0z')).toEqual( + [ + ['M', 2, 0], + ['A', 2, 2, 0, 0, 0, 0, 2], + ['A', 2, 2, 0, 0, 0, 2, 4], + ['A', 0.5, 0.5, 0, 1, 1, 3, 4], + ['Z'] + ] + ) + + expect(pathParser('m10 10 h 80 v 80 h -80 l 300 400 z')).toEqual([ + ['M', 10, 10], + ['H', 90], + ['V', 90], + ['H', 10], + ['L', 310, 490], + ['Z'] + ]) + + expect( + pathParser( + 'm10 80 c 40 10 65 10 95 80 s 150 150 180 80 t 300 300 q 52 10 95 80 z' + ) + ).toEqual([ + ['M', 10, 80], + ['C', 50, 90, 75, 90, 105, 160], + ['S', 255, 310, 285, 240], + ['T', 585, 540], + ['Q', 637, 550, 680, 620], + ['Z'] + ]) + + expect(pathParser('m80 80 A 45 45, 0, 0, 0, 125 125 L 125 80 z')).toEqual( + [['M', 80, 80], ['A', 45, 45, 0, 0, 0, 125, 125], ['L', 125, 80], ['Z']] + ) + + expect( + pathParser( + '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' + ) + ).toEqual([ + ['M', 215.458, 245.23], + ['C', 215.458, 245.23, 292.861, 245.23, 309.73199999999997, 245.23], + ['S', 405, 216.451, 405, 138.054], + ['S', 329.581, 15, 287.9, 15], + [ + 'C', + 246.21999999999997, + 15, + 147.97599999999997, + 15, + 117.21199999999999, + 15 + ], + ['C', 86.45, 15, 15, 60.65, 15, 134.084], + ['C', 15, 207.518, 111.259, 246.221, 129.122, 246.221], + ['C', 146.984, 246.221, 215.458, 245.23, 215.458, 245.23], + ['Z'] + ]) + + expect( + pathParser('M10 10-45-30.5.5 .89L2e-2.5.5-.5C.5.5.5.5.5.5L-3-4z') + ).toEqual([ + ['M', 10, 10], + ['L', -45, -30.5], + ['L', 0.5, 0.89], + ['L', 0.02, 0.5], + ['L', 0.5, -0.5], + ['C', 0.5, 0.5, 0.5, 0.5, 0.5, 0.5], + ['L', -3, -4], + ['Z'] + ]) + + expect( + pathParser( + 'm 0,0 0,3189 2209,0 0,-3189 -2209,0 z m 154,154 1901,0 0,2881 -1901,0 0,-2881 z' + ) + ).toEqual([ + ['M', 0, 0], + ['L', 0, 3189], + ['L', 2209, 3189], + ['L', 2209, 0], + ['L', 0, 0], + ['Z'], + ['M', 154, 154], + ['L', 2055, 154], + ['L', 2055, 3035], + ['L', 154, 3035], + ['L', 154, 154], + ['Z'] + ]) + + expect(pathParser('m 0,0 a 45 45, 0, 0, 0, 125 125')).toEqual([ + ['M', 0, 0], + ['A', 45, 45, 0, 0, 0, 125, 125] + ]) + + expect(pathParser('M10 10 80 80 30 30 Z')).toEqual([ + ['M', 10, 10], + ['L', 80, 80], + ['L', 30, 30], + ['Z'] + ]) + + expect(pathParser('M10 10L.5.5.3.3Z')).toEqual([ + ['M', 10, 10], + ['L', 0.5, 0.5], + ['L', 0.3, 0.3], + ['Z'] + ]) + + // "a" commands without optional whitespace around the flag params and ending coordinate pair + expect(pathParser('a32 32 0 00.03-45.22', false)).toEqual([ + ['a', 32.0, 32.0, 0.0, 0.0, 0.0, 0.03, -45.22] + ]) + + expect(pathParser('a48 48 0 1148-48', false)).toEqual([ + ['a', 48.0, 48.0, 0.0, 1.0, 1.0, 48.0, -48.0] + ]) + + expect(pathParser('a82.6 82.6 0 0033.48-20.25', false)).toEqual([ + ['a', 82.6, 82.6, 0.0, 0.0, 0.0, 33.48, -20.25] + ]) + + expect(pathParser('a82.45 82.45 0 00-20.24 33.47', false)).toEqual([ + ['a', 82.45, 82.45, 0.0, 0.0, 0.0, -20.24, 33.47] + ]) + + expect(pathParser('a2.51 2.51 0 01.25.32', false)).toEqual([ + ['a', 2.51, 2.51, 0, 0, 1, 0.25, 0.32] + ]) + + expect(pathParser('a2.51 2.51 0 00.25.32', false)).toEqual([ + ['a', 2.51, 2.51, 0, 0, 0, 0.25, 0.32] + ]) + + expect(pathParser('a2.51 2.51 0 000.25.32', false)).toEqual([ + ['a', 2.51, 2.51, 0, 0, 0, 0.25, 0.32] + ]) + + expect(pathParser('a48 48 0 1148-48 48 48 0 01-48 48', false)).toEqual([ + ['a', 48.0, 48.0, 0.0, 1.0, 1.0, 48.0, -48.0], + ['a', 48.0, 48.0, 0.0, 0.0, 1.0, -48.0, 48.0] + ]) + + expect(pathParser('M0+0 L100+0 L50+100')).toEqual([ + ['M', 0, 0], + ['L', 100, 0], + ['L', 50, 100] + ]) + }) + }) +}) diff --git a/spec/spec/utils/utils.js b/spec/spec/utils/utils.js new file mode 100644 index 000000000..314c75784 --- /dev/null +++ b/spec/spec/utils/utils.js @@ -0,0 +1,189 @@ +/* globals describe, expect, it, beforeEach, jasmine */ + +import { + map, + filter, + radians, + degrees, + unCamelCase, + capitalize, + proportionalSize, + getOrigin +} from '../../../src/utils/utils.js' + +const { any } = jasmine + +describe('utils.js', function () { + describe('map()', function () { + var arr1 + var arr2 + + beforeEach(function () { + arr1 = [1, 2, 3, 4] + arr2 = map(arr1, function (el) { + return el * 2 + }) + }) + + it('returns a new array', function () { + expect(arr2).toEqual(any(Array)) + expect(arr2).not.toBe(arr1) + }) + + it('executes a function on every element and returns the result in a new array', function () { + expect(arr2).toEqual([2, 4, 6, 8]) + }) + }) + + describe('filter()', function () { + var arr1 + var arr2 + + beforeEach(function () { + arr1 = [1, 2, 3, 4] + arr2 = filter(arr1, function (el) { + return el % 2 === 0 + }) + }) + + it('returns a new array', function () { + expect(arr2).toEqual(any(Array)) + expect(arr2).not.toBe(arr1) + }) + + it('filters elements by function', function () { + expect(arr2).toEqual([2, 4]) + }) + }) + + describe('radians()', function () { + it('converts degrees to radians', function () { + expect(radians(270)).toBe(1.5 * Math.PI) + expect(radians(90)).toBe(Math.PI / 2) + }) + + it('caps at 360 degrees', function () { + expect(radians(360)).toBe(0) + expect(radians(360 + 180)).toBe(Math.PI) + }) + }) + + describe('degrees()', function () { + it('converts radians to degrees', function () { + expect(degrees(1.5 * Math.PI)).toBe(270) + expect(degrees(Math.PI / 2)).toBe(90) + }) + + it('caps at 2 PI', function () { + expect(degrees(2 * Math.PI)).toBe(0) + expect(degrees(3 * Math.PI)).toBe(180) + }) + }) + + describe('unCamelCase()', function () { + it('converts camelCase to dash-case', function () { + var dash1 = 'dash-1' + var dashTwo = 'dash-two' + var camelOne = 'camelOne' + var pascalOne = 'PascalOne' + + expect(unCamelCase(dash1)).toBe('dash-1') + expect(unCamelCase(dashTwo)).toBe('dash-two') + expect(unCamelCase(camelOne)).toBe('camel-one') + expect(unCamelCase(pascalOne)).toBe('-pascal-one') + }) + }) + + describe('capitalize()', function () { + it('capitalizes the first letter', function () { + var dash1 = 'dash-1' + var dashTwo = 'dash-two' + var camelOne = 'camelOne' + var pascalOne = 'PascalOne' + + expect(capitalize(dash1)).toBe('Dash-1') + expect(capitalize(dashTwo)).toBe('Dash-two') + expect(capitalize(camelOne)).toBe('CamelOne') + expect(capitalize(pascalOne)).toBe('PascalOne') + }) + }) + + describe('proportionalSize()', function () { + var box = { width: 150, height: 100 } + var el = { bbox: () => ({ width: 200, height: 100 }) } + + it('calculates height proportionally', function () { + expect(proportionalSize(el, 400, null)).toEqual({ + width: 400, + height: 200 + }) + }) + + it('calculates width proportionally', function () { + expect(proportionalSize(el, null, 200)).toEqual({ + width: 400, + height: 200 + }) + }) + + it('prefers passed box over element', function () { + expect(proportionalSize(el, 300, null, box)).toEqual({ + width: 300, + height: 200 + }) + expect(proportionalSize(el, null, 200, box)).toEqual({ + width: 300, + height: 200 + }) + }) + }) + + describe('getOrigin()', function () { + var el = { bbox: () => ({ width: 200, height: 100, x: 300, y: 400 }) } + + it('gets the origin from [ox, oy]', function () { + var origin = { origin: [10, 20] } + expect(getOrigin(origin, el)).toEqual([10, 20]) + }) + + it('gets the origin from [ox, oy] as strings', function () { + var origin = { origin: ['center', 'top'] } + expect(getOrigin(origin, el)).toEqual([400, 400]) + }) + + it('gets the origin from {x, y}', function () { + var origin = { origin: { x: 10, y: 20 } } + expect(getOrigin(origin, el)).toEqual([10, 20]) + }) + + it('gets the origin from {ox, oy}', function () { + var origin = { ox: 10, oy: 20 } + expect(getOrigin(origin, el)).toEqual([10, 20]) + }) + + it('gets the origin from {ox, oy} as strings', function () { + var origin = { ox: 'center', oy: 'top' } + expect(getOrigin(origin, el)).toEqual([400, 400]) + }) + + it('gets the origin from {originX, originY}', function () { + var origin = { originX: 10, originY: 20 } + expect(getOrigin(origin, el)).toEqual([10, 20]) + }) + + it('gets the origin from {originX, originY} as strings', function () { + var origin = { originX: 'center', originY: 'top' } + expect(getOrigin(origin, el)).toEqual([400, 400]) + }) + + it('gets the origin from string', function () { + var origin = { origin: 'center top' } + expect(getOrigin(origin, el)).toEqual([400, 400]) + }) + + it('gets the origin from number', function () { + var origin = { origin: 5 } + expect(getOrigin(origin, el)).toEqual([5, 5]) + }) + }) +}) diff --git a/spec/spec/utils/window.js b/spec/spec/utils/window.js new file mode 100644 index 000000000..e68149189 --- /dev/null +++ b/spec/spec/utils/window.js @@ -0,0 +1,43 @@ +/* globals describe, expect, it */ + +import { + registerWindow, + globals, + withWindow, + getWindow, + saveWindow, + restoreWindow +} from '../../../src/utils/window.js' + +describe('window.js', () => { + describe('registerWindow()', () => { + it('sets a new window as global', () => { + saveWindow() + const win = {} + const doc = {} + registerWindow(win, doc) + expect(globals.window).toBe(win) + expect(globals.document).toBe(doc) + restoreWindow() // we need this or jasmine will fail in afterAll + }) + }) + + describe('withWindow()', () => { + it('runs a function in the specified window context', () => { + const win = { foo: 'bar', document: {} } + const oldWindow = globals.window + expect(globals.window).not.toBe(win) + withWindow({ foo: 'bar', document: {} }, () => { + expect(globals.window).toEqual(win) + expect(globals.document).toEqual(win.document) + }) + expect(globals.window).toBe(oldWindow) + }) + }) + + describe('getWindow()', () => { + it('returns the registered window', () => { + expect(getWindow()).toBe(globals.window) + }) + }) +}) diff --git a/spec/spec/viewbox.js b/spec/spec/viewbox.js deleted file mode 100644 index cf6ec5c2e..000000000 --- a/spec/spec/viewbox.js +++ /dev/null @@ -1,162 +0,0 @@ -describe('Viewbox', function() { - var viewbox - - beforeEach(function() { - draw.clear() - }) - - describe('initialization', function() { - - - it('creates a new viewbox with default values', function() { - viewbox = new SVG.ViewBox() - - expect(viewbox.x).toBe(0) - expect(viewbox.y).toBe(0) - expect(viewbox.width).toBe(0) - expect(viewbox.height).toBe(0) - }) - - - - it('creates a new viewbox from parsed string', function() { - viewbox = new SVG.ViewBox('10. 100 200 300') - - expect(viewbox.x).toBe(10) - expect(viewbox.y).toBe(100) - expect(viewbox.width).toBe(200) - expect(viewbox.height).toBe(300) - }) - - - - it('creates a new viewbox from array', function() { - viewbox = new SVG.ViewBox([10, 100, 200, 300]) - - expect(viewbox.x).toBe(10) - expect(viewbox.y).toBe(100) - expect(viewbox.width).toBe(200) - expect(viewbox.height).toBe(300) - }) - - - - it('creates a new viewbox from object', function() { - viewbox = new SVG.ViewBox({x:10, y:100, width:200, height:300}) - - expect(viewbox.x).toBe(10) - expect(viewbox.y).toBe(100) - expect(viewbox.width).toBe(200) - expect(viewbox.height).toBe(300) - }) - - - - it('creates a new viewbox from 4 arguments given', function() { - viewbox = new SVG.ViewBox(10, 100, 200, 300) - - expect(viewbox.x).toBe(10) - expect(viewbox.y).toBe(100) - expect(viewbox.width).toBe(200) - expect(viewbox.height).toBe(300) - }) - - - it('creates a new viewbox from parsed string with exponential values', function() { - viewbox = new SVG.ViewBox('-1.12e1 1e-2 +2e2 +.3e+4') - - expect(viewbox.x).toBe(-11.2) - expect(viewbox.y).toBe(0.01) - expect(viewbox.width).toBe(200) - expect(viewbox.height).toBe(3000) - }) - - it('creates a new viewbox with element given', function() { - draw.attr('viewBox', '-1.12e1 1e-2 +2e2 +.3e+4') - viewbox = new SVG.ViewBox(draw) - - expect(viewbox.x).toBe(-11.2) - expect(viewbox.y).toBe(0.01) - expect(viewbox.width).toBe(200) - expect(viewbox.height).toBe(3000) - }) - - }) - - - describe('viewbox()', function() { - - beforeEach(function() { - draw.attr('viewBox', null) - }) - afterEach(function() { - draw.attr('viewBox', null) - }) - - it('should set the viewbox when four arguments are provided', function() { - draw.viewbox(0,0,100,100) - expect(draw.node.getAttribute('viewBox')).toBe('0 0 100 100') - }) - it('should set the viewbox when an object is provided as first argument', function() { - draw.viewbox({ x: 0, y: 0, width: 50, height: 50 }) - expect(draw.node.getAttribute('viewBox')).toBe('0 0 50 50') - }) - it('should set the viewbox when a string is provided as first argument', function() { - draw.viewbox('0 0 50 50') - expect(draw.node.getAttribute('viewBox')).toBe('0 0 50 50') - }) - it('should set the viewbox when an array is provided as first argument', function() { - draw.viewbox([0, 0, 50, 50]) - expect(draw.node.getAttribute('viewBox')).toBe('0 0 50 50') - }) - it('should accept negative values', function() { - draw.size(100,100).viewbox(-100, -100, 50, 50) - expect(draw.node.getAttribute('viewBox')).toEqual('-100 -100 50 50') - }) - it('should get the viewbox if no arguments are given', function() { - draw.viewbox(0, 0, 100, 100) - expect(draw.viewbox()).toEqual(new SVG.ViewBox(draw)) - }) - it('should define the zoom of the viewbox in relation to the canvas size', function() { - draw.size(100,100).viewbox(0,0,50,50) - expect(draw.viewbox().zoom).toEqual(100 / 50) - }) - - }) - - describe('morph()', function() { - it('stores a given viewbox for morphing', function() { - var viewbox1 = new SVG.ViewBox(10, 100, 200, 300) - , viewbox2 = new SVG.ViewBox(50, -100, 300, 300) - - viewbox1.morph(viewbox2) - - expect(viewbox1.destination).toEqual(viewbox2) - }) - it('stores a clone, not the given viewbox itself', function() { - var viewbox1 = new SVG.ViewBox(10, 100, 200, 300) - , viewbox2 = new SVG.ViewBox(50, -100, 300, 300) - - viewbox1.morph(viewbox2) - - expect(viewbox1.destination).not.toBe(viewbox2) - }) - }) - - describe('at()', function() { - it('returns a morphed viewbox at a given position', function() { - var viewbox1 = new SVG.ViewBox(10, 100, 200, 300) - , viewbox2 = new SVG.ViewBox(50, -100, 300, 300) - , viewbox3 = viewbox1.morph(viewbox2).at(0.5) - - expect(viewbox1.toString()).toBe('10 100 200 300') - expect(viewbox2.toString()).toBe('50 -100 300 300') - expect(viewbox3.toString()).toBe('30 0 250 300') - }) - it('returns itself when no destination given', function() { - var viewbox = new SVG.ViewBox(10, 100, 200, 300) - expect(viewbox.at(0.5)).toBe(viewbox) - }) - }) - -}) \ No newline at end of file diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json deleted file mode 100644 index c7845fc98..000000000 --- a/spec/support/jasmine.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "spec_dir": "spec/spec", - "spec_files": [ - "!(helpers).js" - ], - "helpers": [ - "helpers.js" - ] -} diff --git a/src/animation/Animator.js b/src/animation/Animator.js new file mode 100644 index 000000000..11cca546f --- /dev/null +++ b/src/animation/Animator.js @@ -0,0 +1,102 @@ +import { globals } from '../utils/window.js' +import Queue from './Queue.js' + +const Animator = { + nextDraw: null, + frames: new Queue(), + timeouts: new Queue(), + immediates: new Queue(), + timer: () => globals.window.performance || globals.window.Date, + transforms: [], + + frame(fn) { + // Store the node + const node = Animator.frames.push({ run: fn }) + + // Request an animation frame if we don't have one + if (Animator.nextDraw === null) { + Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw) + } + + // Return the node so we can remove it easily + return node + }, + + timeout(fn, delay) { + delay = delay || 0 + + // Work out when the event should fire + const time = Animator.timer().now() + delay + + // Add the timeout to the end of the queue + const node = Animator.timeouts.push({ run: fn, time: time }) + + // Request another animation frame if we need one + if (Animator.nextDraw === null) { + Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw) + } + + return node + }, + + immediate(fn) { + // Add the immediate fn to the end of the queue + const node = Animator.immediates.push(fn) + // Request another animation frame if we need one + if (Animator.nextDraw === null) { + Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw) + } + + return node + }, + + cancelFrame(node) { + node != null && Animator.frames.remove(node) + }, + + clearTimeout(node) { + node != null && Animator.timeouts.remove(node) + }, + + cancelImmediate(node) { + node != null && Animator.immediates.remove(node) + }, + + _draw(now) { + // Run all the timeouts we can run, if they are not ready yet, add them + // to the end of the queue immediately! (bad timeouts!!! [sarcasm]) + let nextTimeout = null + const lastTimeout = Animator.timeouts.last() + while ((nextTimeout = Animator.timeouts.shift())) { + // Run the timeout if its time, or push it to the end + if (now >= nextTimeout.time) { + nextTimeout.run() + } else { + Animator.timeouts.push(nextTimeout) + } + + // If we hit the last item, we should stop shifting out more items + if (nextTimeout === lastTimeout) break + } + + // Run all of the animation frames + let nextFrame = null + const lastFrame = Animator.frames.last() + while (nextFrame !== lastFrame && (nextFrame = Animator.frames.shift())) { + nextFrame.run(now) + } + + let nextImmediate = null + while ((nextImmediate = Animator.immediates.shift())) { + nextImmediate() + } + + // If we have remaining timeouts or frames, draw until we don't anymore + Animator.nextDraw = + Animator.timeouts.first() || Animator.frames.first() + ? globals.window.requestAnimationFrame(Animator._draw) + : null + } +} + +export default Animator diff --git a/src/animation/Controller.js b/src/animation/Controller.js new file mode 100644 index 000000000..1cf879d8a --- /dev/null +++ b/src/animation/Controller.js @@ -0,0 +1,231 @@ +import { timeline } from '../modules/core/defaults.js' +import { extend } from '../utils/adopter.js' + +/*** +Base Class +========== +The base stepper class that will be +***/ + +function makeSetterGetter(k, f) { + return function (v) { + if (v == null) return this[k] + this[k] = v + if (f) f.call(this) + return this + } +} + +export const easing = { + '-': function (pos) { + return pos + }, + '<>': function (pos) { + return -Math.cos(pos * Math.PI) / 2 + 0.5 + }, + '>': function (pos) { + return Math.sin((pos * Math.PI) / 2) + }, + '<': function (pos) { + return -Math.cos((pos * Math.PI) / 2) + 1 + }, + bezier: function (x1, y1, x2, y2) { + // see https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo + return function (t) { + if (t < 0) { + if (x1 > 0) { + return (y1 / x1) * t + } else if (x2 > 0) { + return (y2 / x2) * t + } else { + return 0 + } + } else if (t > 1) { + if (x2 < 1) { + return ((1 - y2) / (1 - x2)) * t + (y2 - x2) / (1 - x2) + } else if (x1 < 1) { + return ((1 - y1) / (1 - x1)) * t + (y1 - x1) / (1 - x1) + } else { + return 1 + } + } else { + return 3 * t * (1 - t) ** 2 * y1 + 3 * t ** 2 * (1 - t) * y2 + t ** 3 + } + } + }, + // see https://www.w3.org/TR/css-easing-1/#step-timing-function-algo + steps: function (steps, stepPosition = 'end') { + // deal with "jump-" prefix + stepPosition = stepPosition.split('-').reverse()[0] + + let jumps = steps + if (stepPosition === 'none') { + --jumps + } else if (stepPosition === 'both') { + ++jumps + } + + // The beforeFlag is essentially useless + return (t, beforeFlag = false) => { + // Step is called currentStep in referenced url + let step = Math.floor(t * steps) + const jumping = (t * step) % 1 === 0 + + if (stepPosition === 'start' || stepPosition === 'both') { + ++step + } + + if (beforeFlag && jumping) { + --step + } + + if (t >= 0 && step < 0) { + step = 0 + } + + if (t <= 1 && step > jumps) { + step = jumps + } + + return step / jumps + } + } +} + +export class Stepper { + done() { + return false + } +} + +/*** +Easing Functions +================ +***/ + +export class Ease extends Stepper { + constructor(fn = timeline.ease) { + super() + this.ease = easing[fn] || fn + } + + step(from, to, pos) { + if (typeof from !== 'number') { + return pos < 1 ? from : to + } + return from + (to - from) * this.ease(pos) + } +} + +/*** +Controller Types +================ +***/ + +export class Controller extends Stepper { + constructor(fn) { + super() + this.stepper = fn + } + + done(c) { + return c.done + } + + step(current, target, dt, c) { + return this.stepper(current, target, dt, c) + } +} + +function recalculate() { + // Apply the default parameters + const duration = (this._duration || 500) / 1000 + const overshoot = this._overshoot || 0 + + // Calculate the PID natural response + const eps = 1e-10 + const pi = Math.PI + const os = Math.log(overshoot / 100 + eps) + const zeta = -os / Math.sqrt(pi * pi + os * os) + const wn = 3.9 / (zeta * duration) + + // Calculate the Spring values + this.d = 2 * zeta * wn + this.k = wn * wn +} + +export class Spring extends Controller { + constructor(duration = 500, overshoot = 0) { + super() + this.duration(duration).overshoot(overshoot) + } + + step(current, target, dt, c) { + if (typeof current === 'string') return current + c.done = dt === Infinity + if (dt === Infinity) return target + if (dt === 0) return current + + if (dt > 100) dt = 16 + + dt /= 1000 + + // Get the previous velocity + const velocity = c.velocity || 0 + + // Apply the control to get the new position and store it + const acceleration = -this.d * velocity - this.k * (current - target) + const newPosition = current + velocity * dt + (acceleration * dt * dt) / 2 + + // Store the velocity + c.velocity = velocity + acceleration * dt + + // Figure out if we have converged, and if so, pass the value + c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002 + return c.done ? target : newPosition + } +} + +extend(Spring, { + duration: makeSetterGetter('_duration', recalculate), + overshoot: makeSetterGetter('_overshoot', recalculate) +}) + +export class PID extends Controller { + constructor(p = 0.1, i = 0.01, d = 0, windup = 1000) { + super() + this.p(p).i(i).d(d).windup(windup) + } + + step(current, target, dt, c) { + if (typeof current === 'string') return current + c.done = dt === Infinity + + if (dt === Infinity) return target + if (dt === 0) return current + + const p = target - current + let i = (c.integral || 0) + p * dt + const d = (p - (c.error || 0)) / dt + const windup = this._windup + + // antiwindup + if (windup !== false) { + i = Math.max(-windup, Math.min(i, windup)) + } + + c.error = p + c.integral = i + + c.done = Math.abs(p) < 0.001 + + return c.done ? target : current + (this.P * p + this.I * i + this.D * d) + } +} + +extend(PID, { + windup: makeSetterGetter('_windup'), + p: makeSetterGetter('P'), + i: makeSetterGetter('I'), + d: makeSetterGetter('D') +}) diff --git a/src/animation/Morphable.js b/src/animation/Morphable.js new file mode 100644 index 000000000..9ce05d629 --- /dev/null +++ b/src/animation/Morphable.js @@ -0,0 +1,336 @@ +import { Ease } from './Controller.js' +import { + delimiter, + numberAndUnit, + isPathLetter +} from '../modules/core/regex.js' +import { extend } from '../utils/adopter.js' +import Color from '../types/Color.js' +import PathArray from '../types/PathArray.js' +import SVGArray from '../types/SVGArray.js' +import SVGNumber from '../types/SVGNumber.js' + +const getClassForType = (value) => { + const type = typeof value + + if (type === 'number') { + return SVGNumber + } else if (type === 'string') { + if (Color.isColor(value)) { + return Color + } else if (delimiter.test(value)) { + return isPathLetter.test(value) ? PathArray : SVGArray + } else if (numberAndUnit.test(value)) { + return SVGNumber + } else { + return NonMorphable + } + } else if (morphableTypes.indexOf(value.constructor) > -1) { + return value.constructor + } else if (Array.isArray(value)) { + return SVGArray + } else if (type === 'object') { + return ObjectBag + } else { + return NonMorphable + } +} + +export default class Morphable { + constructor(stepper) { + this._stepper = stepper || new Ease('-') + + this._from = null + this._to = null + this._type = null + this._context = null + this._morphObj = null + } + + at(pos) { + return this._morphObj.morph( + this._from, + this._to, + pos, + this._stepper, + this._context + ) + } + + done() { + const complete = this._context.map(this._stepper.done).reduce(function ( + last, + curr + ) { + return last && curr + }, true) + return complete + } + + from(val) { + if (val == null) { + return this._from + } + + this._from = this._set(val) + return this + } + + stepper(stepper) { + if (stepper == null) return this._stepper + this._stepper = stepper + return this + } + + to(val) { + if (val == null) { + return this._to + } + + this._to = this._set(val) + return this + } + + type(type) { + // getter + if (type == null) { + return this._type + } + + // setter + this._type = type + return this + } + + _set(value) { + if (!this._type) { + this.type(getClassForType(value)) + } + + let result = new this._type(value) + if (this._type === Color) { + result = this._to + ? result[this._to[4]]() + : this._from + ? result[this._from[4]]() + : result + } + + if (this._type === ObjectBag) { + result = this._to + ? result.align(this._to) + : this._from + ? result.align(this._from) + : result + } + + result = result.toConsumable() + + this._morphObj = this._morphObj || new this._type() + this._context = + this._context || + Array.apply(null, Array(result.length)) + .map(Object) + .map(function (o) { + o.done = true + return o + }) + return result + } +} + +export class NonMorphable { + constructor(...args) { + this.init(...args) + } + + init(val) { + val = Array.isArray(val) ? val[0] : val + this.value = val + return this + } + + toArray() { + return [this.value] + } + + valueOf() { + return this.value + } +} + +export class TransformBag { + constructor(...args) { + this.init(...args) + } + + init(obj) { + if (Array.isArray(obj)) { + obj = { + scaleX: obj[0], + scaleY: obj[1], + shear: obj[2], + rotate: obj[3], + translateX: obj[4], + translateY: obj[5], + originX: obj[6], + originY: obj[7] + } + } + + Object.assign(this, TransformBag.defaults, obj) + return this + } + + toArray() { + const v = this + + return [ + v.scaleX, + v.scaleY, + v.shear, + v.rotate, + v.translateX, + v.translateY, + v.originX, + v.originY + ] + } +} + +TransformBag.defaults = { + scaleX: 1, + scaleY: 1, + shear: 0, + rotate: 0, + translateX: 0, + translateY: 0, + originX: 0, + originY: 0 +} + +const sortByKey = (a, b) => { + return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0 +} + +export class ObjectBag { + constructor(...args) { + this.init(...args) + } + + align(other) { + const values = this.values + for (let i = 0, il = values.length; i < il; ++i) { + // If the type is the same we only need to check if the color is in the correct format + if (values[i + 1] === other[i + 1]) { + if (values[i + 1] === Color && other[i + 7] !== values[i + 7]) { + const space = other[i + 7] + const color = new Color(this.values.splice(i + 3, 5)) + [space]() + .toArray() + this.values.splice(i + 3, 0, ...color) + } + + i += values[i + 2] + 2 + continue + } + + if (!other[i + 1]) { + return this + } + + // The types differ, so we overwrite the new type with the old one + // And initialize it with the types default (e.g. black for color or 0 for number) + const defaultObject = new other[i + 1]().toArray() + + // Than we fix the values array + const toDelete = values[i + 2] + 3 + + values.splice( + i, + toDelete, + other[i], + other[i + 1], + other[i + 2], + ...defaultObject + ) + + i += values[i + 2] + 2 + } + return this + } + + init(objOrArr) { + this.values = [] + + if (Array.isArray(objOrArr)) { + this.values = objOrArr.slice() + return + } + + objOrArr = objOrArr || {} + const entries = [] + + for (const i in objOrArr) { + const Type = getClassForType(objOrArr[i]) + const val = new Type(objOrArr[i]).toArray() + entries.push([i, Type, val.length, ...val]) + } + + entries.sort(sortByKey) + + this.values = entries.reduce((last, curr) => last.concat(curr), []) + return this + } + + toArray() { + return this.values + } + + valueOf() { + const obj = {} + const arr = this.values + + // for (var i = 0, len = arr.length; i < len; i += 2) { + while (arr.length) { + const key = arr.shift() + const Type = arr.shift() + const num = arr.shift() + const values = arr.splice(0, num) + obj[key] = new Type(values) // .valueOf() + } + + return obj + } +} + +const morphableTypes = [NonMorphable, TransformBag, ObjectBag] + +export function registerMorphableType(type = []) { + morphableTypes.push(...[].concat(type)) +} + +export function makeMorphable() { + extend(morphableTypes, { + to(val) { + return new Morphable() + .type(this.constructor) + .from(this.toArray()) // this.valueOf()) + .to(val) + }, + fromArray(arr) { + this.init(arr) + return this + }, + toConsumable() { + return this.toArray() + }, + morph(from, to, pos, stepper, context) { + const mapper = function (i, index) { + return stepper.step(i, to[index], pos, context[index], context) + } + + return this.fromArray(from.map(mapper)) + } + }) +} diff --git a/src/animation/Queue.js b/src/animation/Queue.js new file mode 100644 index 000000000..65108f3f6 --- /dev/null +++ b/src/animation/Queue.js @@ -0,0 +1,62 @@ +export default class Queue { + constructor() { + this._first = null + this._last = null + } + + // Shows us the first item in the list + first() { + return this._first && this._first.value + } + + // Shows us the last item in the list + last() { + return this._last && this._last.value + } + + push(value) { + // An item stores an id and the provided value + const item = + typeof value.next !== 'undefined' + ? value + : { value: value, next: null, prev: null } + + // Deal with the queue being empty or populated + if (this._last) { + item.prev = this._last + this._last.next = item + this._last = item + } else { + this._last = item + this._first = item + } + + // Return the current item + return item + } + + // Removes the item that was returned from the push + remove(item) { + // Relink the previous item + if (item.prev) item.prev.next = item.next + if (item.next) item.next.prev = item.prev + if (item === this._last) this._last = item.prev + if (item === this._first) this._first = item.next + + // Invalidate item + item.prev = null + item.next = null + } + + shift() { + // Check if we have a value + const remove = this._first + if (!remove) return null + + // If we do, remove it and relink things + this._first = remove.next + if (this._first) this._first.prev = null + this._last = this._first ? this._last : null + return remove.value + } +} diff --git a/src/animation/Runner.js b/src/animation/Runner.js new file mode 100644 index 000000000..be74c7add --- /dev/null +++ b/src/animation/Runner.js @@ -0,0 +1,1085 @@ +import { Controller, Ease, Stepper } from './Controller.js' +import { extend, register } from '../utils/adopter.js' +import { from, to } from '../modules/core/gradiented.js' +import { getOrigin } from '../utils/utils.js' +import { noop, timeline } from '../modules/core/defaults.js' +import { registerMethods } from '../utils/methods.js' +import { rx, ry } from '../modules/core/circled.js' +import Animator from './Animator.js' +import Box from '../types/Box.js' +import EventTarget from '../types/EventTarget.js' +import Matrix from '../types/Matrix.js' +import Morphable, { TransformBag, ObjectBag } from './Morphable.js' +import Point from '../types/Point.js' +import SVGNumber from '../types/SVGNumber.js' +import Timeline from './Timeline.js' + +export default class Runner extends EventTarget { + constructor(options) { + super() + + // Store a unique id on the runner, so that we can identify it later + this.id = Runner.id++ + + // Ensure a default value + options = options == null ? timeline.duration : options + + // Ensure that we get a controller + options = typeof options === 'function' ? new Controller(options) : options + + // Declare all of the variables + this._element = null + this._timeline = null + this.done = false + this._queue = [] + + // Work out the stepper and the duration + this._duration = typeof options === 'number' && options + this._isDeclarative = options instanceof Controller + this._stepper = this._isDeclarative ? options : new Ease() + + // We copy the current values from the timeline because they can change + this._history = {} + + // Store the state of the runner + this.enabled = true + this._time = 0 + this._lastTime = 0 + + // At creation, the runner is in reset state + this._reseted = true + + // Save transforms applied to this runner + this.transforms = new Matrix() + this.transformId = 1 + + // Looping variables + this._haveReversed = false + this._reverse = false + this._loopsDone = 0 + this._swing = false + this._wait = 0 + this._times = 1 + + this._frameId = null + + // Stores how long a runner is stored after being done + this._persist = this._isDeclarative ? true : null + } + + static sanitise(duration, delay, when) { + // Initialise the default parameters + let times = 1 + let swing = false + let wait = 0 + duration = duration ?? timeline.duration + delay = delay ?? timeline.delay + when = when || 'last' + + // If we have an object, unpack the values + if (typeof duration === 'object' && !(duration instanceof Stepper)) { + delay = duration.delay ?? delay + when = duration.when ?? when + swing = duration.swing || swing + times = duration.times ?? times + wait = duration.wait ?? wait + duration = duration.duration ?? timeline.duration + } + + return { + duration: duration, + delay: delay, + swing: swing, + times: times, + wait: wait, + when: when + } + } + + active(enabled) { + if (enabled == null) return this.enabled + this.enabled = enabled + return this + } + + /* + Private Methods + =============== + Methods that shouldn't be used externally + */ + addTransform(transform) { + this.transforms.lmultiplyO(transform) + return this + } + + after(fn) { + return this.on('finished', fn) + } + + animate(duration, delay, when) { + const o = Runner.sanitise(duration, delay, when) + const runner = new Runner(o.duration) + if (this._timeline) runner.timeline(this._timeline) + if (this._element) runner.element(this._element) + return runner.loop(o).schedule(o.delay, o.when) + } + + clearTransform() { + this.transforms = new Matrix() + return this + } + + // TODO: Keep track of all transformations so that deletion is faster + clearTransformsFromQueue() { + if ( + !this.done || + !this._timeline || + !this._timeline._runnerIds.includes(this.id) + ) { + this._queue = this._queue.filter((item) => { + return !item.isTransform + }) + } + } + + delay(delay) { + return this.animate(0, delay) + } + + duration() { + return this._times * (this._wait + this._duration) - this._wait + } + + during(fn) { + return this.queue(null, fn) + } + + ease(fn) { + this._stepper = new Ease(fn) + return this + } + /* + Runner Definitions + ================== + These methods help us define the runtime behaviour of the Runner or they + help us make new runners from the current runner + */ + + element(element) { + if (element == null) return this._element + this._element = element + element._prepareRunner() + return this + } + + finish() { + return this.step(Infinity) + } + + loop(times, swing, wait) { + // Deal with the user passing in an object + if (typeof times === 'object') { + swing = times.swing + wait = times.wait + times = times.times + } + + // Sanitise the values and store them + this._times = times || Infinity + this._swing = swing || false + this._wait = wait || 0 + + // Allow true to be passed + if (this._times === true) { + this._times = Infinity + } + + return this + } + + loops(p) { + const loopDuration = this._duration + this._wait + if (p == null) { + const loopsDone = Math.floor(this._time / loopDuration) + const relativeTime = this._time - loopsDone * loopDuration + const position = relativeTime / this._duration + return Math.min(loopsDone + position, this._times) + } + const whole = Math.floor(p) + const partial = p % 1 + const time = loopDuration * whole + this._duration * partial + return this.time(time) + } + + persist(dtOrForever) { + if (dtOrForever == null) return this._persist + this._persist = dtOrForever + return this + } + + position(p) { + // Get all of the variables we need + const x = this._time + const d = this._duration + const w = this._wait + const t = this._times + const s = this._swing + const r = this._reverse + let position + + if (p == null) { + /* + This function converts a time to a position in the range [0, 1] + The full explanation can be found in this desmos demonstration + https://www.desmos.com/calculator/u4fbavgche + The logic is slightly simplified here because we can use booleans + */ + + // Figure out the value without thinking about the start or end time + const f = function (x) { + const swinging = s * Math.floor((x % (2 * (w + d))) / (w + d)) + const backwards = (swinging && !r) || (!swinging && r) + const uncliped = + (Math.pow(-1, backwards) * (x % (w + d))) / d + backwards + const clipped = Math.max(Math.min(uncliped, 1), 0) + return clipped + } + + // Figure out the value by incorporating the start time + const endTime = t * (w + d) - w + position = + x <= 0 + ? Math.round(f(1e-5)) + : x < endTime + ? f(x) + : Math.round(f(endTime - 1e-5)) + return position + } + + // Work out the loops done and add the position to the loops done + const loopsDone = Math.floor(this.loops()) + const swingForward = s && loopsDone % 2 === 0 + const forwards = (swingForward && !r) || (r && swingForward) + position = loopsDone + (forwards ? p : 1 - p) + return this.loops(position) + } + + progress(p) { + if (p == null) { + return Math.min(1, this._time / this.duration()) + } + return this.time(p * this.duration()) + } + + /* + Basic Functionality + =================== + These methods allow us to attach basic functions to the runner directly + */ + queue(initFn, runFn, retargetFn, isTransform) { + this._queue.push({ + initialiser: initFn || noop, + runner: runFn || noop, + retarget: retargetFn, + isTransform: isTransform, + initialised: false, + finished: false + }) + const timeline = this.timeline() + timeline && this.timeline()._continue() + return this + } + + reset() { + if (this._reseted) return this + this.time(0) + this._reseted = true + return this + } + + reverse(reverse) { + this._reverse = reverse == null ? !this._reverse : reverse + return this + } + + schedule(timeline, delay, when) { + // The user doesn't need to pass a timeline if we already have one + if (!(timeline instanceof Timeline)) { + when = delay + delay = timeline + timeline = this.timeline() + } + + // If there is no timeline, yell at the user... + if (!timeline) { + throw Error('Runner cannot be scheduled without timeline') + } + + // Schedule the runner on the timeline provided + timeline.schedule(this, delay, when) + return this + } + + step(dt) { + // If we are inactive, this stepper just gets skipped + if (!this.enabled) return this + + // Update the time and get the new position + dt = dt == null ? 16 : dt + this._time += dt + const position = this.position() + + // Figure out if we need to run the stepper in this frame + const running = this._lastPosition !== position && this._time >= 0 + this._lastPosition = position + + // Figure out if we just started + const duration = this.duration() + const justStarted = this._lastTime <= 0 && this._time > 0 + const justFinished = this._lastTime < duration && this._time >= duration + + this._lastTime = this._time + if (justStarted) { + this.fire('start', this) + } + + // Work out if the runner is finished set the done flag here so animations + // know, that they are running in the last step (this is good for + // transformations which can be merged) + const declarative = this._isDeclarative + this.done = !declarative && !justFinished && this._time >= duration + + // Runner is running. So its not in reset state anymore + this._reseted = false + + let converged = false + // Call initialise and the run function + if (running || declarative) { + this._initialise(running) + + // clear the transforms on this runner so they dont get added again and again + this.transforms = new Matrix() + converged = this._run(declarative ? dt : position) + + this.fire('step', this) + } + // correct the done flag here + // declarative animations itself know when they converged + this.done = this.done || (converged && declarative) + if (justFinished) { + this.fire('finished', this) + } + return this + } + + /* + Runner animation methods + ======================== + Control how the animation plays + */ + time(time) { + if (time == null) { + return this._time + } + const dt = time - this._time + this.step(dt) + return this + } + + timeline(timeline) { + // check explicitly for undefined so we can set the timeline to null + if (typeof timeline === 'undefined') return this._timeline + this._timeline = timeline + return this + } + + unschedule() { + const timeline = this.timeline() + timeline && timeline.unschedule(this) + return this + } + + // Run each initialise function in the runner if required + _initialise(running) { + // If we aren't running, we shouldn't initialise when not declarative + if (!running && !this._isDeclarative) return + + // Loop through all of the initialisers + for (let i = 0, len = this._queue.length; i < len; ++i) { + // Get the current initialiser + const current = this._queue[i] + + // Determine whether we need to initialise + const needsIt = this._isDeclarative || (!current.initialised && running) + running = !current.finished + + // Call the initialiser if we need to + if (needsIt && running) { + current.initialiser.call(this) + current.initialised = true + } + } + } + + // Save a morpher to the morpher list so that we can retarget it later + _rememberMorpher(method, morpher) { + this._history[method] = { + morpher: morpher, + caller: this._queue[this._queue.length - 1] + } + + // We have to resume the timeline in case a controller + // is already done without being ever run + // This can happen when e.g. this is done: + // anim = el.animate(new SVG.Spring) + // and later + // anim.move(...) + if (this._isDeclarative) { + const timeline = this.timeline() + timeline && timeline.play() + } + } + + // Try to set the target for a morpher if the morpher exists, otherwise + // Run each run function for the position or dt given + _run(positionOrDt) { + // Run all of the _queue directly + let allfinished = true + for (let i = 0, len = this._queue.length; i < len; ++i) { + // Get the current function to run + const current = this._queue[i] + + // Run the function if its not finished, we keep track of the finished + // flag for the sake of declarative _queue + const converged = current.runner.call(this, positionOrDt) + current.finished = current.finished || converged === true + allfinished = allfinished && current.finished + } + + // We report when all of the constructors are finished + return allfinished + } + + // do nothing and return false + _tryRetarget(method, target, extra) { + if (this._history[method]) { + // if the last method wasn't even initialised, throw it away + if (!this._history[method].caller.initialised) { + const index = this._queue.indexOf(this._history[method].caller) + this._queue.splice(index, 1) + return false + } + + // for the case of transformations, we use the special retarget function + // which has access to the outer scope + if (this._history[method].caller.retarget) { + this._history[method].caller.retarget.call(this, target, extra) + // for everything else a simple morpher change is sufficient + } else { + this._history[method].morpher.to(target) + } + + this._history[method].caller.finished = false + const timeline = this.timeline() + timeline && timeline.play() + return true + } + return false + } +} + +Runner.id = 0 + +export class FakeRunner { + constructor(transforms = new Matrix(), id = -1, done = true) { + this.transforms = transforms + this.id = id + this.done = done + } + + clearTransformsFromQueue() {} +} + +extend([Runner, FakeRunner], { + mergeWith(runner) { + return new FakeRunner( + runner.transforms.lmultiply(this.transforms), + runner.id + ) + } +}) + +// FakeRunner.emptyRunner = new FakeRunner() + +const lmultiply = (last, curr) => last.lmultiplyO(curr) +const getRunnerTransform = (runner) => runner.transforms + +function mergeTransforms() { + // Find the matrix to apply to the element and apply it + const runners = this._transformationRunners.runners + const netTransform = runners + .map(getRunnerTransform) + .reduce(lmultiply, new Matrix()) + + this.transform(netTransform) + + this._transformationRunners.merge() + + if (this._transformationRunners.length() === 1) { + this._frameId = null + } +} + +export class RunnerArray { + constructor() { + this.runners = [] + this.ids = [] + } + + add(runner) { + if (this.runners.includes(runner)) return + const id = runner.id + 1 + + this.runners.push(runner) + this.ids.push(id) + + return this + } + + clearBefore(id) { + const deleteCnt = this.ids.indexOf(id + 1) || 1 + this.ids.splice(0, deleteCnt, 0) + this.runners + .splice(0, deleteCnt, new FakeRunner()) + .forEach((r) => r.clearTransformsFromQueue()) + return this + } + + edit(id, newRunner) { + const index = this.ids.indexOf(id + 1) + this.ids.splice(index, 1, id + 1) + this.runners.splice(index, 1, newRunner) + return this + } + + getByID(id) { + return this.runners[this.ids.indexOf(id + 1)] + } + + length() { + return this.ids.length + } + + merge() { + let lastRunner = null + for (let i = 0; i < this.runners.length; ++i) { + const runner = this.runners[i] + + const condition = + lastRunner && + runner.done && + lastRunner.done && + // don't merge runner when persisted on timeline + (!runner._timeline || + !runner._timeline._runnerIds.includes(runner.id)) && + (!lastRunner._timeline || + !lastRunner._timeline._runnerIds.includes(lastRunner.id)) + + if (condition) { + // the +1 happens in the function + this.remove(runner.id) + const newRunner = runner.mergeWith(lastRunner) + this.edit(lastRunner.id, newRunner) + lastRunner = newRunner + --i + } else { + lastRunner = runner + } + } + + return this + } + + remove(id) { + const index = this.ids.indexOf(id + 1) + this.ids.splice(index, 1) + this.runners.splice(index, 1) + return this + } +} + +registerMethods({ + Element: { + animate(duration, delay, when) { + const o = Runner.sanitise(duration, delay, when) + const timeline = this.timeline() + return new Runner(o.duration) + .loop(o) + .element(this) + .timeline(timeline.play()) + .schedule(o.delay, o.when) + }, + + delay(by, when) { + return this.animate(0, by, when) + }, + + // this function searches for all runners on the element and deletes the ones + // which run before the current one. This is because absolute transformations + // overwrite anything anyway so there is no need to waste time computing + // other runners + _clearTransformRunnersBefore(currentRunner) { + this._transformationRunners.clearBefore(currentRunner.id) + }, + + _currentTransform(current) { + return ( + this._transformationRunners.runners + // we need the equal sign here to make sure, that also transformations + // on the same runner which execute before the current transformation are + // taken into account + .filter((runner) => runner.id <= current.id) + .map(getRunnerTransform) + .reduce(lmultiply, new Matrix()) + ) + }, + + _addRunner(runner) { + this._transformationRunners.add(runner) + + // Make sure that the runner merge is executed at the very end of + // all Animator functions. That is why we use immediate here to execute + // the merge right after all frames are run + Animator.cancelImmediate(this._frameId) + this._frameId = Animator.immediate(mergeTransforms.bind(this)) + }, + + _prepareRunner() { + if (this._frameId == null) { + this._transformationRunners = new RunnerArray().add( + new FakeRunner(new Matrix(this)) + ) + } + } + } +}) + +// Will output the elements from array A that are not in the array B +const difference = (a, b) => a.filter((x) => !b.includes(x)) + +extend(Runner, { + attr(a, v) { + return this.styleAttr('attr', a, v) + }, + + // Add animatable styles + css(s, v) { + return this.styleAttr('css', s, v) + }, + + styleAttr(type, nameOrAttrs, val) { + if (typeof nameOrAttrs === 'string') { + return this.styleAttr(type, { [nameOrAttrs]: val }) + } + + let attrs = nameOrAttrs + if (this._tryRetarget(type, attrs)) return this + + let morpher = new Morphable(this._stepper).to(attrs) + let keys = Object.keys(attrs) + + this.queue( + function () { + morpher = morpher.from(this.element()[type](keys)) + }, + function (pos) { + this.element()[type](morpher.at(pos).valueOf()) + return morpher.done() + }, + function (newToAttrs) { + // Check if any new keys were added + const newKeys = Object.keys(newToAttrs) + const differences = difference(newKeys, keys) + + // If their are new keys, initialize them and add them to morpher + if (differences.length) { + // Get the values + const addedFromAttrs = this.element()[type](differences) + + // Get the already initialized values + const oldFromAttrs = new ObjectBag(morpher.from()).valueOf() + + // Merge old and new + Object.assign(oldFromAttrs, addedFromAttrs) + morpher.from(oldFromAttrs) + } + + // Get the object from the morpher + const oldToAttrs = new ObjectBag(morpher.to()).valueOf() + + // Merge in new attributes + Object.assign(oldToAttrs, newToAttrs) + + // Change morpher target + morpher.to(oldToAttrs) + + // Make sure that we save the work we did so we don't need it to do again + keys = newKeys + attrs = newToAttrs + } + ) + + this._rememberMorpher(type, morpher) + return this + }, + + zoom(level, point) { + if (this._tryRetarget('zoom', level, point)) return this + + let morpher = new Morphable(this._stepper).to(new SVGNumber(level)) + + this.queue( + function () { + morpher = morpher.from(this.element().zoom()) + }, + function (pos) { + this.element().zoom(morpher.at(pos), point) + return morpher.done() + }, + function (newLevel, newPoint) { + point = newPoint + morpher.to(newLevel) + } + ) + + this._rememberMorpher('zoom', morpher) + return this + }, + + /** + ** absolute transformations + **/ + + // + // M v -----|-----(D M v = F v)------|-----> T v + // + // 1. define the final state (T) and decompose it (once) + // t = [tx, ty, the, lam, sy, sx] + // 2. on every frame: pull the current state of all previous transforms + // (M - m can change) + // and then write this as m = [tx0, ty0, the0, lam0, sy0, sx0] + // 3. Find the interpolated matrix F(pos) = m + pos * (t - m) + // - Note F(0) = M + // - Note F(1) = T + // 4. Now you get the delta matrix as a result: D = F * inv(M) + + transform(transforms, relative, affine) { + // If we have a declarative function, we should retarget it if possible + relative = transforms.relative || relative + if ( + this._isDeclarative && + !relative && + this._tryRetarget('transform', transforms) + ) { + return this + } + + // Parse the parameters + const isMatrix = Matrix.isMatrixLike(transforms) + affine = + transforms.affine != null + ? transforms.affine + : affine != null + ? affine + : !isMatrix + + // Create a morpher and set its type + const morpher = new Morphable(this._stepper).type( + affine ? TransformBag : Matrix + ) + + let origin + let element + let current + let currentAngle + let startTransform + + function setup() { + // make sure element and origin is defined + element = element || this.element() + origin = origin || getOrigin(transforms, element) + + startTransform = new Matrix(relative ? undefined : element) + + // add the runner to the element so it can merge transformations + element._addRunner(this) + + // Deactivate all transforms that have run so far if we are absolute + if (!relative) { + element._clearTransformRunnersBefore(this) + } + } + + function run(pos) { + // clear all other transforms before this in case something is saved + // on this runner. We are absolute. We dont need these! + if (!relative) this.clearTransform() + + const { x, y } = new Point(origin).transform( + element._currentTransform(this) + ) + + let target = new Matrix({ ...transforms, origin: [x, y] }) + let start = this._isDeclarative && current ? current : startTransform + + if (affine) { + target = target.decompose(x, y) + start = start.decompose(x, y) + + // Get the current and target angle as it was set + const rTarget = target.rotate + const rCurrent = start.rotate + + // Figure out the shortest path to rotate directly + const possibilities = [rTarget - 360, rTarget, rTarget + 360] + const distances = possibilities.map((a) => Math.abs(a - rCurrent)) + const shortest = Math.min(...distances) + const index = distances.indexOf(shortest) + target.rotate = possibilities[index] + } + + if (relative) { + // we have to be careful here not to overwrite the rotation + // with the rotate method of Matrix + if (!isMatrix) { + target.rotate = transforms.rotate || 0 + } + if (this._isDeclarative && currentAngle) { + start.rotate = currentAngle + } + } + + morpher.from(start) + morpher.to(target) + + const affineParameters = morpher.at(pos) + currentAngle = affineParameters.rotate + current = new Matrix(affineParameters) + + this.addTransform(current) + element._addRunner(this) + return morpher.done() + } + + function retarget(newTransforms) { + // only get a new origin if it changed since the last call + if ( + (newTransforms.origin || 'center').toString() !== + (transforms.origin || 'center').toString() + ) { + origin = getOrigin(newTransforms, element) + } + + // overwrite the old transformations with the new ones + transforms = { ...newTransforms, origin } + } + + this.queue(setup, run, retarget, true) + this._isDeclarative && this._rememberMorpher('transform', morpher) + return this + }, + + // Animatable x-axis + x(x) { + return this._queueNumber('x', x) + }, + + // Animatable y-axis + y(y) { + return this._queueNumber('y', y) + }, + + ax(x) { + return this._queueNumber('ax', x) + }, + + ay(y) { + return this._queueNumber('ay', y) + }, + + dx(x = 0) { + return this._queueNumberDelta('x', x) + }, + + dy(y = 0) { + return this._queueNumberDelta('y', y) + }, + + dmove(x, y) { + return this.dx(x).dy(y) + }, + + _queueNumberDelta(method, to) { + to = new SVGNumber(to) + + // Try to change the target if we have this method already registered + if (this._tryRetarget(method, to)) return this + + // Make a morpher and queue the animation + const morpher = new Morphable(this._stepper).to(to) + let from = null + this.queue( + function () { + from = this.element()[method]() + morpher.from(from) + morpher.to(from + to) + }, + function (pos) { + this.element()[method](morpher.at(pos)) + return morpher.done() + }, + function (newTo) { + morpher.to(from + new SVGNumber(newTo)) + } + ) + + // Register the morpher so that if it is changed again, we can retarget it + this._rememberMorpher(method, morpher) + return this + }, + + _queueObject(method, to) { + // Try to change the target if we have this method already registered + if (this._tryRetarget(method, to)) return this + + // Make a morpher and queue the animation + const morpher = new Morphable(this._stepper).to(to) + this.queue( + function () { + morpher.from(this.element()[method]()) + }, + function (pos) { + this.element()[method](morpher.at(pos)) + return morpher.done() + } + ) + + // Register the morpher so that if it is changed again, we can retarget it + this._rememberMorpher(method, morpher) + return this + }, + + _queueNumber(method, value) { + return this._queueObject(method, new SVGNumber(value)) + }, + + // Animatable center x-axis + cx(x) { + return this._queueNumber('cx', x) + }, + + // Animatable center y-axis + cy(y) { + return this._queueNumber('cy', y) + }, + + // Add animatable move + move(x, y) { + return this.x(x).y(y) + }, + + amove(x, y) { + return this.ax(x).ay(y) + }, + + // Add animatable center + center(x, y) { + return this.cx(x).cy(y) + }, + + // Add animatable size + size(width, height) { + // animate bbox based size for all other elements + let box + + if (!width || !height) { + box = this._element.bbox() + } + + if (!width) { + width = (box.width / box.height) * height + } + + if (!height) { + height = (box.height / box.width) * width + } + + return this.width(width).height(height) + }, + + // Add animatable width + width(width) { + return this._queueNumber('width', width) + }, + + // Add animatable height + height(height) { + return this._queueNumber('height', height) + }, + + // Add animatable plot + plot(a, b, c, d) { + // Lines can be plotted with 4 arguments + if (arguments.length === 4) { + return this.plot([a, b, c, d]) + } + + if (this._tryRetarget('plot', a)) return this + + const morpher = new Morphable(this._stepper) + .type(this._element.MorphArray) + .to(a) + + this.queue( + function () { + morpher.from(this._element.array()) + }, + function (pos) { + this._element.plot(morpher.at(pos)) + return morpher.done() + } + ) + + this._rememberMorpher('plot', morpher) + return this + }, + + // Add leading method + leading(value) { + return this._queueNumber('leading', value) + }, + + // Add animatable viewbox + viewbox(x, y, width, height) { + return this._queueObject('viewbox', new Box(x, y, width, height)) + }, + + update(o) { + if (typeof o !== 'object') { + return this.update({ + offset: arguments[0], + color: arguments[1], + opacity: arguments[2] + }) + } + + if (o.opacity != null) this.attr('stop-opacity', o.opacity) + if (o.color != null) this.attr('stop-color', o.color) + if (o.offset != null) this.attr('offset', o.offset) + + return this + } +}) + +extend(Runner, { rx, ry, from, to }) +register(Runner, 'Runner') diff --git a/src/animation/Timeline.js b/src/animation/Timeline.js new file mode 100644 index 000000000..2f6f5d3f9 --- /dev/null +++ b/src/animation/Timeline.js @@ -0,0 +1,350 @@ +import { globals } from '../utils/window.js' +import { registerMethods } from '../utils/methods.js' +import Animator from './Animator.js' +import EventTarget from '../types/EventTarget.js' + +const makeSchedule = function (runnerInfo) { + const start = runnerInfo.start + const duration = runnerInfo.runner.duration() + const end = start + duration + return { + start: start, + duration: duration, + end: end, + runner: runnerInfo.runner + } +} + +const defaultSource = function () { + const w = globals.window + return (w.performance || w.Date).now() +} + +export default class Timeline extends EventTarget { + // Construct a new timeline on the given element + constructor(timeSource = defaultSource) { + super() + + this._timeSource = timeSource + + // terminate resets all variables to their initial state + this.terminate() + } + + active() { + return !!this._nextFrame + } + + finish() { + // Go to end and pause + this.time(this.getEndTimeOfTimeline() + 1) + return this.pause() + } + + // Calculates the end of the timeline + getEndTime() { + const lastRunnerInfo = this.getLastRunnerInfo() + const lastDuration = lastRunnerInfo ? lastRunnerInfo.runner.duration() : 0 + const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time + return lastStartTime + lastDuration + } + + getEndTimeOfTimeline() { + const endTimes = this._runners.map((i) => i.start + i.runner.duration()) + return Math.max(0, ...endTimes) + } + + getLastRunnerInfo() { + return this.getRunnerInfoById(this._lastRunnerId) + } + + getRunnerInfoById(id) { + return this._runners[this._runnerIds.indexOf(id)] || null + } + + pause() { + this._paused = true + return this._continue() + } + + persist(dtOrForever) { + if (dtOrForever == null) return this._persist + this._persist = dtOrForever + return this + } + + play() { + // Now make sure we are not paused and continue the animation + this._paused = false + return this.updateTime()._continue() + } + + reverse(yes) { + const currentSpeed = this.speed() + if (yes == null) return this.speed(-currentSpeed) + + const positive = Math.abs(currentSpeed) + return this.speed(yes ? -positive : positive) + } + + // schedules a runner on the timeline + schedule(runner, delay, when) { + if (runner == null) { + return this._runners.map(makeSchedule) + } + + // The start time for the next animation can either be given explicitly, + // derived from the current timeline time or it can be relative to the + // last start time to chain animations directly + + let absoluteStartTime = 0 + const endTime = this.getEndTime() + delay = delay || 0 + + // Work out when to start the animation + if (when == null || when === 'last' || when === 'after') { + // Take the last time and increment + absoluteStartTime = endTime + } else if (when === 'absolute' || when === 'start') { + absoluteStartTime = delay + delay = 0 + } else if (when === 'now') { + absoluteStartTime = this._time + } else if (when === 'relative') { + const runnerInfo = this.getRunnerInfoById(runner.id) + if (runnerInfo) { + absoluteStartTime = runnerInfo.start + delay + delay = 0 + } + } else if (when === 'with-last') { + const lastRunnerInfo = this.getLastRunnerInfo() + const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time + absoluteStartTime = lastStartTime + } else { + throw new Error('Invalid value for the "when" parameter') + } + + // Manage runner + runner.unschedule() + runner.timeline(this) + + const persist = runner.persist() + const runnerInfo = { + persist: persist === null ? this._persist : persist, + start: absoluteStartTime + delay, + runner + } + + this._lastRunnerId = runner.id + + this._runners.push(runnerInfo) + this._runners.sort((a, b) => a.start - b.start) + this._runnerIds = this._runners.map((info) => info.runner.id) + + this.updateTime()._continue() + return this + } + + seek(dt) { + return this.time(this._time + dt) + } + + source(fn) { + if (fn == null) return this._timeSource + this._timeSource = fn + return this + } + + speed(speed) { + if (speed == null) return this._speed + this._speed = speed + return this + } + + stop() { + // Go to start and pause + this.time(0) + return this.pause() + } + + time(time) { + if (time == null) return this._time + this._time = time + return this._continue(true) + } + + // Remove the runner from this timeline + unschedule(runner) { + const index = this._runnerIds.indexOf(runner.id) + if (index < 0) return this + + this._runners.splice(index, 1) + this._runnerIds.splice(index, 1) + + runner.timeline(null) + return this + } + + // Makes sure, that after pausing the time doesn't jump + updateTime() { + if (!this.active()) { + this._lastSourceTime = this._timeSource() + } + return this + } + + // Checks if we are running and continues the animation + _continue(immediateStep = false) { + Animator.cancelFrame(this._nextFrame) + this._nextFrame = null + + if (immediateStep) return this._stepImmediate() + if (this._paused) return this + + this._nextFrame = Animator.frame(this._step) + return this + } + + _stepFn(immediateStep = false) { + // Get the time delta from the last time and update the time + const time = this._timeSource() + let dtSource = time - this._lastSourceTime + + if (immediateStep) dtSource = 0 + + const dtTime = this._speed * dtSource + (this._time - this._lastStepTime) + this._lastSourceTime = time + + // Only update the time if we use the timeSource. + // Otherwise use the current time + if (!immediateStep) { + // Update the time + this._time += dtTime + this._time = this._time < 0 ? 0 : this._time + } + this._lastStepTime = this._time + this.fire('time', this._time) + + // This is for the case that the timeline was seeked so that the time + // is now before the startTime of the runner. That is why we need to set + // the runner to position 0 + + // FIXME: + // However, resetting in insertion order leads to bugs. Considering the case, + // where 2 runners change the same attribute but in different times, + // resetting both of them will lead to the case where the later defined + // runner always wins the reset even if the other runner started earlier + // and therefore should win the attribute battle + // this can be solved by resetting them backwards + for (let k = this._runners.length; k--; ) { + // Get and run the current runner and ignore it if its inactive + const runnerInfo = this._runners[k] + const runner = runnerInfo.runner + + // Make sure that we give the actual difference + // between runner start time and now + const dtToStart = this._time - runnerInfo.start + + // Dont run runner if not started yet + // and try to reset it + if (dtToStart <= 0) { + runner.reset() + } + } + + // Run all of the runners directly + let runnersLeft = false + for (let i = 0, len = this._runners.length; i < len; i++) { + // Get and run the current runner and ignore it if its inactive + const runnerInfo = this._runners[i] + const runner = runnerInfo.runner + let dt = dtTime + + // Make sure that we give the actual difference + // between runner start time and now + const dtToStart = this._time - runnerInfo.start + + // Dont run runner if not started yet + if (dtToStart <= 0) { + runnersLeft = true + continue + } else if (dtToStart < dt) { + // Adjust dt to make sure that animation is on point + dt = dtToStart + } + + if (!runner.active()) continue + + // If this runner is still going, signal that we need another animation + // frame, otherwise, remove the completed runner + const finished = runner.step(dt).done + if (!finished) { + runnersLeft = true + // continue + } else if (runnerInfo.persist !== true) { + // runner is finished. And runner might get removed + const endTime = runner.duration() - runner.time() + this._time + + if (endTime + runnerInfo.persist < this._time) { + // Delete runner and correct index + runner.unschedule() + --i + --len + } + } + } + + // Basically: we continue when there are runners right from us in time + // when -->, and when runners are left from us when <-- + if ( + (runnersLeft && !(this._speed < 0 && this._time === 0)) || + (this._runnerIds.length && this._speed < 0 && this._time > 0) + ) { + this._continue() + } else { + this.pause() + this.fire('finished') + } + + return this + } + + terminate() { + // cleanup memory + + // Store the timing variables + this._startTime = 0 + this._speed = 1.0 + + // Determines how long a runner is hold in memory. Can be a dt or true/false + this._persist = 0 + + // Keep track of the running animations and their starting parameters + this._nextFrame = null + this._paused = true + this._runners = [] + this._runnerIds = [] + this._lastRunnerId = -1 + this._time = 0 + this._lastSourceTime = 0 + this._lastStepTime = 0 + + // Make sure that step is always called in class context + this._step = this._stepFn.bind(this, false) + this._stepImmediate = this._stepFn.bind(this, true) + } +} + +registerMethods({ + Element: { + timeline: function (timeline) { + if (timeline == null) { + this._timeline = this._timeline || new Timeline() + return this._timeline + } else { + this._timeline = timeline + return this + } + } + } +}) diff --git a/src/arrange.js b/src/arrange.js deleted file mode 100644 index 904381f72..000000000 --- a/src/arrange.js +++ /dev/null @@ -1,85 +0,0 @@ -// ### This module adds backward / forward functionality to elements. - -// -SVG.extend(SVG.Element, { - // Get all siblings, including myself - siblings: function() { - return this.parent().children() - } - // Get the curent position siblings -, position: function() { - return this.parent().index(this) - } - // Get the next element (will return null if there is none) -, next: function() { - return this.siblings()[this.position() + 1] - } - // Get the next element (will return null if there is none) -, previous: function() { - return this.siblings()[this.position() - 1] - } - // Send given element one step forward -, forward: function() { - var i = this.position() + 1 - , p = this.parent() - - // move node one step forward - p.removeElement(this).add(this, i) - - // make sure defs node is always at the top - if (p instanceof SVG.Doc) - p.node.appendChild(p.defs().node) - - return this - } - // Send given element one step backward -, backward: function() { - var i = this.position() - - if (i > 0) - this.parent().removeElement(this).add(this, i - 1) - - return this - } - // Send given element all the way to the front -, front: function() { - var p = this.parent() - - // Move node forward - p.node.appendChild(this.node) - - // Make sure defs node is always at the top - if (p instanceof SVG.Doc) - p.node.appendChild(p.defs().node) - - return this - } - // Send given element all the way to the back -, back: function() { - if (this.position() > 0) - this.parent().removeElement(this).add(this, 0) - - return this - } - // Inserts a given element before the targeted element -, before: function(element) { - element.remove() - - var i = this.position() - - this.parent().add(element, i) - - return this - } - // Insters a given element after the targeted element -, after: function(element) { - element.remove() - - var i = this.position() - - this.parent().add(element, i + 1) - - return this - } - -}) \ No newline at end of file diff --git a/src/array.js b/src/array.js deleted file mode 100644 index 9947242be..000000000 --- a/src/array.js +++ /dev/null @@ -1,84 +0,0 @@ -// Module for array conversion -SVG.Array = function(array, fallback) { - array = (array || []).valueOf() - - // if array is empty and fallback is provided, use fallback - if (array.length == 0 && fallback) - array = fallback.valueOf() - - // parse array - this.value = this.parse(array) -} - -SVG.extend(SVG.Array, { - // Make array morphable - morph: function(array) { - this.destination = this.parse(array) - - // normalize length of arrays - if (this.value.length != this.destination.length) { - var lastValue = this.value[this.value.length - 1] - , lastDestination = this.destination[this.destination.length - 1] - - while(this.value.length > this.destination.length) - this.destination.push(lastDestination) - while(this.value.length < this.destination.length) - this.value.push(lastValue) - } - - return this - } - // Clean up any duplicate points -, settle: function() { - // find all unique values - for (var i = 0, il = this.value.length, seen = []; i < il; i++) - if (seen.indexOf(this.value[i]) == -1) - seen.push(this.value[i]) - - // set new value - return this.value = seen - } - // Get morphed array at given position -, at: function(pos) { - // make sure a destination is defined - if (!this.destination) return this - - // generate morphed array - for (var i = 0, il = this.value.length, array = []; i < il; i++) - array.push(this.value[i] + (this.destination[i] - this.value[i]) * pos) - - return new SVG.Array(array) - } - // Convert array to string -, toString: function() { - return this.value.join(' ') - } - // Real value -, valueOf: function() { - return this.value - } - // Parse whitespace separated string -, parse: function(array) { - array = array.valueOf() - - // if already is an array, no need to parse it - if (Array.isArray(array)) return array - - return this.split(array) - } - // Strip unnecessary whitespace -, split: function(string) { - return string.trim().split(SVG.regex.delimiter).map(parseFloat) - } - // Reverse array -, reverse: function() { - this.value.reverse() - - return this - } -, clone: function() { - var clone = new this.constructor() - clone.value = array_clone(this.value) - return clone - } -}) \ No newline at end of file diff --git a/src/attr.js b/src/attr.js deleted file mode 100644 index 134ac19e0..000000000 --- a/src/attr.js +++ /dev/null @@ -1,79 +0,0 @@ -SVG.extend(SVG.Element, { - // Set svg element attribute - attr: function(a, v, n) { - // act as full getter - if (a == null) { - // get an object of attributes - a = {} - v = this.node.attributes - for (n = v.length - 1; n >= 0; n--) - a[v[n].nodeName] = SVG.regex.isNumber.test(v[n].nodeValue) ? parseFloat(v[n].nodeValue) : v[n].nodeValue - - return a - - } else if (typeof a == 'object') { - // apply every attribute individually if an object is passed - for (v in a) this.attr(v, a[v]) - - } else if (v === null) { - // remove value - this.node.removeAttribute(a) - - } else if (v == null) { - // act as a getter if the first and only argument is not an object - v = this.node.getAttribute(a) - return v == null ? - SVG.defaults.attrs[a] : - SVG.regex.isNumber.test(v) ? - parseFloat(v) : v - - } else { - // BUG FIX: some browsers will render a stroke if a color is given even though stroke width is 0 - if (a == 'stroke-width') - this.attr('stroke', parseFloat(v) > 0 ? this._stroke : null) - else if (a == 'stroke') - this._stroke = v - - // convert image fill and stroke to patterns - if (a == 'fill' || a == 'stroke') { - if (SVG.regex.isImage.test(v)) - v = this.doc().defs().image(v, 0, 0) - - if (v instanceof SVG.Image) - v = this.doc().defs().pattern(0, 0, function() { - this.add(v) - }) - } - - // ensure correct numeric values (also accepts NaN and Infinity) - if (typeof v === 'number') - v = new SVG.Number(v) - - // ensure full hex color - else if (SVG.Color.isColor(v)) - v = new SVG.Color(v) - - // parse array values - else if (Array.isArray(v)) - v = new SVG.Array(v) - - // if the passed attribute is leading... - if (a == 'leading') { - // ... call the leading method instead - if (this.leading) - this.leading(v) - } else { - // set given attribute on node - typeof n === 'string' ? - this.node.setAttributeNS(n, a, v.toString()) : - this.node.setAttribute(a, v.toString()) - } - - // rebuild if required - if (this.rebuild && (a == 'font-size' || a == 'x')) - this.rebuild(a, v) - } - - return this - } -}) \ No newline at end of file diff --git a/src/bare.js b/src/bare.js deleted file mode 100644 index 5ac417381..000000000 --- a/src/bare.js +++ /dev/null @@ -1,40 +0,0 @@ - -SVG.Bare = SVG.invent({ - // Initialize - create: function(element, inherit) { - // construct element - this.constructor.call(this, SVG.create(element)) - - // inherit custom methods - if (inherit) - for (var method in inherit.prototype) - if (typeof inherit.prototype[method] === 'function') - this[method] = inherit.prototype[method] - } - - // Inherit from -, inherit: SVG.Element - - // Add methods -, extend: { - // Insert some plain text - words: function(text) { - // remove contents - while (this.node.hasChildNodes()) - this.node.removeChild(this.node.lastChild) - - // create text node - this.node.appendChild(document.createTextNode(text)) - - return this - } - } -}) - - -SVG.extend(SVG.Parent, { - // Create an element that is not described by SVG.js - element: function(element, inherit) { - return this.put(new SVG.Bare(element, inherit)) - } -}) diff --git a/src/boxes.js b/src/boxes.js deleted file mode 100644 index 4a3af8e9d..000000000 --- a/src/boxes.js +++ /dev/null @@ -1,168 +0,0 @@ -SVG.Box = SVG.invent({ - create: function(x, y, width, height) { - if (typeof x == 'object' && !(x instanceof SVG.Element)) { - // chromes getBoundingClientRect has no x and y property - return SVG.Box.call(this, x.left != null ? x.left : x.x , x.top != null ? x.top : x.y, x.width, x.height) - } else if (arguments.length == 4) { - this.x = x - this.y = y - this.width = width - this.height = height - } - - // add center, right, bottom... - fullBox(this) - } -, extend: { - // Merge rect box with another, return a new instance - merge: function(box) { - var b = new this.constructor() - - // merge boxes - b.x = Math.min(this.x, box.x) - b.y = Math.min(this.y, box.y) - b.width = Math.max(this.x + this.width, box.x + box.width) - b.x - b.height = Math.max(this.y + this.height, box.y + box.height) - b.y - - return fullBox(b) - } - - , transform: function(m) { - var xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, p, bbox - - var pts = [ - new SVG.Point(this.x, this.y), - new SVG.Point(this.x2, this.y), - new SVG.Point(this.x, this.y2), - new SVG.Point(this.x2, this.y2) - ] - - pts.forEach(function(p) { - p = p.transform(m) - xMin = Math.min(xMin,p.x) - xMax = Math.max(xMax,p.x) - yMin = Math.min(yMin,p.y) - yMax = Math.max(yMax,p.y) - }) - - bbox = new this.constructor() - bbox.x = xMin - bbox.width = xMax-xMin - bbox.y = yMin - bbox.height = yMax-yMin - - fullBox(bbox) - - return bbox - } - } -}) - -SVG.BBox = SVG.invent({ - // Initialize - create: function(element) { - SVG.Box.apply(this, [].slice.call(arguments)) - - // get values if element is given - if (element instanceof SVG.Element) { - var box - - // yes this is ugly, but Firefox can be a bitch when it comes to elements that are not yet rendered - try { - - if (!document.documentElement.contains){ - // This is IE - it does not support contains() for top-level SVGs - var topParent = element.node - while (topParent.parentNode){ - topParent = topParent.parentNode - } - if (topParent != document) throw new Exception('Element not in the dom') - } else { - // the element is NOT in the dom, throw error - if(!document.documentElement.contains(element.node)) throw new Exception('Element not in the dom') - } - - // find native bbox - box = element.node.getBBox() - } catch(e) { - if(element instanceof SVG.Shape){ - var clone = element.clone(SVG.parser.draw.instance).show() - box = clone.node.getBBox() - clone.remove() - }else{ - box = { - x: element.node.clientLeft - , y: element.node.clientTop - , width: element.node.clientWidth - , height: element.node.clientHeight - } - } - } - - SVG.Box.call(this, box) - } - - } - - // Define ancestor -, inherit: SVG.Box - - // Define Parent -, parent: SVG.Element - - // Constructor -, construct: { - // Get bounding box - bbox: function() { - return new SVG.BBox(this) - } - } - -}) - -SVG.BBox.prototype.constructor = SVG.BBox - - -SVG.extend(SVG.Element, { - tbox: function(){ - console.warn('Use of TBox is deprecated and mapped to RBox. Use .rbox() instead.') - return this.rbox(this.doc()) - } -}) - -SVG.RBox = SVG.invent({ - // Initialize - create: function(element) { - SVG.Box.apply(this, [].slice.call(arguments)) - - if (element instanceof SVG.Element) { - SVG.Box.call(this, element.node.getBoundingClientRect()) - } - } - -, inherit: SVG.Box - - // define Parent -, parent: SVG.Element - -, extend: { - addOffset: function() { - // offset by window scroll position, because getBoundingClientRect changes when window is scrolled - this.x += window.pageXOffset - this.y += window.pageYOffset - return this - } - } - - // Constructor -, construct: { - // Get rect box - rbox: function(el) { - if (el) return new SVG.RBox(this).transform(el.screenCTM().inverse()) - return new SVG.RBox(this).addOffset() - } - } - -}) - -SVG.RBox.prototype.constructor = SVG.RBox diff --git a/src/clip.js b/src/clip.js deleted file mode 100644 index 2a92e440c..000000000 --- a/src/clip.js +++ /dev/null @@ -1,58 +0,0 @@ -SVG.ClipPath = SVG.invent({ - // Initialize node - create: function() { - this.constructor.call(this, SVG.create('clipPath')) - - // keep references to clipped elements - this.targets = [] - } - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Unclip all clipped elements and remove itself - remove: function() { - // unclip all targets - for (var i = this.targets.length - 1; i >= 0; i--) - if (this.targets[i]) - this.targets[i].unclip() - this.targets = [] - - // remove clipPath from parent - this.parent().removeElement(this) - - return this - } - } - - // Add parent method -, construct: { - // Create clipping element - clip: function() { - return this.defs().put(new SVG.ClipPath) - } - } -}) - -// -SVG.extend(SVG.Element, { - // Distribute clipPath to svg element - clipWith: function(element) { - // use given clip or create a new one - this.clipper = element instanceof SVG.ClipPath ? element : this.parent().clip().add(element) - - // store reverence on self in mask - this.clipper.targets.push(this) - - // apply mask - return this.attr('clip-path', 'url("#' + this.clipper.attr('id') + '")') - } - // Unclip element -, unclip: function() { - delete this.clipper - return this.attr('clip-path', null) - } - -}) \ No newline at end of file diff --git a/src/color.js b/src/color.js deleted file mode 100644 index 9d77d4edc..000000000 --- a/src/color.js +++ /dev/null @@ -1,108 +0,0 @@ -// Module for color convertions -SVG.Color = function(color) { - var match - - // initialize defaults - this.r = 0 - this.g = 0 - this.b = 0 - - if(!color) return - - // parse color - if (typeof color === 'string') { - if (SVG.regex.isRgb.test(color)) { - // get rgb values - match = SVG.regex.rgb.exec(color.replace(SVG.regex.whitespace,'')) - - // parse numeric values - this.r = parseInt(match[1]) - this.g = parseInt(match[2]) - this.b = parseInt(match[3]) - - } else if (SVG.regex.isHex.test(color)) { - // get hex values - match = SVG.regex.hex.exec(fullHex(color)) - - // parse numeric values - this.r = parseInt(match[1], 16) - this.g = parseInt(match[2], 16) - this.b = parseInt(match[3], 16) - - } - - } else if (typeof color === 'object') { - this.r = color.r - this.g = color.g - this.b = color.b - - } - -} - -SVG.extend(SVG.Color, { - // Default to hex conversion - toString: function() { - return this.toHex() - } - // Build hex value -, toHex: function() { - return '#' - + compToHex(this.r) - + compToHex(this.g) - + compToHex(this.b) - } - // Build rgb value -, toRgb: function() { - return 'rgb(' + [this.r, this.g, this.b].join() + ')' - } - // Calculate true brightness -, brightness: function() { - return (this.r / 255 * 0.30) - + (this.g / 255 * 0.59) - + (this.b / 255 * 0.11) - } - // Make color morphable -, morph: function(color) { - this.destination = new SVG.Color(color) - - return this - } - // Get morphed color at given position -, at: function(pos) { - // make sure a destination is defined - if (!this.destination) return this - - // normalise pos - pos = pos < 0 ? 0 : pos > 1 ? 1 : pos - - // generate morphed color - return new SVG.Color({ - r: ~~(this.r + (this.destination.r - this.r) * pos) - , g: ~~(this.g + (this.destination.g - this.g) * pos) - , b: ~~(this.b + (this.destination.b - this.b) * pos) - }) - } - -}) - -// Testers - -// Test if given value is a color string -SVG.Color.test = function(color) { - color += '' - return SVG.regex.isHex.test(color) - || SVG.regex.isRgb.test(color) -} - -// Test if given value is a rgb object -SVG.Color.isRgb = function(color) { - return color && typeof color.r == 'number' - && typeof color.g == 'number' - && typeof color.b == 'number' -} - -// Test if given value is a color -SVG.Color.isColor = function(color) { - return SVG.Color.isRgb(color) || SVG.Color.test(color) -} \ No newline at end of file diff --git a/src/container.js b/src/container.js deleted file mode 100644 index 68adf93c6..000000000 --- a/src/container.js +++ /dev/null @@ -1,10 +0,0 @@ -SVG.Container = SVG.invent({ - // Initialize node - create: function(element) { - this.constructor.call(this, element) - } - - // Inherit from -, inherit: SVG.Parent - -}) \ No newline at end of file diff --git a/src/data.js b/src/data.js deleted file mode 100644 index cefa21cf3..000000000 --- a/src/data.js +++ /dev/null @@ -1,29 +0,0 @@ - -SVG.extend(SVG.Element, { - // Store data values on svg nodes - data: function(a, v, r) { - if (typeof a == 'object') { - for (v in a) - this.data(v, a[v]) - - } else if (arguments.length < 2) { - try { - return JSON.parse(this.attr('data-' + a)) - } catch(e) { - return this.attr('data-' + a) - } - - } else { - this.attr( - 'data-' + a - , v === null ? - null : - r === true || typeof v === 'string' || typeof v === 'number' ? - v : - JSON.stringify(v) - ) - } - - return this - } -}) \ No newline at end of file diff --git a/src/default.js b/src/default.js deleted file mode 100644 index d371ca022..000000000 --- a/src/default.js +++ /dev/null @@ -1,36 +0,0 @@ - -SVG.defaults = { - // Default attribute values - attrs: { - // fill and stroke - 'fill-opacity': 1 - , 'stroke-opacity': 1 - , 'stroke-width': 0 - , 'stroke-linejoin': 'miter' - , 'stroke-linecap': 'butt' - , fill: '#000000' - , stroke: '#000000' - , opacity: 1 - // position - , x: 0 - , y: 0 - , cx: 0 - , cy: 0 - // size - , width: 0 - , height: 0 - // radius - , r: 0 - , rx: 0 - , ry: 0 - // gradient - , offset: 0 - , 'stop-opacity': 1 - , 'stop-color': '#000000' - // text - , 'font-size': 16 - , 'font-family': 'Helvetica, Arial, sans-serif' - , 'text-anchor': 'start' - } - -} \ No newline at end of file diff --git a/src/defs.js b/src/defs.js deleted file mode 100644 index ad66cc5e6..000000000 --- a/src/defs.js +++ /dev/null @@ -1,9 +0,0 @@ - -SVG.Defs = SVG.invent({ - // Initialize node - create: 'defs' - - // Inherit from -, inherit: SVG.Container - -}) \ No newline at end of file diff --git a/src/doc.js b/src/doc.js deleted file mode 100644 index 85728a209..000000000 --- a/src/doc.js +++ /dev/null @@ -1,95 +0,0 @@ -SVG.Doc = SVG.invent({ - // Initialize node - create: function(element) { - if (element) { - // ensure the presence of a dom element - element = typeof element == 'string' ? - document.getElementById(element) : - element - - // If the target is an svg element, use that element as the main wrapper. - // This allows svg.js to work with svg documents as well. - if (element.nodeName == 'svg') { - this.constructor.call(this, element) - } else { - this.constructor.call(this, SVG.create('svg')) - element.appendChild(this.node) - this.size('100%', '100%') - } - - // set svg element attributes and ensure defs node - this.namespace().defs() - } - } - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Add namespaces - namespace: function() { - return this - .attr({ xmlns: SVG.ns, version: '1.1' }) - .attr('xmlns:xlink', SVG.xlink, SVG.xmlns) - .attr('xmlns:svgjs', SVG.svgjs, SVG.xmlns) - } - // Creates and returns defs element - , defs: function() { - if (!this._defs) { - var defs - - // Find or create a defs element in this instance - if (defs = this.node.getElementsByTagName('defs')[0]) - this._defs = SVG.adopt(defs) - else - this._defs = new SVG.Defs - - // Make sure the defs node is at the end of the stack - this.node.appendChild(this._defs.node) - } - - return this._defs - } - // custom parent method - , parent: function() { - return this.node.parentNode.nodeName == '#document' ? null : this.node.parentNode - } - // Fix for possible sub-pixel offset. See: - // https://bugzilla.mozilla.org/show_bug.cgi?id=608812 - , spof: function() { - var pos = this.node.getScreenCTM() - - if (pos) - this - .style('left', (-pos.e % 1) + 'px') - .style('top', (-pos.f % 1) + 'px') - - return this - } - - // Removes the doc from the DOM - , remove: function() { - if(this.parent()) { - this.parent().removeChild(this.node) - } - - return this - } - , clear: function() { - // remove children - while(this.node.hasChildNodes()) - this.node.removeChild(this.node.lastChild) - - // remove defs reference - delete this._defs - - // add back parser - if(!SVG.parser.draw.parentNode) - this.node.appendChild(SVG.parser.draw) - - return this - } - } - -}) diff --git a/src/element.js b/src/element.js deleted file mode 100644 index 1c35b42b0..000000000 --- a/src/element.js +++ /dev/null @@ -1,268 +0,0 @@ - -SVG.Element = SVG.invent({ - // Initialize node - create: function(node) { - // make stroke value accessible dynamically - this._stroke = SVG.defaults.attrs.stroke - this._event = null - - // initialize data object - this.dom = {} - - // create circular reference - if (this.node = node) { - this.type = node.nodeName - this.node.instance = this - - // store current attribute value - this._stroke = node.getAttribute('stroke') || this._stroke - } - } - - // Add class methods -, extend: { - // Move over x-axis - x: function(x) { - return this.attr('x', x) - } - // Move over y-axis - , y: function(y) { - return this.attr('y', y) - } - // Move by center over x-axis - , cx: function(x) { - return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2) - } - // Move by center over y-axis - , cy: function(y) { - return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2) - } - // Move element to given x and y values - , move: function(x, y) { - return this.x(x).y(y) - } - // Move element by its center - , center: function(x, y) { - return this.cx(x).cy(y) - } - // Set width of element - , width: function(width) { - return this.attr('width', width) - } - // Set height of element - , height: function(height) { - return this.attr('height', height) - } - // Set element size to given width and height - , size: function(width, height) { - var p = proportionalSize(this, width, height) - - return this - .width(new SVG.Number(p.width)) - .height(new SVG.Number(p.height)) - } - // Clone element - , clone: function(parent, withData) { - // write dom data to the dom so the clone can pickup the data - this.writeDataToDom() - - // clone element and assign new id - var clone = assignNewId(this.node.cloneNode(true)) - - // insert the clone in the given parent or after myself - if(parent) parent.add(clone) - else this.after(clone) - - return clone - } - // Remove element - , remove: function() { - if (this.parent()) - this.parent().removeElement(this) - - return this - } - // Replace element - , replace: function(element) { - this.after(element).remove() - - return element - } - // Add element to given container and return self - , addTo: function(parent) { - return parent.put(this) - } - // Add element to given container and return container - , putIn: function(parent) { - return parent.add(this) - } - // Get / set id - , id: function(id) { - return this.attr('id', id) - } - // Checks whether the given point inside the bounding box of the element - , inside: function(x, y) { - var box = this.bbox() - - return x > box.x - && y > box.y - && x < box.x + box.width - && y < box.y + box.height - } - // Show element - , show: function() { - return this.style('display', '') - } - // Hide element - , hide: function() { - return this.style('display', 'none') - } - // Is element visible? - , visible: function() { - return this.style('display') != 'none' - } - // Return id on string conversion - , toString: function() { - return this.attr('id') - } - // Return array of classes on the node - , classes: function() { - var attr = this.attr('class') - - return attr == null ? [] : attr.trim().split(SVG.regex.delimiter) - } - // Return true if class exists on the node, false otherwise - , hasClass: function(name) { - return this.classes().indexOf(name) != -1 - } - // Add class to the node - , addClass: function(name) { - if (!this.hasClass(name)) { - var array = this.classes() - array.push(name) - this.attr('class', array.join(' ')) - } - - return this - } - // Remove class from the node - , removeClass: function(name) { - if (this.hasClass(name)) { - this.attr('class', this.classes().filter(function(c) { - return c != name - }).join(' ')) - } - - return this - } - // Toggle the presence of a class on the node - , toggleClass: function(name) { - return this.hasClass(name) ? this.removeClass(name) : this.addClass(name) - } - // Get referenced element form attribute value - , reference: function(attr) { - return SVG.get(this.attr(attr)) - } - // Returns the parent element instance - , parent: function(type) { - var parent = this - - // check for parent - if(!parent.node.parentNode) return null - - // get parent element - parent = SVG.adopt(parent.node.parentNode) - - if(!type) return parent - - // loop trough ancestors if type is given - while(parent && parent.node instanceof window.SVGElement){ - if(typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent - parent = SVG.adopt(parent.node.parentNode) - } - } - // Get parent document - , doc: function() { - return this instanceof SVG.Doc ? this : this.parent(SVG.Doc) - } - // return array of all ancestors of given type up to the root svg - , parents: function(type) { - var parents = [], parent = this - - do{ - parent = parent.parent(type) - if(!parent || !parent.node) break - - parents.push(parent) - } while(parent.parent) - - return parents - } - // matches the element vs a css selector - , matches: function(selector){ - return matches(this.node, selector) - } - // Returns the svg node to call native svg methods on it - , native: function() { - return this.node - } - // Import raw svg - , svg: function(svg) { - // create temporary holder - var well = document.createElement('svg') - - // act as a setter if svg is given - if (svg && this instanceof SVG.Parent) { - // dump raw svg - well.innerHTML = '' + svg.replace(/\n/, '').replace(/<(\w+)([^<]+?)\/>/g, '<$1$2>') + '' - - // transplant nodes - for (var i = 0, il = well.firstChild.childNodes.length; i < il; i++) - this.node.appendChild(well.firstChild.firstChild) - - // otherwise act as a getter - } else { - // create a wrapping svg element in case of partial content - well.appendChild(svg = document.createElement('svg')) - - // write svgjs data to the dom - this.writeDataToDom() - - // insert a copy of this node - svg.appendChild(this.node.cloneNode(true)) - - // return target element - return well.innerHTML.replace(/^/, '').replace(/<\/svg>$/, '') - } - - return this - } - // write svgjs data to the dom - , writeDataToDom: function() { - - // dump variables recursively - if(this.each || this.lines){ - var fn = this.each ? this : this.lines(); - fn.each(function(){ - this.writeDataToDom() - }) - } - - // remove previously set data - this.node.removeAttribute('svgjs:data') - - if(Object.keys(this.dom).length) - this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)) // see #428 - - return this - } - // set given data to the elements data property - , setData: function(o){ - this.dom = o - return this - } - , is: function(obj){ - return is(this, obj) - } - } -}) diff --git a/src/elements/A.js b/src/elements/A.js new file mode 100644 index 000000000..231954fcc --- /dev/null +++ b/src/elements/A.js @@ -0,0 +1,83 @@ +import { + nodeOrNew, + register, + wrapWithAttrCheck, + extend +} from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import { xlink } from '../modules/core/namespaces.js' +import Container from './Container.js' +import * as containerGeometry from '../modules/core/containerGeometry.js' + +export default class A extends Container { + constructor(node, attrs = node) { + super(nodeOrNew('a', node), attrs) + } + + // Link target attribute + target(target) { + return this.attr('target', target) + } + + // Link url + to(url) { + return this.attr('href', url, xlink) + } +} + +extend(A, containerGeometry) + +registerMethods({ + Container: { + // Create a hyperlink element + link: wrapWithAttrCheck(function (url) { + return this.put(new A()).to(url) + }) + }, + Element: { + unlink() { + const link = this.linker() + + if (!link) return this + + const parent = link.parent() + + if (!parent) { + return this.remove() + } + + const index = parent.index(link) + parent.add(this, index) + + link.remove() + return this + }, + linkTo(url) { + // reuse old link if possible + let link = this.linker() + + if (!link) { + link = new A() + this.wrap(link) + } + + if (typeof url === 'function') { + url.call(link, link) + } else { + link.to(url) + } + + return this + }, + linker() { + const link = this.parent() + if (link && link.node.nodeName.toLowerCase() === 'a') { + return link + } + + return null + } + } +}) + +register(A, 'A') diff --git a/src/elements/Circle.js b/src/elements/Circle.js new file mode 100644 index 000000000..5dae51e5b --- /dev/null +++ b/src/elements/Circle.js @@ -0,0 +1,47 @@ +import { cx, cy, height, width, x, y } from '../modules/core/circled.js' +import { + extend, + nodeOrNew, + register, + wrapWithAttrCheck +} from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import SVGNumber from '../types/SVGNumber.js' +import Shape from './Shape.js' + +export default class Circle extends Shape { + constructor(node, attrs = node) { + super(nodeOrNew('circle', node), attrs) + } + + radius(r) { + return this.attr('r', r) + } + + // Radius x value + rx(rx) { + return this.attr('r', rx) + } + + // Alias radius x value + ry(ry) { + return this.rx(ry) + } + + size(size) { + return this.radius(new SVGNumber(size).divide(2)) + } +} + +extend(Circle, { x, y, cx, cy, width, height }) + +registerMethods({ + Container: { + // Create circle element + circle: wrapWithAttrCheck(function (size = 0) { + return this.put(new Circle()).size(size).move(0, 0) + }) + } +}) + +register(Circle, 'Circle') diff --git a/src/elements/ClipPath.js b/src/elements/ClipPath.js new file mode 100644 index 000000000..747059dd1 --- /dev/null +++ b/src/elements/ClipPath.js @@ -0,0 +1,58 @@ +import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import Container from './Container.js' +import baseFind from '../modules/core/selector.js' + +export default class ClipPath extends Container { + constructor(node, attrs = node) { + super(nodeOrNew('clipPath', node), attrs) + } + + // Unclip all clipped elements and remove itself + remove() { + // unclip all targets + this.targets().forEach(function (el) { + el.unclip() + }) + + // remove clipPath from parent + return super.remove() + } + + targets() { + return baseFind('svg [clip-path*=' + this.id() + ']') + } +} + +registerMethods({ + Container: { + // Create clipping element + clip: wrapWithAttrCheck(function () { + return this.defs().put(new ClipPath()) + }) + }, + Element: { + // Distribute clipPath to svg element + clipper() { + return this.reference('clip-path') + }, + + clipWith(element) { + // use given clip or create a new one + const clipper = + element instanceof ClipPath + ? element + : this.parent().clip().add(element) + + // apply mask + return this.attr('clip-path', 'url(#' + clipper.id() + ')') + }, + + // Unclip element + unclip() { + return this.attr('clip-path', null) + } + } +}) + +register(ClipPath, 'ClipPath') diff --git a/src/elements/Container.js b/src/elements/Container.js new file mode 100644 index 000000000..0f45b6dd5 --- /dev/null +++ b/src/elements/Container.js @@ -0,0 +1,28 @@ +import { register } from '../utils/adopter.js' +import Element from './Element.js' + +export default class Container extends Element { + flatten() { + this.each(function () { + if (this instanceof Container) { + return this.flatten().ungroup() + } + }) + + return this + } + + ungroup(parent = this.parent(), index = parent.index(this)) { + // when parent != this, we want append all elements to the end + index = index === -1 ? parent.children().length : index + + this.each(function (i, children) { + // reverse each + return children[children.length - i - 1].toParent(parent, index) + }) + + return this.remove() + } +} + +register(Container, 'Container') diff --git a/src/elements/Defs.js b/src/elements/Defs.js new file mode 100644 index 000000000..6d9f725cd --- /dev/null +++ b/src/elements/Defs.js @@ -0,0 +1,18 @@ +import { nodeOrNew, register } from '../utils/adopter.js' +import Container from './Container.js' + +export default class Defs extends Container { + constructor(node, attrs = node) { + super(nodeOrNew('defs', node), attrs) + } + + flatten() { + return this + } + + ungroup() { + return this + } +} + +register(Defs, 'Defs') diff --git a/src/elements/Dom.js b/src/elements/Dom.js new file mode 100644 index 000000000..604a1eeed --- /dev/null +++ b/src/elements/Dom.js @@ -0,0 +1,358 @@ +import { + adopt, + assignNewId, + eid, + extend, + makeInstance, + create, + register +} from '../utils/adopter.js' +import { find, findOne } from '../modules/core/selector.js' +import { globals } from '../utils/window.js' +import { map } from '../utils/utils.js' +import { svg, html } from '../modules/core/namespaces.js' +import EventTarget from '../types/EventTarget.js' +import List from '../types/List.js' +import attr from '../modules/core/attr.js' + +export default class Dom extends EventTarget { + constructor(node, attrs) { + super() + this.node = node + this.type = node.nodeName + + if (attrs && node !== attrs) { + this.attr(attrs) + } + } + + // Add given element at a position + add(element, i) { + element = makeInstance(element) + + // If non-root svg nodes are added we have to remove their namespaces + if ( + element.removeNamespace && + this.node instanceof globals.window.SVGElement + ) { + element.removeNamespace() + } + + if (i == null) { + this.node.appendChild(element.node) + } else if (element.node !== this.node.childNodes[i]) { + this.node.insertBefore(element.node, this.node.childNodes[i]) + } + + return this + } + + // Add element to given container and return self + addTo(parent, i) { + return makeInstance(parent).put(this, i) + } + + // Returns all child elements + children() { + return new List( + map(this.node.children, function (node) { + return adopt(node) + }) + ) + } + + // Remove all elements in this container + clear() { + // remove children + while (this.node.hasChildNodes()) { + this.node.removeChild(this.node.lastChild) + } + + return this + } + + // Clone element + clone(deep = true, assignNewIds = true) { + // write dom data to the dom so the clone can pickup the data + this.writeDataToDom() + + // clone element + let nodeClone = this.node.cloneNode(deep) + if (assignNewIds) { + // assign new id + nodeClone = assignNewId(nodeClone) + } + return new this.constructor(nodeClone) + } + + // Iterates over all children and invokes a given block + each(block, deep) { + const children = this.children() + let i, il + + for (i = 0, il = children.length; i < il; i++) { + block.apply(children[i], [i, children]) + + if (deep) { + children[i].each(block, deep) + } + } + + return this + } + + element(nodeName, attrs) { + return this.put(new Dom(create(nodeName), attrs)) + } + + // Get first child + first() { + return adopt(this.node.firstChild) + } + + // Get a element at the given index + get(i) { + return adopt(this.node.childNodes[i]) + } + + getEventHolder() { + return this.node + } + + getEventTarget() { + return this.node + } + + // Checks if the given element is a child + has(element) { + return this.index(element) >= 0 + } + + html(htmlOrFn, outerHTML) { + return this.xml(htmlOrFn, outerHTML, html) + } + + // Get / set id + id(id) { + // generate new id if no id set + if (typeof id === 'undefined' && !this.node.id) { + this.node.id = eid(this.type) + } + + // don't set directly with this.node.id to make `null` work correctly + return this.attr('id', id) + } + + // Gets index of given element + index(element) { + return [].slice.call(this.node.childNodes).indexOf(element.node) + } + + // Get the last child + last() { + return adopt(this.node.lastChild) + } + + // matches the element vs a css selector + matches(selector) { + const el = this.node + const matcher = + el.matches || + el.matchesSelector || + el.msMatchesSelector || + el.mozMatchesSelector || + el.webkitMatchesSelector || + el.oMatchesSelector || + null + return matcher && matcher.call(el, selector) + } + + // Returns the parent element instance + parent(type) { + let parent = this + + // check for parent + if (!parent.node.parentNode) return null + + // get parent element + parent = adopt(parent.node.parentNode) + + if (!type) return parent + + // loop through ancestors if type is given + do { + if ( + typeof type === 'string' ? parent.matches(type) : parent instanceof type + ) + return parent + } while ((parent = adopt(parent.node.parentNode))) + + return parent + } + + // Basically does the same as `add()` but returns the added element instead + put(element, i) { + element = makeInstance(element) + this.add(element, i) + return element + } + + // Add element to given container and return container + putIn(parent, i) { + return makeInstance(parent).add(this, i) + } + + // Remove element + remove() { + if (this.parent()) { + this.parent().removeElement(this) + } + + return this + } + + // Remove a given child + removeElement(element) { + this.node.removeChild(element.node) + + return this + } + + // Replace this with element + replace(element) { + element = makeInstance(element) + + if (this.node.parentNode) { + this.node.parentNode.replaceChild(element.node, this.node) + } + + return element + } + + round(precision = 2, map = null) { + const factor = 10 ** precision + const attrs = this.attr(map) + + for (const i in attrs) { + if (typeof attrs[i] === 'number') { + attrs[i] = Math.round(attrs[i] * factor) / factor + } + } + + this.attr(attrs) + return this + } + + // Import / Export raw svg + svg(svgOrFn, outerSVG) { + return this.xml(svgOrFn, outerSVG, svg) + } + + // Return id on string conversion + toString() { + return this.id() + } + + words(text) { + // This is faster than removing all children and adding a new one + this.node.textContent = text + return this + } + + wrap(node) { + const parent = this.parent() + + if (!parent) { + return this.addTo(node) + } + + const position = parent.index(this) + return parent.put(node, position).put(this) + } + + // write svgjs data to the dom + writeDataToDom() { + // dump variables recursively + this.each(function () { + this.writeDataToDom() + }) + + return this + } + + // Import / Export raw svg + xml(xmlOrFn, outerXML, ns) { + if (typeof xmlOrFn === 'boolean') { + ns = outerXML + outerXML = xmlOrFn + xmlOrFn = null + } + + // act as getter if no svg string is given + if (xmlOrFn == null || typeof xmlOrFn === 'function') { + // The default for exports is, that the outerNode is included + outerXML = outerXML == null ? true : outerXML + + // write svgjs data to the dom + this.writeDataToDom() + let current = this + + // An export modifier was passed + if (xmlOrFn != null) { + current = adopt(current.node.cloneNode(true)) + + // If the user wants outerHTML we need to process this node, too + if (outerXML) { + const result = xmlOrFn(current) + current = result || current + + // The user does not want this node? Well, then he gets nothing + if (result === false) return '' + } + + // Deep loop through all children and apply modifier + current.each(function () { + const result = xmlOrFn(this) + const _this = result || this + + // If modifier returns false, discard node + if (result === false) { + this.remove() + + // If modifier returns new node, use it + } else if (result && this !== _this) { + this.replace(_this) + } + }, true) + } + + // Return outer or inner content + return outerXML ? current.node.outerHTML : current.node.innerHTML + } + + // Act as setter if we got a string + + // The default for import is, that the current node is not replaced + outerXML = outerXML == null ? false : outerXML + + // Create temporary holder + const well = create('wrapper', ns) + const fragment = globals.document.createDocumentFragment() + + // Dump raw svg + well.innerHTML = xmlOrFn + + // Transplant nodes into the fragment + for (let len = well.children.length; len--; ) { + fragment.appendChild(well.firstElementChild) + } + + const parent = this.parent() + + // Add the whole fragment at once + return outerXML ? this.replace(fragment) && parent : this.add(fragment) + } +} + +extend(Dom, { attr, find, findOne }) +register(Dom, 'Dom') diff --git a/src/elements/Element.js b/src/elements/Element.js new file mode 100644 index 000000000..e3a421139 --- /dev/null +++ b/src/elements/Element.js @@ -0,0 +1,182 @@ +import { bbox, rbox, inside } from '../types/Box.js' +import { ctm, screenCTM } from '../types/Matrix.js' +import { + extend, + getClass, + makeInstance, + register, + root +} from '../utils/adopter.js' +import { globals } from '../utils/window.js' +import { point } from '../types/Point.js' +import { proportionalSize, writeDataToDom } from '../utils/utils.js' +import { reference } from '../modules/core/regex.js' +import Dom from './Dom.js' +import List from '../types/List.js' +import SVGNumber from '../types/SVGNumber.js' + +export default class Element extends Dom { + constructor(node, attrs) { + super(node, attrs) + + // initialize data object + this.dom = {} + + // create circular reference + this.node.instance = this + + if (node.hasAttribute('data-svgjs') || node.hasAttribute('svgjs:data')) { + // pull svgjs data from the dom (getAttributeNS doesn't work in html5) + this.setData( + JSON.parse(node.getAttribute('data-svgjs')) ?? + JSON.parse(node.getAttribute('svgjs:data')) ?? + {} + ) + } + } + + // Move element by its center + center(x, y) { + return this.cx(x).cy(y) + } + + // Move by center over x-axis + cx(x) { + return x == null + ? this.x() + this.width() / 2 + : this.x(x - this.width() / 2) + } + + // Move by center over y-axis + cy(y) { + return y == null + ? this.y() + this.height() / 2 + : this.y(y - this.height() / 2) + } + + // Get defs + defs() { + const root = this.root() + return root && root.defs() + } + + // Relative move over x and y axes + dmove(x, y) { + return this.dx(x).dy(y) + } + + // Relative move over x axis + dx(x = 0) { + return this.x(new SVGNumber(x).plus(this.x())) + } + + // Relative move over y axis + dy(y = 0) { + return this.y(new SVGNumber(y).plus(this.y())) + } + + getEventHolder() { + return this + } + + // Set height of element + height(height) { + return this.attr('height', height) + } + + // Move element to given x and y values + move(x, y) { + return this.x(x).y(y) + } + + // return array of all ancestors of given type up to the root svg + parents(until = this.root()) { + const isSelector = typeof until === 'string' + if (!isSelector) { + until = makeInstance(until) + } + const parents = new List() + let parent = this + + while ( + (parent = parent.parent()) && + parent.node !== globals.document && + parent.nodeName !== '#document-fragment' + ) { + parents.push(parent) + + if (!isSelector && parent.node === until.node) { + break + } + if (isSelector && parent.matches(until)) { + break + } + if (parent.node === this.root().node) { + // We worked our way to the root and didn't match `until` + return null + } + } + + return parents + } + + // Get referenced element form attribute value + reference(attr) { + attr = this.attr(attr) + if (!attr) return null + + const m = (attr + '').match(reference) + return m ? makeInstance(m[1]) : null + } + + // Get parent document + root() { + const p = this.parent(getClass(root)) + return p && p.root() + } + + // set given data to the elements data property + setData(o) { + this.dom = o + return this + } + + // Set element size to given width and height + size(width, height) { + const p = proportionalSize(this, width, height) + + return this.width(new SVGNumber(p.width)).height(new SVGNumber(p.height)) + } + + // Set width of element + width(width) { + return this.attr('width', width) + } + + // write svgjs data to the dom + writeDataToDom() { + writeDataToDom(this, this.dom) + return super.writeDataToDom() + } + + // Move over x-axis + x(x) { + return this.attr('x', x) + } + + // Move over y-axis + y(y) { + return this.attr('y', y) + } +} + +extend(Element, { + bbox, + rbox, + inside, + point, + ctm, + screenCTM +}) + +register(Element, 'Element') diff --git a/src/elements/Ellipse.js b/src/elements/Ellipse.js new file mode 100644 index 000000000..3f8b04b19 --- /dev/null +++ b/src/elements/Ellipse.js @@ -0,0 +1,36 @@ +import { + extend, + nodeOrNew, + register, + wrapWithAttrCheck +} from '../utils/adopter.js' +import { proportionalSize } from '../utils/utils.js' +import { registerMethods } from '../utils/methods.js' +import SVGNumber from '../types/SVGNumber.js' +import Shape from './Shape.js' +import * as circled from '../modules/core/circled.js' + +export default class Ellipse extends Shape { + constructor(node, attrs = node) { + super(nodeOrNew('ellipse', node), attrs) + } + + size(width, height) { + const p = proportionalSize(this, width, height) + + return this.rx(new SVGNumber(p.width).divide(2)).ry( + new SVGNumber(p.height).divide(2) + ) + } +} + +extend(Ellipse, circled) + +registerMethods('Container', { + // Create an ellipse + ellipse: wrapWithAttrCheck(function (width = 0, height = width) { + return this.put(new Ellipse()).size(width, height).move(0, 0) + }) +}) + +register(Ellipse, 'Ellipse') diff --git a/src/elements/ForeignObject.js b/src/elements/ForeignObject.js new file mode 100644 index 000000000..a4148d536 --- /dev/null +++ b/src/elements/ForeignObject.js @@ -0,0 +1,19 @@ +import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import Element from './Element.js' + +export default class ForeignObject extends Element { + constructor(node, attrs = node) { + super(nodeOrNew('foreignObject', node), attrs) + } +} + +registerMethods({ + Container: { + foreignObject: wrapWithAttrCheck(function (width, height) { + return this.put(new ForeignObject()).size(width, height) + }) + } +}) + +register(ForeignObject, 'ForeignObject') diff --git a/src/elements/Fragment.js b/src/elements/Fragment.js new file mode 100644 index 000000000..ece3046d0 --- /dev/null +++ b/src/elements/Fragment.js @@ -0,0 +1,34 @@ +import Dom from './Dom.js' +import { globals } from '../utils/window.js' +import { register, create } from '../utils/adopter.js' + +class Fragment extends Dom { + constructor(node = globals.document.createDocumentFragment()) { + super(node) + } + + // Import / Export raw xml + xml(xmlOrFn, outerXML, ns) { + if (typeof xmlOrFn === 'boolean') { + ns = outerXML + outerXML = xmlOrFn + xmlOrFn = null + } + + // because this is a fragment we have to put all elements into a wrapper first + // before we can get the innerXML from it + if (xmlOrFn == null || typeof xmlOrFn === 'function') { + const wrapper = new Dom(create('wrapper', ns)) + wrapper.add(this.node.cloneNode(true)) + + return wrapper.xml(false, ns) + } + + // Act as setter if we got a string + return super.xml(xmlOrFn, false, ns) + } +} + +register(Fragment, 'Fragment') + +export default Fragment diff --git a/src/elements/G.js b/src/elements/G.js new file mode 100644 index 000000000..4d3b03c29 --- /dev/null +++ b/src/elements/G.js @@ -0,0 +1,28 @@ +import { + nodeOrNew, + register, + wrapWithAttrCheck, + extend +} from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import Container from './Container.js' +import * as containerGeometry from '../modules/core/containerGeometry.js' + +export default class G extends Container { + constructor(node, attrs = node) { + super(nodeOrNew('g', node), attrs) + } +} + +extend(G, containerGeometry) + +registerMethods({ + Container: { + // Create a group element + group: wrapWithAttrCheck(function () { + return this.put(new G()) + }) + } +}) + +register(G, 'G') diff --git a/src/elements/Gradient.js b/src/elements/Gradient.js new file mode 100644 index 000000000..1631c1472 --- /dev/null +++ b/src/elements/Gradient.js @@ -0,0 +1,76 @@ +import { + extend, + nodeOrNew, + register, + wrapWithAttrCheck +} from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import Box from '../types/Box.js' +import Container from './Container.js' +import baseFind from '../modules/core/selector.js' +import * as gradiented from '../modules/core/gradiented.js' + +export default class Gradient extends Container { + constructor(type, attrs) { + super( + nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type), + attrs + ) + } + + // custom attr to handle transform + attr(a, b, c) { + if (a === 'transform') a = 'gradientTransform' + return super.attr(a, b, c) + } + + bbox() { + return new Box() + } + + targets() { + return baseFind('svg [fill*=' + this.id() + ']') + } + + // Alias string conversion to fill + toString() { + return this.url() + } + + // Update gradient + update(block) { + // remove all stops + this.clear() + + // invoke passed block + if (typeof block === 'function') { + block.call(this, this) + } + + return this + } + + // Return the fill id + url() { + return 'url(#' + this.id() + ')' + } +} + +extend(Gradient, gradiented) + +registerMethods({ + Container: { + // Create gradient element in defs + gradient(...args) { + return this.defs().gradient(...args) + } + }, + // define gradient + Defs: { + gradient: wrapWithAttrCheck(function (type, block) { + return this.put(new Gradient(type)).update(block) + }) + } +}) + +register(Gradient, 'Gradient') diff --git a/src/elements/Image.js b/src/elements/Image.js new file mode 100644 index 000000000..080da16c5 --- /dev/null +++ b/src/elements/Image.js @@ -0,0 +1,85 @@ +import { isImage } from '../modules/core/regex.js' +import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js' +import { off, on } from '../modules/core/event.js' +import { registerAttrHook } from '../modules/core/attr.js' +import { registerMethods } from '../utils/methods.js' +import { xlink } from '../modules/core/namespaces.js' +import Pattern from './Pattern.js' +import Shape from './Shape.js' +import { globals } from '../utils/window.js' + +export default class Image extends Shape { + constructor(node, attrs = node) { + super(nodeOrNew('image', node), attrs) + } + + // (re)load image + load(url, callback) { + if (!url) return this + + const img = new globals.window.Image() + + on( + img, + 'load', + function (e) { + const p = this.parent(Pattern) + + // ensure image size + if (this.width() === 0 && this.height() === 0) { + this.size(img.width, img.height) + } + + if (p instanceof Pattern) { + // ensure pattern size if not set + if (p.width() === 0 && p.height() === 0) { + p.size(this.width(), this.height()) + } + } + + if (typeof callback === 'function') { + callback.call(this, e) + } + }, + this + ) + + on(img, 'load error', function () { + // dont forget to unbind memory leaking events + off(img) + }) + + return this.attr('href', (img.src = url), xlink) + } +} + +registerAttrHook(function (attr, val, _this) { + // convert image fill and stroke to patterns + if (attr === 'fill' || attr === 'stroke') { + if (isImage.test(val)) { + val = _this.root().defs().image(val) + } + } + + if (val instanceof Image) { + val = _this + .root() + .defs() + .pattern(0, 0, (pattern) => { + pattern.add(val) + }) + } + + return val +}) + +registerMethods({ + Container: { + // create image element, load image and set its size + image: wrapWithAttrCheck(function (source, callback) { + return this.put(new Image()).size(0, 0).load(source, callback) + }) + } +}) + +register(Image, 'Image') diff --git a/src/elements/Line.js b/src/elements/Line.js new file mode 100644 index 000000000..0dab35ddd --- /dev/null +++ b/src/elements/Line.js @@ -0,0 +1,68 @@ +import { + extend, + nodeOrNew, + register, + wrapWithAttrCheck +} from '../utils/adopter.js' +import { proportionalSize } from '../utils/utils.js' +import { registerMethods } from '../utils/methods.js' +import PointArray from '../types/PointArray.js' +import Shape from './Shape.js' +import * as pointed from '../modules/core/pointed.js' + +export default class Line extends Shape { + // Initialize node + constructor(node, attrs = node) { + super(nodeOrNew('line', node), attrs) + } + + // Get array + array() { + return new PointArray([ + [this.attr('x1'), this.attr('y1')], + [this.attr('x2'), this.attr('y2')] + ]) + } + + // Move by left top corner + move(x, y) { + return this.attr(this.array().move(x, y).toLine()) + } + + // Overwrite native plot() method + plot(x1, y1, x2, y2) { + if (x1 == null) { + return this.array() + } else if (typeof y1 !== 'undefined') { + x1 = { x1, y1, x2, y2 } + } else { + x1 = new PointArray(x1).toLine() + } + + return this.attr(x1) + } + + // Set element size to given width and height + size(width, height) { + const p = proportionalSize(this, width, height) + return this.attr(this.array().size(p.width, p.height).toLine()) + } +} + +extend(Line, pointed) + +registerMethods({ + Container: { + // Create a line element + line: wrapWithAttrCheck(function (...args) { + // make sure plot is called as a setter + // x1 is not necessarily a number, it can also be an array, a string and a PointArray + return Line.prototype.plot.apply( + this.put(new Line()), + args[0] != null ? args : [0, 0, 0, 0] + ) + }) + } +}) + +register(Line, 'Line') diff --git a/src/elements/Marker.js b/src/elements/Marker.js new file mode 100644 index 000000000..5ddf80272 --- /dev/null +++ b/src/elements/Marker.js @@ -0,0 +1,88 @@ +import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import Container from './Container.js' + +export default class Marker extends Container { + // Initialize node + constructor(node, attrs = node) { + super(nodeOrNew('marker', node), attrs) + } + + // Set height of element + height(height) { + return this.attr('markerHeight', height) + } + + orient(orient) { + return this.attr('orient', orient) + } + + // Set marker refX and refY + ref(x, y) { + return this.attr('refX', x).attr('refY', y) + } + + // Return the fill id + toString() { + return 'url(#' + this.id() + ')' + } + + // Update marker + update(block) { + // remove all content + this.clear() + + // invoke passed block + if (typeof block === 'function') { + block.call(this, this) + } + + return this + } + + // Set width of element + width(width) { + return this.attr('markerWidth', width) + } +} + +registerMethods({ + Container: { + marker(...args) { + // Create marker element in defs + return this.defs().marker(...args) + } + }, + Defs: { + // Create marker + marker: wrapWithAttrCheck(function (width, height, block) { + // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto + return this.put(new Marker()) + .size(width, height) + .ref(width / 2, height / 2) + .viewbox(0, 0, width, height) + .attr('orient', 'auto') + .update(block) + }) + }, + marker: { + // Create and attach markers + marker(marker, width, height, block) { + let attr = ['marker'] + + // Build attribute name + if (marker !== 'all') attr.push(marker) + attr = attr.join('-') + + // Set marker attribute + marker = + arguments[1] instanceof Marker + ? arguments[1] + : this.defs().marker(width, height, block) + + return this.attr(attr, marker) + } + } +}) + +register(Marker, 'Marker') diff --git a/src/elements/Mask.js b/src/elements/Mask.js new file mode 100644 index 000000000..b8a2c9978 --- /dev/null +++ b/src/elements/Mask.js @@ -0,0 +1,56 @@ +import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import Container from './Container.js' +import baseFind from '../modules/core/selector.js' + +export default class Mask extends Container { + // Initialize node + constructor(node, attrs = node) { + super(nodeOrNew('mask', node), attrs) + } + + // Unmask all masked elements and remove itself + remove() { + // unmask all targets + this.targets().forEach(function (el) { + el.unmask() + }) + + // remove mask from parent + return super.remove() + } + + targets() { + return baseFind('svg [mask*=' + this.id() + ']') + } +} + +registerMethods({ + Container: { + mask: wrapWithAttrCheck(function () { + return this.defs().put(new Mask()) + }) + }, + Element: { + // Distribute mask to svg element + masker() { + return this.reference('mask') + }, + + maskWith(element) { + // use given mask or create a new one + const masker = + element instanceof Mask ? element : this.parent().mask().add(element) + + // apply mask + return this.attr('mask', 'url(#' + masker.id() + ')') + }, + + // Unmask element + unmask() { + return this.attr('mask', null) + } + } +}) + +register(Mask, 'Mask') diff --git a/src/elements/Path.js b/src/elements/Path.js new file mode 100644 index 000000000..ec9a19f1f --- /dev/null +++ b/src/elements/Path.js @@ -0,0 +1,84 @@ +import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js' +import { proportionalSize } from '../utils/utils.js' +import { registerMethods } from '../utils/methods.js' +import PathArray from '../types/PathArray.js' +import Shape from './Shape.js' + +export default class Path extends Shape { + // Initialize node + constructor(node, attrs = node) { + super(nodeOrNew('path', node), attrs) + } + + // Get array + array() { + return this._array || (this._array = new PathArray(this.attr('d'))) + } + + // Clear array cache + clear() { + delete this._array + return this + } + + // Set height of element + height(height) { + return height == null + ? this.bbox().height + : this.size(this.bbox().width, height) + } + + // Move by left top corner + move(x, y) { + return this.attr('d', this.array().move(x, y)) + } + + // Plot new path + plot(d) { + return d == null + ? this.array() + : this.clear().attr( + 'd', + typeof d === 'string' ? d : (this._array = new PathArray(d)) + ) + } + + // Set element size to given width and height + size(width, height) { + const p = proportionalSize(this, width, height) + return this.attr('d', this.array().size(p.width, p.height)) + } + + // Set width of element + width(width) { + return width == null + ? this.bbox().width + : this.size(width, this.bbox().height) + } + + // Move by left top corner over x-axis + x(x) { + return x == null ? this.bbox().x : this.move(x, this.bbox().y) + } + + // Move by left top corner over y-axis + y(y) { + return y == null ? this.bbox().y : this.move(this.bbox().x, y) + } +} + +// Define morphable array +Path.prototype.MorphArray = PathArray + +// Add parent method +registerMethods({ + Container: { + // Create a wrapped path element + path: wrapWithAttrCheck(function (d) { + // make sure plot is called as a setter + return this.put(new Path()).plot(d || new PathArray()) + }) + } +}) + +register(Path, 'Path') diff --git a/src/elements/Pattern.js b/src/elements/Pattern.js new file mode 100644 index 000000000..b42a83ab2 --- /dev/null +++ b/src/elements/Pattern.js @@ -0,0 +1,71 @@ +import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import Box from '../types/Box.js' +import Container from './Container.js' +import baseFind from '../modules/core/selector.js' + +export default class Pattern extends Container { + // Initialize node + constructor(node, attrs = node) { + super(nodeOrNew('pattern', node), attrs) + } + + // custom attr to handle transform + attr(a, b, c) { + if (a === 'transform') a = 'patternTransform' + return super.attr(a, b, c) + } + + bbox() { + return new Box() + } + + targets() { + return baseFind('svg [fill*=' + this.id() + ']') + } + + // Alias string conversion to fill + toString() { + return this.url() + } + + // Update pattern by rebuilding + update(block) { + // remove content + this.clear() + + // invoke passed block + if (typeof block === 'function') { + block.call(this, this) + } + + return this + } + + // Return the fill id + url() { + return 'url(#' + this.id() + ')' + } +} + +registerMethods({ + Container: { + // Create pattern element in defs + pattern(...args) { + return this.defs().pattern(...args) + } + }, + Defs: { + pattern: wrapWithAttrCheck(function (width, height, block) { + return this.put(new Pattern()).update(block).attr({ + x: 0, + y: 0, + width: width, + height: height, + patternUnits: 'userSpaceOnUse' + }) + }) + } +}) + +register(Pattern, 'Pattern') diff --git a/src/elements/Polygon.js b/src/elements/Polygon.js new file mode 100644 index 000000000..d64dcb35f --- /dev/null +++ b/src/elements/Polygon.js @@ -0,0 +1,32 @@ +import { + extend, + nodeOrNew, + register, + wrapWithAttrCheck +} from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import PointArray from '../types/PointArray.js' +import Shape from './Shape.js' +import * as pointed from '../modules/core/pointed.js' +import * as poly from '../modules/core/poly.js' + +export default class Polygon extends Shape { + // Initialize node + constructor(node, attrs = node) { + super(nodeOrNew('polygon', node), attrs) + } +} + +registerMethods({ + Container: { + // Create a wrapped polygon element + polygon: wrapWithAttrCheck(function (p) { + // make sure plot is called as a setter + return this.put(new Polygon()).plot(p || new PointArray()) + }) + } +}) + +extend(Polygon, pointed) +extend(Polygon, poly) +register(Polygon, 'Polygon') diff --git a/src/elements/Polyline.js b/src/elements/Polyline.js new file mode 100644 index 000000000..2f063f249 --- /dev/null +++ b/src/elements/Polyline.js @@ -0,0 +1,32 @@ +import { + extend, + nodeOrNew, + register, + wrapWithAttrCheck +} from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import PointArray from '../types/PointArray.js' +import Shape from './Shape.js' +import * as pointed from '../modules/core/pointed.js' +import * as poly from '../modules/core/poly.js' + +export default class Polyline extends Shape { + // Initialize node + constructor(node, attrs = node) { + super(nodeOrNew('polyline', node), attrs) + } +} + +registerMethods({ + Container: { + // Create a wrapped polygon element + polyline: wrapWithAttrCheck(function (p) { + // make sure plot is called as a setter + return this.put(new Polyline()).plot(p || new PointArray()) + }) + } +}) + +extend(Polyline, pointed) +extend(Polyline, poly) +register(Polyline, 'Polyline') diff --git a/src/elements/Rect.js b/src/elements/Rect.js new file mode 100644 index 000000000..749cf6dc9 --- /dev/null +++ b/src/elements/Rect.js @@ -0,0 +1,29 @@ +import { + extend, + nodeOrNew, + register, + wrapWithAttrCheck +} from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import { rx, ry } from '../modules/core/circled.js' +import Shape from './Shape.js' + +export default class Rect extends Shape { + // Initialize node + constructor(node, attrs = node) { + super(nodeOrNew('rect', node), attrs) + } +} + +extend(Rect, { rx, ry }) + +registerMethods({ + Container: { + // Create a rect element + rect: wrapWithAttrCheck(function (width, height) { + return this.put(new Rect()).size(width, height) + }) + } +}) + +register(Rect, 'Rect') diff --git a/src/elements/Shape.js b/src/elements/Shape.js new file mode 100644 index 000000000..25ab6ccc8 --- /dev/null +++ b/src/elements/Shape.js @@ -0,0 +1,6 @@ +import { register } from '../utils/adopter.js' +import Element from './Element.js' + +export default class Shape extends Element {} + +register(Shape, 'Shape') diff --git a/src/elements/Stop.js b/src/elements/Stop.js new file mode 100644 index 000000000..193256ef0 --- /dev/null +++ b/src/elements/Stop.js @@ -0,0 +1,39 @@ +import { nodeOrNew, register } from '../utils/adopter.js' +import Element from './Element.js' +import SVGNumber from '../types/SVGNumber.js' +import { registerMethods } from '../utils/methods.js' + +export default class Stop extends Element { + constructor(node, attrs = node) { + super(nodeOrNew('stop', node), attrs) + } + + // add color stops + update(o) { + if (typeof o === 'number' || o instanceof SVGNumber) { + o = { + offset: arguments[0], + color: arguments[1], + opacity: arguments[2] + } + } + + // set attributes + if (o.opacity != null) this.attr('stop-opacity', o.opacity) + if (o.color != null) this.attr('stop-color', o.color) + if (o.offset != null) this.attr('offset', new SVGNumber(o.offset)) + + return this + } +} + +registerMethods({ + Gradient: { + // Add a color stop + stop: function (offset, color, opacity) { + return this.put(new Stop()).update(offset, color, opacity) + } + } +}) + +register(Stop, 'Stop') diff --git a/src/elements/Style.js b/src/elements/Style.js new file mode 100644 index 000000000..fc1a27eed --- /dev/null +++ b/src/elements/Style.js @@ -0,0 +1,53 @@ +import { nodeOrNew, register } from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import { unCamelCase } from '../utils/utils.js' +import Element from './Element.js' + +function cssRule(selector, rule) { + if (!selector) return '' + if (!rule) return selector + + let ret = selector + '{' + + for (const i in rule) { + ret += unCamelCase(i) + ':' + rule[i] + ';' + } + + ret += '}' + + return ret +} + +export default class Style extends Element { + constructor(node, attrs = node) { + super(nodeOrNew('style', node), attrs) + } + + addText(w = '') { + this.node.textContent += w + return this + } + + font(name, src, params = {}) { + return this.rule('@font-face', { + fontFamily: name, + src: src, + ...params + }) + } + + rule(selector, obj) { + return this.addText(cssRule(selector, obj)) + } +} + +registerMethods('Dom', { + style(selector, obj) { + return this.put(new Style()).rule(selector, obj) + }, + fontface(name, src, params) { + return this.put(new Style()).font(name, src, params) + } +}) + +register(Style, 'Style') diff --git a/src/elements/Svg.js b/src/elements/Svg.js new file mode 100644 index 000000000..40d50c9f3 --- /dev/null +++ b/src/elements/Svg.js @@ -0,0 +1,67 @@ +import { + adopt, + nodeOrNew, + register, + wrapWithAttrCheck +} from '../utils/adopter.js' +import { svg, xlink, xmlns } from '../modules/core/namespaces.js' +import { registerMethods } from '../utils/methods.js' +import Container from './Container.js' +import Defs from './Defs.js' +import { globals } from '../utils/window.js' + +export default class Svg extends Container { + constructor(node, attrs = node) { + super(nodeOrNew('svg', node), attrs) + this.namespace() + } + + // Creates and returns defs element + defs() { + if (!this.isRoot()) return this.root().defs() + + return adopt(this.node.querySelector('defs')) || this.put(new Defs()) + } + + isRoot() { + return ( + !this.node.parentNode || + (!(this.node.parentNode instanceof globals.window.SVGElement) && + this.node.parentNode.nodeName !== '#document-fragment') + ) + } + + // Add namespaces + namespace() { + if (!this.isRoot()) return this.root().namespace() + return this.attr({ xmlns: svg, version: '1.1' }).attr( + 'xmlns:xlink', + xlink, + xmlns + ) + } + + removeNamespace() { + return this.attr({ xmlns: null, version: null }) + .attr('xmlns:xlink', null, xmlns) + .attr('xmlns:svgjs', null, xmlns) + } + + // Check if this is a root svg + // If not, call root() from this element + root() { + if (this.isRoot()) return this + return super.root() + } +} + +registerMethods({ + Container: { + // Create nested svg document + nested: wrapWithAttrCheck(function () { + return this.put(new Svg()) + }) + } +}) + +register(Svg, 'Svg', true) diff --git a/src/elements/Symbol.js b/src/elements/Symbol.js new file mode 100644 index 000000000..28ad20678 --- /dev/null +++ b/src/elements/Symbol.js @@ -0,0 +1,20 @@ +import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import Container from './Container.js' + +export default class Symbol extends Container { + // Initialize node + constructor(node, attrs = node) { + super(nodeOrNew('symbol', node), attrs) + } +} + +registerMethods({ + Container: { + symbol: wrapWithAttrCheck(function () { + return this.put(new Symbol()) + }) + } +}) + +register(Symbol, 'Symbol') diff --git a/src/elements/Text.js b/src/elements/Text.js new file mode 100644 index 000000000..c703e3bd7 --- /dev/null +++ b/src/elements/Text.js @@ -0,0 +1,158 @@ +import { + adopt, + extend, + nodeOrNew, + register, + wrapWithAttrCheck +} from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import SVGNumber from '../types/SVGNumber.js' +import Shape from './Shape.js' +import { globals } from '../utils/window.js' +import * as textable from '../modules/core/textable.js' +import { isDescriptive, writeDataToDom } from '../utils/utils.js' + +export default class Text extends Shape { + // Initialize node + constructor(node, attrs = node) { + super(nodeOrNew('text', node), attrs) + + this.dom.leading = this.dom.leading ?? new SVGNumber(1.3) // store leading value for rebuilding + this._rebuild = true // enable automatic updating of dy values + this._build = false // disable build mode for adding multiple lines + } + + // Set / get leading + leading(value) { + // act as getter + if (value == null) { + return this.dom.leading + } + + // act as setter + this.dom.leading = new SVGNumber(value) + + return this.rebuild() + } + + // Rebuild appearance type + rebuild(rebuild) { + // store new rebuild flag if given + if (typeof rebuild === 'boolean') { + this._rebuild = rebuild + } + + // define position of all lines + if (this._rebuild) { + const self = this + let blankLineOffset = 0 + const leading = this.dom.leading + + this.each(function (i) { + if (isDescriptive(this.node)) return + + const fontSize = globals.window + .getComputedStyle(this.node) + .getPropertyValue('font-size') + + const dy = leading * new SVGNumber(fontSize) + + if (this.dom.newLined) { + this.attr('x', self.attr('x')) + + if (this.text() === '\n') { + blankLineOffset += dy + } else { + this.attr('dy', i ? dy + blankLineOffset : 0) + blankLineOffset = 0 + } + } + }) + + this.fire('rebuild') + } + + return this + } + + // overwrite method from parent to set data properly + setData(o) { + this.dom = o + this.dom.leading = new SVGNumber(o.leading || 1.3) + return this + } + + writeDataToDom() { + writeDataToDom(this, this.dom, { leading: 1.3 }) + return this + } + + // Set the text content + text(text) { + // act as getter + if (text === undefined) { + const children = this.node.childNodes + let firstLine = 0 + text = '' + + for (let i = 0, len = children.length; i < len; ++i) { + // skip textPaths - they are no lines + if (children[i].nodeName === 'textPath' || isDescriptive(children[i])) { + if (i === 0) firstLine = i + 1 + continue + } + + // add newline if its not the first child and newLined is set to true + if ( + i !== firstLine && + children[i].nodeType !== 3 && + adopt(children[i]).dom.newLined === true + ) { + text += '\n' + } + + // add content of this node + text += children[i].textContent + } + + return text + } + + // remove existing content + this.clear().build(true) + + if (typeof text === 'function') { + // call block + text.call(this, this) + } else { + // store text and make sure text is not blank + text = (text + '').split('\n') + + // build new lines + for (let j = 0, jl = text.length; j < jl; j++) { + this.newLine(text[j]) + } + } + + // disable build mode and rebuild lines + return this.build(false).rebuild() + } +} + +extend(Text, textable) + +registerMethods({ + Container: { + // Create text element + text: wrapWithAttrCheck(function (text = '') { + return this.put(new Text()).text(text) + }), + + // Create plain text element + plain: wrapWithAttrCheck(function (text = '') { + return this.put(new Text()).plain(text) + }) + } +}) + +register(Text, 'Text') diff --git a/src/elements/TextPath.js b/src/elements/TextPath.js new file mode 100644 index 000000000..89c6c426f --- /dev/null +++ b/src/elements/TextPath.js @@ -0,0 +1,106 @@ +import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import { xlink } from '../modules/core/namespaces.js' +import Path from './Path.js' +import PathArray from '../types/PathArray.js' +import Text from './Text.js' +import baseFind from '../modules/core/selector.js' + +export default class TextPath extends Text { + // Initialize node + constructor(node, attrs = node) { + super(nodeOrNew('textPath', node), attrs) + } + + // return the array of the path track element + array() { + const track = this.track() + + return track ? track.array() : null + } + + // Plot path if any + plot(d) { + const track = this.track() + let pathArray = null + + if (track) { + pathArray = track.plot(d) + } + + return d == null ? pathArray : this + } + + // Get the path element + track() { + return this.reference('href') + } +} + +registerMethods({ + Container: { + textPath: wrapWithAttrCheck(function (text, path) { + // Convert text to instance if needed + if (!(text instanceof Text)) { + text = this.text(text) + } + + return text.path(path) + }) + }, + Text: { + // Create path for text to run on + path: wrapWithAttrCheck(function (track, importNodes = true) { + const textPath = new TextPath() + + // if track is a path, reuse it + if (!(track instanceof Path)) { + // create path element + track = this.defs().path(track) + } + + // link textPath to path and add content + textPath.attr('href', '#' + track, xlink) + + // Transplant all nodes from text to textPath + let node + if (importNodes) { + while ((node = this.node.firstChild)) { + textPath.node.appendChild(node) + } + } + + // add textPath element as child node and return textPath + return this.put(textPath) + }), + + // Get the textPath children + textPath() { + return this.findOne('textPath') + } + }, + Path: { + // creates a textPath from this path + text: wrapWithAttrCheck(function (text) { + // Convert text to instance if needed + if (!(text instanceof Text)) { + text = new Text().addTo(this.parent()).text(text) + } + + // Create textPath from text and path and return + return text.path(this) + }), + + targets() { + return baseFind('svg textPath').filter((node) => { + return (node.attr('href') || '').includes(this.id()) + }) + + // Does not work in IE11. Use when IE support is dropped + // return baseFind('svg textPath[*|href*=' + this.id() + ']') + } + } +}) + +TextPath.prototype.MorphArray = PathArray +register(TextPath, 'TextPath') diff --git a/src/elements/Tspan.js b/src/elements/Tspan.js new file mode 100644 index 000000000..12b49f892 --- /dev/null +++ b/src/elements/Tspan.js @@ -0,0 +1,95 @@ +import { + extend, + nodeOrNew, + register, + wrapWithAttrCheck +} from '../utils/adopter.js' +import { globals } from '../utils/window.js' +import { registerMethods } from '../utils/methods.js' +import SVGNumber from '../types/SVGNumber.js' +import Shape from './Shape.js' +import Text from './Text.js' +import * as textable from '../modules/core/textable.js' + +export default class Tspan extends Shape { + // Initialize node + constructor(node, attrs = node) { + super(nodeOrNew('tspan', node), attrs) + this._build = false // disable build mode for adding multiple lines + } + + // Shortcut dx + dx(dx) { + return this.attr('dx', dx) + } + + // Shortcut dy + dy(dy) { + return this.attr('dy', dy) + } + + // Create new line + newLine() { + // mark new line + this.dom.newLined = true + + // fetch parent + const text = this.parent() + + // early return in case we are not in a text element + if (!(text instanceof Text)) { + return this + } + + const i = text.index(this) + + const fontSize = globals.window + .getComputedStyle(this.node) + .getPropertyValue('font-size') + const dy = text.dom.leading * new SVGNumber(fontSize) + + // apply new position + return this.dy(i ? dy : 0).attr('x', text.x()) + } + + // Set text content + text(text) { + if (text == null) + return this.node.textContent + (this.dom.newLined ? '\n' : '') + + if (typeof text === 'function') { + this.clear().build(true) + text.call(this, this) + this.build(false) + } else { + this.plain(text) + } + + return this + } +} + +extend(Tspan, textable) + +registerMethods({ + Tspan: { + tspan: wrapWithAttrCheck(function (text = '') { + const tspan = new Tspan() + + // clear if build mode is disabled + if (!this._build) { + this.clear() + } + + // add new tspan + return this.put(tspan).text(text) + }) + }, + Text: { + newLine: function (text = '') { + return this.tspan(text).newLine() + } + } +}) + +register(Tspan, 'Tspan') diff --git a/src/elements/Use.js b/src/elements/Use.js new file mode 100644 index 000000000..e92dd4806 --- /dev/null +++ b/src/elements/Use.js @@ -0,0 +1,27 @@ +import { nodeOrNew, register, wrapWithAttrCheck } from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import { xlink } from '../modules/core/namespaces.js' +import Shape from './Shape.js' + +export default class Use extends Shape { + constructor(node, attrs = node) { + super(nodeOrNew('use', node), attrs) + } + + // Use element as a reference + use(element, file) { + // Set lined element + return this.attr('href', (file || '') + '#' + element, xlink) + } +} + +registerMethods({ + Container: { + // Create a use element + use: wrapWithAttrCheck(function (element, file) { + return this.put(new Use()).use(element, file) + }) + } +}) + +register(Use, 'Use') diff --git a/src/ellipse.js b/src/ellipse.js deleted file mode 100644 index 043084cce..000000000 --- a/src/ellipse.js +++ /dev/null @@ -1,89 +0,0 @@ -SVG.Circle = SVG.invent({ - // Initialize node - create: 'circle' - - // Inherit from -, inherit: SVG.Shape - - // Add parent method -, construct: { - // Create circle element, based on ellipse - circle: function(size) { - return this.put(new SVG.Circle).rx(new SVG.Number(size).divide(2)).move(0, 0) - } - } -}) - -SVG.extend(SVG.Circle, SVG.FX, { - // Radius x value - rx: function(rx) { - return this.attr('r', rx) - } - // Alias radius x value -, ry: function(ry) { - return this.rx(ry) - } -}) - -SVG.Ellipse = SVG.invent({ - // Initialize node - create: 'ellipse' - - // Inherit from -, inherit: SVG.Shape - - // Add parent method -, construct: { - // Create an ellipse - ellipse: function(width, height) { - return this.put(new SVG.Ellipse).size(width, height).move(0, 0) - } - } -}) - -SVG.extend(SVG.Ellipse, SVG.Rect, SVG.FX, { - // Radius x value - rx: function(rx) { - return this.attr('rx', rx) - } - // Radius y value -, ry: function(ry) { - return this.attr('ry', ry) - } -}) - -// Add common method -SVG.extend(SVG.Circle, SVG.Ellipse, { - // Move over x-axis - x: function(x) { - return x == null ? this.cx() - this.rx() : this.cx(x + this.rx()) - } - // Move over y-axis - , y: function(y) { - return y == null ? this.cy() - this.ry() : this.cy(y + this.ry()) - } - // Move by center over x-axis - , cx: function(x) { - return x == null ? this.attr('cx') : this.attr('cx', x) - } - // Move by center over y-axis - , cy: function(y) { - return y == null ? this.attr('cy') : this.attr('cy', y) - } - // Set width of element - , width: function(width) { - return width == null ? this.rx() * 2 : this.rx(new SVG.Number(width).divide(2)) - } - // Set height of element - , height: function(height) { - return height == null ? this.ry() * 2 : this.ry(new SVG.Number(height).divide(2)) - } - // Custom size function - , size: function(width, height) { - var p = proportionalSize(this, width, height) - - return this - .rx(new SVG.Number(p.width).divide(2)) - .ry(new SVG.Number(p.height).divide(2)) - } -}) \ No newline at end of file diff --git a/src/event.js b/src/event.js deleted file mode 100644 index 60f7a698f..000000000 --- a/src/event.js +++ /dev/null @@ -1,144 +0,0 @@ -// Add events to elements -;[ 'click' - , 'dblclick' - , 'mousedown' - , 'mouseup' - , 'mouseover' - , 'mouseout' - , 'mousemove' - // , 'mouseenter' -> not supported by IE - // , 'mouseleave' -> not supported by IE - , 'touchstart' - , 'touchmove' - , 'touchleave' - , 'touchend' - , 'touchcancel' ].forEach(function(event) { - - // add event to SVG.Element - SVG.Element.prototype[event] = function(f) { - // bind event to element rather than element node - SVG.on(this.node, event, f) - return this - } -}) - -// Initialize listeners stack -SVG.listeners = [] -SVG.handlerMap = [] -SVG.listenerId = 0 - -// Add event binder in the SVG namespace -SVG.on = function(node, event, listener, binding, options) { - // create listener, get object-index - var l = listener.bind(binding || node.instance || node) - , index = (SVG.handlerMap.indexOf(node) + 1 || SVG.handlerMap.push(node)) - 1 - , ev = event.split('.')[0] - , ns = event.split('.')[1] || '*' - - - // ensure valid object - SVG.listeners[index] = SVG.listeners[index] || {} - SVG.listeners[index][ev] = SVG.listeners[index][ev] || {} - SVG.listeners[index][ev][ns] = SVG.listeners[index][ev][ns] || {} - - if(!listener._svgjsListenerId) - listener._svgjsListenerId = ++SVG.listenerId - - // reference listener - SVG.listeners[index][ev][ns][listener._svgjsListenerId] = l - - // add listener - node.addEventListener(ev, l, options || false) -} - -// Add event unbinder in the SVG namespace -SVG.off = function(node, event, listener) { - var index = SVG.handlerMap.indexOf(node) - , ev = event && event.split('.')[0] - , ns = event && event.split('.')[1] - , namespace = '' - - if(index == -1) return - - if (listener) { - if(typeof listener == 'function') listener = listener._svgjsListenerId - if(!listener) return - - // remove listener reference - if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns || '*']) { - // remove listener - node.removeEventListener(ev, SVG.listeners[index][ev][ns || '*'][listener], false) - - delete SVG.listeners[index][ev][ns || '*'][listener] - } - - } else if (ns && ev) { - // remove all listeners for a namespaced event - if (SVG.listeners[index][ev] && SVG.listeners[index][ev][ns]) { - for (listener in SVG.listeners[index][ev][ns]) - SVG.off(node, [ev, ns].join('.'), listener) - - delete SVG.listeners[index][ev][ns] - } - - } else if (ns){ - // remove all listeners for a specific namespace - for(event in SVG.listeners[index]){ - for(namespace in SVG.listeners[index][event]){ - if(ns === namespace){ - SVG.off(node, [event, ns].join('.')) - } - } - } - - } else if (ev) { - // remove all listeners for the event - if (SVG.listeners[index][ev]) { - for (namespace in SVG.listeners[index][ev]) - SVG.off(node, [ev, namespace].join('.')) - - delete SVG.listeners[index][ev] - } - - } else { - // remove all listeners on a given node - for (event in SVG.listeners[index]) - SVG.off(node, event) - - delete SVG.listeners[index] - delete SVG.handlerMap[index] - - } -} - -// -SVG.extend(SVG.Element, { - // Bind given event to listener - on: function(event, listener, binding, options) { - SVG.on(this.node, event, listener, binding, options) - - return this - } - // Unbind event from listener -, off: function(event, listener) { - SVG.off(this.node, event, listener) - - return this - } - // Fire given event -, fire: function(event, data) { - - // Dispatch event - if(event instanceof window.Event){ - this.node.dispatchEvent(event) - }else{ - this.node.dispatchEvent(event = new window.CustomEvent(event, {detail:data, cancelable: true})) - } - - this._event = event - return this - } -, event: function() { - return this._event - } -}) diff --git a/src/fx.js b/src/fx.js deleted file mode 100644 index 4f2a9a921..000000000 --- a/src/fx.js +++ /dev/null @@ -1,915 +0,0 @@ -SVG.easing = { - '-': function(pos){return pos} -, '<>':function(pos){return -Math.cos(pos * Math.PI) / 2 + 0.5} -, '>': function(pos){return Math.sin(pos * Math.PI / 2)} -, '<': function(pos){return -Math.cos(pos * Math.PI / 2) + 1} -} - -SVG.morph = function(pos){ - return function(from, to) { - return new SVG.MorphObj(from, to).at(pos) - } -} - -SVG.Situation = SVG.invent({ - - create: function(o){ - this.init = false - this.reversed = false - this.reversing = false - - this.duration = new SVG.Number(o.duration).valueOf() - this.delay = new SVG.Number(o.delay).valueOf() - - this.start = +new Date() + this.delay - this.finish = this.start + this.duration - this.ease = o.ease - - // this.loop is incremented from 0 to this.loops - // it is also incremented when in an infinite loop (when this.loops is true) - this.loop = 0 - this.loops = false - - this.animations = { - // functionToCall: [list of morphable objects] - // e.g. move: [SVG.Number, SVG.Number] - } - - this.attrs = { - // holds all attributes which are not represented from a function svg.js provides - // e.g. someAttr: SVG.Number - } - - this.styles = { - // holds all styles which should be animated - // e.g. fill-color: SVG.Color - } - - this.transforms = [ - // holds all transformations as transformation objects - // e.g. [SVG.Rotate, SVG.Translate, SVG.Matrix] - ] - - this.once = { - // functions to fire at a specific position - // e.g. "0.5": function foo(){} - } - - } - -}) - - -SVG.FX = SVG.invent({ - - create: function(element) { - this._target = element - this.situations = [] - this.active = false - this.situation = null - this.paused = false - this.lastPos = 0 - this.pos = 0 - // The absolute position of an animation is its position in the context of its complete duration (including delay and loops) - // When performing a delay, absPos is below 0 and when performing a loop, its value is above 1 - this.absPos = 0 - this._speed = 1 - } - -, extend: { - - /** - * sets or returns the target of this animation - * @param o object || number In case of Object it holds all parameters. In case of number its the duration of the animation - * @param ease function || string Function which should be used for easing or easing keyword - * @param delay Number indicating the delay before the animation starts - * @return target || this - */ - animate: function(o, ease, delay){ - - if(typeof o == 'object'){ - ease = o.ease - delay = o.delay - o = o.duration - } - - var situation = new SVG.Situation({ - duration: o || 1000, - delay: delay || 0, - ease: SVG.easing[ease || '-'] || ease - }) - - this.queue(situation) - - return this - } - - /** - * sets a delay before the next element of the queue is called - * @param delay Duration of delay in milliseconds - * @return this.target() - */ - , delay: function(delay){ - // The delay is performed by an empty situation with its duration - // attribute set to the duration of the delay - var situation = new SVG.Situation({ - duration: delay, - delay: 0, - ease: SVG.easing['-'] - }) - - return this.queue(situation) - } - - /** - * sets or returns the target of this animation - * @param null || target SVG.Element which should be set as new target - * @return target || this - */ - , target: function(target){ - if(target && target instanceof SVG.Element){ - this._target = target - return this - } - - return this._target - } - - // returns the absolute position at a given time - , timeToAbsPos: function(timestamp){ - return (timestamp - this.situation.start) / (this.situation.duration/this._speed) - } - - // returns the timestamp from a given absolute positon - , absPosToTime: function(absPos){ - return this.situation.duration/this._speed * absPos + this.situation.start - } - - // starts the animationloop - , startAnimFrame: function(){ - this.stopAnimFrame() - this.animationFrame = window.requestAnimationFrame(function(){ this.step() }.bind(this)) - } - - // cancels the animationframe - , stopAnimFrame: function(){ - window.cancelAnimationFrame(this.animationFrame) - } - - // kicks off the animation - only does something when the queue is currently not active and at least one situation is set - , start: function(){ - // dont start if already started - if(!this.active && this.situation){ - this.active = true - this.startCurrent() - } - - return this - } - - // start the current situation - , startCurrent: function(){ - this.situation.start = +new Date + this.situation.delay/this._speed - this.situation.finish = this.situation.start + this.situation.duration/this._speed - return this.initAnimations().step() - } - - /** - * adds a function / Situation to the animation queue - * @param fn function / situation to add - * @return this - */ - , queue: function(fn){ - if(typeof fn == 'function' || fn instanceof SVG.Situation) - this.situations.push(fn) - - if(!this.situation) this.situation = this.situations.shift() - - return this - } - - /** - * pulls next element from the queue and execute it - * @return this - */ - , dequeue: function(){ - // stop current animation - this.stop() - - // get next animation from queue - this.situation = this.situations.shift() - - if(this.situation){ - if(this.situation instanceof SVG.Situation) { - this.start() - } else { - // If it is not a SVG.Situation, then it is a function, we execute it - this.situation.call(this) - } - } - - return this - } - - // updates all animations to the current state of the element - // this is important when one property could be changed from another property - , initAnimations: function() { - var i, j, source - var s = this.situation - - if(s.init) return this - - for(i in s.animations){ - source = this.target()[i]() - - if(!Array.isArray(source)) { - source = [source] - } - - if(!Array.isArray(s.animations[i])) { - s.animations[i] = [s.animations[i]] - } - - //if(s.animations[i].length > source.length) { - // source.concat = source.concat(s.animations[i].slice(source.length, s.animations[i].length)) - //} - - for(j = source.length; j--;) { - // The condition is because some methods return a normal number instead - // of a SVG.Number - if(s.animations[i][j] instanceof SVG.Number) - source[j] = new SVG.Number(source[j]) - - s.animations[i][j] = source[j].morph(s.animations[i][j]) - } - } - - for(i in s.attrs){ - s.attrs[i] = new SVG.MorphObj(this.target().attr(i), s.attrs[i]) - } - - for(i in s.styles){ - s.styles[i] = new SVG.MorphObj(this.target().style(i), s.styles[i]) - } - - s.initialTransformation = this.target().matrixify() - - s.init = true - return this - } - , clearQueue: function(){ - this.situations = [] - return this - } - , clearCurrent: function(){ - this.situation = null - return this - } - /** stops the animation immediately - * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately. - * @param clearQueue A Boolean indicating whether to remove queued animation as well. - * @return this - */ - , stop: function(jumpToEnd, clearQueue){ - var active = this.active - this.active = false - - if(clearQueue){ - this.clearQueue() - } - - if(jumpToEnd && this.situation){ - // initialize the situation if it was not - !active && this.startCurrent() - this.atEnd() - } - - this.stopAnimFrame() - - return this.clearCurrent() - } - - /** resets the element to the state where the current element has started - * @return this - */ - , reset: function(){ - if(this.situation){ - var temp = this.situation - this.stop() - this.situation = temp - this.atStart() - } - return this - } - - // Stop the currently-running animation, remove all queued animations, and complete all animations for the element. - , finish: function(){ - - this.stop(true, false) - - while(this.dequeue().situation && this.stop(true, false)); - - this.clearQueue().clearCurrent() - - return this - } - - // set the internal animation pointer at the start position, before any loops, and updates the visualisation - , atStart: function() { - return this.at(0, true) - } - - // set the internal animation pointer at the end position, after all the loops, and updates the visualisation - , atEnd: function() { - if (this.situation.loops === true) { - // If in a infinite loop, we end the current iteration - this.situation.loops = this.situation.loop + 1 - } - - if(typeof this.situation.loops == 'number') { - // If performing a finite number of loops, we go after all the loops - return this.at(this.situation.loops, true) - } else { - // If no loops, we just go at the end - return this.at(1, true) - } - } - - // set the internal animation pointer to the specified position and updates the visualisation - // if isAbsPos is true, pos is treated as an absolute position - , at: function(pos, isAbsPos){ - var durDivSpd = this.situation.duration/this._speed - - this.absPos = pos - // If pos is not an absolute position, we convert it into one - if (!isAbsPos) { - if (this.situation.reversed) this.absPos = 1 - this.absPos - this.absPos += this.situation.loop - } - - this.situation.start = +new Date - this.absPos * durDivSpd - this.situation.finish = this.situation.start + durDivSpd - - return this.step(true) - } - - /** - * sets or returns the speed of the animations - * @param speed null || Number The new speed of the animations - * @return Number || this - */ - , speed: function(speed){ - if (speed === 0) return this.pause() - - if (speed) { - this._speed = speed - // We use an absolute position here so that speed can affect the delay before the animation - return this.at(this.absPos, true) - } else return this._speed - } - - // Make loopable - , loop: function(times, reverse) { - var c = this.last() - - // store total loops - c.loops = (times != null) ? times : true - c.loop = 0 - - if(reverse) c.reversing = true - return this - } - - // pauses the animation - , pause: function(){ - this.paused = true - this.stopAnimFrame() - - return this - } - - // unpause the animation - , play: function(){ - if(!this.paused) return this - this.paused = false - // We use an absolute position here so that the delay before the animation can be paused - return this.at(this.absPos, true) - } - - /** - * toggle or set the direction of the animation - * true sets direction to backwards while false sets it to forwards - * @param reversed Boolean indicating whether to reverse the animation or not (default: toggle the reverse status) - * @return this - */ - , reverse: function(reversed){ - var c = this.last() - - if(typeof reversed == 'undefined') c.reversed = !c.reversed - else c.reversed = reversed - - return this - } - - - /** - * returns a float from 0-1 indicating the progress of the current animation - * @param eased Boolean indicating whether the returned position should be eased or not - * @return number - */ - , progress: function(easeIt){ - return easeIt ? this.situation.ease(this.pos) : this.pos - } - - /** - * adds a callback function which is called when the current animation is finished - * @param fn Function which should be executed as callback - * @return number - */ - , after: function(fn){ - var c = this.last() - , wrapper = function wrapper(e){ - if(e.detail.situation == c){ - fn.call(this, c) - this.off('finished.fx', wrapper) // prevent memory leak - } - } - - this.target().on('finished.fx', wrapper) - - return this._callStart() - } - - // adds a callback which is called whenever one animation step is performed - , during: function(fn){ - var c = this.last() - , wrapper = function(e){ - if(e.detail.situation == c){ - fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, c) - } - } - - // see above - this.target().off('during.fx', wrapper).on('during.fx', wrapper) - - this.after(function(){ - this.off('during.fx', wrapper) - }) - - return this._callStart() - } - - // calls after ALL animations in the queue are finished - , afterAll: function(fn){ - var wrapper = function wrapper(e){ - fn.call(this) - this.off('allfinished.fx', wrapper) - } - - // see above - this.target().off('allfinished.fx', wrapper).on('allfinished.fx', wrapper) - - return this._callStart() - } - - // calls on every animation step for all animations - , duringAll: function(fn){ - var wrapper = function(e){ - fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, e.detail.situation) - } - - this.target().off('during.fx', wrapper).on('during.fx', wrapper) - - this.afterAll(function(){ - this.off('during.fx', wrapper) - }) - - return this._callStart() - } - - , last: function(){ - return this.situations.length ? this.situations[this.situations.length-1] : this.situation - } - - // adds one property to the animations - , add: function(method, args, type){ - this.last()[type || 'animations'][method] = args - return this._callStart() - } - - /** perform one step of the animation - * @param ignoreTime Boolean indicating whether to ignore time and use position directly or recalculate position based on time - * @return this - */ - , step: function(ignoreTime){ - - // convert current time to an absolute position - if(!ignoreTime) this.absPos = this.timeToAbsPos(+new Date) - - // This part convert an absolute position to a position - if(this.situation.loops !== false) { - var absPos, absPosInt, lastLoop - - // If the absolute position is below 0, we just treat it as if it was 0 - absPos = Math.max(this.absPos, 0) - absPosInt = Math.floor(absPos) - - if(this.situation.loops === true || absPosInt < this.situation.loops) { - this.pos = absPos - absPosInt - lastLoop = this.situation.loop - this.situation.loop = absPosInt - } else { - this.absPos = this.situation.loops - this.pos = 1 - // The -1 here is because we don't want to toggle reversed when all the loops have been completed - lastLoop = this.situation.loop - 1 - this.situation.loop = this.situation.loops - } - - if(this.situation.reversing) { - // Toggle reversed if an odd number of loops as occured since the last call of step - this.situation.reversed = this.situation.reversed != Boolean((this.situation.loop - lastLoop) % 2) - } - - } else { - // If there are no loop, the absolute position must not be above 1 - this.absPos = Math.min(this.absPos, 1) - this.pos = this.absPos - } - - // while the absolute position can be below 0, the position must not be below 0 - if(this.pos < 0) this.pos = 0 - - if(this.situation.reversed) this.pos = 1 - this.pos - - - // apply easing - var eased = this.situation.ease(this.pos) - - // call once-callbacks - for(var i in this.situation.once){ - if(i > this.lastPos && i <= eased){ - this.situation.once[i].call(this.target(), this.pos, eased) - delete this.situation.once[i] - } - } - - // fire during callback with position, eased position and current situation as parameter - if(this.active) this.target().fire('during', {pos: this.pos, eased: eased, fx: this, situation: this.situation}) - - // the user may call stop or finish in the during callback - // so make sure that we still have a valid situation - if(!this.situation){ - return this - } - - // apply the actual animation to every property - this.eachAt() - - // do final code when situation is finished - if((this.pos == 1 && !this.situation.reversed) || (this.situation.reversed && this.pos == 0)){ - - // stop animation callback - this.stopAnimFrame() - - // fire finished callback with current situation as parameter - this.target().fire('finished', {fx:this, situation: this.situation}) - - if(!this.situations.length){ - this.target().fire('allfinished') - - // Recheck the length since the user may call animate in the afterAll callback - if(!this.situations.length){ - this.target().off('.fx') // there shouldnt be any binding left, but to make sure... - this.active = false - } - } - - // start next animation - if(this.active) this.dequeue() - else this.clearCurrent() - - }else if(!this.paused && this.active){ - // we continue animating when we are not at the end - this.startAnimFrame() - } - - // save last eased position for once callback triggering - this.lastPos = eased - return this - - } - - // calculates the step for every property and calls block with it - , eachAt: function(){ - var i, len, at, self = this, target = this.target(), s = this.situation - - // apply animations which can be called trough a method - for(i in s.animations){ - - at = [].concat(s.animations[i]).map(function(el){ - return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el - }) - - target[i].apply(target, at) - - } - - // apply animation which has to be applied with attr() - for(i in s.attrs){ - - at = [i].concat(s.attrs[i]).map(function(el){ - return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el - }) - - target.attr.apply(target, at) - - } - - // apply animation which has to be applied with style() - for(i in s.styles){ - - at = [i].concat(s.styles[i]).map(function(el){ - return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el - }) - - target.style.apply(target, at) - - } - - // animate initialTransformation which has to be chained - if(s.transforms.length){ - - // get initial initialTransformation - at = s.initialTransformation - for(i = 0, len = s.transforms.length; i < len; i++){ - - // get next transformation in chain - var a = s.transforms[i] - - // multiply matrix directly - if(a instanceof SVG.Matrix){ - - if(a.relative){ - at = at.multiply(new SVG.Matrix().morph(a).at(s.ease(this.pos))) - }else{ - at = at.morph(a).at(s.ease(this.pos)) - } - continue - } - - // when transformation is absolute we have to reset the needed transformation first - if(!a.relative) - a.undo(at.extract()) - - // and reapply it after - at = at.multiply(a.at(s.ease(this.pos))) - - } - - // set new matrix on element - target.matrix(at) - } - - return this - - } - - - // adds an once-callback which is called at a specific position and never again - , once: function(pos, fn, isEased){ - var c = this.last() - if(!isEased) pos = c.ease(pos) - - c.once[pos] = fn - - return this - } - - , _callStart: function() { - setTimeout(function(){this.start()}.bind(this), 0) - return this - } - - } - -, parent: SVG.Element - - // Add method to parent elements -, construct: { - // Get fx module or create a new one, then animate with given duration and ease - animate: function(o, ease, delay) { - return (this.fx || (this.fx = new SVG.FX(this))).animate(o, ease, delay) - } - , delay: function(delay){ - return (this.fx || (this.fx = new SVG.FX(this))).delay(delay) - } - , stop: function(jumpToEnd, clearQueue) { - if (this.fx) - this.fx.stop(jumpToEnd, clearQueue) - - return this - } - , finish: function() { - if (this.fx) - this.fx.finish() - - return this - } - // Pause current animation - , pause: function() { - if (this.fx) - this.fx.pause() - - return this - } - // Play paused current animation - , play: function() { - if (this.fx) - this.fx.play() - - return this - } - // Set/Get the speed of the animations - , speed: function(speed) { - if (this.fx) - if (speed == null) - return this.fx.speed() - else - this.fx.speed(speed) - - return this - } - } - -}) - -// MorphObj is used whenever no morphable object is given -SVG.MorphObj = SVG.invent({ - - create: function(from, to){ - // prepare color for morphing - if(SVG.Color.isColor(to)) return new SVG.Color(from).morph(to) - // prepare value list for morphing - if(SVG.regex.delimiter.test(from)) return new SVG.Array(from).morph(to) - // prepare number for morphing - if(SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) - - // prepare for plain morphing - this.value = from - this.destination = to - } - -, extend: { - at: function(pos, real){ - return real < 1 ? this.value : this.destination - }, - - valueOf: function(){ - return this.value - } - } - -}) - -SVG.extend(SVG.FX, { - // Add animatable attributes - attr: function(a, v, relative) { - // apply attributes individually - if (typeof a == 'object') { - for (var key in a) - this.attr(key, a[key]) - - } else { - this.add(a, v, 'attrs') - } - - return this - } - // Add animatable styles -, style: function(s, v) { - if (typeof s == 'object') - for (var key in s) - this.style(key, s[key]) - - else - this.add(s, v, 'styles') - - return this - } - // Animatable x-axis -, x: function(x, relative) { - if(this.target() instanceof SVG.G){ - this.transform({x:x}, relative) - return this - } - - var num = new SVG.Number(x) - num.relative = relative - return this.add('x', num) - } - // Animatable y-axis -, y: function(y, relative) { - if(this.target() instanceof SVG.G){ - this.transform({y:y}, relative) - return this - } - - var num = new SVG.Number(y) - num.relative = relative - return this.add('y', num) - } - // Animatable center x-axis -, cx: function(x) { - return this.add('cx', new SVG.Number(x)) - } - // Animatable center y-axis -, cy: function(y) { - return this.add('cy', new SVG.Number(y)) - } - // Add animatable move -, move: function(x, y) { - return this.x(x).y(y) - } - // Add animatable center -, center: function(x, y) { - return this.cx(x).cy(y) - } - // Add animatable size -, size: function(width, height) { - if (this.target() instanceof SVG.Text) { - // animate font size for Text elements - this.attr('font-size', width) - - } else { - // animate bbox based size for all other elements - var box - - if(!width || !height){ - box = this.target().bbox() - } - - if(!width){ - width = box.width / box.height * height - } - - if(!height){ - height = box.height / box.width * width - } - - this.add('width' , new SVG.Number(width)) - .add('height', new SVG.Number(height)) - - } - - return this - } - // Add animatable width -, width: function(width) { - return this.add('width', new SVG.Number(width)) - } - // Add animatable height -, height: function(height) { - return this.add('height', new SVG.Number(height)) - } - // Add animatable plot -, plot: function(a, b, c, d) { - // Lines can be plotted with 4 arguments - if(arguments.length == 4) { - return this.plot([a, b, c, d]) - } - - return this.add('plot', new (this.target().morphArray)(a)) - } - // Add leading method -, leading: function(value) { - return this.target().leading ? - this.add('leading', new SVG.Number(value)) : - this - } - // Add animatable viewbox -, viewbox: function(x, y, width, height) { - if (this.target() instanceof SVG.Container) { - this.add('viewbox', new SVG.ViewBox(x, y, width, height)) - } - - return this - } -, update: function(o) { - if (this.target() instanceof SVG.Stop) { - if (typeof o == 'number' || o instanceof SVG.Number) { - return this.update({ - offset: arguments[0] - , color: arguments[1] - , opacity: arguments[2] - }) - } - - if (o.opacity != null) this.attr('stop-opacity', o.opacity) - if (o.color != null) this.attr('stop-color', o.color) - if (o.offset != null) this.attr('offset', o.offset) - } - - return this - } -}) diff --git a/src/gradient.js b/src/gradient.js deleted file mode 100644 index 17145e6cf..000000000 --- a/src/gradient.js +++ /dev/null @@ -1,107 +0,0 @@ -SVG.Gradient = SVG.invent({ - // Initialize node - create: function(type) { - this.constructor.call(this, SVG.create(type + 'Gradient')) - - // store type - this.type = type - } - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Add a color stop - at: function(offset, color, opacity) { - return this.put(new SVG.Stop).update(offset, color, opacity) - } - // Update gradient - , update: function(block) { - // remove all stops - this.clear() - - // invoke passed block - if (typeof block == 'function') - block.call(this, this) - - return this - } - // Return the fill id - , fill: function() { - return 'url(#' + this.id() + ')' - } - // Alias string convertion to fill - , toString: function() { - return this.fill() - } - // custom attr to handle transform - , attr: function(a, b, c) { - if(a == 'transform') a = 'gradientTransform' - return SVG.Container.prototype.attr.call(this, a, b, c) - } - } - - // Add parent method -, construct: { - // Create gradient element in defs - gradient: function(type, block) { - return this.defs().gradient(type, block) - } - } -}) - -// Add animatable methods to both gradient and fx module -SVG.extend(SVG.Gradient, SVG.FX, { - // From position - from: function(x, y) { - return (this._target || this).type == 'radial' ? - this.attr({ fx: new SVG.Number(x), fy: new SVG.Number(y) }) : - this.attr({ x1: new SVG.Number(x), y1: new SVG.Number(y) }) - } - // To position -, to: function(x, y) { - return (this._target || this).type == 'radial' ? - this.attr({ cx: new SVG.Number(x), cy: new SVG.Number(y) }) : - this.attr({ x2: new SVG.Number(x), y2: new SVG.Number(y) }) - } -}) - -// Base gradient generation -SVG.extend(SVG.Defs, { - // define gradient - gradient: function(type, block) { - return this.put(new SVG.Gradient(type)).update(block) - } - -}) - -SVG.Stop = SVG.invent({ - // Initialize node - create: 'stop' - - // Inherit from -, inherit: SVG.Element - - // Add class methods -, extend: { - // add color stops - update: function(o) { - if (typeof o == 'number' || o instanceof SVG.Number) { - o = { - offset: arguments[0] - , color: arguments[1] - , opacity: arguments[2] - } - } - - // set attributes - if (o.opacity != null) this.attr('stop-opacity', o.opacity) - if (o.color != null) this.attr('stop-color', o.color) - if (o.offset != null) this.attr('offset', new SVG.Number(o.offset)) - - return this - } - } - -}) diff --git a/src/group.js b/src/group.js deleted file mode 100644 index 9ec89f268..000000000 --- a/src/group.js +++ /dev/null @@ -1,50 +0,0 @@ -SVG.G = SVG.invent({ - // Initialize node - create: 'g' - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Move over x-axis - x: function(x) { - return x == null ? this.transform('x') : this.transform({ x: x - this.x() }, true) - } - // Move over y-axis - , y: function(y) { - return y == null ? this.transform('y') : this.transform({ y: y - this.y() }, true) - } - // Move by center over x-axis - , cx: function(x) { - return x == null ? this.gbox().cx : this.x(x - this.gbox().width / 2) - } - // Move by center over y-axis - , cy: function(y) { - return y == null ? this.gbox().cy : this.y(y - this.gbox().height / 2) - } - , gbox: function() { - - var bbox = this.bbox() - , trans = this.transform() - - bbox.x += trans.x - bbox.x2 += trans.x - bbox.cx += trans.x - - bbox.y += trans.y - bbox.y2 += trans.y - bbox.cy += trans.y - - return bbox - } - } - - // Add parent method -, construct: { - // Create a group element - group: function() { - return this.put(new SVG.G) - } - } -}) diff --git a/src/helpers.js b/src/helpers.js deleted file mode 100644 index 90c4995f7..000000000 --- a/src/helpers.js +++ /dev/null @@ -1,172 +0,0 @@ -function pathRegReplace(a, b, c, d) { - return c + d.replace(SVG.regex.dots, ' .') -} - -// creates deep clone of array -function array_clone(arr){ - var clone = arr.slice(0) - for(var i = clone.length; i--;){ - if(Array.isArray(clone[i])){ - clone[i] = array_clone(clone[i]) - } - } - return clone -} - -// tests if a given element is instance of an object -function is(el, obj){ - return el instanceof obj -} - -// tests if a given selector matches an element -function matches(el, selector) { - return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector); -} - -// Convert dash-separated-string to camelCase -function camelCase(s) { - return s.toLowerCase().replace(/-(.)/g, function(m, g) { - return g.toUpperCase() - }) -} - -// Capitalize first letter of a string -function capitalize(s) { - return s.charAt(0).toUpperCase() + s.slice(1) -} - -// Ensure to six-based hex -function fullHex(hex) { - return hex.length == 4 ? - [ '#', - hex.substring(1, 2), hex.substring(1, 2) - , hex.substring(2, 3), hex.substring(2, 3) - , hex.substring(3, 4), hex.substring(3, 4) - ].join('') : hex -} - -// Component to hex value -function compToHex(comp) { - var hex = comp.toString(16) - return hex.length == 1 ? '0' + hex : hex -} - -// Calculate proportional width and height values when necessary -function proportionalSize(element, width, height) { - if (width == null || height == null) { - var box = element.bbox() - - if (width == null) - width = box.width / box.height * height - else if (height == null) - height = box.height / box.width * width - } - - return { - width: width - , height: height - } -} - -// Delta transform point -function deltaTransformPoint(matrix, x, y) { - return { - x: x * matrix.a + y * matrix.c + 0 - , y: x * matrix.b + y * matrix.d + 0 - } -} - -// Map matrix array to object -function arrayToMatrix(a) { - return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] } -} - -// Parse matrix if required -function parseMatrix(matrix) { - if (!(matrix instanceof SVG.Matrix)) - matrix = new SVG.Matrix(matrix) - - return matrix -} - -// Add centre point to transform object -function ensureCentre(o, target) { - o.cx = o.cx == null ? target.bbox().cx : o.cx - o.cy = o.cy == null ? target.bbox().cy : o.cy -} - -// PathArray Helpers -function arrayToString(a) { - for (var i = 0, il = a.length, s = ''; i < il; i++) { - s += a[i][0] - - if (a[i][1] != null) { - s += a[i][1] - - if (a[i][2] != null) { - s += ' ' - s += a[i][2] - - if (a[i][3] != null) { - s += ' ' - s += a[i][3] - s += ' ' - s += a[i][4] - - if (a[i][5] != null) { - s += ' ' - s += a[i][5] - s += ' ' - s += a[i][6] - - if (a[i][7] != null) { - s += ' ' - s += a[i][7] - } - } - } - } - } - } - - return s + ' ' -} - -// Deep new id assignment -function assignNewId(node) { - // do the same for SVG child nodes as well - for (var i = node.childNodes.length - 1; i >= 0; i--) - if (node.childNodes[i] instanceof window.SVGElement) - assignNewId(node.childNodes[i]) - - return SVG.adopt(node).id(SVG.eid(node.nodeName)) -} - -// Add more bounding box properties -function fullBox(b) { - if (b.x == null) { - b.x = 0 - b.y = 0 - b.width = 0 - b.height = 0 - } - - b.w = b.width - b.h = b.height - b.x2 = b.x + b.width - b.y2 = b.y + b.height - b.cx = b.x + b.width / 2 - b.cy = b.y + b.height / 2 - - return b -} - -// Get id from reference string -function idFromReference(url) { - var m = url.toString().match(SVG.regex.reference) - - if (m) return m[1] -} - -// Create matrix array for looping -var abcdef = 'abcdef'.split('') \ No newline at end of file diff --git a/src/hyperlink.js b/src/hyperlink.js deleted file mode 100644 index a967707d9..000000000 --- a/src/hyperlink.js +++ /dev/null @@ -1,46 +0,0 @@ -SVG.A = SVG.invent({ - // Initialize node - create: 'a' - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Link url - to: function(url) { - return this.attr('href', url, SVG.xlink) - } - // Link show attribute - , show: function(target) { - return this.attr('show', target, SVG.xlink) - } - // Link target attribute - , target: function(target) { - return this.attr('target', target) - } - } - - // Add parent method -, construct: { - // Create a hyperlink element - link: function(url) { - return this.put(new SVG.A).to(url) - } - } -}) - -SVG.extend(SVG.Element, { - // Create a hyperlink element - linkTo: function(url) { - var link = new SVG.A - - if (typeof url == 'function') - url.call(link, link) - else - link.to(url) - - return this.parent().put(link).put(this) - } - -}) \ No newline at end of file diff --git a/src/image.js b/src/image.js deleted file mode 100644 index feb369ea8..000000000 --- a/src/image.js +++ /dev/null @@ -1,69 +0,0 @@ -SVG.Image = SVG.invent({ - // Initialize node - create: 'image' - - // Inherit from -, inherit: SVG.Shape - - // Add class methods -, extend: { - // (re)load image - load: function(url) { - if (!url) return this - - var self = this - , img = new window.Image() - - // preload image - SVG.on(img, 'load', function() { - var p = self.parent(SVG.Pattern) - - if(p === null) return - - // ensure image size - if (self.width() == 0 && self.height() == 0) - self.size(img.width, img.height) - - // ensure pattern size if not set - if (p && p.width() == 0 && p.height() == 0) - p.size(self.width(), self.height()) - - // callback - if (typeof self._loaded === 'function') - self._loaded.call(self, { - width: img.width - , height: img.height - , ratio: img.width / img.height - , url: url - }) - }) - - SVG.on(img, 'error', function(e){ - if (typeof self._error === 'function'){ - self._error.call(self, e) - } - }) - - return this.attr('href', (img.src = this.src = url), SVG.xlink) - } - // Add loaded callback - , loaded: function(loaded) { - this._loaded = loaded - return this - } - - , error: function(error) { - this._error = error - return this - } - } - - // Add parent method -, construct: { - // create image element, load image and set its size - image: function(source, width, height) { - return this.put(new SVG.Image).load(source).size(width || 0, height || width || 0) - } - } - -}) \ No newline at end of file diff --git a/src/line.js b/src/line.js deleted file mode 100644 index 9e8692f93..000000000 --- a/src/line.js +++ /dev/null @@ -1,52 +0,0 @@ -SVG.Line = SVG.invent({ - // Initialize node - create: 'line' - - // Inherit from -, inherit: SVG.Shape - - // Add class methods -, extend: { - // Get array - array: function() { - return new SVG.PointArray([ - [ this.attr('x1'), this.attr('y1') ] - , [ this.attr('x2'), this.attr('y2') ] - ]) - } - // Overwrite native plot() method - , plot: function(x1, y1, x2, y2) { - if (x1 == null) - return this.array() - else if (typeof y1 !== 'undefined') - x1 = { x1: x1, y1: y1, x2: x2, y2: y2 } - else - x1 = new SVG.PointArray(x1).toLine() - - return this.attr(x1) - } - // Move by left top corner - , move: function(x, y) { - return this.attr(this.array().move(x, y).toLine()) - } - // Set element size to given width and height - , size: function(width, height) { - var p = proportionalSize(this, width, height) - - return this.attr(this.array().size(p.width, p.height).toLine()) - } - } - - // Add parent method -, construct: { - // Create a line element - line: function(x1, y1, x2, y2) { - // make sure plot is called as a setter - // x1 is not necessarily a number, it can also be an array, a string and a SVG.PointArray - return SVG.Line.prototype.plot.apply( - this.put(new SVG.Line) - , x1 != null ? [x1, y1, x2, y2] : [0, 0, 0, 0] - ) - } - } -}) diff --git a/src/main.js b/src/main.js new file mode 100644 index 000000000..5b652c448 --- /dev/null +++ b/src/main.js @@ -0,0 +1,170 @@ +/* Optional Modules */ +import './modules/optional/arrange.js' +import './modules/optional/class.js' +import './modules/optional/css.js' +import './modules/optional/data.js' +import './modules/optional/memory.js' +import './modules/optional/sugar.js' +import './modules/optional/transform.js' + +import { extend, makeInstance } from './utils/adopter.js' +import { getMethodNames, getMethodsFor } from './utils/methods.js' +import Box from './types/Box.js' +import Color from './types/Color.js' +import Container from './elements/Container.js' +import Defs from './elements/Defs.js' +import Dom from './elements/Dom.js' +import Element from './elements/Element.js' +import Ellipse from './elements/Ellipse.js' +import EventTarget from './types/EventTarget.js' +import Fragment from './elements/Fragment.js' +import Gradient from './elements/Gradient.js' +import Image from './elements/Image.js' +import Line from './elements/Line.js' +import List from './types/List.js' +import Marker from './elements/Marker.js' +import Matrix from './types/Matrix.js' +import Morphable, { + NonMorphable, + ObjectBag, + TransformBag, + makeMorphable, + registerMorphableType +} from './animation/Morphable.js' +import Path from './elements/Path.js' +import PathArray from './types/PathArray.js' +import Pattern from './elements/Pattern.js' +import PointArray from './types/PointArray.js' +import Point from './types/Point.js' +import Polygon from './elements/Polygon.js' +import Polyline from './elements/Polyline.js' +import Rect from './elements/Rect.js' +import Runner from './animation/Runner.js' +import SVGArray from './types/SVGArray.js' +import SVGNumber from './types/SVGNumber.js' +import Shape from './elements/Shape.js' +import Svg from './elements/Svg.js' +import Symbol from './elements/Symbol.js' +import Text from './elements/Text.js' +import Tspan from './elements/Tspan.js' +import * as defaults from './modules/core/defaults.js' +import * as utils from './utils/utils.js' +import * as namespaces from './modules/core/namespaces.js' +import * as regex from './modules/core/regex.js' + +export { + Morphable, + registerMorphableType, + makeMorphable, + TransformBag, + ObjectBag, + NonMorphable +} + +export { defaults, utils, namespaces, regex } +export const SVG = makeInstance +export { default as parser } from './modules/core/parser.js' +export { default as find } from './modules/core/selector.js' +export * from './modules/core/event.js' +export * from './utils/adopter.js' +export { + getWindow, + registerWindow, + restoreWindow, + saveWindow, + withWindow +} from './utils/window.js' + +/* Animation Modules */ +export { default as Animator } from './animation/Animator.js' +export { + Controller, + Ease, + PID, + Spring, + easing +} from './animation/Controller.js' +export { default as Queue } from './animation/Queue.js' +export { default as Runner } from './animation/Runner.js' +export { default as Timeline } from './animation/Timeline.js' + +/* Types */ +export { default as Array } from './types/SVGArray.js' +export { default as Box } from './types/Box.js' +export { default as Color } from './types/Color.js' +export { default as EventTarget } from './types/EventTarget.js' +export { default as Matrix } from './types/Matrix.js' +export { default as Number } from './types/SVGNumber.js' +export { default as PathArray } from './types/PathArray.js' +export { default as Point } from './types/Point.js' +export { default as PointArray } from './types/PointArray.js' +export { default as List } from './types/List.js' + +/* Elements */ +export { default as Circle } from './elements/Circle.js' +export { default as ClipPath } from './elements/ClipPath.js' +export { default as Container } from './elements/Container.js' +export { default as Defs } from './elements/Defs.js' +export { default as Dom } from './elements/Dom.js' +export { default as Element } from './elements/Element.js' +export { default as Ellipse } from './elements/Ellipse.js' +export { default as ForeignObject } from './elements/ForeignObject.js' +export { default as Fragment } from './elements/Fragment.js' +export { default as Gradient } from './elements/Gradient.js' +export { default as G } from './elements/G.js' +export { default as A } from './elements/A.js' +export { default as Image } from './elements/Image.js' +export { default as Line } from './elements/Line.js' +export { default as Marker } from './elements/Marker.js' +export { default as Mask } from './elements/Mask.js' +export { default as Path } from './elements/Path.js' +export { default as Pattern } from './elements/Pattern.js' +export { default as Polygon } from './elements/Polygon.js' +export { default as Polyline } from './elements/Polyline.js' +export { default as Rect } from './elements/Rect.js' +export { default as Shape } from './elements/Shape.js' +export { default as Stop } from './elements/Stop.js' +export { default as Style } from './elements/Style.js' +export { default as Svg } from './elements/Svg.js' +export { default as Symbol } from './elements/Symbol.js' +export { default as Text } from './elements/Text.js' +export { default as TextPath } from './elements/TextPath.js' +export { default as Tspan } from './elements/Tspan.js' +export { default as Use } from './elements/Use.js' + +extend([Svg, Symbol, Image, Pattern, Marker], getMethodsFor('viewbox')) + +extend([Line, Polyline, Polygon, Path], getMethodsFor('marker')) + +extend(Text, getMethodsFor('Text')) +extend(Path, getMethodsFor('Path')) + +extend(Defs, getMethodsFor('Defs')) + +extend([Text, Tspan], getMethodsFor('Tspan')) + +extend([Rect, Ellipse, Gradient, Runner], getMethodsFor('radius')) + +extend(EventTarget, getMethodsFor('EventTarget')) +extend(Dom, getMethodsFor('Dom')) +extend(Element, getMethodsFor('Element')) +extend(Shape, getMethodsFor('Shape')) +extend([Container, Fragment], getMethodsFor('Container')) +extend(Gradient, getMethodsFor('Gradient')) + +extend(Runner, getMethodsFor('Runner')) + +List.extend(getMethodNames()) + +registerMorphableType([ + SVGNumber, + Color, + Box, + Matrix, + SVGArray, + PointArray, + PathArray, + Point +]) + +makeMorphable() diff --git a/src/marker.js b/src/marker.js deleted file mode 100644 index cdde0ff88..000000000 --- a/src/marker.js +++ /dev/null @@ -1,80 +0,0 @@ -SVG.Marker = SVG.invent({ - // Initialize node - create: 'marker' - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Set width of element - width: function(width) { - return this.attr('markerWidth', width) - } - // Set height of element - , height: function(height) { - return this.attr('markerHeight', height) - } - // Set marker refX and refY - , ref: function(x, y) { - return this.attr('refX', x).attr('refY', y) - } - // Update marker - , update: function(block) { - // remove all content - this.clear() - - // invoke passed block - if (typeof block == 'function') - block.call(this, this) - - return this - } - // Return the fill id - , toString: function() { - return 'url(#' + this.id() + ')' - } - } - - // Add parent method -, construct: { - marker: function(width, height, block) { - // Create marker element in defs - return this.defs().marker(width, height, block) - } - } - -}) - -SVG.extend(SVG.Defs, { - // Create marker - marker: function(width, height, block) { - // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto - return this.put(new SVG.Marker) - .size(width, height) - .ref(width / 2, height / 2) - .viewbox(0, 0, width, height) - .attr('orient', 'auto') - .update(block) - } - -}) - -SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, SVG.Path, { - // Create and attach markers - marker: function(marker, width, height, block) { - var attr = ['marker'] - - // Build attribute name - if (marker != 'all') attr.push(marker) - attr = attr.join('-') - - // Set marker attribute - marker = arguments[1] instanceof SVG.Marker ? - arguments[1] : - this.doc().marker(width, height, block) - - return this.attr(attr, marker) - } - -}) \ No newline at end of file diff --git a/src/mask.js b/src/mask.js deleted file mode 100644 index 51e61e9ba..000000000 --- a/src/mask.js +++ /dev/null @@ -1,58 +0,0 @@ -SVG.Mask = SVG.invent({ - // Initialize node - create: function() { - this.constructor.call(this, SVG.create('mask')) - - // keep references to masked elements - this.targets = [] - } - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Unmask all masked elements and remove itself - remove: function() { - // unmask all targets - for (var i = this.targets.length - 1; i >= 0; i--) - if (this.targets[i]) - this.targets[i].unmask() - this.targets = [] - - // remove mask from parent - this.parent().removeElement(this) - - return this - } - } - - // Add parent method -, construct: { - // Create masking element - mask: function() { - return this.defs().put(new SVG.Mask) - } - } -}) - - -SVG.extend(SVG.Element, { - // Distribute mask to svg element - maskWith: function(element) { - // use given mask or create a new one - this.masker = element instanceof SVG.Mask ? element : this.parent().mask().add(element) - - // store reverence on self in mask - this.masker.targets.push(this) - - // apply mask - return this.attr('mask', 'url("#' + this.masker.attr('id') + '")') - } - // Unmask element -, unmask: function() { - delete this.masker - return this.attr('mask', null) - } - -}) diff --git a/src/matrix.js b/src/matrix.js deleted file mode 100644 index 449f8b6d4..000000000 --- a/src/matrix.js +++ /dev/null @@ -1,199 +0,0 @@ -SVG.Matrix = SVG.invent({ - // Initialize - create: function(source) { - var i, base = arrayToMatrix([1, 0, 0, 1, 0, 0]) - - // ensure source as object - source = source instanceof SVG.Element ? - source.matrixify() : - typeof source === 'string' ? - arrayToMatrix(source.split(SVG.regex.delimiter).map(parseFloat)) : - arguments.length == 6 ? - arrayToMatrix([].slice.call(arguments)) : - Array.isArray(source) ? - arrayToMatrix(source) : - typeof source === 'object' ? - source : base - - // merge source - for (i = abcdef.length - 1; i >= 0; --i) - this[abcdef[i]] = source[abcdef[i]] != null ? - source[abcdef[i]] : base[abcdef[i]] - } - - // Add methods -, extend: { - // Extract individual transformations - extract: function() { - // find delta transform points - var px = deltaTransformPoint(this, 0, 1) - , py = deltaTransformPoint(this, 1, 0) - , skewX = 180 / Math.PI * Math.atan2(px.y, px.x) - 90 - - return { - // translation - x: this.e - , y: this.f - , transformedX:(this.e * Math.cos(skewX * Math.PI / 180) + this.f * Math.sin(skewX * Math.PI / 180)) / Math.sqrt(this.a * this.a + this.b * this.b) - , transformedY:(this.f * Math.cos(skewX * Math.PI / 180) + this.e * Math.sin(-skewX * Math.PI / 180)) / Math.sqrt(this.c * this.c + this.d * this.d) - // skew - , skewX: -skewX - , skewY: 180 / Math.PI * Math.atan2(py.y, py.x) - // scale - , scaleX: Math.sqrt(this.a * this.a + this.b * this.b) - , scaleY: Math.sqrt(this.c * this.c + this.d * this.d) - // rotation - , rotation: skewX - , a: this.a - , b: this.b - , c: this.c - , d: this.d - , e: this.e - , f: this.f - , matrix: new SVG.Matrix(this) - } - } - // Clone matrix - , clone: function() { - return new SVG.Matrix(this) - } - // Morph one matrix into another - , morph: function(matrix) { - // store new destination - this.destination = new SVG.Matrix(matrix) - - return this - } - // Get morphed matrix at a given position - , at: function(pos) { - // make sure a destination is defined - if (!this.destination) return this - - // calculate morphed matrix at a given position - var matrix = new SVG.Matrix({ - a: this.a + (this.destination.a - this.a) * pos - , b: this.b + (this.destination.b - this.b) * pos - , c: this.c + (this.destination.c - this.c) * pos - , d: this.d + (this.destination.d - this.d) * pos - , e: this.e + (this.destination.e - this.e) * pos - , f: this.f + (this.destination.f - this.f) * pos - }) - - return matrix - } - // Multiplies by given matrix - , multiply: function(matrix) { - return new SVG.Matrix(this.native().multiply(parseMatrix(matrix).native())) - } - // Inverses matrix - , inverse: function() { - return new SVG.Matrix(this.native().inverse()) - } - // Translate matrix - , translate: function(x, y) { - return new SVG.Matrix(this.native().translate(x || 0, y || 0)) - } - // Scale matrix - , scale: function(x, y, cx, cy) { - // support uniformal scale - if (arguments.length == 1) { - y = x - } else if (arguments.length == 3) { - cy = cx - cx = y - y = x - } - - return this.around(cx, cy, new SVG.Matrix(x, 0, 0, y, 0, 0)) - } - // Rotate matrix - , rotate: function(r, cx, cy) { - // convert degrees to radians - r = SVG.utils.radians(r) - - return this.around(cx, cy, new SVG.Matrix(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0)) - } - // Flip matrix on x or y, at a given offset - , flip: function(a, o) { - return a == 'x' ? - this.scale(-1, 1, o, 0) : - a == 'y' ? - this.scale(1, -1, 0, o) : - this.scale(-1, -1, a, o != null ? o : a) - } - // Skew - , skew: function(x, y, cx, cy) { - // support uniformal skew - if (arguments.length == 1) { - y = x - } else if (arguments.length == 3) { - cy = cx - cx = y - y = x - } - - // convert degrees to radians - x = SVG.utils.radians(x) - y = SVG.utils.radians(y) - - return this.around(cx, cy, new SVG.Matrix(1, Math.tan(y), Math.tan(x), 1, 0, 0)) - } - // SkewX - , skewX: function(x, cx, cy) { - return this.skew(x, 0, cx, cy) - } - // SkewY - , skewY: function(y, cx, cy) { - return this.skew(0, y, cx, cy) - } - // Transform around a center point - , around: function(cx, cy, matrix) { - return this - .multiply(new SVG.Matrix(1, 0, 0, 1, cx || 0, cy || 0)) - .multiply(matrix) - .multiply(new SVG.Matrix(1, 0, 0, 1, -cx || 0, -cy || 0)) - } - // Convert to native SVGMatrix - , native: function() { - // create new matrix - var matrix = SVG.parser.native.createSVGMatrix() - - // update with current values - for (var i = abcdef.length - 1; i >= 0; i--) - matrix[abcdef[i]] = this[abcdef[i]] - - return matrix - } - // Convert matrix to string - , toString: function() { - return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')' - } - } - - // Define parent -, parent: SVG.Element - - // Add parent method -, construct: { - // Get current matrix - ctm: function() { - return new SVG.Matrix(this.node.getCTM()) - }, - // Get current screen matrix - screenCTM: function() { - /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537 - This is needed because FF does not return the transformation matrix - for the inner coordinate system when getScreenCTM() is called on nested svgs. - However all other Browsers do that */ - if(this instanceof SVG.Nested) { - var rect = this.rect(1,1) - var m = rect.node.getScreenCTM() - rect.remove() - return new SVG.Matrix(m) - } - return new SVG.Matrix(this.node.getScreenCTM()) - } - - } - -}) diff --git a/src/memory.js b/src/memory.js deleted file mode 100644 index 1a30faa61..000000000 --- a/src/memory.js +++ /dev/null @@ -1,36 +0,0 @@ -SVG.extend(SVG.Element, { - // Remember arbitrary data - remember: function(k, v) { - // remember every item in an object individually - if (typeof arguments[0] == 'object') - for (var v in k) - this.remember(v, k[v]) - - // retrieve memory - else if (arguments.length == 1) - return this.memory()[k] - - // store memory - else - this.memory()[k] = v - - return this - } - - // Erase a given memory -, forget: function() { - if (arguments.length == 0) - this._memory = {} - else - for (var i = arguments.length - 1; i >= 0; i--) - delete this.memory()[arguments[i]] - - return this - } - - // Initialize or return local memory object -, memory: function() { - return this._memory || (this._memory = {}) - } - -}) \ No newline at end of file diff --git a/src/modules/core/attr.js b/src/modules/core/attr.js new file mode 100644 index 000000000..8875c4143 --- /dev/null +++ b/src/modules/core/attr.js @@ -0,0 +1,94 @@ +import { attrs as defaults } from './defaults.js' +import { isNumber } from './regex.js' +import Color from '../../types/Color.js' +import SVGArray from '../../types/SVGArray.js' +import SVGNumber from '../../types/SVGNumber.js' + +const colorAttributes = new Set([ + 'fill', + 'stroke', + 'color', + 'bgcolor', + 'stop-color', + 'flood-color', + 'lighting-color' +]) + +const hooks = [] +export function registerAttrHook(fn) { + hooks.push(fn) +} + +// Set svg element attribute +export default function attr(attr, val, ns) { + // act as full getter + if (attr == null) { + // get an object of attributes + attr = {} + val = this.node.attributes + + for (const node of val) { + attr[node.nodeName] = isNumber.test(node.nodeValue) + ? parseFloat(node.nodeValue) + : node.nodeValue + } + + return attr + } else if (attr instanceof Array) { + // loop through array and get all values + return attr.reduce((last, curr) => { + last[curr] = this.attr(curr) + return last + }, {}) + } else if (typeof attr === 'object' && attr.constructor === Object) { + // apply every attribute individually if an object is passed + for (val in attr) this.attr(val, attr[val]) + } else if (val === null) { + // remove value + this.node.removeAttribute(attr) + } else if (val == null) { + // act as a getter if the first and only argument is not an object + val = this.node.getAttribute(attr) + return val == null + ? defaults[attr] + : isNumber.test(val) + ? parseFloat(val) + : val + } else { + // Loop through hooks and execute them to convert value + val = hooks.reduce((_val, hook) => { + return hook(attr, _val, this) + }, val) + + // ensure correct numeric values (also accepts NaN and Infinity) + if (typeof val === 'number') { + val = new SVGNumber(val) + } else if (colorAttributes.has(attr) && Color.isColor(val)) { + // ensure full hex color + val = new Color(val) + } else if (val.constructor === Array) { + // Check for plain arrays and parse array values + val = new SVGArray(val) + } + + // if the passed attribute is leading... + if (attr === 'leading') { + // ... call the leading method instead + if (this.leading) { + this.leading(val) + } + } else { + // set given attribute on node + typeof ns === 'string' + ? this.node.setAttributeNS(ns, attr, val.toString()) + : this.node.setAttribute(attr, val.toString()) + } + + // rebuild if required + if (this.rebuild && (attr === 'font-size' || attr === 'x')) { + this.rebuild() + } + } + + return this +} diff --git a/src/modules/core/circled.js b/src/modules/core/circled.js new file mode 100644 index 000000000..3c3a65fa9 --- /dev/null +++ b/src/modules/core/circled.js @@ -0,0 +1,43 @@ +import SVGNumber from '../../types/SVGNumber.js' + +// Radius x value +export function rx(rx) { + return this.attr('rx', rx) +} + +// Radius y value +export function ry(ry) { + return this.attr('ry', ry) +} + +// Move over x-axis +export function x(x) { + return x == null ? this.cx() - this.rx() : this.cx(x + this.rx()) +} + +// Move over y-axis +export function y(y) { + return y == null ? this.cy() - this.ry() : this.cy(y + this.ry()) +} + +// Move by center over x-axis +export function cx(x) { + return this.attr('cx', x) +} + +// Move by center over y-axis +export function cy(y) { + return this.attr('cy', y) +} + +// Set width of element +export function width(width) { + return width == null ? this.rx() * 2 : this.rx(new SVGNumber(width).divide(2)) +} + +// Set height of element +export function height(height) { + return height == null + ? this.ry() * 2 + : this.ry(new SVGNumber(height).divide(2)) +} diff --git a/src/modules/core/containerGeometry.js b/src/modules/core/containerGeometry.js new file mode 100644 index 000000000..574581cb6 --- /dev/null +++ b/src/modules/core/containerGeometry.js @@ -0,0 +1,88 @@ +import Matrix from '../../types/Matrix.js' +import Point from '../../types/Point.js' +import Box from '../../types/Box.js' +import { proportionalSize } from '../../utils/utils.js' +import { getWindow } from '../../utils/window.js' + +export function dmove(dx, dy) { + this.children().forEach((child) => { + let bbox + + // We have to wrap this for elements that dont have a bbox + // e.g. title and other descriptive elements + try { + // Get the childs bbox + // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1905039 + // Because bbox for nested svgs returns the contents bbox in the coordinate space of the svg itself (weird!), we cant use bbox for svgs + // Therefore we have to use getBoundingClientRect. But THAT is broken (as explained in the bug). + // Funnily enough the broken behavior would work for us but that breaks it in chrome + // So we have to replicate the broken behavior of FF by just reading the attributes of the svg itself + bbox = + child.node instanceof getWindow().SVGSVGElement + ? new Box(child.attr(['x', 'y', 'width', 'height'])) + : child.bbox() + } catch (e) { + return + } + + // Get childs matrix + const m = new Matrix(child) + // Translate childs matrix by amount and + // transform it back into parents space + const matrix = m.translate(dx, dy).transform(m.inverse()) + // Calculate new x and y from old box + const p = new Point(bbox.x, bbox.y).transform(matrix) + // Move element + child.move(p.x, p.y) + }) + + return this +} + +export function dx(dx) { + return this.dmove(dx, 0) +} + +export function dy(dy) { + return this.dmove(0, dy) +} + +export function height(height, box = this.bbox()) { + if (height == null) return box.height + return this.size(box.width, height, box) +} + +export function move(x = 0, y = 0, box = this.bbox()) { + const dx = x - box.x + const dy = y - box.y + + return this.dmove(dx, dy) +} + +export function size(width, height, box = this.bbox()) { + const p = proportionalSize(this, width, height, box) + const scaleX = p.width / box.width + const scaleY = p.height / box.height + + this.children().forEach((child) => { + const o = new Point(box).transform(new Matrix(child).inverse()) + child.scale(scaleX, scaleY, o.x, o.y) + }) + + return this +} + +export function width(width, box = this.bbox()) { + if (width == null) return box.width + return this.size(width, box.height, box) +} + +export function x(x, box = this.bbox()) { + if (x == null) return box.x + return this.move(x, box.y, box) +} + +export function y(y, box = this.bbox()) { + if (y == null) return box.y + return this.move(box.x, y, box) +} diff --git a/src/modules/core/defaults.js b/src/modules/core/defaults.js new file mode 100644 index 000000000..2c346a768 --- /dev/null +++ b/src/modules/core/defaults.js @@ -0,0 +1,44 @@ +export function noop() {} + +// Default animation values +export const timeline = { + duration: 400, + ease: '>', + delay: 0 +} + +// Default attribute values +export const attrs = { + // fill and stroke + 'fill-opacity': 1, + 'stroke-opacity': 1, + 'stroke-width': 0, + 'stroke-linejoin': 'miter', + 'stroke-linecap': 'butt', + fill: '#000000', + stroke: '#000000', + opacity: 1, + + // position + x: 0, + y: 0, + cx: 0, + cy: 0, + + // size + width: 0, + height: 0, + + // radius + r: 0, + rx: 0, + ry: 0, + + // gradient + offset: 0, + 'stop-opacity': 1, + 'stop-color': '#000000', + + // text + 'text-anchor': 'start' +} diff --git a/src/modules/core/event.js b/src/modules/core/event.js new file mode 100644 index 000000000..8e0871643 --- /dev/null +++ b/src/modules/core/event.js @@ -0,0 +1,143 @@ +import { delimiter } from './regex.js' +import { makeInstance } from '../../utils/adopter.js' +import { globals } from '../../utils/window.js' + +let listenerId = 0 +export const windowEvents = {} + +export function getEvents(instance) { + let n = instance.getEventHolder() + + // We dont want to save events in global space + if (n === globals.window) n = windowEvents + if (!n.events) n.events = {} + return n.events +} + +export function getEventTarget(instance) { + return instance.getEventTarget() +} + +export function clearEvents(instance) { + let n = instance.getEventHolder() + if (n === globals.window) n = windowEvents + if (n.events) n.events = {} +} + +// Add event binder in the SVG namespace +export function on(node, events, listener, binding, options) { + const l = listener.bind(binding || node) + const instance = makeInstance(node) + const bag = getEvents(instance) + const n = getEventTarget(instance) + + // events can be an array of events or a string of events + events = Array.isArray(events) ? events : events.split(delimiter) + + // add id to listener + if (!listener._svgjsListenerId) { + listener._svgjsListenerId = ++listenerId + } + + events.forEach(function (event) { + const ev = event.split('.')[0] + const ns = event.split('.')[1] || '*' + + // ensure valid object + bag[ev] = bag[ev] || {} + bag[ev][ns] = bag[ev][ns] || {} + + // reference listener + bag[ev][ns][listener._svgjsListenerId] = l + + // add listener + n.addEventListener(ev, l, options || false) + }) +} + +// Add event unbinder in the SVG namespace +export function off(node, events, listener, options) { + const instance = makeInstance(node) + const bag = getEvents(instance) + const n = getEventTarget(instance) + + // listener can be a function or a number + if (typeof listener === 'function') { + listener = listener._svgjsListenerId + if (!listener) return + } + + // events can be an array of events or a string or undefined + events = Array.isArray(events) ? events : (events || '').split(delimiter) + + events.forEach(function (event) { + const ev = event && event.split('.')[0] + const ns = event && event.split('.')[1] + let namespace, l + + if (listener) { + // remove listener reference + if (bag[ev] && bag[ev][ns || '*']) { + // removeListener + n.removeEventListener( + ev, + bag[ev][ns || '*'][listener], + options || false + ) + + delete bag[ev][ns || '*'][listener] + } + } else if (ev && ns) { + // remove all listeners for a namespaced event + if (bag[ev] && bag[ev][ns]) { + for (l in bag[ev][ns]) { + off(n, [ev, ns].join('.'), l) + } + + delete bag[ev][ns] + } + } else if (ns) { + // remove all listeners for a specific namespace + for (event in bag) { + for (namespace in bag[event]) { + if (ns === namespace) { + off(n, [event, ns].join('.')) + } + } + } + } else if (ev) { + // remove all listeners for the event + if (bag[ev]) { + for (namespace in bag[ev]) { + off(n, [ev, namespace].join('.')) + } + + delete bag[ev] + } + } else { + // remove all listeners on a given node + for (event in bag) { + off(n, event) + } + + clearEvents(instance) + } + }) +} + +export function dispatch(node, event, data, options) { + const n = getEventTarget(node) + + // Dispatch event + if (event instanceof globals.window.Event) { + n.dispatchEvent(event) + } else { + event = new globals.window.CustomEvent(event, { + detail: data, + cancelable: true, + ...options + }) + n.dispatchEvent(event) + } + return event +} diff --git a/src/modules/core/gradiented.js b/src/modules/core/gradiented.js new file mode 100644 index 000000000..cd0a512dc --- /dev/null +++ b/src/modules/core/gradiented.js @@ -0,0 +1,13 @@ +import SVGNumber from '../../types/SVGNumber.js' + +export function from(x, y) { + return (this._element || this).type === 'radialGradient' + ? this.attr({ fx: new SVGNumber(x), fy: new SVGNumber(y) }) + : this.attr({ x1: new SVGNumber(x), y1: new SVGNumber(y) }) +} + +export function to(x, y) { + return (this._element || this).type === 'radialGradient' + ? this.attr({ cx: new SVGNumber(x), cy: new SVGNumber(y) }) + : this.attr({ x2: new SVGNumber(x), y2: new SVGNumber(y) }) +} diff --git a/src/modules/core/namespaces.js b/src/modules/core/namespaces.js new file mode 100644 index 000000000..544efa28d --- /dev/null +++ b/src/modules/core/namespaces.js @@ -0,0 +1,5 @@ +// Default namespaces +export const svg = 'http://www.w3.org/2000/svg' +export const html = 'http://www.w3.org/1999/xhtml' +export const xmlns = 'http://www.w3.org/2000/xmlns/' +export const xlink = 'http://www.w3.org/1999/xlink' diff --git a/src/modules/core/parser.js b/src/modules/core/parser.js new file mode 100644 index 000000000..fc48c3b33 --- /dev/null +++ b/src/modules/core/parser.js @@ -0,0 +1,30 @@ +import { globals } from '../../utils/window.js' +import { makeInstance } from '../../utils/adopter.js' + +export default function parser() { + // Reuse cached element if possible + if (!parser.nodes) { + const svg = makeInstance().size(2, 0) + svg.node.style.cssText = [ + 'opacity: 0', + 'position: absolute', + 'left: -100%', + 'top: -100%', + 'overflow: hidden' + ].join(';') + + svg.attr('focusable', 'false') + svg.attr('aria-hidden', 'true') + + const path = svg.path().node + + parser.nodes = { svg, path } + } + + if (!parser.nodes.svg.node.parentNode) { + const b = globals.document.body || globals.document.documentElement + parser.nodes.svg.addTo(b) + } + + return parser.nodes +} diff --git a/src/modules/core/pointed.js b/src/modules/core/pointed.js new file mode 100644 index 000000000..0d4ef7a6c --- /dev/null +++ b/src/modules/core/pointed.js @@ -0,0 +1,25 @@ +import PointArray from '../../types/PointArray.js' + +export const MorphArray = PointArray + +// Move by left top corner over x-axis +export function x(x) { + return x == null ? this.bbox().x : this.move(x, this.bbox().y) +} + +// Move by left top corner over y-axis +export function y(y) { + return y == null ? this.bbox().y : this.move(this.bbox().x, y) +} + +// Set width of element +export function width(width) { + const b = this.bbox() + return width == null ? b.width : this.size(width, b.height) +} + +// Set height of element +export function height(height) { + const b = this.bbox() + return height == null ? b.height : this.size(b.width, height) +} diff --git a/src/modules/core/poly.js b/src/modules/core/poly.js new file mode 100644 index 000000000..0640735a1 --- /dev/null +++ b/src/modules/core/poly.js @@ -0,0 +1,34 @@ +import { proportionalSize } from '../../utils/utils.js' +import PointArray from '../../types/PointArray.js' + +// Get array +export function array() { + return this._array || (this._array = new PointArray(this.attr('points'))) +} + +// Clear array cache +export function clear() { + delete this._array + return this +} + +// Move by left top corner +export function move(x, y) { + return this.attr('points', this.array().move(x, y)) +} + +// Plot new path +export function plot(p) { + return p == null + ? this.array() + : this.clear().attr( + 'points', + typeof p === 'string' ? p : (this._array = new PointArray(p)) + ) +} + +// Set element size to given width and height +export function size(width, height) { + const p = proportionalSize(this, width, height) + return this.attr('points', this.array().size(p.width, p.height)) +} diff --git a/src/modules/core/regex.js b/src/modules/core/regex.js new file mode 100644 index 000000000..03d1fa3f5 --- /dev/null +++ b/src/modules/core/regex.js @@ -0,0 +1,39 @@ +// Parse unit value +export const numberAndUnit = + /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i + +// Parse hex value +export const hex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i + +// Parse rgb value +export const rgb = /rgb\((\d+),(\d+),(\d+)\)/ + +// Parse reference id +export const reference = /(#[a-z_][a-z0-9\-_]*)/i + +// splits a transformation chain +export const transforms = /\)\s*,?\s*/ + +// Whitespace +export const whitespace = /\s/g + +// Test hex value +export const isHex = /^#[a-f0-9]{3}$|^#[a-f0-9]{6}$/i + +// Test rgb value +export const isRgb = /^rgb\(/ + +// Test for blank string +export const isBlank = /^(\s+)?$/ + +// Test for numeric string +export const isNumber = /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i + +// Test for image url +export const isImage = /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i + +// split at whitespace and comma +export const delimiter = /[\s,]+/ + +// Test for path letter +export const isPathLetter = /[MLHVCSQTAZ]/i diff --git a/src/modules/core/selector.js b/src/modules/core/selector.js new file mode 100644 index 000000000..7dec4e44d --- /dev/null +++ b/src/modules/core/selector.js @@ -0,0 +1,21 @@ +import { adopt } from '../../utils/adopter.js' +import { globals } from '../../utils/window.js' +import { map } from '../../utils/utils.js' +import List from '../../types/List.js' + +export default function baseFind(query, parent) { + return new List( + map((parent || globals.document).querySelectorAll(query), function (node) { + return adopt(node) + }) + ) +} + +// Scoped find method +export function find(query) { + return baseFind(query, this.node) +} + +export function findOne(query) { + return adopt(this.node.querySelector(query)) +} diff --git a/src/modules/core/textable.js b/src/modules/core/textable.js new file mode 100644 index 000000000..44a1ee581 --- /dev/null +++ b/src/modules/core/textable.js @@ -0,0 +1,83 @@ +import { globals } from '../../utils/window.js' + +// Create plain text node +export function plain(text) { + // clear if build mode is disabled + if (this._build === false) { + this.clear() + } + + // create text node + this.node.appendChild(globals.document.createTextNode(text)) + + return this +} + +// Get length of text element +export function length() { + return this.node.getComputedTextLength() +} + +// Move over x-axis +// Text is moved by its bounding box +// text-anchor does NOT matter +export function x(x, box = this.bbox()) { + if (x == null) { + return box.x + } + + return this.attr('x', this.attr('x') + x - box.x) +} + +// Move over y-axis +export function y(y, box = this.bbox()) { + if (y == null) { + return box.y + } + + return this.attr('y', this.attr('y') + y - box.y) +} + +export function move(x, y, box = this.bbox()) { + return this.x(x, box).y(y, box) +} + +// Move center over x-axis +export function cx(x, box = this.bbox()) { + if (x == null) { + return box.cx + } + + return this.attr('x', this.attr('x') + x - box.cx) +} + +// Move center over y-axis +export function cy(y, box = this.bbox()) { + if (y == null) { + return box.cy + } + + return this.attr('y', this.attr('y') + y - box.cy) +} + +export function center(x, y, box = this.bbox()) { + return this.cx(x, box).cy(y, box) +} + +export function ax(x) { + return this.attr('x', x) +} + +export function ay(y) { + return this.attr('y', y) +} + +export function amove(x, y) { + return this.ax(x).ay(y) +} + +// Enable / disable build mode +export function build(build) { + this._build = !!build + return this +} diff --git a/src/modules/optional/arrange.js b/src/modules/optional/arrange.js new file mode 100644 index 000000000..292cd7994 --- /dev/null +++ b/src/modules/optional/arrange.js @@ -0,0 +1,114 @@ +import { makeInstance } from '../../utils/adopter.js' +import { registerMethods } from '../../utils/methods.js' + +// Get all siblings, including myself +export function siblings() { + return this.parent().children() +} + +// Get the current position siblings +export function position() { + return this.parent().index(this) +} + +// Get the next element (will return null if there is none) +export function next() { + return this.siblings()[this.position() + 1] +} + +// Get the next element (will return null if there is none) +export function prev() { + return this.siblings()[this.position() - 1] +} + +// Send given element one step forward +export function forward() { + const i = this.position() + const p = this.parent() + + // move node one step forward + p.add(this.remove(), i + 1) + + return this +} + +// Send given element one step backward +export function backward() { + const i = this.position() + const p = this.parent() + + p.add(this.remove(), i ? i - 1 : 0) + + return this +} + +// Send given element all the way to the front +export function front() { + const p = this.parent() + + // Move node forward + p.add(this.remove()) + + return this +} + +// Send given element all the way to the back +export function back() { + const p = this.parent() + + // Move node back + p.add(this.remove(), 0) + + return this +} + +// Inserts a given element before the targeted element +export function before(element) { + element = makeInstance(element) + element.remove() + + const i = this.position() + + this.parent().add(element, i) + + return this +} + +// Inserts a given element after the targeted element +export function after(element) { + element = makeInstance(element) + element.remove() + + const i = this.position() + + this.parent().add(element, i + 1) + + return this +} + +export function insertBefore(element) { + element = makeInstance(element) + element.before(this) + return this +} + +export function insertAfter(element) { + element = makeInstance(element) + element.after(this) + return this +} + +registerMethods('Dom', { + siblings, + position, + next, + prev, + forward, + backward, + front, + back, + before, + after, + insertBefore, + insertAfter +}) diff --git a/src/modules/optional/class.js b/src/modules/optional/class.js new file mode 100644 index 000000000..314164489 --- /dev/null +++ b/src/modules/optional/class.js @@ -0,0 +1,53 @@ +import { delimiter } from '../core/regex.js' +import { registerMethods } from '../../utils/methods.js' + +// Return array of classes on the node +export function classes() { + const attr = this.attr('class') + return attr == null ? [] : attr.trim().split(delimiter) +} + +// Return true if class exists on the node, false otherwise +export function hasClass(name) { + return this.classes().indexOf(name) !== -1 +} + +// Add class to the node +export function addClass(name) { + if (!this.hasClass(name)) { + const array = this.classes() + array.push(name) + this.attr('class', array.join(' ')) + } + + return this +} + +// Remove class from the node +export function removeClass(name) { + if (this.hasClass(name)) { + this.attr( + 'class', + this.classes() + .filter(function (c) { + return c !== name + }) + .join(' ') + ) + } + + return this +} + +// Toggle the presence of a class on the node +export function toggleClass(name) { + return this.hasClass(name) ? this.removeClass(name) : this.addClass(name) +} + +registerMethods('Dom', { + classes, + hasClass, + addClass, + removeClass, + toggleClass +}) diff --git a/src/modules/optional/css.js b/src/modules/optional/css.js new file mode 100644 index 000000000..1c5ed4090 --- /dev/null +++ b/src/modules/optional/css.js @@ -0,0 +1,79 @@ +import { isBlank } from '../core/regex.js' +import { registerMethods } from '../../utils/methods.js' + +// Dynamic style generator +export function css(style, val) { + const ret = {} + if (arguments.length === 0) { + // get full style as object + this.node.style.cssText + .split(/\s*;\s*/) + .filter(function (el) { + return !!el.length + }) + .forEach(function (el) { + const t = el.split(/\s*:\s*/) + ret[t[0]] = t[1] + }) + return ret + } + + if (arguments.length < 2) { + // get style properties as array + if (Array.isArray(style)) { + for (const name of style) { + const cased = name + ret[name] = this.node.style.getPropertyValue(cased) + } + return ret + } + + // get style for property + if (typeof style === 'string') { + return this.node.style.getPropertyValue(style) + } + + // set styles in object + if (typeof style === 'object') { + for (const name in style) { + // set empty string if null/undefined/'' was given + this.node.style.setProperty( + name, + style[name] == null || isBlank.test(style[name]) ? '' : style[name] + ) + } + } + } + + // set style for property + if (arguments.length === 2) { + this.node.style.setProperty( + style, + val == null || isBlank.test(val) ? '' : val + ) + } + + return this +} + +// Show element +export function show() { + return this.css('display', '') +} + +// Hide element +export function hide() { + return this.css('display', 'none') +} + +// Is element visible? +export function visible() { + return this.css('display') !== 'none' +} + +registerMethods('Dom', { + css, + show, + hide, + visible +}) diff --git a/src/modules/optional/data.js b/src/modules/optional/data.js new file mode 100644 index 000000000..a9d7ac784 --- /dev/null +++ b/src/modules/optional/data.js @@ -0,0 +1,47 @@ +import { registerMethods } from '../../utils/methods.js' +import { filter, map } from '../../utils/utils.js' + +// Store data values on svg nodes +export function data(a, v, r) { + if (a == null) { + // get an object of attributes + return this.data( + map( + filter( + this.node.attributes, + (el) => el.nodeName.indexOf('data-') === 0 + ), + (el) => el.nodeName.slice(5) + ) + ) + } else if (a instanceof Array) { + const data = {} + for (const key of a) { + data[key] = this.data(key) + } + return data + } else if (typeof a === 'object') { + for (v in a) { + this.data(v, a[v]) + } + } else if (arguments.length < 2) { + try { + return JSON.parse(this.attr('data-' + a)) + } catch (e) { + return this.attr('data-' + a) + } + } else { + this.attr( + 'data-' + a, + v === null + ? null + : r === true || typeof v === 'string' || typeof v === 'number' + ? v + : JSON.stringify(v) + ) + } + + return this +} + +registerMethods('Dom', { data }) diff --git a/src/modules/optional/memory.js b/src/modules/optional/memory.js new file mode 100644 index 000000000..31058c3a5 --- /dev/null +++ b/src/modules/optional/memory.js @@ -0,0 +1,40 @@ +import { registerMethods } from '../../utils/methods.js' + +// Remember arbitrary data +export function remember(k, v) { + // remember every item in an object individually + if (typeof arguments[0] === 'object') { + for (const key in k) { + this.remember(key, k[key]) + } + } else if (arguments.length === 1) { + // retrieve memory + return this.memory()[k] + } else { + // store memory + this.memory()[k] = v + } + + return this +} + +// Erase a given memory +export function forget() { + if (arguments.length === 0) { + this._memory = {} + } else { + for (let i = arguments.length - 1; i >= 0; i--) { + delete this.memory()[arguments[i]] + } + } + return this +} + +// This triggers creation of a new hidden class which is not performant +// However, this function is not rarely used so it will not happen frequently +// Return local memory object +export function memory() { + return (this._memory = this._memory || {}) +} + +registerMethods('Dom', { remember, forget, memory }) diff --git a/src/modules/optional/sugar.js b/src/modules/optional/sugar.js new file mode 100644 index 000000000..2ee8c966a --- /dev/null +++ b/src/modules/optional/sugar.js @@ -0,0 +1,200 @@ +import { registerMethods } from '../../utils/methods.js' +import Color from '../../types/Color.js' +import Element from '../../elements/Element.js' +import Matrix from '../../types/Matrix.js' +import Point from '../../types/Point.js' +import SVGNumber from '../../types/SVGNumber.js' + +// Define list of available attributes for stroke and fill +const sugar = { + stroke: [ + 'color', + 'width', + 'opacity', + 'linecap', + 'linejoin', + 'miterlimit', + 'dasharray', + 'dashoffset' + ], + fill: ['color', 'opacity', 'rule'], + prefix: function (t, a) { + return a === 'color' ? t : t + '-' + a + } +} + +// Add sugar for fill and stroke +;['fill', 'stroke'].forEach(function (m) { + const extension = {} + let i + + extension[m] = function (o) { + if (typeof o === 'undefined') { + return this.attr(m) + } + if ( + typeof o === 'string' || + o instanceof Color || + Color.isRgb(o) || + o instanceof Element + ) { + this.attr(m, o) + } else { + // set all attributes from sugar.fill and sugar.stroke list + for (i = sugar[m].length - 1; i >= 0; i--) { + if (o[sugar[m][i]] != null) { + this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]) + } + } + } + + return this + } + + registerMethods(['Element', 'Runner'], extension) +}) + +registerMethods(['Element', 'Runner'], { + // Let the user set the matrix directly + matrix: function (mat, b, c, d, e, f) { + // Act as a getter + if (mat == null) { + return new Matrix(this) + } + + // Act as a setter, the user can pass a matrix or a set of numbers + return this.attr('transform', new Matrix(mat, b, c, d, e, f)) + }, + + // Map rotation to transform + rotate: function (angle, cx, cy) { + return this.transform({ rotate: angle, ox: cx, oy: cy }, true) + }, + + // Map skew to transform + skew: function (x, y, cx, cy) { + return arguments.length === 1 || arguments.length === 3 + ? this.transform({ skew: x, ox: y, oy: cx }, true) + : this.transform({ skew: [x, y], ox: cx, oy: cy }, true) + }, + + shear: function (lam, cx, cy) { + return this.transform({ shear: lam, ox: cx, oy: cy }, true) + }, + + // Map scale to transform + scale: function (x, y, cx, cy) { + return arguments.length === 1 || arguments.length === 3 + ? this.transform({ scale: x, ox: y, oy: cx }, true) + : this.transform({ scale: [x, y], ox: cx, oy: cy }, true) + }, + + // Map translate to transform + translate: function (x, y) { + return this.transform({ translate: [x, y] }, true) + }, + + // Map relative translations to transform + relative: function (x, y) { + return this.transform({ relative: [x, y] }, true) + }, + + // Map flip to transform + flip: function (direction = 'both', origin = 'center') { + if ('xybothtrue'.indexOf(direction) === -1) { + origin = direction + direction = 'both' + } + + return this.transform({ flip: direction, origin: origin }, true) + }, + + // Opacity + opacity: function (value) { + return this.attr('opacity', value) + } +}) + +registerMethods('radius', { + // Add x and y radius + radius: function (x, y = x) { + const type = (this._element || this).type + return type === 'radialGradient' + ? this.attr('r', new SVGNumber(x)) + : this.rx(x).ry(y) + } +}) + +registerMethods('Path', { + // Get path length + length: function () { + return this.node.getTotalLength() + }, + // Get point at length + pointAt: function (length) { + return new Point(this.node.getPointAtLength(length)) + } +}) + +registerMethods(['Element', 'Runner'], { + // Set font + font: function (a, v) { + if (typeof a === 'object') { + for (v in a) this.font(v, a[v]) + return this + } + + return a === 'leading' + ? this.leading(v) + : a === 'anchor' + ? this.attr('text-anchor', v) + : a === 'size' || + a === 'family' || + a === 'weight' || + a === 'stretch' || + a === 'variant' || + a === 'style' + ? this.attr('font-' + a, v) + : this.attr(a, v) + } +}) + +// Add events to elements +const methods = [ + 'click', + 'dblclick', + 'mousedown', + 'mouseup', + 'mouseover', + 'mouseout', + 'mousemove', + 'mouseenter', + 'mouseleave', + 'touchstart', + 'touchmove', + 'touchleave', + 'touchend', + 'touchcancel', + 'contextmenu', + 'wheel', + 'pointerdown', + 'pointermove', + 'pointerup', + 'pointerleave', + 'pointercancel' +].reduce(function (last, event) { + // add event to Element + const fn = function (f) { + if (f === null) { + this.off(event) + } else { + this.on(event, f) + } + return this + } + + last[event] = fn + return last +}, {}) + +registerMethods('Element', methods) diff --git a/src/modules/optional/transform.js b/src/modules/optional/transform.js new file mode 100644 index 000000000..b8ba46a7c --- /dev/null +++ b/src/modules/optional/transform.js @@ -0,0 +1,83 @@ +import { getOrigin, isDescriptive } from '../../utils/utils.js' +import { delimiter, transforms } from '../core/regex.js' +import { registerMethods } from '../../utils/methods.js' +import Matrix from '../../types/Matrix.js' + +// Reset all transformations +export function untransform() { + return this.attr('transform', null) +} + +// merge the whole transformation chain into one matrix and returns it +export function matrixify() { + const matrix = (this.attr('transform') || '') + // split transformations + .split(transforms) + .slice(0, -1) + .map(function (str) { + // generate key => value pairs + const kv = str.trim().split('(') + return [ + kv[0], + kv[1].split(delimiter).map(function (str) { + return parseFloat(str) + }) + ] + }) + .reverse() + // merge every transformation into one matrix + .reduce(function (matrix, transform) { + if (transform[0] === 'matrix') { + return matrix.lmultiply(Matrix.fromArray(transform[1])) + } + return matrix[transform[0]].apply(matrix, transform[1]) + }, new Matrix()) + + return matrix +} + +// add an element to another parent without changing the visual representation on the screen +export function toParent(parent, i) { + if (this === parent) return this + + if (isDescriptive(this.node)) return this.addTo(parent, i) + + const ctm = this.screenCTM() + const pCtm = parent.screenCTM().inverse() + + this.addTo(parent, i).untransform().transform(pCtm.multiply(ctm)) + + return this +} + +// same as above with parent equals root-svg +export function toRoot(i) { + return this.toParent(this.root(), i) +} + +// Add transformations +export function transform(o, relative) { + // Act as a getter if no object was passed + if (o == null || typeof o === 'string') { + const decomposed = new Matrix(this).decompose() + return o == null ? decomposed : decomposed[o] + } + + if (!Matrix.isMatrixLike(o)) { + // Set the origin according to the defined transform + o = { ...o, origin: getOrigin(o, this) } + } + + // The user can pass a boolean, an Element or an Matrix or nothing + const cleanRelative = relative === true ? this : relative || false + const result = new Matrix(cleanRelative).transform(o) + return this.attr('transform', result) +} + +registerMethods('Element', { + untransform, + matrixify, + toParent, + toRoot, + transform +}) diff --git a/src/nested.js b/src/nested.js deleted file mode 100644 index f856e52c4..000000000 --- a/src/nested.js +++ /dev/null @@ -1,19 +0,0 @@ -SVG.Nested = SVG.invent({ - // Initialize node - create: function() { - this.constructor.call(this, SVG.create('svg')) - - this.style('overflow', 'visible') - } - - // Inherit from -, inherit: SVG.Container - - // Add parent method -, construct: { - // Create nested svg document - nested: function() { - return this.put(new SVG.Nested) - } - } -}) \ No newline at end of file diff --git a/src/number.js b/src/number.js deleted file mode 100644 index 8198e7c60..000000000 --- a/src/number.js +++ /dev/null @@ -1,110 +0,0 @@ -// Module for unit convertions -SVG.Number = SVG.invent({ - // Initialize - create: function(value, unit) { - // initialize defaults - this.value = 0 - this.unit = unit || '' - - // parse value - if (typeof value === 'number') { - // ensure a valid numeric value - this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value - - } else if (typeof value === 'string') { - unit = value.match(SVG.regex.numberAndUnit) - - if (unit) { - // make value numeric - this.value = parseFloat(unit[1]) - - // normalize - if (unit[5] == '%') - this.value /= 100 - else if (unit[5] == 's') - this.value *= 1000 - - // store unit - this.unit = unit[5] - } - - } else { - if (value instanceof SVG.Number) { - this.value = value.valueOf() - this.unit = value.unit - } - } - - } - // Add methods -, extend: { - // Stringalize - toString: function() { - return ( - this.unit == '%' ? - ~~(this.value * 1e8) / 1e6: - this.unit == 's' ? - this.value / 1e3 : - this.value - ) + this.unit - } - , toJSON: function() { - return this.toString() - } - , // Convert to primitive - valueOf: function() { - return this.value - } - // Add number - , plus: function(number) { - number = new SVG.Number(number) - return new SVG.Number(this + number, this.unit || number.unit) - } - // Subtract number - , minus: function(number) { - number = new SVG.Number(number) - return new SVG.Number(this - number, this.unit || number.unit) - } - // Multiply number - , times: function(number) { - number = new SVG.Number(number) - return new SVG.Number(this * number, this.unit || number.unit) - } - // Divide number - , divide: function(number) { - number = new SVG.Number(number) - return new SVG.Number(this / number, this.unit || number.unit) - } - // Convert to different unit - , to: function(unit) { - var number = new SVG.Number(this) - - if (typeof unit === 'string') - number.unit = unit - - return number - } - // Make number morphable - , morph: function(number) { - this.destination = new SVG.Number(number) - - if(number.relative) { - this.destination.value += this.value - } - - return this - } - // Get morphed number at given position - , at: function(pos) { - // Make sure a destination is defined - if (!this.destination) return this - - // Generate new morphed number - return new SVG.Number(this.destination) - .minus(this) - .times(pos) - .plus(this) - } - - } -}) diff --git a/src/parent.js b/src/parent.js deleted file mode 100644 index ee99e4d65..000000000 --- a/src/parent.js +++ /dev/null @@ -1,90 +0,0 @@ -SVG.Parent = SVG.invent({ - // Initialize node - create: function(element) { - this.constructor.call(this, element) - } - - // Inherit from -, inherit: SVG.Element - - // Add class methods -, extend: { - // Returns all child elements - children: function() { - return SVG.utils.map(SVG.utils.filterSVGElements(this.node.childNodes), function(node) { - return SVG.adopt(node) - }) - } - // Add given element at a position - , add: function(element, i) { - if (i == null) - this.node.appendChild(element.node) - else if (element.node != this.node.childNodes[i]) - this.node.insertBefore(element.node, this.node.childNodes[i]) - - return this - } - // Basically does the same as `add()` but returns the added element instead - , put: function(element, i) { - this.add(element, i) - return element - } - // Checks if the given element is a child - , has: function(element) { - return this.index(element) >= 0 - } - // Gets index of given element - , index: function(element) { - return [].slice.call(this.node.childNodes).indexOf(element.node) - } - // Get a element at the given index - , get: function(i) { - return SVG.adopt(this.node.childNodes[i]) - } - // Get first child - , first: function() { - return this.get(0) - } - // Get the last child - , last: function() { - return this.get(this.node.childNodes.length - 1) - } - // Iterates over all children and invokes a given block - , each: function(block, deep) { - var i, il - , children = this.children() - - for (i = 0, il = children.length; i < il; i++) { - if (children[i] instanceof SVG.Element) - block.apply(children[i], [i, children]) - - if (deep && (children[i] instanceof SVG.Container)) - children[i].each(block, deep) - } - - return this - } - // Remove a given child - , removeElement: function(element) { - this.node.removeChild(element.node) - - return this - } - // Remove all elements in this container - , clear: function() { - // remove children - while(this.node.hasChildNodes()) - this.node.removeChild(this.node.lastChild) - - // remove defs reference - delete this._defs - - return this - } - , // Get defs - defs: function() { - return this.doc().defs() - } - } - -}) diff --git a/src/path.js b/src/path.js deleted file mode 100644 index e5504bbd7..000000000 --- a/src/path.js +++ /dev/null @@ -1,64 +0,0 @@ -SVG.Path = SVG.invent({ - // Initialize node - create: 'path' - - // Inherit from -, inherit: SVG.Shape - - // Add class methods -, extend: { - // Define morphable array - morphArray: SVG.PathArray - // Get array - , array: function() { - return this._array || (this._array = new SVG.PathArray(this.attr('d'))) - } - // Plot new path - , plot: function(d) { - return (d == null) ? - this.array() : - this.clear().attr('d', typeof d == 'string' ? d : (this._array = new SVG.PathArray(d))) - } - // Clear array cache - , clear: function() { - delete this._array - return this - } - // Move by left top corner - , move: function(x, y) { - return this.attr('d', this.array().move(x, y)) - } - // Move by left top corner over x-axis - , x: function(x) { - return x == null ? this.bbox().x : this.move(x, this.bbox().y) - } - // Move by left top corner over y-axis - , y: function(y) { - return y == null ? this.bbox().y : this.move(this.bbox().x, y) - } - // Set element size to given width and height - , size: function(width, height) { - var p = proportionalSize(this, width, height) - - return this.attr('d', this.array().size(p.width, p.height)) - } - // Set width of element - , width: function(width) { - return width == null ? this.bbox().width : this.size(width, this.bbox().height) - } - // Set height of element - , height: function(height) { - return height == null ? this.bbox().height : this.size(this.bbox().width, height) - } - - } - - // Add parent method -, construct: { - // Create a wrapped path element - path: function(d) { - // make sure plot is called as a setter - return this.put(new SVG.Path).plot(d || new SVG.PathArray) - } - } -}) diff --git a/src/patharray.js b/src/patharray.js deleted file mode 100644 index 4fb9318a8..000000000 --- a/src/patharray.js +++ /dev/null @@ -1,297 +0,0 @@ -var pathHandlers = { - M: function(c, p, p0) { - p.x = p0.x = c[0] - p.y = p0.y = c[1] - - return ['M', p.x, p.y] - }, - L: function(c, p) { - p.x = c[0] - p.y = c[1] - return ['L', c[0], c[1]] - }, - H: function(c, p) { - p.x = c[0] - return ['H', c[0]] - }, - V: function(c, p) { - p.y = c[0] - return ['V', c[0]] - }, - C: function(c, p) { - p.x = c[4] - p.y = c[5] - return ['C', c[0], c[1], c[2], c[3], c[4], c[5]] - }, - S: function(c, p) { - p.x = c[2] - p.y = c[3] - return ['S', c[0], c[1], c[2], c[3]] - }, - Q: function(c, p) { - p.x = c[2] - p.y = c[3] - return ['Q', c[0], c[1], c[2], c[3]] - }, - T: function(c, p) { - p.x = c[0] - p.y = c[1] - return ['T', c[0], c[1]] - }, - Z: function(c, p, p0) { - p.x = p0.x - p.y = p0.y - return ['Z'] - }, - A: function(c, p) { - p.x = c[5] - p.y = c[6] - return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]] - } -} - -var mlhvqtcsa = 'mlhvqtcsaz'.split('') - -for(var i = 0, il = mlhvqtcsa.length; i < il; ++i){ - pathHandlers[mlhvqtcsa[i]] = (function(i){ - return function(c, p, p0) { - if(i == 'H') c[0] = c[0] + p.x - else if(i == 'V') c[0] = c[0] + p.y - else if(i == 'A'){ - c[5] = c[5] + p.x, - c[6] = c[6] + p.y - } - else - for(var j = 0, jl = c.length; j < jl; ++j) { - c[j] = c[j] + (j%2 ? p.y : p.x) - } - - return pathHandlers[i](c, p, p0) - } - })(mlhvqtcsa[i].toUpperCase()) -} - -// Path points array -SVG.PathArray = function(array, fallback) { - SVG.Array.call(this, array, fallback || [['M', 0, 0]]) -} - -// Inherit from SVG.Array -SVG.PathArray.prototype = new SVG.Array -SVG.PathArray.prototype.constructor = SVG.PathArray - -SVG.extend(SVG.PathArray, { - // Convert array to string - toString: function() { - return arrayToString(this.value) - } - // Move path string -, move: function(x, y) { - // get bounding box of current situation - var box = this.bbox() - - // get relative offset - x -= box.x - y -= box.y - - if (!isNaN(x) && !isNaN(y)) { - // move every point - for (var l, i = this.value.length - 1; i >= 0; i--) { - l = this.value[i][0] - - if (l == 'M' || l == 'L' || l == 'T') { - this.value[i][1] += x - this.value[i][2] += y - - } else if (l == 'H') { - this.value[i][1] += x - - } else if (l == 'V') { - this.value[i][1] += y - - } else if (l == 'C' || l == 'S' || l == 'Q') { - this.value[i][1] += x - this.value[i][2] += y - this.value[i][3] += x - this.value[i][4] += y - - if (l == 'C') { - this.value[i][5] += x - this.value[i][6] += y - } - - } else if (l == 'A') { - this.value[i][6] += x - this.value[i][7] += y - } - - } - } - - return this - } - // Resize path string -, size: function(width, height) { - // get bounding box of current situation - var i, l, box = this.bbox() - - // recalculate position of all points according to new size - for (i = this.value.length - 1; i >= 0; i--) { - l = this.value[i][0] - - if (l == 'M' || l == 'L' || l == 'T') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x - this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y - - } else if (l == 'H') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x - - } else if (l == 'V') { - this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y - - } else if (l == 'C' || l == 'S' || l == 'Q') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x - this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y - this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x - this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y - - if (l == 'C') { - this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x - this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y - } - - } else if (l == 'A') { - // resize radii - this.value[i][1] = (this.value[i][1] * width) / box.width - this.value[i][2] = (this.value[i][2] * height) / box.height - - // move position values - this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x - this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y - } - - } - - return this - } - // Test if the passed path array use the same path data commands as this path array -, equalCommands: function(pathArray) { - var i, il, equalCommands - - pathArray = new SVG.PathArray(pathArray) - - equalCommands = this.value.length === pathArray.value.length - for(i = 0, il = this.value.length; equalCommands && i < il; i++) { - equalCommands = this.value[i][0] === pathArray.value[i][0] - } - - return equalCommands - } - // Make path array morphable -, morph: function(pathArray) { - pathArray = new SVG.PathArray(pathArray) - - if(this.equalCommands(pathArray)) { - this.destination = pathArray - } else { - this.destination = null - } - - return this - } - // Get morphed path array at given position -, at: function(pos) { - // make sure a destination is defined - if (!this.destination) return this - - var sourceArray = this.value - , destinationArray = this.destination.value - , array = [], pathArray = new SVG.PathArray() - , i, il, j, jl - - // Animate has specified in the SVG spec - // See: https://www.w3.org/TR/SVG11/paths.html#PathElement - for (i = 0, il = sourceArray.length; i < il; i++) { - array[i] = [sourceArray[i][0]] - for(j = 1, jl = sourceArray[i].length; j < jl; j++) { - array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos - } - // For the two flags of the elliptical arc command, the SVG spec say: - // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true - // Elliptical arc command as an array followed by corresponding indexes: - // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] - // 0 1 2 3 4 5 6 7 - if(array[i][0] === 'A') { - array[i][4] = +(array[i][4] != 0) - array[i][5] = +(array[i][5] != 0) - } - } - - // Directly modify the value of a path array, this is done this way for performance - pathArray.value = array - return pathArray - } - // Absolutize and parse path to array -, parse: function(array) { - // if it's already a patharray, no need to parse it - if (array instanceof SVG.PathArray) return array.valueOf() - - // prepare for parsing - var i, x0, y0, s, seg, arr - , x = 0 - , y = 0 - , paramCnt = { 'M':2, 'L':2, 'H':1, 'V':1, 'C':6, 'S':4, 'Q':4, 'T':2, 'A':7, 'Z':0 } - - if(typeof array == 'string'){ - - array = array - .replace(SVG.regex.numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123 - .replace(SVG.regex.pathLetters, ' $& ') // put some room between letters and numbers - .replace(SVG.regex.hyphen, '$1 -') // add space before hyphen - .trim() // trim - .split(SVG.regex.delimiter) // split into array - - }else{ - array = array.reduce(function(prev, curr){ - return [].concat.call(prev, curr) - }, []) - } - - // array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...] - var arr = [] - , p = new SVG.Point() - , p0 = new SVG.Point() - , index = 0 - , len = array.length - - do{ - // Test if we have a path letter - if(SVG.regex.isPathLetter.test(array[index])){ - s = array[index] - ++index - // If last letter was a move command and we got no new, it defaults to [L]ine - }else if(s == 'M'){ - s = 'L' - }else if(s == 'm'){ - s = 'l' - } - - arr.push(pathHandlers[s].call(null, - array.slice(index, (index = index + paramCnt[s.toUpperCase()])).map(parseFloat), - p, p0 - ) - ) - - }while(len > index) - - return arr - - } - // Get bounding box of path -, bbox: function() { - SVG.parser.path.setAttribute('d', this.toString()) - - return SVG.parser.path.getBBox() - } - -}) diff --git a/src/pattern.js b/src/pattern.js deleted file mode 100644 index fed33c88e..000000000 --- a/src/pattern.js +++ /dev/null @@ -1,58 +0,0 @@ -SVG.Pattern = SVG.invent({ - // Initialize node - create: 'pattern' - - // Inherit from -, inherit: SVG.Container - - // Add class methods -, extend: { - // Return the fill id - fill: function() { - return 'url(#' + this.id() + ')' - } - // Update pattern by rebuilding - , update: function(block) { - // remove content - this.clear() - - // invoke passed block - if (typeof block == 'function') - block.call(this, this) - - return this - } - // Alias string convertion to fill - , toString: function() { - return this.fill() - } - // custom attr to handle transform - , attr: function(a, b, c) { - if(a == 'transform') a = 'patternTransform' - return SVG.Container.prototype.attr.call(this, a, b, c) - } - - } - - // Add parent method -, construct: { - // Create pattern element in defs - pattern: function(width, height, block) { - return this.defs().pattern(width, height, block) - } - } -}) - -SVG.extend(SVG.Defs, { - // Define gradient - pattern: function(width, height, block) { - return this.put(new SVG.Pattern).update(block).attr({ - x: 0 - , y: 0 - , width: width - , height: height - , patternUnits: 'userSpaceOnUse' - }) - } - -}) \ No newline at end of file diff --git a/src/point.js b/src/point.js deleted file mode 100644 index 3a54d4337..000000000 --- a/src/point.js +++ /dev/null @@ -1,72 +0,0 @@ -SVG.Point = SVG.invent({ - // Initialize - create: function(x,y) { - var i, source, base = {x:0, y:0} - - // ensure source as object - source = Array.isArray(x) ? - {x:x[0], y:x[1]} : - typeof x === 'object' ? - {x:x.x, y:x.y} : - x != null ? - {x:x, y:(y != null ? y : x)} : base // If y has no value, then x is used has its value - - // merge source - this.x = source.x - this.y = source.y - } - - // Add methods -, extend: { - // Clone point - clone: function() { - return new SVG.Point(this) - } - // Morph one point into another - , morph: function(x, y) { - // store new destination - this.destination = new SVG.Point(x, y) - - return this - } - // Get morphed point at a given position - , at: function(pos) { - // make sure a destination is defined - if (!this.destination) return this - - // calculate morphed matrix at a given position - var point = new SVG.Point({ - x: this.x + (this.destination.x - this.x) * pos - , y: this.y + (this.destination.y - this.y) * pos - }) - - return point - } - // Convert to native SVGPoint - , native: function() { - // create new point - var point = SVG.parser.native.createSVGPoint() - - // update with current values - point.x = this.x - point.y = this.y - - return point - } - // transform point with matrix - , transform: function(matrix) { - return new SVG.Point(this.native().matrixTransform(matrix.native())) - } - - } - -}) - -SVG.extend(SVG.Element, { - - // Get point - point: function(x, y) { - return new SVG.Point(x,y).transform(this.screenCTM().inverse()); - } - -}) diff --git a/src/pointarray.js b/src/pointarray.js deleted file mode 100644 index b0a3d5426..000000000 --- a/src/pointarray.js +++ /dev/null @@ -1,102 +0,0 @@ -// Poly points array -SVG.PointArray = function(array, fallback) { - SVG.Array.call(this, array, fallback || [[0,0]]) -} - -// Inherit from SVG.Array -SVG.PointArray.prototype = new SVG.Array -SVG.PointArray.prototype.constructor = SVG.PointArray - -SVG.extend(SVG.PointArray, { - // Convert array to string - toString: function() { - // convert to a poly point string - for (var i = 0, il = this.value.length, array = []; i < il; i++) - array.push(this.value[i].join(',')) - - return array.join(' ') - } - // Convert array to line object -, toLine: function() { - return { - x1: this.value[0][0] - , y1: this.value[0][1] - , x2: this.value[1][0] - , y2: this.value[1][1] - } - } - // Get morphed array at given position -, at: function(pos) { - // make sure a destination is defined - if (!this.destination) return this - - // generate morphed point string - for (var i = 0, il = this.value.length, array = []; i < il; i++) - array.push([ - this.value[i][0] + (this.destination[i][0] - this.value[i][0]) * pos - , this.value[i][1] + (this.destination[i][1] - this.value[i][1]) * pos - ]) - - return new SVG.PointArray(array) - } - // Parse point string and flat array -, parse: function(array) { - var points = [] - - array = array.valueOf() - - // if it is an array - if (Array.isArray(array)) { - // and it is not flat, there is no need to parse it - if(Array.isArray(array[0])) { - return array - } - } else { // Else, it is considered as a string - // parse points - array = array.trim().split(SVG.regex.delimiter).map(parseFloat) - } - - // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints - // Odd number of coordinates is an error. In such cases, drop the last odd coordinate. - if (array.length % 2 !== 0) array.pop() - - // wrap points in two-tuples and parse points as floats - for(var i = 0, len = array.length; i < len; i = i + 2) - points.push([ array[i], array[i+1] ]) - - return points - } - // Move point string -, move: function(x, y) { - var box = this.bbox() - - // get relative offset - x -= box.x - y -= box.y - - // move every point - if (!isNaN(x) && !isNaN(y)) - for (var i = this.value.length - 1; i >= 0; i--) - this.value[i] = [this.value[i][0] + x, this.value[i][1] + y] - - return this - } - // Resize poly string -, size: function(width, height) { - var i, box = this.bbox() - - // recalculate position of all points according to new size - for (i = this.value.length - 1; i >= 0; i--) { - if(box.width) this.value[i][0] = ((this.value[i][0] - box.x) * width) / box.width + box.x - if(box.height) this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y - } - - return this - } - // Get bounding box of points -, bbox: function() { - SVG.parser.poly.setAttribute('points', this.toString()) - - return SVG.parser.poly.getBBox() - } -}) diff --git a/src/pointed.js b/src/pointed.js deleted file mode 100644 index 02ff44e7a..000000000 --- a/src/pointed.js +++ /dev/null @@ -1,25 +0,0 @@ -// unify all point to point elements -SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, { - // Define morphable array - morphArray: SVG.PointArray - // Move by left top corner over x-axis -, x: function(x) { - return x == null ? this.bbox().x : this.move(x, this.bbox().y) - } - // Move by left top corner over y-axis -, y: function(y) { - return y == null ? this.bbox().y : this.move(this.bbox().x, y) - } - // Set width of element -, width: function(width) { - var b = this.bbox() - - return width == null ? b.width : this.size(width, b.height) - } - // Set height of element -, height: function(height) { - var b = this.bbox() - - return height == null ? b.height : this.size(b.width, height) - } -}) \ No newline at end of file diff --git a/src/poly.js b/src/poly.js deleted file mode 100644 index 269b1125c..000000000 --- a/src/poly.js +++ /dev/null @@ -1,63 +0,0 @@ -SVG.Polyline = SVG.invent({ - // Initialize node - create: 'polyline' - - // Inherit from -, inherit: SVG.Shape - - // Add parent method -, construct: { - // Create a wrapped polyline element - polyline: function(p) { - // make sure plot is called as a setter - return this.put(new SVG.Polyline).plot(p || new SVG.PointArray) - } - } -}) - -SVG.Polygon = SVG.invent({ - // Initialize node - create: 'polygon' - - // Inherit from -, inherit: SVG.Shape - - // Add parent method -, construct: { - // Create a wrapped polygon element - polygon: function(p) { - // make sure plot is called as a setter - return this.put(new SVG.Polygon).plot(p || new SVG.PointArray) - } - } -}) - -// Add polygon-specific functions -SVG.extend(SVG.Polyline, SVG.Polygon, { - // Get array - array: function() { - return this._array || (this._array = new SVG.PointArray(this.attr('points'))) - } - // Plot new path -, plot: function(p) { - return (p == null) ? - this.array() : - this.clear().attr('points', typeof p == 'string' ? p : (this._array = new SVG.PointArray(p))) - } - // Clear array cache -, clear: function() { - delete this._array - return this - } - // Move by left top corner -, move: function(x, y) { - return this.attr('points', this.array().move(x, y)) - } - // Set element size to given width and height -, size: function(width, height) { - var p = proportionalSize(this, width, height) - - return this.attr('points', this.array().size(p.width, p.height)) - } - -}) diff --git a/src/polyfill.js b/src/polyfill.js deleted file mode 100644 index d99ab76ca..000000000 --- a/src/polyfill.js +++ /dev/null @@ -1,42 +0,0 @@ -// Add CustomEvent to IE9 and IE10 -if (typeof window.CustomEvent !== 'function') { - // Code from: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent - var CustomEvent = function(event, options) { - options = options || { bubbles: false, cancelable: false, detail: undefined } - var e = document.createEvent('CustomEvent') - e.initCustomEvent(event, options.bubbles, options.cancelable, options.detail) - return e - } - - CustomEvent.prototype = window.Event.prototype - - window.CustomEvent = CustomEvent -} - -// requestAnimationFrame / cancelAnimationFrame Polyfill with fallback based on Paul Irish -(function(w) { - var lastTime = 0 - var vendors = ['moz', 'webkit'] - - for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { - w.requestAnimationFrame = w[vendors[x] + 'RequestAnimationFrame'] - w.cancelAnimationFrame = w[vendors[x] + 'CancelAnimationFrame'] || - w[vendors[x] + 'CancelRequestAnimationFrame'] - } - - w.requestAnimationFrame = w.requestAnimationFrame || - function(callback) { - var currTime = new Date().getTime() - var timeToCall = Math.max(0, 16 - (currTime - lastTime)) - - var id = w.setTimeout(function() { - callback(currTime + timeToCall) - }, timeToCall) - - lastTime = currTime + timeToCall - return id - } - - w.cancelAnimationFrame = w.cancelAnimationFrame || w.clearTimeout; - -}(window)) \ No newline at end of file diff --git a/src/polyfills/children.js b/src/polyfills/children.js new file mode 100644 index 000000000..690e23ad1 --- /dev/null +++ b/src/polyfills/children.js @@ -0,0 +1,8 @@ +import { filter } from '../utils/utils.js' + +// IE11: children does not work for svg nodes +export default function children(node) { + return filter(node.childNodes, function (child) { + return child.nodeType === 1 + }) +} diff --git a/src/polyfills/innerHTML.js b/src/polyfills/innerHTML.js new file mode 100644 index 000000000..51632afe8 --- /dev/null +++ b/src/polyfills/innerHTML.js @@ -0,0 +1,115 @@ +;(function () { + try { + if (SVGElement.prototype.innerHTML) return + } catch (e) { + return + } + + const serializeXML = function (node, output) { + const nodeType = node.nodeType + if (nodeType === 3) { + output.push( + node.textContent + .replace(/&/, '&') + .replace(/', '>') + ) + } else if (nodeType === 1) { + output.push('<', node.tagName) + if (node.hasAttributes()) { + ;[].forEach.call(node.attributes, function (attrNode) { + output.push(' ', attrNode.name, '="', attrNode.value, '"') + }) + } + output.push('>') + if (node.hasChildNodes()) { + ;[].forEach.call(node.childNodes, function (childNode) { + serializeXML(childNode, output) + }) + } else { + // output.push('/>') + } + output.push('') + } else if (nodeType === 8) { + output.push('') + } + } + + Object.defineProperty(SVGElement.prototype, 'innerHTML', { + get: function () { + const output = [] + let childNode = this.firstChild + while (childNode) { + serializeXML(childNode, output) + childNode = childNode.nextSibling + } + return output.join('') + }, + set: function (markupText) { + while (this.firstChild) { + this.removeChild(this.firstChild) + } + + try { + const dXML = new DOMParser() + dXML.async = false + + const sXML = + "" + + markupText + + '' + const svgDocElement = dXML.parseFromString( + sXML, + 'text/xml' + ).documentElement + + let childNode = svgDocElement.firstChild + while (childNode) { + this.appendChild(this.ownerDocument.importNode(childNode, true)) + childNode = childNode.nextSibling + } + } catch (e) { + throw new Error('Can not set innerHTML on node') + } + } + }) + + Object.defineProperty(SVGElement.prototype, 'outerHTML', { + get: function () { + const output = [] + serializeXML(this, output) + return output.join('') + }, + set: function (markupText) { + while (this.firstChild) { + this.removeChild(this.firstChild) + } + + try { + const dXML = new DOMParser() + dXML.async = false + + const sXML = + "" + + markupText + + '' + const svgDocElement = dXML.parseFromString( + sXML, + 'text/xml' + ).documentElement + + let childNode = svgDocElement.firstChild + while (childNode) { + this.parentNode.insertBefore( + this.ownerDocument.importNode(childNode, true), + this + ) + // this.appendChild(this.ownerDocument.importNode(childNode, true)); + childNode = childNode.nextSibling + } + } catch (e) { + throw new Error('Can not set outerHTML on node') + } + } + }) +})() diff --git a/src/rect.js b/src/rect.js deleted file mode 100644 index 6c639fe84..000000000 --- a/src/rect.js +++ /dev/null @@ -1,15 +0,0 @@ -SVG.Rect = SVG.invent({ - // Initialize node - create: 'rect' - - // Inherit from -, inherit: SVG.Shape - - // Add parent method -, construct: { - // Create a rect element - rect: function(width, height) { - return this.put(new SVG.Rect()).size(width, height) - } - } -}) \ No newline at end of file diff --git a/src/regex.js b/src/regex.js deleted file mode 100644 index c0ca70681..000000000 --- a/src/regex.js +++ /dev/null @@ -1,61 +0,0 @@ -// Storage for regular expressions -SVG.regex = { - // Parse unit value - numberAndUnit: /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i - - // Parse hex value -, hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i - - // Parse rgb value -, rgb: /rgb\((\d+),(\d+),(\d+)\)/ - - // Parse reference id -, reference: /#([a-z0-9\-_]+)/i - - // splits a transformation chain -, transforms: /\)\s*,?\s*/ - - // Whitespace -, whitespace: /\s/g - - // Test hex value -, isHex: /^#[a-f0-9]{3,6}$/i - - // Test rgb value -, isRgb: /^rgb\(/ - - // Test css declaration -, isCss: /[^:]+:[^;]+;?/ - - // Test for blank string -, isBlank: /^(\s+)?$/ - - // Test for numeric string -, isNumber: /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i - - // Test for percent value -, isPercent: /^-?[\d\.]+%$/ - - // Test for image url -, isImage: /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i - - // split at whitespace and comma -, delimiter: /[\s,]+/ - - // The following regex are used to parse the d attribute of a path - - // Matches all hyphens which are not after an exponent -, hyphen: /([^e])\-/gi - - // Replaces and tests for all path letters -, pathLetters: /[MLHVCSQTAZ]/gi - - // yes we need this one, too -, isPathLetter: /[MLHVCSQTAZ]/i - - // matches 0.154.23.45 -, numbersWithDots: /((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi - - // matches . -, dots: /\./g -} diff --git a/src/selector.js b/src/selector.js deleted file mode 100644 index fe87e4e2d..000000000 --- a/src/selector.js +++ /dev/null @@ -1,22 +0,0 @@ -// Method for getting an element by id -SVG.get = function(id) { - var node = document.getElementById(idFromReference(id) || id) - return SVG.adopt(node) -} - -// Select elements by query string -SVG.select = function(query, parent) { - return new SVG.Set( - SVG.utils.map((parent || document).querySelectorAll(query), function(node) { - return SVG.adopt(node) - }) - ) -} - -SVG.extend(SVG.Parent, { - // Scoped select method - select: function(query) { - return SVG.select(query, this.node) - } - -}) \ No newline at end of file diff --git a/src/set.js b/src/set.js deleted file mode 100644 index 9da52c703..000000000 --- a/src/set.js +++ /dev/null @@ -1,147 +0,0 @@ -SVG.Set = SVG.invent({ - // Initialize - create: function(members) { - // Set initial state - Array.isArray(members) ? this.members = members : this.clear() - } - - // Add class methods -, extend: { - // Add element to set - add: function() { - var i, il, elements = [].slice.call(arguments) - - for (i = 0, il = elements.length; i < il; i++) - this.members.push(elements[i]) - - return this - } - // Remove element from set - , remove: function(element) { - var i = this.index(element) - - // remove given child - if (i > -1) - this.members.splice(i, 1) - - return this - } - // Iterate over all members - , each: function(block) { - for (var i = 0, il = this.members.length; i < il; i++) - block.apply(this.members[i], [i, this.members]) - - return this - } - // Restore to defaults - , clear: function() { - // initialize store - this.members = [] - - return this - } - // Get the length of a set - , length: function() { - return this.members.length - } - // Checks if a given element is present in set - , has: function(element) { - return this.index(element) >= 0 - } - // retuns index of given element in set - , index: function(element) { - return this.members.indexOf(element) - } - // Get member at given index - , get: function(i) { - return this.members[i] - } - // Get first member - , first: function() { - return this.get(0) - } - // Get last member - , last: function() { - return this.get(this.members.length - 1) - } - // Default value - , valueOf: function() { - return this.members - } - // Get the bounding box of all members included or empty box if set has no items - , bbox: function(){ - // return an empty box of there are no members - if (this.members.length == 0) - return new SVG.RBox() - - // get the first rbox and update the target bbox - var rbox = this.members[0].rbox(this.members[0].doc()) - - this.each(function() { - // user rbox for correct position and visual representation - rbox = rbox.merge(this.rbox(this.doc())) - }) - - return rbox - } - } - - // Add parent method -, construct: { - // Create a new set - set: function(members) { - return new SVG.Set(members) - } - } -}) - -SVG.FX.Set = SVG.invent({ - // Initialize node - create: function(set) { - // store reference to set - this.set = set - } - -}) - -// Alias methods -SVG.Set.inherit = function() { - var m - , methods = [] - - // gather shape methods - for(var m in SVG.Shape.prototype) - if (typeof SVG.Shape.prototype[m] == 'function' && typeof SVG.Set.prototype[m] != 'function') - methods.push(m) - - // apply shape aliasses - methods.forEach(function(method) { - SVG.Set.prototype[method] = function() { - for (var i = 0, il = this.members.length; i < il; i++) - if (this.members[i] && typeof this.members[i][method] == 'function') - this.members[i][method].apply(this.members[i], arguments) - - return method == 'animate' ? (this.fx || (this.fx = new SVG.FX.Set(this))) : this - } - }) - - // clear methods for the next round - methods = [] - - // gather fx methods - for(var m in SVG.FX.prototype) - if (typeof SVG.FX.prototype[m] == 'function' && typeof SVG.FX.Set.prototype[m] != 'function') - methods.push(m) - - // apply fx aliasses - methods.forEach(function(method) { - SVG.FX.Set.prototype[method] = function() { - for (var i = 0, il = this.set.members.length; i < il; i++) - this.set.members[i].fx[method].apply(this.set.members[i].fx, arguments) - - return this - } - }) -} - - diff --git a/src/shape.js b/src/shape.js deleted file mode 100644 index 3bfc21008..000000000 --- a/src/shape.js +++ /dev/null @@ -1,10 +0,0 @@ -SVG.Shape = SVG.invent({ - // Initialize node - create: function(element) { - this.constructor.call(this, element) - } - - // Inherit from -, inherit: SVG.Element - -}) \ No newline at end of file diff --git a/src/style.js b/src/style.js deleted file mode 100644 index 053d60682..000000000 --- a/src/style.js +++ /dev/null @@ -1,35 +0,0 @@ -SVG.extend(SVG.Element, { - // Dynamic style generator - style: function(s, v) { - if (arguments.length == 0) { - // get full style - return this.node.style.cssText || '' - - } else if (arguments.length < 2) { - // apply every style individually if an object is passed - if (typeof s == 'object') { - for (v in s) this.style(v, s[v]) - - } else if (SVG.regex.isCss.test(s)) { - // parse css string - s = s.split(/\s*;\s*/) - // filter out suffix ; and stuff like ;; - .filter(function(e) { return !!e }) - .map(function(e){ return e.split(/\s*:\s*/) }) - - // apply every definition individually - while (v = s.pop()) { - this.style(v[0], v[1]) - } - } else { - // act as a getter if the first and only argument is not an object - return this.node.style[camelCase(s)] - } - - } else { - this.node.style[camelCase(s)] = v === null || SVG.regex.isBlank.test(v) ? '' : v - } - - return this - } -}) \ No newline at end of file diff --git a/src/sugar.js b/src/sugar.js deleted file mode 100644 index e577ea77a..000000000 --- a/src/sugar.js +++ /dev/null @@ -1,117 +0,0 @@ -// Define list of available attributes for stroke and fill -var sugar = { - stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'] -, fill: ['color', 'opacity', 'rule'] -, prefix: function(t, a) { - return a == 'color' ? t : t + '-' + a - } -} - -// Add sugar for fill and stroke -;['fill', 'stroke'].forEach(function(m) { - var i, extension = {} - - extension[m] = function(o) { - if (typeof o == 'undefined') - return this - if (typeof o == 'string' || SVG.Color.isRgb(o) || (o && typeof o.fill === 'function')) - this.attr(m, o) - - else - // set all attributes from sugar.fill and sugar.stroke list - for (i = sugar[m].length - 1; i >= 0; i--) - if (o[sugar[m][i]] != null) - this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]) - - return this - } - - SVG.extend(SVG.Element, SVG.FX, extension) - -}) - -SVG.extend(SVG.Element, SVG.FX, { - // Map rotation to transform - rotate: function(d, cx, cy) { - return this.transform({ rotation: d, cx: cx, cy: cy }) - } - // Map skew to transform -, skew: function(x, y, cx, cy) { - return arguments.length == 1 || arguments.length == 3 ? - this.transform({ skew: x, cx: y, cy: cx }) : - this.transform({ skewX: x, skewY: y, cx: cx, cy: cy }) - } - // Map scale to transform -, scale: function(x, y, cx, cy) { - return arguments.length == 1 || arguments.length == 3 ? - this.transform({ scale: x, cx: y, cy: cx }) : - this.transform({ scaleX: x, scaleY: y, cx: cx, cy: cy }) - } - // Map translate to transform -, translate: function(x, y) { - return this.transform({ x: x, y: y }) - } - // Map flip to transform -, flip: function(a, o) { - o = typeof a == 'number' ? a : o - return this.transform({ flip: a || 'both', offset: o }) - } - // Map matrix to transform -, matrix: function(m) { - return this.attr('transform', new SVG.Matrix(arguments.length == 6 ? [].slice.call(arguments) : m)) - } - // Opacity -, opacity: function(value) { - return this.attr('opacity', value) - } - // Relative move over x axis -, dx: function(x) { - return this.x(new SVG.Number(x).plus(this instanceof SVG.FX ? 0 : this.x()), true) - } - // Relative move over y axis -, dy: function(y) { - return this.y(new SVG.Number(y).plus(this instanceof SVG.FX ? 0 : this.y()), true) - } - // Relative move over x and y axes -, dmove: function(x, y) { - return this.dx(x).dy(y) - } -}) - -SVG.extend(SVG.Rect, SVG.Ellipse, SVG.Circle, SVG.Gradient, SVG.FX, { - // Add x and y radius - radius: function(x, y) { - var type = (this._target || this).type; - return type == 'radial' || type == 'circle' ? - this.attr('r', new SVG.Number(x)) : - this.rx(x).ry(y == null ? x : y) - } -}) - -SVG.extend(SVG.Path, { - // Get path length - length: function() { - return this.node.getTotalLength() - } - // Get point at length -, pointAt: function(length) { - return this.node.getPointAtLength(length) - } -}) - -SVG.extend(SVG.Parent, SVG.Text, SVG.Tspan, SVG.FX, { - // Set font - font: function(a, v) { - if (typeof a == 'object') { - for (v in a) this.font(v, a[v]) - } - - return a == 'leading' ? - this.leading(v) : - a == 'anchor' ? - this.attr('text-anchor', v) : - a == 'size' || a == 'family' || a == 'weight' || a == 'stretch' || a == 'variant' || a == 'style' ? - this.attr('font-'+ a, v) : - this.attr(a, v) - } -}) diff --git a/src/svg.js b/src/svg.js index ee8832e18..69e4161a7 100644 --- a/src/svg.js +++ b/src/svg.js @@ -1,152 +1,9 @@ -// The main wrapping element -var SVG = this.SVG = function(element) { - if (SVG.supported) { - element = new SVG.Doc(element) - - if(!SVG.parser.draw) - SVG.prepare() - - return element - } -} - -// Default namespaces -SVG.ns = 'http://www.w3.org/2000/svg' -SVG.xmlns = 'http://www.w3.org/2000/xmlns/' -SVG.xlink = 'http://www.w3.org/1999/xlink' -SVG.svgjs = 'http://svgjs.com/svgjs' - -// Svg support test -SVG.supported = (function() { - return !! document.createElementNS && - !! document.createElementNS(SVG.ns,'svg').createSVGRect -})() - -// Don't bother to continue if SVG is not supported -if (!SVG.supported) return false - -// Element id sequence -SVG.did = 1000 - -// Get next named element id -SVG.eid = function(name) { - return 'Svgjs' + capitalize(name) + (SVG.did++) -} - -// Method for element creation -SVG.create = function(name) { - // create element - var element = document.createElementNS(this.ns, name) - - // apply unique id - element.setAttribute('id', this.eid(name)) - - return element -} - -// Method for extending objects -SVG.extend = function() { - var modules, methods, key, i - - // Get list of modules - modules = [].slice.call(arguments) - - // Get object with extensions - methods = modules.pop() - - for (i = modules.length - 1; i >= 0; i--) - if (modules[i]) - for (key in methods) - modules[i].prototype[key] = methods[key] - - // Make sure SVG.Set inherits any newly added methods - if (SVG.Set && SVG.Set.inherit) - SVG.Set.inherit() -} - -// Invent new element -SVG.invent = function(config) { - // Create element initializer - var initializer = typeof config.create == 'function' ? - config.create : - function() { - this.constructor.call(this, SVG.create(config.create)) - } +import * as svgMembers from './main.js' +import { makeInstance } from './utils/adopter.js' - // Inherit prototype - if (config.inherit) - initializer.prototype = new config.inherit - - // Extend with methods - if (config.extend) - SVG.extend(initializer, config.extend) - - // Attach construct method to parent - if (config.construct) - SVG.extend(config.parent || SVG.Container, config.construct) - - return initializer -} - -// Adopt existing svg elements -SVG.adopt = function(node) { - // check for presence of node - if (!node) return null - - // make sure a node isn't already adopted - if (node.instance) return node.instance - - // initialize variables - var element - - // adopt with element-specific settings - if (node.nodeName == 'svg') - element = node.parentNode instanceof window.SVGElement ? new SVG.Nested : new SVG.Doc - else if (node.nodeName == 'linearGradient') - element = new SVG.Gradient('linear') - else if (node.nodeName == 'radialGradient') - element = new SVG.Gradient('radial') - else if (SVG[capitalize(node.nodeName)]) - element = new SVG[capitalize(node.nodeName)] - else - element = new SVG.Element(node) - - // ensure references - element.type = node.nodeName - element.node = node - node.instance = element - - // SVG.Class specific preparations - if (element instanceof SVG.Doc) - element.namespace().defs() - - // pull svgjs data from the dom (getAttributeNS doesn't work in html5) - element.setData(JSON.parse(node.getAttribute('svgjs:data')) || {}) - - return element -} - -// Initialize parsing element -SVG.prepare = function() { - // Select document body and create invisible svg element - var body = document.getElementsByTagName('body')[0] - , draw = (body ? new SVG.Doc(body) : SVG.adopt(document.documentElement).nested()).size(2, 0) - - // Create parser object - SVG.parser = { - body: body || document.documentElement - , draw: draw.style('opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden').node - , poly: draw.polyline().node - , path: draw.path().node - , native: SVG.create('svg') - } -} - -SVG.parser = { - native: SVG.create('svg') +// The main wrapping element +export default function SVG(element, isHTML) { + return makeInstance(element, isHTML) } -document.addEventListener('DOMContentLoaded', function() { - if(!SVG.parser.draw) - SVG.prepare() -}, false) +Object.assign(SVG, svgMembers) diff --git a/src/symbol.js b/src/symbol.js deleted file mode 100644 index f9c83e995..000000000 --- a/src/symbol.js +++ /dev/null @@ -1,14 +0,0 @@ -SVG.Symbol = SVG.invent({ - // Initialize node - create: 'symbol' - - // Inherit from -, inherit: SVG.Container - -, construct: { - // create symbol - symbol: function() { - return this.put(new SVG.Symbol) - } - } -}) diff --git a/src/text.js b/src/text.js deleted file mode 100644 index 20c2c1e50..000000000 --- a/src/text.js +++ /dev/null @@ -1,249 +0,0 @@ -SVG.Text = SVG.invent({ - // Initialize node - create: function() { - this.constructor.call(this, SVG.create('text')) - - this.dom.leading = new SVG.Number(1.3) // store leading value for rebuilding - this._rebuild = true // enable automatic updating of dy values - this._build = false // disable build mode for adding multiple lines - - // set default font - this.attr('font-family', SVG.defaults.attrs['font-family']) - } - - // Inherit from -, inherit: SVG.Shape - - // Add class methods -, extend: { - // Move over x-axis - x: function(x) { - // act as getter - if (x == null) - return this.attr('x') - - return this.attr('x', x) - } - // Move over y-axis - , y: function(y) { - var oy = this.attr('y') - , o = typeof oy === 'number' ? oy - this.bbox().y : 0 - - // act as getter - if (y == null) - return typeof oy === 'number' ? oy - o : oy - - return this.attr('y', typeof y === 'number' ? y + o : y) - } - // Move center over x-axis - , cx: function(x) { - return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2) - } - // Move center over y-axis - , cy: function(y) { - return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2) - } - // Set the text content - , text: function(text) { - // act as getter - if (typeof text === 'undefined'){ - var text = '' - var children = this.node.childNodes - for(var i = 0, len = children.length; i < len; ++i){ - - // add newline if its not the first child and newLined is set to true - if(i != 0 && children[i].nodeType != 3 && SVG.adopt(children[i]).dom.newLined == true){ - text += '\n' - } - - // add content of this node - text += children[i].textContent - } - - return text - } - - // remove existing content - this.clear().build(true) - - if (typeof text === 'function') { - // call block - text.call(this, this) - - } else { - // store text and make sure text is not blank - text = text.split('\n') - - // build new lines - for (var i = 0, il = text.length; i < il; i++) - this.tspan(text[i]).newLine() - } - - // disable build mode and rebuild lines - return this.build(false).rebuild() - } - // Set font size - , size: function(size) { - return this.attr('font-size', size).rebuild() - } - // Set / get leading - , leading: function(value) { - // act as getter - if (value == null) - return this.dom.leading - - // act as setter - this.dom.leading = new SVG.Number(value) - - return this.rebuild() - } - // Get all the first level lines - , lines: function() { - var node = (this.textPath && this.textPath() || this).node - - // filter tspans and map them to SVG.js instances - var lines = SVG.utils.map(SVG.utils.filterSVGElements(node.childNodes), function(el){ - return SVG.adopt(el) - }) - - // return an instance of SVG.set - return new SVG.Set(lines) - } - // Rebuild appearance type - , rebuild: function(rebuild) { - // store new rebuild flag if given - if (typeof rebuild == 'boolean') - this._rebuild = rebuild - - // define position of all lines - if (this._rebuild) { - var self = this - , blankLineOffset = 0 - , dy = this.dom.leading * new SVG.Number(this.attr('font-size')) - - this.lines().each(function() { - if (this.dom.newLined) { - if (!self.textPath()) - this.attr('x', self.attr('x')) - if(this.text() == '\n') { - blankLineOffset += dy - }else{ - this.attr('dy', dy + blankLineOffset) - blankLineOffset = 0 - } - } - }) - - this.fire('rebuild') - } - - return this - } - // Enable / disable build mode - , build: function(build) { - this._build = !!build - return this - } - // overwrite method from parent to set data properly - , setData: function(o){ - this.dom = o - this.dom.leading = new SVG.Number(o.leading || 1.3) - return this - } - } - - // Add parent method -, construct: { - // Create text element - text: function(text) { - return this.put(new SVG.Text).text(text) - } - // Create plain text element - , plain: function(text) { - return this.put(new SVG.Text).plain(text) - } - } - -}) - -SVG.Tspan = SVG.invent({ - // Initialize node - create: 'tspan' - - // Inherit from -, inherit: SVG.Shape - - // Add class methods -, extend: { - // Set text content - text: function(text) { - if(text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '') - - typeof text === 'function' ? text.call(this, this) : this.plain(text) - - return this - } - // Shortcut dx - , dx: function(dx) { - return this.attr('dx', dx) - } - // Shortcut dy - , dy: function(dy) { - return this.attr('dy', dy) - } - // Create new line - , newLine: function() { - // fetch text parent - var t = this.parent(SVG.Text) - - // mark new line - this.dom.newLined = true - - // apply new hyĀ”n - return this.dy(t.dom.leading * t.attr('font-size')).attr('x', t.x()) - } - } - -}) - -SVG.extend(SVG.Text, SVG.Tspan, { - // Create plain text node - plain: function(text) { - // clear if build mode is disabled - if (this._build === false) - this.clear() - - // create text node - this.node.appendChild(document.createTextNode(text)) - - return this - } - // Create a tspan -, tspan: function(text) { - var node = (this.textPath && this.textPath() || this).node - , tspan = new SVG.Tspan - - // clear if build mode is disabled - if (this._build === false) - this.clear() - - // add new tspan - node.appendChild(tspan.node) - - return tspan.text(text) - } - // Clear all lines -, clear: function() { - var node = (this.textPath && this.textPath() || this).node - - // remove existing child nodes - while (node.hasChildNodes()) - node.removeChild(node.lastChild) - - return this - } - // Get length of text element -, length: function() { - return this.node.getComputedTextLength() - } -}) diff --git a/src/textpath.js b/src/textpath.js deleted file mode 100644 index 18e214919..000000000 --- a/src/textpath.js +++ /dev/null @@ -1,62 +0,0 @@ -SVG.TextPath = SVG.invent({ - // Initialize node - create: 'textPath' - - // Inherit from -, inherit: SVG.Parent - - // Define parent class -, parent: SVG.Text - - // Add parent method -, construct: { - morphArray: SVG.PathArray - // Create path for text to run on - , path: function(d) { - // create textPath element - var path = new SVG.TextPath - , track = this.doc().defs().path(d) - - // move lines to textpath - while (this.node.hasChildNodes()) - path.node.appendChild(this.node.firstChild) - - // add textPath element as child node - this.node.appendChild(path.node) - - // link textPath to path and add content - path.attr('href', '#' + track, SVG.xlink) - - return this - } - // return the array of the path track element - , array: function() { - var track = this.track() - - return track ? track.array() : null - } - // Plot path if any - , plot: function(d) { - var track = this.track() - , pathArray = null - - if (track) { - pathArray = track.plot(d) - } - - return (d == null) ? pathArray : this - } - // Get the path track element - , track: function() { - var path = this.textPath() - - if (path) - return path.reference('href') - } - // Get the textPath child - , textPath: function() { - if (this.node.firstChild && this.node.firstChild.nodeName == 'textPath') - return SVG.adopt(this.node.firstChild) - } - } -}) diff --git a/src/transform.js b/src/transform.js deleted file mode 100644 index b7386030e..000000000 --- a/src/transform.js +++ /dev/null @@ -1,370 +0,0 @@ -SVG.extend(SVG.Element, { - // Add transformations - transform: function(o, relative) { - // get target in case of the fx module, otherwise reference this - var target = this - , matrix, bbox - - // act as a getter - if (typeof o !== 'object') { - // get current matrix - matrix = new SVG.Matrix(target).extract() - - return typeof o === 'string' ? matrix[o] : matrix - } - - // get current matrix - matrix = new SVG.Matrix(target) - - // ensure relative flag - relative = !!relative || !!o.relative - - // act on matrix - if (o.a != null) { - matrix = relative ? - // relative - matrix.multiply(new SVG.Matrix(o)) : - // absolute - new SVG.Matrix(o) - - // act on rotation - } else if (o.rotation != null) { - // ensure centre point - ensureCentre(o, target) - - // apply transformation - matrix = relative ? - // relative - matrix.rotate(o.rotation, o.cx, o.cy) : - // absolute - matrix.rotate(o.rotation - matrix.extract().rotation, o.cx, o.cy) - - // act on scale - } else if (o.scale != null || o.scaleX != null || o.scaleY != null) { - // ensure centre point - ensureCentre(o, target) - - // ensure scale values on both axes - o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 - o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 - - if (!relative) { - // absolute; multiply inversed values - var e = matrix.extract() - o.scaleX = o.scaleX * 1 / e.scaleX - o.scaleY = o.scaleY * 1 / e.scaleY - } - - matrix = matrix.scale(o.scaleX, o.scaleY, o.cx, o.cy) - - // act on skew - } else if (o.skew != null || o.skewX != null || o.skewY != null) { - // ensure centre point - ensureCentre(o, target) - - // ensure skew values on both axes - o.skewX = o.skew != null ? o.skew : o.skewX != null ? o.skewX : 0 - o.skewY = o.skew != null ? o.skew : o.skewY != null ? o.skewY : 0 - - if (!relative) { - // absolute; reset skew values - var e = matrix.extract() - matrix = matrix.multiply(new SVG.Matrix().skew(e.skewX, e.skewY, o.cx, o.cy).inverse()) - } - - matrix = matrix.skew(o.skewX, o.skewY, o.cx, o.cy) - - // act on flip - } else if (o.flip) { - if(o.flip == 'x' || o.flip == 'y') { - o.offset = o.offset == null ? target.bbox()['c' + o.flip] : o.offset - } else { - if(o.offset == null) { - bbox = target.bbox() - o.flip = bbox.cx - o.offset = bbox.cy - } else { - o.flip = o.offset - } - } - - matrix = new SVG.Matrix().flip(o.flip, o.offset) - - // act on translate - } else if (o.x != null || o.y != null) { - if (relative) { - // relative - matrix = matrix.translate(o.x, o.y) - } else { - // absolute - if (o.x != null) matrix.e = o.x - if (o.y != null) matrix.f = o.y - } - } - - return this.attr('transform', matrix) - } -}) - -SVG.extend(SVG.FX, { - transform: function(o, relative) { - // get target in case of the fx module, otherwise reference this - var target = this.target() - , matrix, bbox - - // act as a getter - if (typeof o !== 'object') { - // get current matrix - matrix = new SVG.Matrix(target).extract() - - return typeof o === 'string' ? matrix[o] : matrix - } - - // ensure relative flag - relative = !!relative || !!o.relative - - // act on matrix - if (o.a != null) { - matrix = new SVG.Matrix(o) - - // act on rotation - } else if (o.rotation != null) { - // ensure centre point - ensureCentre(o, target) - - // apply transformation - matrix = new SVG.Rotate(o.rotation, o.cx, o.cy) - - // act on scale - } else if (o.scale != null || o.scaleX != null || o.scaleY != null) { - // ensure centre point - ensureCentre(o, target) - - // ensure scale values on both axes - o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 - o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 - - matrix = new SVG.Scale(o.scaleX, o.scaleY, o.cx, o.cy) - - // act on skew - } else if (o.skewX != null || o.skewY != null) { - // ensure centre point - ensureCentre(o, target) - - // ensure skew values on both axes - o.skewX = o.skewX != null ? o.skewX : 0 - o.skewY = o.skewY != null ? o.skewY : 0 - - matrix = new SVG.Skew(o.skewX, o.skewY, o.cx, o.cy) - - // act on flip - } else if (o.flip) { - if(o.flip == 'x' || o.flip == 'y') { - o.offset = o.offset == null ? target.bbox()['c' + o.flip] : o.offset - } else { - if(o.offset == null) { - bbox = target.bbox() - o.flip = bbox.cx - o.offset = bbox.cy - } else { - o.flip = o.offset - } - } - - matrix = new SVG.Matrix().flip(o.flip, o.offset) - - // act on translate - } else if (o.x != null || o.y != null) { - matrix = new SVG.Translate(o.x, o.y) - } - - if(!matrix) return this - - matrix.relative = relative - - this.last().transforms.push(matrix) - - return this._callStart() - } -}) - -SVG.extend(SVG.Element, { - // Reset all transformations - untransform: function() { - return this.attr('transform', null) - }, - // merge the whole transformation chain into one matrix and returns it - matrixify: function() { - - var matrix = (this.attr('transform') || '') - // split transformations - .split(SVG.regex.transforms).slice(0,-1).map(function(str){ - // generate key => value pairs - var kv = str.trim().split('(') - return [kv[0], kv[1].split(SVG.regex.delimiter).map(function(str){ return parseFloat(str) })] - }) - // merge every transformation into one matrix - .reduce(function(matrix, transform){ - - if(transform[0] == 'matrix') return matrix.multiply(arrayToMatrix(transform[1])) - return matrix[transform[0]].apply(matrix, transform[1]) - - }, new SVG.Matrix()) - - return matrix - }, - // add an element to another parent without changing the visual representation on the screen - toParent: function(parent) { - if(this == parent) return this - var ctm = this.screenCTM() - var pCtm = parent.screenCTM().inverse() - - this.addTo(parent).untransform().transform(pCtm.multiply(ctm)) - - return this - }, - // same as above with parent equals root-svg - toDoc: function() { - return this.toParent(this.doc()) - } - -}) - -SVG.Transformation = SVG.invent({ - - create: function(source, inversed){ - - if(arguments.length > 1 && typeof inversed != 'boolean'){ - return this.constructor.call(this, [].slice.call(arguments)) - } - - if(Array.isArray(source)){ - for(var i = 0, len = this.arguments.length; i < len; ++i){ - this[this.arguments[i]] = source[i] - } - } else if(typeof source == 'object'){ - for(var i = 0, len = this.arguments.length; i < len; ++i){ - this[this.arguments[i]] = source[this.arguments[i]] - } - } - - this.inversed = false - - if(inversed === true){ - this.inversed = true - } - - } - -, extend: { - - arguments: [] - , method: '' - - , at: function(pos){ - - var params = [] - - for(var i = 0, len = this.arguments.length; i < len; ++i){ - params.push(this[this.arguments[i]]) - } - - var m = this._undo || new SVG.Matrix() - - m = new SVG.Matrix().morph(SVG.Matrix.prototype[this.method].apply(m, params)).at(pos) - - return this.inversed ? m.inverse() : m - - } - - , undo: function(o){ - for(var i = 0, len = this.arguments.length; i < len; ++i){ - o[this.arguments[i]] = typeof this[this.arguments[i]] == 'undefined' ? 0 : o[this.arguments[i]] - } - - // The method SVG.Matrix.extract which was used before calling this - // method to obtain a value for the parameter o doesn't return a cx and - // a cy so we use the ones that were provided to this object at its creation - o.cx = this.cx - o.cy = this.cy - - this._undo = new SVG[capitalize(this.method)](o, true).at(1) - - return this - } - - } - -}) - -SVG.Translate = SVG.invent({ - - parent: SVG.Matrix -, inherit: SVG.Transformation - -, create: function(source, inversed){ - this.constructor.apply(this, [].slice.call(arguments)) - } - -, extend: { - arguments: ['transformedX', 'transformedY'] - , method: 'translate' - } - -}) - -SVG.Rotate = SVG.invent({ - - parent: SVG.Matrix -, inherit: SVG.Transformation - -, create: function(source, inversed){ - this.constructor.apply(this, [].slice.call(arguments)) - } - -, extend: { - arguments: ['rotation', 'cx', 'cy'] - , method: 'rotate' - , at: function(pos){ - var m = new SVG.Matrix().rotate(new SVG.Number().morph(this.rotation - (this._undo ? this._undo.rotation : 0)).at(pos), this.cx, this.cy) - return this.inversed ? m.inverse() : m - } - , undo: function(o){ - this._undo = o - return this - } - } - -}) - -SVG.Scale = SVG.invent({ - - parent: SVG.Matrix -, inherit: SVG.Transformation - -, create: function(source, inversed){ - this.constructor.apply(this, [].slice.call(arguments)) - } - -, extend: { - arguments: ['scaleX', 'scaleY', 'cx', 'cy'] - , method: 'scale' - } - -}) - -SVG.Skew = SVG.invent({ - - parent: SVG.Matrix -, inherit: SVG.Transformation - -, create: function(source, inversed){ - this.constructor.apply(this, [].slice.call(arguments)) - } - -, extend: { - arguments: ['skewX', 'skewY', 'cx', 'cy'] - , method: 'skew' - } - -}) diff --git a/src/types/Base.js b/src/types/Base.js new file mode 100644 index 000000000..d2897a1b7 --- /dev/null +++ b/src/types/Base.js @@ -0,0 +1,10 @@ +export default class Base { + // constructor (node/*, {extensions = []} */) { + // // this.tags = [] + // // + // // for (let extension of extensions) { + // // extension.setup.call(this, node) + // // this.tags.push(extension.name) + // // } + // } +} diff --git a/src/types/Box.js b/src/types/Box.js new file mode 100644 index 000000000..4e90acfcc --- /dev/null +++ b/src/types/Box.js @@ -0,0 +1,270 @@ +import { delimiter } from '../modules/core/regex.js' +import { globals } from '../utils/window.js' +import { register } from '../utils/adopter.js' +import { registerMethods } from '../utils/methods.js' +import Matrix from './Matrix.js' +import Point from './Point.js' +import parser from '../modules/core/parser.js' + +export function isNulledBox(box) { + return !box.width && !box.height && !box.x && !box.y +} + +export function domContains(node) { + return ( + node === globals.document || + ( + globals.document.documentElement.contains || + function (node) { + // This is IE - it does not support contains() for top-level SVGs + while (node.parentNode) { + node = node.parentNode + } + return node === globals.document + } + ).call(globals.document.documentElement, node) + ) +} + +export default class Box { + constructor(...args) { + this.init(...args) + } + + addOffset() { + // offset by window scroll position, because getBoundingClientRect changes when window is scrolled + this.x += globals.window.pageXOffset + this.y += globals.window.pageYOffset + return new Box(this) + } + + init(source) { + const base = [0, 0, 0, 0] + source = + typeof source === 'string' + ? source.split(delimiter).map(parseFloat) + : Array.isArray(source) + ? source + : typeof source === 'object' + ? [ + source.left != null ? source.left : source.x, + source.top != null ? source.top : source.y, + source.width, + source.height + ] + : arguments.length === 4 + ? [].slice.call(arguments) + : base + + this.x = source[0] || 0 + this.y = source[1] || 0 + this.width = this.w = source[2] || 0 + this.height = this.h = source[3] || 0 + + // Add more bounding box properties + this.x2 = this.x + this.w + this.y2 = this.y + this.h + this.cx = this.x + this.w / 2 + this.cy = this.y + this.h / 2 + + return this + } + + isNulled() { + return isNulledBox(this) + } + + // Merge rect box with another, return a new instance + merge(box) { + const x = Math.min(this.x, box.x) + const y = Math.min(this.y, box.y) + const width = Math.max(this.x + this.width, box.x + box.width) - x + const height = Math.max(this.y + this.height, box.y + box.height) - y + + return new Box(x, y, width, height) + } + + toArray() { + return [this.x, this.y, this.width, this.height] + } + + toString() { + return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height + } + + transform(m) { + if (!(m instanceof Matrix)) { + m = new Matrix(m) + } + + let xMin = Infinity + let xMax = -Infinity + let yMin = Infinity + let yMax = -Infinity + + const pts = [ + new Point(this.x, this.y), + new Point(this.x2, this.y), + new Point(this.x, this.y2), + new Point(this.x2, this.y2) + ] + + pts.forEach(function (p) { + p = p.transform(m) + xMin = Math.min(xMin, p.x) + xMax = Math.max(xMax, p.x) + yMin = Math.min(yMin, p.y) + yMax = Math.max(yMax, p.y) + }) + + return new Box(xMin, yMin, xMax - xMin, yMax - yMin) + } +} + +function getBox(el, getBBoxFn, retry) { + let box + + try { + // Try to get the box with the provided function + box = getBBoxFn(el.node) + + // If the box is worthless and not even in the dom, retry + // by throwing an error here... + if (isNulledBox(box) && !domContains(el.node)) { + throw new Error('Element not in the dom') + } + } catch (e) { + // ... and calling the retry handler here + box = retry(el) + } + + return box +} + +export function bbox() { + // Function to get bbox is getBBox() + const getBBox = (node) => node.getBBox() + + // Take all measures so that a stupid browser renders the element + // so we can get the bbox from it when we try again + const retry = (el) => { + try { + const clone = el.clone().addTo(parser().svg).show() + const box = clone.node.getBBox() + clone.remove() + return box + } catch (e) { + // We give up... + throw new Error( + `Getting bbox of element "${ + el.node.nodeName + }" is not possible: ${e.toString()}` + ) + } + } + + const box = getBox(this, getBBox, retry) + const bbox = new Box(box) + + return bbox +} + +export function rbox(el) { + const getRBox = (node) => node.getBoundingClientRect() + const retry = (el) => { + // There is no point in trying tricks here because if we insert the element into the dom ourselves + // it obviously will be at the wrong position + throw new Error( + `Getting rbox of element "${el.node.nodeName}" is not possible` + ) + } + + const box = getBox(this, getRBox, retry) + const rbox = new Box(box) + + // If an element was passed, we want the bbox in the coordinate system of that element + if (el) { + return rbox.transform(el.screenCTM().inverseO()) + } + + // Else we want it in absolute screen coordinates + // Therefore we need to add the scrollOffset + return rbox.addOffset() +} + +// Checks whether the given point is inside the bounding box +export function inside(x, y) { + const box = this.bbox() + + return ( + x > box.x && y > box.y && x < box.x + box.width && y < box.y + box.height + ) +} + +registerMethods({ + viewbox: { + viewbox(x, y, width, height) { + // act as getter + if (x == null) return new Box(this.attr('viewBox')) + + // act as setter + return this.attr('viewBox', new Box(x, y, width, height)) + }, + + zoom(level, point) { + // Its best to rely on the attributes here and here is why: + // clientXYZ: Doesn't work on non-root svgs because they dont have a CSSBox (silly!) + // getBoundingClientRect: Doesn't work because Chrome just ignores width and height of nested svgs completely + // that means, their clientRect is always as big as the content. + // Furthermore this size is incorrect if the element is further transformed by its parents + // computedStyle: Only returns meaningful values if css was used with px. We dont go this route here! + // getBBox: returns the bounding box of its content - that doesn't help! + let { width, height } = this.attr(['width', 'height']) + + // Width and height is a string when a number with a unit is present which we can't use + // So we try clientXYZ + if ( + (!width && !height) || + typeof width === 'string' || + typeof height === 'string' + ) { + width = this.node.clientWidth + height = this.node.clientHeight + } + + // Giving up... + if (!width || !height) { + throw new Error( + 'Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element' + ) + } + + const v = this.viewbox() + + const zoomX = width / v.width + const zoomY = height / v.height + const zoom = Math.min(zoomX, zoomY) + + if (level == null) { + return zoom + } + + let zoomAmount = zoom / level + + // Set the zoomAmount to the highest value which is safe to process and recover from + // The * 100 is a bit of wiggle room for the matrix transformation + if (zoomAmount === Infinity) zoomAmount = Number.MAX_SAFE_INTEGER / 100 + + point = + point || new Point(width / 2 / zoomX + v.x, height / 2 / zoomY + v.y) + + const box = new Box(v).transform( + new Matrix({ scale: zoomAmount, origin: point }) + ) + + return this.viewbox(box) + } + } +}) + +register(Box, 'Box') diff --git a/src/types/Color.js b/src/types/Color.js new file mode 100644 index 000000000..2f61b5a0f --- /dev/null +++ b/src/types/Color.js @@ -0,0 +1,450 @@ +import { hex, isHex, isRgb, rgb, whitespace } from '../modules/core/regex.js' + +function sixDigitHex(hex) { + return hex.length === 4 + ? [ + '#', + hex.substring(1, 2), + hex.substring(1, 2), + hex.substring(2, 3), + hex.substring(2, 3), + hex.substring(3, 4), + hex.substring(3, 4) + ].join('') + : hex +} + +function componentHex(component) { + const integer = Math.round(component) + const bounded = Math.max(0, Math.min(255, integer)) + const hex = bounded.toString(16) + return hex.length === 1 ? '0' + hex : hex +} + +function is(object, space) { + for (let i = space.length; i--; ) { + if (object[space[i]] == null) { + return false + } + } + return true +} + +function getParameters(a, b) { + const params = is(a, 'rgb') + ? { _a: a.r, _b: a.g, _c: a.b, _d: 0, space: 'rgb' } + : is(a, 'xyz') + ? { _a: a.x, _b: a.y, _c: a.z, _d: 0, space: 'xyz' } + : is(a, 'hsl') + ? { _a: a.h, _b: a.s, _c: a.l, _d: 0, space: 'hsl' } + : is(a, 'lab') + ? { _a: a.l, _b: a.a, _c: a.b, _d: 0, space: 'lab' } + : is(a, 'lch') + ? { _a: a.l, _b: a.c, _c: a.h, _d: 0, space: 'lch' } + : is(a, 'cmyk') + ? { _a: a.c, _b: a.m, _c: a.y, _d: a.k, space: 'cmyk' } + : { _a: 0, _b: 0, _c: 0, space: 'rgb' } + + params.space = b || params.space + return params +} + +function cieSpace(space) { + if (space === 'lab' || space === 'xyz' || space === 'lch') { + return true + } else { + return false + } +} + +function hueToRgb(p, q, t) { + if (t < 0) t += 1 + if (t > 1) t -= 1 + if (t < 1 / 6) return p + (q - p) * 6 * t + if (t < 1 / 2) return q + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6 + return p +} + +export default class Color { + constructor(...inputs) { + this.init(...inputs) + } + + // Test if given value is a color + static isColor(color) { + return ( + color && (color instanceof Color || this.isRgb(color) || this.test(color)) + ) + } + + // Test if given value is an rgb object + static isRgb(color) { + return ( + color && + typeof color.r === 'number' && + typeof color.g === 'number' && + typeof color.b === 'number' + ) + } + + /* + Generating random colors + */ + static random(mode = 'vibrant', t) { + // Get the math modules + const { random, round, sin, PI: pi } = Math + + // Run the correct generator + if (mode === 'vibrant') { + const l = (81 - 57) * random() + 57 + const c = (83 - 45) * random() + 45 + const h = 360 * random() + const color = new Color(l, c, h, 'lch') + return color + } else if (mode === 'sine') { + t = t == null ? random() : t + const r = round(80 * sin((2 * pi * t) / 0.5 + 0.01) + 150) + const g = round(50 * sin((2 * pi * t) / 0.5 + 4.6) + 200) + const b = round(100 * sin((2 * pi * t) / 0.5 + 2.3) + 150) + const color = new Color(r, g, b) + return color + } else if (mode === 'pastel') { + const l = (94 - 86) * random() + 86 + const c = (26 - 9) * random() + 9 + const h = 360 * random() + const color = new Color(l, c, h, 'lch') + return color + } else if (mode === 'dark') { + const l = 10 + 10 * random() + const c = (125 - 75) * random() + 86 + const h = 360 * random() + const color = new Color(l, c, h, 'lch') + return color + } else if (mode === 'rgb') { + const r = 255 * random() + const g = 255 * random() + const b = 255 * random() + const color = new Color(r, g, b) + return color + } else if (mode === 'lab') { + const l = 100 * random() + const a = 256 * random() - 128 + const b = 256 * random() - 128 + const color = new Color(l, a, b, 'lab') + return color + } else if (mode === 'grey') { + const grey = 255 * random() + const color = new Color(grey, grey, grey) + return color + } else { + throw new Error('Unsupported random color mode') + } + } + + // Test if given value is a color string + static test(color) { + return typeof color === 'string' && (isHex.test(color) || isRgb.test(color)) + } + + cmyk() { + // Get the rgb values for the current color + const { _a, _b, _c } = this.rgb() + const [r, g, b] = [_a, _b, _c].map((v) => v / 255) + + // Get the cmyk values in an unbounded format + const k = Math.min(1 - r, 1 - g, 1 - b) + + if (k === 1) { + // Catch the black case + return new Color(0, 0, 0, 1, 'cmyk') + } + + const c = (1 - r - k) / (1 - k) + const m = (1 - g - k) / (1 - k) + const y = (1 - b - k) / (1 - k) + + // Construct the new color + const color = new Color(c, m, y, k, 'cmyk') + return color + } + + hsl() { + // Get the rgb values + const { _a, _b, _c } = this.rgb() + const [r, g, b] = [_a, _b, _c].map((v) => v / 255) + + // Find the maximum and minimum values to get the lightness + const max = Math.max(r, g, b) + const min = Math.min(r, g, b) + const l = (max + min) / 2 + + // If the r, g, v values are identical then we are grey + const isGrey = max === min + + // Calculate the hue and saturation + const delta = max - min + const s = isGrey + ? 0 + : l > 0.5 + ? delta / (2 - max - min) + : delta / (max + min) + const h = isGrey + ? 0 + : max === r + ? ((g - b) / delta + (g < b ? 6 : 0)) / 6 + : max === g + ? ((b - r) / delta + 2) / 6 + : max === b + ? ((r - g) / delta + 4) / 6 + : 0 + + // Construct and return the new color + const color = new Color(360 * h, 100 * s, 100 * l, 'hsl') + return color + } + + init(a = 0, b = 0, c = 0, d = 0, space = 'rgb') { + // This catches the case when a falsy value is passed like '' + a = !a ? 0 : a + + // Reset all values in case the init function is rerun with new color space + if (this.space) { + for (const component in this.space) { + delete this[this.space[component]] + } + } + + if (typeof a === 'number') { + // Allow for the case that we don't need d... + space = typeof d === 'string' ? d : space + d = typeof d === 'string' ? 0 : d + + // Assign the values straight to the color + Object.assign(this, { _a: a, _b: b, _c: c, _d: d, space }) + // If the user gave us an array, make the color from it + } else if (a instanceof Array) { + this.space = b || (typeof a[3] === 'string' ? a[3] : a[4]) || 'rgb' + Object.assign(this, { _a: a[0], _b: a[1], _c: a[2], _d: a[3] || 0 }) + } else if (a instanceof Object) { + // Set the object up and assign its values directly + const values = getParameters(a, b) + Object.assign(this, values) + } else if (typeof a === 'string') { + if (isRgb.test(a)) { + const noWhitespace = a.replace(whitespace, '') + const [_a, _b, _c] = rgb + .exec(noWhitespace) + .slice(1, 4) + .map((v) => parseInt(v)) + Object.assign(this, { _a, _b, _c, _d: 0, space: 'rgb' }) + } else if (isHex.test(a)) { + const hexParse = (v) => parseInt(v, 16) + const [, _a, _b, _c] = hex.exec(sixDigitHex(a)).map(hexParse) + Object.assign(this, { _a, _b, _c, _d: 0, space: 'rgb' }) + } else throw Error("Unsupported string format, can't construct Color") + } + + // Now add the components as a convenience + const { _a, _b, _c, _d } = this + const components = + this.space === 'rgb' + ? { r: _a, g: _b, b: _c } + : this.space === 'xyz' + ? { x: _a, y: _b, z: _c } + : this.space === 'hsl' + ? { h: _a, s: _b, l: _c } + : this.space === 'lab' + ? { l: _a, a: _b, b: _c } + : this.space === 'lch' + ? { l: _a, c: _b, h: _c } + : this.space === 'cmyk' + ? { c: _a, m: _b, y: _c, k: _d } + : {} + Object.assign(this, components) + } + + lab() { + // Get the xyz color + const { x, y, z } = this.xyz() + + // Get the lab components + const l = 116 * y - 16 + const a = 500 * (x - y) + const b = 200 * (y - z) + + // Construct and return a new color + const color = new Color(l, a, b, 'lab') + return color + } + + lch() { + // Get the lab color directly + const { l, a, b } = this.lab() + + // Get the chromaticity and the hue using polar coordinates + const c = Math.sqrt(a ** 2 + b ** 2) + let h = (180 * Math.atan2(b, a)) / Math.PI + if (h < 0) { + h *= -1 + h = 360 - h + } + + // Make a new color and return it + const color = new Color(l, c, h, 'lch') + return color + } + /* + Conversion Methods + */ + + rgb() { + if (this.space === 'rgb') { + return this + } else if (cieSpace(this.space)) { + // Convert to the xyz color space + let { x, y, z } = this + if (this.space === 'lab' || this.space === 'lch') { + // Get the values in the lab space + let { l, a, b } = this + if (this.space === 'lch') { + const { c, h } = this + const dToR = Math.PI / 180 + a = c * Math.cos(dToR * h) + b = c * Math.sin(dToR * h) + } + + // Undo the nonlinear function + const yL = (l + 16) / 116 + const xL = a / 500 + yL + const zL = yL - b / 200 + + // Get the xyz values + const ct = 16 / 116 + const mx = 0.008856 + const nm = 7.787 + x = 0.95047 * (xL ** 3 > mx ? xL ** 3 : (xL - ct) / nm) + y = 1.0 * (yL ** 3 > mx ? yL ** 3 : (yL - ct) / nm) + z = 1.08883 * (zL ** 3 > mx ? zL ** 3 : (zL - ct) / nm) + } + + // Convert xyz to unbounded rgb values + const rU = x * 3.2406 + y * -1.5372 + z * -0.4986 + const gU = x * -0.9689 + y * 1.8758 + z * 0.0415 + const bU = x * 0.0557 + y * -0.204 + z * 1.057 + + // Convert the values to true rgb values + const pow = Math.pow + const bd = 0.0031308 + const r = rU > bd ? 1.055 * pow(rU, 1 / 2.4) - 0.055 : 12.92 * rU + const g = gU > bd ? 1.055 * pow(gU, 1 / 2.4) - 0.055 : 12.92 * gU + const b = bU > bd ? 1.055 * pow(bU, 1 / 2.4) - 0.055 : 12.92 * bU + + // Make and return the color + const color = new Color(255 * r, 255 * g, 255 * b) + return color + } else if (this.space === 'hsl') { + // https://bgrins.github.io/TinyColor/docs/tinycolor.html + // Get the current hsl values + let { h, s, l } = this + h /= 360 + s /= 100 + l /= 100 + + // If we are grey, then just make the color directly + if (s === 0) { + l *= 255 + const color = new Color(l, l, l) + return color + } + + // TODO I have no idea what this does :D If you figure it out, tell me! + const q = l < 0.5 ? l * (1 + s) : l + s - l * s + const p = 2 * l - q + + // Get the rgb values + const r = 255 * hueToRgb(p, q, h + 1 / 3) + const g = 255 * hueToRgb(p, q, h) + const b = 255 * hueToRgb(p, q, h - 1 / 3) + + // Make a new color + const color = new Color(r, g, b) + return color + } else if (this.space === 'cmyk') { + // https://gist.github.com/felipesabino/5066336 + // Get the normalised cmyk values + const { c, m, y, k } = this + + // Get the rgb values + const r = 255 * (1 - Math.min(1, c * (1 - k) + k)) + const g = 255 * (1 - Math.min(1, m * (1 - k) + k)) + const b = 255 * (1 - Math.min(1, y * (1 - k) + k)) + + // Form the color and return it + const color = new Color(r, g, b) + return color + } else { + return this + } + } + + toArray() { + const { _a, _b, _c, _d, space } = this + return [_a, _b, _c, _d, space] + } + + toHex() { + const [r, g, b] = this._clamped().map(componentHex) + return `#${r}${g}${b}` + } + + toRgb() { + const [rV, gV, bV] = this._clamped() + const string = `rgb(${rV},${gV},${bV})` + return string + } + + toString() { + return this.toHex() + } + + xyz() { + // Normalise the red, green and blue values + const { _a: r255, _b: g255, _c: b255 } = this.rgb() + const [r, g, b] = [r255, g255, b255].map((v) => v / 255) + + // Convert to the lab rgb space + const rL = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92 + const gL = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92 + const bL = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92 + + // Convert to the xyz color space without bounding the values + const xU = (rL * 0.4124 + gL * 0.3576 + bL * 0.1805) / 0.95047 + const yU = (rL * 0.2126 + gL * 0.7152 + bL * 0.0722) / 1.0 + const zU = (rL * 0.0193 + gL * 0.1192 + bL * 0.9505) / 1.08883 + + // Get the proper xyz values by applying the bounding + const x = xU > 0.008856 ? Math.pow(xU, 1 / 3) : 7.787 * xU + 16 / 116 + const y = yU > 0.008856 ? Math.pow(yU, 1 / 3) : 7.787 * yU + 16 / 116 + const z = zU > 0.008856 ? Math.pow(zU, 1 / 3) : 7.787 * zU + 16 / 116 + + // Make and return the color + const color = new Color(x, y, z, 'xyz') + return color + } + + /* + Input and Output methods + */ + + _clamped() { + const { _a, _b, _c } = this.rgb() + const { max, min, round } = Math + const format = (v) => max(0, min(round(v), 255)) + return [_a, _b, _c].map(format) + } + + /* + Constructing colors + */ +} diff --git a/src/types/EventTarget.js b/src/types/EventTarget.js new file mode 100644 index 000000000..de13a5f16 --- /dev/null +++ b/src/types/EventTarget.js @@ -0,0 +1,56 @@ +import { dispatch, off, on } from '../modules/core/event.js' +import { register } from '../utils/adopter.js' +import Base from './Base.js' + +export default class EventTarget extends Base { + addEventListener() {} + + dispatch(event, data, options) { + return dispatch(this, event, data, options) + } + + dispatchEvent(event) { + const bag = this.getEventHolder().events + if (!bag) return true + + const events = bag[event.type] + + for (const i in events) { + for (const j in events[i]) { + events[i][j](event) + } + } + + return !event.defaultPrevented + } + + // Fire given event + fire(event, data, options) { + this.dispatch(event, data, options) + return this + } + + getEventHolder() { + return this + } + + getEventTarget() { + return this + } + + // Unbind event from listener + off(event, listener, options) { + off(this, event, listener, options) + return this + } + + // Bind given event to listener + on(event, listener, binding, options) { + on(this, event, listener, binding, options) + return this + } + + removeEventListener() {} +} + +register(EventTarget, 'EventTarget') diff --git a/src/types/List.js b/src/types/List.js new file mode 100644 index 000000000..22b90278f --- /dev/null +++ b/src/types/List.js @@ -0,0 +1,63 @@ +import { extend } from '../utils/adopter.js' +// import { subClassArray } from './ArrayPolyfill.js' + +class List extends Array { + constructor(arr = [], ...args) { + super(arr, ...args) + if (typeof arr === 'number') return this + this.length = 0 + this.push(...arr) + } +} + +/* = subClassArray('List', Array, function (arr = []) { + // This catches the case, that native map tries to create an array with new Array(1) + if (typeof arr === 'number') return this + this.length = 0 + this.push(...arr) +}) */ + +export default List + +extend([List], { + each(fnOrMethodName, ...args) { + if (typeof fnOrMethodName === 'function') { + return this.map((el, i, arr) => { + return fnOrMethodName.call(el, el, i, arr) + }) + } else { + return this.map((el) => { + return el[fnOrMethodName](...args) + }) + } + }, + + toArray() { + return Array.prototype.concat.apply([], this) + } +}) + +const reserved = ['toArray', 'constructor', 'each'] + +List.extend = function (methods) { + methods = methods.reduce((obj, name) => { + // Don't overwrite own methods + if (reserved.includes(name)) return obj + + // Don't add private methods + if (name[0] === '_') return obj + + // Allow access to original Array methods through a prefix + if (name in Array.prototype) { + obj['$' + name] = Array.prototype[name] + } + + // Relay every call to each() + obj[name] = function (...attrs) { + return this.each(name, ...attrs) + } + return obj + }, {}) + + extend([List], methods) +} diff --git a/src/types/Matrix.js b/src/types/Matrix.js new file mode 100644 index 000000000..803ec37bc --- /dev/null +++ b/src/types/Matrix.js @@ -0,0 +1,543 @@ +import { delimiter } from '../modules/core/regex.js' +import { radians } from '../utils/utils.js' +import { register } from '../utils/adopter.js' +import Element from '../elements/Element.js' +import Point from './Point.js' + +function closeEnough(a, b, threshold) { + return Math.abs(b - a) < (threshold || 1e-6) +} + +export default class Matrix { + constructor(...args) { + this.init(...args) + } + + static formatTransforms(o) { + // Get all of the parameters required to form the matrix + const flipBoth = o.flip === 'both' || o.flip === true + const flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1 + const flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1 + const skewX = + o.skew && o.skew.length + ? o.skew[0] + : isFinite(o.skew) + ? o.skew + : isFinite(o.skewX) + ? o.skewX + : 0 + const skewY = + o.skew && o.skew.length + ? o.skew[1] + : isFinite(o.skew) + ? o.skew + : isFinite(o.skewY) + ? o.skewY + : 0 + const scaleX = + o.scale && o.scale.length + ? o.scale[0] * flipX + : isFinite(o.scale) + ? o.scale * flipX + : isFinite(o.scaleX) + ? o.scaleX * flipX + : flipX + const scaleY = + o.scale && o.scale.length + ? o.scale[1] * flipY + : isFinite(o.scale) + ? o.scale * flipY + : isFinite(o.scaleY) + ? o.scaleY * flipY + : flipY + const shear = o.shear || 0 + const theta = o.rotate || o.theta || 0 + const origin = new Point( + o.origin || o.around || o.ox || o.originX, + o.oy || o.originY + ) + const ox = origin.x + const oy = origin.y + // We need Point to be invalid if nothing was passed because we cannot default to 0 here. That is why NaN + const position = new Point( + o.position || o.px || o.positionX || NaN, + o.py || o.positionY || NaN + ) + const px = position.x + const py = position.y + const translate = new Point( + o.translate || o.tx || o.translateX, + o.ty || o.translateY + ) + const tx = translate.x + const ty = translate.y + const relative = new Point( + o.relative || o.rx || o.relativeX, + o.ry || o.relativeY + ) + const rx = relative.x + const ry = relative.y + + // Populate all of the values + return { + scaleX, + scaleY, + skewX, + skewY, + shear, + theta, + rx, + ry, + tx, + ty, + ox, + oy, + px, + py + } + } + + static fromArray(a) { + return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] } + } + + static isMatrixLike(o) { + return ( + o.a != null || + o.b != null || + o.c != null || + o.d != null || + o.e != null || + o.f != null + ) + } + + // left matrix, right matrix, target matrix which is overwritten + static matrixMultiply(l, r, o) { + // Work out the product directly + const a = l.a * r.a + l.c * r.b + const b = l.b * r.a + l.d * r.b + const c = l.a * r.c + l.c * r.d + const d = l.b * r.c + l.d * r.d + const e = l.e + l.a * r.e + l.c * r.f + const f = l.f + l.b * r.e + l.d * r.f + + // make sure to use local variables because l/r and o could be the same + o.a = a + o.b = b + o.c = c + o.d = d + o.e = e + o.f = f + + return o + } + + around(cx, cy, matrix) { + return this.clone().aroundO(cx, cy, matrix) + } + + // Transform around a center point + aroundO(cx, cy, matrix) { + const dx = cx || 0 + const dy = cy || 0 + return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy) + } + + // Clones this matrix + clone() { + return new Matrix(this) + } + + // Decomposes this matrix into its affine parameters + decompose(cx = 0, cy = 0) { + // Get the parameters from the matrix + const a = this.a + const b = this.b + const c = this.c + const d = this.d + const e = this.e + const f = this.f + + // Figure out if the winding direction is clockwise or counterclockwise + const determinant = a * d - b * c + const ccw = determinant > 0 ? 1 : -1 + + // Since we only shear in x, we can use the x basis to get the x scale + // and the rotation of the resulting matrix + const sx = ccw * Math.sqrt(a * a + b * b) + const thetaRad = Math.atan2(ccw * b, ccw * a) + const theta = (180 / Math.PI) * thetaRad + const ct = Math.cos(thetaRad) + const st = Math.sin(thetaRad) + + // We can then solve the y basis vector simultaneously to get the other + // two affine parameters directly from these parameters + const lam = (a * c + b * d) / determinant + const sy = (c * sx) / (lam * a - b) || (d * sx) / (lam * b + a) + + // Use the translations + const tx = e - cx + cx * ct * sx + cy * (lam * ct * sx - st * sy) + const ty = f - cy + cx * st * sx + cy * (lam * st * sx + ct * sy) + + // Construct the decomposition and return it + return { + // Return the affine parameters + scaleX: sx, + scaleY: sy, + shear: lam, + rotate: theta, + translateX: tx, + translateY: ty, + originX: cx, + originY: cy, + + // Return the matrix parameters + a: this.a, + b: this.b, + c: this.c, + d: this.d, + e: this.e, + f: this.f + } + } + + // Check if two matrices are equal + equals(other) { + if (other === this) return true + const comp = new Matrix(other) + return ( + closeEnough(this.a, comp.a) && + closeEnough(this.b, comp.b) && + closeEnough(this.c, comp.c) && + closeEnough(this.d, comp.d) && + closeEnough(this.e, comp.e) && + closeEnough(this.f, comp.f) + ) + } + + // Flip matrix on x or y, at a given offset + flip(axis, around) { + return this.clone().flipO(axis, around) + } + + flipO(axis, around) { + return axis === 'x' + ? this.scaleO(-1, 1, around, 0) + : axis === 'y' + ? this.scaleO(1, -1, 0, around) + : this.scaleO(-1, -1, axis, around || axis) // Define an x, y flip point + } + + // Initialize + init(source) { + const base = Matrix.fromArray([1, 0, 0, 1, 0, 0]) + + // ensure source as object + source = + source instanceof Element + ? source.matrixify() + : typeof source === 'string' + ? Matrix.fromArray(source.split(delimiter).map(parseFloat)) + : Array.isArray(source) + ? Matrix.fromArray(source) + : typeof source === 'object' && Matrix.isMatrixLike(source) + ? source + : typeof source === 'object' + ? new Matrix().transform(source) + : arguments.length === 6 + ? Matrix.fromArray([].slice.call(arguments)) + : base + + // Merge the source matrix with the base matrix + this.a = source.a != null ? source.a : base.a + this.b = source.b != null ? source.b : base.b + this.c = source.c != null ? source.c : base.c + this.d = source.d != null ? source.d : base.d + this.e = source.e != null ? source.e : base.e + this.f = source.f != null ? source.f : base.f + + return this + } + + inverse() { + return this.clone().inverseO() + } + + // Inverses matrix + inverseO() { + // Get the current parameters out of the matrix + const a = this.a + const b = this.b + const c = this.c + const d = this.d + const e = this.e + const f = this.f + + // Invert the 2x2 matrix in the top left + const det = a * d - b * c + if (!det) throw new Error('Cannot invert ' + this) + + // Calculate the top 2x2 matrix + const na = d / det + const nb = -b / det + const nc = -c / det + const nd = a / det + + // Apply the inverted matrix to the top right + const ne = -(na * e + nc * f) + const nf = -(nb * e + nd * f) + + // Construct the inverted matrix + this.a = na + this.b = nb + this.c = nc + this.d = nd + this.e = ne + this.f = nf + + return this + } + + lmultiply(matrix) { + return this.clone().lmultiplyO(matrix) + } + + lmultiplyO(matrix) { + const r = this + const l = matrix instanceof Matrix ? matrix : new Matrix(matrix) + + return Matrix.matrixMultiply(l, r, this) + } + + // Left multiplies by the given matrix + multiply(matrix) { + return this.clone().multiplyO(matrix) + } + + multiplyO(matrix) { + // Get the matrices + const l = this + const r = matrix instanceof Matrix ? matrix : new Matrix(matrix) + + return Matrix.matrixMultiply(l, r, this) + } + + // Rotate matrix + rotate(r, cx, cy) { + return this.clone().rotateO(r, cx, cy) + } + + rotateO(r, cx = 0, cy = 0) { + // Convert degrees to radians + r = radians(r) + + const cos = Math.cos(r) + const sin = Math.sin(r) + + const { a, b, c, d, e, f } = this + + this.a = a * cos - b * sin + this.b = b * cos + a * sin + this.c = c * cos - d * sin + this.d = d * cos + c * sin + this.e = e * cos - f * sin + cy * sin - cx * cos + cx + this.f = f * cos + e * sin - cx * sin - cy * cos + cy + + return this + } + + // Scale matrix + scale() { + return this.clone().scaleO(...arguments) + } + + scaleO(x, y = x, cx = 0, cy = 0) { + // Support uniform scaling + if (arguments.length === 3) { + cy = cx + cx = y + y = x + } + + const { a, b, c, d, e, f } = this + + this.a = a * x + this.b = b * y + this.c = c * x + this.d = d * y + this.e = e * x - cx * x + cx + this.f = f * y - cy * y + cy + + return this + } + + // Shear matrix + shear(a, cx, cy) { + return this.clone().shearO(a, cx, cy) + } + + // eslint-disable-next-line no-unused-vars + shearO(lx, cx = 0, cy = 0) { + const { a, b, c, d, e, f } = this + + this.a = a + b * lx + this.c = c + d * lx + this.e = e + f * lx - cy * lx + + return this + } + + // Skew Matrix + skew() { + return this.clone().skewO(...arguments) + } + + skewO(x, y = x, cx = 0, cy = 0) { + // support uniformal skew + if (arguments.length === 3) { + cy = cx + cx = y + y = x + } + + // Convert degrees to radians + x = radians(x) + y = radians(y) + + const lx = Math.tan(x) + const ly = Math.tan(y) + + const { a, b, c, d, e, f } = this + + this.a = a + b * lx + this.b = b + a * ly + this.c = c + d * lx + this.d = d + c * ly + this.e = e + f * lx - cy * lx + this.f = f + e * ly - cx * ly + + return this + } + + // SkewX + skewX(x, cx, cy) { + return this.skew(x, 0, cx, cy) + } + + // SkewY + skewY(y, cx, cy) { + return this.skew(0, y, cx, cy) + } + + toArray() { + return [this.a, this.b, this.c, this.d, this.e, this.f] + } + + // Convert matrix to string + toString() { + return ( + 'matrix(' + + this.a + + ',' + + this.b + + ',' + + this.c + + ',' + + this.d + + ',' + + this.e + + ',' + + this.f + + ')' + ) + } + + // Transform a matrix into another matrix by manipulating the space + transform(o) { + // Check if o is a matrix and then left multiply it directly + if (Matrix.isMatrixLike(o)) { + const matrix = new Matrix(o) + return matrix.multiplyO(this) + } + + // Get the proposed transformations and the current transformations + const t = Matrix.formatTransforms(o) + const current = this + const { x: ox, y: oy } = new Point(t.ox, t.oy).transform(current) + + // Construct the resulting matrix + const transformer = new Matrix() + .translateO(t.rx, t.ry) + .lmultiplyO(current) + .translateO(-ox, -oy) + .scaleO(t.scaleX, t.scaleY) + .skewO(t.skewX, t.skewY) + .shearO(t.shear) + .rotateO(t.theta) + .translateO(ox, oy) + + // If we want the origin at a particular place, we force it there + if (isFinite(t.px) || isFinite(t.py)) { + const origin = new Point(ox, oy).transform(transformer) + // TODO: Replace t.px with isFinite(t.px) + // Doesn't work because t.px is also 0 if it wasn't passed + const dx = isFinite(t.px) ? t.px - origin.x : 0 + const dy = isFinite(t.py) ? t.py - origin.y : 0 + transformer.translateO(dx, dy) + } + + // Translate now after positioning + transformer.translateO(t.tx, t.ty) + return transformer + } + + // Translate matrix + translate(x, y) { + return this.clone().translateO(x, y) + } + + translateO(x, y) { + this.e += x || 0 + this.f += y || 0 + return this + } + + valueOf() { + return { + a: this.a, + b: this.b, + c: this.c, + d: this.d, + e: this.e, + f: this.f + } + } +} + +export function ctm() { + return new Matrix(this.node.getCTM()) +} + +export function screenCTM() { + try { + /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537 + This is needed because FF does not return the transformation matrix + for the inner coordinate system when getScreenCTM() is called on nested svgs. + However all other Browsers do that */ + if (typeof this.isRoot === 'function' && !this.isRoot()) { + const rect = this.rect(1, 1) + const m = rect.node.getScreenCTM() + rect.remove() + return new Matrix(m) + } + return new Matrix(this.node.getScreenCTM()) + } catch (e) { + console.warn( + `Cannot get CTM from SVG node ${this.node.nodeName}. Is the element rendered?` + ) + return new Matrix() + } +} + +register(Matrix, 'Matrix') diff --git a/src/types/PathArray.js b/src/types/PathArray.js new file mode 100644 index 000000000..9e59ed705 --- /dev/null +++ b/src/types/PathArray.js @@ -0,0 +1,150 @@ +import SVGArray from './SVGArray.js' +import parser from '../modules/core/parser.js' +import Box from './Box.js' +import { pathParser } from '../utils/pathParser.js' + +function arrayToString(a) { + let s = '' + for (let i = 0, il = a.length; i < il; i++) { + s += a[i][0] + + if (a[i][1] != null) { + s += a[i][1] + + if (a[i][2] != null) { + s += ' ' + s += a[i][2] + + if (a[i][3] != null) { + s += ' ' + s += a[i][3] + s += ' ' + s += a[i][4] + + if (a[i][5] != null) { + s += ' ' + s += a[i][5] + s += ' ' + s += a[i][6] + + if (a[i][7] != null) { + s += ' ' + s += a[i][7] + } + } + } + } + } + } + + return s + ' ' +} + +export default class PathArray extends SVGArray { + // Get bounding box of path + bbox() { + parser().path.setAttribute('d', this.toString()) + return new Box(parser.nodes.path.getBBox()) + } + + // Move path string + move(x, y) { + // get bounding box of current situation + const box = this.bbox() + + // get relative offset + x -= box.x + y -= box.y + + if (!isNaN(x) && !isNaN(y)) { + // move every point + for (let l, i = this.length - 1; i >= 0; i--) { + l = this[i][0] + + if (l === 'M' || l === 'L' || l === 'T') { + this[i][1] += x + this[i][2] += y + } else if (l === 'H') { + this[i][1] += x + } else if (l === 'V') { + this[i][1] += y + } else if (l === 'C' || l === 'S' || l === 'Q') { + this[i][1] += x + this[i][2] += y + this[i][3] += x + this[i][4] += y + + if (l === 'C') { + this[i][5] += x + this[i][6] += y + } + } else if (l === 'A') { + this[i][6] += x + this[i][7] += y + } + } + } + + return this + } + + // Absolutize and parse path to array + parse(d = 'M0 0') { + if (Array.isArray(d)) { + d = Array.prototype.concat.apply([], d).toString() + } + + return pathParser(d) + } + + // Resize path string + size(width, height) { + // get bounding box of current situation + const box = this.bbox() + let i, l + + // If the box width or height is 0 then we ignore + // transformations on the respective axis + box.width = box.width === 0 ? 1 : box.width + box.height = box.height === 0 ? 1 : box.height + + // recalculate position of all points according to new size + for (i = this.length - 1; i >= 0; i--) { + l = this[i][0] + + if (l === 'M' || l === 'L' || l === 'T') { + this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x + this[i][2] = ((this[i][2] - box.y) * height) / box.height + box.y + } else if (l === 'H') { + this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x + } else if (l === 'V') { + this[i][1] = ((this[i][1] - box.y) * height) / box.height + box.y + } else if (l === 'C' || l === 'S' || l === 'Q') { + this[i][1] = ((this[i][1] - box.x) * width) / box.width + box.x + this[i][2] = ((this[i][2] - box.y) * height) / box.height + box.y + this[i][3] = ((this[i][3] - box.x) * width) / box.width + box.x + this[i][4] = ((this[i][4] - box.y) * height) / box.height + box.y + + if (l === 'C') { + this[i][5] = ((this[i][5] - box.x) * width) / box.width + box.x + this[i][6] = ((this[i][6] - box.y) * height) / box.height + box.y + } + } else if (l === 'A') { + // resize radii + this[i][1] = (this[i][1] * width) / box.width + this[i][2] = (this[i][2] * height) / box.height + + // move position values + this[i][6] = ((this[i][6] - box.x) * width) / box.width + box.x + this[i][7] = ((this[i][7] - box.y) * height) / box.height + box.y + } + } + + return this + } + + // Convert array to string + toString() { + return arrayToString(this) + } +} diff --git a/src/types/Point.js b/src/types/Point.js new file mode 100644 index 000000000..cfd204e2e --- /dev/null +++ b/src/types/Point.js @@ -0,0 +1,57 @@ +import Matrix from './Matrix.js' + +export default class Point { + // Initialize + constructor(...args) { + this.init(...args) + } + + // Clone point + clone() { + return new Point(this) + } + + init(x, y) { + const base = { x: 0, y: 0 } + + // ensure source as object + const source = Array.isArray(x) + ? { x: x[0], y: x[1] } + : typeof x === 'object' + ? { x: x.x, y: x.y } + : { x: x, y: y } + + // merge source + this.x = source.x == null ? base.x : source.x + this.y = source.y == null ? base.y : source.y + + return this + } + + toArray() { + return [this.x, this.y] + } + + transform(m) { + return this.clone().transformO(m) + } + + // Transform point with matrix + transformO(m) { + if (!Matrix.isMatrixLike(m)) { + m = new Matrix(m) + } + + const { x, y } = this + + // Perform the matrix multiplication + this.x = m.a * x + m.c * y + m.e + this.y = m.b * x + m.d * y + m.f + + return this + } +} + +export function point(x, y) { + return new Point(x, y).transformO(this.screenCTM().inverseO()) +} diff --git a/src/types/PointArray.js b/src/types/PointArray.js new file mode 100644 index 000000000..f72c2bc8d --- /dev/null +++ b/src/types/PointArray.js @@ -0,0 +1,121 @@ +import { delimiter } from '../modules/core/regex.js' +import SVGArray from './SVGArray.js' +import Box from './Box.js' +import Matrix from './Matrix.js' + +export default class PointArray extends SVGArray { + // Get bounding box of points + bbox() { + let maxX = -Infinity + let maxY = -Infinity + let minX = Infinity + let minY = Infinity + this.forEach(function (el) { + maxX = Math.max(el[0], maxX) + maxY = Math.max(el[1], maxY) + minX = Math.min(el[0], minX) + minY = Math.min(el[1], minY) + }) + return new Box(minX, minY, maxX - minX, maxY - minY) + } + + // Move point string + move(x, y) { + const box = this.bbox() + + // get relative offset + x -= box.x + y -= box.y + + // move every point + if (!isNaN(x) && !isNaN(y)) { + for (let i = this.length - 1; i >= 0; i--) { + this[i] = [this[i][0] + x, this[i][1] + y] + } + } + + return this + } + + // Parse point string and flat array + parse(array = [0, 0]) { + const points = [] + + // if it is an array, we flatten it and therefore clone it to 1 depths + if (array instanceof Array) { + array = Array.prototype.concat.apply([], array) + } else { + // Else, it is considered as a string + // parse points + array = array.trim().split(delimiter).map(parseFloat) + } + + // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints + // Odd number of coordinates is an error. In such cases, drop the last odd coordinate. + if (array.length % 2 !== 0) array.pop() + + // wrap points in two-tuples + for (let i = 0, len = array.length; i < len; i = i + 2) { + points.push([array[i], array[i + 1]]) + } + + return points + } + + // Resize poly string + size(width, height) { + let i + const box = this.bbox() + + // recalculate position of all points according to new size + for (i = this.length - 1; i >= 0; i--) { + if (box.width) + this[i][0] = ((this[i][0] - box.x) * width) / box.width + box.x + if (box.height) + this[i][1] = ((this[i][1] - box.y) * height) / box.height + box.y + } + + return this + } + + // Convert array to line object + toLine() { + return { + x1: this[0][0], + y1: this[0][1], + x2: this[1][0], + y2: this[1][1] + } + } + + // Convert array to string + toString() { + const array = [] + // convert to a poly point string + for (let i = 0, il = this.length; i < il; i++) { + array.push(this[i].join(',')) + } + + return array.join(' ') + } + + transform(m) { + return this.clone().transformO(m) + } + + // transform points with matrix (similar to Point.transform) + transformO(m) { + if (!Matrix.isMatrixLike(m)) { + m = new Matrix(m) + } + + for (let i = this.length; i--; ) { + // Perform the matrix multiplication + const [x, y] = this[i] + this[i][0] = m.a * x + m.c * y + m.e + this[i][1] = m.b * x + m.d * y + m.f + } + + return this + } +} diff --git a/src/types/SVGArray.js b/src/types/SVGArray.js new file mode 100644 index 000000000..582640652 --- /dev/null +++ b/src/types/SVGArray.js @@ -0,0 +1,47 @@ +import { delimiter } from '../modules/core/regex.js' + +export default class SVGArray extends Array { + constructor(...args) { + super(...args) + this.init(...args) + } + + clone() { + return new this.constructor(this) + } + + init(arr) { + // This catches the case, that native map tries to create an array with new Array(1) + if (typeof arr === 'number') return this + this.length = 0 + this.push(...this.parse(arr)) + return this + } + + // Parse whitespace separated string + parse(array = []) { + // If already is an array, no need to parse it + if (array instanceof Array) return array + + return array.trim().split(delimiter).map(parseFloat) + } + + toArray() { + return Array.prototype.concat.apply([], this) + } + + toSet() { + return new Set(this) + } + + toString() { + return this.join(' ') + } + + // Flattens the array if needed + valueOf() { + const ret = [] + ret.push(...this) + return ret + } +} diff --git a/src/types/SVGNumber.js b/src/types/SVGNumber.js new file mode 100644 index 000000000..2770f67df --- /dev/null +++ b/src/types/SVGNumber.js @@ -0,0 +1,104 @@ +import { numberAndUnit } from '../modules/core/regex.js' + +// Module for unit conversions +export default class SVGNumber { + // Initialize + constructor(...args) { + this.init(...args) + } + + convert(unit) { + return new SVGNumber(this.value, unit) + } + + // Divide number + divide(number) { + number = new SVGNumber(number) + return new SVGNumber(this / number, this.unit || number.unit) + } + + init(value, unit) { + unit = Array.isArray(value) ? value[1] : unit + value = Array.isArray(value) ? value[0] : value + + // initialize defaults + this.value = 0 + this.unit = unit || '' + + // parse value + if (typeof value === 'number') { + // ensure a valid numeric value + this.value = isNaN(value) + ? 0 + : !isFinite(value) + ? value < 0 + ? -3.4e38 + : +3.4e38 + : value + } else if (typeof value === 'string') { + unit = value.match(numberAndUnit) + + if (unit) { + // make value numeric + this.value = parseFloat(unit[1]) + + // normalize + if (unit[5] === '%') { + this.value /= 100 + } else if (unit[5] === 's') { + this.value *= 1000 + } + + // store unit + this.unit = unit[5] + } + } else { + if (value instanceof SVGNumber) { + this.value = value.valueOf() + this.unit = value.unit + } + } + + return this + } + + // Subtract number + minus(number) { + number = new SVGNumber(number) + return new SVGNumber(this - number, this.unit || number.unit) + } + + // Add number + plus(number) { + number = new SVGNumber(number) + return new SVGNumber(this + number, this.unit || number.unit) + } + + // Multiply number + times(number) { + number = new SVGNumber(number) + return new SVGNumber(this * number, this.unit || number.unit) + } + + toArray() { + return [this.value, this.unit] + } + + toJSON() { + return this.toString() + } + + toString() { + return ( + (this.unit === '%' + ? ~~(this.value * 1e8) / 1e6 + : this.unit === 's' + ? this.value / 1e3 + : this.value) + this.unit + ) + } + + valueOf() { + return this.value + } +} diff --git a/src/umd.js b/src/umd.js deleted file mode 100644 index 55a9de009..000000000 --- a/src/umd.js +++ /dev/null @@ -1,18 +0,0 @@ -(function(root, factory) { - /* istanbul ignore next */ - if (typeof define === 'function' && define.amd) { - define(function(){ - return factory(root, root.document) - }) - } else if (typeof exports === 'object') { - module.exports = root.document ? factory(root, root.document) : function(w){ return factory(w, w.document) } - } else { - root.SVG = factory(root, root.document) - } -}(typeof window !== "undefined" ? window : this, function(window, document) { - -<%= contents %> - -return SVG - -})); diff --git a/src/ungroup.js b/src/ungroup.js deleted file mode 100644 index a76a5a6ab..000000000 --- a/src/ungroup.js +++ /dev/null @@ -1,24 +0,0 @@ -SVG.extend(SVG.Parent, { - - ungroup: function(parent, depth) { - if(depth === 0 || this instanceof SVG.Defs || this.node == SVG.parser.draw) return this - - parent = parent || (this instanceof SVG.Doc ? this : this.parent(SVG.Parent)) - depth = depth || Infinity - - this.each(function(){ - if(this instanceof SVG.Defs) return this - if(this instanceof SVG.Parent) return this.ungroup(parent, depth-1) - return this.toParent(parent) - }) - - this.node.firstChild || this.remove() - - return this - }, - - flatten: function(parent, depth) { - return this.ungroup(parent, depth) - } - -}) \ No newline at end of file diff --git a/src/use.js b/src/use.js deleted file mode 100644 index d3150f752..000000000 --- a/src/use.js +++ /dev/null @@ -1,24 +0,0 @@ -SVG.Use = SVG.invent({ - // Initialize node - create: 'use' - - // Inherit from -, inherit: SVG.Shape - - // Add class methods -, extend: { - // Use element as a reference - element: function(element, file) { - // Set lined element - return this.attr('href', (file || '') + '#' + element, SVG.xlink) - } - } - - // Add parent method -, construct: { - // Create a use element - use: function(element, file) { - return this.put(new SVG.Use).element(element, file) - } - } -}) \ No newline at end of file diff --git a/src/utilities.js b/src/utilities.js deleted file mode 100644 index c41e8e41a..000000000 --- a/src/utilities.js +++ /dev/null @@ -1,41 +0,0 @@ -SVG.utils = { - // Map function - map: function(array, block) { - var i - , il = array.length - , result = [] - - for (i = 0; i < il; i++) - result.push(block(array[i])) - - return result - } - - // Filter function -, filter: function(array, block) { - var i - , il = array.length - , result = [] - - for (i = 0; i < il; i++) - if (block(array[i])) - result.push(array[i]) - - return result - } - - // Degrees to radians -, radians: function(d) { - return d % 360 * Math.PI / 180 - } - - // Radians to degrees -, degrees: function(r) { - return r * 180 / Math.PI % 360 - } - -, filterSVGElements: function(nodes) { - return this.filter( nodes, function(el) { return el instanceof window.SVGElement }) - } - -} \ No newline at end of file diff --git a/src/utils/adopter.js b/src/utils/adopter.js new file mode 100644 index 000000000..962412fd2 --- /dev/null +++ b/src/utils/adopter.js @@ -0,0 +1,145 @@ +import { addMethodNames } from './methods.js' +import { capitalize } from './utils.js' +import { svg } from '../modules/core/namespaces.js' +import { globals } from '../utils/window.js' +import Base from '../types/Base.js' + +const elements = {} +export const root = '___SYMBOL___ROOT___' + +// Method for element creation +export function create(name, ns = svg) { + // create element + return globals.document.createElementNS(ns, name) +} + +export function makeInstance(element, isHTML = false) { + if (element instanceof Base) return element + + if (typeof element === 'object') { + return adopter(element) + } + + if (element == null) { + return new elements[root]() + } + + if (typeof element === 'string' && element.charAt(0) !== '<') { + return adopter(globals.document.querySelector(element)) + } + + // Make sure, that HTML elements are created with the correct namespace + const wrapper = isHTML ? globals.document.createElement('div') : create('svg') + wrapper.innerHTML = element + + // We can use firstChild here because we know, + // that the first char is < and thus an element + element = adopter(wrapper.firstChild) + + // make sure, that element doesn't have its wrapper attached + wrapper.removeChild(wrapper.firstChild) + return element +} + +export function nodeOrNew(name, node) { + return node && + (node instanceof globals.window.Node || + (node.ownerDocument && + node instanceof node.ownerDocument.defaultView.Node)) + ? node + : create(name) +} + +// Adopt existing svg elements +export function adopt(node) { + // check for presence of node + if (!node) return null + + // make sure a node isn't already adopted + if (node.instance instanceof Base) return node.instance + + if (node.nodeName === '#document-fragment') { + return new elements.Fragment(node) + } + + // initialize variables + let className = capitalize(node.nodeName || 'Dom') + + // Make sure that gradients are adopted correctly + if (className === 'LinearGradient' || className === 'RadialGradient') { + className = 'Gradient' + + // Fallback to Dom if element is not known + } else if (!elements[className]) { + className = 'Dom' + } + + return new elements[className](node) +} + +let adopter = adopt + +export function mockAdopt(mock = adopt) { + adopter = mock +} + +export function register(element, name = element.name, asRoot = false) { + elements[name] = element + if (asRoot) elements[root] = element + + addMethodNames(Object.getOwnPropertyNames(element.prototype)) + + return element +} + +export function getClass(name) { + return elements[name] +} + +// Element id sequence +let did = 1000 + +// Get next named element id +export function eid(name) { + return 'Svgjs' + capitalize(name) + did++ +} + +// Deep new id assignment +export function assignNewId(node) { + // do the same for SVG child nodes as well + for (let i = node.children.length - 1; i >= 0; i--) { + assignNewId(node.children[i]) + } + + if (node.id) { + node.id = eid(node.nodeName) + return node + } + + return node +} + +// Method for extending objects +export function extend(modules, methods) { + let key, i + + modules = Array.isArray(modules) ? modules : [modules] + + for (i = modules.length - 1; i >= 0; i--) { + for (key in methods) { + modules[i].prototype[key] = methods[key] + } + } +} + +export function wrapWithAttrCheck(fn) { + return function (...args) { + const o = args[args.length - 1] + + if (o && o.constructor === Object && !(o instanceof Array)) { + return fn.apply(this, args.slice(0, -1)).attr(o) + } else { + return fn.apply(this, args) + } + } +} diff --git a/src/utils/methods.js b/src/utils/methods.js new file mode 100644 index 000000000..9f61f916e --- /dev/null +++ b/src/utils/methods.js @@ -0,0 +1,33 @@ +const methods = {} +const names = [] + +export function registerMethods(name, m) { + if (Array.isArray(name)) { + for (const _name of name) { + registerMethods(_name, m) + } + return + } + + if (typeof name === 'object') { + for (const _name in name) { + registerMethods(_name, name[_name]) + } + return + } + + addMethodNames(Object.getOwnPropertyNames(m)) + methods[name] = Object.assign(methods[name] || {}, m) +} + +export function getMethodsFor(name) { + return methods[name] || {} +} + +export function getMethodNames() { + return [...new Set(names)] +} + +export function addMethodNames(_names) { + names.push(..._names) +} diff --git a/src/utils/pathParser.js b/src/utils/pathParser.js new file mode 100644 index 000000000..2b97add93 --- /dev/null +++ b/src/utils/pathParser.js @@ -0,0 +1,250 @@ +import { isPathLetter } from '../modules/core/regex.js' +import Point from '../types/Point.js' + +const segmentParameters = { + M: 2, + L: 2, + H: 1, + V: 1, + C: 6, + S: 4, + Q: 4, + T: 2, + A: 7, + Z: 0 +} + +const pathHandlers = { + M: function (c, p, p0) { + p.x = p0.x = c[0] + p.y = p0.y = c[1] + + return ['M', p.x, p.y] + }, + L: function (c, p) { + p.x = c[0] + p.y = c[1] + return ['L', c[0], c[1]] + }, + H: function (c, p) { + p.x = c[0] + return ['H', c[0]] + }, + V: function (c, p) { + p.y = c[0] + return ['V', c[0]] + }, + C: function (c, p) { + p.x = c[4] + p.y = c[5] + return ['C', c[0], c[1], c[2], c[3], c[4], c[5]] + }, + S: function (c, p) { + p.x = c[2] + p.y = c[3] + return ['S', c[0], c[1], c[2], c[3]] + }, + Q: function (c, p) { + p.x = c[2] + p.y = c[3] + return ['Q', c[0], c[1], c[2], c[3]] + }, + T: function (c, p) { + p.x = c[0] + p.y = c[1] + return ['T', c[0], c[1]] + }, + Z: function (c, p, p0) { + p.x = p0.x + p.y = p0.y + return ['Z'] + }, + A: function (c, p) { + p.x = c[5] + p.y = c[6] + return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]] + } +} + +const mlhvqtcsaz = 'mlhvqtcsaz'.split('') + +for (let i = 0, il = mlhvqtcsaz.length; i < il; ++i) { + pathHandlers[mlhvqtcsaz[i]] = (function (i) { + return function (c, p, p0) { + if (i === 'H') c[0] = c[0] + p.x + else if (i === 'V') c[0] = c[0] + p.y + else if (i === 'A') { + c[5] = c[5] + p.x + c[6] = c[6] + p.y + } else { + for (let j = 0, jl = c.length; j < jl; ++j) { + c[j] = c[j] + (j % 2 ? p.y : p.x) + } + } + + return pathHandlers[i](c, p, p0) + } + })(mlhvqtcsaz[i].toUpperCase()) +} + +function makeAbsolut(parser) { + const command = parser.segment[0] + return pathHandlers[command](parser.segment.slice(1), parser.p, parser.p0) +} + +function segmentComplete(parser) { + return ( + parser.segment.length && + parser.segment.length - 1 === + segmentParameters[parser.segment[0].toUpperCase()] + ) +} + +function startNewSegment(parser, token) { + parser.inNumber && finalizeNumber(parser, false) + const pathLetter = isPathLetter.test(token) + + if (pathLetter) { + parser.segment = [token] + } else { + const lastCommand = parser.lastCommand + const small = lastCommand.toLowerCase() + const isSmall = lastCommand === small + parser.segment = [small === 'm' ? (isSmall ? 'l' : 'L') : lastCommand] + } + + parser.inSegment = true + parser.lastCommand = parser.segment[0] + + return pathLetter +} + +function finalizeNumber(parser, inNumber) { + if (!parser.inNumber) throw new Error('Parser Error') + parser.number && parser.segment.push(parseFloat(parser.number)) + parser.inNumber = inNumber + parser.number = '' + parser.pointSeen = false + parser.hasExponent = false + + if (segmentComplete(parser)) { + finalizeSegment(parser) + } +} + +function finalizeSegment(parser) { + parser.inSegment = false + if (parser.absolute) { + parser.segment = makeAbsolut(parser) + } + parser.segments.push(parser.segment) +} + +function isArcFlag(parser) { + if (!parser.segment.length) return false + const isArc = parser.segment[0].toUpperCase() === 'A' + const length = parser.segment.length + + return isArc && (length === 4 || length === 5) +} + +function isExponential(parser) { + return parser.lastToken.toUpperCase() === 'E' +} + +const pathDelimiters = new Set([' ', ',', '\t', '\n', '\r', '\f']) +export function pathParser(d, toAbsolute = true) { + let index = 0 + let token = '' + const parser = { + segment: [], + inNumber: false, + number: '', + lastToken: '', + inSegment: false, + segments: [], + pointSeen: false, + hasExponent: false, + absolute: toAbsolute, + p0: new Point(), + p: new Point() + } + + while (((parser.lastToken = token), (token = d.charAt(index++)))) { + if (!parser.inSegment) { + if (startNewSegment(parser, token)) { + continue + } + } + + if (token === '.') { + if (parser.pointSeen || parser.hasExponent) { + finalizeNumber(parser, false) + --index + continue + } + parser.inNumber = true + parser.pointSeen = true + parser.number += token + continue + } + + if (!isNaN(parseInt(token))) { + if (parser.number === '0' || isArcFlag(parser)) { + parser.inNumber = true + parser.number = token + finalizeNumber(parser, true) + continue + } + + parser.inNumber = true + parser.number += token + continue + } + + if (pathDelimiters.has(token)) { + if (parser.inNumber) { + finalizeNumber(parser, false) + } + continue + } + + if (token === '-' || token === '+') { + if (parser.inNumber && !isExponential(parser)) { + finalizeNumber(parser, false) + --index + continue + } + parser.number += token + parser.inNumber = true + continue + } + + if (token.toUpperCase() === 'E') { + parser.number += token + parser.hasExponent = true + continue + } + + if (isPathLetter.test(token)) { + if (parser.inNumber) { + finalizeNumber(parser, false) + } else if (!segmentComplete(parser)) { + throw new Error('parser Error') + } else { + finalizeSegment(parser) + } + --index + } + } + + if (parser.inNumber) { + finalizeNumber(parser, false) + } + + if (parser.inSegment && segmentComplete(parser)) { + finalizeSegment(parser) + } + + return parser.segments +} diff --git a/src/utils/utils.js b/src/utils/utils.js new file mode 100644 index 000000000..ab0fda066 --- /dev/null +++ b/src/utils/utils.js @@ -0,0 +1,136 @@ +// Map function +export function map(array, block) { + let i + const il = array.length + const result = [] + + for (i = 0; i < il; i++) { + result.push(block(array[i])) + } + + return result +} + +// Filter function +export function filter(array, block) { + let i + const il = array.length + const result = [] + + for (i = 0; i < il; i++) { + if (block(array[i])) { + result.push(array[i]) + } + } + + return result +} + +// Degrees to radians +export function radians(d) { + return ((d % 360) * Math.PI) / 180 +} + +// Radians to degrees +export function degrees(r) { + return ((r * 180) / Math.PI) % 360 +} + +// Convert camel cased string to dash separated +export function unCamelCase(s) { + return s.replace(/([A-Z])/g, function (m, g) { + return '-' + g.toLowerCase() + }) +} + +// Capitalize first letter of a string +export function capitalize(s) { + return s.charAt(0).toUpperCase() + s.slice(1) +} + +// Calculate proportional width and height values when necessary +export function proportionalSize(element, width, height, box) { + if (width == null || height == null) { + box = box || element.bbox() + + if (width == null) { + width = (box.width / box.height) * height + } else if (height == null) { + height = (box.height / box.width) * width + } + } + + return { + width: width, + height: height + } +} + +/** + * This function adds support for string origins. + * It searches for an origin in o.origin o.ox and o.originX. + * This way, origin: {x: 'center', y: 50} can be passed as well as ox: 'center', oy: 50 + **/ +export function getOrigin(o, element) { + const origin = o.origin + // First check if origin is in ox or originX + let ox = o.ox != null ? o.ox : o.originX != null ? o.originX : 'center' + let oy = o.oy != null ? o.oy : o.originY != null ? o.originY : 'center' + + // Then check if origin was used and overwrite in that case + if (origin != null) { + ;[ox, oy] = Array.isArray(origin) + ? origin + : typeof origin === 'object' + ? [origin.x, origin.y] + : [origin, origin] + } + + // Make sure to only call bbox when actually needed + const condX = typeof ox === 'string' + const condY = typeof oy === 'string' + if (condX || condY) { + const { height, width, x, y } = element.bbox() + + // And only overwrite if string was passed for this specific axis + if (condX) { + ox = ox.includes('left') + ? x + : ox.includes('right') + ? x + width + : x + width / 2 + } + + if (condY) { + oy = oy.includes('top') + ? y + : oy.includes('bottom') + ? y + height + : y + height / 2 + } + } + + // Return the origin as it is if it wasn't a string + return [ox, oy] +} + +const descriptiveElements = new Set(['desc', 'metadata', 'title']) +export const isDescriptive = (element) => + descriptiveElements.has(element.nodeName) + +export const writeDataToDom = (element, data, defaults = {}) => { + const cloned = { ...data } + + for (const key in cloned) { + if (cloned[key].valueOf() === defaults[key]) { + delete cloned[key] + } + } + + if (Object.keys(cloned).length) { + element.node.setAttribute('data-svgjs', JSON.stringify(cloned)) // see #428 + } else { + element.node.removeAttribute('data-svgjs') + element.node.removeAttribute('svgjs:data') + } +} diff --git a/src/utils/window.js b/src/utils/window.js new file mode 100644 index 000000000..5009c77fa --- /dev/null +++ b/src/utils/window.js @@ -0,0 +1,32 @@ +export const globals = { + window: typeof window === 'undefined' ? null : window, + document: typeof document === 'undefined' ? null : document +} + +export function registerWindow(win = null, doc = null) { + globals.window = win + globals.document = doc +} + +const save = {} + +export function saveWindow() { + save.window = globals.window + save.document = globals.document +} + +export function restoreWindow() { + globals.window = save.window + globals.document = save.document +} + +export function withWindow(win, fn) { + saveWindow() + registerWindow(win, win.document) + fn(win, win.document) + restoreWindow() +} + +export function getWindow() { + return globals.window +} diff --git a/src/viewbox.js b/src/viewbox.js deleted file mode 100644 index 8ce7e04c7..000000000 --- a/src/viewbox.js +++ /dev/null @@ -1,127 +0,0 @@ - -SVG.ViewBox = SVG.invent({ - - create: function(source) { - var i, base = [0, 0, 0, 0] - - var x, y, width, height, box, view, we, he - , wm = 1 // width multiplier - , hm = 1 // height multiplier - , reg = /[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/gi - - if(source instanceof SVG.Element){ - - we = source - he = source - view = (source.attr('viewBox') || '').match(reg) - box = source.bbox - - // get dimensions of current node - width = new SVG.Number(source.width()) - height = new SVG.Number(source.height()) - - // find nearest non-percentual dimensions - while (width.unit == '%') { - wm *= width.value - width = new SVG.Number(we instanceof SVG.Doc ? we.parent().offsetWidth : we.parent().width()) - we = we.parent() - } - while (height.unit == '%') { - hm *= height.value - height = new SVG.Number(he instanceof SVG.Doc ? he.parent().offsetHeight : he.parent().height()) - he = he.parent() - } - - // ensure defaults - this.x = 0 - this.y = 0 - this.width = width * wm - this.height = height * hm - this.zoom = 1 - - if (view) { - // get width and height from viewbox - x = parseFloat(view[0]) - y = parseFloat(view[1]) - width = parseFloat(view[2]) - height = parseFloat(view[3]) - - // calculate zoom accoring to viewbox - this.zoom = ((this.width / this.height) > (width / height)) ? - this.height / height : - this.width / width - - // calculate real pixel dimensions on parent SVG.Doc element - this.x = x - this.y = y - this.width = width - this.height = height - - } - - }else{ - - // ensure source as object - source = typeof source === 'string' ? - source.match(reg).map(function(el){ return parseFloat(el) }) : - Array.isArray(source) ? - source : - typeof source == 'object' ? - [source.x, source.y, source.width, source.height] : - arguments.length == 4 ? - [].slice.call(arguments) : - base - - this.x = source[0] - this.y = source[1] - this.width = source[2] - this.height = source[3] - } - - - } - -, extend: { - - toString: function() { - return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height - } - , morph: function(x, y, width, height){ - this.destination = new SVG.ViewBox(x, y, width, height) - return this - } - - , at: function(pos) { - - if(!this.destination) return this - - return new SVG.ViewBox([ - this.x + (this.destination.x - this.x) * pos - , this.y + (this.destination.y - this.y) * pos - , this.width + (this.destination.width - this.width) * pos - , this.height + (this.destination.height - this.height) * pos - ]) - - } - - } - - // Define parent -, parent: SVG.Container - - // Add parent method -, construct: { - - // get/set viewbox - viewbox: function(x, y, width, height) { - if (arguments.length == 0) - // act as a getter if there are no arguments - return new SVG.ViewBox(this) - - // otherwise act as a setter - return this.attr('viewBox', new SVG.ViewBox(x, y, width, height)) - } - - } - -}) \ No newline at end of file diff --git a/svg.js.d.ts b/svg.js.d.ts index f87f8316e..d58375478 100644 --- a/svg.js.d.ts +++ b/svg.js.d.ts @@ -1,1016 +1,1994 @@ -export = svgjs; -export as namespace svgjs; - -declare var svgjs: svgjs.Library; - -// todo add SVG.FX -declare namespace svgjs { - export interface Library { - (id: string): Doc; - (domElement: HTMLElement): Doc; - ns: string; - xmlns: string; - xlink: string; - svgjs: string; - supported: boolean; - - did: number; - eid(name: string): string; - - create(name: string): any; - extend(parent: Object, obj: Object): void; - invent(config: Object): any; - adopt(node: HTMLElement): Element; - prepare(element: HTMLElement): void; - } - interface LinkedHTMLElement extends HTMLElement { - instance: Element; - } - - // arrange.js - interface Element { - front(): this; - back(): this; - forward(): this; - backward(): this; - - siblings(): Element[]; - position(): number; - next(): Element; - previous(): Element; - before(element: Element): Element; - after(element: Element): Element; - } - - // array.js - type ArrayAlias = _Array | number[] | string; - - interface _Array { - new (array?: ArrayAlias, fallback?: number[]): _Array; - value: number[]; - morph(array: number[]): this; - settle(): number[]; - at(pos: NumberAlias): _Array; - toString(): string; - valueOf(): number[]; - parse(array: ArrayAlias): number[]; - split(string: string): number[]; - reverse(): this; - clone(): _Array; - } - interface Library { Array: _Array } - - // attr.js - interface Element { - attr(): object; - attr(name: string): any; - attr(obj: Object): this; - attr(name: string, value: any, namespace?: string): this; - } - - // bare.js - export interface Bare extends Element { - new (element: string, inherit?: any): Bare; - words(text: string): this; - } - interface Parent { - element(element: string, inherit?: Object): Bare; - } - interface Library { Bare: Bare; } - - // boxes.js - interface Box { - height: number; - width: number; - y: number; - x: number; - cx: number; - cy: number; - w: number; - h: number; - x2: number; - y2: number; - merge(box: Box): Box; - transform(m: Matrix): Box - } - - export interface BBox extends Box { - new (element?: Element): BBox; - } - export interface RBox extends Box { - new (element?: Element): RBox; - } - export interface TBox extends Box { - new (element?: Element): TBox; - } - interface Element { - bbox(): BBox; - rbox(): RBox; - tbox(): TBox; - } - interface Library { - BBox: BBox; - RBox: RBox; - TBox: TBox; - } - - // clip.js - export interface ClipPath extends Container { - new (): ClipPath; - targets: Element[]; - remove(): this; - } - interface Container { - clip(): ClipPath; - } - interface Element { - clipWith(element: Element): this; - clipper: ClipPath; - unclip(): this; - } - interface Library { ClipPath: ClipPath; } - - // color.js - interface ColorLike { - r: number; - g: number; - b: number; - } - - type ColorAlias = string | ColorLike; - - export interface Color extends ColorLike{ - new (): Color; - new (color: ColorAlias): Color; - - toString(): string; - toHex(): string; - toRgb(): string; - brightness(): number; - morph(color: ColorAlias): Color; - at(pos: number): Color; - } - interface Library { Color: Color; } - - // container.js - interface ViewBoxLike { - x: number; - y: number; - width: number; - height:number; - } - - export interface Container extends Parent { - new (): Container; - } - interface Library { Container: Container } - - // data.js - interface Element { - data(name: string): any; - data(name: string, value: any, sustain?: boolean): this; - } - - // default.js - interface Library { - defaults: { - attrs: { - 'fill-opacity': number; - 'stroke-opacity': number; - 'stroke-width': number; - 'stroke-linejoin': string; - 'stroke-linecap': string; - 'fill': string; - 'stroke': string; - 'opacity': number; - 'x': number; - 'y': number; - 'cx': number; - 'cy': number; - 'width': number; - 'height': number; - 'r': number; - 'rx': number; - 'ry': number; - 'offset': number; - 'stop-opacity': number; - 'stop-color': string; - 'font-size': number; - 'font-family': string; - 'text-anchor': string; - } - } - } - - // defs.js - export interface Defs extends Container { - new (): Defs; - } - interface Library { Defs: Defs } - - // doc.js - export interface Doc extends Container { - new (): Doc; - new (id: string): Doc; - new (domElement: HTMLElement): Doc; - namespace(): this; - defs(): Defs; - parent(): HTMLElement; - spof(): this; - remove(): this; - } - interface Library { Doc: Doc; } - - // element.js - export interface Element { - new (): Element; - node: LinkedHTMLElement; - type: string; - - x(x: NumberAlias): this; - x(): number; - y(y: NumberAlias): this; - y(): number; - //cx(x: number, anchor?: boolean): this; - cx(x: number): this; - cx(): number; - //cy(y: number, anchor?: boolean): this; - cy(y: number): this; - cy(): number; - move(x: NumberAlias, y: NumberAlias): this; - center(x: number, y: number): this; - - width(width: NumberAlias): this; - width(): number; - height(height: NumberAlias): this; - height(): number; - size(width?: NumberAlias, height?: NumberAlias): this; - - clone(): Element; - remove(): this; - replace(element: Element): Element; - - addTo(parent: Parent): this; - putIn(parent: Parent): Parent; - - id(): string; - id(id: string): this; - - inside(x: number, y: number): boolean; - - show(): this; - hide(): this; - visible(): boolean; - - toString(): string; - - classes(): string[]; - hasClass(name: string): boolean; - addClass(name: string): this; - removeClass(name: string): this; - toggleClass(name: string): this; - - reference(type: string): Element; - - parents(): Parent[]; - - matches(selector: string): boolean; - native(): LinkedHTMLElement; - - svg(svg: string): this; - - writeDataToDom(): this, - setData(data: object): this, - - is(cls: any): boolean; - } - interface Library { Element: Element; } - - // ellipse.js - interface CircleMethods extends Shape { - rx(rx: number): this; - rx(): this; - ry(ry: number): this; - ry(): this; - - radius(x: number, y?: number): this; - } - export interface Circle extends CircleMethods { - new (): Circle; - } - export interface Ellipse extends CircleMethods { - new (): Ellipse; - } - interface Container { - circle(size?: number): Circle; - ellipse(width?: number, height?: number): Ellipse; - } - interface Library { - Circle: Circle; - Ellipse: Ellipse; - } - - // event.js - interface Element { - on(event: string, cb: Function, context?: Object): this; - off(event: string, cb: Function, context?: Object): this; - fire(event: string, data?: any): this; - fire(event: Event): this; - - click(cb: Function): this; - dblclick(cb: Function): this; - mousedown(cb: Function): this; - mouseup(cb: Function): this; - mouseover(cb: Function): this; - mouseout(cb: Function): this; - mousemove(cb: Function): this; - touchstart(cb: Function): this; - touchmove(cb: Function): this; - touchleave(cb: Function): this; - touchend(cb: Function): this; - touchcancel(cb: Function): this; - } - - //fx.js - interface Library { - easing: { - '-'(pos: number): number; - '<>'(pos: number): number; - '>'(pos: number): number; - '<'(pos: number): number; - } - } - interface Element { - animate(duration?: number, ease?: string, delay?: number): Animation; - animate(info: { ease?: string; duration?: number; delay?: number }): Animation; - stop(jumpToEnd:boolean,clearQueue:boolean): Animation; - } - // TODO finishs FX - interface StopProperties { - color?: ColorAlias; - offset?: number; - opacity?: number; - } - - // gradient.js - export interface Stop extends Element { - new (): Stop; - update(offset?: number, color?: ColorAlias, opacity?: number): this; - update(opts: StopProperties): this; - } - export interface Gradient extends Container { - new (type: string): Gradient; - at(offset?: number, color?: ColorAlias, opacity?: number): Stop; - at(opts: StopProperties): Stop; - update(block?: Function): this; - fill(): string; - fill(...params: any[]): never; - toString(): string; - from(x: number, y: number): this; - to(x: number, y: number): this; - radius(x: number, y?: number): this; - } - interface Container { - gradient(type: string, block?: (stop: Gradient) => void): Gradient; - } - interface Library { - Gradient: Gradient; - Stop: Stop; - } - - // group.js - export interface G extends Container { - new (): G; - gbox(): BBox; - } - interface Container { group(): G; } - interface Library { G: G; } - - // hyperlink.js - export interface A extends Container { - new (): A; - to(url: string): this; - to(): string; - show(target: string): this; - show(): string; - show(...params: any[]): never; - target(target: string): this; - target(): string; - } - interface Container { - link(url: string): A; - } - interface Element { - linkTo(url: string): A; - linkTo(url: (link: A) => void): A; - } - interface Library { A: A; } - - // image.js - export interface Image extends Shape { - new (): Image; - load(url?: string): this; - loaded(cb: (info: { width: number, height: number, ratio: number, url: string }) => void): this; - error(cb: (event: Event) => void): this; - } - interface Container { - image(): Image; - image(href: string, size?: number): Image; - image(href: string, width?: number, height?: number): Image; - } - interface Library { Image: Image; } - - // line.js - interface ArrayPoint extends Array { } - type PointArrayAlias = ArrayPoint | number[] | PointArray | string; - - export interface Line extends Shape { - new (): Line; - array(): PointArray; - plot(points: PointArrayAlias): this; - plot(x1: number, y1: number, x2: number, y2: number): this; - move(x: number, y: number): this; - size(width?: number, height?: number): this; - } - interface Container { - line(points: PointArrayAlias): Line; - line(x1: number, y1: number, x2: number, y2: number): Line; - } - interface Library { Line: Line; } - - // marker.js - export interface Marker extends Container { - new (): Marker; - ref(x: string | number, y: string | number): this; - update(block: (marker: Marker) => void): this; - toString(): string; - } - interface Container { - marker(width?: number, height?: number, block?: (marker: Marker) => void): Marker - } - interface Defs { - marker(width?: number, height?: number, block?: (marker: Marker) => void): Marker - } - interface Line { - marker(position: string, width?: number, height?: number, block?: (marker: Marker) => void): Marker; - marker(position: string, marker: Marker): Marker; - } - interface Polyline { - marker(position: string, width?: number, height?: number, block?: (marker: Marker) => void): Marker; - marker(position: string, marker: Marker): Marker; - } - interface Polygon { - marker(position: string, width?: number, height?: number, block?: (marker: Marker) => void): Marker; - marker(position: string, marker: Marker): Marker; - } - interface Path { - marker(position: string, width?: number, height?: number, block?: (marker: Marker) => void): Marker; - marker(position: string, marker: Marker): Marker; - } - interface Library { - Marker: Marker; - } - - // mask.js - export interface Mask extends Container { - new (): Mask; - targets: Element[]; - } - interface Container { mask(): Mask; } - interface Element { - maskWith(mask: Mask): this; - maskWith(element: Element): this; - masker: Mask; - unmask(): this; - } - interface Library { Mask: Mask; } - - // matrix.js - interface MatrixExtract { - x: number; - y: number; - transformedX: number; - transformedY: number; - skewX: number; - skewY: number; - scaleX: number; - scaleY: number; - rotation: number; - a: number; - b: number; - c: number; - d: number; - e: number; - f: number; - matrix: Matrix; - } - - interface MatrixLike { - a: number; - b: number; - c: number; - d: number; - e: number; - f: number; - } - - type MatrixAlias = MatrixLike | number[] | Element | string; - - export interface Matrix { - new (): Matrix; - new (source: MatrixAlias): Matrix; - new (a: number, b: number, c: number, d: number, e: number, f: number): Matrix; - a: number; - b: number; - c: number; - d: number; - e: number; - f: number; - extract(): MatrixExtract; - clone(): Matrix; - morph(matrix: Matrix): this; - at(pos: number): Matrix; - multiply(matrix: Matrix): Matrix; - inverse(): Matrix; - translate(x: number, y: number): Matrix; - scale(x: number, y?: number, cx?: number, cy?: number): Matrix; - rotate(r: number, cx?: number, cy?: number): Matrix; - flip(a: string, offset?: number): Matrix; - flip(offset?: number): Matrix; - skew(x: number, y?: number, cx?: number, cy?: number): Matrix; - skewX(x: number, cx?: number, cy?: number): Matrix; - skewY(y: number, cx?: number, cy?: number): Matrix; - around(cx: number, cy: number, matrix: Matrix): Matrix; - native(): SVGMatrix; - toString(): string; - } - interface Element { - ctm(): Matrix; - screenCTM(): Matrix; - } - interface Library { Matrix: Matrix } - - // memory.js - interface Element { - remember(name: string, value: any): this; - remember(obj: Object): this; - remember(name: string): any; - forget(...keys: string[]): this; - forget(): this; - memory(): Object; - } - - // nested.js - export interface Nested extends Container { - new (): Nested; - } - interface Container { nested(): Nested; } - interface Library { Nested: Nested; } - - // number.js - interface _Number { - new (): _Number; - new (value: _Number): _Number; - new (value: string): _Number; - new (value: number, unit?: any): _Number; - toString(): string; - toJSON(): Object; - valueOf(): number; - plus(number: number): _Number; - minus(number: number): _Number; - times(number: number): _Number; - divide(number: number): _Number; - to(unit: string): _Number; - morph(number: any): this; - at(pos: number): _Number; - } - interface Library { Number: _Number; } - - type NumberAlias = _Number | number | string; - - // parent.js - export interface Parent extends Element { - new (): Parent; - children(): Element[]; - add(element: Element, i?: number): this; - put(element: Element, i?: number): Element; - has(element: Element): boolean; - index(element: Element): number; - get(i: number): Element; - first(): Element; - last(): Element; - each(block: (index: number, children: Element[]) => void, deep?: boolean): this; - removeElement(element: Element): this; - clear(): this; - defs(): Defs; - } - interface Library{ Parent: Parent } - - // path.js - interface PathArrayPoint extends Array { } - type PathArrayAlias = PathArray | (string | number)[] | PathArrayPoint[] | string; - - export interface Path extends Shape { - new (): Path; - morphArray: PathArray; - array(): PathArray; - plot(d: PathArrayAlias): this; - } - interface Container { - path(): Path; - path(d: PathArrayAlias): Path; - } - interface Library{ Path: Path } - - // pathArray.js - export interface PathArray extends _Array { - new (): PathArray; - new (d: PathArrayAlias): PathArray; - move(x: number, y: number): this; - size(width?: number, height?: number): this; - parse(array: PathArrayAlias): PathArrayPoint[]; - parse(array: ArrayAlias): never; - bbox(): BBox; - } - interface Library { PathArray: PathArray; } - - // pattern.js - export interface Pattern extends Container { - new (): Pattern; - fill(): string; - fill(...rest: any[]): never; - update(block: (pattern: Pattern) => void): this; - toString(): string; - } - interface Container { - pattern(width?: number, height?: number, block?: (pattern: Pattern) => void): Pattern - } - interface Library { Pattern: Pattern } - - // point.js - export interface Point { - new (): Point; - new (position: ArrayPoint): Point; - new (point: Point): Point; - new (position: { x: number, y: number }): Point; - new (x: number, y: number): Point; - - clone(): Point; - morph(point: Point): this; - at(pos: number): Point; - native(): SVGPoint; - transform(matrix: Matrix): Point; - } - interface Library { Point: Point; } - interface Element { - point(): Point; - point(position: ArrayPoint): Point; - point(position: { x: number, y: number }): Point; - point(x: number, y: number): Point; - } - - // pointArray.js - export interface PointArray extends _Array { - new (): PointArray; - new (points: PointArrayAlias): PointArray; - toString(): string; - toLine(): { - x1: number; - y1: number; - x2: number; - y2: number; - }; - parse(points: PointArrayAlias): ArrayPoint[]; - parse(array: ArrayAlias): never; - move(x: number, y: number): this; - size(width?: number, height?: number): this; - bbox(): BBox; - } - interface Library { PointArray: PointArray } - - // poly.js - interface poly extends Shape { - array(): PointArray; - plot(p: PointArrayAlias): this; - move(x: number, y: number): this; - size(width: number, height: number): this; - } - export interface PolyLine extends poly { - new (): PolyLine; - } - interface Library { PolyLine: PolyLine; } - interface Container { - polyline(points: PointArrayAlias): PolyLine; - } - export interface Polygon extends poly { - new (): Polygon; - } - interface Library { Polygon: Polygon; } - interface Container { - polygon(points: PointArrayAlias): Polygon; - } - - // rect.js - export interface Rect extends Shape { - new (): Rect; - radius(x: number, y?: number): this; - } - interface Library { Rect: Rect; } - interface Container { - rect(width?: number, height?: number): Rect; - } - - // regex.js - interface Library { - regex: { - numberAndUnit: RegExp; - hex: RegExp; - rgb: RegExp; - reference: RegExp; - transforms: RegExp; - whitespace: RegExp; - isHex: RegExp; - isRgb: RegExp; - isCss: RegExp; - isBlank: RegExp; - isNumber: RegExp; - isPercent: RegExp; - isImage: RegExp; - delimiter: RegExp; - hyphen: RegExp; - pathLetters: RegExp; - isPathLetter: RegExp; - dots: RegExp; - } - } - - // selector.js - interface Library { - get(id: string): Element; - select(query: string, parent: HTMLElement): Set; - } - interface Parent { - select(query: string): Set; - } - - // set.js - export interface Set { - new (members?: Element[]): Set; - add(...elments: Element[]): this; - remove(element: Element): this; - each(block: (index: number, members: Element[]) => void): this; - clear(): this; - length(): number; - has(element: Element): this; - index(element: Element): number; - get(i: number): Element; - first(): Element; - last(): Element; - valueOf(): Element[]; - bbox(): BBox; - } - interface Container { set(members?: Element[]): Set; } - interface Library { Set: Set; } - - // shape.js - export interface Shape extends Element { - new (): Shape; - } - interface Library { Shape: Shape; } - - // style.js - interface Element { - style(styles: Object): this; - style(style: string): any; - style(style: string, value: any): this; - } - - // sugar.js - interface StrokeData { - color?: string; - width?: number; - opacity?: number; - linecap?: string; - linejoin?: string; - miterlimit?: number; - dasharray?: string; - dashoffset?: number; - } - interface Element { - fill(fill: { color?: string; opacity?: number, rule?: string }): this; - fill(color: string): this; - fill(pattern: Element): this; - fill(image: Image): this; - stroke(stroke: StrokeData): this; - stroke(color: string): this; - rotate(d: number, cx?: number, cy?: number): this; - skew(x: number, y?: number, cx?: number, cy?: number): this; - scale(x: number, y?: number, cx?: number, cy?: number): this; - translate(x: number, y: number): this; - flip(a: string, offset?: number): this; - flip(offset?: number): this; - matrix(m: MatrixAlias): this; - matrix(a: number, b: number, c: number, d: number, e: number, f: number): this; - opacity(o: number): this; - opacity(): number; - dx(x: NumberAlias): this; - dy(y: NumberAlias): this; - dmove(x: NumberAlias, y: NumberAlias): this; - } - interface Path { - length(): number; - pointAt(length: number): { x: number, y: number }; - } - interface FontData { - family?: string; - size?: NumberAlias; - anchor?: string; - leading?: NumberAlias; - weight?: string; - style?: string - } - interface Parent { - font(font: FontData): this; - } - interface Text { - font(font: FontData): this; - } - - // text.js - export interface Text extends Shape { - new (): Text; - clone(): Text; - text(): string; - text(text: string): this; - text(block: (text: Text) => void): this; - size(fontSize: NumberAlias): this; - leading(): number; - leading(leading: NumberAlias): this; - lines(): Set; - rebuild(enabled: boolean): this; - build(enabled: boolean): this; - plain(text: string): this; - tspan(text: string): Tspan; - tspan(block: (tspan: Tspan) => void): this; - clear(): this; - length(): number; - } - interface Container { - text(text: string): Text; - text(block: (tspan: Tspan) => void): Text; - plain(text: string): Text; - } - interface Library { Text: Text; } - export interface Tspan extends Shape { - new (): Tspan; - text(): string; - text(text: string): Tspan; - text(block: (tspan: Tspan) => void): this; - dx(x: NumberAlias): this; - dy(y: NumberAlias): this; - newLine(): this; - plain(text: any): this; - tspan(text: string): Tspan; - tspan(block: (tspan: Tspan) => void): this; - clear(): this; - length(): number; - } - interface Library { Tspan: Tspan; } - - // textpath.js - export interface TextPath extends Parent { - new (): TextPath; - } - interface Text { - path(d: PathArrayAlias): this; - track(): Element; - textPath(): Element; - } - interface Library { TextPath: TextPath; } - - // transform.js - interface Element { - transform(t: Transform, relative?: boolean): Element; - transform(): Transform; - untransform(): this; - matrixify(): Matrix; - toParent(parent: Parent): this; - toDoc(): this; - } - interface Transform { - x?: number; - y?: number; - rotation?: number; - cx?: number; - cy?: number; - scaleX?: number; - scaleY?: number; - skewX?: number; - skewY?: number; - matrix?: Matrix; // 1,0,0,1,0,0 - a?: number; // direct digits of matrix - b?: number; - c?: number; - d?: number; - e?: number; - f?: number; - } - export interface Transformation { - new (...transform: Transform[]): Transformation; - new (source: Transform, inversed?: boolean): Transformation; - at(pos: number): Matrix; - undo(transform: Transform): this - } - export interface Translate extends Transformation {new (): Translate} - export interface Rotate extends Transformation {new (): Rotate} - export interface Scale extends Transformation {new (): Scale} - export interface Skew extends Transformation {new (): Skew} - interface Library { - Transformation: Transformation; - Translate: Translate; - Rotate: Rotate; - Scale: Scale; - Skew: Skew; - } - - // ungroup.js - interface Parent { - ungroup(parent: Parent, depth?: number): this; - flatten(parent: Parent, depth?: number): this; - } - - // use.js - export interface Use extends Shape { - new (): Use; - element(element: Element, file?: string): this; - } - interface Container { - use(element: Element, file?: string): Use; - } - interface Library { Use: Use; } - - // utilities.js - interface Library { - utils: { - map(array: any[], block: Function): any; - filter(array: any[], block: Function): any; - radians(d: number): number; - degrees(r: number): number; - filterSVGElements: HTMLElement[] - } - } - - // viewbox.js - type ViewBoxAlias = ViewBoxLike | number[] | string | Element; - - interface ViewBox { - new (source: ViewBoxAlias): ViewBox; - new (x: number, y: number, width: number, height: number): ViewBox; - x: number; - y: number; - width: number; - height: number; - zoom?: number; - toString(): string; - morph(source: ViewBoxAlias): ViewBox; - morph(x: number, y: number, width: number, height: number): ViewBox; - at(pos:number): ViewBox; - } - interface Container { - viewbox(): ViewBox; - viewbox(x: number, y: number, width: number, height: number): this; - viewbox(viewbox: ViewBoxLike): this; - } - interface Library { ViewBox: ViewBox; } - - export interface Animation { - stop(): Animation; - - attr(name: string, value: any, namespace?: string): Animation; - attr(obj: Object): Animation; - attr(name: string): any; - attr(): object; - - viewbox(x: number, y: number, w: number, h: number): Animation; - - move(x: number, y: number, anchor?: boolean): Animation; - x(x: number, anchor?: boolean): Animation; - y(y: number, anchor?: boolean): Animation; - - center(x: number, y: number, anchor?: boolean): Animation; - cx(x: number, anchor?: boolean): Animation; - cy(y: number, anchor?: boolean): Animation; - - size(w: number, h: number, anchor?: boolean): Animation; - during(cb: (pos: number) => void): Animation; - to(value: number): Animation; - after(cb: () => void): Animation; - - rotate(degrees: number, cx?: number, cy?: number): Animation - skew(skewX: number, skewY?: number, cx?: number, cy?: number): Animation - scale(scaleX: number, scaleY?: number, cx?: number, cy?: number): Animation - translate(x: number, y: number): Animation - - // TODO style, etc, bbox... - } -} +// Type definitions for @svgdotjs version 3.x +// Project: @svgdotjs/svg.js + +// trick to keep reference to Array build-in type +declare class BuiltInArray extends Array {} + +// camelCase to kebab-case +declare type CamelToKebab = S extends `${infer T}${infer U}` + ? U extends Uncapitalize + ? `${Lowercase}${CamelToKebab}` + : `${Lowercase}-${CamelToKebab}` + : S; + +declare type ConvertKeysToKebab = { + [K in keyof T as CamelToKebab]: T[K]; +}; + +declare type KebabCSSStyleDeclaration = ConvertKeysToKebab + +// trick to have nice attribute list for CSS +declare type CSSStyleName = Exclude< + keyof KebabCSSStyleDeclaration, + 'parent-rule' | 'length' +> + +// create our own style declaration that includes css vars +interface CSSStyleDeclarationWithVars extends KebabCSSStyleDeclaration { + [key: `--${string}`]: string +} + +declare module '@svgdotjs/svg.js' { + function SVG(): Svg + /** + * @param selectorOrHtml pass in a css selector or an html/svg string + * @param isHTML only used if first argument is an html string. Will treat the svg/html as html and not svg + */ + function SVG(selectorOrHtml: QuerySelector, isHTML?: boolean): Element + function SVG(el: T): SVGTypeMapping + function SVG(domElement: HTMLElement): Element + + function eid(name: string): string + function get(id: string): Element + + function create(name: string): any + function extend(parent: object, obj: object): void + function invent(config: object): any + function adopt(node: HTMLElement): Element + function prepare(element: HTMLElement): void + function getClass(name: string): Element + + function on( + el: Node | Window, + events: string, + cb: EventListener, + binbind?: any, + options?: AddEventListenerOptions + ): void + function on( + el: Node | Window, + events: Event[], + cb: EventListener, + binbind?: any, + options?: AddEventListenerOptions + ): void + + function off( + el: Node | Window, + events?: string, + cb?: EventListener | number, + options?: AddEventListenerOptions + ): void + function off( + el: Node | Window, + events?: Event[], + cb?: EventListener | number, + options?: AddEventListenerOptions + ): void + + function dispatch( + node: Node | Window, + event: Event, + data?: object, + options?: object + ): Event + + function find(query: QuerySelector): List + function findOne(query: QuerySelector): Element | null + + function getWindow(): Window + function registerWindow(win: Window, doc: Document): void + function restoreWindow(): void + function saveWindow(): void + function withWindow( + win: Window, + fn: (win: Window, doc: Document) => void + ): void + + let utils: { + map(array: any[], block: Function): any + filter(array: any[], block: Function): any + radians(d: number): number + degrees(r: number): number + camelCase(s: string): string + unCamelCase(s: string): string + capitalize(s: string): string + // proportionalSize + // getOrigin + } + + let defaults: { + attrs: { + 'fill-opacity': number + 'stroke-opacity': number + 'stroke-width': number + 'stroke-linejoin': string + 'stroke-linecap': string + fill: string + stroke: string + opacity: number + x: number + y: number + cx: number + cy: number + width: number + height: number + r: number + rx: number + ry: number + offset: number + 'stop-opacity': number + 'stop-color': string + 'font-size': number + 'font-family': string + 'text-anchor': string + } + timeline: { + duration: number + ease: string + delay: number + } + } + + // let easing: { + // '-'(pos: number): number; + // '<>'(pos: number): number; + // '>'(pos: number): number; + // '<'(pos: number): number; + // bezier(x1: number, y1: number, x2: number, y2: number): (t: number) => number; + // steps(steps: number, stepPosition?: "jump-start"|"jump-end"|"jump-none"|"jump-both"|"start"|"end"): (t: number, beforeFlag?: boolean) => number; + // } + + let regex: { + delimiter: RegExp + dots: RegExp + hex: RegExp + hyphen: RegExp + isBlank: RegExp + isHex: RegExp + isImage: RegExp + isNumber: RegExp + isPathLetter: RegExp + isRgb: RegExp + numberAndUnit: RegExp + numbersWithDots: RegExp + pathLetters: RegExp + reference: RegExp + rgb: RegExp + transforms: RegExp + whitespace: RegExp + } + + let namespaces: { + ns: string + xmlns: string + xlink: string + svgjs: string + } + + interface LinkedHTMLElement extends HTMLElement { + instance: Element + } + + // ************ Standard object/option/properties declaration ************ + + type AttrNumberValue = number | 'auto' + + /** + * The SVG core attributes are all the common attributes that can be specified on any SVG element. + * More information see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Core + */ + interface CoreAttr { + id?: string + lang?: string + tabindex?: number + 'xml:lang'?: string + } + + /** + * The SVG styling attributes are all the attributes that can be specified on any SVG element to apply CSS styling effects. + * More information see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Styling + */ + interface StylingAttr { + /** + * a valid HTML class name + */ + class?: string + /** + * SVG css style string format. It all can be find here https://www.w3.org/TR/SVG/styling.html#StyleAttribute + */ + style?: string + } + + /** + * A global attribute that can be use with any svg element + */ + interface GlobalAttr extends CoreAttr, StylingAttr {} + + // TODO: implement SVG Presentation Attributes. See https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation + + interface PathBaseAttr { + pathLength?: number + } + + interface RadiusAxisAttr { + rx?: AttrNumberValue + ry?: AttrNumberValue + } + + /** + * SVG Rectangle attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect + */ + interface RectAttr extends RadiusAxisAttr, PathBaseAttr, GlobalAttr { + x?: number + y?: number + width: AttrNumberValue + height: AttrNumberValue + } + + /** + * SVG Line attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/line + */ + interface LineAttr extends PathBaseAttr, GlobalAttr { + x1?: number + y1?: number + x2?: number + y2?: number + } + + /** + * SVG Circle attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/circle + */ + interface CircleAttr extends PathBaseAttr, GlobalAttr { + cx?: number | string + cy?: number | string + r?: number | string + } + + /** + * SVG Ellipse attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/ellipse + */ + interface EllipseAttr extends PathBaseAttr, GlobalAttr { + cx?: number | string + cy?: number | string + rx?: number | string + ry?: number | string + } + + /** + * SVG Path attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path + */ + interface PathAttr extends PathBaseAttr, GlobalAttr { + d?: string + } + + /** + * SVG Path attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polygon + * or https://developer.mozilla.org/en-US/docs/Web/SVG/Element/polyline + */ + interface PolyAttr extends PathBaseAttr, GlobalAttr { + points?: string + } + + /** + * SVG Text attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/text + */ + interface TextAttr extends GlobalAttr { + x?: number | string + y?: number | string + dx?: number | string + dy?: number | string + lengthAdjust?: 'spacing' | 'spacingAndGlyphs' + textLength?: number | string + // see https://developer.mozilla.org/en-US/docs/Web/API/SVGNumberList + // or https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#List-of-Ts + // TODO: tbd + // rotate?: string + } + + /** + * SVG TextPath attribute, more information see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/textPath + */ + interface TextPathAttr extends GlobalAttr { + href?: string + lengthAdjust?: 'spacing' | 'spacingAndGlyphs' + method?: 'align' | 'stretch' + side?: 'left' | 'right' + spacing?: 'auto' | 'exact' + startOffset?: number | string + textLength?: number | string + // See https://developer.mozilla.org/en-US/docs/Web/SVG/Element/textPath + // TODO: tbd as there is no reference to see the detail of how it would look like + // path?: string + } + + /** + * A generic Dom Box object. + * Notice: DOMRect is still in experiment state and document is not complete (Draft) + * See https://developer.mozilla.org/en-US/docs/Web/API/DOMRect + */ + interface DOMRect { + x?: number + y?: number + width?: number + height?: number + top?: number + right?: number + bottom?: number + left?: number + } + + // ************ SVG.JS generic Conditional Types declaration ************ + + type SVGTypeMapping = T extends HTMLElement + ? Dom + : T extends SVGSVGElement + ? Svg + : T extends SVGRectElement + ? Rect + : T extends SVGCircleElement + ? Circle + : T extends SVGPathElement + ? Path + : T extends SVGTextElement + ? Text + : T extends SVGTextPathElement + ? TextPath + : T extends SVGGElement + ? G + : T extends SVGLineElement + ? Line + : T extends SVGPolylineElement + ? Polyline + : T extends SVGPolygonElement + ? Polygon + : T extends SVGGradientElement + ? Gradient + : T extends SVGImageElement + ? Image + : T extends SVGEllipseElement + ? Ellipse + : T extends SVGMaskElement + ? Mask + : T extends SVGMarkerElement + ? Marker + : T extends SVGClipPathElement + ? ClipPath + : T extends SVGTSpanElement + ? Tspan + : T extends SVGSymbolElement + ? Symbol + : T extends SVGUseElement + ? Use + : Element + + // element type as string + type SvgType = 'svg' + type ClipPathType = 'clipPath' + type TextType = 'text' + type GType = 'g' + type AType = 'a' + + type ParentElement = SvgType | GType | AType + + type AttrTypeMapping = T extends Rect ? RectAttr : GlobalAttr + + type ElementAlias = + | Dom + | Svg + | Rect + | Line + | Polygon + | Polyline + | Ellipse + | ClipPath + | Use + | Text + | Path + | TextPath + | Circle + | G + | Gradient + | Image + | Element + + type ElementTypeAlias = + | typeof Dom + | typeof Svg + | typeof Rect + | typeof Line + | typeof Polygon + | typeof Polyline + | typeof Ellipse + | typeof ClipPath + | typeof Use + | typeof Text + | typeof Path + | typeof TextPath + | typeof Circle + | typeof G + | typeof Gradient + | typeof Image + | typeof Element + + type AttributeReference = + | 'href' + | 'marker-start' + | 'marker-mid' + | 'marker-end' + | 'mask' + | 'clip-path' + | 'filter' + | 'fill' + + // ************* SVG.JS Type Declaration ************* + // ********** Locate in directory src/types ********** + + // SVGArray.js + // Notice: below class is defined the name as `Array` rather than `SVGArray`. + // The purpose of giving the name as `Array` is to allow it to be aligned with SVG.JS export type + // as SVG.JS export it as `Array` (to be precise `SVG.Array`) so reading through JS documentation + // should be more straightforward. + /** + * Type alias to native array. + * + * **Caution**: If argument is a string, generic type must be a number or array of number and + * the string is format as a concatenate of number separate by comma. + * This is expensive to build runtime type check for such as case so please use it carefully. + */ + type ArrayAlias = BuiltInArray | T[] | string + + class Array extends BuiltInArray { + constructor(array?: ArrayAlias) + + /** + * Return array of generic T however it's flatten array by 1 level as it using `apply` function. + * For example: if T is a `number[]` which is the number of 2 dimension `Array` the result will be `number[]` + */ + toArray(): any[] + /** + * return a concatenated string of each element separated by space + */ + toString(): string + valueOf(): T[] + clone(): Array + toSet(): Set + parse(a?: ArrayAlias): T[] + to(a: any): Morphable + } + + // point.js + class Point { + x: number + y: number + constructor() + constructor(position: CoordinateXY) + constructor(point: Point) + constructor(x: number, y?: number) + clone(): Point + transform(matrix: Matrix): this + transformO(matrix: Matrix): this + toArray(): ArrayXY + } + + // pointArray.js + class PointArray extends Array { + constructor() + constructor(array?: ArrayAlias | number[]) + + toLine(): LineAttr + transform(m: Matrix | MatrixLike): PointArray + move(x: number, y: number): this + size(width: number, height: number): this + bbox(): Box + to(a: any): Morphable + toString(): string + } + + // SVGNumber.js + type NumberUnit = [number, string] + + class Number { + constructor() + constructor(value: Number) + constructor(value: string) + constructor(value: number, unit?: any) + constructor(n: NumberUnit) + + value: number + unit: any + + toString(): string + toJSON(): object // same as toString + toArray(): NumberUnit + valueOf(): number + plus(number: NumberAlias): Number + minus(number: NumberAlias): Number + times(number: NumberAlias): Number + divide(number: NumberAlias): Number + convert(unit: string): Number + to(a: any): Morphable + } + + type NumberAlias = Number | number | string + + // PathArray.js + + type LineCommand = + | ['M' | 'm' | 'L' | 'l', number, number] + | ['H' | 'h' | 'V' | 'v', number] + | ['Z' | 'z'] + + type CurveCommand = + // Bezier Curves + | ['C' | 'c', number, number, number, number, number, number] + | ['S' | 's' | 'Q' | 'q', number, number, number, number] + | ['T' | 't', number, number] + // Arcs + | ['A' | 'a', number, number, number, number, number, number, number] + + type PathCommand = LineCommand | CurveCommand + + type PathArrayAlias = PathArray | PathCommand[] | (string | number)[] | string + + class PathArray extends Array { + constructor() + constructor(d: ArrayAlias | PathArrayAlias) + + move(x: number, y: number): this + size(width: number, height: number): this + equalCommands(other: PathArray): boolean + morph(pa: PathArray): this + parse(array?: ArrayAlias | PathArrayAlias): PathCommand[] + bbox(): Box + to(a: any): Morphable + } + + // Matrix.js + interface TransformData { + origin?: number[] + scaleX?: number + scaleY?: number + shear?: number + rotate?: number + translateX?: number + translateY?: number + originX?: number + originY?: number + } + + interface MatrixLike { + a?: number + b?: number + c?: number + d?: number + e?: number + f?: number + } + + interface MatrixExtract extends TransformData, MatrixLike {} + + type FlipType = 'both' | 'x' | 'y' | boolean + type ArrayXY = [number, number] + type CoordinateXY = ArrayXY | { x: number; y: number } + + interface MatrixTransformParam { + rotate?: number + flip?: FlipType + skew?: ArrayXY | number + skewX?: number + skewY?: number + scale?: ArrayXY | number + scaleX?: number + scaleY?: number + shear?: number + theta?: number + origin?: CoordinateXY | string + around?: CoordinateXY + ox?: number + originX?: number + oy?: number + originY?: number + position?: CoordinateXY + px?: number + positionX?: number + py?: number + positionY?: number + translate?: CoordinateXY + tx?: number + translateX?: number + ty?: number + translateY?: number + relative?: CoordinateXY + rx?: number + relativeX?: number + ry?: number + relativeY?: number + } + + type MatrixAlias = + | MatrixLike + | TransformData + | MatrixTransformParam + | number[] + | Element + | string + + class Matrix implements MatrixLike { + constructor() + constructor(source: MatrixAlias) + constructor( + a: number, + b: number, + c: number, + d: number, + e: number, + f: number + ) + + a: number + b: number + c: number + d: number + e: number + f: number; + + // *** To Be use by Test Only in restrict mode *** + [key: string]: any + + clone(): Matrix + transform(o: MatrixLike | MatrixTransformParam): Matrix + compose(o: MatrixExtract): Matrix + decompose(cx?: number, cy?: number): MatrixExtract + multiply(m: MatrixAlias | Matrix): Matrix + multiplyO(m: MatrixAlias | Matrix): this + lmultiply(m: MatrixAlias | Matrix): Matrix + lmultiplyO(m: MatrixAlias | Matrix): this + inverse(): Matrix + inverseO(): this + translate(x?: number, y?: number): Matrix + translateO(x?: number, y?: number): this + scale(x: number, y?: number, cx?: number, cy?: number): Matrix + scaleO(x: number, y?: number, cx?: number, cy?: number): this + rotate(r: number, cx?: number, cy?: number): Matrix + rotateO(r: number, cx?: number, cy?: number): this + flip(a: NumberAlias, offset?: number): Matrix + flipO(a: NumberAlias, offset?: number): this + flip(offset?: number): Matrix + shear(a: number, cx?: number, cy?: number): Matrix + shearO(a: number, cx?: number, cy?: number): this + skew(y?: number, cx?: number, cy?: number): Matrix + skewO(y?: number, cx?: number, cy?: number): this + skew(x: number, y?: number, cx?: number, cy?: number): Matrix + skewX(x: number, cx?: number, cy?: number): Matrix + skewY(y: number, cx?: number, cy?: number): Matrix + around(cx?: number, cy?: number, matrix?: Matrix): Matrix + aroundO(cx?: number, cy?: number, matrix?: Matrix): this + equals(m: Matrix): boolean + toString(): string + toArray(): number[] + valueOf(): MatrixLike + to(a: any): Morphable + } + + type ListEachCallback = (el: T, index: number, list: List) => any + + // List.js + class List extends BuiltInArray { + each(fn: ListEachCallback): List + each(name: string, ...args: any[]): List + toArray(): T[] + } + + class Eventobject { + [key: string]: Eventobject + } + + // EventTarget.js + class EventTarget { + events: Eventobject + + addEventListener(): void + dispatch(event: Event | string, data?: object): Event + dispatchEvent(event: Event): boolean + fire(event: Event | string, data?: object): this + getEventHolder(): this | Node + getEventTarget(): this | Node + + on( + events: string | Event[], + cb: EventListener, + binbind?: any, + options?: AddEventListenerOptions + ): this + off( + events?: string | Event[], + cb?: EventListener | number, + options?: AddEventListenerOptions + ): this + + removeEventListener(): void + } + + // Color.js + interface ColorLike { + r: number + g: number + b: number + + x: number + y: number + z: number + + h: number + s: number + l: number + a: number + c: number + + m: number + k: number + + space: string + } + + type ColorAlias = string | ColorLike + + class Color implements ColorLike { + r: number + g: number + b: number + + x: number + y: number + z: number + + h: number + s: number + l: number + a: number + c: number + + m: number + k: number + + space: string + constructor() + constructor(color: ColorAlias, space?: string) + constructor(a: number, b: number, c: number, space?: string) + constructor(a: number, b: number, c: number, d: number, space?: string) + constructor(a: number[], space?: string) + + rgb(): Color + lab(): Color + xyz(): Color + lch(): Color + hsl(): Color + cmyk(): Color + toHex(): string + toString(): string + toRgb(): string + toArray(): any[] + + to(a: any): Morphable + fromArray(a: any): this + + static random(mode: 'sine', time?: number): Color + static random(mode?: string): Color + } + + // Box.js + interface BoxLike { + height: number + width: number + y: number + x: number + cx?: number + cy?: number + w?: number + h?: number + x2?: number + y2?: number + } + + class Box implements BoxLike { + height: number + width: number + y: number + x: number + cx: number + cy: number + w: number + h: number + x2: number + y2: number + + constructor() + constructor(source: string) + constructor(source: number[]) + constructor(source: DOMRect) + constructor(x: number, y: number, width: number, height: number) + + merge(box: BoxLike): Box + transform(m: Matrix): Box + addOffset(): this + toString(): string + toArray(): number[] + isNulled(): boolean + to(v: MorphValueLike): Morphable + } + + // Morphable.js + type MorphValueLike = + | string + | number + | objectBag + | NonMorphable + | MatrixExtract + | Array + | any[] + + class Morphable { + constructor() + constructor(st: Stepper) + + from(): MorphValueLike + from(v: MorphValueLike): this + to(): MorphValueLike + to(v: MorphValueLike): this + type(): any + type(t: any): this + stepper(): Stepper + stepper(st: Stepper): this + done(): boolean + at(pos: number): any + } + + class objectBag { + constructor() + constructor(a: object) + valueOf(): object + toArray(): object[] + + to(a: object): Morphable + fromArray(a: any[]): this + } + + class NonMorphable { + constructor(a: object) + valueOf(): object + toArray(): object[] + + to(a: object): Morphable + fromArray(a: object): this + } + + class TransformBag { + constructor() + constructor(a: number[]) + constructor(a: TransformData) + defaults: TransformData + toArray(): number[] + to(t: TransformData): Morphable + fromArray(t: number[]): this + } + + interface Stepper { + done(c?: object): boolean + } + + class Ease implements Stepper { + constructor() + constructor(fn: string) + constructor(fn: Function) + + step(from: number, to: number, pos: number): number + done(): boolean + } + + class Controller implements Stepper { + constructor(fn?: Function) + step(current: number, target: number, dt: number, c: number): number + done(c?: object): boolean + } + + // Queue.js + interface QueueParam { + value: any + next?: any + prev?: any + } + + class Queue { + constructor() + + push(value: any): QueueParam + shift(): any + first(): number + last(): number + remove(item: QueueParam): void + } + + // Timeline.js + interface ScheduledRunnerInfo { + start: number + duration: number + end: number + runner: Runner + } + + class Timeline extends EventTarget { + constructor() + constructor(fn: Function) + + active(): boolean + schedule(runner: Runner, delay?: number, when?: string): this + schedule(): ScheduledRunnerInfo[] + unschedule(runner: Runner): this + getEndTime(): number + updateTime(): this + persist(dtOrForever?: number | boolean): this + play(): this + pause(): this + stop(): this + finish(): this + speed(speed: number): this + reverse(yes: boolean): this + seek(dt: number): this + time(): number + time(time: number): this + source(): Function + source(fn: Function): this + } + + // Runner.js + interface TimesParam { + duration: number + delay: number + when: number | string + swing: boolean + wait: number + times: number + } + + type TimeLike = number | TimesParam | Stepper + + type EasingCallback = (...any: any) => number + type EasingLiteral = '<>' | '-' | '<' | '>' + + class Runner { + constructor() + constructor(options: Function) + constructor(options: number) + constructor(options: Controller) + + static sanitise: ( + duration?: TimeLike, + delay?: number, + when?: string + ) => object + + element(): Element + element(el: Element): this + timeline(): Timeline + timeline(timeline: Timeline): this + animate(duration?: TimeLike, delay?: number, when?: string): this + schedule(delay: number, when?: string): this + schedule(timeline: Timeline, delay?: number, when?: string): this + unschedule(): this + loop(times?: number, swing?: boolean, wait?: number): this + loop(times: TimesParam): this + delay(delay: number): this + + during(fn: Function): this + queue( + initFn: Function, + runFn: Function, + retargetFn?: boolean | Function, + isTransform?: boolean + ): this + after(fn: EventListener): this + time(): number + time(time: number): this + duration(): number + loops(): number + loops(p: number): this + persist(dtOrForever?: number | boolean): this + position(): number + position(p: number): this + progress(): number + progress(p: number): this + step(deta?: number): this + reset(): this + finish(): this + reverse(r?: boolean): this + ease(fn: EasingCallback): this + ease(kind: EasingLiteral): this + active(): boolean + active(a: boolean): this + addTransform(m: Matrix): this + clearTransform(): this + clearTransformsFromQueue(): void + + // extends prototypes + attr(a: string | object, v?: string): this + css(s: string | object, v?: string): this + styleAttr(type: string, name: string | object, val?: string): this + zoom(level: NumberAlias, point?: Point): this + transform( + transforms: MatrixTransformParam, + relative?: boolean, + affine?: boolean + ): this + x(x: number): this + y(y: number): this + ax(x: number): this + ay(y: number): this + dx(dx: number): this + dy(dy: number): this + cx(x: number): this + cy(y: number): this + dmove(dx: number, dy: number): this + move(x: number, y: number): this + amove(x: number, y: number): this + center(x: number, y: number): this + size(width: number, height: number): this + width(width: number): this + height(height: number): this + plot(a: object): this + plot(a: number, b: number, c: number, d: number): this + leading(value: number): this + viewbox(x: number, y: number, width: number, height: number): this + update(offset: number, color: number, opacity: number): this + update(o: StopProperties): this + rx(): number + rx(rx: number): this + ry(): number + ry(ry: number): this + from(x: NumberAlias, y: NumberAlias): this + to(x: NumberAlias, y: NumberAlias): this + + fill(): string + fill(fill: FillData): this + fill(color: string): this + fill(pattern: Element): this + fill(image: Image): this + stroke(): string + stroke(stroke: StrokeData): this + stroke(color: string): this + matrix(): Matrix + matrix( + a: number, + b: number, + c: number, + d: number, + e: number, + f: number + ): this + matrix(mat: MatrixAlias): this + rotate(degrees: number, cx?: number, cy?: number): this + skew(skewX?: number, skewY?: number, cx?: number, cy?: number): this + scale(scaleX?: number, scaleY?: number, cx?: number, cy?: number): this + translate(x: number, y: number): this + shear(lam: number, cx: number, cy: number): this + relative(x: number, y: number): this + flip(direction?: string, around?: number): this + flip(around: number): this + opacity(): number + opacity(value: number): this + font(a: string): string + font(a: string, v: string | number): this + font(a: object): this + } + + // Animator.js + let Animator: { + nextDraw: any + frames: Queue + timeouts: Queue + immediates: Queue + + timer(): boolean + frame(fn: Function): object + timeout(fn: Function, delay?: number): object + immediate(fn: Function): object + cancelFrame(o: object): void + clearTimeout(o: object): void + cancelImmediate(o: object): void + } + + /** + * Just fancy type alias to refer to css query selector. + */ + type QuerySelector = string + + class Dom extends EventTarget { + node: HTMLElement | SVGElement + type: string + + constructor(node?: HTMLElement, attr?: object) + constructor(att: object) + add(element: Element, i?: number): this + addTo(parent: Dom | HTMLElement | string, i?: number): this + children(): List + clear(): this + clone(deep?: boolean, assignNewIds?: boolean): this + each( + block: (index: number, children: Element[]) => void, + deep?: boolean + ): this + element(element: string, inherit?: object): this + first(): Element + get(i: number): Element + getEventHolder(): LinkedHTMLElement + getEventTarget(): LinkedHTMLElement + has(element: Element): boolean + id(): string + id(id: string): this + index(element: Element): number + last(): Element + matches(selector: string): boolean + /** + * Finds the closest ancestor which matches the string or is of passed type. If nothing is passed, the parent is returned + * @param type can be either string, svg.js object or undefined. + */ + parent(type?: ElementTypeAlias | QuerySelector): Dom | null + put(element: Element, i?: number): Element + /** + * Put the element into the given parent element and returns the parent element + * @param parent The parent in which the current element is inserted + */ + putIn(parent: ElementAlias | Node | QuerySelector): Dom + + remove(): this + removeElement(element: Element): this + replace(element: T): T + round(precision?: number, map?: string[]): this + svg(): string + svg(a: string, outer: true): Element + svg(a: string, outer?: false): this + svg(a: boolean, outer?: boolean): string + svg(a: null | Function, outer?: boolean): string + + toString(): string + words(text: string): this + writeDataToDom(): this + + // prototype extend Attribute in attr.js + /** + * Get the attribute object of SVG Element. The return object will be vary based on + * the instance itself. For example, G element will only return GlobalAttr where Rect + * will return RectAttr instead. + */ + attr(): any + /** + * Add or update the attribute from the SVG Element. To remove the attribute from the element set value to null + * @param name name of attribute + * @param value value of attribute can be string or number or null + * @param namespace optional string that define namespace + */ + attr(name: string, value: any, namespace?: string): this + attr(name: string): any + attr(obj: object): this + attr(obj: string[]): object + + // prototype extend Selector in selector.js + find(query: string): List + findOne(query: string): Dom | null + + // prototype method register in data.js + data(a: string): object | string | number + data(a: string, v: object, substain?: boolean): this + data(a: object): this + + // prototype method register in arrange.js + siblings(): List + position(): number + next(): Element + prev(): Element + forward(): this + backward(): this + front(): this + back(): this + before(el: Element): Element + after(el: Element): Element + insertBefore(el: Element): this + insertAfter(el: Element): this + + // prototype method register in class.js + classes(): string[] + hasClass(name: string): boolean + addClass(name: string): this + removeClass(name: string): this + toggleClass(name: string): this + + // prototype method register in css.js + css(): Partial + css(style: T): CSSStyleDeclarationWithVars[T] + css( + style: T + ): Partial + css( + style: T, + val: CSSStyleDeclarationWithVars[T] + ): this + css(style: Partial): this + show(): this + hide(): this + visible(): boolean + + // memory.js + remember(name: string, value: any): this + remember(name: string): any + remember(obj: object): this + forget(...keys: string[]): this + forget(): this + memory(): object + + addEventListener(): void + dispatch(event: Event | string, data?: object): Event + dispatchEvent(event: Event): boolean + fire(event: Event | string, data?: object): this + getEventHolder(): this | Node + getEventTarget(): this | Node + + // on(events: string | Event[], cb: EventListener, binbind?: any, options?: AddEventListenerOptions): this; + // off(events?: string | Event[], cb?: EventListener | number): this; + removeEventListener(): void + } + + // clip.js + class ClipPath extends Container { + constructor() + constructor(node?: SVGClipPathElement) + constructor(attr: object) + node: SVGClipPathElement + + targets(): List + remove(): this + } + + // container.js + interface ViewBoxLike { + x: number + y: number + width: number + height: number + } + + class Containable { + circle(size?: NumberAlias): Circle + clip(): ClipPath + ellipse(width?: number, height?: number): Ellipse + foreignObject(width: number, height: number): ForeignObject + gradient(type: string, block?: (stop: Gradient) => void): Gradient + group(): G + + image(): Image + image(href?: string, callback?: (e: Event) => void): Image + line(points?: PointArrayAlias): Line + line(x1: number, y1: number, x2: number, y2: number): Line + link(url: string): A + marker( + width?: number, + height?: number, + block?: (marker: Marker) => void + ): Marker + mask(): Mask + nested(): Svg + path(): Path + path(d: PathArrayAlias): Path + pattern( + width?: number, + height?: number, + block?: (pattern: Pattern) => void + ): Pattern + plain(text: string): Text + polygon(points?: PointArrayAlias): Polygon + polyline(points?: PointArrayAlias): Polyline + rect(width?: NumberAlias, height?: NumberAlias): Rect + style(): Style + text(block: (tspan: Tspan) => void): Text + text(text: string): Text + use(element: Element | string, file?: string): Use + viewbox(): Box + viewbox(viewbox: ViewBoxLike | string): this + viewbox(x: number, y: number, width: number, height: number): this + textPath(text: string | Text, path: string | Path): TextPath + symbol(): Symbol + zoom(): number + zoom(level: NumberAlias, point?: Point): this + } + + type DynamicExtends = { + new (...args: any[]): Containable & T + } + + /** + * only for declaration purpose. actually cannot be used. + */ + let ContainableDom: DynamicExtends + class Fragment extends ContainableDom { + constructor(node?: Node) + } + + /** + * only for declaration purpose. actually cannot be used. + */ + let ContainableElement: DynamicExtends + class Container extends ContainableElement { + constructor() + flatten(parent: Dom, depth?: number): this + ungroup(parent: Dom, depth?: number): this + } + + class Defs extends Container { + constructor(node?: SVGDefsElement) + node: SVGDefsElement + marker( + width?: number, + height?: number, + block?: (marker: Marker) => void + ): Marker + } + + class Svg extends Container { + constructor(svgElement?: SVGSVGElement) + constructor(id: string) + node: SVGSVGElement + namespace(): this + defs(): Defs + remove(): this + isRoot(): boolean + } + + type EventHandler = (event: T) => void + + interface Sugar { + fill(): string + fill(fill: FillData): this + fill(color: string): this + fill(pattern: Element): this + fill(image: Image): this + stroke(): string + stroke(stroke: StrokeData): this + stroke(color: string): this + matrix(): Matrix + matrix( + a: number, + b: number, + c: number, + d: number, + e: number, + f: number + ): this + matrix(mat: MatrixAlias): this + rotate(degrees: number, cx?: number, cy?: number): this + skew(skewX?: number, skewY?: number, cx?: number, cy?: number): this + scale(scaleX?: number, scaleY?: number, cx?: number, cy?: number): this + translate(x: number, y: number): this + shear(lam: number, cx: number, cy: number): this + relative(x: number, y: number): this + flip(direction?: string, around?: number): this + flip(around: number): this + opacity(): number + opacity(value: number): this + font(a: string): string + font(a: string, v: string | number): this + font(a: object): this + + click(cb: EventHandler): this + dblclick(cb: EventHandler): this + mousedown(cb: EventHandler): this + mouseup(cb: EventHandler): this + mouseover(cb: EventHandler): this + mouseout(cb: EventHandler): this + mousemove(cb: EventHandler): this + mouseenter(cb: EventHandler): this + mouseleave(cb: EventHandler): this + touchstart(cb: EventHandler): this + touchmove(cb: EventHandler): this + touchleave(cb: EventHandler): this + touchend(cb: EventHandler): this + touchcancel(cb: EventHandler): this + contextmenu(cb: EventHandler): this + wheel(cb: EventHandler): this + pointerdown(cb: EventHandler): this + pointermove(cb: EventHandler): this + pointerup(cb: EventHandler): this + pointerleave(cb: EventHandler): this + pointercancel(cb: EventHandler): this + } + + // Symbol.js + class Symbol extends Container { + constructor(svgElement?: SVGSymbolElement) + constructor(attr: object) + node: SVGSymbolElement + } + + class Element extends Dom implements Sugar { + constructor(node?: SVGElement) + constructor(attr: object) + node: SVGElement + type: string + dom: any + + addClass(name: string): this + after(element: Element): Element + animate(duration?: TimeLike, delay?: number, when?: string): Runner + delay(by: number, when?: string): Runner + attr(): any + attr(name: string, value: any, namespace?: string): this + attr(name: string): any + attr(obj: string[]): object + attr(obj: object): this + back(): this + backward(): this + bbox(): Box + before(element: Element): Element + center(x: number, y: number): this + classes(): string[] + clipper(): ClipPath + clipWith(element: Element): this + clone(deep?: boolean, assignNewIds?: boolean): this + ctm(): Matrix + cx(): number + cx(x: number): this + cy(): number + cy(y: number): this + data(name: string, value: any, sustain?: boolean): this + data(name: string): any + data(val: object): this + defs(): Defs + dmove(x: NumberAlias, y: NumberAlias): this + dx(x: NumberAlias): this + dy(y: NumberAlias): this + event(): Event | CustomEvent + + fire(event: Event): this + fire(event: string, data?: any): this + forget(...keys: string[]): this + forget(): this + forward(): this + front(): this + hasClass(name: string): boolean + height(): NumberAlias + height(height: NumberAlias): this + hide(): this + hide(): this + id(): string + id(id: string): this + inside(x: number, y: number): boolean + is(cls: any): boolean + linkTo(url: (link: A) => void): A + linkTo(url: string): A + masker(): Mask + maskWith(element: Element): this + maskWith(mask: Mask): this + matches(selector: string): boolean + matrixify(): Matrix + memory(): object + move(x: NumberAlias, y: NumberAlias): this + native(): LinkedHTMLElement + next(): Element + // off(events?: string | Event[], cb?: EventListener | number): this; + // on(event: string, cb: Function, context?: object): this; + toRoot(): Svg + /** + * By default parents will return a list of elements up until the root svg. + */ + parents(): List + /** + * List the parent by hierarchy until the given parent type or matcher. If the given value is null + * then the result is only provided the list up until Svg root element which mean no Dom parent element is included. + * @param util a parent type + */ + parents(util: QuerySelector | T | null): List + /** + * Get reference svg element based on the given attribute. + * @param attr a svg attribute + */ + reference(attr: AttributeReference): R | null + + point(): Point + point(position: CoordinateXY): Point + point(point: Point): Point + point(x: number, y: number): Point + position(): number + prev(): Element + rbox(element?: Element): Box + reference(type: string): Element + remember(name: string, value: any): this + remember(name: string): any + remember(obj: object): this + remove(): this + removeClass(name: string): this + root(): Svg + screenCTM(): Matrix + setData(data: object): this + show(): this + show(): this + size(width?: NumberAlias, height?: NumberAlias): this + stop(jumpToEnd: boolean, clearQueue: boolean): Animation + stop( + offset?: NumberAlias | string, + color?: NumberAlias, + opacity?: NumberAlias + ): Stop + stop(val: { + offset?: NumberAlias | string + color?: NumberAlias + opacity?: NumberAlias + }): Stop + timeline(): Timeline + timeline(tl: Timeline): this + toggleClass(name: string): this + toParent(parent: Dom): this + toParent(parent: Dom, i: number): this + toSvg(): this + transform(): MatrixExtract + transform(t: MatrixAlias, relative?: boolean): this + unclip(): this + unmask(): this + untransform(): this + visible(): boolean + width(): NumberAlias + width(width: NumberAlias): this + x(): NumberAlias + x(x: NumberAlias): this + y(): NumberAlias + y(y: NumberAlias): this + + fill(): string + fill(fill: FillData): this + fill(color: string): this + fill(pattern: Element): this + fill(image: Image): this + stroke(): string + stroke(stroke: StrokeData): this + stroke(color: string): this + matrix(): Matrix + matrix( + a: number, + b: number, + c: number, + d: number, + e: number, + f: number + ): this + matrix(mat: MatrixAlias): this + rotate(degrees: number, cx?: number, cy?: number): this + skew(skewX?: number, skewY?: number, cx?: number, cy?: number): this + scale(scaleX?: number, scaleY?: number, cx?: number, cy?: number): this + translate(x: number, y: number): this + shear(lam: number, cx: number, cy: number): this + relative(x: number, y: number): this + flip(direction?: string, around?: number): this + flip(around: number): this + opacity(): number + opacity(value: number): this + font(a: string): string + font(a: string, v: string | number): this + font(a: object): this + + click(cb: EventHandler): this + dblclick(cb: EventHandler): this + mousedown(cb: EventHandler): this + mouseup(cb: EventHandler): this + mouseover(cb: EventHandler): this + mouseout(cb: EventHandler): this + mousemove(cb: EventHandler): this + mouseenter(cb: EventHandler): this + mouseleave(cb: EventHandler): this + touchstart(cb: EventHandler): this + touchmove(cb: EventHandler): this + touchleave(cb: EventHandler): this + touchend(cb: EventHandler): this + touchcancel(cb: EventHandler): this + contextmenu(cb: EventHandler): this + wheel(cb: EventHandler): this + pointerdown(cb: EventHandler): this + pointermove(cb: EventHandler): this + pointerup(cb: EventHandler): this + pointerleave(cb: EventHandler): this + pointercancel(cb: EventHandler): this + } + + // ellipse.js + interface CircleMethods extends Shape { + rx(rx: number): this + rx(): this + ry(ry: number): this + ry(): this + radius(x: number, y?: number): this + } + class Circle extends Shape implements CircleMethods { + constructor(node?: SVGCircleElement) + constructor(attr: CircleAttr) + + node: SVGCircleElement + + rx(rx: number): this + rx(): this + ry(ry: number): this + ry(): this + radius(x: number, y?: number): this + } + class Ellipse extends Shape implements CircleMethods { + node: SVGEllipseElement + constructor(attr: EllipseAttr) + constructor(node?: SVGEllipseElement) + + rx(rx: number): this + rx(): this + ry(ry: number): this + ry(): this + radius(x: number, y?: number): this + } + + interface StopProperties { + color?: ColorAlias + offset?: number | string + opacity?: number + } + + // gradient.js + class Stop extends Element { + update(offset?: number, color?: ColorAlias, opacity?: number): this + update(opts: StopProperties): this + } + class Gradient extends Container { + constructor(node?: SVGGradientElement) + constructor(attr: object) + constructor(type: string) + node: SVGGradientElement + + at(offset?: number, color?: ColorAlias, opacity?: number): Stop + at(opts: StopProperties): Stop + url(): string + toString(): string + targets(): List + bbox(): Box + + // gradiented.js + from(x: number, y: number): this + to(x: number, y: number): this + + // TODO: check with main.js + radius(x: number, y?: number): this + targets(): List + bbox(): Box + update(block?: (gradient: Gradient) => void): this + } + + // group.js + class G extends Container { + constructor(node?: SVGGElement) + constructor(attr: object) + node: SVGGElement + } + + // hyperlink.js + class A extends Container { + constructor(node?: SVGAElement) + constructor(attr: object) + node: SVGAElement + to(url: string): this + to(): string + target(target: string): this + target(): string + } + + // ForeignObject.js + class ForeignObject extends Element { + constructor(node?: SVGForeignObjectElement, attrs?: object) + constructor(attrs?: object) + add(element: Dom, i?: number): this + } + + // image.js + class Image extends Shape { + constructor(node?: SVGImageElement) + constructor(attr: object) + node: SVGImageElement + load(url?: string, callback?: (event: Event) => void): this + } + + // line.js + type PointArrayAlias = number[] | ArrayXY[] | PointArray | string + + class Line extends Shape { + constructor(attr: LineAttr) + constructor(node?: SVGLineElement) + + node: SVGLineElement + + array(): PointArray + plot(): PointArray + plot(points?: PointArrayAlias): this + plot(x1: number, y1: number, x2: number, y2: number): this + move(x: number, y: number): this + size(width?: number, height?: number): this + marker( + position: string, + width?: number, + height?: number, + block?: (marker: Marker) => void + ): Marker + marker(position: string, marker: Marker): Marker + } + + // marker.js + // TODO: check register method marker + class Marker extends Container { + constructor() + + node: SVGMarkerElement + + ref(x: string | number, y: string | number): this + update(block: (marker: Marker) => void): this + toString(): string + orient(orientation: 'auto' | 'auto-start-reverse' | number | Number): this + orient(): string + } + // mask.js + class Mask extends Container { + constructor(node?: SVGMaskElement) + constructor(attr: object) + node: SVGMaskElement + remove(): this + targets(): List + } + + // path.js + class Path extends Shape { + constructor(attr: PathAttr) + constructor(node?: SVGPathElement) + + node: SVGPathElement + + morphArray: PathArray + array(): PathArray + plot(): PathArray + plot(d: PathArrayAlias): this + marker( + position: string, + width?: number, + height?: number, + block?: (marker: Marker) => void + ): this + marker(position: string, marker: Marker): this + + // sugar.js + length(): number + pointAt(length: number): { x: number; y: number } + text(text: string): TextPath + text(text: Text): TextPath + targets(): List + } + + // pattern.js + class Pattern extends Container { + url(): string + url(...rest: any[]): never + update(block: (pattern: Pattern) => void): this + toString(): string + } + + // poly.js + interface poly { + array(): PointArray + plot(): PointArray + plot(p: PointArrayAlias): this + clear(): this + move(x: number, y: number): this + size(width: number, height?: number): this + } + + // pointed.js + interface pointed { + x(): number + x(x: number): this + y(): number + y(y: number): this + height(): number + height(h: number): this + width(): number + width(w: number): this + } + + class Polyline extends Shape implements poly, pointed { + constructor(node?: SVGPolylineElement) + constructor(attr: PolyAttr) + + node: SVGPolylineElement + + array(): PointArray + plot(): PointArray + plot(p: PointArrayAlias): this + x(): number + x(x: number): this + y(): number + y(y: number): this + height(): number + height(h: number): this + width(): number + width(w: number): this + move(x: number, y: number): this + size(width: number, height?: number): this + marker( + position: string, + width?: number, + height?: number, + block?: (marker: Marker) => void + ): Marker + marker(position: string, marker: Marker): Marker + } + + class Polygon extends Shape implements poly, pointed { + constructor(node?: SVGPolygonElement) + constructor(attr: PolyAttr) + + node: SVGPolygonElement + array(): PointArray + plot(): PointArray + plot(p: PointArrayAlias): this + x(): number + x(x: number): this + y(): number + y(y: number): this + height(): number + height(h: number): this + width(): number + width(w: number): this + move(x: number, y: number): this + size(width: number, height?: number): this + marker( + position: string, + width?: number, + height?: number, + block?: (marker: Marker) => void + ): Marker + marker(position: string, marker: Marker): Marker + } + + class Rect extends Shape { + constructor(node?: SVGRectElement) + constructor(attr: RectAttr) + node: SVGRectElement + radius(x: number, y?: number): this + } + + // shape.js + class Shape extends Element {} + + // sugar.js + interface StrokeData { + color?: string + width?: number + opacity?: number + linecap?: string + linejoin?: string + miterlimit?: number + dasharray?: string + dashoffset?: number + } + + interface FillData { + color?: string + opacity?: number + rule?: string + } + + interface FontData { + family?: string + size?: NumberAlias + anchor?: string + leading?: NumberAlias + weight?: string + style?: string + } + // textable.js + interface Textable { + plain(text: string): this + length(): number + } + + // text.js + class Text extends Shape implements Textable { + constructor(node?: SVGElement) + constructor(attr: TextAttr) + + clone(): this + text(): string + text(text: string): this + text(block: (text: this) => void): this + leading(): Number + leading(leading: NumberAlias): this + rebuild(enabled: boolean): this + build(enabled: boolean): this + clear(): this + plain(text: string): this + length(): number + get(i: number): Tspan + path(): TextPath + path(d: PathArrayAlias | Path): TextPath + track(): Element + ax(): string + ax(x: string): this + ay(): string + ay(y: string): this + amove(x: number, y: number): this + textPath(): TextPath + + // main.js, from extend/copy prototypes from Tspan + tspan(text: string): Tspan + tspan(block: (tspan: Tspan) => void): this + } + + class Tspan extends Text implements Textable { + constructor(node?: SVGElement) + constructor(attr: TextAttr) + dx(): number + dx(x: NumberAlias): this + dy(): number + dy(y: NumberAlias): this + newLine(): this + tspan(text: string): Tspan + tspan(block: (tspan: Tspan) => void): this + length(): number + text(): string + text(text: string): this + text(block: (text: this) => void): this + plain(text: string): this + } + + // textpath.js + class TextPath extends Text { + constructor() + constructor(attr: TextPathAttr) + + array(): Array + plot(): PathArray + plot(d: string): this + track(): Path + } + + // style.js + class Style extends Element { + constructor(node: SVGElement, attr?: StylingAttr) + addText(text: string): this + font(a: object): this + font(a: string, v: string | number): this + font(a: string): string + rule(selector: string, obj: any): this + } + + // use.js + class Use extends Shape { + use(element: string, file?: string): this + } + + // viewbox.js + type ViewBoxAlias = ViewBoxLike | number[] | string | Element + + interface ViewBox { + x: number + y: number + width: number + height: number + toString(): string + at(pos: number): ViewBox + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1 @@ +{}