diff --git a/.eslintignore b/.eslintignore index 62117a94..0711ad31 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,5 +3,8 @@ coverage/ docs/lib/ examples/ /mithril.js +/mithril.mjs /mithril.min.js +/mithril.min.mjs +/stream/stream.mjs node_modules/ diff --git a/.gitattributes b/.gitattributes index 7296866f..91f1f822 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,7 @@ * text=auto /mithril.js binary /mithril.min.js binary +/mithril.mjs binary +/mithril.min.mjs binary /package-lock.json binary /yarn.lock binary diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 04e141d1..58df1782 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,5 @@ render/ @pygy +render/ @isiahmeadows +api/ @isiahmeadows +performance/ @isiahmeadows +util/ @isiahmeadows diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/Bug_report.md similarity index 81% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/Bug_report.md index be4f8168..a8c9faee 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -1,3 +1,13 @@ +--- +name: "\U0001F41BBug report" +about: Report a bug in MithrilJS +--- + +### Mithril Version: x.x.x + + + + ## Expected Behavior @@ -12,7 +22,7 @@ -## Steps to Reproduce (for bugs) +## Steps to Reproduce 1. @@ -24,9 +34,12 @@ +
+ Additional Information + ## Your Environment -* Version used: * Browser Name and version: * Operating System and version (desktop or mobile): * Link to your project: +
diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md new file mode 100644 index 00000000..eed57e70 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -0,0 +1,25 @@ +--- +name: "\U0001F680Feature or Enhancement" +about: Suggest an Idea or Enhancement for MithrilJS +--- + +### Description + + + +### Why + + + + + +### Possible Implementation & Open Questions + + + + + +### Is this something you're interested in working on? + + + diff --git a/.travis.yml b/.travis.yml index b664f24a..61b391ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,8 @@ before_script: - npm run build-browser # Pass -save so it'll update the readme as well - npm run build-min -- -save +# must run after build-min in order to generate min.mjs +- npm run build-esm # Run tests, lint, and then check for perf regressions script: diff --git a/README.md b/README.md index 881fab6e..681158a9 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,11 @@ mithril.js [![NPM Version](https://img.shields.io/npm/v/mithril.svg)](https://ww ## What is Mithril? -A modern client-side Javascript framework for building Single Page Applications. It's small (8.89 KB gzipped), fast and provides routing and XHR utilities out of the box. +A modern client-side Javascript framework for building Single Page Applications. It's small (8.86 KB gzipped), fast and provides routing and XHR utilities out of the box. Mithril is used by companies like Vimeo and Nike, and open source platforms like Lichess 👍. -Browsers all the way back to IE9 are supported, no polyfills required 👌. +Mithril supports IE11, Firefox ESR, and the last two versions of Firefox, Edge, Safari, and Chrome. No polyfills required. 👌 ## Installation @@ -30,12 +30,13 @@ Browsers all the way back to IE9 are supported, no polyfills required 👌. ```html + + ``` ### npm - ```bash -$ npm install mithril +$ npm install mithril --save ``` The ["Getting started" guide](https://mithril.js.org/#getting-started) is a good place to start learning how to use mithril. diff --git a/api/redraw.js b/api/redraw.js index a6549975..e43ffffc 100644 --- a/api/redraw.js +++ b/api/redraw.js @@ -3,18 +3,13 @@ var coreRenderer = require("../render/render") function throttle(callback) { - //60fps translates to 16.6ms, round it down since setTimeout requires int - var delay = 16 - var last = 0, pending = null - var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout + var pending = null return function() { - var elapsed = Date.now() - last if (pending === null) { - pending = timeout(function() { + pending = requestAnimationFrame(function() { pending = null callback() - last = Date.now() - }, delay - elapsed) + }) } } } @@ -22,11 +17,6 @@ function throttle(callback) { module.exports = function($window, throttleMock) { var renderService = coreRenderer($window) - renderService.setEventCallback(function(e) { - if (e.redraw === false) e.redraw = undefined - else redraw() - }) - var callbacks = [] var rendering = false @@ -47,5 +37,6 @@ module.exports = function($window, throttleMock) { var redraw = (throttleMock || throttle)(sync) redraw.sync = sync + renderService.setRedraw(redraw) return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render} } diff --git a/api/tests/test-redraw.js b/api/tests/test-redraw.js index 65831bb0..0652f351 100644 --- a/api/tests/test-redraw.js +++ b/api/tests/test-redraw.js @@ -5,6 +5,19 @@ var domMock = require("../../test-utils/domMock") var throttleMocker = require("../../test-utils/throttleMock") var apiRedraw = require("../../api/redraw") +// Because Node doesn't have this. +if (typeof requestAnimationFrame !== "function") { + global.requestAnimationFrame = (function (delay, last) { + return function(callback) { + var elapsed = Date.now() - last + return setTimeout(function() { + callback() + last = Date.now() + }, delay - elapsed) + } + })(16, 0) +} + o.spec("redrawService", function() { var root, redrawService, $document o.beforeEach(function() { diff --git a/bundler/bundle.js b/bundler/bundle.js index af42e3ad..3a561d68 100644 --- a/bundler/bundle.js +++ b/bundler/bundle.js @@ -16,7 +16,7 @@ function parse(file) { } var error -function run(input, output) { +module.exports = function (input) { var modules = {} var bindings = {} var declaration = /^\s*(?:var|let|const|function)[\t ]+([\w_$]+)/gm @@ -115,19 +115,7 @@ function run(input, output) { .replace(versionTag, isFile(packageFile) ? parse(packageFile).version : versionTag) // set version code = ";(function() {\n" + code + "\n}());" - - if (!isFile(output) || code !== read(output)) { - //try {new Function(code); console.log("build completed at " + new Date())} catch (e) {} - error = null - fs.writeFileSync(output, code, "utf8") - } -} - -module.exports = function(input, output, options) { - run(input, output) - if (options && options.watch) { - fs.watch(process.cwd(), {recursive: true}, function(file) { - if (typeof file === "string" && path.resolve(output) !== path.resolve(file)) run(input, output) - }) - } + //try {new Function(code); console.log("build completed at " + new Date())} catch (e) {} + error = null + return code } diff --git a/bundler/cli.js b/bundler/cli.js index 5533342d..03e5c939 100644 --- a/bundler/cli.js +++ b/bundler/cli.js @@ -1,10 +1,11 @@ "use strict" -var fs = require("fs"); +var fs = require("fs") var zlib = require("zlib") +var chokidar = require("chokidar") +var Terser = require("terser") var bundle = require("./bundle") -var minify = require("./minify") var aliases = {o: "output", m: "minify", w: "watch", s: "save"} var params = {} @@ -29,14 +30,20 @@ function format(n) { return n.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,") } -bundle(params.input, params.output, {watch: params.watch}) -if (params.minify) { - // mFiles = { original: String(mithril.js), compressed: String(mithril.min.js) } - var mFiles = minify(params.output, {watch: params.watch}) - var originalSize = mFiles.original.length - var compressedSize = mFiles.compressed.length - var originalGzipSize = zlib.gzipSync(mFiles.original).byteLength - var compressedGzipSize = zlib.gzipSync(mFiles.compressed).byteLength +function build() { + var original = bundle(params.input) + if (!params.minify) { + fs.writeFileSync(params.output, original, "utf-8") + return + } + console.log("minifying...") + var minified = Terser.minify(original) + if (minified.error) throw new Error(minified.error) + fs.writeFileSync(params.output, minified.code, "utf-8") + var originalSize = original.length + var compressedSize = minified.code.length + var originalGzipSize = zlib.gzipSync(original).byteLength + var compressedGzipSize = zlib.gzipSync(minified.code).byteLength console.log("Original size: " + format(originalGzipSize) + " bytes gzipped (" + format(originalSize) + " bytes uncompressed)") console.log("Compiled size: " + format(compressedGzipSize) + " bytes gzipped (" + format(compressedSize) + " bytes uncompressed)") @@ -52,4 +59,7 @@ if (params.minify) { ) ) } -} \ No newline at end of file +} + +build() +if (params.watch) chokidar.watch(".", {ignored: params.output}).on("all", build) diff --git a/bundler/minify.js b/bundler/minify.js deleted file mode 100644 index 987d378a..00000000 --- a/bundler/minify.js +++ /dev/null @@ -1,26 +0,0 @@ -"use strict" - -var fs = require("fs") -var UglifyES = require("uglify-es") - -module.exports = function(filePath, options) { - function minify(filePath) { - var original = fs.readFileSync(filePath, "utf8"), - uglified = UglifyES.minify(original), - compressed = uglified.code - - if (uglified.error) throw new Error(uglified.error) - - fs.writeFileSync(filePath, compressed, "utf8") - return {original: original, compressed: compressed} - } - - function run() { - console.log("minifying...") - return minify(filePath) - } - - if (options && options.watch) fs.watchFile(filePath, run) - - return run() -} diff --git a/bundler/tests/test-bundler.js b/bundler/tests/test-bundler.js index ff8bb32e..91da45ed 100644 --- a/bundler/tests/test-bundler.js +++ b/bundler/tests/test-bundler.js @@ -6,9 +6,6 @@ var bundle = require("../bundle") var fs = require("fs") var ns = "./" -function read(filepath) { - try {return fs.readFileSync(ns + filepath, "utf8")} catch (e) {/* ignore */} -} function write(filepath, data) { try {var exists = fs.statSync(ns + filepath).isFile()} catch (e) {/* ignore */} if (exists) throw new Error("Don't call `write('" + filepath + "')`. Cannot overwrite file") @@ -22,266 +19,221 @@ o.spec("bundler", function() { o("relative imports works", function() { write("a.js", 'var b = require("./b")') write("b.js", "module.exports = 1") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar b = 1\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar b = 1\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("relative imports works with semicolons", function() { write("a.js", 'var b = require("./b");') write("b.js", "module.exports = 1;") - bundle(ns + "a.js", ns + "out.js") - - o(read("out.js")).equals(";(function() {\nvar b = 1;\n}());") + + o(bundle(ns + "a.js")).equals(";(function() {\nvar b = 1;\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("relative imports works with let", function() { write("a.js", 'let b = require("./b")') write("b.js", "module.exports = 1") - bundle(ns + "a.js", ns + "out.js") - - o(read("out.js")).equals(";(function() {\nlet b = 1\n}());") + + o(bundle(ns + "a.js")).equals(";(function() {\nlet b = 1\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("relative imports works with const", function() { write("a.js", 'const b = require("./b")') write("b.js", "module.exports = 1") - bundle(ns + "a.js", ns + "out.js") - - o(read("out.js")).equals(";(function() {\nconst b = 1\n}());") + + o(bundle(ns + "a.js")).equals(";(function() {\nconst b = 1\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("relative imports works with assignment", function() { write("a.js", 'var a = {}\na.b = require("./b")') write("b.js", "module.exports = 1") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar a = {}\na.b = 1\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar a = {}\na.b = 1\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("relative imports works with reassignment", function() { write("a.js", 'var b = {}\nb = require("./b")') write("b.js", "module.exports = 1") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar b = {}\nb = 1\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar b = {}\nb = 1\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("relative imports removes extra use strict", function() { write("a.js", '"use strict"\nvar b = require("./b")') write("b.js", '"use strict"\nmodule.exports = 1') - bundle(ns + "a.js", ns + "out.js") - - o(read("out.js")).equals(';(function() {\n"use strict"\nvar b = 1\n}());') + + o(bundle(ns + "a.js")).equals(';(function() {\n"use strict"\nvar b = 1\n}());') remove("a.js") remove("b.js") - remove("out.js") }) o("relative imports removes extra use strict using single quotes", function() { write("a.js", "'use strict'\nvar b = require(\"./b\")") write("b.js", "'use strict'\nmodule.exports = 1") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\n'use strict'\nvar b = 1\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\n'use strict'\nvar b = 1\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("relative imports removes extra use strict using mixed quotes", function() { write("a.js", '"use strict"\nvar b = require("./b")') write("b.js", "'use strict'\nmodule.exports = 1") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(';(function() {\n"use strict"\nvar b = 1\n}());') - + o(bundle(ns + "a.js")).equals(';(function() {\n"use strict"\nvar b = 1\n}());') + remove("a.js") remove("b.js") - remove("out.js") }) o("works w/ window", function() { write("a.js", 'window.a = 1\nvar b = require("./b")') write("b.js", "module.exports = function() {return a}") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nwindow.a = 1\nvar b = function() {return a}\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nwindow.a = 1\nvar b = function() {return a}\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("works without assignment", function() { write("a.js", 'require("./b")') write("b.js", "1 + 1") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\n1 + 1\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\n1 + 1\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("works if used fluently", function() { write("a.js", 'var b = require("./b").toString()') write("b.js", "module.exports = []") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar _0 = []\nvar b = _0.toString()\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar _0 = []\nvar b = _0.toString()\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("works if used fluently w/ multiline", function() { write("a.js", 'var b = require("./b")\n\t.toString()') write("b.js", "module.exports = []") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar _0 = []\nvar b = _0\n\t.toString()\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar _0 = []\nvar b = _0\n\t.toString()\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("works if used w/ curry", function() { write("a.js", 'var b = require("./b")()') write("b.js", "module.exports = function() {}") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar _0 = function() {}\nvar b = _0()\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar _0 = function() {}\nvar b = _0()\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("works if used w/ curry w/ multiline", function() { write("a.js", 'var b = require("./b")\n()') write("b.js", "module.exports = function() {}") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar _0 = function() {}\nvar b = _0\n()\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar _0 = function() {}\nvar b = _0\n()\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("works if used fluently in one place and not in another", function() { write("a.js", 'var b = require("./b").toString()\nvar c = require("./c")') write("b.js", "module.exports = []") write("c.js", 'var b = require("./b")\nmodule.exports = function() {return b}') - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar _0 = []\nvar b = _0.toString()\nvar b0 = _0\nvar c = function() {return b0}\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar _0 = []\nvar b = _0.toString()\nvar b0 = _0\nvar c = function() {return b0}\n}());") remove("a.js") remove("b.js") remove("c.js") - remove("out.js") }) o("works if used in sequence", function() { write("a.js", 'var b = require("./b"), c = require("./c")') write("b.js", "module.exports = 1") write("c.js", "var x\nmodule.exports = 2") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar b = 1\nvar x\nvar c = 2\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar b = 1\nvar x\nvar c = 2\n}());") remove("a.js") remove("b.js") remove("c.js") - remove("out.js") }) o("works if assigned to property", function() { write("a.js", 'var x = {}\nx.b = require("./b")\nx.c = require("./c")') write("b.js", "var bb = 1\nmodule.exports = bb") write("c.js", "var cc = 2\nmodule.exports = cc") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar x = {}\nvar bb = 1\nx.b = bb\nvar cc = 2\nx.c = cc\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar x = {}\nvar bb = 1\nx.b = bb\nvar cc = 2\nx.c = cc\n}());") remove("a.js") remove("b.js") remove("c.js") - remove("out.js") }) o("works if assigned to property using bracket notation", function() { write("a.js", 'var x = {}\nx["b"] = require("./b")\nx["c"] = require("./c")') write("b.js", "var bb = 1\nmodule.exports = bb") write("c.js", "var cc = 2\nmodule.exports = cc") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(';(function() {\nvar x = {}\nvar bb = 1\nx["b"] = bb\nvar cc = 2\nx["c"] = cc\n}());') + o(bundle(ns + "a.js")).equals(';(function() {\nvar x = {}\nvar bb = 1\nx["b"] = bb\nvar cc = 2\nx["c"] = cc\n}());') remove("a.js") remove("b.js") remove("c.js") - remove("out.js") }) o("works if collision", function() { write("a.js", 'var b = require("./b")') write("b.js", "var b = 1\nmodule.exports = 2") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar b0 = 1\nvar b = 2\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar b0 = 1\nvar b = 2\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("works if multiple aliases", function() { write("a.js", 'var b = require("./b")\n') write("b.js", 'var b = require("./c")\nb.x = 1\nmodule.exports = b') write("c.js", "var b = {}\nmodule.exports = b") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar b = {}\nb.x = 1\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar b = {}\nb.x = 1\n}());") remove("a.js") remove("b.js") remove("c.js") - remove("out.js") }) o("works if multiple collision", function() { write("a.js", 'var b = require("./b")\nvar c = require("./c")\nvar d = require("./d")') write("b.js", "var a = 1\nmodule.exports = a") write("c.js", "var a = 2\nmodule.exports = a") write("d.js", "var a = 3\nmodule.exports = a") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar a = 1\nvar b = a\nvar a0 = 2\nvar c = a0\nvar a1 = 3\nvar d = a1\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar a = 1\nvar b = a\nvar a0 = 2\nvar c = a0\nvar a1 = 3\nvar d = a1\n}());") remove("a.js") remove("b.js") remove("c.js") remove("d.js") - remove("out.js") }) o("works if included multiple times", function() { write("a.js", "module.exports = 123") write("b.js", 'var a = require("./a").toString()\nmodule.exports = a') write("c.js", 'var a = require("./a").toString()\nvar b = require("./b")') - bundle(ns + "c.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar _0 = 123\nvar a = _0.toString()\nvar a0 = _0.toString()\nvar b = a0\n}());") + o(bundle(ns + "c.js")).equals(";(function() {\nvar _0 = 123\nvar a = _0.toString()\nvar a0 = _0.toString()\nvar b = a0\n}());") remove("a.js") remove("b.js") @@ -291,9 +243,8 @@ o.spec("bundler", function() { write("a.js", "module.exports = 123") write("b.js", 'var a = require("./a").toString()\nmodule.exports = a') write("c.js", 'var b = require("./b")\nvar a = require("./a").toString()') - bundle(ns + "c.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar _0 = 123\nvar a0 = _0.toString()\nvar b = a0\nvar a = _0.toString()\n}());") + o(bundle(ns + "c.js")).equals(";(function() {\nvar _0 = 123\nvar a0 = _0.toString()\nvar b = a0\nvar a = _0.toString()\n}());") remove("a.js") remove("b.js") @@ -304,82 +255,68 @@ o.spec("bundler", function() { write("b.js", 'var d = require("./d")\nmodule.exports = function() {return d + 1}') write("c.js", 'var d = require("./d")\nmodule.exports = function() {return d + 2}') write("d.js", "module.exports = 1") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar d = 1\nvar b = function() {return d + 1}\nvar c = function() {return d + 2}\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar d = 1\nvar b = function() {return d + 1}\nvar c = function() {return d + 2}\n}());") remove("a.js") remove("b.js") remove("c.js") remove("d.js") - remove("out.js") }) o("disambiguates conflicts if imported collides with itself", function() { write("a.js", 'var b = require("./b")') write("b.js", "var b = 1\nmodule.exports = function() {return b}") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar b0 = 1\nvar b = function() {return b0}\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar b0 = 1\nvar b = function() {return b0}\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("disambiguates conflicts if imported collides with something else", function() { write("a.js", 'var a = 1\nvar b = require("./b")') write("b.js", "var a = 2\nmodule.exports = function() {return a}") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar a = 1\nvar a0 = 2\nvar b = function() {return a0}\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar a = 1\nvar a0 = 2\nvar b = function() {return a0}\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("disambiguates conflicts if imported collides with function declaration", function() { write("a.js", 'function a() {}\nvar b = require("./b")') write("b.js", "var a = 2\nmodule.exports = function() {return a}") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nfunction a() {}\nvar a0 = 2\nvar b = function() {return a0}\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nfunction a() {}\nvar a0 = 2\nvar b = function() {return a0}\n}());") remove("a.js") remove("b.js") - remove("out.js") }) o("disambiguates conflicts if imported collides with another module's private", function() { write("a.js", 'var b = require("./b")\nvar c = require("./c")') write("b.js", "var a = 1\nmodule.exports = function() {return a}") write("c.js", "var a = 2\nmodule.exports = function() {return a}") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar a = 1\nvar b = function() {return a}\nvar a0 = 2\nvar c = function() {return a0}\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar a = 1\nvar b = function() {return a}\nvar a0 = 2\nvar c = function() {return a0}\n}());") remove("a.js") remove("b.js") remove("c.js") - remove("out.js") }) o("does not mess up strings", function() { write("a.js", 'var b = require("./b")') write("b.js", 'var b = "b b b \\" b"\nmodule.exports = function() {return b}') - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(';(function() {\nvar b0 = "b b b \\\" b"\nvar b = function() {return b0}\n}());') + o(bundle(ns + "a.js")).equals(';(function() {\nvar b0 = "b b b \\\" b"\nvar b = function() {return b0}\n}());') remove("a.js") remove("b.js") - remove("out.js") }) o("does not mess up properties", function() { write("a.js", 'var b = require("./b")') write("b.js", "var b = {b: 1}\nmodule.exports = function() {return b.b}") - bundle(ns + "a.js", ns + "out.js") - o(read("out.js")).equals(";(function() {\nvar b0 = {b: 1}\nvar b = function() {return b0.b}\n}());") + o(bundle(ns + "a.js")).equals(";(function() {\nvar b0 = {b: 1}\nvar b = function() {return b0.b}\n}());") remove("a.js") remove("b.js") - remove("out.js") }) }) diff --git a/docs/api.md b/docs/api.md index d25c8dec..53b54d9f 100644 --- a/docs/api.md +++ b/docs/api.md @@ -121,48 +121,6 @@ var querystring = m.buildQueryString({a: "1", b: "2"}) --- -#### m.withAttr(attrName, callback) - [docs](withAttr.md) - -```javascript -var state = { - value: "", - setValue: function(v) {state.value = v} -} - -var Component = { - view: function() { - return m("input", { - oninput: m.withAttr("value", state.setValue), - value: state.value, - }) - } -} - -m.mount(document.body, Component) -``` - ---- - -#### m.prop(initial) - [docs](prop.md) - -```javascript -var Component = { - oninit: function(vnode) { - vnode.state.current = m.prop("") - }, - view: function(vnode) { - return m("input", { - oninput: function(ev) { vnode.state.current.set(ev.target.value) }, - value: vnode.state.current.get(), - }) - } -} - -m.mount(document.body, Component) -``` - ---- - #### m.trust(htmlString) - [docs](trust.md) ```javascript diff --git a/docs/autoredraw.md b/docs/autoredraw.md index eb1f093f..6fcc48de 100644 --- a/docs/autoredraw.md +++ b/docs/autoredraw.md @@ -96,13 +96,17 @@ Mithril also does not redraw after lifecycle methods. Parts of the UI may be red If you need to explicitly trigger a redraw within a lifecycle method, you should call `m.redraw()`, which will trigger an asynchronous redraw. ```javascript -var StableComponent = { - oncreate: function(vnode) { - vnode.state.height = vnode.dom.offsetHeight - m.redraw() - }, - view: function() { - return m("div", "This component is " + vnode.state.height + "px tall") +function StableComponent() { + var height = 0 + + return { + oncreate: function(vnode) { + height = vnode.dom.offsetHeight + m.redraw() + }, + view: function() { + return m("div", "This component is " + height + "px tall") + } } } ``` diff --git a/docs/change-log.md b/docs/change-log.md index 3580b8f1..380fd761 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -25,14 +25,19 @@ - API: Assigning to `vnode.state` (as in `vnode.state = ...`) is no longer supported. Instead, an error is thrown if `vnode.state` changes upon the invocation of a lifecycle hook. - API: `m.request` will no longer reject the Promise on server errors (eg. status >= 400) if the caller supplies an `extract` callback. This gives applications more control over handling server responses. - hyperscript: when attributes have a `null` or `undefined` value, they are treated as if they were absent. [#1773](https://github.com/MithrilJS/mithril.js/issues/1773) ([#2174](https://github.com/MithrilJS/mithril.js/pull/2174)) +- API: `m.request` errors no longer copy response fields to the error, but instead assign the parsed JSON response to `error.response` and the HTTP status code `error.code`. - hyperscript: when an attribute is defined on both the first and second argument (as a CSS selector and an `attrs` field, respectively), the latter takes precedence, except for `class` attributes that are still added together. [#2172](https://github.com/MithrilJS/mithril.js/issues/2172) ([#2174](https://github.com/MithrilJS/mithril.js/pull/2174)) - stream: when a stream conditionally returns HALT, dependant stream will also end ([#2200](https://github.com/MithrilJS/mithril.js/pull/2200)) - render: remove some redundancy within the component initialization code ([#2213](https://github.com/MithrilJS/mithril.js/pull/2213)) - render: Align custom elements to work like normal elements, minus all the HTML-specific magic. ([#2221](https://github.com/MithrilJS/mithril.js/pull/2221)) - render: simplify component removal ([#2214](https://github.com/MithrilJS/mithril.js/pull/2214)) +- cast className using toString ([#2309](https://github.com/MithrilJS/mithril.js/pull/2309)) +- render: call attrs' hooks first, with express exception of `onbeforeupdate` to allow attrs to block components from even diffing ([#2297](https://github.com/MithrilJS/mithril.js/pull/2297)) +- API: `m.withAttr` removed. ([#2317](https://github.com/MithrilJS/mithril.js/pull/2317)) #### News +- Mithril now only officially supports IE11, Firefox ESR, and the last two versions of Chrome/FF/Edge/Safari. ([#2296](https://github.com/MithrilJS/mithril.js/pull/2296)) - API: Introduction of `m.redraw.sync()` ([#1592](https://github.com/MithrilJS/mithril.js/pull/1592)) - API: Event handlers may also be objects with `handleEvent` methods ([#1949](https://github.com/MithrilJS/mithril.js/pull/1949), [#2222](https://github.com/MithrilJS/mithril.js/pull/2222)). - API: `m.route.link` accepts an optional `options` object ([#1930](https://github.com/MithrilJS/mithril.js/pull/1930)) @@ -43,7 +48,13 @@ - API: add support for raw SVG in `m.trust()` string ([#2097](https://github.com/MithrilJS/mithril.js/pull/2097)) - render/core: remove the DOM nodes recycling pool ([#2122](https://github.com/MithrilJS/mithril.js/pull/2122)) - render/core: revamp the core diff engine, and introduce a longest-increasing-subsequence-based logic to minimize DOM operations when re-ordering keyed nodes. -- API: Introduction of `m.prop()` ([#2268](https://github.com/MithrilJS/mithril.js/pull/2268)) +- docs: Emphasize Closure Components for stateful components, use them for all stateful component examples. +- stream: Add `stream.lift` as a user-friendly alternative to `merge -> map` or `combine` [#1944](https://github.com/MithrilJS/mithril.js/issues/1944) +- API: ES module bundles are now available for `mithril` and `mithril/stream` ([#2194](https://github.com/MithrilJS/mithril.js/pull/2194) [@porsager](https://github.com/porsager)). + - All of the `m.*` properties from `mithril` are re-exported as named exports in addition to being attached to `m`. + - `m()` itself from `mithril` is exported as the default export. + - `mithril/stream`'s primary export is exported as the default export. +- fragments: allow same attrs/children overloading logic as hyperscript ([#2328](https://github.com/MithrilJS/mithril.js/pull/2328)) #### Bug fixes @@ -59,18 +70,30 @@ - render/events: `Object.prototype` properties can no longer interfere with event listener calls. - render/events: Event handlers, when set to literally `undefined` (or any non-function), are now correctly removed. - render/hooks: fixed an ommission that caused `oninit` to be called unnecessarily in some cases [#1992](https://github.com/MithrilJS/mithril.js/issues/1992) -- docs: tweaks: ([#2104](https://github.com/MithrilJS/mithril.js/pull/2104) [@mikeyb](https://github.com/mikeyb), [#2205](https://github.com/MithrilJS/mithril.js/pull/2205), [@cavemansspa](https://github.com/cavemansspa), [#2265](https://github.com/MithrilJS/mithril.js/pull/2265), [@isiahmeadows](https://github.com/isiahmeadows)) +- docs: tweaks: ([#2104](https://github.com/MithrilJS/mithril.js/pull/2104) [@mikeyb](https://github.com/mikeyb), [#2205](https://github.com/MithrilJS/mithril.js/pull/2205), [@cavemansspa](https://github.com/cavemansspa), [#2250](https://github.com/MithrilJS/mithril.js/pull/2250) [@isiahmeadows](https://github.com/isiahmeadows), [#2265](https://github.com/MithrilJS/mithril.js/pull/2265), [@isiahmeadows](https://github.com/isiahmeadows)) - render/core: avoid touching `Object.prototype.__proto__` setter with `key: "__proto__"` in certain situations ([#2251](https://github.com/MithrilJS/mithril.js/pull/2251)) - render/core: Vnodes stored in the dom node supplied to `m.render()` are now normalized [#2266](https://github.com/MithrilJS/mithril.js/pull/2266) +- render/core: CSS vars can now be specified in `{style}` attributes ([#2192](https://github.com/MithrilJS/mithril.js/pull/2192) [@barneycarroll](https://github.com/barneycarroll)), ([#2311](https://github.com/MithrilJS/mithril.js/pull/2311) [@porsager](https://github.com/porsager)), ([#2312](https://github.com/MithrilJS/mithril.js/pull/2312) [@isiahmeadows](https://github.com/isiahmeadows)) +- request: don't modify params, call `extract`/`serialize`/`deserialize` with correct `this` value ([#2288](https://github.com/MithrilJS/mithril.js/pull/2288)) --- -### v1.1.7 -- Stream references no longer magically coerce to their underlying values ([#2150](https://github.com/MithrilJS/mithril.js/pull/2150), breaking change: `mithril-stream@2.0.0`) +### v1.2.0 + +#### News + - Promise polyfill implementation separated from polyfilling logic. - `PromisePolyfill` is now available on the exported/global `m`. +#### Bug fixes + +- core: Workaround for [Internet Explorer bug](https://www.tjvantoll.com/2013/08/30/bugs-with-document-activeelement-in-internet-explorer/) when running in an iframe + +#### Note + +- Stream references no longer magically coerce to their underlying values ([#2150](https://github.com/MithrilJS/mithril.js/pull/2150), stream breaking change: `mithril-stream@2.0.0`) + --- ### v1.1.6 @@ -112,6 +135,10 @@ - Fix IE bug where active element is null causing render function to throw error ([#1943](https://github.com/MithrilJS/mithril.js/pull/1943), [@JacksonJN](https://github.com/JacksonJN)) +#### Ospec improvements: + +- Log using util.inspect to show object content instead of "[object Object]" ([#1661](https://github.com/MithrilJS/mithril.js/issues/1661), [@porsager](https://github.com/porsager)) + --- ### v1.1.3 diff --git a/docs/components.md b/docs/components.md index 89ccf2b0..3dccc5c1 100644 --- a/docs/components.md +++ b/docs/components.md @@ -2,23 +2,29 @@ - [Structure](#structure) - [Lifecycle methods](#lifecycle-methods) -- [Syntactic variants](#syntactic-variants) +- [Passing data to components](#passing-data-to-components) - [State](#state) + - [Closure component state](#closure-component-state) + - [POJO component state](#pojo-component-state) +- [ES6 Classes](#es6-classes) + - [Class component state](#class-component-state) - [Avoid anti-patterns](#avoid-anti-patterns) ### Structure Components are a mechanism to encapsulate parts of a view to make code easier to organize and/or reuse. -Any Javascript object that has a view method is a Mithril component. Components can be consumed via the [`m()`](hyperscript.md) utility: +Any Javascript object that has a `view` method is a Mithril component. Components can be consumed via the [`m()`](hyperscript.md) utility: ```javascript +// define your component var Example = { - view: function() { + view: function(vnode) { return m("div", "Hello") } } +// consume your component m(Example) // equivalent HTML @@ -27,31 +33,9 @@ m(Example) --- -### Passing data to components - -Data can be passed to component instances by passing an `attrs` object as the second parameter in the hyperscript function: - -```javascript -m(Example, {name: "Floyd"}) -``` - -This data can be accessed in the component's view or lifecycle methods via the `vnode.attrs`: - -```javascript -var Example = { - view: function (vnode) { - return m("div", "Hello, " + vnode.attrs.name) - } -} -``` - -NOTE: Lifecycle methods can also be provided via the `attrs` object, so you should avoid using the lifecycle method names for your own callbacks as they would also be invoked by Mithril. Use lifecycle methods in `attrs` only when you specifically wish to create lifecycle hooks. - ---- - ### Lifecycle methods -Components can have the same [lifecycle methods](lifecycle-methods.md) as virtual DOM nodes: `oninit`, `oncreate`, `onupdate`, `onbeforeremove`, `onremove` and `onbeforeupdate`. +Components can have the same [lifecycle methods](lifecycle-methods.md) as virtual DOM nodes. Note that `vnode` is passed as an argument to each lifecycle method, as well as to `view` (with the _previous_ vnode passed additionally to `onbeforeupdate`): ```javascript var ComponentWithHooks = { @@ -61,7 +45,7 @@ var ComponentWithHooks = { oncreate: function(vnode) { console.log("DOM created") }, - onbeforeupdate: function(vnode, old) { + onbeforeupdate: function(newVnode, oldVnode) { return true }, onupdate: function(vnode) { @@ -86,7 +70,7 @@ var ComponentWithHooks = { Like other types of virtual DOM nodes, components may have additional lifecycle methods defined when consumed as vnode types. ```javascript -function initialize() { +function initialize(vnode) { console.log("initialized as vnode") } @@ -101,104 +85,25 @@ To learn more about lifecycle methods, [see the lifecycle methods page](lifecycl --- -### Syntactic variants +### Passing data to components -#### ES6 classes - -Components can also be written using ES6 class syntax: +Data can be passed to component instances by passing an `attrs` object as the second parameter in the hyperscript function: ```javascript -class ES6ClassComponent { - constructor(vnode) { - // vnode.state is undefined at this point - this.kind = "ES6 class" - } - view() { - return m("div", `Hello from an ${this.kind}`) - } - oncreate() { - console.log(`A ${this.kind} component was created`) +m(Example, {name: "Floyd"}) +``` + +This data can be accessed in the component's view or lifecycle methods via the `vnode.attrs`: + +```javascript +var Example = { + view: function (vnode) { + return m("div", "Hello, " + vnode.attrs.name) } } ``` -Component classes must define a `view()` method, detected via `.prototype.view`, to get the tree to render. - -They can be consumed in the same way regular components can. - -```javascript -// EXAMPLE: via m.render -m.render(document.body, m(ES6ClassComponent)) - -// EXAMPLE: via m.mount -m.mount(document.body, ES6ClassComponent) - -// EXAMPLE: via m.route -m.route(document.body, "/", { - "/": ES6ClassComponent -}) - -// EXAMPLE: component composition -class AnotherES6ClassComponent { - view() { - return m("main", [ - m(ES6ClassComponent) - ]) - } -} -``` - -#### Closure components - -Functionally minded developers may prefer using the "closure component" syntax: - -```javascript -function closureComponent(vnode) { - // vnode.state is undefined at this point - var kind = "closure component" - - return { - view: function() { - return m("div", "Hello from a " + kind) - }, - oncreate: function() { - console.log("We've created a " + kind) - } - } -} -``` - -The returned object must hold a `view` function, used to get the tree to render. - -They can be consumed in the same way regular components can. - -```javascript -// EXAMPLE: via m.render -m.render(document.body, m(closureComponent)) - -// EXAMPLE: via m.mount -m.mount(document.body, closureComponent) - -// EXAMPLE: via m.route -m.route(document.body, "/", { - "/": closureComponent -}) - -// EXAMPLE: component composition -function anotherClosureComponent() { - return { - view: function() { - return m("main", [ - m(closureComponent) - ]) - } - } -} -``` - -#### Mixing component kinds - -Components can be freely mixed. A Class component can have closure or POJO components as children, etc... +NOTE: Lifecycle methods can also be defined in the `attrs` object, so you should avoid using their names for your own callbacks as they would also be invoked by Mithril itself. Use them in `attrs` only when you specifically wish to use them as lifecycle methods. --- @@ -206,13 +111,87 @@ Components can be freely mixed. A Class component can have closure or POJO compo Like all virtual DOM nodes, component vnodes can have state. Component state is useful for supporting object-oriented architectures, for encapsulation and for separation of concerns. -The state of a component can be accessed three ways: as a blueprint at initialization, via `vnode.state` and via the `this` keyword in component methods. +Note that unlike many other frameworks, mutating component state does *not* trigger [redraws](autoredraw.md) or DOM updates. Instead, redraws are performed when event handlers fire, when HTTP requests made by [m.request](request.md) complete or when the browser navigates to different routes. Mithril's component state mechanisms simply exist as a convenience for applications. + +If a state change occurs that is not as a result of any of the above conditions (e.g. after a `setTimeout`), then you can use `m.redraw()` to trigger a redraw manually. + +#### Closure Component State + +In the above examples, each component is defined as a POJO (Plain Old Javascript Object), which is used by Mithril internally as the prototype for that component's instances. It's possible to use component state with a POJO (as we'll discuss below), but it's not the cleanest or simplest approach. For that we'll use a **_closure component_**, which is simply a wrapper function which _returns_ a POJO component instance, which in turn carries its own, closed-over scope. + +With a closure component, state can simply be maintained by variables that are declared within the outer function: + +```javascript +function ComponentWithState(initialVnode) { + // Component state variable, unique to each instance + var count = 0 + + // POJO component instance: any object with a + // view function which returns a vnode + return { + oninit: function(vnode){ + console.log("init a closure component") + }, + view: function(vnode) { + return m("div", + m("p", "Count: " + count), + m("button", { + onclick: function() { + count += 1 + } + }, "Increment count") + ) + } + } +} +``` + +Any functions declared within the closure also have access to its state variables. + +```javascript +function ComponentWithState(initialVnode) { + var count = 0 + + function increment() { + count += 1 + } + + function decrement() { + count -= 1 + } + + return { + view: function(vnode) { + return m("div", + m("p", "Count: " + count), + m("button", { + onclick: increment + }, "Increment"), + m("button", { + onclick: decrement + }, "Decrement") + ) + } + } +} +``` + +Closure components are consumed in the same way as POJOs, e.g. `m(ComponentWithState, { passedData: ... })`. + +A big advantage of closure components is that we don't need to worry about binding `this` when attaching event handler callbacks. In fact `this` is never used at all and we never have to think about `this` context ambiguities. + + +--- + +#### POJO Component State + +It is generally recommended that you use closures for managing component state. If, however, you have reason to manage state in a POJO, the state of a component can be accessed in three ways: as a blueprint at initialization, via `vnode.state` and via the `this` keyword in component methods. #### At initialization -For POJO components, the component object is the prototype of each component instance, so any property defined on the component object will be accessible as a property of `vnode.state`. This allows simple state initialization. +For POJO components, the component object is the prototype of each component instance, so any property defined on the component object will be accessible as a property of `vnode.state`. This allows simple "blueprint" state initialization. -In the example below, `data` is a property of the `ComponentWithInitialState` component's state object. +In the example below, `data` becomes a property of the `ComponentWithInitialState` component's `vnode.state` object. ```javascript var ComponentWithInitialState = { @@ -228,13 +207,9 @@ m(ComponentWithInitialState) //
Initial content
``` -For class components, the state is an instance of the class, set right after the constructor is called. - -For closure components, the state is the object returned by the closure, set right after the closure returns. The state object is mostly redundant for closure components (since variables defined in the closure scope can be used instead). - #### Via vnode.state -State can also be accessed via the `vnode.state` property, which is available to all lifecycle methods as well as the `view` method of a component. +As you can see, state can also be accessed via the `vnode.state` property, which is available to all lifecycle methods as well as the `view` method of a component. ```javascript var ComponentWithDynamicState = { @@ -274,6 +249,90 @@ m(ComponentUsingThis, {text: "Hello"}) Be aware that when using ES5 functions, the value of `this` in nested anonymous functions is not the component instance. There are two recommended ways to get around this Javascript limitation, use ES6 arrow functions, or if ES6 is not available, use `vnode.state`. +--- + +### ES6 classes + +If it suits your needs (like in object-oriented projects), components can also be written using ES6 class syntax: + +```javascript +class ES6ClassComponent { + constructor(vnode) { + this.kind = "ES6 class" + } + view() { + return m("div", `Hello from an ${this.kind}`) + } + oncreate() { + console.log(`A ${this.kind} component was created`) + } +} +``` + +Component classes must define a `view()` method, detected via `.prototype.view`, to get the tree to render. + +They can be consumed in the same way regular components can. + +```javascript +// EXAMPLE: via m.render +m.render(document.body, m(ES6ClassComponent)) + +// EXAMPLE: via m.mount +m.mount(document.body, ES6ClassComponent) + +// EXAMPLE: via m.route +m.route(document.body, "/", { + "/": ES6ClassComponent +}) + +// EXAMPLE: component composition +class AnotherES6ClassComponent { + view() { + return m("main", [ + m(ES6ClassComponent) + ]) + } +} +``` + +#### Class Component State + +With classes, state can be managed by class instance properties and methods, and accessed via `this`: + +```javascript +class ComponentWithState { + constructor(vnode) { + this.count = 0 + } + increment() { + this.count += 1 + } + decrement() { + this.count -= 1 + } + view() { + return m("div", + m("p", "Count: " + count), + m("button", { + onclick: () => {this.increment()} + }, "Increment"), + m("button", { + onclick: () => {this.decrement()} + }, "Decrement") + ) + } +} +``` + +Note that we must wrap the event callbacks in arrow functions so that the `this` context is preserved correctly. + +--- + +### Mixing component kinds + +Components can be freely mixed. A class component can have closure or POJO components as children, etc... + + --- ### Avoid anti-patterns @@ -306,8 +365,14 @@ var Login = { login: function() {/*...*/}, view: function() { return m(".login", [ - m("input[type=text]", {oninput: m.withAttr("value", this.setUsername.bind(this)), value: this.username}), - m("input[type=password]", {oninput: m.withAttr("value", this.setPassword.bind(this)), value: this.password}), + m("input[type=text]", { + oninput: function (e) { this.setUsername(e.target.value) }, + value: this.username, + }), + m("input[type=password]", { + oninput: function (e) { this.setPassword(e.target.value) }, + value: this.password, + }), m("button", {disabled: !this.canSubmit(), onclick: this.login}, "Login"), ]) } @@ -351,9 +416,18 @@ var Auth = require("../models/Auth") var Login = { view: function() { return m(".login", [ - m("input[type=text]", {oninput: m.withAttr("value", Auth.setUsername), value: Auth.username}), - m("input[type=password]", {oninput: m.withAttr("value", Auth.setPassword), value: Auth.password}), - m("button", {disabled: !Auth.canSubmit(), onclick: Auth.login}, "Login"), + m("input[type=text]", { + oninput: function (e) { Auth.setUsername(e.target.value) }, + value: Auth.username + }), + m("input[type=password]", { + oninput: function (e) { Auth.setPassword(e.target.value) }, + value: Auth.password + }), + m("button", { + disabled: !Auth.canSubmit(), + onclick: Auth.login + }, "Login") ]) } } @@ -363,6 +437,70 @@ This way, the `Auth` module is now the source of truth for auth-related state, a As a bonus, notice that we no longer need to use `.bind` to keep a reference to the state for the component's event handlers. +#### Don't forward `vnode.attrs` itself to other vnodes + +Sometimes, you might want to keep an interface flexible and your implementation simpler by forwarding attributes to a particular child component or element, in this case [Bootstrap's modal](https://getbootstrap.com/docs/4.1/components/modal/). It might be tempting to forward a vnode's attributes like this: + +```javascript +// AVOID +var Modal = { + // ... + view: function(vnode) { + return m(".modal[tabindex=-1][role=dialog]", vnode.attrs, [ + // forwarding `vnode.attrs` here ^ + // ... + ]) + } +} +``` + +If you do it like above, you could run into issues when using it: + +```js +var MyModal = { + view: function() { + return m(Modal, { + // This toggles it twice, so it doesn't show + onupdate: function(vnode) { + if (toggle) $(vnode.dom).modal("toggle") + } + }, [ + // ... + ]) + } +} +``` + +Instead, you should forward *single* attributes into vnodes: + +```js +// PREFER +var Modal = { + // ... + view: function(vnode) { + return m(".modal[tabindex=-1][role=dialog]", vnode.attrs.attrs, [ + // forwarding `attrs:` here ^ + // ... + ]) + } +} + +// Example +var MyModal = { + view: function() { + return m(Modal, { + attrs: { + // This toggles it once + onupdate: function(vnode) { + if (toggle) $(vnode.dom).modal("toggle") + } + }, + // ... + }) + } +} +``` + #### Don't manipulate `children` If a component is opinionated in how it applies attributes or children, you should switch to using custom attributes. diff --git a/docs/contributing.md b/docs/contributing.md index 4e672470..febd682c 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -79,17 +79,15 @@ This simplifies the workflow for bug fixes, which means they can be fixed faster -## Why doesn't the Mithril codebase use ES6 via Babel? Would a PR to upgrade be welcome? +## Why doesn't the Mithril codebase use ES6 via Babel or Bublé? Would a PR to upgrade be welcome? -Being able to run Mithril raw source code in IE is a requirement for all browser-related modules in this repo. - -In addition, ES6 features are usually less performant than equivalent ES5 code, and transpiled code is bulkier. +Being able to run Mithril's raw source code in all supported browsers is a requirement for all browser-related modules in this repo. In addition, transpiled code is generally much bulkier. ## Why doesn't the Mithril codebase use trailing semi-colons? Would a PR to add them be welcome? -I don't use them. Adding them means the semi-colon usage in the codebase will eventually become inconsistent. +I don't use them. Adding them means the semi-colon usage in the codebase will eventually become inconsistent. Besides, [we aren't the only one who've decided to drop the semicolon](https://standardjs.com/#who-uses-javascript-standard-style). (We don't use Standard, though.) diff --git a/docs/fragment.md b/docs/fragment.md index d3bddc7a..5a914165 100644 --- a/docs/fragment.md +++ b/docs/fragment.md @@ -37,9 +37,9 @@ Generates a fragment [vnode](vnodes.md) Argument | Type | Required | Description ----------- | --------------------------------------------------- | -------- | --- -`attrs` | `Object` | Yes | A map of attributes -`children` | `Array` | Yes | A list of vnodes -**returns** | `Vnode` | | A fragment [vnode](vnodes.md) +`attrs` | `Object` | No | HTML attributes or element properties +`children` | `Array|String|Number|Boolean` | No | Child [vnodes](vnodes.md#structure). Can be written as [splat arguments](signatures.md#splats) +**returns** | `Vnode` | | A fragment [vnode](vnodes.md#structure) [How to read signatures](signatures.md) @@ -49,14 +49,14 @@ Argument | Type | Required | D `m.fragment()` creates a [fragment vnode](vnodes.md) with attributes. It is meant for advanced use cases involving [keys](keys.md) or [lifecyle methods](lifecycle-methods.md). -A fragment vnode represents a list of DOM elements. If you want a regular element vnode that represents only one DOM element, you should use [`m()`](hyperscript.md) instead. +A fragment vnode represents a list of DOM elements. If you want a regular element vnode that represents only one DOM element and don't require keyed logic, you should use [`m()`](hyperscript.md) instead. -Normally you can use simple arrays instead to denote a list of nodes: +Normally you can use simple arrays or splats instead to denote a list of nodes: ```javascript var groupVisible = true -m("ul", [ +m("ul", m("li", "child 1"), m("li", "child 2"), groupVisible ? [ @@ -64,7 +64,7 @@ m("ul", [ m("li", "child 3"), m("li", "child 4"), ] : null -]) +) ``` However, Javascript arrays cannot be keyed or hold lifecycle methods. One option would be to create a wrapper element to host the key or lifecycle method, but sometimes it is not desirable to have an extra element (for example in complex table structures). In those cases, a fragment vnode can be used instead. diff --git a/docs/hyperscript.md b/docs/hyperscript.md index 3d66c54b..4adb8516 100644 --- a/docs/hyperscript.md +++ b/docs/hyperscript.md @@ -5,7 +5,7 @@ - [How it works](#how-it-works) - [Flexibility](#flexibility) - [CSS selectors](#css-selectors) -- [Attributes passed as the second argument](attributes-passed-as-the-second-argument) +- [Attributes passed as the second argument](#attributes-passed-as-the-second-argument) - [DOM attributes](#dom-attributes) - [Style attribute](#style-attribute) - [Events](#events) @@ -40,12 +40,12 @@ You can also [use HTML syntax](https://babeljs.io/repl/#?code=%2F**%20%40jsx%20m ### Signature -`vnode = m(selector, attributes, children)` +`vnode = m(selector, attrs, children)` Argument | Type | Required | Description ------------ | ------------------------------------------ | -------- | --- `selector` | `String|Object` | Yes | A CSS selector or a [component](components.md) -`attributes` | `Object` | No | HTML attributes or element properties +`attrs` | `Object` | No | HTML attributes or element properties `children` | `Array|String|Number|Boolean` | No | Child [vnodes](vnodes.md#structure). Can be written as [splat arguments](signatures.md#splats) **returns** | `Vnode` | | A [vnode](vnodes.md#structure) @@ -55,7 +55,7 @@ Argument | Type | Required | Descripti ### How it works -Mithril provides a hyperscript function `m()`, which allows expressing any HTML structure using javascript syntax. It accepts a `selector` string (required), an `attributes` object (optional) and a `children` array (optional). +Mithril provides a hyperscript function `m()`, which allows expressing any HTML structure using javascript syntax. It accepts a `selector` string (required), an `attrs` object (optional) and a `children` array (optional). ```javascript m("div", {id: "box"}, "hello") @@ -262,6 +262,8 @@ m("div[style=background:red]") Using a string as a `style` would overwrite all inline styles in the element if it is redrawn, and not only CSS rules whose values have changed. +You can use both hyphenated CSS property names (like `background-color`) and camel cased DOM `style` property names (like `backgroundColor`). You can also define [CSS custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables), if your browser supports them. + Mithril does not attempt to add units to number values. It simply stringifies them. --- @@ -539,7 +541,7 @@ Instead, prefer using Javascript expressions such as the ternary operator and Ar ```javascript // PREFER var BetterListComponent = { - view: function() { + view: function(vnode) { return m("ul", vnode.attrs.items.map(function(item) { return m("li", item) })) diff --git a/docs/index.md b/docs/index.md index 97dd2b13..979212db 100644 --- a/docs/index.md +++ b/docs/index.md @@ -44,7 +44,7 @@ Mithril is used by companies like Vimeo and Nike, and open source platforms like If you are an experienced developer and want to know how Mithril compares to other frameworks, see the [framework comparison](framework-comparison.md) page. -Mithril supports browsers all the way back to IE9, no polyfills required. +Mithril supports IE11, Firefox ESR, and the last two versions of Firefox, Edge, Safari, and Chrome. No polyfills required. --- diff --git a/docs/integrating-libs.md b/docs/integrating-libs.md index cf0753de..2a45a196 100644 --- a/docs/integrating-libs.md +++ b/docs/integrating-libs.md @@ -2,56 +2,123 @@ Integration with third party libraries or vanilla javascript code can be achieved via [lifecycle methods](lifecycle-methods.md). -## Example +## noUiSlider Example ```javascript -var FullCalendar = { +/** NoUiSlider wrapper component */ +function Slider() { + var slider - oncreate: function (vnode) { - console.log('FullCalendar::oncreate') - $(vnode.dom).fullCalendar({ - // put your initial options and callbacks here - }) - - Object.assign(vnode.attrs.parentState, {fullCalendarEl: vnode.dom}) - }, - - // Consider that the lib will modify this parent element in the DOM (e.g. add dependent class attribute and values). - // As long as you return the same view results here, mithril will not - // overwrite the actual DOM because it's always comparing old and new VDOM - // before applying DOM updates. - view: function (vnode) { - return m('div') - }, - - onbeforeremove: function (vnode) { - // Run any destroy / cleanup methods here. - //E.g. $(vnode.state.fullCalendarEl).fullCalendar('destroy') + return { + oncreate: function(vnode) { + // Initialize 3rd party lib here + slider = noUiSlider.create(vnode.dom, { + start: 0, + range: {min: 0, max: 100} + }) + slider.on('update', function(values) { + vnode.attrs.onChange(values[0]) + m.redraw() + }) + }, + onremove: function() { + // Cleanup 3rd party lib on removal + slider.destroy() + }, + view: function() { + return m('div') + } } } -m.mount(document.body, { - view: function (vnode) { - return [ - m('h1', 'Calendar'), - m(FullCalendar, {parentState: vnode.state}), - m('button', {onclick: prev}, 'Mithril Button -'), - m('button', {onclick: next}, 'Mithril Button +') +/** Demo app component */ +function Demo() { + var showSlider = false + var value = 0 - ] - - function next() { - $(vnode.state.fullCalendarEl).fullCalendar('next') + return { + view: function() { + return m('.app', + m('p', + m('button', + { + type: 'button', + onclick: function() { + showSlider = !showSlider + } + }, + showSlider ? "Destroy Slider" : "Create Slider" + ) + ), + showSlider && m(Slider, { + onChange: function(v) { + value = v + } + }), + m('p', value) + ) } - - function prev() { - $(vnode.state.fullCalendarEl).fullCalendar('prev') - } - } +} -}) - +m.mount(document.body, Demo) ``` -Running example [flems: FullCalendar](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvAHigjQGsACAJxigBeADog4xAJ6w4hGDGKjuhfmBEgSxAA5xEAel3UAJmgBWcfNSi0ArobBQM-C7Sy6MJjAA9d7AEZxdMGsoKGoMWDRDR10AZnwAdnwABkDg0PCmKN58LA4LODhRAD4QAF8KdGxcRAIzShp6RmYagDdHbgAxNIBhDMj2wW5gYTQR4WJ6an4MRkRuILRqYgh6bgAKFrRaQxgASiGxhWI6NDhaWHwrAHM1gHIukN6oft5EREnpxlvdw-GAEg2Wx2+EMLl2+CCjz6WTWw1GR3G+m4mmsxG4EhsvG4HAgy3C3FommW9Dg3AwkW4YRCvgw1E4pNk-F+xFKP1G8PGAHlfCYYEt8BgChArmhAdsYALiMReOZNI4mMQAMrEGYwChDSFQJ6ZRwAUSgc024pBLlZh3KY3hLQgMAA7nMFksVmh1kadvs4eNxvxiNZeC6sHdDBAWt9zRRLeN6L4YGBaPx+FhaC0YA7rItiS6xe6DhziEiAErpsloCTcHbiXi0Mu6SmwcnWTTcHDEQjbBkwJzM-QAt0S8SqiE9aF6qDgzXal5B+DS6th+GlEaL9lYHI2BhrUHUaw4Bj4XzbCTqz3Ea12tMZ52uoF7XNe6XyP0u5DM8aB26EACMt3Vt0nWW+CM8zfNYHi1EdeGPOV+AYZVVUNG98AHRhWSA+8QNuXxUQmNAfzvBEjkmdg6TmTR+BaV8WV-ABZXFlGgbgACFsNWABaQDKPfLCpXoPCT3QnDLAgEjuDQGBPAUYCqO4W5aNbXgGOYniXQAannZkAF1IyOR1M1E8TiDWD1KN7RDkIlCcIP1cdhwiGFbjEiT1KOZdmV0q8yJgFojPw+9TONcyhyhOzRxs4KdV4O5PNDNl71chdLVZMoKhATAcDwfIECoE4mmIPAyg0qh2C4BAUEqdKalyeToHqP1yBqDRtD0XR000TgrmcVwqvoqAAAFP3wAaAFZdG6hSoHwOoqEkTRqhAOpynKuak13PKqDqvBGp0fRWvazrRpcBVeoAJkGgBOfBjoO1bJqykAZrmhaUrSx6AEdrE7CRat4er1ClJqdrQNqOroVwTHez7eriU7P10YNxF0cGPt4CRbvqB68Cepa8E1KkIu+36tua3aQZcVIQjxl4oYSZI4YgBHcYgtHpokWbMYQUoNNKIA) +[Live Demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvEAXwvW10QICsEqdBk2J4A9ACoJAAgBytAKoQAylAgATGACdpAdy0YADoe3S6WQ-RHSJYgDpowAVzTViEetNUbtACgCU0sAO0tIAbhg6cGqaWg4h0lowxE5aaEEJofTUSRiMiNLOru70vmFotJqBwemhdWJi0gCSaBDuGGoAXjDSAMxa6tKGkcQAntJqAEbShNowmXXRPjoAvNIVSt6x+DkweTBlFZr46rRYFBm1dYvEIwUADBQL1wZoAOYwBcBYEGgPF1gMAAPAoARnu91Yz2krH80KW21KAHInIZ1PskRcim4PGgyh0nPBqtDQuVKjB8HliFo4Ph6ABhQgYd4HCJQQlwZD3AC6cKu12kWHwSXUBl0AWhsIW7AW9CSWFoYU+hRcONKxP5oQa0npsGZqL6AyGI3GU2knnlio68Ji2hO8GptFGEv5Mv5YQgMF0BWxJTxGoFiWSqXSWF8SPUEDCSL51yhtXj8YckhkABEYArpEZDGYzpY0NZbA5fbjpOmFQFLqFYMRpHBCLRdFtTGswB04PNajXwgSemt7vFakkUmkq3UPV6faq-ZWaoHhyHBeHKcZMSSl0jDGvNQKw0jJk5iMR6NvA4G52fA2MTAV94fj2hT5eBdk1NQANZT4q42fry-1xtm1WaQAEIAKbW04h3S942fOo3Tg0JwKA6QAH5pDsEB0zgR1xiAzDpAKTD6VyRgvEgzC-2kWMz38J5oLrBsIOWaQADJWKXICLgvS8GSZFkvzVPEwgDRC2UJaQ1jCKjYLPWF6MvPctwucSYBo651JhBJE0HIUFRcYhfFOagnBwBh8EmSpRguctaD5NgOBATAcDwHY4AEGh6EYZgeDYbkqDUNB3wQFBOBcngKicCAEW0SgQFScgeBIYhDDgRAGhcQx3zeHYzjESLosggABUEACZ8FBfB7jESMcK0CAD0YfLaCimKtHwfg4uvbgQDgHIIEMUR2DCnqCratyPISvBktS9KxEy7LcqwZrWuKsqKqqmroupBrDxgFbCuWCautGEw8Bw0ZYAcka8B+YhCHq8gqCmpKj1mjK0CynLzDEO6HugEqNoANl+tp-qgDqPO687+sGvzWCAA) + +## Bootstrap FullCalandar Example + +```javascript +/** FullCalendar wrapper component */ +var FullCalendar = { + oncreate: function (vnode) { + console.log('FullCalndar::oncreate') + $(vnode.dom).fullCalendar({ + // put your initial options and callbacks here + }) + }, + onremove: function (vnode) { + // Run any destroy / cleanup methods here. + $(vnode.dom).fullCalendar('destroy') + }, + view: function (vnode) { + return m('div') + } +} + +/** Demo app component */ +function Demo() { + var fullCalendarEl + + function next() { + $(fullCalendarEl).fullCalendar('next') + } + + function prev() { + $(fullCalendarEl).fullCalendar('prev') + } + + return { + view: function (vnode) { + return [ + m('h1', 'Calendar'), + m(FullCalendar, { + oncreate: function(vnode) { + fullCalendarEl = vnode.dom + } + }), + m('button', { + onclick: prev + }, 'Mithril Button -'), + m('button', { + onclick: next + }, 'Mithril Button +') + ] + } + } +} + +m.mount(document.body, Demo) +``` + +[Live Demo](https://flems.io/#0=N4IgZglgNgpgziAXAbVAOwIYFsZJAOgAsAXLKEAGhAGMB7NYmBvAHigjQGsACAJxigBeADog4xAJ6w4hGDGKjuhfmBEgSxAA5xEAel3UAJmgBWcfNSi0ArobBQM-C7Sy6MJjAA9d7AEZxdMGsoKGoMWDRDR10AZnwAdnwABkDg0PCmKN58LA4LODhRAD4QAF8KdGxcRAIzShp6RmYa3QAqVu4AMTSAYQzIx24Ad14MTU0YXm46LE16JmJuVt1hNAA3Qe6Qvois7kFuYFXhYnpqfgxGRG4gtGpiCHpuAAo1tFpDGABKQ+OFYjoaDgtFg+CsAHNngByLZQHYDXiIRBnC6MKFfP4nAAkr3en3whhcX3wQW2-SyzyOaBONOI+m4mmsiwkNimHAgD3C3Fomge9Dg3AwkWm4Sgvgw1E4Atk-ExxFKGOp8oof3o-CwtDWMGut3ujzQLzeH2+vyVJ3pACVrAahRJuJ9xLxaHbdNNYELrJpuDhiIQPtLJjB8HKcUb8YSsMTSXDyY5oQ7iE6JOi-uU-msIDAhjrrXqnrjjT8qbT+MRrLwDVh4xA1imlaVVg3qWg2h0ACIwDWC8bTFxzNALJYrNC6vkGjsa55F1bcbgbKbRnaZRwAUSgxwNN1zY+4A88xCnptns5xi9jvDXUd65+he+IddnTZnW7uO80-DWh6px+4p+vu1XKArzJADeGhd8YFrRVHw3WdS3LA1v2PDMsxzV99UNPETSQn94IrbhkGfH9ZyrKFCAARihChuChJcEXRFVN2I71nlhOismonDmO5O5UW1F88zQAtPmnJjuNnM9QLXfY5ywgkXCI7im3EhVGPE0jfCZU40Coo9xJ4ywIEla4ILWRSf3KGiAFkOWUaBuAAIS0p4AFoGPM48NOcnTOI8n8znYYzdxgfc-O4SyoRs31eHspziG07gAGo6w8gBdRTlPCxsNywHIbAYZ5CWoawcAYfBfA+CRqInWhFTKCoQEwHA8HyBAqEBJpiDwMpUqodguAQFBKmampcmi6B6nLcgag0bQ9F0a1NE4cFnFcMa7KgAABcj8B2gBWXR1piqB8DqKhJAmPA4HOCBeXq4bqhADVSq6qgprwWadH0RbltWw6XAWTaACZdoATnwIH-pe062pAC7HuumK7vKB68BMABHaxJgkSbeGm9R4rm760CWlaZl0DGsd4CRNriEHyN0QwIHECnMexmH6nhq6buRhqmse6MwlA3H8c++afrJlxUhCIXl14WmEmSRnmbpQXzw586JEumpEdurrSlS0ogA) diff --git a/docs/lifecycle-methods.md b/docs/lifecycle-methods.md index cac48a82..381b1fd2 100644 --- a/docs/lifecycle-methods.md +++ b/docs/lifecycle-methods.md @@ -60,19 +60,24 @@ Like in other hooks, the `this` keyword in the `oninit` callback points to `vnod The `oninit` hook is useful for initializing component state based on arguments passed via `vnode.attrs` or `vnode.children`. ```javascript -var ComponentWithState = { - oninit: function(vnode) { - this.data = vnode.attrs.data - }, - view: function() { - return m("div", this.data) // displays data from initialization time +function ComponentWithState() { + var initialData + return { + oninit: function(vnode) { + initialData = vnode.attrs.data + }, + view: function(vnode) { + return [ + // displays data from initialization time: + m("div", "Initial: " + initialData), + // displays current data: + m("div", "Current: " + vnode.attrs.data) + ] + } } } m(ComponentWithState, {data: "Hello"}) - -// Equivalent HTML -//
Hello
``` You should not modify model data synchronously from this method. Since `oninit` makes no guarantees regarding the status of other elements, model changes created from this method may not be reflected in all parts of the UI until the next render cycle. @@ -110,17 +115,19 @@ The `onupdate(vnode)` hook is called after a DOM element is updated, while attac This hook is only called if the element existed in the previous render cycle. It is not called when an element is created or when it is recycled. -Like in other hooks, the `this` keyword in the `onupdate` callback points to `vnode.state`. DOM elements whose vnodes have an `onupdate` hook do not get recycled. +DOM elements whose vnodes have an `onupdate` hook do not get recycled. The `onupdate` hook is useful for reading layout values that may trigger a repaint, and for dynamically updating UI-affecting state in third party libraries after model data has been changed. ```javascript -var RedrawReporter = { - count: 0, - onupdate: function(vnode) { - console.log("Redraws so far: ", ++vnode.state.count) - }, - view: function() {} +function RedrawReporter() { + var count = 0 + return { + onupdate: function() { + console.log("Redraws so far: ", ++count) + }, + view: function() {} + } } m(RedrawReporter, {data: "Hello"}) @@ -163,16 +170,17 @@ Like in other hooks, the `this` keyword in the `onremove` callback points to `vn The `onremove` hook is useful for running clean up tasks. ```javascript -var Timer = { - oninit: function(vnode) { - this.timeout = setTimeout(function() { - console.log("timed out") - }, 1000) - }, - onremove: function(vnode) { - clearTimeout(this.timeout) - }, - view: function() {} +function Timer() { + var timeout = setTimeout(function() { + console.log("timed out") + }, 1000) + + return { + onremove: function() { + clearTimeout(timeout) + }, + view: function() {} + } } ``` diff --git a/docs/lint.js b/docs/lint.js index 9fcb93b4..6a3d8cb9 100644 --- a/docs/lint.js +++ b/docs/lint.js @@ -183,4 +183,4 @@ traverseDirectory("./docs", function(pathname) { }) } }) -.then(process.exit) + .then(process.exit) diff --git a/docs/nav-methods.md b/docs/nav-methods.md index 9b0d108f..ed0d7af7 100644 --- a/docs/nav-methods.md +++ b/docs/nav-methods.md @@ -7,8 +7,6 @@ - [m.jsonp](jsonp.md) - [m.parseQueryString](parseQueryString.md) - [m.buildQueryString](buildQueryString.md) - - [m.withAttr](withAttr.md) - - [m.prop](prop.md) - [m.trust](trust.md) - [m.fragment](fragment.md) - [m.redraw](redraw.md) diff --git a/docs/prop.md b/docs/prop.md deleted file mode 100644 index f79afd40..00000000 --- a/docs/prop.md +++ /dev/null @@ -1,152 +0,0 @@ -# prop(attrName, callback) - -- [Description](#description) -- [Signature](#signature) -- [How it works](#how-it-works) -- [Sending through requests](#sending-through-requests) - ---- - -### Description - -Returns a simple getter/setter object. - -```javascript -var name = m.prop("John") - -var oldName = name.get() // First, it's set to "John" -name.set("Mary") // Set the value to "Mary" -var newName = name.get() // Now it's "Mary", not "John" -``` - ---- - -### Signature - -`prop = m.prop(initial?)` - -Argument | Type | Required | Description ------------ | ------ | -------- | --- -`initial` | `any` | No | The prop's initial value -**returns** | `Prop` | | A prop - -`value = prop.get()` - -Argument | Type | Required | Description ------------ | ----- | -------- | --- -**returns** | `any` | | The prop's current value - -`newValue = prop.set(newValue)` - -Argument | Type | Required | Description ------------ | ----- | -------- | --- -`newValue` | `any` | Yes | The value to set the prop to -**returns** | `any` | | The value you just set the prop to, for convenience - -[How to read signatures](signatures.md) - ---- - -### How it works - -The `m.prop` method creates a prop, a getter/setter object wrapping a single mutable reference. You can get the current value with `prop.get()` and set it with `prop.set(value)`. Unlike [streams](stream.md), you can't observe them, so you can't do as much with them. - -In conjunction with [`m.withAttr`](withAttr.md), you can emulate two-way binding pretty easily. - -```javascript -var Component = { - oninit: function(vnode) { - vnode.state.current = m.prop("") - }, - view: function(vnode) { - return m("input", { - oninput: m.withAttr("value", vnode.state.current.set), - value: vnode.state.current.get(), - }) - } -} -``` - -They're also useful for making simpler models. - -```javascript -// With props -var Auth = { - username: m.prop(""), - password: m.prop(""), - canSubmit: function() { - return Auth.username.get() !== "" && Auth.password.get() !== "" - }, - login: function() { - // ... - }, -} - -// Without props -var Auth = { - username: "", - password: "", - setUsername: function(value) { - Auth.username = value - } - setPassword: function(value) { - Auth.password = value - } - canSubmit: function() { - return Auth.username !== "" && Auth.password !== "" - }, - login: function() { - // ... - }, -} -``` - ---- - -### Sending through requests - -For convenience, props define `.toJSON` as an alias for `.get`. This is so you can send them through `m.request` without serializing them manually. - -We could also take this model and simplify it: - -```javascript -// How it's loaded -User.load = function(id) { - return m.request({ - method: "GET", - url: "https://rem-rest-api.herokuapp.com/api/users/" + id, - withCredentials: true, - }) - .then(function(result) { - User.current = { - id: result.id, - firstName: m.prop(result.firstName), - lastName: m.prop(result.lastName), - } - }) -} - -// Original -User.save = function(user) { - return m.request({ - method: "PUT", - url: "https://rem-rest-api.herokuapp.com/api/users/" + user.id, - data: { - id: user.id, - firstName: user.firstName.get(), - lastName: user.lastName.get(), - }, - withCredentials: true, - }) -} - -// Simplified -User.save = function(user) { - return m.request({ - method: "PUT", - url: "https://rem-rest-api.herokuapp.com/api/users/" + user.id, - data: user, - withCredentials: true, - }) -} -``` diff --git a/docs/redraw.md b/docs/redraw.md index 0d079592..f53612a5 100644 --- a/docs/redraw.md +++ b/docs/redraw.md @@ -44,7 +44,7 @@ When callbacks outside of Mithril run, you need to notify Mithril's rendering en To trigger a redraw, call `m.redraw()`. Note that `m.redraw` only works if you used `m.mount` or `m.route`. If you rendered via `m.render`, you should use `m.render` to redraw. -`m.redraw()` always triggers an asynchronous redraws, whereas `m.redraw.sync()` triggers a synchronous one. `m.redraw()` is tied to `window.requestAnimationFrame()` (we provide a fallback for IE9). It will thus typically fire at most 60 times per second. It may fire faster if your monitor has a higher refresh rate. +`m.redraw()` always triggers an asynchronous redraws, whereas `m.redraw.sync()` triggers a synchronous one. `m.redraw()` is tied to `window.requestAnimationFrame()`. It will thus typically fire at most 60 times per second. It may fire faster if your monitor has a higher refresh rate. `m.redraw.sync()` is mostly intended to make videos play work in iOS. That only works in response to user-triggered events. It comes with several caveat: @@ -52,4 +52,4 @@ To trigger a redraw, call `m.redraw()`. Note that `m.redraw` only works if you u - `m.redraw.sync()` called from an event handler can cause the DOM to be modified while an event is bubbling. Depending on the structure of the old and new DOM trees, the event can finish the bubbling phase in the new tree and trigger unwanted handlers. - It is not throttled. One call to `m.redraw.sync()` causes immediately one `m.render()` call per root registered with [`m.mount()`](mount.md) or [`m.route()`](route.md). -`m.redraw()` doesn't have any of those issues, you can call it from wherever you like. \ No newline at end of file +`m.redraw()` doesn't have any of those issues, you can call it from wherever you like. diff --git a/docs/request.md b/docs/request.md index 3412ebea..6a68333f 100644 --- a/docs/request.md +++ b/docs/request.md @@ -4,6 +4,7 @@ - [Signature](#signature) - [How it works](#how-it-works) - [Typical usage](#typical-usage) +- [Error handling](#error-handling) - [Loading icons and error messages](#loading-icons-and-error-messages) - [Dynamic URLs](#dynamic-urls) - [Aborting requests](#aborting-requests) @@ -127,6 +128,18 @@ When `m.route` is called at the bottom, the `Todos` component is initialized. `o --- +### Error handling + +When a non-`file:` request returns with any status other than 2xx or 304, it rejects with an error. This error is a normal [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) instance, but with a few special properties. + +- `error.message` is set to the raw response text. +- `error.code` is set to the status code itself. +- `error.response` is set to the parsed response, using `options.extract` and `options.deserialize` as is done with normal responses. + +This is useful in many cases where errors are themselves things you can account for. If you want to detect if a session expired - you can do `if (error.code === 401) return promptForAuth().then(retry)`. If you hit an API's throttling mechanism and it returned an error with a `"timeout": 1000`, you could do a `setTimeout(retry, error.response.timeout)`. + +--- + ### Loading icons and error messages Here's an expanded version of the example above that implements a loading indicator and an error message: diff --git a/docs/route.md b/docs/route.md index 630ca16b..a93199c5 100644 --- a/docs/route.md +++ b/docs/route.md @@ -78,6 +78,19 @@ Argument | Type | Required | Description `options.title` | `String` | No | The `title` string to pass to the underlying `history.pushState` / `history.replaceState` call. **returns** | | | Returns `undefined` +Remember that when using `.set` with params you also need to define the route: +```javascript +var Article = { + view: function(vnode) { + return "This is article " + vnode.attrs.articleid + } +} + +m.route(document.body, { + '/article/:articleid': Article +}) +m.route.set('/article/:articleid', {articleid: 1}) +``` ##### m.route.get Returns the last fully resolved routing path, without the prefix. It may differ from the path displayed in the location bar while an asynchronous route is [pending resolution](#code-splitting). @@ -205,11 +218,11 @@ The routing strategy dictates how a library might actually implement routing. Th - Using the querystring. A URL using this strategy typically looks like `http://localhost/?/page1` - Using the pathname. A URL using this strategy typically looks like `http://localhost/page1` -Using the hash strategy is guaranteed to work in browsers that don't support `history.pushState` (namely, Internet Explorer 9), because it can fall back to using `onhashchange`. Use this strategy if you want to support IE9. +Using the hash strategy is guaranteed to work in browsers that don't support `history.pushState`, because it can fall back to using `onhashchange`. Use this strategy if you want to keep the hashes purely local. -The querystring strategy also technically works in IE9, but it falls back to reloading the page. Use this strategy if you want to support anchored links and you are not able to make the server-side necessary to support the pathname strategy. +The querystring strategy allows server-side detection, but it doesn't appear as a normal path. Use this strategy if you want to support and potentially detect anchored links server-side and you are not able to make the changes necessary to support the pathname strategy (like if you're using Apache and can't modify your .htaccess). -The pathname strategy produces the cleanest looking URLs, but does not work in IE9 *and* requires setting up the server to serve the single page application code from every URL that the application can route to. Use this strategy if you want cleaner-looking URLs and do not need to support IE9. +The pathname strategy produces the cleanest looking URLs, but requires setting up the server to serve the single page application code from every URL that the application can route to. Use this strategy if you want cleaner-looking URLs. Single page applications that use the hash strategy often use the convention of having an exclamation mark after the hash to indicate that they're using the hash as a routing mechanism and not for the purposes of linking to anchors. The `#!` string is known as a *hashbang*. @@ -370,7 +383,10 @@ var Form = { }, view: function() { return m("form", [ - m("input[placeholder='Search']", {oninput: m.withAttr("value", function(v) {state.term = v}), value: state.term}), + m("input[placeholder='Search']", { + oninput: function (e) { state.term = e.target.value }, + value: state.term + }), m("button", {onclick: state.search}, "Search") ]) } @@ -576,8 +592,14 @@ var Auth = { var Login = { view: function() { return m("form", [ - m("input[type=text]", {oninput: m.withAttr("value", Auth.setUsername), value: Auth.username}), - m("input[type=password]", {oninput: m.withAttr("value", Auth.setPassword), value: Auth.password}), + m("input[type=text]", { + oninput: function (e) { Auth.setUsername(e.target.value) }, + value: Auth.username + }), + m("input[type=password]", { + oninput: function (e) { Auth.setPassword(e.target.value) }, + value: Auth.password + }), m("button[type=button]", {onclick: Auth.login}, "Login") ]) } diff --git a/docs/simple-application.md b/docs/simple-application.md index baa46d12..3482caa9 100644 --- a/docs/simple-application.md +++ b/docs/simple-application.md @@ -471,12 +471,12 @@ module.exports = { }, [ m("label.label", "First name"), m("input.input[type=text][placeholder=First name]", { - oninput: m.withAttr("value", function(value) {User.current.firstName = value}), + oninput: function (e) {User.current.firstName = e.target.value}, value: User.current.firstName }), m("label.label", "Last name"), m("input.input[placeholder=Last name]", { - oninput: m.withAttr("value", function(value) {User.current.lastName = value}), + oninput: function (e) {User.current.lastName = e.target.value}, value: User.current.lastName }), m("button.button[type=submit]", "Save"), diff --git a/docs/stream.md b/docs/stream.md index 679294be..0e318f30 100644 --- a/docs/stream.md +++ b/docs/stream.md @@ -7,7 +7,8 @@ - [Stream.merge](#streammerge) - [Stream.scan](#streamscan) - [Stream.scanMerge](#streamscanmerge) - - [Stream.HALT](#streamhalt) + - [Stream.lift](#streamlift) + - [Stream.SKIP](#streamskip) - [Stream["fantasy-land/of"]](#streamfantasy-landof) - [Instance members](#instance-members) - [stream.map](#streammap) @@ -120,13 +121,13 @@ Argument | Type | Required | Description Creates a new stream with the results of calling the function on every value in the stream with an accumulator and the incoming value. -Note that you can prevent dependent streams from being updated by returning the special value `stream.HALT` inside the accumulator function. +Note that you can prevent dependent streams from being updated by returning the special value `stream.SKIP` inside the accumulator function. `stream = Stream.scan(fn, accumulator, stream)` Argument | Type | Required | Description ------------- | -------------------------------- | -------- | --- -`fn` | `(accumulator, value) -> result \| HALT` | Yes | A function that takes an accumulator and value parameter and returns a new accumulator value +`fn` | `(accumulator, value) -> result \| SKIP` | Yes | A function that takes an accumulator and value parameter and returns a new accumulator value of the same type `accumulator` | `any` | Yes | The starting value for the accumulator `stream` | `Stream` | Yes | Stream containing the values **returns** | `Stream` | | Returns a new stream containing the result @@ -151,9 +152,40 @@ Argument | Type | Required | De --- -##### Stream.HALT +##### Stream.lift -A special value that can be returned to stream callbacks to halt execution of downstreams +Creates a computed stream that reactively updates if any of its upstreams are updated. See [combining streams](#combining-streams). Unlike `combine`, the input streams are a variable number of arguments (instead of an array) and the callback receives the stream values instead of streams. There is no `changed` parameter. This is generally a more user-friendly function for applications than `combine`. + +`stream = Stream.lift(lifter, stream1, stream2, ...)` + +Argument | Type | Required | Description +------------ | --------------------------- | -------- | --- +`lifter` | `(any...) -> any` | Yes | See [lifter](#lifter) argument +`streams...` | list of `Streams` | Yes | Streams to be lifted +**returns** | `Stream` | | Returns a stream + +[How to read signatures](signatures.md) + +--- + +###### lifter + +Specifies how the value of a computed stream is generated. See [combining streams](#combining-streams) + +`any = lifter(streams...)` + +Argument | Type | Required | Description +------------ | -------------------- | -------- | --- +`streams...` | splat of `Streams` | No | Splat of zero or more streams that correspond to the streams passed to [`stream.lift`](#stream-lift) +**returns** | `any` | | Returns a computed value + +[How to read signatures](signatures.md) + +--- + +##### Stream.SKIP + +A special value that can be returned to stream callbacks to skip execution of downstreams --- @@ -274,7 +306,7 @@ In the example above, the `users` stream is populated with the response data whe #### Bidirectional bindings -Streams can also be populated from other higher order functions, such as [`m.withAttr`](withAttr.md) +Streams can also be populated from event callbacks and similar. ```javascript // a stream @@ -282,7 +314,7 @@ var user = stream("") // a bi-directional binding to the stream m("input", { - oninput: m.withAttr("value", user), + oninput: function (e) { user(e.target.value) }, value: user() }) ``` @@ -343,14 +375,14 @@ console.log(doubled()) // logs 2 Dependent streams are *reactive*: their values are updated any time the value of their parent stream is updated. This happens regardless of whether the dependent stream was created before or after the value of the parent stream was set. -You can prevent dependent streams from being updated by returning the special value `stream.HALT` +You can prevent dependent streams from being updated by returning the special value `stream.SKIP` ```javascript -var halted = stream(1).map(function(value) { - return stream.HALT +var skipped = stream(1).map(function(value) { + return stream.SKIP }) -halted.map(function() { +skipped.map(function() { // never runs }) ``` @@ -372,6 +404,18 @@ var greeting = stream.merge([a, b]).map(function(values) { console.log(greeting()) // logs "hello world" ``` +Or you can use the helper function `stream.lift()` + +```javascript +var a = stream("hello") +var b = stream("world") + +var greeting = stream.lift(function(_a, _b) { + return _a + " " + _b +}, a, b) + +console.log(greeting()) // logs "hello world" +``` There's also a lower level method called `stream.combine()` that exposes the stream themselves in the reactive computations for more advanced use cases @@ -388,14 +432,14 @@ console.log(added()) // logs 12 A stream can depend on any number of streams and it's guaranteed to update atomically. For example, if a stream A has two dependent streams B and C, and a fourth stream D is dependent on both B and C, the stream D will only update once if the value of A changes. This guarantees that the callback for stream D is never called with unstable values such as when B has a new value but C has the old value. Atomicity also brings the performance benefits of not recomputing downstreams unnecessarily. -You can prevent dependent streams from being updated by returning the special value `stream.HALT` +You can prevent dependent streams from being updated by returning the special value `stream.SKIP` ```javascript -var halted = stream.combine(function(stream) { - return stream.HALT +var skipped = stream.combine(function(stream) { + return stream.SKIP }, [stream(1)]) -halted.map(function() { +skipped.map(function() { // never runs }) ``` diff --git a/docs/vnodes.md b/docs/vnodes.md index a663e197..ce438ef1 100644 --- a/docs/vnodes.md +++ b/docs/vnodes.md @@ -21,7 +21,7 @@ It may seem wasteful to recreate vnodes so frequently, but as it turns out, mode For that reason, Mithril uses a sophisticated and highly optimized virtual DOM diffing algorithm to minimize the amount of DOM updates. Mithril *also* generates carefully crafted vnode data structures that are compiled by Javascript engines for near-native data structure access performance. In addition, Mithril aggressively optimizes the function that creates vnodes as well. -The reason Mithril goes to such great lengths to support a rendering model that recreates the entire virtual DOM tree on every render is to provide a declarative [immediate mode](https://en.wikipedia.org/wiki/Immediate_mode_(computer_graphics)) API, a style of rendering that makes it drastically easier to manage UI complexity. +The reason Mithril goes to such great lengths to support a rendering model that recreates the entire virtual DOM tree on every render is to provide a declarative [immediate mode](https://en.wikipedia.org/wiki/Immediate_mode_(computer_graphics%29) API, a style of rendering that makes it drastically easier to manage UI complexity. To illustrate why immediate mode is so important, consider the DOM API and HTML. The DOM API is an imperative [retained mode](https://en.wikipedia.org/wiki/Retained_mode) API and requires 1. writing out exact instructions to assemble a DOM tree procedurally, and 2. writing out other instructions to update that tree. The imperative nature of the DOM API means you have many opportunities to micro-optimize your code, but it also means that you have more chances of introducing bugs and more chances to make code harder to understand. diff --git a/docs/withAttr.md b/docs/withAttr.md deleted file mode 100644 index 53dfd9c9..00000000 --- a/docs/withAttr.md +++ /dev/null @@ -1,136 +0,0 @@ -# withAttr(attrName, callback) - -- [Description](#description) -- [Signature](#signature) -- [How it works](#how-it-works) -- [Predictable event target](#predictable-event-target) -- [Attributes and properties](#attributes-and-properties) - ---- - -### Description - -Returns an event handler that runs `callback` with the value of the specified DOM attribute - -```javascript -var state = { - value: "", - setValue: function(v) {state.value = v} -} - -var Component = { - view: function() { - return m("input", { - oninput: m.withAttr("value", state.setValue), - value: state.value, - }) - } -} - -m.mount(document.body, Component) -``` - ---- - -### Signature - -`m.withAttr(attrName, callback, thisArg?)` - -Argument | Type | Required | Description ------------ | -------------------- | -------- | --- -`attrName` | `String` | Yes | The name of the attribute or property whose value will be used -`callback` | `any -> undefined` | Yes | The callback -`thisArg` | `any` | No | An object to bind to the `this` keyword in the callback function -**returns** | `Event -> undefined` | | An event handler function - -[How to read signatures](signatures.md) - ---- - -### How it works - -The `m.withAttr` method creates an event handler. The event handler takes the value of a DOM element's property and calls a function with it as the argument. - -This helper function is provided to help decouple the browser's event model from application code. - -```javascript -// standalone usage -document.body.onclick = m.withAttr("title", function(value) { - console.log(value) // logs the title of the element when clicked -}) -``` - -Typically, `m.withAttr()` can be used in Mithril component views to avoid polluting the data layer with DOM event model concerns: - -```javascript -var state = { - email: "", - setEmail: function(email) { - state.email = email.toLowerCase() - } -} - -var MyComponent = { - view: function() { - return m("input", { - oninput: m.withAttr("value", state.setEmail), - value: state.email - }) - } -} - -m.mount(document.body, MyComponent) -``` - ---- - -### Predictable event target - -The `m.withAttr()` helper reads the value of the element to which the event handler is bound, which is not necessarily the same as the element where the event originated. - -```javascript -var state = { - url: "", - setURL: function(url) {state.url = url} -} - -var MyComponent = { - view: function() { - return m("a[href='/foo']", {onclick: m.withAttr("href", state.setURL)}, [ - m("span", state.url) - ]) - } -} - -m.mount(document.body, MyComponent) -``` - -In the example above, if the user clicks on the text within the link, `e.target` will point to the ``, not the ``. - -While this behavior works as per its specs, it's not very intuitive or useful most of the time. Therefore, `m.withAttr` uses the value of `e.currentTarget` which does point to the ``, as one would normally expect. - ---- - -### Attributes and properties - -The first argument of `m.withAttr()` can be either an attribute or a property. - -```javascript -// reads from `select.selectedIndex` property -var state = { - index: 0, - setIndex: function(index) {state.index = index} -} -m("select", {onclick: m.withAttr("selectedIndex", state.setIndex)}) -``` - -If a value can be both an attribute *and* a property, the property value is used. - -```javascript -// value is a boolean, because the `input.checked` property is boolean -var state = { - selected: false, - setSelected: function(selected) {state.selected = selected} -} -m("input[type=checkbox]", {onclick: m.withAttr("checked", state.setSelected)}) -``` diff --git a/esm.js b/esm.js new file mode 100644 index 00000000..91ea2410 --- /dev/null +++ b/esm.js @@ -0,0 +1,67 @@ +"use strict" + +/* + +This script will create esm compatible scripts +from the already compiled versions of: + +- mithril.js > mithril.mjs +- mithril.min.js > mithril.min.mjs +- /stream/stream.js > stream.mjs + +*/ + +var fs = require("fs") + +var namedExports = [ + "m", + "trust", + "fragment", + "mount", + "route", + "render", + "redraw", + "request", + "jsonp", + "parseQueryString", + "buildQueryString", + "version", + "vnode", + "PromisePolyfill" +] + +var mithril = fs.readFileSync("mithril.js", "utf8") +fs.writeFileSync("mithril.mjs", + mithril.slice( + mithril.indexOf("\"use strict\"") + 13, + mithril.lastIndexOf("if (typeof module") + ) + + "\nexport default m" + // The exports are declared with prefixed underscores to avoid overwriting previously + // declared variables with the same name + + "\nvar " + namedExports.map(function(n) { return "_" + n + " = m." + n }).join(",") + + "\nexport {" + namedExports.map(function(n) { return "_" + n + " as " + n }).join(",") + "}" +) + +var mithrilMin = fs.readFileSync("mithril.min.js", "utf8") +var mName = mithrilMin.match(/window\.m=([a-z])}/)[1] +fs.writeFileSync("mithril.min.mjs", + mithrilMin.slice( + 12, + mithrilMin.lastIndexOf("\"undefined\"!==typeof module") + ) + + "export default " + mName + ";" + // The exports are declared with prefixed underscores to avoid overwriting previously + // declared variables with the same name + + "var " + namedExports.map(function(n) { return "_" + n + "=m." + n }).join(",") + ";" + + "export {" + namedExports.map(function(n) { return "_" + n + " as " + n }).join(",") + "};" +) + +var stream = fs.readFileSync("stream/stream.js", "utf8") +fs.writeFileSync("stream/stream.mjs", + stream.slice( + stream.indexOf("\"use strict\"") + 13, + stream.lastIndexOf("if (typeof module") + ) + + "\nexport default Stream" +) diff --git a/examples/editor/index.html b/examples/editor/index.html index 42eceb2e..a246a667 100644 --- a/examples/editor/index.html +++ b/examples/editor/index.html @@ -28,7 +28,7 @@ var Editor = { view: function() { return [ m("textarea.input", { - oninput: m.withAttr("value", state.update), + oninput: function (e) { state.update(e.traget.value) }, value: state.text }), m(".preview", m.trust(marked(state.text))), diff --git a/index.js b/index.js index 469a832b..9e422940 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,11 @@ "use strict" -var m = require("./hyperscript") +var hyperscript = require("./hyperscript") +var m = function m() { return hyperscript.apply(this, arguments) } +m.m = hyperscript +m.trust = hyperscript.trust +m.fragment = hyperscript.fragment + var requestService = require("./request") var redrawService = require("./redraw") @@ -8,8 +13,6 @@ requestService.setCompletionCallback(redrawService.redraw) m.mount = require("./mount") m.route = require("./route") -m.withAttr = require("./util/withAttr") -m.prop = require("./util/prop") m.render = require("./render").render m.redraw = redrawService.redraw m.request = requestService.request diff --git a/mithril.js b/mithril.js index b8a7e104..c5590d70 100644 --- a/mithril.js +++ b/mithril.js @@ -1,7 +1,7 @@ ;(function() { "use strict" -function Vnode(tag, key, attrs0, children, text, dom) { - return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: undefined, events: undefined, instance: undefined} +function Vnode(tag, key, attrs0, children0, text, dom) { + return {tag: tag, key: key, attrs: attrs0, children: children0, text: text, dom: dom, domSize: undefined, state: undefined, events: undefined, instance: undefined} } Vnode.normalize = function(node) { if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined) @@ -9,11 +9,57 @@ Vnode.normalize = function(node) { return node } Vnode.normalizeChildren = function(input) { - var children = [] + var children0 = [] for (var i = 0; i < input.length; i++) { - children[i] = Vnode.normalize(input[i]) + children0[i] = Vnode.normalize(input[i]) } - return children + return children0 +} +// Call via `hyperscriptVnode0.apply(startOffset, arguments)` +// +// The reason I do it this way, forwarding the arguments and passing the start +// offset in `this`, is so I don't have to create a temporary array in a +// performance-critical path. +// +// In native ES6, I'd instead add a final `...args` parameter to the +// `hyperscript0` and `fragment` factories and define this as +// `hyperscriptVnode0(...args)`, since modern engines do optimize that away. But +// ES5 (what Mithril requires thanks to IE support) doesn't give me that luxury, +// and engines aren't nearly intelligent enough to do either of these: +// +// 1. Elide the allocation for `[].slice.call(arguments, 1)` when it's passed to +// another function only to be indexed. +// 2. Elide an `arguments` allocation when it's passed to any function other +// than `Function.prototype.apply` or `Reflect.apply`. +// +// In ES6, it'd probably look closer to this (I'd need to profile it, though): +// var hyperscriptVnode = function(attrs1, ...children1) { +// if (attrs1 == null || typeof attrs1 === "object" && attrs1.tag == null && !Array.isArray(attrs1)) { +// if (children1.length === 1 && Array.isArray(children1[0])) children1 = children1[0] +// } else { +// children1 = children1.length === 0 && Array.isArray(attrs1) ? attrs1 : [attrs1, ...children1] +// attrs1 = undefined +// } +// +// if (attrs1 == null) attrs1 = {} +// return Vnode("", attrs1.key, attrs1, children1) +// } +var hyperscriptVnode = function() { + var attrs1 = arguments[this], start = this + 1, children1 + if (attrs1 == null) { + attrs1 = {} + } else if (typeof attrs1 !== "object" || attrs1.tag != null || Array.isArray(attrs1)) { + attrs1 = {} + start = this + } + if (arguments.length === start + 1) { + children1 = arguments[start] + if (!Array.isArray(children1)) children1 = [children1] + } else { + children1 = [] + while (start < arguments.length) children1.push(arguments[start++]) + } + return Vnode("", attrs1.key, attrs1, children1) } var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g var selectorCache = {} @@ -39,16 +85,18 @@ function compileSelector(selector) { if (classes.length > 0) attrs.className = classes.join(" ") return selectorCache[selector] = {tag: tag, attrs: attrs} } -function execSelector(state, attrs, children) { - var hasAttrs = false, childList, text - var classAttr = hasOwn.call(attrs, "class") ? "class" : "className" - var className = attrs[classAttr] +function execSelector(state, vnode) { + var attrs = vnode.attrs + var children = Vnode.normalizeChildren(vnode.children) + var hasClass = hasOwn.call(attrs, "class") + var className = hasClass ? attrs.class : attrs.className + vnode.tag = state.tag + vnode.attrs = null + vnode.children = undefined if (!isEmpty(state.attrs) && !isEmpty(attrs)) { var newAttrs = {} - for(var key in attrs) { - if (hasOwn.call(attrs, key)) { - newAttrs[key] = attrs[key] - } + for (var key in attrs) { + if (hasOwn.call(attrs, key)) newAttrs[key] = attrs[key] } attrs = newAttrs } @@ -60,57 +108,51 @@ function execSelector(state, attrs, children) { if (className != null || state.attrs.className != null) attrs.className = className != null ? state.attrs.className != null - ? state.attrs.className + " " + className + ? String(state.attrs.className) + " " + String(className) : className : state.attrs.className != null ? state.attrs.className : null - if (classAttr === "class") attrs.class = null + if (hasClass) attrs.class = null for (var key in attrs) { if (hasOwn.call(attrs, key) && key !== "key") { - hasAttrs = true + vnode.attrs = attrs break } } if (Array.isArray(children) && children.length === 1 && children[0] != null && children[0].tag === "#") { - text = children[0].children + vnode.text = children[0].children } else { - childList = children + vnode.children = children } - return Vnode(state.tag, attrs.key, hasAttrs ? attrs : null, childList, text) + return vnode } function hyperscript(selector) { if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") { throw Error("The selector must be either a string or a component."); } - var attrs = arguments[1], start = 2, children - if (attrs == null) { - attrs = {} - } else if (typeof attrs !== "object" || attrs.tag != null || Array.isArray(attrs)) { - attrs = {} - start = 1 - } - if (arguments.length === start + 1) { - children = arguments[start] - if (!Array.isArray(children)) children = [children] - } else { - children = [] - while (start < arguments.length) children.push(arguments[start++]) - } + var vnode = hyperscriptVnode.apply(1, arguments) if (typeof selector === "string") { - return execSelector(selectorCache[selector] || compileSelector(selector), attrs, Vnode.normalizeChildren(children)) + return execSelector(selectorCache[selector] || compileSelector(selector), vnode) } else { - return Vnode(selector, attrs.key, attrs, children) + vnode.tag = selector + return vnode } } hyperscript.trust = function(html) { if (html == null) html = "" return Vnode("<", undefined, undefined, html, undefined, undefined) } -hyperscript.fragment = function(attrs1, children0) { - return Vnode("[", attrs1.key, attrs1, Vnode.normalizeChildren(children0), undefined, undefined) +hyperscript.fragment = function() { + var vnode2 = hyperscriptVnode.apply(0, arguments) + vnode2.tag = "[" + vnode2.children = Vnode.normalizeChildren(vnode2.children) + return vnode2 } -var m = hyperscript +var m = function m() { return hyperscript.apply(this, arguments) } +m.m = hyperscript +m.trust = hyperscript.trust +m.fragment = hyperscript.fragment /** @constructor */ var PromisePolyfill = function(executor) { if (!(this instanceof PromisePolyfill)) throw new Error("Promise must be called with `new`") @@ -237,67 +279,93 @@ if (typeof window !== "undefined") { var buildQueryString = function(object) { if (Object.prototype.toString.call(object) !== "[object Object]") return "" var args = [] - for (var key0 in object) { - destructure(key0, object[key0]) + for (var key in object) { + destructure(key, object[key]) } return args.join("&") - function destructure(key0, value) { + function destructure(key, value) { if (Array.isArray(value)) { for (var i = 0; i < value.length; i++) { - destructure(key0 + "[" + i + "]", value[i]) + destructure(key + "[" + i + "]", value[i]) } } else if (Object.prototype.toString.call(value) === "[object Object]") { for (var i in value) { - destructure(key0 + "[" + i + "]", value[i]) + destructure(key + "[" + i + "]", value[i]) } } - else args.push(encodeURIComponent(key0) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : "")) + else args.push(encodeURIComponent(key) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : "")) } } -var FILE_PROTOCOL_REGEX = new RegExp("^file://", "i") -var _9 = function($window, Promise) { +var _12 = function($window, Promise) { var callbackCount = 0 var oncompletion - function setCompletionCallback(callback) {oncompletion = callback} - function finalizer() { - var count = 0 - function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()} - return function finalize(promise0) { - var then0 = promise0.then - promise0.then = function() { - count++ - var next = then0.apply(promise0, arguments) - next.then(complete, function(e) { - complete() - if (count === 0) throw e - }) - return finalize(next) + function makeRequest(factory) { + return function(url, args) { + if (typeof url !== "string") { args = url; url = url.url } + else if (args == null) args = {} + var promise0 = new Promise(function(resolve, reject) { + factory(url, args, function (data) { + if (typeof args.type === "function") { + if (Array.isArray(data)) { + for (var i = 0; i < data.length; i++) { + data[i] = new args.type(data[i]) + } + } + else data = new args.type(data) + } + resolve(data) + }, reject) + }) + if (args.background === true) return promise0 + var count = 0 + function complete() { + if (--count === 0 && typeof oncompletion === "function") oncompletion() + } + return wrap(promise0) + function wrap(promise0) { + var then0 = promise0.then + promise0.then = function() { + count++ + var next = then0.apply(promise0, arguments) + next.then(complete, function(e) { + complete() + if (count === 0) throw e + }) + return wrap(next) + } + return promise0 } - return promise0 } } - function normalize(args, extra) { - if (typeof args === "string") { - var url = args - args = extra || {} - if (args.url == null) args.url = url + function hasHeader(args, name) { + for (var key in args.headers) { + if ({}.hasOwnProperty.call(args.headers, key) && name.test(key)) return true } - return args + return false } - function request(args, extra) { - var finalize = finalizer() - args = normalize(args, extra) - var promise0 = new Promise(function(resolve, reject) { - if (args.method == null) args.method = "GET" - args.method = args.method.toUpperCase() - var useBody = (args.method === "GET" || args.method === "TRACE") ? false : (typeof args.useBody === "boolean" ? args.useBody : true) - if (typeof args.serialize !== "function") args.serialize = typeof FormData !== "undefined" && args.data instanceof FormData ? function(value) {return value} : JSON.stringify - if (typeof args.deserialize !== "function") args.deserialize = deserialize - if (typeof args.extract !== "function") args.extract = extract - args.url = interpolate(args.url, args.data) - if (useBody) args.data = args.serialize(args.data) - else args.url = assemble(args.url, args.data) + function interpolate(url, data, assemble) { + if (data == null) return url + url = url.replace(/:([^\/]+)/gi, function (m0, key) { + return data[key] != null ? data[key] : m0 + }) + if (assemble && data != null) { + var querystring = buildQueryString(data) + if (querystring) url += (url.indexOf("?") < 0 ? "?" : "&") + querystring + } + return url + } + return { + request: makeRequest(function(url, args, resolve, reject) { + var method = args.method != null ? args.method.toUpperCase() : "GET" + var useBody = method !== "GET" && method !== "TRACE" && + (typeof args.useBody !== "boolean" || args.useBody) + var data = args.data + var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(data instanceof $window.FormData) + if (useBody) { + if (typeof args.serialize === "function") data = args.serialize(data) + else if (!(data instanceof $window.FormData)) data = JSON.stringify(data) + } var xhr = new $window.XMLHttpRequest(), aborted = false, _abort = xhr.abort @@ -305,19 +373,20 @@ var _9 = function($window, Promise) { aborted = true _abort.call(xhr) } - xhr.open(args.method, args.url, typeof args.async === "boolean" ? args.async : true, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) - if (args.serialize === JSON.stringify && useBody && !(args.headers && args.headers.hasOwnProperty("Content-Type"))) { + xhr.open(method, interpolate(url, args.data, !useBody), typeof args.async !== "boolean" || args.async, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) + if (assumeJSON && useBody && !hasHeader(args, /^content-type0$/i)) { xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") } - if (args.deserialize === deserialize && !(args.headers && args.headers.hasOwnProperty("Accept"))) { + if (typeof args.deserialize !== "function" && !hasHeader(args, /^accept$/i)) { xhr.setRequestHeader("Accept", "application/json, text/*") } if (args.withCredentials) xhr.withCredentials = args.withCredentials if (args.timeout) xhr.timeout = args.timeout - if (args.responseType) xhr.responseType = args.responseType - for (var key in args.headers) if ({}.hasOwnProperty.call(args.headers, key)) { - xhr.setRequestHeader(key, args.headers[key]) + for (var key in args.headers) { + if ({}.hasOwnProperty.call(args.headers, key)) { + xhr.setRequestHeader(key, args.headers[key]) + } } if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr xhr.onreadystatechange = function() { @@ -325,10 +394,18 @@ var _9 = function($window, Promise) { if(aborted) return if (xhr.readyState === 4) { try { - var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args)) - if (args.extract !== extract || (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) { - resolve(cast(args.type, response)) + var success = (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || (/^file:\/\//i).test(url) + var response = xhr.responseText + if (typeof args.extract === "function") { + response = args.extract(xhr, args) + success = true + } else if (typeof args.deserialize === "function") { + response = args.deserialize(response) + } else { + try {response = response ? JSON.parse(response) : null} + catch (e) {throw new Error("Invalid JSON: " + response)} } + if (success) resolve(response) else { var error = new Error(xhr.responseText) error.code = xhr.status @@ -341,20 +418,15 @@ var _9 = function($window, Promise) { } } } - if (useBody && (args.data != null)) xhr.send(args.data) + if (useBody && data != null) xhr.send(data) else xhr.send() - }) - return args.background === true ? promise0 : finalize(promise0) - } - function jsonp(args, extra) { - var finalize = finalizer() - args = normalize(args, extra) - var promise0 = new Promise(function(resolve, reject) { + }), + jsonp: makeRequest(function(url, args, resolve, reject) { var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ var script = $window.document.createElement("script") $window[callbackName] = function(data) { script.parentNode.removeChild(script) - resolve(cast(args.type, data)) + resolve(data) delete $window[callbackName] } script.onerror = function() { @@ -362,123 +434,98 @@ var _9 = function($window, Promise) { reject(new Error("JSONP request failed")) delete $window[callbackName] } - if (args.data == null) args.data = {} - args.url = interpolate(args.url, args.data) - args.data[args.callbackKey || "callback"] = callbackName - script.src = assemble(args.url, args.data) + url = interpolate(url, args.data, true) + script.src = url + (url.indexOf("?") < 0 ? "?" : "&") + + encodeURIComponent(args.callbackKey || "callback") + "=" + + encodeURIComponent(callbackName) $window.document.documentElement.appendChild(script) - }) - return args.background === true? promise0 : finalize(promise0) + }), + setCompletionCallback: function(callback) { + oncompletion = callback + }, } - function interpolate(url, data) { - if (data == null) return url - var tokens = url.match(/:[^\/]+/gi) || [] - for (var i = 0; i < tokens.length; i++) { - var key = tokens[i].slice(1) - if (data[key] != null) { - url = url.replace(tokens[i], data[key]) - } - } - return url - } - function assemble(url, data) { - var querystring = buildQueryString(data) - if (querystring !== "") { - var prefix = url.indexOf("?") < 0 ? "?" : "&" - url += prefix + querystring - } - return url - } - function deserialize(data) { - try {return data !== "" ? JSON.parse(data) : null} - catch (e) {throw new Error("Invalid JSON: " + data)} - } - function extract(xhr) {return xhr.responseText} - function cast(type0, data) { - if (typeof type0 === "function") { - if (Array.isArray(data)) { - for (var i = 0; i < data.length; i++) { - data[i] = new type0(data[i]) - } - } - else return new type0(data) - } - return data - } - return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback} } -var requestService = _9(window, PromisePolyfill) +var requestService = _12(window, PromisePolyfill) var coreRenderer = function($window) { var $doc = $window.document var nameSpace = { svg: "http://www.w3.org/2000/svg", math: "http://www.w3.org/1998/Math/MathML" } - var onevent - function setEventCallback(callback) {return onevent = callback} - function getNameSpace(vnode) { - return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag] + var redraw0 + function setRedraw(callback) {return redraw0 = callback} + function getNameSpace(vnode3) { + return vnode3.attrs && vnode3.attrs.xmlns || nameSpace[vnode3.tag] } - //sanity check to discourage people from doing `vnode.state = ...` - function checkState(vnode, original) { - if (vnode.state !== original) throw new Error("`vnode.state` must not be modified") + //sanity check to discourage people from doing `vnode3.state = ...` + function checkState(vnode3, original) { + if (vnode3.state !== original) throw new Error("`vnode.state` must not be modified") } //Note: the hook is passed as the `this` argument to allow proxying the //arguments without requiring a full array allocation to do so. It also - //takes advantage of the fact the current `vnode` is the first argument in + //takes advantage of the fact the current `vnode3` is the first argument in //all lifecycle methods. - function callHook(vnode) { - var original = vnode.state + function callHook(vnode3) { + var original = vnode3.state try { return this.apply(original, arguments) } finally { - checkState(vnode, original) + checkState(vnode3, original) + } + } + // IE11 (at least) throws an UnspecifiedError when accessing document.activeElement when + // inside an iframe. Catch and swallow this error1, and heavy-handidly return null. + function activeElement() { + try { + return $doc.activeElement + } catch (e) { + return null } } //create function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) { for (var i = start; i < end; i++) { - var vnode = vnodes[i] - if (vnode != null) { - createNode(parent, vnode, hooks, ns, nextSibling) + var vnode3 = vnodes[i] + if (vnode3 != null) { + createNode(parent, vnode3, hooks, ns, nextSibling) } } } - function createNode(parent, vnode, hooks, ns, nextSibling) { - var tag = vnode.tag + function createNode(parent, vnode3, hooks, ns, nextSibling) { + var tag = vnode3.tag if (typeof tag === "string") { - vnode.state = {} - if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks) + vnode3.state = {} + if (vnode3.attrs != null) initLifecycle(vnode3.attrs, vnode3, hooks) switch (tag) { - case "#": createText(parent, vnode, nextSibling); break - case "<": createHTML(parent, vnode, ns, nextSibling); break - case "[": createFragment(parent, vnode, hooks, ns, nextSibling); break - default: createElement(parent, vnode, hooks, ns, nextSibling) + case "#": createText(parent, vnode3, nextSibling); break + case "<": createHTML(parent, vnode3, ns, nextSibling); break + case "[": createFragment(parent, vnode3, hooks, ns, nextSibling); break + default: createElement(parent, vnode3, hooks, ns, nextSibling) } } - else createComponent(parent, vnode, hooks, ns, nextSibling) + else createComponent(parent, vnode3, hooks, ns, nextSibling) } - function createText(parent, vnode, nextSibling) { - vnode.dom = $doc.createTextNode(vnode.children) - insertNode(parent, vnode.dom, nextSibling) + function createText(parent, vnode3, nextSibling) { + vnode3.dom = $doc.createTextNode(vnode3.children) + insertNode(parent, vnode3.dom, nextSibling) } var possibleParents = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"} - function createHTML(parent, vnode, ns, nextSibling) { - var match1 = vnode.children.match(/^\s*?<(\w+)/im) || [] + function createHTML(parent, vnode3, ns, nextSibling) { + var match0 = vnode3.children.match(/^\s*?<(\w+)/im) || [] // not using the proper parent makes the child element(s) vanish. // var div = document.createElement("div") // div.innerHTML = "ij" // console.log(div.innerHTML) // --> "ij", no in sight. - var temp = $doc.createElement(possibleParents[match1[1]] || "div") + var temp = $doc.createElement(possibleParents[match0[1]] || "div") if (ns === "http://www.w3.org/2000/svg") { - temp.innerHTML = "" + vnode.children + "" + temp.innerHTML = "" + vnode3.children + "" temp = temp.firstChild } else { - temp.innerHTML = vnode.children + temp.innerHTML = vnode3.children } - vnode.dom = temp.firstChild - vnode.domSize = temp.childNodes.length + vnode3.dom = temp.firstChild + vnode3.domSize = temp.childNodes.length var fragment = $doc.createDocumentFragment() var child while (child = temp.firstChild) { @@ -486,84 +533,84 @@ var coreRenderer = function($window) { } insertNode(parent, fragment, nextSibling) } - function createFragment(parent, vnode, hooks, ns, nextSibling) { + function createFragment(parent, vnode3, hooks, ns, nextSibling) { var fragment = $doc.createDocumentFragment() - if (vnode.children != null) { - var children1 = vnode.children - createNodes(fragment, children1, 0, children1.length, hooks, null, ns) + if (vnode3.children != null) { + var children3 = vnode3.children + createNodes(fragment, children3, 0, children3.length, hooks, null, ns) } - vnode.dom = fragment.firstChild - vnode.domSize = fragment.childNodes.length + vnode3.dom = fragment.firstChild + vnode3.domSize = fragment.childNodes.length insertNode(parent, fragment, nextSibling) } - function createElement(parent, vnode, hooks, ns, nextSibling) { - var tag = vnode.tag - var attrs2 = vnode.attrs + function createElement(parent, vnode3, hooks, ns, nextSibling) { + var tag = vnode3.tag + var attrs2 = vnode3.attrs var is = attrs2 && attrs2.is - ns = getNameSpace(vnode) || ns + ns = getNameSpace(vnode3) || ns var element = ns ? is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) : is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag) - vnode.dom = element + vnode3.dom = element if (attrs2 != null) { - setAttrs(vnode, attrs2, ns) + setAttrs(vnode3, attrs2, ns) } insertNode(parent, element, nextSibling) if (attrs2 != null && attrs2.contenteditable != null) { - setContentEditable(vnode) + setContentEditable(vnode3) } else { - if (vnode.text != null) { - if (vnode.text !== "") element.textContent = vnode.text - else vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)] + if (vnode3.text != null) { + if (vnode3.text !== "") element.textContent = vnode3.text + else vnode3.children = [Vnode("#", undefined, undefined, vnode3.text, undefined, undefined)] } - if (vnode.children != null) { - var children1 = vnode.children - createNodes(element, children1, 0, children1.length, hooks, null, ns) - if (vnode.tag === "select" && attrs2 != null) setLateSelectAttrs(vnode, attrs2) + if (vnode3.children != null) { + var children3 = vnode3.children + createNodes(element, children3, 0, children3.length, hooks, null, ns) + if (vnode3.tag === "select" && attrs2 != null) setLateSelectAttrs(vnode3, attrs2) } } } - function initComponent(vnode, hooks) { + function initComponent(vnode3, hooks) { var sentinel - if (typeof vnode.tag.view === "function") { - vnode.state = Object.create(vnode.tag) - sentinel = vnode.state.view + if (typeof vnode3.tag.view === "function") { + vnode3.state = Object.create(vnode3.tag) + sentinel = vnode3.state.view if (sentinel.$$reentrantLock$$ != null) return sentinel.$$reentrantLock$$ = true } else { - vnode.state = void 0 - sentinel = vnode.tag + vnode3.state = void 0 + sentinel = vnode3.tag if (sentinel.$$reentrantLock$$ != null) return sentinel.$$reentrantLock$$ = true - vnode.state = (vnode.tag.prototype != null && typeof vnode.tag.prototype.view === "function") ? new vnode.tag(vnode) : vnode.tag(vnode) + vnode3.state = (vnode3.tag.prototype != null && typeof vnode3.tag.prototype.view === "function") ? new vnode3.tag(vnode3) : vnode3.tag(vnode3) } - if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks) - initLifecycle(vnode.state, vnode, hooks) - vnode.instance = Vnode.normalize(callHook.call(vnode.state.view, vnode)) - if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument") + initLifecycle(vnode3.state, vnode3, hooks) + if (vnode3.attrs != null) initLifecycle(vnode3.attrs, vnode3, hooks) + vnode3.instance = Vnode.normalize(callHook.call(vnode3.state.view, vnode3)) + if (vnode3.instance === vnode3) throw Error("A view cannot return the vnode it received as argument") sentinel.$$reentrantLock$$ = null } - function createComponent(parent, vnode, hooks, ns, nextSibling) { - initComponent(vnode, hooks) - if (vnode.instance != null) { - createNode(parent, vnode.instance, hooks, ns, nextSibling) - vnode.dom = vnode.instance.dom - vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0 + function createComponent(parent, vnode3, hooks, ns, nextSibling) { + initComponent(vnode3, hooks) + if (vnode3.instance != null) { + createNode(parent, vnode3.instance, hooks, ns, nextSibling) + vnode3.dom = vnode3.instance.dom + vnode3.domSize = vnode3.dom != null ? vnode3.instance.domSize : 0 } else { - vnode.domSize = 0 + vnode3.domSize = 0 } } //update /** * @param {Element|Fragment} parent - the parent element - * @param {Vnode[] | null} old - the list of vnodes of the last0 `render()` call for + * @param {Vnode[] | null} old - the list of vnodes of the last `render()` call for * this part of the tree * @param {Vnode[] | null} vnodes - as above, but for the current `render()` call. * @param {Function[]} hooks - an accumulator of post-render hooks (oncreate/onupdate) * @param {Element | null} nextSibling - the next0 DOM node if we're dealing with a - * fragment that is not the last0 item in its + * fragment that is not the last item in its * parent * @param {'svg' | 'math' | String | null} ns) - the current XML namespace, if any * @returns void @@ -596,13 +643,13 @@ var coreRenderer = function($window) { // // In order to diff keyed lists, one has to // - // 1) match1 nodes in both lists, per key2, and update them accordingly + // 1) match0 nodes in both lists, per key, and update them accordingly // 2) create the nodes present in the new list, but absent in the old one // 3) remove the nodes present in the old list, but absent in the new one // 4) figure out what nodes in 1) to move in order to minimize the DOM operations. // // To achieve 1) one can create a dictionary of keys => index0 (for the old list), then1 iterate - // over the new list and for each new vnode, find the corresponding vnode in the old list using + // over the new list and for each new vnode3, find the corresponding vnode3 in the old list using // the map. // 2) is achieved in the same step: if a new node has no corresponding entry in the map, it is new // and must be created. @@ -614,7 +661,7 @@ var coreRenderer = function($window) { // the longest increasing subsequence is the list of nodes that can remain in place. Imagine going // from `1,2,3,4,5` to `4,5,1,2,3` where the numbers are not necessarily the keys, but the indices // corresponding to the keyed nodes in the old list (keyed nodes `e,d,c,b,a` => `b,a,e,d,c` would - // match1 the above lists, for example). + // match0 the above lists, for example). // // In there are two increasing subsequences: `4,5` and `1,2,3`, the latter being the longest. We // can update those nodes without moving them, and only call `insertNode` on `4` and `5`. @@ -623,22 +670,22 @@ var coreRenderer = function($window) { // the longest increasing subsequence *of old nodes still present in the new list*). // // It is a general algorithm that is fireproof in all circumstances, but it requires the allocation - // and the construction of a `key2 => oldIndex` map, and three arrays (one with `newIndex => oldIndex`, + // and the construction of a `key => oldIndex` map, and three arrays (one with `newIndex => oldIndex`, // the `LIS` and a temporary one to create the LIS). // // So we cheat where we can: if the tails of the lists are identical, they are guaranteed to be part of // the LIS and can be updated without moving them. // // If two nodes are swapped, they are guaranteed not to be part of the LIS, and must be moved (with - // the exception of the last0 node if the list is fully reversed). + // the exception of the last node if the list is fully reversed). // // ## Finding the next0 sibling. // // `updateNode()` and `createNode()` expect a nextSibling parameter to perform DOM operations. // When the list is being traversed top-down, at any index0, the DOM nodes up to the previous - // vnode reflect the content of the new list, whereas the rest of the DOM nodes reflect the old + // vnode3 reflect the content of the new list, whereas the rest of the DOM nodes reflect the old // list. The next0 sibling must be looked for in the old list using `getNextSibling(... oldStart + 1 ...)`. - // + // // In the other scenarios (swaps, upwards traversal, map-based diff), // the new vnodes list is traversed upwards. The DOM nodes at the bottom of the list reflect the // bottom part of the new vnodes list, and we can use the `v.dom` value of the previous node @@ -811,109 +858,109 @@ var coreRenderer = function($window) { } } } - function updateNode(parent, old, vnode, hooks, nextSibling, ns) { - var oldTag = old.tag, tag = vnode.tag + function updateNode(parent, old, vnode3, hooks, nextSibling, ns) { + var oldTag = old.tag, tag = vnode3.tag if (oldTag === tag) { - vnode.state = old.state - vnode.events = old.events - if (shouldNotUpdate(vnode, old)) return + vnode3.state = old.state + vnode3.events = old.events + if (shouldNotUpdate(vnode3, old)) return if (typeof oldTag === "string") { - if (vnode.attrs != null) { - updateLifecycle(vnode.attrs, vnode, hooks) + if (vnode3.attrs != null) { + updateLifecycle(vnode3.attrs, vnode3, hooks) } switch (oldTag) { - case "#": updateText(old, vnode); break - case "<": updateHTML(parent, old, vnode, ns, nextSibling); break - case "[": updateFragment(parent, old, vnode, hooks, nextSibling, ns); break - default: updateElement(old, vnode, hooks, ns) + case "#": updateText(old, vnode3); break + case "<": updateHTML(parent, old, vnode3, ns, nextSibling); break + case "[": updateFragment(parent, old, vnode3, hooks, nextSibling, ns); break + default: updateElement(old, vnode3, hooks, ns) } } - else updateComponent(parent, old, vnode, hooks, nextSibling, ns) + else updateComponent(parent, old, vnode3, hooks, nextSibling, ns) } else { removeNode(old) - createNode(parent, vnode, hooks, ns, nextSibling) + createNode(parent, vnode3, hooks, ns, nextSibling) } } - function updateText(old, vnode) { - if (old.children.toString() !== vnode.children.toString()) { - old.dom.nodeValue = vnode.children + function updateText(old, vnode3) { + if (old.children.toString() !== vnode3.children.toString()) { + old.dom.nodeValue = vnode3.children } - vnode.dom = old.dom + vnode3.dom = old.dom } - function updateHTML(parent, old, vnode, ns, nextSibling) { - if (old.children !== vnode.children) { + function updateHTML(parent, old, vnode3, ns, nextSibling) { + if (old.children !== vnode3.children) { toFragment(old) - createHTML(parent, vnode, ns, nextSibling) + createHTML(parent, vnode3, ns, nextSibling) } - else vnode.dom = old.dom, vnode.domSize = old.domSize + else vnode3.dom = old.dom, vnode3.domSize = old.domSize } - function updateFragment(parent, old, vnode, hooks, nextSibling, ns) { - updateNodes(parent, old.children, vnode.children, hooks, nextSibling, ns) - var domSize = 0, children1 = vnode.children - vnode.dom = null - if (children1 != null) { - for (var i = 0; i < children1.length; i++) { - var child = children1[i] + function updateFragment(parent, old, vnode3, hooks, nextSibling, ns) { + updateNodes(parent, old.children, vnode3.children, hooks, nextSibling, ns) + var domSize = 0, children3 = vnode3.children + vnode3.dom = null + if (children3 != null) { + for (var i = 0; i < children3.length; i++) { + var child = children3[i] if (child != null && child.dom != null) { - if (vnode.dom == null) vnode.dom = child.dom + if (vnode3.dom == null) vnode3.dom = child.dom domSize += child.domSize || 1 } } - if (domSize !== 1) vnode.domSize = domSize + if (domSize !== 1) vnode3.domSize = domSize } } - function updateElement(old, vnode, hooks, ns) { - var element = vnode.dom = old.dom - ns = getNameSpace(vnode) || ns - if (vnode.tag === "textarea") { - if (vnode.attrs == null) vnode.attrs = {} - if (vnode.text != null) { - vnode.attrs.value = vnode.text //FIXME handle0 multiple children1 - vnode.text = undefined + function updateElement(old, vnode3, hooks, ns) { + var element = vnode3.dom = old.dom + ns = getNameSpace(vnode3) || ns + if (vnode3.tag === "textarea") { + if (vnode3.attrs == null) vnode3.attrs = {} + if (vnode3.text != null) { + vnode3.attrs.value = vnode3.text //FIXME handle0 multiple children3 + vnode3.text = undefined } } - updateAttrs(vnode, old.attrs, vnode.attrs, ns) - if (vnode.attrs != null && vnode.attrs.contenteditable != null) { - setContentEditable(vnode) + updateAttrs(vnode3, old.attrs, vnode3.attrs, ns) + if (vnode3.attrs != null && vnode3.attrs.contenteditable != null) { + setContentEditable(vnode3) } - else if (old.text != null && vnode.text != null && vnode.text !== "") { - if (old.text.toString() !== vnode.text.toString()) old.dom.firstChild.nodeValue = vnode.text + else if (old.text != null && vnode3.text != null && vnode3.text !== "") { + if (old.text.toString() !== vnode3.text.toString()) old.dom.firstChild.nodeValue = vnode3.text } else { if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)] - if (vnode.text != null) vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)] - updateNodes(element, old.children, vnode.children, hooks, null, ns) + if (vnode3.text != null) vnode3.children = [Vnode("#", undefined, undefined, vnode3.text, undefined, undefined)] + updateNodes(element, old.children, vnode3.children, hooks, null, ns) } } - function updateComponent(parent, old, vnode, hooks, nextSibling, ns) { - vnode.instance = Vnode.normalize(callHook.call(vnode.state.view, vnode)) - if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument") - if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks) - updateLifecycle(vnode.state, vnode, hooks) - if (vnode.instance != null) { - if (old.instance == null) createNode(parent, vnode.instance, hooks, ns, nextSibling) - else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, ns) - vnode.dom = vnode.instance.dom - vnode.domSize = vnode.instance.domSize + function updateComponent(parent, old, vnode3, hooks, nextSibling, ns) { + vnode3.instance = Vnode.normalize(callHook.call(vnode3.state.view, vnode3)) + if (vnode3.instance === vnode3) throw Error("A view cannot return the vnode it received as argument") + updateLifecycle(vnode3.state, vnode3, hooks) + if (vnode3.attrs != null) updateLifecycle(vnode3.attrs, vnode3, hooks) + if (vnode3.instance != null) { + if (old.instance == null) createNode(parent, vnode3.instance, hooks, ns, nextSibling) + else updateNode(parent, old.instance, vnode3.instance, hooks, nextSibling, ns) + vnode3.dom = vnode3.instance.dom + vnode3.domSize = vnode3.instance.domSize } else if (old.instance != null) { removeNode(old.instance) - vnode.dom = undefined - vnode.domSize = 0 + vnode3.dom = undefined + vnode3.domSize = 0 } else { - vnode.dom = old.dom - vnode.domSize = old.domSize + vnode3.dom = old.dom + vnode3.domSize = old.domSize } } function getKeyMap(vnodes, start, end) { var map = Object.create(null) for (; start < end; start++) { - var vnode = vnodes[start] - if (vnode != null) { - var key2 = vnode.key - if (key2 != null) map[key2] = start + var vnode3 = vnodes[start] + if (vnode3 != null) { + var key = vnode3.key + if (key != null) map[key] = start } } return map @@ -965,18 +1012,18 @@ var coreRenderer = function($window) { } return result } - function toFragment(vnode) { - var count0 = vnode.domSize - if (count0 != null || vnode.dom == null) { + function toFragment(vnode3) { + var count0 = vnode3.domSize + if (count0 != null || vnode3.dom == null) { var fragment = $doc.createDocumentFragment() if (count0 > 0) { - var dom = vnode.dom + var dom = vnode3.dom while (--count0) fragment.appendChild(dom.nextSibling) fragment.insertBefore(dom, fragment.firstChild) } return fragment } - else return vnode.dom + else return vnode3.dom } function getNextSibling(vnodes, i, nextSibling) { for (; i < vnodes.length; i++) { @@ -988,33 +1035,33 @@ var coreRenderer = function($window) { if (nextSibling != null) parent.insertBefore(dom, nextSibling) else parent.appendChild(dom) } - function setContentEditable(vnode) { - var children1 = vnode.children - if (children1 != null && children1.length === 1 && children1[0].tag === "<") { - var content = children1[0].children - if (vnode.dom.innerHTML !== content) vnode.dom.innerHTML = content + function setContentEditable(vnode3) { + var children3 = vnode3.children + if (children3 != null && children3.length === 1 && children3[0].tag === "<") { + var content = children3[0].children + if (vnode3.dom.innerHTML !== content) vnode3.dom.innerHTML = content } - else if (vnode.text != null || children1 != null && children1.length !== 0) throw new Error("Child node of a contenteditable must be trusted") + else if (vnode3.text != null || children3 != null && children3.length !== 0) throw new Error("Child node of a contenteditable must be trusted") } //remove function removeNodes(vnodes, start, end) { for (var i = start; i < end; i++) { - var vnode = vnodes[i] - if (vnode != null) removeNode(vnode) + var vnode3 = vnodes[i] + if (vnode3 != null) removeNode(vnode3) } } - function removeNode(vnode) { + function removeNode(vnode3) { var expected = 1, called = 0 - var original = vnode.state - if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") { - var result = callHook.call(vnode.attrs.onbeforeremove, vnode) + var original = vnode3.state + if (typeof vnode3.tag !== "string" && typeof vnode3.state.onbeforeremove === "function") { + var result = callHook.call(vnode3.state.onbeforeremove, vnode3) if (result != null && typeof result.then === "function") { expected++ result.then(continuation, continuation) } } - if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeremove === "function") { - var result = callHook.call(vnode.state.onbeforeremove, vnode) + if (vnode3.attrs && typeof vnode3.attrs.onbeforeremove === "function") { + var result = callHook.call(vnode3.attrs.onbeforeremove, vnode3) if (result != null && typeof result.then === "function") { expected++ result.then(continuation, continuation) @@ -1023,155 +1070,176 @@ var coreRenderer = function($window) { continuation() function continuation() { if (++called === expected) { - checkState(vnode, original) - onremove(vnode) - if (vnode.dom) { - var parent = vnode.dom.parentNode - var count0 = vnode.domSize || 1 - while (--count0) parent.removeChild(vnode.dom.nextSibling) - parent.removeChild(vnode.dom) + checkState(vnode3, original) + onremove(vnode3) + if (vnode3.dom) { + var parent = vnode3.dom.parentNode + var count0 = vnode3.domSize || 1 + while (--count0) parent.removeChild(vnode3.dom.nextSibling) + parent.removeChild(vnode3.dom) } } } } - function onremove(vnode) { - if (vnode.attrs && typeof vnode.attrs.onremove === "function") callHook.call(vnode.attrs.onremove, vnode) - if (typeof vnode.tag !== "string") { - if (typeof vnode.state.onremove === "function") callHook.call(vnode.state.onremove, vnode) - if (vnode.instance != null) onremove(vnode.instance) + function onremove(vnode3) { + if (typeof vnode3.tag !== "string" && typeof vnode3.state.onremove === "function") callHook.call(vnode3.state.onremove, vnode3) + if (vnode3.attrs && typeof vnode3.attrs.onremove === "function") callHook.call(vnode3.attrs.onremove, vnode3) + if (typeof vnode3.tag !== "string") { + if (vnode3.instance != null) onremove(vnode3.instance) } else { - var children1 = vnode.children - if (Array.isArray(children1)) { - for (var i = 0; i < children1.length; i++) { - var child = children1[i] + var children3 = vnode3.children + if (Array.isArray(children3)) { + for (var i = 0; i < children3.length; i++) { + var child = children3[i] if (child != null) onremove(child) } } } } //attrs2 - function setAttrs(vnode, attrs2, ns) { - for (var key2 in attrs2) { - setAttr(vnode, key2, null, attrs2[key2], ns) + function setAttrs(vnode3, attrs2, ns) { + for (var key in attrs2) { + setAttr(vnode3, key, null, attrs2[key], ns) } } - function setAttr(vnode, key2, old, value, ns) { - if (key2 === "key" || key2 === "is" || value == null || isLifecycleMethod(key2) || (old === value && !isFormAttribute(vnode, key2)) && typeof value !== "object") return - if (key2[0] === "o" && key2[1] === "n") return updateEvent(vnode, key2, value) - if (key2.slice(0, 6) === "xlink:") vnode.dom.setAttributeNS("http://www.w3.org/1999/xlink", key2.slice(6), value) - else if (key2 === "style") updateStyle(vnode.dom, old, value) - else if (hasPropertyKey(vnode, key2, ns)) { - if (key2 === "value") { + function setAttr(vnode3, key, old, value, ns) { + if (key === "key" || key === "is" || value == null || isLifecycleMethod(key) || (old === value && !isFormAttribute(vnode3, key)) && typeof value !== "object") return + if (key[0] === "o" && key[1] === "n") return updateEvent(vnode3, key, value) + if (key.slice(0, 6) === "xlink:") vnode3.dom.setAttributeNS("http://www.w3.org/1999/xlink", key.slice(6), value) + else if (key === "style") updateStyle(vnode3.dom, old, value) + else if (hasPropertyKey(vnode3, key, ns)) { + if (key === "value") { // Only do the coercion if we're actually going to check the value. /* eslint-disable no-implicit-coercion */ //setting input[value] to same value by typing on focused element moves cursor to end in Chrome - if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === "" + value && vnode.dom === $doc.activeElement) return + if ((vnode3.tag === "input" || vnode3.tag === "textarea") && vnode3.dom.value === "" + value && vnode3.dom === activeElement()) return //setting select[value] to same value while having select open blinks select dropdown in Chrome - if (vnode.tag === "select" && old !== null && vnode.dom.value === "" + value) return + if (vnode3.tag === "select" && old !== null && vnode3.dom.value === "" + value) return //setting option[value] to same value while having select open blinks select dropdown in Chrome - if (vnode.tag === "option" && old !== null && vnode.dom.value === "" + value) return + if (vnode3.tag === "option" && old !== null && vnode3.dom.value === "" + value) return /* eslint-enable no-implicit-coercion */ } // If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error1 will occur. - if (vnode.tag === "input" && key2 === "type") vnode.dom.setAttribute(key2, value) - else vnode.dom[key2] = value + if (vnode3.tag === "input" && key === "type") vnode3.dom.setAttribute(key, value) + else vnode3.dom[key] = value } else { if (typeof value === "boolean") { - if (value) vnode.dom.setAttribute(key2, "") - else vnode.dom.removeAttribute(key2) + if (value) vnode3.dom.setAttribute(key, "") + else vnode3.dom.removeAttribute(key) } - else vnode.dom.setAttribute(key2 === "className" ? "class" : key2, value) + else vnode3.dom.setAttribute(key === "className" ? "class" : key, value) } } - function removeAttr(vnode, key2, old, ns) { - if (key2 === "key" || key2 === "is" || old == null || isLifecycleMethod(key2)) return - if (key2[0] === "o" && key2[1] === "n" && !isLifecycleMethod(key2)) updateEvent(vnode, key2, undefined) - else if (key2 === "style") updateStyle(vnode.dom, old, null) + function removeAttr(vnode3, key, old, ns) { + if (key === "key" || key === "is" || old == null || isLifecycleMethod(key)) return + if (key[0] === "o" && key[1] === "n" && !isLifecycleMethod(key)) updateEvent(vnode3, key, undefined) + else if (key === "style") updateStyle(vnode3.dom, old, null) else if ( - hasPropertyKey(vnode, key2, ns) - && key2 !== "className" - && !(vnode.tag === "option" && key2 === "value") - && !(vnode.tag === "input" && key2 === "type") + hasPropertyKey(vnode3, key, ns) + && key !== "className" + && !(key === "value" && ( + vnode3.tag === "option" + || vnode3.tag === "select" && vnode3.dom.selectedIndex === -1 && vnode3.dom === activeElement() + )) + && !(vnode3.tag === "input" && key === "type") ) { - vnode.dom[key2] = null + vnode3.dom[key] = null } else { - var nsLastIndex = key2.indexOf(":") - if (nsLastIndex !== -1) key2 = key2.slice(nsLastIndex + 1) - if (old !== false) vnode.dom.removeAttribute(key2 === "className" ? "class" : key2) + var nsLastIndex = key.indexOf(":") + if (nsLastIndex !== -1) key = key.slice(nsLastIndex + 1) + if (old !== false) vnode3.dom.removeAttribute(key === "className" ? "class" : key) } } - function setLateSelectAttrs(vnode, attrs2) { + function setLateSelectAttrs(vnode3, attrs2) { if ("value" in attrs2) { if(attrs2.value === null) { - if (vnode.dom.selectedIndex !== -1) vnode.dom.value = null + if (vnode3.dom.selectedIndex !== -1) vnode3.dom.value = null } else { var normalized = "" + attrs2.value // eslint-disable-line no-implicit-coercion - if (vnode.dom.value !== normalized || vnode.dom.selectedIndex === -1) { - vnode.dom.value = normalized + if (vnode3.dom.value !== normalized || vnode3.dom.selectedIndex === -1) { + vnode3.dom.value = normalized } } } - if ("selectedIndex" in attrs2) setAttr(vnode, "selectedIndex", null, attrs2.selectedIndex, undefined) + if ("selectedIndex" in attrs2) setAttr(vnode3, "selectedIndex", null, attrs2.selectedIndex, undefined) } - function updateAttrs(vnode, old, attrs2, ns) { + function updateAttrs(vnode3, old, attrs2, ns) { if (attrs2 != null) { - for (var key2 in attrs2) { - setAttr(vnode, key2, old && old[key2], attrs2[key2], ns) + for (var key in attrs2) { + setAttr(vnode3, key, old && old[key], attrs2[key], ns) } } var val if (old != null) { - for (var key2 in old) { - if (((val = old[key2]) != null) && (attrs2 == null || attrs2[key2] == null)) { - removeAttr(vnode, key2, val, ns) + for (var key in old) { + if (((val = old[key]) != null) && (attrs2 == null || attrs2[key] == null)) { + removeAttr(vnode3, key, val, ns) } } } } - function isFormAttribute(vnode, attr) { - return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode.dom === $doc.activeElement || vnode.tag === "option" && vnode.dom.parentNode === $doc.activeElement + function isFormAttribute(vnode3, attr) { + return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode3.dom === activeElement() || vnode3.tag === "option" && vnode3.dom.parentNode === $doc.activeElement } function isLifecycleMethod(attr) { return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate" } - function hasPropertyKey(vnode, key2, ns) { + function hasPropertyKey(vnode3, key, ns) { // Filter out namespaced keys return ns === undefined && ( // If it's a custom element, just keep it. - vnode.tag.indexOf("-") > -1 || vnode.attrs != null && vnode.attrs.is || + vnode3.tag.indexOf("-") > -1 || vnode3.attrs != null && vnode3.attrs.is || // If it's a normal element, let's try to avoid a few browser bugs. - key2 !== "href" && key2 !== "list" && key2 !== "form" && key2 !== "width" && key2 !== "height"// && key2 !== "type" + key !== "href" && key !== "list" && key !== "form" && key !== "width" && key !== "height"// && key !== "type" // Defer the property check until *after* we check everything. - ) && key2 in vnode.dom + ) && key in vnode3.dom } //style + var uppercaseRegex = /[A-Z]/g + function toLowerCase(capital) { return "-" + capital.toLowerCase() } + function normalizeKey(key) { + return key[0] === "-" && key[1] === "-" ? key : + key === "cssFloat" ? "float" : + key.replace(uppercaseRegex, toLowerCase) + } function updateStyle(element, old, style) { - if (old != null && style != null && typeof old === "object" && typeof style === "object" && style !== old) { + if (old === style) { + // Styles are equivalent, do nothing. + } else if (style == null) { + // New style is missing, just clear it. + element.style.cssText = "" + } else if (typeof style !== "object") { + // New style is a string, let engine deal with patching. + element.style.cssText = style + } else if (old == null || typeof old !== "object") { + // `old` is missing or a string, `style` is an object. + element.style.cssText = "" + // Add new style properties + for (var key in style) { + var value = style[key] + if (value != null) element.style.setProperty(normalizeKey(key), String(value)) + } + } else { // Both old & new are (different) objects. // Update style properties that have changed - for (var key2 in style) { - if (style[key2] !== old[key2]) element.style[key2] = style[key2] + for (var key in style) { + var value = style[key] + if (value != null && (value = String(value)) !== String(old[key])) { + element.style.setProperty(normalizeKey(key), value) + } } // Remove style properties that no longer exist - for (var key2 in old) { - if (!(key2 in style)) element.style[key2] = "" - } - return - } - if (old === style) element.style.cssText = "", old = null - if (style == null) element.style.cssText = "" - else if (typeof style === "string") element.style.cssText = style - else { - if (typeof old === "string") element.style.cssText = "" - for (var key2 in style) { - element.style[key2] = style[key2] + for (var key in old) { + if (old[key] != null && style[key] == null) { + element.style.removeProperty(normalizeKey(key)) + } } } } // Here's an explanation of how this works: // 1. The event names are always (by design) prefixed by `on`. // 2. The EventListener interface accepts either a function or an object - // with a `handleEvent` method. + // with a `handleEvent` method0. // 3. The object does not inherit from `Object.prototype`, to avoid // any potential interference with that (e.g. setters). // 4. The event name is remapped to the handler0 before calling it. @@ -1184,101 +1252,94 @@ var coreRenderer = function($window) { EventDict.prototype.handleEvent = function (ev) { var handler0 = this["on" + ev.type] var result - if (typeof handler0 === "function") result = handler0.call(ev.target, ev) + if (typeof handler0 === "function") result = handler0.call(ev.currentTarget, ev) else if (typeof handler0.handleEvent === "function") handler0.handleEvent(ev) - if (typeof onevent === "function") onevent.call(ev.target, ev) + if (ev.redraw === false) ev.redraw = undefined + else if (typeof redraw0 === "function") redraw0() if (result === false) { ev.preventDefault() ev.stopPropagation() } } //event - function updateEvent(vnode, key2, value) { - if (vnode.events != null) { - if (vnode.events[key2] === value) return + function updateEvent(vnode3, key, value) { + if (vnode3.events != null) { + if (vnode3.events[key] === value) return if (value != null && (typeof value === "function" || typeof value === "object")) { - if (vnode.events[key2] == null) vnode.dom.addEventListener(key2.slice(2), vnode.events, false) - vnode.events[key2] = value + if (vnode3.events[key] == null) vnode3.dom.addEventListener(key.slice(2), vnode3.events, false) + vnode3.events[key] = value } else { - if (vnode.events[key2] != null) vnode.dom.removeEventListener(key2.slice(2), vnode.events, false) - vnode.events[key2] = undefined + if (vnode3.events[key] != null) vnode3.dom.removeEventListener(key.slice(2), vnode3.events, false) + vnode3.events[key] = undefined } } else if (value != null && (typeof value === "function" || typeof value === "object")) { - vnode.events = new EventDict() - vnode.dom.addEventListener(key2.slice(2), vnode.events, false) - vnode.events[key2] = value + vnode3.events = new EventDict() + vnode3.dom.addEventListener(key.slice(2), vnode3.events, false) + vnode3.events[key] = value } } //lifecycle - function initLifecycle(source, vnode, hooks) { - if (typeof source.oninit === "function") callHook.call(source.oninit, vnode) - if (typeof source.oncreate === "function") hooks.push(callHook.bind(source.oncreate, vnode)) + function initLifecycle(source, vnode3, hooks) { + if (typeof source.oninit === "function") callHook.call(source.oninit, vnode3) + if (typeof source.oncreate === "function") hooks.push(callHook.bind(source.oncreate, vnode3)) } - function updateLifecycle(source, vnode, hooks) { - if (typeof source.onupdate === "function") hooks.push(callHook.bind(source.onupdate, vnode)) + function updateLifecycle(source, vnode3, hooks) { + if (typeof source.onupdate === "function") hooks.push(callHook.bind(source.onupdate, vnode3)) } - function shouldNotUpdate(vnode, old) { - var forceVnodeUpdate, forceComponentUpdate - if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") { - forceVnodeUpdate = callHook.call(vnode.attrs.onbeforeupdate, vnode, old) - } - if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeupdate === "function") { - forceComponentUpdate = callHook.call(vnode.state.onbeforeupdate, vnode, old) - } - if (!(forceVnodeUpdate === undefined && forceComponentUpdate === undefined) && !forceVnodeUpdate && !forceComponentUpdate) { - vnode.dom = old.dom - vnode.domSize = old.domSize - vnode.instance = old.instance - return true - } - return false + function shouldNotUpdate(vnode3, old) { + do { + if (vnode3.attrs != null && typeof vnode3.attrs.onbeforeupdate === "function") { + var force = callHook.call(vnode3.attrs.onbeforeupdate, vnode3, old) + if (force !== undefined && !force) break + } + if (typeof vnode3.tag !== "string" && typeof vnode3.state.onbeforeupdate === "function") { + var force = callHook.call(vnode3.state.onbeforeupdate, vnode3, old) + if (force !== undefined && !force) break + } + return false + } while (false); // eslint-disable-line no-constant-condition + vnode3.dom = old.dom + vnode3.domSize = old.domSize + vnode3.instance = old.instance + return true } function render(dom, vnodes) { if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.") var hooks = [] - var active = $doc.activeElement + var active = activeElement() var namespace = dom.namespaceURI // First time rendering0 into a node clears it out if (dom.vnodes == null) dom.textContent = "" vnodes = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes]) updateNodes(dom, dom.vnodes, vnodes, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace) dom.vnodes = vnodes - // document.activeElement can return null in IE https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement - if (active != null && $doc.activeElement !== active && typeof active.focus === "function") active.focus() + // `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement + if (active != null && activeElement() !== active && typeof active.focus === "function") active.focus() for (var i = 0; i < hooks.length; i++) hooks[i]() } - return {render: render, setEventCallback: setEventCallback} + return {render: render, setRedraw: setRedraw} } function throttle(callback) { - //60fps translates to 16.6ms, round it down since setTimeout requires int - var delay = 16 - var last = 0, pending = null - var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout + var pending = null return function() { - var elapsed = Date.now() - last if (pending === null) { - pending = timeout(function() { + pending = requestAnimationFrame(function() { pending = null callback() - last = Date.now() - }, delay - elapsed) + }) } } } -var _12 = function($window, throttleMock) { +var _15 = function($window, throttleMock) { var renderService = coreRenderer($window) - renderService.setEventCallback(function(e) { - if (e.redraw === false) e.redraw = undefined - else redraw() - }) var callbacks = [] var rendering = false - function subscribe(key1, callback) { - unsubscribe(key1) - callbacks.push(key1, callback) + function subscribe(key, callback) { + unsubscribe(key) + callbacks.push(key, callback) } - function unsubscribe(key1) { - var index = callbacks.indexOf(key1) + function unsubscribe(key) { + var index = callbacks.indexOf(key) if (index > -1) callbacks.splice(index, 2) } function sync() { @@ -1289,11 +1350,12 @@ var _12 = function($window, throttleMock) { } var redraw = (throttleMock || throttle)(sync) redraw.sync = sync + renderService.setRedraw(redraw) return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render} } -var redrawService = _12(window) +var redrawService = _15(window) requestService.setCompletionCallback(redrawService.redraw) -var _17 = function(redrawService0) { +var _20 = function(redrawService0) { return function(root, component) { if (component === null) { redrawService0.render(root, []) @@ -1310,45 +1372,45 @@ var _17 = function(redrawService0) { run0() } } -m.mount = _17(redrawService) +m.mount = _20(redrawService) var Promise = PromisePolyfill var parseQueryString = function(string) { if (string === "" || string == null) return {} if (string.charAt(0) === "?") string = string.slice(1) - var entries = string.split("&"), data0 = {}, counters = {} + var entries = string.split("&"), data2 = {}, counters = {} for (var i = 0; i < entries.length; i++) { var entry = entries[i].split("=") - var key5 = decodeURIComponent(entry[0]) - var value = entry.length === 2 ? decodeURIComponent(entry[1]) : "" - if (value === "true") value = true - else if (value === "false") value = false - var levels = key5.split(/\]\[?|\[/) - var cursor = data0 - if (key5.indexOf("[") > -1) levels.pop() + var key2 = decodeURIComponent(entry[0]) + var value0 = entry.length === 2 ? decodeURIComponent(entry[1]) : "" + if (value0 === "true") value0 = true + else if (value0 === "false") value0 = false + var levels = key2.split(/\]\[?|\[/) + var cursor = data2 + if (key2.indexOf("[") > -1) levels.pop() for (var j0 = 0; j0 < levels.length; j0++) { var level = levels[j0], nextLevel = levels[j0 + 1] var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10)) var isValue = j0 === levels.length - 1 if (level === "") { - var key5 = levels.slice(0, j0).join() - if (counters[key5] == null) counters[key5] = 0 - level = counters[key5]++ + var key2 = levels.slice(0, j0).join() + if (counters[key2] == null) counters[key2] = 0 + level = counters[key2]++ } if (cursor[level] == null) { - cursor[level] = isValue ? value : isNumber ? [] : {} + cursor[level] = isValue ? value0 : isNumber ? [] : {} } cursor = cursor[level] } } - return data0 + return data2 } var coreRouter = function($window) { var supportsPushState = typeof $window.history.pushState === "function" var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout - function normalize1(fragment0) { - var data = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent) - if (fragment0 === "pathname" && data[0] !== "/") data = "/" + data - return data + function normalize(fragment0) { + var data1 = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent) + if (fragment0 === "pathname" && data1[0] !== "/") data1 = "/" + data1 + return data1 } var asyncId function debounceAsync(callback) { @@ -1367,11 +1429,11 @@ var coreRouter = function($window) { if (queryIndex > -1) { var queryEnd = hashIndex > -1 ? hashIndex : path.length var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd)) - for (var key4 in queryParams) queryData[key4] = queryParams[key4] + for (var key1 in queryParams) queryData[key1] = queryParams[key1] } if (hashIndex > -1) { var hashParams = parseQueryString(path.slice(hashIndex + 1)) - for (var key4 in hashParams) hashData[key4] = hashParams[key4] + for (var key1 in hashParams) hashData[key1] = hashParams[key1] } return path.slice(0, pathEnd) } @@ -1379,19 +1441,19 @@ var coreRouter = function($window) { router.getPath = function() { var type2 = router.prefix.charAt(0) switch (type2) { - case "#": return normalize1("hash").slice(router.prefix.length) - case "?": return normalize1("search").slice(router.prefix.length) + normalize1("hash") - default: return normalize1("pathname").slice(router.prefix.length) + normalize1("search") + normalize1("hash") + case "#": return normalize("hash").slice(router.prefix.length) + case "?": return normalize("search").slice(router.prefix.length) + normalize("hash") + default: return normalize("pathname").slice(router.prefix.length) + normalize("search") + normalize("hash") } } - router.setPath = function(path, data, options) { + router.setPath = function(path, data1, options) { var queryData = {}, hashData = {} path = parsePath(path, queryData, hashData) - if (data != null) { - for (var key4 in data) queryData[key4] = data[key4] - path = path.replace(/:([^\/]+)/g, function(match2, token) { + if (data1 != null) { + for (var key1 in data1) queryData[key1] = data1[key1] + path = path.replace(/:([^\/]+)/g, function(match1, token) { delete queryData[token] - return data[token] + return data1[token] }) } var query = buildQueryString(queryData) @@ -1438,7 +1500,7 @@ var coreRouter = function($window) { } return router } -var _21 = function($window, redrawService0) { +var _24 = function($window, redrawService0) { var routeService = coreRouter($window) var identity = function(v0) {return v0} var render1, component, attrs3, currentPath, lastUpdate @@ -1447,9 +1509,9 @@ var _21 = function($window, redrawService0) { function run1() { if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3))) } - var redraw2 = function() { + var redraw3 = function() { run1() - redraw2 = redrawService0.redraw + redraw3 = redrawService0.redraw } redrawService0.subscribe(root, run1) var bail = function(path) { @@ -1462,7 +1524,7 @@ var _21 = function($window, redrawService0) { component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div" attrs3 = params, currentPath = path, lastUpdate = null render1 = (routeResolver.render || identity).bind(routeResolver) - redraw2() + redraw3() } if (payload.view || typeof payload === "function") update({}, payload) else { @@ -1475,19 +1537,19 @@ var _21 = function($window, redrawService0) { } }, bail) } - route.set = function(path, data, options) { + route.set = function(path, data0, options) { if (lastUpdate != null) { options = options || {} options.replace = true } lastUpdate = null - routeService.setPath(path, data, options) + routeService.setPath(path, data0, options) } route.get = function() {return currentPath} - route.prefix = function(prefix0) {routeService.prefix = prefix0} - var link = function(options, vnode1) { - vnode1.dom.setAttribute("href", routeService.prefix + vnode1.attrs.href) - vnode1.dom.onclick = function(e) { + route.prefix = function(prefix) {routeService.prefix = prefix} + var link = function(options, vnode5) { + vnode5.dom.setAttribute("href", routeService.prefix + vnode5.attrs.href) + vnode5.dom.onclick = function(e) { if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return e.preventDefault() e.redraw = false @@ -1500,33 +1562,21 @@ var _21 = function($window, redrawService0) { if (args0.tag == null) return link.bind(link, args0) return link({}, args0) } - route.param = function(key3) { - if(typeof attrs3 !== "undefined" && typeof key3 !== "undefined") return attrs3[key3] + route.param = function(key0) { + if(typeof attrs3 !== "undefined" && typeof key0 !== "undefined") return attrs3[key0] return attrs3 } return route } -m.route = _21(window, redrawService) -m.withAttr = function(attrName, callback, context) { - return function(e) { - callback.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName)) - } -} -m.prop = function (store) { - return { - get: function() { return store }, - toJSON: function() { return store }, - set: function(value0) { return store = value0 } - } -} -var _30 = coreRenderer(window) -m.render = _30.render +m.route = _24(window, redrawService) +var _31 = coreRenderer(window) +m.render = _31.render m.redraw = redrawService.redraw m.request = requestService.request m.jsonp = requestService.jsonp m.parseQueryString = parseQueryString m.buildQueryString = buildQueryString -m.version = "2.0.0-rc.1" +m.version = "2.0.0-rc.2" m.vnode = Vnode m.PromisePolyfill = PromisePolyfill if (typeof module !== "undefined") module["exports"] = m diff --git a/mithril.min.js b/mithril.min.js index e085aa04..76fac95f 100644 --- a/mithril.min.js +++ b/mithril.min.js @@ -1 +1 @@ -!function(){"use strict";function e(e,t,n,r,o,i){return{tag:e,key:t,attrs:n,children:r,text:o,dom:i,domSize:void 0,state:void 0,events:void 0,instance:void 0}}e.normalize=function(t){return Array.isArray(t)?e("[",void 0,void 0,e.normalizeChildren(t),void 0,void 0):null!=t&&"object"!=typeof t?e("#",void 0,void 0,!1===t?"":t,void 0,void 0):t},e.normalizeChildren=function(t){for(var n=[],r=0;r0&&(l.className=i.join(" ")),n[e]={tag:o,attrs:l}}(i),a,e.normalizeChildren(l)):e(i,a.key,a,l)}i.trust=function(t){return null==t&&(t=""),e("<",void 0,void 0,t,void 0,void 0)},i.fragment=function(t,n){return e("[",t.key,t,e.normalizeChildren(n),void 0,void 0)};var l=i;if((a=function(e){if(!(this instanceof a))throw new Error("Promise must be called with `new`");if("function"!=typeof e)throw new TypeError("executor must be a function");var t=this,n=[],r=[],o=f(n,!0),i=f(r,!1),l=t._instance={resolvers:n,rejectors:r},u="function"==typeof setImmediate?setImmediate:setTimeout;function f(e,o){return function a(f){var c;try{if(!o||null==f||"object"!=typeof f&&"function"!=typeof f||"function"!=typeof(c=f.then))u(function(){o||0!==e.length||console.error("Possible unhandled promise rejection:",f);for(var t=0;t0||e(n)}}var r=n(i);try{e(n(o),r)}catch(e){r(e)}}s(e)}).prototype.then=function(e,t){var n,r,o=this._instance;function i(e,t,i,l){t.push(function(t){if("function"!=typeof e)i(t);else try{n(e(t))}catch(e){r&&r(e)}}),"function"==typeof o.retry&&l===o.state&&o.retry()}var l=new a(function(e,t){n=e,r=t});return i(e,o.resolvers,n,!0),i(t,o.rejectors,r,!1),l},a.prototype.catch=function(e){return this.then(null,e)},a.prototype.finally=function(e){return this.then(function(t){return a.resolve(e()).then(function(){return t})},function(t){return a.resolve(e()).then(function(){return a.reject(t)})})},a.resolve=function(e){return e instanceof a?e:new a(function(t){t(e)})},a.reject=function(e){return new a(function(t,n){n(e)})},a.all=function(e){return new a(function(t,n){var r=e.length,o=0,i=[];if(0===e.length)t([]);else for(var l=0;l=200&&i.status<300||304===i.status||f.test(n.url))t(d(n.type,e));else{var o=new Error(i.responseText);o.code=i.status,o.response=e,r(o)}}catch(e){r(e)}},o&&null!=n.data?i.send(n.data):i.send()});return!0===n.background?v:u(v)},jsonp:function(n,u){var f=o();n=i(n,u);var s=new t(function(t,o){var i=n.callbackName||"_mithril_"+Math.round(1e16*Math.random())+"_"+r++,u=e.document.createElement("script");e[i]=function(r){u.parentNode.removeChild(u),t(d(n.type,r)),delete e[i]},u.onerror=function(){u.parentNode.removeChild(u),o(new Error("JSONP request failed")),delete e[i]},null==n.data&&(n.data={}),n.url=l(n.url,n.data),n.data[n.callbackKey||"callback"]=i,u.src=a(n.url,n.data),e.document.documentElement.appendChild(u)});return!0===n.background?s:f(s)},setCompletionCallback:function(e){n=e}}}(window,a),c=function(t){var n,r=t.document,o={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"};function i(e){return e.attrs&&e.attrs.xmlns||o[e.tag]}function l(e,t){if(e.state!==t)throw new Error("`vnode.state` must not be modified")}function a(e){var t=e.state;try{return this.apply(t,arguments)}finally{l(e,t)}}function u(e,t,n,r,o,i,l){for(var a=n;a'+t.children+"",l=l.firstChild):l.innerHTML=t.children,t.dom=l.firstChild,t.domSize=l.childNodes.length;for(var a,u=r.createDocumentFragment();a=l.firstChild;)u.appendChild(a);y(e,u,o)}function d(e,t,n,r,o,i){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)u(e,n,0,n.length,r,o,i);else if(null==n||0===n.length)w(t,0,t.length);else{for(var l=0,a=0,s=null,c=null;a=a&&S>=l;)if(x=t[E],k=n[S],null==x)E--;else if(null==k)S--;else{if(x.key!==k.key)break;x!==k&&v(e,x,k,r,o,i),null!=k.dom&&(o=k.dom),E--,S--}for(;E>=a&&S>=l;)if(d=t[a],g=n[l],null==d)a++;else if(null==g)l++;else{if(d.key!==g.key)break;a++,l++,d!==g&&v(e,d,g,r,m(t,a,o),i)}for(;E>=a&&S>=l;){if(null==d)a++;else if(null==g)l++;else if(null==x)E--;else if(null==k)S--;else{if(l===S)break;if(d.key!==k.key||x.key!==g.key)break;C=m(t,a,o),y(e,p(x),C),x!==g&&v(e,x,g,r,C,i),++l<=--S&&y(e,p(d),o),d!==k&&v(e,d,k,r,o,i),null!=k.dom&&(o=k.dom),a++,E--}x=t[E],k=n[S],d=t[a],g=n[l]}for(;E>=a&&S>=l;){if(null==x)E--;else if(null==k)S--;else{if(x.key!==k.key)break;x!==k&&v(e,x,k,r,o,i),null!=k.dom&&(o=k.dom),E--,S--}x=t[E],k=n[S]}if(l>S)w(t,a,E+1);else if(a>E)u(e,n,l,S+1,r,o,i);else{var A,z,N=o,j=S-l+1,T=new Array(j),O=0,P=0,$=2147483647,I=0;for(P=0;P=l;P--)if(null==A&&(A=h(t,a,E+1)),null!=(k=n[P])){var R=A[k.key];null!=R&&($=R<$?R:-1,T[P-l]=R,x=t[R],t[R]=null,x!==k&&v(e,x,k,r,o,i),null!=k.dom&&(o=k.dom),I++)}if(o=N,I!==E-a+1&&w(t,a,E+1),0===I)u(e,n,l,S+1,r,o,i);else if(-1===$)for(O=(z=function(e){var t,n,r=e.slice(),o=[];o.push(0);for(var i=0,l=e.length;i0&&(r[i]=o[t-1]),o[t]=i)}}t=o.length,n=o[t-1];for(;t-- >0;)o[t]=n,n=r[n];return o}(T)).length-1,P=S;P>=l;P--)g=n[P],-1===T[P-l]?f(e,g,r,i,o):z[O]===P-l?O--:y(e,p(g),o),null!=g.dom&&(o=n[P].dom);else for(P=S;P>=l;P--)g=n[P],-1===T[P-l]&&f(e,g,r,i,o),null!=g.dom&&(o=n[P].dom)}}else{var L=t.lengthL&&w(t,l,t.length),n.length>L&&u(e,n,l,n.length,r,o,i)}}}function v(t,n,r,o,l,u){var s=n.tag;if(s===r.tag){if(r.state=n.state,r.events=n.events,function(e,t){var n,r;null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate&&(n=a.call(e.attrs.onbeforeupdate,e,t));"string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate&&(r=a.call(e.state.onbeforeupdate,e,t));if(!(void 0===n&&void 0===r||n||r))return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,!0;return!1}(r,n))return;if("string"==typeof s)switch(null!=r.attrs&&j(r.attrs,r,o),s){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children);t.dom=e.dom}(n,r);break;case"<":!function(e,t,n,r,o){t.children!==n.children?(p(t),c(e,n,r,o)):(n.dom=t.dom,n.domSize=t.domSize)}(t,n,r,u,l);break;case"[":!function(e,t,n,r,o,i){d(e,t.children,n.children,r,o,i);var l=0,a=n.children;if(n.dom=null,null!=a){for(var u=0;u0){for(var o=e.dom;--t;)n.appendChild(o.nextSibling);n.insertBefore(o,n.firstChild)}return n}return e.dom}function m(e,t,n){for(;t-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}function S(e,t,n){if(null==t||null==n||"object"!=typeof t||"object"!=typeof n||n===t)if(t===n&&(e.style.cssText="",t=null),null==n)e.style.cssText="";else if("string"==typeof n)e.style.cssText=n;else for(var r in"string"==typeof t&&(e.style.cssText=""),n)e.style[r]=n[r];else{for(var r in n)n[r]!==t[r]&&(e.style[r]=n[r]);for(var r in t)r in n||(e.style[r]="")}}function A(){}function z(e,t,n){if(null!=e.events){if(e.events[t]===n)return;null==n||"function"!=typeof n&&"object"!=typeof n?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}else null==n||"function"!=typeof n&&"object"!=typeof n||(e.events=new A,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}function N(e,t,n){"function"==typeof e.oninit&&a.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(a.bind(e.oncreate,t))}function j(e,t,n){"function"==typeof e.onupdate&&n.push(a.bind(e.onupdate,t))}return A.prototype=Object.create(null),A.prototype.handleEvent=function(e){var t,r=this["on"+e.type];"function"==typeof r?t=r.call(e.target,e):"function"==typeof r.handleEvent&&r.handleEvent(e),"function"==typeof n&&n.call(e.target,e),!1===t&&(e.preventDefault(),e.stopPropagation())},{render:function(t,n){if(!t)throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var o=[],i=r.activeElement,l=t.namespaceURI;null==t.vnodes&&(t.textContent=""),n=e.normalizeChildren(Array.isArray(n)?n:[n]),d(t,t.vnodes,n,o,null,"http://www.w3.org/1999/xhtml"===l?void 0:l),t.vnodes=n,null!=i&&r.activeElement!==i&&"function"==typeof i.focus&&i.focus();for(var a=0;a-1&&r.splice(t,2)}function l(){if(o)throw new Error("Nested m.redraw.sync() call");o=!0;for(var e=1;e-1&&u.pop();for(var s=0;s-1?r:o>-1?o:e.length;if(r>-1){var l=o>-1?o:e.length,a=p(e.slice(r+1,l));for(var u in a)t[u]=a[u]}if(o>-1){var f=p(e.slice(o+1));for(var u in f)n[u]=f[u]}return e.slice(0,i)}var l={prefix:"#!",getPath:function(){switch(l.prefix.charAt(0)){case"#":return o("hash").slice(l.prefix.length);case"?":return o("search").slice(l.prefix.length)+o("hash");default:return o("pathname").slice(l.prefix.length)+o("search")+o("hash")}},setPath:function(t,r,o){var a={},f={};if(t=i(t,a,f),null!=r){for(var s in r)a[s]=r[s];t=t.replace(/:([^\/]+)/g,function(e,t){return delete a[t],r[t]})}var c=u(a);c&&(t+="?"+c);var d=u(f);if(d&&(t+="#"+d),n){var v=o?o.state:null,h=o?o.title:null;e.onpopstate(),o&&o.replace?e.history.replaceState(v,h,l.prefix+t):e.history.pushState(v,h,l.prefix+t)}else e.location.href=l.prefix+t}};return l.defineRoutes=function(o,a,u){function f(){var t=l.getPath(),n={},r=i(t,n,n),f=e.history.state;if(null!=f)for(var s in f)n[s]=f[s];for(var c in o){var d=new RegExp("^"+c.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(d.test(r))return void r.replace(d,function(){for(var e=c.match(/:[^\/]+/g)||[],r=[].slice.call(arguments,1,-2),i=0;i0&&(l.className=i.join(" ")),r[e]={tag:o,attrs:l}}(l),a):(a.tag=l,a)}l.trust=function(t){return null==t&&(t=""),e("<",void 0,void 0,t,void 0,void 0)},l.fragment=function(){var n=t.apply(0,arguments);return n.tag="[",n.children=e.normalizeChildren(n.children),n};var a=function(){return l.apply(this,arguments)};if(a.m=l,a.trust=l.trust,a.fragment=l.fragment,(u=function(e){if(!(this instanceof u))throw new Error("Promise must be called with `new`");if("function"!=typeof e)throw new TypeError("executor must be a function");var t=this,n=[],r=[],o=f(n,!0),i=f(r,!1),l=t._instance={resolvers:n,rejectors:r},a="function"==typeof setImmediate?setImmediate:setTimeout;function f(e,o){return function u(f){var c;try{if(!o||null==f||"object"!=typeof f&&"function"!=typeof f||"function"!=typeof(c=f.then))a(function(){o||0!==e.length||console.error("Possible unhandled promise rejection:",f);for(var t=0;t0||e(n)}}var r=n(i);try{e(n(o),r)}catch(e){r(e)}}s(e)}).prototype.then=function(e,t){var n,r,o=this._instance;function i(e,t,i,l){t.push(function(t){if("function"!=typeof e)i(t);else try{n(e(t))}catch(e){r&&r(e)}}),"function"==typeof o.retry&&l===o.state&&o.retry()}var l=new u(function(e,t){n=e,r=t});return i(e,o.resolvers,n,!0),i(t,o.rejectors,r,!1),l},u.prototype.catch=function(e){return this.then(null,e)},u.prototype.finally=function(e){return this.then(function(t){return u.resolve(e()).then(function(){return t})},function(t){return u.resolve(e()).then(function(){return u.reject(t)})})},u.resolve=function(e){return e instanceof u?e:new u(function(t){t(e)})},u.reject=function(e){return new u(function(t,n){n(e)})},u.all=function(e){return new u(function(t,n){var r=e.length,o=0,i=[];if(0===e.length)t([]);else for(var l=0;l=200&&c.status<300||304===c.status||/^file:\/\//i.test(t),i=c.responseText;if("function"==typeof n.extract)i=n.extract(c,n),e=!0;else if("function"==typeof n.deserialize)i=n.deserialize(i);else try{i=i?JSON.parse(i):null}catch(e){throw new Error("Invalid JSON: "+i)}if(e)r(i);else{var l=new Error(c.responseText);l.code=c.status,l.response=i,o(l)}}catch(e){o(e)}},u&&null!=f?c.send(f):c.send()}),jsonp:o(function(t,n,o,i){var a=n.callbackName||"_mithril_"+Math.round(1e16*Math.random())+"_"+r++,u=e.document.createElement("script");e[a]=function(t){u.parentNode.removeChild(u),o(t),delete e[a]},u.onerror=function(){u.parentNode.removeChild(u),i(new Error("JSONP request failed")),delete e[a]},t=l(t,n.data,!0),u.src=t+(t.indexOf("?")<0?"?":"&")+encodeURIComponent(n.callbackKey||"callback")+"="+encodeURIComponent(a),e.document.documentElement.appendChild(u)}),setCompletionCallback:function(e){n=e}}}(window,u),c=function(t){var n,r=t.document,o={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"};function i(e){return e.attrs&&e.attrs.xmlns||o[e.tag]}function l(e,t){if(e.state!==t)throw new Error("`vnode.state` must not be modified")}function a(e){var t=e.state;try{return this.apply(t,arguments)}finally{l(e,t)}}function u(){try{return r.activeElement}catch(e){return null}}function f(e,t,n,r,o,i,l){for(var a=n;a'+t.children+"",l=l.firstChild):l.innerHTML=t.children,t.dom=l.firstChild,t.domSize=l.childNodes.length;for(var a,u=r.createDocumentFragment();a=l.firstChild;)u.appendChild(a);g(e,u,o)}function v(e,t,n,r,o,i){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)f(e,n,0,n.length,r,o,i);else if(null==n||0===n.length)b(t,0,t.length);else{for(var l=0,a=0,u=null,c=null;a=a&&E>=l;)if(w=t[C],k=n[E],null==w)C--;else if(null==k)E--;else{if(w.key!==k.key)break;w!==k&&h(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,E--}for(;C>=a&&E>=l;)if(d=t[a],v=n[l],null==d)a++;else if(null==v)l++;else{if(d.key!==v.key)break;a++,l++,d!==v&&h(e,d,v,r,y(t,a,o),i)}for(;C>=a&&E>=l;){if(null==d)a++;else if(null==v)l++;else if(null==w)C--;else if(null==k)E--;else{if(l===E)break;if(d.key!==k.key||w.key!==v.key)break;S=y(t,a,o),g(e,m(w),S),w!==v&&h(e,w,v,r,S,i),++l<=--E&&g(e,m(d),o),d!==k&&h(e,d,k,r,o,i),null!=k.dom&&(o=k.dom),a++,C--}w=t[C],k=n[E],d=t[a],v=n[l]}for(;C>=a&&E>=l;){if(null==w)C--;else if(null==k)E--;else{if(w.key!==k.key)break;w!==k&&h(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,E--}w=t[C],k=n[E]}if(l>E)b(t,a,C+1);else if(a>C)f(e,n,l,E+1,r,o,i);else{var z,A,j=o,N=E-l+1,P=new Array(N),O=0,$=0,T=2147483647,I=0;for($=0;$=l;$--)if(null==z&&(z=p(t,a,C+1)),null!=(k=n[$])){var R=z[k.key];null!=R&&(T=R0&&(r[i]=o[t-1]),o[t]=i)}}t=o.length,n=o[t-1];for(;t-- >0;)o[t]=n,n=r[n];return o}(P)).length-1,$=E;$>=l;$--)v=n[$],-1===P[$-l]?s(e,v,r,i,o):A[O]===$-l?O--:g(e,m(v),o),null!=v.dom&&(o=n[$].dom);else for($=E;$>=l;$--)v=n[$],-1===P[$-l]&&s(e,v,r,i,o),null!=v.dom&&(o=n[$].dom)}}else{var L=t.lengthL&&b(t,l,t.length),n.length>L&&f(e,n,l,n.length,r,o,i)}}}function h(t,n,r,o,l,u){var f=n.tag;if(f===r.tag){if(r.state=n.state,r.events=n.events,function(e,t){do{if(null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate){var n=a.call(e.attrs.onbeforeupdate,e,t);if(void 0!==n&&!n)break}if("string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate){var n=a.call(e.state.onbeforeupdate,e,t);if(void 0!==n&&!n)break}return!1}while(0);return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,!0}(r,n))return;if("string"==typeof f)switch(null!=r.attrs&&T(r.attrs,r,o),f){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children);t.dom=e.dom}(n,r);break;case"<":!function(e,t,n,r,o){t.children!==n.children?(m(t),d(e,n,r,o)):(n.dom=t.dom,n.domSize=t.domSize)}(t,n,r,u,l);break;case"[":!function(e,t,n,r,o,i){v(e,t.children,n.children,r,o,i);var l=0,a=n.children;if(n.dom=null,null!=a){for(var u=0;u0){for(var o=e.dom;--t;)n.appendChild(o.nextSibling);n.insertBefore(o,n.firstChild)}return n}return e.dom}function y(e,t,n){for(;t-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}var z=/[A-Z]/g;function A(e){return"-"+e.toLowerCase()}function j(e){return"-"===e[0]&&"-"===e[1]?e:"cssFloat"===e?"float":e.replace(z,A)}function N(e,t,n){if(t===n);else if(null==n)e.style.cssText="";else if("object"!=typeof n)e.style.cssText=n;else if(null==t||"object"!=typeof t)for(var r in e.style.cssText="",n){null!=(o=n[r])&&e.style.setProperty(j(r),String(o))}else{for(var r in n){var o;null!=(o=n[r])&&(o=String(o))!==String(t[r])&&e.style.setProperty(j(r),o)}for(var r in t)null!=t[r]&&null==n[r]&&e.style.removeProperty(j(r))}}function P(){}function O(e,t,n){if(null!=e.events){if(e.events[t]===n)return;null==n||"function"!=typeof n&&"object"!=typeof n?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}else null==n||"function"!=typeof n&&"object"!=typeof n||(e.events=new P,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}function $(e,t,n){"function"==typeof e.oninit&&a.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(a.bind(e.oncreate,t))}function T(e,t,n){"function"==typeof e.onupdate&&n.push(a.bind(e.onupdate,t))}return P.prototype=Object.create(null),P.prototype.handleEvent=function(e){var t,r=this["on"+e.type];"function"==typeof r?t=r.call(e.currentTarget,e):"function"==typeof r.handleEvent&&r.handleEvent(e),!1===e.redraw?e.redraw=void 0:"function"==typeof n&&n(),!1===t&&(e.preventDefault(),e.stopPropagation())},{render:function(t,n){if(!t)throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var r=[],o=u(),i=t.namespaceURI;null==t.vnodes&&(t.textContent=""),n=e.normalizeChildren(Array.isArray(n)?n:[n]),v(t,t.vnodes,n,r,null,"http://www.w3.org/1999/xhtml"===i?void 0:i),t.vnodes=n,null!=o&&u()!==o&&"function"==typeof o.focus&&o.focus();for(var l=0;l-1&&r.splice(t,2)}function l(){if(o)throw new Error("Nested m.redraw.sync() call");o=!0;for(var e=1;e-1&&u.pop();for(var s=0;s-1?r:o>-1?o:e.length;if(r>-1){var l=o>-1?o:e.length,a=p(e.slice(r+1,l));for(var u in a)t[u]=a[u]}if(o>-1){var f=p(e.slice(o+1));for(var u in f)n[u]=f[u]}return e.slice(0,i)}var l={prefix:"#!",getPath:function(){switch(l.prefix.charAt(0)){case"#":return o("hash").slice(l.prefix.length);case"?":return o("search").slice(l.prefix.length)+o("hash");default:return o("pathname").slice(l.prefix.length)+o("search")+o("hash")}},setPath:function(t,r,o){var a={},u={};if(t=i(t,a,u),null!=r){for(var s in r)a[s]=r[s];t=t.replace(/:([^\/]+)/g,function(e,t){return delete a[t],r[t]})}var c=f(a);c&&(t+="?"+c);var d=f(u);if(d&&(t+="#"+d),n){var v=o?o.state:null,h=o?o.title:null;e.onpopstate(),o&&o.replace?e.history.replaceState(v,h,l.prefix+t):e.history.pushState(v,h,l.prefix+t)}else e.location.href=l.prefix+t}};return l.defineRoutes=function(o,a,u){function f(){var t=l.getPath(),n={},r=i(t,n,n),f=e.history.state;if(null!=f)for(var s in f)n[s]=f[s];for(var c in o){var d=new RegExp("^"+c.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(d.test(r))return void r.replace(d,function(){for(var e=c.match(/:[^\/]+/g)||[],r=[].slice.call(arguments,1,-2),i=0;i0&&(l.className=i.join(" ")),r[e]={tag:o,attrs:l}}(l),a):(a.tag=l,a)}l.trust=function(t){return null==t&&(t=""),e("<",void 0,void 0,t,void 0,void 0)},l.fragment=function(){var n=t.apply(0,arguments);return n.tag="[",n.children=e.normalizeChildren(n.children),n};var a=function(){return l.apply(this,arguments)};if(a.m=l,a.trust=l.trust,a.fragment=l.fragment,(u=function(e){if(!(this instanceof u))throw new Error("Promise must be called with `new`");if("function"!=typeof e)throw new TypeError("executor must be a function");var t=this,n=[],r=[],o=f(n,!0),i=f(r,!1),l=t._instance={resolvers:n,rejectors:r},a="function"==typeof setImmediate?setImmediate:setTimeout;function f(e,o){return function u(f){var c;try{if(!o||null==f||"object"!=typeof f&&"function"!=typeof f||"function"!=typeof(c=f.then))a(function(){o||0!==e.length||console.error("Possible unhandled promise rejection:",f);for(var t=0;t0||e(n)}}var r=n(i);try{e(n(o),r)}catch(e){r(e)}}s(e)}).prototype.then=function(e,t){var n,r,o=this._instance;function i(e,t,i,l){t.push(function(t){if("function"!=typeof e)i(t);else try{n(e(t))}catch(e){r&&r(e)}}),"function"==typeof o.retry&&l===o.state&&o.retry()}var l=new u(function(e,t){n=e,r=t});return i(e,o.resolvers,n,!0),i(t,o.rejectors,r,!1),l},u.prototype.catch=function(e){return this.then(null,e)},u.prototype.finally=function(e){return this.then(function(t){return u.resolve(e()).then(function(){return t})},function(t){return u.resolve(e()).then(function(){return u.reject(t)})})},u.resolve=function(e){return e instanceof u?e:new u(function(t){t(e)})},u.reject=function(e){return new u(function(t,n){n(e)})},u.all=function(e){return new u(function(t,n){var r=e.length,o=0,i=[];if(0===e.length)t([]);else for(var l=0;l=200&&c.status<300||304===c.status||/^file:\/\//i.test(t),i=c.responseText;if("function"==typeof n.extract)i=n.extract(c,n),e=!0;else if("function"==typeof n.deserialize)i=n.deserialize(i);else try{i=i?JSON.parse(i):null}catch(e){throw new Error("Invalid JSON: "+i)}if(e)r(i);else{var l=new Error(c.responseText);l.code=c.status,l.response=i,o(l)}}catch(e){o(e)}},u&&null!=f?c.send(f):c.send()}),jsonp:o(function(t,n,o,i){var a=n.callbackName||"_mithril_"+Math.round(1e16*Math.random())+"_"+r++,u=e.document.createElement("script");e[a]=function(t){u.parentNode.removeChild(u),o(t),delete e[a]},u.onerror=function(){u.parentNode.removeChild(u),i(new Error("JSONP request failed")),delete e[a]},t=l(t,n.data,!0),u.src=t+(t.indexOf("?")<0?"?":"&")+encodeURIComponent(n.callbackKey||"callback")+"="+encodeURIComponent(a),e.document.documentElement.appendChild(u)}),setCompletionCallback:function(e){n=e}}}(window,u),c=function(t){var n,r=t.document,o={svg:"http://www.w3.org/2000/svg",math:"http://www.w3.org/1998/Math/MathML"};function i(e){return e.attrs&&e.attrs.xmlns||o[e.tag]}function l(e,t){if(e.state!==t)throw new Error("`vnode.state` must not be modified")}function a(e){var t=e.state;try{return this.apply(t,arguments)}finally{l(e,t)}}function u(){try{return r.activeElement}catch(e){return null}}function f(e,t,n,r,o,i,l){for(var a=n;a'+t.children+"",l=l.firstChild):l.innerHTML=t.children,t.dom=l.firstChild,t.domSize=l.childNodes.length;for(var a,u=r.createDocumentFragment();a=l.firstChild;)u.appendChild(a);g(e,u,o)}function v(e,t,n,r,o,i){if(t!==n&&(null!=t||null!=n))if(null==t||0===t.length)f(e,n,0,n.length,r,o,i);else if(null==n||0===n.length)b(t,0,t.length);else{for(var l=0,a=0,u=null,c=null;a=a&&E>=l;)if(w=t[C],k=n[E],null==w)C--;else if(null==k)E--;else{if(w.key!==k.key)break;w!==k&&h(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,E--}for(;C>=a&&E>=l;)if(d=t[a],v=n[l],null==d)a++;else if(null==v)l++;else{if(d.key!==v.key)break;a++,l++,d!==v&&h(e,d,v,r,y(t,a,o),i)}for(;C>=a&&E>=l;){if(null==d)a++;else if(null==v)l++;else if(null==w)C--;else if(null==k)E--;else{if(l===E)break;if(d.key!==k.key||w.key!==v.key)break;S=y(t,a,o),g(e,m(w),S),w!==v&&h(e,w,v,r,S,i),++l<=--E&&g(e,m(d),o),d!==k&&h(e,d,k,r,o,i),null!=k.dom&&(o=k.dom),a++,C--}w=t[C],k=n[E],d=t[a],v=n[l]}for(;C>=a&&E>=l;){if(null==w)C--;else if(null==k)E--;else{if(w.key!==k.key)break;w!==k&&h(e,w,k,r,o,i),null!=k.dom&&(o=k.dom),C--,E--}w=t[C],k=n[E]}if(l>E)b(t,a,C+1);else if(a>C)f(e,n,l,E+1,r,o,i);else{var z,A,j=o,N=E-l+1,P=new Array(N),O=0,$=0,T=2147483647,I=0;for($=0;$=l;$--)if(null==z&&(z=p(t,a,C+1)),null!=(k=n[$])){var R=z[k.key];null!=R&&(T=R0&&(r[i]=o[t-1]),o[t]=i)}}t=o.length,n=o[t-1];for(;t-- >0;)o[t]=n,n=r[n];return o}(P)).length-1,$=E;$>=l;$--)v=n[$],-1===P[$-l]?s(e,v,r,i,o):A[O]===$-l?O--:g(e,m(v),o),null!=v.dom&&(o=n[$].dom);else for($=E;$>=l;$--)v=n[$],-1===P[$-l]&&s(e,v,r,i,o),null!=v.dom&&(o=n[$].dom)}}else{var L=t.lengthL&&b(t,l,t.length),n.length>L&&f(e,n,l,n.length,r,o,i)}}}function h(t,n,r,o,l,u){var f=n.tag;if(f===r.tag){if(r.state=n.state,r.events=n.events,function(e,t){do{if(null!=e.attrs&&"function"==typeof e.attrs.onbeforeupdate){var n=a.call(e.attrs.onbeforeupdate,e,t);if(void 0!==n&&!n)break}if("string"!=typeof e.tag&&"function"==typeof e.state.onbeforeupdate){var n=a.call(e.state.onbeforeupdate,e,t);if(void 0!==n&&!n)break}return!1}while(0);return e.dom=t.dom,e.domSize=t.domSize,e.instance=t.instance,!0}(r,n))return;if("string"==typeof f)switch(null!=r.attrs&&T(r.attrs,r,o),f){case"#":!function(e,t){e.children.toString()!==t.children.toString()&&(e.dom.nodeValue=t.children);t.dom=e.dom}(n,r);break;case"<":!function(e,t,n,r,o){t.children!==n.children?(m(t),d(e,n,r,o)):(n.dom=t.dom,n.domSize=t.domSize)}(t,n,r,u,l);break;case"[":!function(e,t,n,r,o,i){v(e,t.children,n.children,r,o,i);var l=0,a=n.children;if(n.dom=null,null!=a){for(var u=0;u0){for(var o=e.dom;--t;)n.appendChild(o.nextSibling);n.insertBefore(o,n.firstChild)}return n}return e.dom}function y(e,t,n){for(;t-1||null!=e.attrs&&e.attrs.is||"href"!==t&&"list"!==t&&"form"!==t&&"width"!==t&&"height"!==t)&&t in e.dom}var z=/[A-Z]/g;function A(e){return"-"+e.toLowerCase()}function j(e){return"-"===e[0]&&"-"===e[1]?e:"cssFloat"===e?"float":e.replace(z,A)}function N(e,t,n){if(t===n);else if(null==n)e.style.cssText="";else if("object"!=typeof n)e.style.cssText=n;else if(null==t||"object"!=typeof t)for(var r in e.style.cssText="",n){null!=(o=n[r])&&e.style.setProperty(j(r),String(o))}else{for(var r in n){var o;null!=(o=n[r])&&(o=String(o))!==String(t[r])&&e.style.setProperty(j(r),o)}for(var r in t)null!=t[r]&&null==n[r]&&e.style.removeProperty(j(r))}}function P(){}function O(e,t,n){if(null!=e.events){if(e.events[t]===n)return;null==n||"function"!=typeof n&&"object"!=typeof n?(null!=e.events[t]&&e.dom.removeEventListener(t.slice(2),e.events,!1),e.events[t]=void 0):(null==e.events[t]&&e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}else null==n||"function"!=typeof n&&"object"!=typeof n||(e.events=new P,e.dom.addEventListener(t.slice(2),e.events,!1),e.events[t]=n)}function $(e,t,n){"function"==typeof e.oninit&&a.call(e.oninit,t),"function"==typeof e.oncreate&&n.push(a.bind(e.oncreate,t))}function T(e,t,n){"function"==typeof e.onupdate&&n.push(a.bind(e.onupdate,t))}return P.prototype=Object.create(null),P.prototype.handleEvent=function(e){var t,r=this["on"+e.type];"function"==typeof r?t=r.call(e.currentTarget,e):"function"==typeof r.handleEvent&&r.handleEvent(e),!1===e.redraw?e.redraw=void 0:"function"==typeof n&&n(),!1===t&&(e.preventDefault(),e.stopPropagation())},{render:function(t,n){if(!t)throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.");var r=[],o=u(),i=t.namespaceURI;null==t.vnodes&&(t.textContent=""),n=e.normalizeChildren(Array.isArray(n)?n:[n]),v(t,t.vnodes,n,r,null,"http://www.w3.org/1999/xhtml"===i?void 0:i),t.vnodes=n,null!=o&&u()!==o&&"function"==typeof o.focus&&o.focus();for(var l=0;l-1&&r.splice(t,2)}function l(){if(o)throw new Error("Nested m.redraw.sync() call");o=!0;for(var e=1;e-1&&u.pop();for(var s=0;s-1?r:o>-1?o:e.length;if(r>-1){var l=o>-1?o:e.length,a=p(e.slice(r+1,l));for(var u in a)t[u]=a[u]}if(o>-1){var f=p(e.slice(o+1));for(var u in f)n[u]=f[u]}return e.slice(0,i)}var l={prefix:"#!",getPath:function(){switch(l.prefix.charAt(0)){case"#":return o("hash").slice(l.prefix.length);case"?":return o("search").slice(l.prefix.length)+o("hash");default:return o("pathname").slice(l.prefix.length)+o("search")+o("hash")}},setPath:function(t,r,o){var a={},u={};if(t=i(t,a,u),null!=r){for(var s in r)a[s]=r[s];t=t.replace(/:([^\/]+)/g,function(e,t){return delete a[t],r[t]})}var c=f(a);c&&(t+="?"+c);var d=f(u);if(d&&(t+="#"+d),n){var v=o?o.state:null,h=o?o.title:null;e.onpopstate(),o&&o.replace?e.history.replaceState(v,h,l.prefix+t):e.history.pushState(v,h,l.prefix+t)}else e.location.href=l.prefix+t}};return l.defineRoutes=function(o,a,u){function f(){var t=l.getPath(),n={},r=i(t,n,n),f=e.history.state;if(null!=f)for(var s in f)n[s]=f[s];for(var c in o){var d=new RegExp("^"+c.replace(/:[^\/]+?\.{3}/g,"(.*?)").replace(/:[^\/]+/g,"([^\\/]+)")+"/?$");if(d.test(r))return void r.replace(d,function(){for(var e=c.match(/:[^\/]+/g)||[],r=[].slice.call(arguments,1,-2),i=0;i 0) attrs.className = classes.join(" ") + return selectorCache[selector] = {tag: tag, attrs: attrs} +} +function execSelector(state, vnode) { + var attrs = vnode.attrs + var children = Vnode.normalizeChildren(vnode.children) + var hasClass = hasOwn.call(attrs, "class") + var className = hasClass ? attrs.class : attrs.className + vnode.tag = state.tag + vnode.attrs = null + vnode.children = undefined + if (!isEmpty(state.attrs) && !isEmpty(attrs)) { + var newAttrs = {} + for (var key in attrs) { + if (hasOwn.call(attrs, key)) newAttrs[key] = attrs[key] + } + attrs = newAttrs + } + for (var key in state.attrs) { + if (hasOwn.call(state.attrs, key) && key !== "className" && !hasOwn.call(attrs, key)){ + attrs[key] = state.attrs[key] + } + } + if (className != null || state.attrs.className != null) attrs.className = + className != null + ? state.attrs.className != null + ? String(state.attrs.className) + " " + String(className) + : className + : state.attrs.className != null + ? state.attrs.className + : null + if (hasClass) attrs.class = null + for (var key in attrs) { + if (hasOwn.call(attrs, key) && key !== "key") { + vnode.attrs = attrs + break + } + } + if (Array.isArray(children) && children.length === 1 && children[0] != null && children[0].tag === "#") { + vnode.text = children[0].children + } else { + vnode.children = children + } + return vnode +} +function hyperscript(selector) { + if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") { + throw Error("The selector must be either a string or a component."); + } + var vnode = hyperscriptVnode.apply(1, arguments) + if (typeof selector === "string") { + return execSelector(selectorCache[selector] || compileSelector(selector), vnode) + } else { + vnode.tag = selector + return vnode + } +} +hyperscript.trust = function(html) { + if (html == null) html = "" + return Vnode("<", undefined, undefined, html, undefined, undefined) +} +hyperscript.fragment = function() { + var vnode2 = hyperscriptVnode.apply(0, arguments) + vnode2.tag = "[" + vnode2.children = Vnode.normalizeChildren(vnode2.children) + return vnode2 +} +var m = function m() { return hyperscript.apply(this, arguments) } +m.m = hyperscript +m.trust = hyperscript.trust +m.fragment = hyperscript.fragment +/** @constructor */ +var PromisePolyfill = function(executor) { + if (!(this instanceof PromisePolyfill)) throw new Error("Promise must be called with `new`") + if (typeof executor !== "function") throw new TypeError("executor must be a function") + var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false) + var instance = self._instance = {resolvers: resolvers, rejectors: rejectors} + var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout + function handler(list, shouldAbsorb) { + return function execute(value) { + var then + try { + if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") { + if (value === self) throw new TypeError("Promise can't be resolved w/ itself") + executeOnce(then.bind(value)) + } + else { + callAsync(function() { + if (!shouldAbsorb && list.length === 0) console.error("Possible unhandled promise rejection:", value) + for (var i = 0; i < list.length; i++) list[i](value) + resolvers.length = 0, rejectors.length = 0 + instance.state = shouldAbsorb + instance.retry = function() {execute(value)} + }) + } + } + catch (e) { + rejectCurrent(e) + } + } + } + function executeOnce(then) { + var runs = 0 + function run(fn) { + return function(value) { + if (runs++ > 0) return + fn(value) + } + } + var onerror = run(rejectCurrent) + try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)} + } + executeOnce(executor) +} +PromisePolyfill.prototype.then = function(onFulfilled, onRejection) { + var self = this, instance = self._instance + function handle(callback, list, next, state) { + list.push(function(value) { + if (typeof callback !== "function") next(value) + else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)} + }) + if (typeof instance.retry === "function" && state === instance.state) instance.retry() + } + var resolveNext, rejectNext + var promise = new PromisePolyfill(function(resolve, reject) {resolveNext = resolve, rejectNext = reject}) + handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false) + return promise +} +PromisePolyfill.prototype.catch = function(onRejection) { + return this.then(null, onRejection) +} +PromisePolyfill.prototype.finally = function(callback) { + return this.then( + function(value) { + return PromisePolyfill.resolve(callback()).then(function() { + return value + }) + }, + function(reason) { + return PromisePolyfill.resolve(callback()).then(function() { + return PromisePolyfill.reject(reason); + }) + } + ) +} +PromisePolyfill.resolve = function(value) { + if (value instanceof PromisePolyfill) return value + return new PromisePolyfill(function(resolve) {resolve(value)}) +} +PromisePolyfill.reject = function(value) { + return new PromisePolyfill(function(resolve, reject) {reject(value)}) +} +PromisePolyfill.all = function(list) { + return new PromisePolyfill(function(resolve, reject) { + var total = list.length, count = 0, values = [] + if (list.length === 0) resolve([]) + else for (var i = 0; i < list.length; i++) { + (function(i) { + function consume(value) { + count++ + values[i] = value + if (count === total) resolve(values) + } + if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") { + list[i].then(consume, reject) + } + else consume(list[i]) + })(i) + } + }) +} +PromisePolyfill.race = function(list) { + return new PromisePolyfill(function(resolve, reject) { + for (var i = 0; i < list.length; i++) { + list[i].then(resolve, reject) + } + }) +} +if (typeof window !== "undefined") { + if (typeof window.Promise === "undefined") { + window.Promise = PromisePolyfill + } else if (!window.Promise.prototype.finally) { + window.Promise.prototype.finally = PromisePolyfill.prototype.finally + } + var PromisePolyfill = window.Promise +} else if (typeof global !== "undefined") { + if (typeof global.Promise === "undefined") { + global.Promise = PromisePolyfill + } else if (!global.Promise.prototype.finally) { + global.Promise.prototype.finally = PromisePolyfill.prototype.finally + } + var PromisePolyfill = global.Promise +} else { +} +var buildQueryString = function(object) { + if (Object.prototype.toString.call(object) !== "[object Object]") return "" + var args = [] + for (var key in object) { + destructure(key, object[key]) + } + return args.join("&") + function destructure(key, value) { + if (Array.isArray(value)) { + for (var i = 0; i < value.length; i++) { + destructure(key + "[" + i + "]", value[i]) + } + } + else if (Object.prototype.toString.call(value) === "[object Object]") { + for (var i in value) { + destructure(key + "[" + i + "]", value[i]) + } + } + else args.push(encodeURIComponent(key) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : "")) + } +} +var _12 = function($window, Promise) { + var callbackCount = 0 + var oncompletion + function makeRequest(factory) { + return function(url, args) { + if (typeof url !== "string") { args = url; url = url.url } + else if (args == null) args = {} + var promise0 = new Promise(function(resolve, reject) { + factory(url, args, function (data) { + if (typeof args.type === "function") { + if (Array.isArray(data)) { + for (var i = 0; i < data.length; i++) { + data[i] = new args.type(data[i]) + } + } + else data = new args.type(data) + } + resolve(data) + }, reject) + }) + if (args.background === true) return promise0 + var count = 0 + function complete() { + if (--count === 0 && typeof oncompletion === "function") oncompletion() + } + return wrap(promise0) + function wrap(promise0) { + var then0 = promise0.then + promise0.then = function() { + count++ + var next = then0.apply(promise0, arguments) + next.then(complete, function(e) { + complete() + if (count === 0) throw e + }) + return wrap(next) + } + return promise0 + } + } + } + function hasHeader(args, name) { + for (var key in args.headers) { + if ({}.hasOwnProperty.call(args.headers, key) && name.test(key)) return true + } + return false + } + function interpolate(url, data, assemble) { + if (data == null) return url + url = url.replace(/:([^\/]+)/gi, function (m0, key) { + return data[key] != null ? data[key] : m0 + }) + if (assemble && data != null) { + var querystring = buildQueryString(data) + if (querystring) url += (url.indexOf("?") < 0 ? "?" : "&") + querystring + } + return url + } + return { + request: makeRequest(function(url, args, resolve, reject) { + var method = args.method != null ? args.method.toUpperCase() : "GET" + var useBody = method !== "GET" && method !== "TRACE" && + (typeof args.useBody !== "boolean" || args.useBody) + var data = args.data + var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(data instanceof $window.FormData) + if (useBody) { + if (typeof args.serialize === "function") data = args.serialize(data) + else if (!(data instanceof $window.FormData)) data = JSON.stringify(data) + } + var xhr = new $window.XMLHttpRequest(), + aborted = false, + _abort = xhr.abort + xhr.abort = function abort() { + aborted = true + _abort.call(xhr) + } + xhr.open(method, interpolate(url, args.data, !useBody), typeof args.async !== "boolean" || args.async, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) + if (assumeJSON && useBody && !hasHeader(args, /^content-type0$/i)) { + xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") + } + if (typeof args.deserialize !== "function" && !hasHeader(args, /^accept$/i)) { + xhr.setRequestHeader("Accept", "application/json, text/*") + } + if (args.withCredentials) xhr.withCredentials = args.withCredentials + if (args.timeout) xhr.timeout = args.timeout + if (args.responseType) xhr.responseType = args.responseType + for (var key in args.headers) { + if ({}.hasOwnProperty.call(args.headers, key)) { + xhr.setRequestHeader(key, args.headers[key]) + } + } + if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr + xhr.onreadystatechange = function() { + // Don't throw errors on xhr.abort(). + if(aborted) return + if (xhr.readyState === 4) { + try { + var success = (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || (/^file:\/\//i).test(url) + var response = xhr.responseText + if (typeof args.extract === "function") { + response = args.extract(xhr, args) + success = true + } else if (typeof args.deserialize === "function") { + response = args.deserialize(response) + } else { + try {response = response ? JSON.parse(response) : null} + catch (e) {throw new Error("Invalid JSON: " + response)} + } + if (success) resolve(response) + else { + var error = new Error(xhr.responseText) + error.code = xhr.status + error.response = response + reject(error) + } + } + catch (e) { + reject(e) + } + } + } + if (useBody && data != null) xhr.send(data) + else xhr.send() + }), + jsonp: makeRequest(function(url, args, resolve, reject) { + var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ + var script = $window.document.createElement("script") + $window[callbackName] = function(data) { + script.parentNode.removeChild(script) + resolve(data) + delete $window[callbackName] + } + script.onerror = function() { + script.parentNode.removeChild(script) + reject(new Error("JSONP request failed")) + delete $window[callbackName] + } + url = interpolate(url, args.data, true) + script.src = url + (url.indexOf("?") < 0 ? "?" : "&") + + encodeURIComponent(args.callbackKey || "callback") + "=" + + encodeURIComponent(callbackName) + $window.document.documentElement.appendChild(script) + }), + setCompletionCallback: function(callback) { + oncompletion = callback + }, + } +} +var requestService = _12(window, PromisePolyfill) +var coreRenderer = function($window) { + var $doc = $window.document + var nameSpace = { + svg: "http://www.w3.org/2000/svg", + math: "http://www.w3.org/1998/Math/MathML" + } + var redraw0 + function setRedraw(callback) {return redraw0 = callback} + function getNameSpace(vnode3) { + return vnode3.attrs && vnode3.attrs.xmlns || nameSpace[vnode3.tag] + } + //sanity check to discourage people from doing `vnode3.state = ...` + function checkState(vnode3, original) { + if (vnode3.state !== original) throw new Error("`vnode.state` must not be modified") + } + //Note: the hook is passed as the `this` argument to allow proxying the + //arguments without requiring a full array allocation to do so. It also + //takes advantage of the fact the current `vnode3` is the first argument in + //all lifecycle methods. + function callHook(vnode3) { + var original = vnode3.state + try { + return this.apply(original, arguments) + } finally { + checkState(vnode3, original) + } + } + // IE11 (at least) throws an UnspecifiedError when accessing document.activeElement when + // inside an iframe. Catch and swallow this error1, and heavy-handidly return null. + function activeElement() { + try { + return $doc.activeElement + } catch (e) { + return null + } + } + //create + function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) { + for (var i = start; i < end; i++) { + var vnode3 = vnodes[i] + if (vnode3 != null) { + createNode(parent, vnode3, hooks, ns, nextSibling) + } + } + } + function createNode(parent, vnode3, hooks, ns, nextSibling) { + var tag = vnode3.tag + if (typeof tag === "string") { + vnode3.state = {} + if (vnode3.attrs != null) initLifecycle(vnode3.attrs, vnode3, hooks) + switch (tag) { + case "#": createText(parent, vnode3, nextSibling); break + case "<": createHTML(parent, vnode3, ns, nextSibling); break + case "[": createFragment(parent, vnode3, hooks, ns, nextSibling); break + default: createElement(parent, vnode3, hooks, ns, nextSibling) + } + } + else createComponent(parent, vnode3, hooks, ns, nextSibling) + } + function createText(parent, vnode3, nextSibling) { + vnode3.dom = $doc.createTextNode(vnode3.children) + insertNode(parent, vnode3.dom, nextSibling) + } + var possibleParents = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"} + function createHTML(parent, vnode3, ns, nextSibling) { + var match0 = vnode3.children.match(/^\s*?<(\w+)/im) || [] + // not using the proper parent makes the child element(s) vanish. + // var div = document.createElement("div") + // div.innerHTML = "ij" + // console.log(div.innerHTML) + // --> "ij", no in sight. + var temp = $doc.createElement(possibleParents[match0[1]] || "div") + if (ns === "http://www.w3.org/2000/svg") { + temp.innerHTML = "" + vnode3.children + "" + temp = temp.firstChild + } else { + temp.innerHTML = vnode3.children + } + vnode3.dom = temp.firstChild + vnode3.domSize = temp.childNodes.length + var fragment = $doc.createDocumentFragment() + var child + while (child = temp.firstChild) { + fragment.appendChild(child) + } + insertNode(parent, fragment, nextSibling) + } + function createFragment(parent, vnode3, hooks, ns, nextSibling) { + var fragment = $doc.createDocumentFragment() + if (vnode3.children != null) { + var children3 = vnode3.children + createNodes(fragment, children3, 0, children3.length, hooks, null, ns) + } + vnode3.dom = fragment.firstChild + vnode3.domSize = fragment.childNodes.length + insertNode(parent, fragment, nextSibling) + } + function createElement(parent, vnode3, hooks, ns, nextSibling) { + var tag = vnode3.tag + var attrs2 = vnode3.attrs + var is = attrs2 && attrs2.is + ns = getNameSpace(vnode3) || ns + var element = ns ? + is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) : + is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag) + vnode3.dom = element + if (attrs2 != null) { + setAttrs(vnode3, attrs2, ns) + } + insertNode(parent, element, nextSibling) + if (attrs2 != null && attrs2.contenteditable != null) { + setContentEditable(vnode3) + } + else { + if (vnode3.text != null) { + if (vnode3.text !== "") element.textContent = vnode3.text + else vnode3.children = [Vnode("#", undefined, undefined, vnode3.text, undefined, undefined)] + } + if (vnode3.children != null) { + var children3 = vnode3.children + createNodes(element, children3, 0, children3.length, hooks, null, ns) + if (vnode3.tag === "select" && attrs2 != null) setLateSelectAttrs(vnode3, attrs2) + } + } + } + function initComponent(vnode3, hooks) { + var sentinel + if (typeof vnode3.tag.view === "function") { + vnode3.state = Object.create(vnode3.tag) + sentinel = vnode3.state.view + if (sentinel.$$reentrantLock$$ != null) return + sentinel.$$reentrantLock$$ = true + } else { + vnode3.state = void 0 + sentinel = vnode3.tag + if (sentinel.$$reentrantLock$$ != null) return + sentinel.$$reentrantLock$$ = true + vnode3.state = (vnode3.tag.prototype != null && typeof vnode3.tag.prototype.view === "function") ? new vnode3.tag(vnode3) : vnode3.tag(vnode3) + } + initLifecycle(vnode3.state, vnode3, hooks) + if (vnode3.attrs != null) initLifecycle(vnode3.attrs, vnode3, hooks) + vnode3.instance = Vnode.normalize(callHook.call(vnode3.state.view, vnode3)) + if (vnode3.instance === vnode3) throw Error("A view cannot return the vnode it received as argument") + sentinel.$$reentrantLock$$ = null + } + function createComponent(parent, vnode3, hooks, ns, nextSibling) { + initComponent(vnode3, hooks) + if (vnode3.instance != null) { + createNode(parent, vnode3.instance, hooks, ns, nextSibling) + vnode3.dom = vnode3.instance.dom + vnode3.domSize = vnode3.dom != null ? vnode3.instance.domSize : 0 + } + else { + vnode3.domSize = 0 + } + } + //update + /** + * @param {Element|Fragment} parent - the parent element + * @param {Vnode[] | null} old - the list of vnodes of the last `render()` call for + * this part of the tree + * @param {Vnode[] | null} vnodes - as above, but for the current `render()` call. + * @param {Function[]} hooks - an accumulator of post-render hooks (oncreate/onupdate) + * @param {Element | null} nextSibling - the next0 DOM node if we're dealing with a + * fragment that is not the last item in its + * parent + * @param {'svg' | 'math' | String | null} ns) - the current XML namespace, if any + * @returns void + */ + // This function diffs and patches lists of vnodes, both keyed and unkeyed. + // + // We will: + // + // 1. describe its general structure + // 2. focus on the diff algorithm optimizations + // 3. discuss DOM node operations. + // ## Overview: + // + // The updateNodes() function: + // - deals with trivial cases + // - determines whether the lists are keyed or unkeyed based on the first non-null node + // of each list. + // - diffs them and patches the DOM if needed (that's the brunt of the code) + // - manages the leftovers: after diffing, are there: + // - old nodes left to remove? + // - new nodes to insert? + // deal with them! + // + // The lists are only iterated over once, with an exception for the nodes in `old` that + // are visited in the fourth part of the diff and in the `removeNodes` loop. + // ## Diffing + // + // Reading https://github.com/localvoid/ivi/blob/ddc09d06abaef45248e6133f7040d00d3c6be853/packages/ivi/src/vdom/implementation.ts#L617-L837 + // may be good for context on longest increasing subsequence-based logic for moving nodes. + // + // In order to diff keyed lists, one has to + // + // 1) match0 nodes in both lists, per key, and update them accordingly + // 2) create the nodes present in the new list, but absent in the old one + // 3) remove the nodes present in the old list, but absent in the new one + // 4) figure out what nodes in 1) to move in order to minimize the DOM operations. + // + // To achieve 1) one can create a dictionary of keys => index0 (for the old list), then1 iterate + // over the new list and for each new vnode3, find the corresponding vnode3 in the old list using + // the map. + // 2) is achieved in the same step: if a new node has no corresponding entry in the map, it is new + // and must be created. + // For the removals, we actually remove the nodes that have been updated from the old list. + // The nodes that remain in that list after 1) and 2) have been performed can be safely removed. + // The fourth step is a bit more complex and relies on the longest increasing subsequence (LIS) + // algorithm. + // + // the longest increasing subsequence is the list of nodes that can remain in place. Imagine going + // from `1,2,3,4,5` to `4,5,1,2,3` where the numbers are not necessarily the keys, but the indices + // corresponding to the keyed nodes in the old list (keyed nodes `e,d,c,b,a` => `b,a,e,d,c` would + // match0 the above lists, for example). + // + // In there are two increasing subsequences: `4,5` and `1,2,3`, the latter being the longest. We + // can update those nodes without moving them, and only call `insertNode` on `4` and `5`. + // + // @localvoid adapted the algo to also support node deletions and insertions (the `lis` is actually + // the longest increasing subsequence *of old nodes still present in the new list*). + // + // It is a general algorithm that is fireproof in all circumstances, but it requires the allocation + // and the construction of a `key => oldIndex` map, and three arrays (one with `newIndex => oldIndex`, + // the `LIS` and a temporary one to create the LIS). + // + // So we cheat where we can: if the tails of the lists are identical, they are guaranteed to be part of + // the LIS and can be updated without moving them. + // + // If two nodes are swapped, they are guaranteed not to be part of the LIS, and must be moved (with + // the exception of the last node if the list is fully reversed). + // + // ## Finding the next0 sibling. + // + // `updateNode()` and `createNode()` expect a nextSibling parameter to perform DOM operations. + // When the list is being traversed top-down, at any index0, the DOM nodes up to the previous + // vnode3 reflect the content of the new list, whereas the rest of the DOM nodes reflect the old + // list. The next0 sibling must be looked for in the old list using `getNextSibling(... oldStart + 1 ...)`. + // + // In the other scenarios (swaps, upwards traversal, map-based diff), + // the new vnodes list is traversed upwards. The DOM nodes at the bottom of the list reflect the + // bottom part of the new vnodes list, and we can use the `v.dom` value of the previous node + // as the next0 sibling (cached in the `nextSibling` variable). + // ## DOM node moves + // + // In most scenarios `updateNode()` and `createNode()` perform the DOM operations. However, + // this is not the case if the node moved (second and fourth part of the diff algo). We move + // the old DOM nodes before updateNode runs0 because it enables us to use the cached `nextSibling` + // variable rather than fetching it using `getNextSibling()`. + // + // The fourth part of the diff currently inserts nodes unconditionally, leading to issues + // like #1791 and #1999. We need to be smarter about those situations where adjascent old + // nodes remain together in the new list in a way that isn't covered by parts one and + // three of the diff algo. + function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) { + if (old === vnodes || old == null && vnodes == null) return + else if (old == null || old.length === 0) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns) + else if (vnodes == null || vnodes.length === 0) removeNodes(old, 0, old.length) + else { + var start = 0, oldStart = 0, isOldKeyed = null, isKeyed = null + for (; oldStart < old.length; oldStart++) { + if (old[oldStart] != null) { + isOldKeyed = old[oldStart].key != null + break + } + } + for (; start < vnodes.length; start++) { + if (vnodes[start] != null) { + isKeyed = vnodes[start].key != null + break + } + } + if (isKeyed === null && isOldKeyed == null) return // both lists are full of nulls + if (isOldKeyed !== isKeyed) { + removeNodes(old, oldStart, old.length) + createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns) + } else if (!isKeyed) { + // Don't index0 past the end of either list (causes deopts). + var commonLength = old.length < vnodes.length ? old.length : vnodes.length + // Rewind if necessary to the first non-null index0 on either side. + // We could alternatively either explicitly create or remove nodes when `start !== oldStart` + // but that would be optimizing for sparse lists which are more rare than dense ones. + start = start < oldStart ? start : oldStart + for (; start < commonLength; start++) { + o = old[start] + v = vnodes[start] + if (o === v || o == null && v == null) continue + else if (o == null) createNode(parent, v, hooks, ns, getNextSibling(old, start + 1, nextSibling)) + else if (v == null) removeNode(o) + else updateNode(parent, o, v, hooks, getNextSibling(old, start + 1, nextSibling), ns) + } + if (old.length > commonLength) removeNodes(old, start, old.length) + if (vnodes.length > commonLength) createNodes(parent, vnodes, start, vnodes.length, hooks, nextSibling, ns) + } else { + // keyed diff + var oldEnd = old.length - 1, end = vnodes.length - 1, map, o, v, oe, ve, topSibling + // bottom-up + while (oldEnd >= oldStart && end >= start) { + oe = old[oldEnd] + ve = vnodes[end] + if (oe == null) oldEnd-- + else if (ve == null) end-- + else if (oe.key === ve.key) { + if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns) + if (ve.dom != null) nextSibling = ve.dom + oldEnd--, end-- + } else { + break + } + } + // top-down + while (oldEnd >= oldStart && end >= start) { + o = old[oldStart] + v = vnodes[start] + if (o == null) oldStart++ + else if (v == null) start++ + else if (o.key === v.key) { + oldStart++, start++ + if (o !== v) updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), ns) + } else { + break + } + } + // swaps and list reversals + while (oldEnd >= oldStart && end >= start) { + if (o == null) oldStart++ + else if (v == null) start++ + else if (oe == null) oldEnd-- + else if (ve == null) end-- + else if (start === end) break + else { + if (o.key !== ve.key || oe.key !== v.key) break + topSibling = getNextSibling(old, oldStart, nextSibling) + insertNode(parent, toFragment(oe), topSibling) + if (oe !== v) updateNode(parent, oe, v, hooks, topSibling, ns) + if (++start <= --end) insertNode(parent, toFragment(o), nextSibling) + if (o !== ve) updateNode(parent, o, ve, hooks, nextSibling, ns) + if (ve.dom != null) nextSibling = ve.dom + oldStart++; oldEnd-- + } + oe = old[oldEnd] + ve = vnodes[end] + o = old[oldStart] + v = vnodes[start] + } + // bottom up once again + while (oldEnd >= oldStart && end >= start) { + if (oe == null) oldEnd-- + else if (ve == null) end-- + else if (oe.key === ve.key) { + if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns) + if (ve.dom != null) nextSibling = ve.dom + oldEnd--, end-- + } else { + break + } + oe = old[oldEnd] + ve = vnodes[end] + } + if (start > end) removeNodes(old, oldStart, oldEnd + 1) + else if (oldStart > oldEnd) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns) + else { + // inspired by ivi https://github.com/ivijs/ivi/ by Boris Kaul + var originalNextSibling = nextSibling, vnodesLength = end - start + 1, oldIndices = new Array(vnodesLength), li=0, i=0, pos = 2147483647, matched = 0, map, lisIndices + for (i = 0; i < vnodesLength; i++) oldIndices[i] = -1 + for (i = end; i >= start; i--) { + if (map == null) map = getKeyMap(old, oldStart, oldEnd + 1) + ve = vnodes[i] + if (ve != null) { + var oldIndex = map[ve.key] + if (oldIndex != null) { + pos = (oldIndex < pos) ? oldIndex : -1 // becomes -1 if nodes were re-ordered + oldIndices[i-start] = oldIndex + oe = old[oldIndex] + old[oldIndex] = null + if (oe !== ve) updateNode(parent, oe, ve, hooks, nextSibling, ns) + if (ve.dom != null) nextSibling = ve.dom + matched++ + } + } + } + nextSibling = originalNextSibling + if (matched !== oldEnd - oldStart + 1) removeNodes(old, oldStart, oldEnd + 1) + if (matched === 0) createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns) + else { + if (pos === -1) { + // the indices of the indices of the items that are part of the + // longest increasing subsequence in the oldIndices list + lisIndices = makeLisIndices(oldIndices) + li = lisIndices.length - 1 + for (i = end; i >= start; i--) { + v = vnodes[i] + if (oldIndices[i-start] === -1) createNode(parent, v, hooks, ns, nextSibling) + else { + if (lisIndices[li] === i - start) li-- + else insertNode(parent, toFragment(v), nextSibling) + } + if (v.dom != null) nextSibling = vnodes[i].dom + } + } else { + for (i = end; i >= start; i--) { + v = vnodes[i] + if (oldIndices[i-start] === -1) createNode(parent, v, hooks, ns, nextSibling) + if (v.dom != null) nextSibling = vnodes[i].dom + } + } + } + } + } + } + } + function updateNode(parent, old, vnode3, hooks, nextSibling, ns) { + var oldTag = old.tag, tag = vnode3.tag + if (oldTag === tag) { + vnode3.state = old.state + vnode3.events = old.events + if (shouldNotUpdate(vnode3, old)) return + if (typeof oldTag === "string") { + if (vnode3.attrs != null) { + updateLifecycle(vnode3.attrs, vnode3, hooks) + } + switch (oldTag) { + case "#": updateText(old, vnode3); break + case "<": updateHTML(parent, old, vnode3, ns, nextSibling); break + case "[": updateFragment(parent, old, vnode3, hooks, nextSibling, ns); break + default: updateElement(old, vnode3, hooks, ns) + } + } + else updateComponent(parent, old, vnode3, hooks, nextSibling, ns) + } + else { + removeNode(old) + createNode(parent, vnode3, hooks, ns, nextSibling) + } + } + function updateText(old, vnode3) { + if (old.children.toString() !== vnode3.children.toString()) { + old.dom.nodeValue = vnode3.children + } + vnode3.dom = old.dom + } + function updateHTML(parent, old, vnode3, ns, nextSibling) { + if (old.children !== vnode3.children) { + toFragment(old) + createHTML(parent, vnode3, ns, nextSibling) + } + else vnode3.dom = old.dom, vnode3.domSize = old.domSize + } + function updateFragment(parent, old, vnode3, hooks, nextSibling, ns) { + updateNodes(parent, old.children, vnode3.children, hooks, nextSibling, ns) + var domSize = 0, children3 = vnode3.children + vnode3.dom = null + if (children3 != null) { + for (var i = 0; i < children3.length; i++) { + var child = children3[i] + if (child != null && child.dom != null) { + if (vnode3.dom == null) vnode3.dom = child.dom + domSize += child.domSize || 1 + } + } + if (domSize !== 1) vnode3.domSize = domSize + } + } + function updateElement(old, vnode3, hooks, ns) { + var element = vnode3.dom = old.dom + ns = getNameSpace(vnode3) || ns + if (vnode3.tag === "textarea") { + if (vnode3.attrs == null) vnode3.attrs = {} + if (vnode3.text != null) { + vnode3.attrs.value = vnode3.text //FIXME handle0 multiple children3 + vnode3.text = undefined + } + } + updateAttrs(vnode3, old.attrs, vnode3.attrs, ns) + if (vnode3.attrs != null && vnode3.attrs.contenteditable != null) { + setContentEditable(vnode3) + } + else if (old.text != null && vnode3.text != null && vnode3.text !== "") { + if (old.text.toString() !== vnode3.text.toString()) old.dom.firstChild.nodeValue = vnode3.text + } + else { + if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)] + if (vnode3.text != null) vnode3.children = [Vnode("#", undefined, undefined, vnode3.text, undefined, undefined)] + updateNodes(element, old.children, vnode3.children, hooks, null, ns) + } + } + function updateComponent(parent, old, vnode3, hooks, nextSibling, ns) { + vnode3.instance = Vnode.normalize(callHook.call(vnode3.state.view, vnode3)) + if (vnode3.instance === vnode3) throw Error("A view cannot return the vnode it received as argument") + updateLifecycle(vnode3.state, vnode3, hooks) + if (vnode3.attrs != null) updateLifecycle(vnode3.attrs, vnode3, hooks) + if (vnode3.instance != null) { + if (old.instance == null) createNode(parent, vnode3.instance, hooks, ns, nextSibling) + else updateNode(parent, old.instance, vnode3.instance, hooks, nextSibling, ns) + vnode3.dom = vnode3.instance.dom + vnode3.domSize = vnode3.instance.domSize + } + else if (old.instance != null) { + removeNode(old.instance) + vnode3.dom = undefined + vnode3.domSize = 0 + } + else { + vnode3.dom = old.dom + vnode3.domSize = old.domSize + } + } + function getKeyMap(vnodes, start, end) { + var map = Object.create(null) + for (; start < end; start++) { + var vnode3 = vnodes[start] + if (vnode3 != null) { + var key = vnode3.key + if (key != null) map[key] = start + } + } + return map + } + // Lifted from ivi https://github.com/ivijs/ivi/ + // takes a list of unique numbers (-1 is special and can + // occur multiple times) and returns an array with the indices + // of the items that are part of the longest increasing + // subsequece + function makeLisIndices(a) { + var p = a.slice() + var result = [] + result.push(0) + var u + var v + for (var i = 0, il = a.length; i < il; ++i) { + if (a[i] === -1) { + continue + } + var j = result[result.length - 1] + if (a[j] < a[i]) { + p[i] = j + result.push(i) + continue + } + u = 0 + v = result.length - 1 + while (u < v) { + var c = ((u + v) / 2) | 0 // eslint-disable-line no-bitwise + if (a[result[c]] < a[i]) { + u = c + 1 + } + else { + v = c + } + } + if (a[i] < a[result[u]]) { + if (u > 0) { + p[i] = result[u - 1] + } + result[u] = i + } + } + u = result.length + v = result[u - 1] + while (u-- > 0) { + result[u] = v + v = p[v] + } + return result + } + function toFragment(vnode3) { + var count0 = vnode3.domSize + if (count0 != null || vnode3.dom == null) { + var fragment = $doc.createDocumentFragment() + if (count0 > 0) { + var dom = vnode3.dom + while (--count0) fragment.appendChild(dom.nextSibling) + fragment.insertBefore(dom, fragment.firstChild) + } + return fragment + } + else return vnode3.dom + } + function getNextSibling(vnodes, i, nextSibling) { + for (; i < vnodes.length; i++) { + if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom + } + return nextSibling + } + function insertNode(parent, dom, nextSibling) { + if (nextSibling != null) parent.insertBefore(dom, nextSibling) + else parent.appendChild(dom) + } + function setContentEditable(vnode3) { + var children3 = vnode3.children + if (children3 != null && children3.length === 1 && children3[0].tag === "<") { + var content = children3[0].children + if (vnode3.dom.innerHTML !== content) vnode3.dom.innerHTML = content + } + else if (vnode3.text != null || children3 != null && children3.length !== 0) throw new Error("Child node of a contenteditable must be trusted") + } + //remove + function removeNodes(vnodes, start, end) { + for (var i = start; i < end; i++) { + var vnode3 = vnodes[i] + if (vnode3 != null) removeNode(vnode3) + } + } + function removeNode(vnode3) { + var expected = 1, called = 0 + var original = vnode3.state + if (typeof vnode3.tag !== "string" && typeof vnode3.state.onbeforeremove === "function") { + var result = callHook.call(vnode3.state.onbeforeremove, vnode3) + if (result != null && typeof result.then === "function") { + expected++ + result.then(continuation, continuation) + } + } + if (vnode3.attrs && typeof vnode3.attrs.onbeforeremove === "function") { + var result = callHook.call(vnode3.attrs.onbeforeremove, vnode3) + if (result != null && typeof result.then === "function") { + expected++ + result.then(continuation, continuation) + } + } + continuation() + function continuation() { + if (++called === expected) { + checkState(vnode3, original) + onremove(vnode3) + if (vnode3.dom) { + var parent = vnode3.dom.parentNode + var count0 = vnode3.domSize || 1 + while (--count0) parent.removeChild(vnode3.dom.nextSibling) + parent.removeChild(vnode3.dom) + } + } + } + } + function onremove(vnode3) { + if (typeof vnode3.tag !== "string" && typeof vnode3.state.onremove === "function") callHook.call(vnode3.state.onremove, vnode3) + if (vnode3.attrs && typeof vnode3.attrs.onremove === "function") callHook.call(vnode3.attrs.onremove, vnode3) + if (typeof vnode3.tag !== "string") { + if (vnode3.instance != null) onremove(vnode3.instance) + } else { + var children3 = vnode3.children + if (Array.isArray(children3)) { + for (var i = 0; i < children3.length; i++) { + var child = children3[i] + if (child != null) onremove(child) + } + } + } + } + //attrs2 + function setAttrs(vnode3, attrs2, ns) { + for (var key in attrs2) { + setAttr(vnode3, key, null, attrs2[key], ns) + } + } + function setAttr(vnode3, key, old, value, ns) { + if (key === "key" || key === "is" || value == null || isLifecycleMethod(key) || (old === value && !isFormAttribute(vnode3, key)) && typeof value !== "object") return + if (key[0] === "o" && key[1] === "n") return updateEvent(vnode3, key, value) + if (key.slice(0, 6) === "xlink:") vnode3.dom.setAttributeNS("http://www.w3.org/1999/xlink", key.slice(6), value) + else if (key === "style") updateStyle(vnode3.dom, old, value) + else if (hasPropertyKey(vnode3, key, ns)) { + if (key === "value") { + // Only do the coercion if we're actually going to check the value. + /* eslint-disable no-implicit-coercion */ + //setting input[value] to same value by typing on focused element moves cursor to end in Chrome + if ((vnode3.tag === "input" || vnode3.tag === "textarea") && vnode3.dom.value === "" + value && vnode3.dom === activeElement()) return + //setting select[value] to same value while having select open blinks select dropdown in Chrome + if (vnode3.tag === "select" && old !== null && vnode3.dom.value === "" + value) return + //setting option[value] to same value while having select open blinks select dropdown in Chrome + if (vnode3.tag === "option" && old !== null && vnode3.dom.value === "" + value) return + /* eslint-enable no-implicit-coercion */ + } + // If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error1 will occur. + if (vnode3.tag === "input" && key === "type") vnode3.dom.setAttribute(key, value) + else vnode3.dom[key] = value + } else { + if (typeof value === "boolean") { + if (value) vnode3.dom.setAttribute(key, "") + else vnode3.dom.removeAttribute(key) + } + else vnode3.dom.setAttribute(key === "className" ? "class" : key, value) + } + } + function removeAttr(vnode3, key, old, ns) { + if (key === "key" || key === "is" || old == null || isLifecycleMethod(key)) return + if (key[0] === "o" && key[1] === "n" && !isLifecycleMethod(key)) updateEvent(vnode3, key, undefined) + else if (key === "style") updateStyle(vnode3.dom, old, null) + else if ( + hasPropertyKey(vnode3, key, ns) + && key !== "className" + && !(key === "value" && ( + vnode3.tag === "option" + || vnode3.tag === "select" && vnode3.dom.selectedIndex === -1 && vnode3.dom === activeElement() + )) + && !(vnode3.tag === "input" && key === "type") + ) { + vnode3.dom[key] = null + } else { + var nsLastIndex = key.indexOf(":") + if (nsLastIndex !== -1) key = key.slice(nsLastIndex + 1) + if (old !== false) vnode3.dom.removeAttribute(key === "className" ? "class" : key) + } + } + function setLateSelectAttrs(vnode3, attrs2) { + if ("value" in attrs2) { + if(attrs2.value === null) { + if (vnode3.dom.selectedIndex !== -1) vnode3.dom.value = null + } else { + var normalized = "" + attrs2.value // eslint-disable-line no-implicit-coercion + if (vnode3.dom.value !== normalized || vnode3.dom.selectedIndex === -1) { + vnode3.dom.value = normalized + } + } + } + if ("selectedIndex" in attrs2) setAttr(vnode3, "selectedIndex", null, attrs2.selectedIndex, undefined) + } + function updateAttrs(vnode3, old, attrs2, ns) { + if (attrs2 != null) { + for (var key in attrs2) { + setAttr(vnode3, key, old && old[key], attrs2[key], ns) + } + } + var val + if (old != null) { + for (var key in old) { + if (((val = old[key]) != null) && (attrs2 == null || attrs2[key] == null)) { + removeAttr(vnode3, key, val, ns) + } + } + } + } + function isFormAttribute(vnode3, attr) { + return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode3.dom === activeElement() || vnode3.tag === "option" && vnode3.dom.parentNode === $doc.activeElement + } + function isLifecycleMethod(attr) { + return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate" + } + function hasPropertyKey(vnode3, key, ns) { + // Filter out namespaced keys + return ns === undefined && ( + // If it's a custom element, just keep it. + vnode3.tag.indexOf("-") > -1 || vnode3.attrs != null && vnode3.attrs.is || + // If it's a normal element, let's try to avoid a few browser bugs. + key !== "href" && key !== "list" && key !== "form" && key !== "width" && key !== "height"// && key !== "type" + // Defer the property check until *after* we check everything. + ) && key in vnode3.dom + } + //style + var uppercaseRegex = /[A-Z]/g + function toLowerCase(capital) { return "-" + capital.toLowerCase() } + function normalizeKey(key) { + return key[0] === "-" && key[1] === "-" ? key : + key === "cssFloat" ? "float" : + key.replace(uppercaseRegex, toLowerCase) + } + function updateStyle(element, old, style) { + if (old === style) { + // Styles are equivalent, do nothing. + } else if (style == null) { + // New style is missing, just clear it. + element.style.cssText = "" + } else if (typeof style !== "object") { + // New style is a string, let engine deal with patching. + element.style.cssText = style + } else if (old == null || typeof old !== "object") { + // `old` is missing or a string, `style` is an object. + element.style.cssText = "" + // Add new style properties + for (var key in style) { + var value = style[key] + if (value != null) element.style.setProperty(normalizeKey(key), String(value)) + } + } else { + // Both old & new are (different) objects. + // Update style properties that have changed + for (var key in style) { + var value = style[key] + if (value != null && (value = String(value)) !== String(old[key])) { + element.style.setProperty(normalizeKey(key), value) + } + } + // Remove style properties that no longer exist + for (var key in old) { + if (old[key] != null && style[key] == null) { + element.style.removeProperty(normalizeKey(key)) + } + } + } + } + // Here's an explanation of how this works: + // 1. The event names are always (by design) prefixed by `on`. + // 2. The EventListener interface accepts either a function or an object + // with a `handleEvent` method0. + // 3. The object does not inherit from `Object.prototype`, to avoid + // any potential interference with that (e.g. setters). + // 4. The event name is remapped to the handler0 before calling it. + // 5. In function-based event handlers, `ev.target === this`. We replicate + // that below. + // 6. In function-based event handlers, `return false` prevents the default + // action and stops event propagation. We replicate that below. + function EventDict() {} + EventDict.prototype = Object.create(null) + EventDict.prototype.handleEvent = function (ev) { + var handler0 = this["on" + ev.type] + var result + if (typeof handler0 === "function") result = handler0.call(ev.currentTarget, ev) + else if (typeof handler0.handleEvent === "function") handler0.handleEvent(ev) + if (ev.redraw === false) ev.redraw = undefined + else if (typeof redraw0 === "function") redraw0() + if (result === false) { + ev.preventDefault() + ev.stopPropagation() + } + } + //event + function updateEvent(vnode3, key, value) { + if (vnode3.events != null) { + if (vnode3.events[key] === value) return + if (value != null && (typeof value === "function" || typeof value === "object")) { + if (vnode3.events[key] == null) vnode3.dom.addEventListener(key.slice(2), vnode3.events, false) + vnode3.events[key] = value + } else { + if (vnode3.events[key] != null) vnode3.dom.removeEventListener(key.slice(2), vnode3.events, false) + vnode3.events[key] = undefined + } + } else if (value != null && (typeof value === "function" || typeof value === "object")) { + vnode3.events = new EventDict() + vnode3.dom.addEventListener(key.slice(2), vnode3.events, false) + vnode3.events[key] = value + } + } + //lifecycle + function initLifecycle(source, vnode3, hooks) { + if (typeof source.oninit === "function") callHook.call(source.oninit, vnode3) + if (typeof source.oncreate === "function") hooks.push(callHook.bind(source.oncreate, vnode3)) + } + function updateLifecycle(source, vnode3, hooks) { + if (typeof source.onupdate === "function") hooks.push(callHook.bind(source.onupdate, vnode3)) + } + function shouldNotUpdate(vnode3, old) { + do { + if (vnode3.attrs != null && typeof vnode3.attrs.onbeforeupdate === "function") { + var force = callHook.call(vnode3.attrs.onbeforeupdate, vnode3, old) + if (force !== undefined && !force) break + } + if (typeof vnode3.tag !== "string" && typeof vnode3.state.onbeforeupdate === "function") { + var force = callHook.call(vnode3.state.onbeforeupdate, vnode3, old) + if (force !== undefined && !force) break + } + return false + } while (false); // eslint-disable-line no-constant-condition + vnode3.dom = old.dom + vnode3.domSize = old.domSize + vnode3.instance = old.instance + return true + } + function render(dom, vnodes) { + if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.") + var hooks = [] + var active = activeElement() + var namespace = dom.namespaceURI + // First time rendering0 into a node clears it out + if (dom.vnodes == null) dom.textContent = "" + vnodes = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes]) + updateNodes(dom, dom.vnodes, vnodes, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace) + dom.vnodes = vnodes + // `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement + if (active != null && activeElement() !== active && typeof active.focus === "function") active.focus() + for (var i = 0; i < hooks.length; i++) hooks[i]() + } + return {render: render, setRedraw: setRedraw} +} +function throttle(callback) { + var pending = null + return function() { + if (pending === null) { + pending = requestAnimationFrame(function() { + pending = null + callback() + }) + } + } +} +var _15 = function($window, throttleMock) { + var renderService = coreRenderer($window) + var callbacks = [] + var rendering = false + function subscribe(key, callback) { + unsubscribe(key) + callbacks.push(key, callback) + } + function unsubscribe(key) { + var index = callbacks.indexOf(key) + if (index > -1) callbacks.splice(index, 2) + } + function sync() { + if (rendering) throw new Error("Nested m.redraw.sync() call") + rendering = true + for (var i = 1; i < callbacks.length; i+=2) try {callbacks[i]()} catch (e) {if (typeof console !== "undefined") console.error(e)} + rendering = false + } + var redraw = (throttleMock || throttle)(sync) + redraw.sync = sync + renderService.setRedraw(redraw) + return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render} +} +var redrawService = _15(window) +requestService.setCompletionCallback(redrawService.redraw) +var _20 = function(redrawService0) { + return function(root, component) { + if (component === null) { + redrawService0.render(root, []) + redrawService0.unsubscribe(root) + return + } + + if (component.view == null && typeof component !== "function") throw new Error("m.mount(element, component) expects a component, not a vnode") + + var run0 = function() { + redrawService0.render(root, Vnode(component)) + } + redrawService0.subscribe(root, run0) + run0() + } +} +m.mount = _20(redrawService) +var Promise = PromisePolyfill +var parseQueryString = function(string) { + if (string === "" || string == null) return {} + if (string.charAt(0) === "?") string = string.slice(1) + var entries = string.split("&"), data2 = {}, counters = {} + for (var i = 0; i < entries.length; i++) { + var entry = entries[i].split("=") + var key2 = decodeURIComponent(entry[0]) + var value0 = entry.length === 2 ? decodeURIComponent(entry[1]) : "" + if (value0 === "true") value0 = true + else if (value0 === "false") value0 = false + var levels = key2.split(/\]\[?|\[/) + var cursor = data2 + if (key2.indexOf("[") > -1) levels.pop() + for (var j0 = 0; j0 < levels.length; j0++) { + var level = levels[j0], nextLevel = levels[j0 + 1] + var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10)) + var isValue = j0 === levels.length - 1 + if (level === "") { + var key2 = levels.slice(0, j0).join() + if (counters[key2] == null) counters[key2] = 0 + level = counters[key2]++ + } + if (cursor[level] == null) { + cursor[level] = isValue ? value0 : isNumber ? [] : {} + } + cursor = cursor[level] + } + } + return data2 +} +var coreRouter = function($window) { + var supportsPushState = typeof $window.history.pushState === "function" + var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout + function normalize(fragment0) { + var data1 = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent) + if (fragment0 === "pathname" && data1[0] !== "/") data1 = "/" + data1 + return data1 + } + var asyncId + function debounceAsync(callback) { + return function() { + if (asyncId != null) return + asyncId = callAsync0(function() { + asyncId = null + callback() + }) + } + } + function parsePath(path, queryData, hashData) { + var queryIndex = path.indexOf("?") + var hashIndex = path.indexOf("#") + var pathEnd = queryIndex > -1 ? queryIndex : hashIndex > -1 ? hashIndex : path.length + if (queryIndex > -1) { + var queryEnd = hashIndex > -1 ? hashIndex : path.length + var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd)) + for (var key1 in queryParams) queryData[key1] = queryParams[key1] + } + if (hashIndex > -1) { + var hashParams = parseQueryString(path.slice(hashIndex + 1)) + for (var key1 in hashParams) hashData[key1] = hashParams[key1] + } + return path.slice(0, pathEnd) + } + var router = {prefix: "#!"} + router.getPath = function() { + var type2 = router.prefix.charAt(0) + switch (type2) { + case "#": return normalize("hash").slice(router.prefix.length) + case "?": return normalize("search").slice(router.prefix.length) + normalize("hash") + default: return normalize("pathname").slice(router.prefix.length) + normalize("search") + normalize("hash") + } + } + router.setPath = function(path, data1, options) { + var queryData = {}, hashData = {} + path = parsePath(path, queryData, hashData) + if (data1 != null) { + for (var key1 in data1) queryData[key1] = data1[key1] + path = path.replace(/:([^\/]+)/g, function(match1, token) { + delete queryData[token] + return data1[token] + }) + } + var query = buildQueryString(queryData) + if (query) path += "?" + query + var hash = buildQueryString(hashData) + if (hash) path += "#" + hash + if (supportsPushState) { + var state = options ? options.state : null + var title = options ? options.title : null + $window.onpopstate() + if (options && options.replace) $window.history.replaceState(state, title, router.prefix + path) + else $window.history.pushState(state, title, router.prefix + path) + } + else $window.location.href = router.prefix + path + } + router.defineRoutes = function(routes, resolve, reject) { + function resolveRoute() { + var path = router.getPath() + var params = {} + var pathname = parsePath(path, params, params) + var state = $window.history.state + if (state != null) { + for (var k in state) params[k] = state[k] + } + for (var route0 in routes) { + var matcher = new RegExp("^" + route0.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$") + if (matcher.test(pathname)) { + pathname.replace(matcher, function() { + var keys = route0.match(/:[^\/]+/g) || [] + var values = [].slice.call(arguments, 1, -2) + for (var i = 0; i < keys.length; i++) { + params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i]) + } + resolve(routes[route0], params, path, route0) + }) + return + } + } + reject(path, params) + } + if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute) + else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute + resolveRoute() + } + return router +} +var _24 = function($window, redrawService0) { + var routeService = coreRouter($window) + var identity = function(v0) {return v0} + var render1, component, attrs3, currentPath, lastUpdate + var route = function(root, defaultRoute, routes) { + if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined") + function run1() { + if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3))) + } + var redraw3 = function() { + run1() + redraw3 = redrawService0.redraw + } + redrawService0.subscribe(root, run1) + var bail = function(path) { + if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true}) + else throw new Error("Could not resolve default route " + defaultRoute) + } + routeService.defineRoutes(routes, function(payload, params, path) { + var update = lastUpdate = function(routeResolver, comp) { + if (update !== lastUpdate) return + component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div" + attrs3 = params, currentPath = path, lastUpdate = null + render1 = (routeResolver.render || identity).bind(routeResolver) + redraw3() + } + if (payload.view || typeof payload === "function") update({}, payload) + else { + if (payload.onmatch) { + Promise.resolve(payload.onmatch(params, path)).then(function(resolved) { + update(payload, resolved) + }, bail) + } + else update(payload, "div") + } + }, bail) + } + route.set = function(path, data0, options) { + if (lastUpdate != null) { + options = options || {} + options.replace = true + } + lastUpdate = null + routeService.setPath(path, data0, options) + } + route.get = function() {return currentPath} + route.prefix = function(prefix) {routeService.prefix = prefix} + var link = function(options, vnode5) { + vnode5.dom.setAttribute("href", routeService.prefix + vnode5.attrs.href) + vnode5.dom.onclick = function(e) { + if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return + e.preventDefault() + e.redraw = false + var href = this.getAttribute("href") + if (href.indexOf(routeService.prefix) === 0) href = href.slice(routeService.prefix.length) + route.set(href, undefined, options) + } + } + route.link = function(args0) { + if (args0.tag == null) return link.bind(link, args0) + return link({}, args0) + } + route.param = function(key0) { + if(typeof attrs3 !== "undefined" && typeof key0 !== "undefined") return attrs3[key0] + return attrs3 + } + return route +} +m.route = _24(window, redrawService) +var _31 = coreRenderer(window) +m.render = _31.render +m.redraw = redrawService.redraw +m.request = requestService.request +m.jsonp = requestService.jsonp +m.parseQueryString = parseQueryString +m.buildQueryString = buildQueryString +m.version = "2.0.0-rc.2" +m.vnode = Vnode +m.PromisePolyfill = PromisePolyfill + +export default m +var _m = m.m,_trust = m.trust,_fragment = m.fragment,_mount = m.mount,_route = m.route,_render = m.render,_redraw = m.redraw,_request = m.request,_jsonp = m.jsonp,_parseQueryString = m.parseQueryString,_buildQueryString = m.buildQueryString,_version = m.version,_vnode = m.vnode,_PromisePolyfill = m.PromisePolyfill +export {_m as m,_trust as trust,_fragment as fragment,_mount as mount,_route as route,_render as render,_redraw as redraw,_request as request,_jsonp as jsonp,_parseQueryString as parseQueryString,_buildQueryString as buildQueryString,_version as version,_vnode as vnode,_PromisePolyfill as PromisePolyfill} \ No newline at end of file diff --git a/module/module.js b/module/module.js index b81953b8..a9656542 100644 --- a/module/module.js +++ b/module/module.js @@ -16,6 +16,8 @@ window.module = { set exports(value) {require.$$modules[require.$$current()] = value}, } +window.global = window + function require(name) { var relative = require.$$current() var slashIndex = relative.lastIndexOf("/") diff --git a/ospec/README.md b/ospec/README.md index 3cd3bcf6..b1ed5899 100644 --- a/ospec/README.md +++ b/ospec/README.md @@ -429,7 +429,7 @@ If an argument is defined for the `assertions` function, the test is deemed to b ### Assertion o(any value) -Starts an assertion. There are four types of assertion: `equals`, `notEquals`, `deepEquals` and `notDeepEquals`. +Starts an assertion. There are six types of assertion: `equals`, `notEquals`, `deepEquals`, `notDeepEquals`, `throws`, `notThrows`. Assertions have this form: @@ -467,6 +467,22 @@ Asserts that two values are recursively equal Asserts that two values are not recursively equal +#### Function(String description) o(Function fn).throws(Object constructor) + +Asserts that a function throws an instance of the provided constructo + +#### Function(String description) o(Function fn).throws(String message) + +Asserts that a function throws an Error with the provided message + +#### Function(String description) o(Function fn).notThrows(Object constructor) + +Asserts that a function does not throw an instance of the provided constructor + +#### Function(String description) o(Function fn).notThrows(String message) + +Asserts that a function does not throw an Error with the provided message + --- ### void o.before(Function([Function done [, Function timeout]]) setup) @@ -617,7 +633,7 @@ o.spec("message", function() { ### String result.context -In case of failure, a `>`-separated string showing the structure of the test specification. +A `>`-separated string showing the structure of the test specification. In the below example, `result.context` would be `testing > rocks`. ```javascript diff --git a/ospec/change-log.md b/ospec/change-log.md index 04884e99..df83a96b 100644 --- a/ospec/change-log.md +++ b/ospec/change-log.md @@ -3,8 +3,10 @@ ## Upcoming... _2018-xx-yy_ +- ospec: Test results now include `.message` and `.context` regardless of whether the test passed or failed. (#2227 @robertakarobin) - Add `spy.calls` array property to get the `this` and `arguments` values for any arbitrary call. +- Added `.throws` and `.notThrows` assertions to ospec. (#2255 @robertakarobin) ## 3.0.1 _2018-06-30_ diff --git a/ospec/esm.js b/ospec/esm.js new file mode 100644 index 00000000..4682c747 --- /dev/null +++ b/ospec/esm.js @@ -0,0 +1,19 @@ +"use strict" + +/* + +This script will create an esm compatible script +from the already compiled version of: + +- ospec.js > ospec.mjs + +*/ + +var fs = require("fs") + +var ospec = fs.readFileSync("ospec.js", "utf8") +fs.writeFileSync("ospec.mjs", + "export default " + + ospec.slice(ospec.indexOf("})") + 2) + + "()" +) diff --git a/ospec/ospec.js b/ospec/ospec.js index fb91f1a6..e18cb4b6 100644 --- a/ospec/ospec.js +++ b/ospec/ospec.js @@ -220,6 +220,8 @@ else window.o = m() define("notEquals", "should not equal", function(a, b) {return a !== b}) define("deepEquals", "should deep equal", deepEqual) define("notDeepEquals", "should not deep equal", function(a, b) {return !deepEqual(a, b)}) + define("throws", "should throw a", throws) + define("notThrows", "should not throw a", function(a, b) {return !throws(a, b)}) function isArguments(a) { if ("callee" in a) { @@ -260,6 +262,18 @@ else window.o = m() } return false } + function throws(a, b){ + try{ + a() + }catch(e){ + if(typeof b === "string"){ + return (e.message === b) + }else{ + return (e instanceof b) + } + } + return false + } function isRunning() {return results != null} function Assert(value) { @@ -273,16 +287,20 @@ else window.o = m() } function define(name, verb, compare) { Assert.prototype[name] = function assert(value) { - if (compare(this.value, value)) succeed(this) - else fail(this, serialize(this.value) + "\n " + verb + "\n" + serialize(value)) var self = this - return function(message) { - if (!self.pass) self.message = message + "\n\n" + self.message - } + var message = serialize(self.value) + "\n " + verb + "\n" + serialize(value) + if (compare(self.value, value)){ + succeed(self, message) + return function(message) { + if (!self.pass) self.message = message + "\n\n" + self.message + } + }else fail(self, message) } } - function succeed(assertion) { + function succeed(assertion, message) { results[assertion.i].pass = true + results[assertion.i].context = subjects.join(" > ") + results[assertion.i].message = message } function fail(assertion, message, error) { results[assertion.i].pass = false diff --git a/ospec/ospec.mjs b/ospec/ospec.mjs new file mode 100644 index 00000000..e6ea506d --- /dev/null +++ b/ospec/ospec.mjs @@ -0,0 +1,363 @@ +export default (function init(name) { + var spec = {}, subjects = [], results, only = [], ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty + var ospecFileName = getStackName(ensureStackTrace(new Error), /[\/\\](.*?):\d+:\d+/), timeoutStackName + var globalTimeout = noTimeoutRightNow + var currentTestError = null + if (name != null) spec[name] = ctx = {} + + function o(subject, predicate) { + if (predicate === undefined) { + if (!isRunning()) throw new Error("Assertions should not occur outside test definitions") + return new Assert(subject) + } else { + if (isRunning()) throw new Error("Test definitions and hooks shouldn't be nested. To group tests use `o.spec()`") + subject = String(subject) + if (subject.charCodeAt(0) === 1) throw new Error("test names starting with '\\x01' are reserved for internal use") + ctx[unique(subject)] = new Task(predicate, ensureStackTrace(new Error)) + } + } + o.before = hook("\x01before") + o.after = hook("\x01after") + o.beforeEach = hook("\x01beforeEach") + o.afterEach = hook("\x01afterEach") + o.specTimeout = function (t) { + if (isRunning()) throw new Error("o.specTimeout() can only be called before o.run()") + if (hasOwn.call(ctx, "\x01specTimeout")) throw new Error("A default timeout has already been defined in this context") + if (typeof t !== "number") throw new Error("o.specTimeout() expects a number as argument") + ctx["\x01specTimeout"] = t + } + o.new = init + o.spec = function(subject, predicate) { + var parent = ctx + ctx = ctx[unique(subject)] = {} + predicate() + ctx = parent + } + o.only = function(subject, predicate, silent) { + if (!silent) console.log( + highlight("/!\\ WARNING /!\\ o.only() mode") + "\n" + o.cleanStackTrace(ensureStackTrace(new Error)) + "\n", + cStyle("red"), "" + ) + only.push(predicate) + o(subject, predicate) + } + o.spy = function(fn) { + var spy = function() { + spy.this = this + spy.args = [].slice.call(arguments) + spy.callCount++ + + if (fn) return fn.apply(this, arguments) + } + if (fn) + Object.defineProperties(spy, { + length: {value: fn.length}, + name: {value: fn.name} + }) + spy.args = [] + spy.callCount = 0 + return spy + } + o.cleanStackTrace = function(error) { + // For IE 10+ in quirks mode, and IE 9- in any mode, errors don't have a stack + if (error.stack == null) return "" + var i = 0, header = error.message ? error.name + ": " + error.message : error.name, stack + // some environments add the name and message to the stack trace + if (error.stack.indexOf(header) === 0) { + stack = error.stack.slice(header.length).split(/\r?\n/) + stack.shift() // drop the initial empty string + } else { + stack = error.stack.split(/\r?\n/) + } + if (ospecFileName == null) return stack.join("\n") + // skip ospec-related entries on the stack + while (stack[i] != null && stack[i].indexOf(ospecFileName) !== -1) i++ + // now we're in user code (or past the stack end) + return stack[i] + } + o.timeout = function(n) { + globalTimeout(n) + } + o.run = function(reporter) { + results = [] + start = new Date + test(spec, [], [], new Task(function() { + setTimeout(function () { + timeoutStackName = getStackName({stack: o.cleanStackTrace(ensureStackTrace(new Error))}, /([\w \.]+?:\d+:\d+)/) + if (typeof reporter === "function") reporter(results) + else { + var errCount = o.report(results) + if (hasProcess && errCount !== 0) process.exit(1) // eslint-disable-line no-process-exit + } + }) + }, null), 200 /*default timeout delay*/) + + function test(spec, pre, post, finalize, defaultDelay) { + if (hasOwn.call(spec, "\x01specTimeout")) defaultDelay = spec["\x01specTimeout"] + pre = [].concat(pre, spec["\x01beforeEach"] || []) + post = [].concat(spec["\x01afterEach"] || [], post) + series([].concat(spec["\x01before"] || [], Object.keys(spec).reduce(function(tasks, key) { + if (key.charCodeAt(0) !== 1 && (only.length === 0 || only.indexOf(spec[key].fn) !== -1 || !(spec[key] instanceof Task))) { + tasks.push(new Task(function(done) { + o.timeout(Infinity) + subjects.push(key) + var pop = new Task(function pop() {subjects.pop(), done()}, null) + if (spec[key] instanceof Task) series([].concat(pre, spec[key], post, pop), defaultDelay) + else test(spec[key], pre, post, pop, defaultDelay) + }, null)) + } + return tasks + }, []), spec["\x01after"] || [], finalize), defaultDelay) + } + + function series(tasks, defaultDelay) { + var cursor = 0 + next() + + function next() { + if (cursor === tasks.length) return + + var task = tasks[cursor++] + var fn = task.fn + currentTestError = task.err + var timeout = 0, delay = defaultDelay, s = new Date + var current = cursor + var arg + + globalTimeout = setDelay + + var isDone = false + // public API, may only be called once from use code (or after returned Promise resolution) + function done(err) { + if (!isDone) isDone = true + else throw new Error("`" + arg + "()` should only be called once") + if (timeout === undefined) console.warn("# elapsed: " + Math.round(new Date - s) + "ms, expected under " + delay + "ms\n" + o.cleanStackTrace(task.err)) + finalizeAsync(err) + } + // for internal use only + function finalizeAsync(err) { + if (err == null) { + if (task.err != null) succeed(new Assert) + } else { + if (err instanceof Error) fail(new Assert, err.message, err) + else fail(new Assert, String(err), null) + } + if (timeout !== undefined) timeout = clearTimeout(timeout) + if (current === cursor) next() + } + function startTimer() { + timeout = setTimeout(function() { + timeout = undefined + finalizeAsync("async test timed out after " + delay + "ms") + }, Math.min(delay, 2147483647)) + } + function setDelay (t) { + if (typeof t !== "number") throw new Error("timeout() and o.timeout() expect a number as argument") + delay = t + } + if (fn.length > 0) { + var body = fn.toString() + arg = (body.match(/^(.+?)(?:\s|\/\*[\s\S]*?\*\/|\/\/.*?\n)*=>/) || body.match(/\((?:\s|\/\*[\s\S]*?\*\/|\/\/.*?\n)*(.+?)(?:\s|\/\*[\s\S]*?\*\/|\/\/.*?\n)*[,\)]/) || []).pop() + if (body.indexOf(arg) === body.lastIndexOf(arg)) { + var e = new Error + e.stack = "`" + arg + "()` should be called at least once\n" + o.cleanStackTrace(task.err) + throw e + } + try { + fn(done, setDelay) + } + catch (e) { + if (task.err != null) finalizeAsync(e) + // The errors of internal tasks (which don't have an Err) are ospec bugs and must be rethrown. + else throw e + } + if (timeout === 0) { + startTimer() + } + } else { + try{ + var p = fn() + if (p && p.then) { + startTimer() + p.then(function() { done() }, done) + } else { + nextTickish(next) + } + } catch (e) { + if (task.err != null) finalizeAsync(e) + // The errors of internal tasks (which don't have an Err) are ospec bugs and must be rethrown. + else throw e + } + } + globalTimeout = noTimeoutRightNow + } + } + } + function unique(subject) { + if (hasOwn.call(ctx, subject)) { + console.warn("A test or a spec named `" + subject + "` was already defined") + while (hasOwn.call(ctx, subject)) subject += "*" + } + return subject + } + function hook(name) { + return function(predicate) { + if (ctx[name]) throw new Error("This hook should be defined outside of a loop or inside a nested test group:\n" + predicate) + ctx[name] = new Task(predicate, ensureStackTrace(new Error)) + } + } + + define("equals", "should equal", function(a, b) {return a === b}) + define("notEquals", "should not equal", function(a, b) {return a !== b}) + define("deepEquals", "should deep equal", deepEqual) + define("notDeepEquals", "should not deep equal", function(a, b) {return !deepEqual(a, b)}) + + function isArguments(a) { + if ("callee" in a) { + for (var i in a) if (i === "callee") return false + return true + } + } + function deepEqual(a, b) { + if (a === b) return true + if (a === null ^ b === null || a === undefined ^ b === undefined) return false // eslint-disable-line no-bitwise + if (typeof a === "object" && typeof b === "object") { + var aIsArgs = isArguments(a), bIsArgs = isArguments(b) + if (a.constructor === Object && b.constructor === Object && !aIsArgs && !bIsArgs) { + for (var i in a) { + if ((!(i in b)) || !deepEqual(a[i], b[i])) return false + } + for (var i in b) { + if (!(i in a)) return false + } + return true + } + if (a.length === b.length && (a instanceof Array && b instanceof Array || aIsArgs && bIsArgs)) { + var aKeys = Object.getOwnPropertyNames(a), bKeys = Object.getOwnPropertyNames(b) + if (aKeys.length !== bKeys.length) return false + for (var i = 0; i < aKeys.length; i++) { + if (!hasOwn.call(b, aKeys[i]) || !deepEqual(a[aKeys[i]], b[aKeys[i]])) return false + } + return true + } + if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime() + if (typeof Buffer === "function" && a instanceof Buffer && b instanceof Buffer) { + for (var i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false + } + return true + } + if (a.valueOf() === b.valueOf()) return true + } + return false + } + + function isRunning() {return results != null} + function Assert(value) { + this.value = value + this.i = results.length + results.push({pass: null, context: "", message: "Incomplete assertion in the test definition starting at...", error: currentTestError, testError: currentTestError}) + } + function Task(fn, err) { + this.fn = fn + this.err = err + } + function define(name, verb, compare) { + Assert.prototype[name] = function assert(value) { + if (compare(this.value, value)) succeed(this) + else fail(this, serialize(this.value) + "\n " + verb + "\n" + serialize(value)) + var self = this + return function(message) { + if (!self.pass) self.message = message + "\n\n" + self.message + } + } + } + function succeed(assertion) { + results[assertion.i].pass = true + } + function fail(assertion, message, error) { + results[assertion.i].pass = false + results[assertion.i].context = subjects.join(" > ") + results[assertion.i].message = message + results[assertion.i].error = error != null ? error : ensureStackTrace(new Error) + } + function serialize(value) { + if (hasProcess) return require("util").inspect(value) // eslint-disable-line global-require + if (value === null || (typeof value === "object" && !(value instanceof Array)) || typeof value === "number") return String(value) + else if (typeof value === "function") return value.name || "" + try {return JSON.stringify(value)} catch (e) {return String(value)} + } + function noTimeoutRightNow() { + throw new Error("o.timeout must be called snchronously from within a test definition or a hook") + } + var colorCodes = { + red: "31m", + red2: "31;1m", + green: "32;1m" + } + function highlight(message, color) { + var code = colorCodes[color] || colorCodes.red; + return hasProcess ? (process.stdout.isTTY ? "\x1b[" + code + message + "\x1b[0m" : message) : "%c" + message + "%c " + } + function cStyle(color, bold) { + return hasProcess||!color ? "" : "color:"+color+(bold ? ";font-weight:bold" : "") + } + function ensureStackTrace(error) { + // mandatory to get a stack in IE 10 and 11 (and maybe other envs?) + if (error.stack === undefined) try { throw error } catch(e) {return e} + else return error + } + function getStackName(e, exp) { + return e.stack && exp.test(e.stack) ? e.stack.match(exp)[1] : null + } + + o.report = function (results) { + var errCount = 0 + for (var i = 0, r; r = results[i]; i++) { + if (r.pass == null) { + r.testError.stack = r.message + "\n" + o.cleanStackTrace(r.testError) + r.testError.message = r.message + throw r.testError + } + if (!r.pass) { + var stackTrace = o.cleanStackTrace(r.error) + var couldHaveABetterStackTrace = !stackTrace || timeoutStackName != null && stackTrace.indexOf(timeoutStackName) !== -1 + if (couldHaveABetterStackTrace) stackTrace = r.testError != null ? o.cleanStackTrace(r.testError) : r.error.stack || "" + console.error( + (hasProcess ? "\n" : "") + + highlight(r.context + ":", "red2") + "\n" + + highlight(r.message, "red") + + (stackTrace ? "\n" + stackTrace + "\n" : ""), + + cStyle("black", true), "", // reset to default + cStyle("red"), cStyle("black") + ) + errCount++ + } + } + var pl = results.length === 1 ? "" : "s" + var resultSummary = (errCount === 0) ? + highlight((pl ? "All " : "The ") + results.length + " assertion" + pl + " passed", "green"): + highlight(errCount + " out of " + results.length + " assertion" + pl + " failed", "red2") + var runningTime = " in " + Math.round(Date.now() - start) + "ms" + + console.log( + (hasProcess ? "––––––\n" : "") + + (name ? name + ": " : "") + resultSummary + runningTime, + cStyle((errCount === 0 ? "green" : "red"), true), "" + ) + return errCount + } + + if (hasProcess) { + nextTickish = process.nextTick + } else { + nextTickish = function fakeFastNextTick(next) { + if (stack++ < 5000) next() + else setTimeout(next, stack = 0) + } + } + + return o +}) +() \ No newline at end of file diff --git a/ospec/package.json b/ospec/package.json index fa2baf42..d6282329 100644 --- a/ospec/package.json +++ b/ospec/package.json @@ -3,6 +3,7 @@ "version": "3.0.1", "description": "Noiseless testing framework", "main": "ospec.js", + "module": "ospec.mjs", "directories": { "test": "tests" }, @@ -12,6 +13,9 @@ "bin": { "ospec": "./bin/ospec" }, + "scripts": { + "prepublishOnly": "node esm.js" + }, "repository": "MithrilJS/mithril.js", "dependencies": { "glob": "^7.1.2" diff --git a/ospec/tests/test-ospec.js b/ospec/tests/test-ospec.js index 3ab3fa99..5e92d770 100644 --- a/ospec/tests/test-ospec.js +++ b/ospec/tests/test-ospec.js @@ -89,6 +89,8 @@ o.spec("reporting", function() { o(results.length).equals(2)("Two results") o("error" in results[0] && "pass" in results[0]).equals(true)("error and pass keys present in failing result") + o("message" in results[0] && "context" in results[0]).equals(true)("message and context keys present in failing result") + o("message" in results[1] && "context" in results[1]).equals(true)("message and context keys present in passing result") o(results[0].pass).equals(false)("Test meant to fail has failed") o(results[1].pass).equals(true)("Test meant to pass has passed") @@ -163,6 +165,14 @@ o.spec("ospec", function() { o(a).notEquals(2) o({a: [1, 2], b: 3}).deepEquals({a: [1, 2], b: 3}) o([{a: 1, b: 2}, {c: 3}]).deepEquals([{a: 1, b: 2}, {c: 3}]) + o(function(){throw new Error()}).throws(Error) + o(function(){"ayy".foo()}).throws(TypeError) + o(function(){Math.PI.toFixed(Math.pow(10,20))}).throws(RangeError) + o(function(){decodeURIComponent("%")}).throws(URIError) + + o(function(){"ayy".foo()}).notThrows(SyntaxError) + o(function(){throw new Error("foo")}).throws("foo") + o(function(){throw new Error("foo")}).notThrows("bar") var undef1 = {undef: void 0} var undef2 = {UNDEF: void 0} @@ -666,7 +676,7 @@ o.spec("the done parser", function() { var threw = false oo("test", function(/*hey */ /**/ //ho - done /*hey + done /*hey */ /**/ //huuu , timeout ) { diff --git a/package-lock.json b/package-lock.json index ab8267fe..0061c2d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "mithril", - "version": "2.0.0-rc.1", + "version": "2.0.0-rc.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10,45 +10,87 @@ "integrity": "sha1-RdW5NXMXtsxVU9/ZmTGEOuCw5To=", "dev": true }, + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "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, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", "dev": true }, + "acorn": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz", + "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==", + "dev": true + }, "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", + "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "dev": true }, "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz", + "integrity": "sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==", "dev": true, "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", - "dev": true - }, "align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", @@ -84,6 +126,16 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, "app-root-path": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz", @@ -99,6 +151,24 @@ "sprintf-js": "~1.0.2" } }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -114,10 +184,16 @@ "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", "dev": true }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, "async": { @@ -129,16 +205,17 @@ "lodash": "^4.14.0" } }, - "babel-code-frame": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", - "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", - "dev": true, - "requires": { - "chalk": "^1.1.0", - "esutils": "^2.0.2", - "js-tokens": "^3.0.0" - } + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "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 }, "balanced-match": { "version": "1.0.0", @@ -146,6 +223,67 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, "benchmark": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", @@ -156,6 +294,12 @@ "platform": "^1.3.3" } }, + "binary-extensions": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", + "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", + "dev": true + }, "brace-expansion": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", @@ -166,6 +310,58 @@ "concat-map": "0.0.1" } }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", @@ -205,12 +401,62 @@ "supports-color": "^2.0.0" } }, - "circular-json": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz", - "integrity": "sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0=", + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "chokidar": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.2.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.5" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, "cli-cursor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", @@ -237,15 +483,9 @@ } }, "cli-width": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz", - "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=", - "dev": true - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, "code-point-at": { @@ -254,6 +494,16 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, "collections": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/collections/-/collections-0.2.2.tgz", @@ -263,28 +513,44 @@ "weak-map": "1.0.0" } }, + "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, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "commander": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", "dev": true }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, "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 }, - "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -327,15 +593,6 @@ "which": "^1.2.9" } }, - "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true, - "requires": { - "es5-ext": "^0.10.9" - } - }, "date-fns": { "version": "1.28.5", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.28.5.tgz", @@ -343,9 +600,9 @@ "dev": true }, "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" @@ -358,6 +615,12 @@ "dev": true, "optional": true }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, "dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -370,29 +633,60 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } } }, "doctrine": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", - "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" + "esutils": "^2.0.2" } }, "elegant-spinner": { @@ -410,76 +704,6 @@ "is-arrayish": "^0.2.1" } }, - "es5-ext": { - "version": "0.10.24", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.24.tgz", - "integrity": "sha1-pVh3yZJLwMjZvTwsvhdJWsFwmxQ=", - "dev": true, - "requires": { - "es6-iterator": "2", - "es6-symbol": "~3.1" - } - }, - "es6-iterator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz", - "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.14", - "es6-symbol": "^3.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.14", - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -499,14 +723,148 @@ "source-map": "~0.2.0" } }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "eslint": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.9.0.tgz", + "integrity": "sha512-g4KWpPdqN0nth+goDNICNXGfJF7nNnepthp46CAlJoJtC5K/cLu3NgCM3AHu1CkJ5Hzt9V0Y0PBAO6Ay/gGb+w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^4.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "imurmurhash": "^0.1.4", + "inquirer": "^6.1.0", + "is-resolvable": "^1.1.0", + "js-yaml": "^3.12.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.0.2", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "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, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "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, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "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 + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "eslint-scope": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", "dev": true, "requires": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", "esrecurse": "^4.1.0", "estraverse": "^4.1.1" }, @@ -519,73 +877,27 @@ } } }, - "eslint": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", - "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", - "dev": true, - "requires": { - "babel-code-frame": "^6.16.0", - "chalk": "^1.1.3", - "concat-stream": "^1.5.2", - "debug": "^2.1.1", - "doctrine": "^2.0.0", - "escope": "^3.6.0", - "espree": "^3.4.0", - "esquery": "^1.0.0", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "glob": "^7.0.3", - "globals": "^9.14.0", - "ignore": "^3.2.0", - "imurmurhash": "^0.1.4", - "inquirer": "^0.12.0", - "is-my-json-valid": "^2.10.0", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.5.1", - "json-stable-stringify": "^1.0.0", - "levn": "^0.3.0", - "lodash": "^4.0.0", - "mkdirp": "^0.5.0", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.1", - "pluralize": "^1.2.1", - "progress": "^1.1.8", - "require-uncached": "^1.0.2", - "shelljs": "^0.7.5", - "strip-bom": "^3.0.0", - "strip-json-comments": "~2.0.1", - "table": "^3.7.8", - "text-table": "~0.2.0", - "user-home": "^2.0.0" - }, - "dependencies": { - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - } - } + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true }, "espree": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz", - "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-4.1.0.tgz", + "integrity": "sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w==", "dev": true, "requires": { - "acorn": "^5.0.1", - "acorn-jsx": "^3.0.0" - }, - "dependencies": { - "acorn": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.1.tgz", - "integrity": "sha512-vOk6uEMctu0vQrvuSqFdJyqj1Q0S5VTDL79qtjo+DhRr+1mmaD+tluFSCZqhvi/JUhXSzoZN2BhtstaPEeE8cw==", - "dev": true - } + "acorn": "^6.0.2", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" } }, "esprima": { @@ -595,9 +907,9 @@ "dev": true }, "esquery": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", - "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -612,13 +924,12 @@ } }, "esrecurse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", - "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "^4.1.0", - "object-assign": "^4.0.1" + "estraverse": "^4.1.0" }, "dependencies": { "estraverse": { @@ -641,22 +952,162 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, "exit-hook": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", "dev": true }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", @@ -683,16 +1134,65 @@ "object-assign": "^4.0.1" } }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "flat-cache": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", - "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", "dev": true, "requires": { "circular-json": "^0.3.1", - "del": "^2.0.2", "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", "write": "^0.2.1" + }, + "dependencies": { + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" } }, "fs.realpath": { @@ -701,27 +1201,553 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "generate-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", - "dev": true - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", "dev": true, + "optional": true, "requires": { - "is-property": "^1.0.0" + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "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" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "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" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } } }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, "gh-pages": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-0.12.0.tgz", @@ -781,26 +1807,33 @@ "path-is-absolute": "^1.0.0" } }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } } }, + "globals": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", + "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", + "dev": true + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", @@ -851,10 +1884,57 @@ "ansi-regex": "^2.0.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "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, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "ignore": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz", - "integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, "imurmurhash": { @@ -889,31 +1969,148 @@ "dev": true }, "inquirer": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", - "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", + "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", "dev": true, "requires": { - "ansi-escapes": "^1.1.0", - "ansi-regex": "^2.0.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "readline2": "^1.0.1", - "run-async": "^0.1.0", - "rx-lite": "^3.1.2", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", + "external-editor": "^3.0.0", + "figures": "^2.0.0", + "lodash": "^4.17.10", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.1.0", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", "through": "^2.3.6" + }, + "dependencies": { + "ansi-escapes": { + "version": "3.1.0", + "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "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, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "interpret": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz", - "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=", - "dev": true + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } }, "is-arrayish": { "version": "0.2.1", @@ -921,12 +2118,61 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, "is-buffer": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", "dev": true }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "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 + }, "is-finite": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", @@ -945,40 +2191,31 @@ "number-is-nan": "^1.0.0" } }, - "is-my-json-valid": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz", - "integrity": "sha1-8Hndm/2uZe4gOKrorLyGqxCeNpM=", + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "generate-function": "^2.0.0", - "generate-object-property": "^1.1.0", - "jsonpointer": "^4.0.0", - "xtend": "^4.0.0" + "is-extglob": "^2.1.1" } }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", - "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "is-path-inside": "^1.0.0" + "kind-of": "^3.0.2" } }, - "is-path-inside": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", - "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "path-is-inside": "^1.0.1" + "isobject": "^3.0.1" } }, "is-promise": { @@ -987,20 +2224,11 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, "is-resolvable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", - "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", - "dev": true, - "requires": { - "tryit": "^1.0.1" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true }, "is-stream": { "version": "1.1.0", @@ -1008,6 +2236,12 @@ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", "dev": true }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1020,6 +2254,12 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, "istanbul": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", @@ -1093,9 +2333,9 @@ } }, "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, "js-yaml": { @@ -1116,25 +2356,16 @@ } } }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "~0.0.0" - } - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "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 }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "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 }, "kind-of": { @@ -1270,9 +2501,9 @@ "dev": true }, "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, "lodash.chunk": { @@ -1281,6 +2512,12 @@ "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=", "dev": true }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, "log-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", @@ -1316,12 +2553,56 @@ "yallist": "^2.1.2" } }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, "marked": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "dev": true }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, "mime": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.6.tgz", @@ -1334,6 +2615,12 @@ "integrity": "sha1-2vsCdSNw/SJgk64xUsJxrwGsJUo=", "dev": true }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1349,6 +2636,27 @@ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -1365,17 +2673,57 @@ "dev": true }, "mute-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, + "nan": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, "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 }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", @@ -1385,6 +2733,15 @@ "abbrev": "1" } }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, "npm-path": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.3.tgz", @@ -1426,6 +2783,46 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1491,6 +2888,12 @@ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -1512,6 +2915,18 @@ "error-ex": "^1.2.0" } }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1530,12 +2945,6 @@ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true - }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -1570,9 +2979,15 @@ "dev": true }, "pluralize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", - "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", "dev": true }, "prelude-ls": { @@ -1588,9 +3003,9 @@ "dev": true }, "progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz", + "integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==", "dev": true }, "pseudomap": { @@ -1599,6 +3014,12 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, + "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 + }, "q": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", @@ -1642,26 +3063,45 @@ "util-deprecate": "~1.0.1" } }, - "readline2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", - "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "mute-stream": "0.0.5" + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" } }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { - "resolve": "^1.1.6" + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" } }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -1693,21 +3133,18 @@ "resolve-from": "^1.0.0" } }, - "resolve": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz", - "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=", - "dev": true, - "requires": { - "path-parse": "^1.0.5" - } - }, "resolve-from": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", "dev": true }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, "restore-cursor": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", @@ -1718,6 +3155,12 @@ "onetime": "^1.0.0" } }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", @@ -1738,20 +3181,14 @@ } }, "run-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", "dev": true, "requires": { - "once": "^1.3.0" + "is-promise": "^2.1.0" } }, - "rx-lite": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", - "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", - "dev": true - }, "rxjs": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.2.tgz", @@ -1767,6 +3204,50 @@ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true }, + "safe-regex": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -1782,17 +3263,6 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, - "shelljs": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", - "dev": true, - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -1805,6 +3275,114 @@ "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", "dev": true }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + } + }, "source-map": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", @@ -1815,6 +3393,52 @@ "amdefine": ">=0.0.4" } }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", + "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "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 + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -1827,6 +3451,27 @@ "integrity": "sha1-15fhtVHKemOd7AI33G60u5vhfTU=", "dev": true }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, "stream-to-observable": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.1.0.tgz", @@ -1862,12 +3507,6 @@ "ansi-regex": "^2.0.0" } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", @@ -1893,17 +3532,15 @@ "dev": true }, "table": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/table/-/table-5.1.0.tgz", + "integrity": "sha512-e542in22ZLhD/fOIuXs/8yDZ9W61ltF8daM88rkRNtgTIct+vI2fTnAyu/Db2TCfEcI8i7mjZz6meLq0nW7TYg==", "dev": true, "requires": { - "ajv": "^4.7.0", - "ajv-keywords": "^1.0.0", - "chalk": "^1.1.1", - "lodash": "^4.0.0", - "slice-ansi": "0.0.4", - "string-width": "^2.0.0" + "ajv": "^6.5.3", + "lodash": "^4.17.10", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" }, "dependencies": { "ansi-regex": { @@ -1918,6 +3555,15 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -1939,6 +3585,31 @@ } } }, + "terser": { + "version": "3.10.11", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.10.11.tgz", + "integrity": "sha512-iruZ7j14oBbRYJC5cP0/vTU7YOWjN+J1ZskEGoF78tFzXdkK2hbCL/3TRZN8XB+MuvFhvOHMp7WkOCBO4VEL5g==", + "dev": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1", + "source-map-support": "~0.5.6" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "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 + } + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -1951,10 +3622,50 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, - "tryit": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", - "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", "dev": true }, "type-check": { @@ -1966,36 +3677,6 @@ "prelude-ls": "~1.1.2" } }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "uglify-es": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", - "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", - "dev": true, - "requires": { - "commander": "~2.13.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", - "dev": true - }, - "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 - } - } - }, "uglify-js": { "version": "2.8.29", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", @@ -2070,20 +3751,113 @@ "dev": true, "optional": true }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, "url2": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/url2/-/url2-0.0.0.tgz", "integrity": "sha1-Tqq9HVw6yQ1iq0SFyZhCKGWgSxo=", "dev": true }, - "user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true }, "util-deprecate": { "version": "1.0.2", @@ -2127,12 +3901,6 @@ "mkdirp": "^0.5.1" } }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", diff --git a/package.json b/package.json index 7ed8170d..fdcfcd21 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,18 @@ { "name": "mithril", - "version": "2.0.0-rc.1", + "version": "2.0.0-rc.2", "description": "A framework for building brilliant applications", "author": "Leo Horie", "license": "MIT", "main": "mithril.js", + "module": "mithril.mjs", "repository": "MithrilJS/mithril.js", "scripts": { "dev": "node bundler/cli browser.js -output mithril.js -watch", - "build": "npm run build-browser & npm run build-min", + "build": "npm run build-browser & npm run build-min && npm run build-esm", "build-browser": "node bundler/cli browser.js -output mithril.js", "build-min": "node bundler/cli browser.js -output mithril.min.js -minify", + "build-esm": "node esm.js", "precommit": "lint-staged", "lintdocs": "node docs/lint", "gendocs": "node docs/generate", @@ -22,14 +24,15 @@ "cover": "istanbul cover --print both ospec/bin/ospec", "release": "npm version -m 'v%s'", "preversion": "npm run test", - "version": "npm run build && git add mithril.js mithril.min.js", + "version": "npm run build && git add mithril.js mithril.min.js mithril.mjs mithril.min.mjs", "postversion": "git push --follow-tags" }, "devDependencies": { "@alrra/travis-scripts": "^3.0.1", "benchmark": "^2.1.4", + "chokidar": "^2.0.4", "dedent": "^0.7.0", - "eslint": "^3.19.0", + "eslint": "^5.9.0", "gh-pages": "^0.12.0", "glob": "^7.1.2", "istanbul": "^0.4.5", @@ -37,7 +40,7 @@ "locater": "^1.3.0", "marked": "^0.3.19", "pinpoint": "^1.1.0", - "uglify-es": "^3.3.9" + "terser": "^3.10.11" }, "bin": { "ospec": "./ospec/bin/ospec" diff --git a/render/fragment.js b/render/fragment.js index 82918975..90cad40d 100644 --- a/render/fragment.js +++ b/render/fragment.js @@ -1,7 +1,12 @@ "use strict" var Vnode = require("../render/vnode") +var hyperscriptVnode = require("./hyperscriptVnode") -module.exports = function(attrs, children) { - return Vnode("[", attrs.key, attrs, Vnode.normalizeChildren(children), undefined, undefined) +module.exports = function() { + var vnode = hyperscriptVnode.apply(0, arguments) + + vnode.tag = "[" + vnode.children = Vnode.normalizeChildren(vnode.children) + return vnode } diff --git a/render/hyperscript.js b/render/hyperscript.js index 36eabbc5..c6205410 100644 --- a/render/hyperscript.js +++ b/render/hyperscript.js @@ -1,6 +1,7 @@ "use strict" var Vnode = require("../render/vnode") +var hyperscriptVnode = require("./hyperscriptVnode") var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g var selectorCache = {} @@ -29,18 +30,21 @@ function compileSelector(selector) { return selectorCache[selector] = {tag: tag, attrs: attrs} } -function execSelector(state, attrs, children) { - var hasAttrs = false, childList, text - var classAttr = hasOwn.call(attrs, "class") ? "class" : "className" - var className = attrs[classAttr] +function execSelector(state, vnode) { + var attrs = vnode.attrs + var children = Vnode.normalizeChildren(vnode.children) + var hasClass = hasOwn.call(attrs, "class") + var className = hasClass ? attrs.class : attrs.className + + vnode.tag = state.tag + vnode.attrs = null + vnode.children = undefined if (!isEmpty(state.attrs) && !isEmpty(attrs)) { var newAttrs = {} - for(var key in attrs) { - if (hasOwn.call(attrs, key)) { - newAttrs[key] = attrs[key] - } + for (var key in attrs) { + if (hasOwn.call(attrs, key)) newAttrs[key] = attrs[key] } attrs = newAttrs @@ -54,28 +58,28 @@ function execSelector(state, attrs, children) { if (className != null || state.attrs.className != null) attrs.className = className != null ? state.attrs.className != null - ? state.attrs.className + " " + className + ? String(state.attrs.className) + " " + String(className) : className : state.attrs.className != null ? state.attrs.className : null - if (classAttr === "class") attrs.class = null + if (hasClass) attrs.class = null for (var key in attrs) { if (hasOwn.call(attrs, key) && key !== "key") { - hasAttrs = true + vnode.attrs = attrs break } } if (Array.isArray(children) && children.length === 1 && children[0] != null && children[0].tag === "#") { - text = children[0].children + vnode.text = children[0].children } else { - childList = children + vnode.children = children } - return Vnode(state.tag, attrs.key, hasAttrs ? attrs : null, childList, text) + return vnode } function hyperscript(selector) { @@ -83,27 +87,13 @@ function hyperscript(selector) { throw Error("The selector must be either a string or a component."); } - var attrs = arguments[1], start = 2, children - - if (attrs == null) { - attrs = {} - } else if (typeof attrs !== "object" || attrs.tag != null || Array.isArray(attrs)) { - attrs = {} - start = 1 - } - - if (arguments.length === start + 1) { - children = arguments[start] - if (!Array.isArray(children)) children = [children] - } else { - children = [] - while (start < arguments.length) children.push(arguments[start++]) - } + var vnode = hyperscriptVnode.apply(1, arguments) if (typeof selector === "string") { - return execSelector(selectorCache[selector] || compileSelector(selector), attrs, Vnode.normalizeChildren(children)) + return execSelector(selectorCache[selector] || compileSelector(selector), vnode) } else { - return Vnode(selector, attrs.key, attrs, children) + vnode.tag = selector + return vnode } } diff --git a/render/hyperscriptVnode.js b/render/hyperscriptVnode.js new file mode 100644 index 00000000..37e92b35 --- /dev/null +++ b/render/hyperscriptVnode.js @@ -0,0 +1,53 @@ +"use strict" + +var Vnode = require("../render/vnode") + +// Call via `hyperscriptVnode.apply(startOffset, arguments)` +// +// The reason I do it this way, forwarding the arguments and passing the start +// offset in `this`, is so I don't have to create a temporary array in a +// performance-critical path. +// +// In native ES6, I'd instead add a final `...args` parameter to the +// `hyperscript` and `fragment` factories and define this as +// `hyperscriptVnode(...args)`, since modern engines do optimize that away. But +// ES5 (what Mithril requires thanks to IE support) doesn't give me that luxury, +// and engines aren't nearly intelligent enough to do either of these: +// +// 1. Elide the allocation for `[].slice.call(arguments, 1)` when it's passed to +// another function only to be indexed. +// 2. Elide an `arguments` allocation when it's passed to any function other +// than `Function.prototype.apply` or `Reflect.apply`. +// +// In ES6, it'd probably look closer to this (I'd need to profile it, though): +// module.exports = function(attrs, ...children) { +// if (attrs == null || typeof attrs === "object" && attrs.tag == null && !Array.isArray(attrs)) { +// if (children.length === 1 && Array.isArray(children[0])) children = children[0] +// } else { +// children = children.length === 0 && Array.isArray(attrs) ? attrs : [attrs, ...children] +// attrs = undefined +// } +// +// if (attrs == null) attrs = {} +// return Vnode("", attrs.key, attrs, children) +// } +module.exports = function() { + var attrs = arguments[this], start = this + 1, children + + if (attrs == null) { + attrs = {} + } else if (typeof attrs !== "object" || attrs.tag != null || Array.isArray(attrs)) { + attrs = {} + start = this + } + + if (arguments.length === start + 1) { + children = arguments[start] + if (!Array.isArray(children)) children = [children] + } else { + children = [] + while (start < arguments.length) children.push(arguments[start++]) + } + + return Vnode("", attrs.key, attrs, children) +} diff --git a/render/render.js b/render/render.js index d4598a24..f364d731 100644 --- a/render/render.js +++ b/render/render.js @@ -10,8 +10,8 @@ module.exports = function($window) { math: "http://www.w3.org/1998/Math/MathML" } - var onevent - function setEventCallback(callback) {return onevent = callback} + var redraw + function setRedraw(callback) {return redraw = callback} function getNameSpace(vnode) { return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag] @@ -35,6 +35,15 @@ module.exports = function($window) { } } + // IE11 (at least) throws an UnspecifiedError when accessing document.activeElement when + // inside an iframe. Catch and swallow this error, and heavy-handidly return null. + function activeElement() { + try { + return $doc.activeElement + } catch (e) { + return null + } + } //create function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) { for (var i = start; i < end; i++) { @@ -143,8 +152,8 @@ module.exports = function($window) { sentinel.$$reentrantLock$$ = true vnode.state = (vnode.tag.prototype != null && typeof vnode.tag.prototype.view === "function") ? new vnode.tag(vnode) : vnode.tag(vnode) } - if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks) initLifecycle(vnode.state, vnode, hooks) + if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks) vnode.instance = Vnode.normalize(callHook.call(vnode.state.view, vnode)) if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument") sentinel.$$reentrantLock$$ = null @@ -246,7 +255,7 @@ module.exports = function($window) { // When the list is being traversed top-down, at any index, the DOM nodes up to the previous // vnode reflect the content of the new list, whereas the rest of the DOM nodes reflect the old // list. The next sibling must be looked for in the old list using `getNextSibling(... oldStart + 1 ...)`. - // + // // In the other scenarios (swaps, upwards traversal, map-based diff), // the new vnodes list is traversed upwards. The DOM nodes at the bottom of the list reflect the // bottom part of the new vnodes list, and we can use the `v.dom` value of the previous node @@ -502,8 +511,8 @@ module.exports = function($window) { function updateComponent(parent, old, vnode, hooks, nextSibling, ns) { vnode.instance = Vnode.normalize(callHook.call(vnode.state.view, vnode)) if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument") - if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks) updateLifecycle(vnode.state, vnode, hooks) + if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks) if (vnode.instance != null) { if (old.instance == null) createNode(parent, vnode.instance, hooks, ns, nextSibling) else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, ns) @@ -623,15 +632,15 @@ module.exports = function($window) { function removeNode(vnode) { var expected = 1, called = 0 var original = vnode.state - if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") { - var result = callHook.call(vnode.attrs.onbeforeremove, vnode) + if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeremove === "function") { + var result = callHook.call(vnode.state.onbeforeremove, vnode) if (result != null && typeof result.then === "function") { expected++ result.then(continuation, continuation) } } - if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeremove === "function") { - var result = callHook.call(vnode.state.onbeforeremove, vnode) + if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") { + var result = callHook.call(vnode.attrs.onbeforeremove, vnode) if (result != null && typeof result.then === "function") { expected++ result.then(continuation, continuation) @@ -652,9 +661,9 @@ module.exports = function($window) { } } function onremove(vnode) { + if (typeof vnode.tag !== "string" && typeof vnode.state.onremove === "function") callHook.call(vnode.state.onremove, vnode) if (vnode.attrs && typeof vnode.attrs.onremove === "function") callHook.call(vnode.attrs.onremove, vnode) if (typeof vnode.tag !== "string") { - if (typeof vnode.state.onremove === "function") callHook.call(vnode.state.onremove, vnode) if (vnode.instance != null) onremove(vnode.instance) } else { var children = vnode.children @@ -683,7 +692,7 @@ module.exports = function($window) { // Only do the coercion if we're actually going to check the value. /* eslint-disable no-implicit-coercion */ //setting input[value] to same value by typing on focused element moves cursor to end in Chrome - if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === "" + value && vnode.dom === $doc.activeElement) return + if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === "" + value && vnode.dom === activeElement()) return //setting select[value] to same value while having select open blinks select dropdown in Chrome if (vnode.tag === "select" && old !== null && vnode.dom.value === "" + value) return //setting option[value] to same value while having select open blinks select dropdown in Chrome @@ -708,7 +717,10 @@ module.exports = function($window) { else if ( hasPropertyKey(vnode, key, ns) && key !== "className" - && !(vnode.tag === "option" && key === "value") + && !(key === "value" && ( + vnode.tag === "option" + || vnode.tag === "select" && vnode.dom.selectedIndex === -1 && vnode.dom === activeElement() + )) && !(vnode.tag === "input" && key === "type") ) { vnode.dom[key] = null @@ -747,7 +759,7 @@ module.exports = function($window) { } } function isFormAttribute(vnode, attr) { - return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode.dom === $doc.activeElement || vnode.tag === "option" && vnode.dom.parentNode === $doc.activeElement + return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode.dom === activeElement() || vnode.tag === "option" && vnode.dom.parentNode === $doc.activeElement } function isLifecycleMethod(attr) { return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate" @@ -764,26 +776,44 @@ module.exports = function($window) { } //style + var uppercaseRegex = /[A-Z]/g + function toLowerCase(capital) { return "-" + capital.toLowerCase() } + function normalizeKey(key) { + return key[0] === "-" && key[1] === "-" ? key : + key === "cssFloat" ? "float" : + key.replace(uppercaseRegex, toLowerCase) + } function updateStyle(element, old, style) { - if (old != null && style != null && typeof old === "object" && typeof style === "object" && style !== old) { + if (old === style) { + // Styles are equivalent, do nothing. + } else if (style == null) { + // New style is missing, just clear it. + element.style.cssText = "" + } else if (typeof style !== "object") { + // New style is a string, let engine deal with patching. + element.style.cssText = style + } else if (old == null || typeof old !== "object") { + // `old` is missing or a string, `style` is an object. + element.style.cssText = "" + // Add new style properties + for (var key in style) { + var value = style[key] + if (value != null) element.style.setProperty(normalizeKey(key), String(value)) + } + } else { // Both old & new are (different) objects. // Update style properties that have changed for (var key in style) { - if (style[key] !== old[key]) element.style[key] = style[key] + var value = style[key] + if (value != null && (value = String(value)) !== String(old[key])) { + element.style.setProperty(normalizeKey(key), value) + } } // Remove style properties that no longer exist for (var key in old) { - if (!(key in style)) element.style[key] = "" - } - return - } - if (old === style) element.style.cssText = "", old = null - if (style == null) element.style.cssText = "" - else if (typeof style === "string") element.style.cssText = style - else { - if (typeof old === "string") element.style.cssText = "" - for (var key in style) { - element.style[key] = style[key] + if (old[key] != null && style[key] == null) { + element.style.removeProperty(normalizeKey(key)) + } } } } @@ -804,9 +834,10 @@ module.exports = function($window) { EventDict.prototype.handleEvent = function (ev) { var handler = this["on" + ev.type] var result - if (typeof handler === "function") result = handler.call(ev.target, ev) + if (typeof handler === "function") result = handler.call(ev.currentTarget, ev) else if (typeof handler.handleEvent === "function") handler.handleEvent(ev) - if (typeof onevent === "function") onevent.call(ev.target, ev) + if (ev.redraw === false) ev.redraw = undefined + else if (typeof redraw === "function") redraw() if (result === false) { ev.preventDefault() ev.stopPropagation() @@ -840,26 +871,27 @@ module.exports = function($window) { if (typeof source.onupdate === "function") hooks.push(callHook.bind(source.onupdate, vnode)) } function shouldNotUpdate(vnode, old) { - var forceVnodeUpdate, forceComponentUpdate - if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") { - forceVnodeUpdate = callHook.call(vnode.attrs.onbeforeupdate, vnode, old) - } - if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeupdate === "function") { - forceComponentUpdate = callHook.call(vnode.state.onbeforeupdate, vnode, old) - } - if (!(forceVnodeUpdate === undefined && forceComponentUpdate === undefined) && !forceVnodeUpdate && !forceComponentUpdate) { - vnode.dom = old.dom - vnode.domSize = old.domSize - vnode.instance = old.instance - return true - } - return false + do { + if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") { + var force = callHook.call(vnode.attrs.onbeforeupdate, vnode, old) + if (force !== undefined && !force) break + } + if (typeof vnode.tag !== "string" && typeof vnode.state.onbeforeupdate === "function") { + var force = callHook.call(vnode.state.onbeforeupdate, vnode, old) + if (force !== undefined && !force) break + } + return false + } while (false); // eslint-disable-line no-constant-condition + vnode.dom = old.dom + vnode.domSize = old.domSize + vnode.instance = old.instance + return true } function render(dom, vnodes) { if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.") var hooks = [] - var active = $doc.activeElement + var active = activeElement() var namespace = dom.namespaceURI // First time rendering into a node clears it out @@ -868,10 +900,10 @@ module.exports = function($window) { vnodes = Vnode.normalizeChildren(Array.isArray(vnodes) ? vnodes : [vnodes]) updateNodes(dom, dom.vnodes, vnodes, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace) dom.vnodes = vnodes - // document.activeElement can return null in IE https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement - if (active != null && $doc.activeElement !== active && typeof active.focus === "function") active.focus() + // `document.activeElement` can return null: https://html.spec.whatwg.org/multipage/interaction.html#dom-document-activeelement + if (active != null && activeElement() !== active && typeof active.focus === "function") active.focus() for (var i = 0; i < hooks.length; i++) hooks[i]() } - return {render: render, setEventCallback: setEventCallback} + return {render: render, setRedraw: setRedraw} } diff --git a/render/tests/manual/iframe.html b/render/tests/manual/iframe.html new file mode 100644 index 00000000..01cc505d --- /dev/null +++ b/render/tests/manual/iframe.html @@ -0,0 +1,24 @@ + + + + + + +
+ + + + diff --git a/render/tests/manual/index.html b/render/tests/manual/index.html new file mode 100644 index 00000000..c11b4190 --- /dev/null +++ b/render/tests/manual/index.html @@ -0,0 +1,9 @@ + + + Various parent website content. + There should be a clickable button below, which is inside an iframe containing a mithril app: +
+ +
+ + diff --git a/render/tests/test-component.js b/render/tests/test-component.js index dd27ef10..29c04a44 100644 --- a/render/tests/test-component.js +++ b/render/tests/test-component.js @@ -699,13 +699,23 @@ o.spec("component", function() { "onupdate", "onbeforeremove", "onremove" ] hooks.forEach(function(hook) { - // the `attrs` hooks are called before the component ones - attrs[hook] = o.spy(function() { - o(attrs[hook].callCount).equals(methods[hook].callCount + 1) - }) - methods[hook] = o.spy(function() { - o(attrs[hook].callCount).equals(methods[hook].callCount) - }) + if (hook === "onbeforeupdate") { + // the component's `onbeforeupdate` is called after the `attrs`' one + attrs[hook] = o.spy(function() { + o(attrs[hook].callCount).equals(methods[hook].callCount + 1)(hook) + }) + methods[hook] = o.spy(function() { + o(attrs[hook].callCount).equals(methods[hook].callCount)(hook) + }) + } else { + // the other component hooks are called before the `attrs` ones + methods[hook] = o.spy(function() { + o(attrs[hook].callCount).equals(methods[hook].callCount - 1)(hook) + }) + attrs[hook] = o.spy(function() { + o(attrs[hook].callCount).equals(methods[hook].callCount)(hook) + }) + } }) var component = createComponent(methods) diff --git a/render/tests/test-createElement.js b/render/tests/test-createElement.js index ea4d01c8..54119c68 100644 --- a/render/tests/test-createElement.js +++ b/render/tests/test-createElement.js @@ -33,6 +33,25 @@ o.spec("createElement", function() { o(vnode.dom.nodeName).equals("DIV") o(vnode.dom.style.backgroundColor).equals("red") }) + o("allows css vars in style", function() { + var vnode = {tag: "div", attrs: {style: {"--css-var": "red"}}} + render(root, [vnode]) + + o(vnode.dom.style["--css-var"]).equals("red") + }) + o("allows css vars in style with uppercase letters", function() { + var vnode = {tag: "div", attrs: {style: {"--cssVar": "red"}}} + render(root, [vnode]) + + o(vnode.dom.style["--cssVar"]).equals("red") + }) + o("censors cssFloat to float", function() { + var vnode = {tag: "a", attrs: {style: {cssFloat: "left"}}} + + render(root, [vnode]) + + o(vnode.dom.style.float).equals("left") + }) o("creates children", function() { var vnode = {tag: "div", children: [{tag: "a"}, {tag: "b"}]} render(root, [vnode]) diff --git a/render/tests/test-event.js b/render/tests/test-event.js index 02246e18..74dcb75b 100644 --- a/render/tests/test-event.js +++ b/render/tests/test-event.js @@ -5,13 +5,13 @@ var domMock = require("../../test-utils/domMock") var vdom = require("../../render/render") o.spec("event", function() { - var $window, root, onevent, render + var $window, root, redraw, render o.beforeEach(function() { $window = domMock() root = $window.document.body - onevent = o.spy() + redraw = o.spy() var renderer = vdom($window) - renderer.setEventCallback(onevent) + renderer.setRedraw(redraw) render = renderer.render }) @@ -28,10 +28,9 @@ o.spec("event", function() { o(spy.this).equals(div.dom) o(spy.args[0].type).equals("click") o(spy.args[0].target).equals(div.dom) - o(onevent.callCount).equals(1) - o(onevent.this).equals(div.dom) - o(onevent.args[0].type).equals("click") - o(onevent.args[0].target).equals(div.dom) + o(redraw.callCount).equals(1) + o(redraw.this).equals(undefined) + o(redraw.args.length).equals(0) o(e.$defaultPrevented).equals(false) o(e.$propagationStopped).equals(false) }) @@ -49,10 +48,9 @@ o.spec("event", function() { o(spy.this).equals(div.dom) o(spy.args[0].type).equals("click") o(spy.args[0].target).equals(div.dom) - o(onevent.callCount).equals(1) - o(onevent.this).equals(div.dom) - o(onevent.args[0].type).equals("click") - o(onevent.args[0].target).equals(div.dom) + o(redraw.callCount).equals(1) + o(redraw.this).equals(undefined) + o(redraw.args.length).equals(0) o(e.$defaultPrevented).equals(true) o(e.$propagationStopped).equals(true) }) @@ -71,10 +69,9 @@ o.spec("event", function() { o(spy.this).equals(listener) o(spy.args[0].type).equals("click") o(spy.args[0].target).equals(div.dom) - o(onevent.callCount).equals(1) - o(onevent.this).equals(div.dom) - o(onevent.args[0].type).equals("click") - o(onevent.args[0].target).equals(div.dom) + o(redraw.callCount).equals(1) + o(redraw.this).equals(undefined) + o(redraw.args.length).equals(0) o(e.$defaultPrevented).equals(false) o(e.$propagationStopped).equals(false) }) @@ -93,10 +90,30 @@ o.spec("event", function() { o(spy.this).equals(listener) o(spy.args[0].type).equals("click") o(spy.args[0].target).equals(div.dom) - o(onevent.callCount).equals(1) - o(onevent.this).equals(div.dom) - o(onevent.args[0].type).equals("click") - o(onevent.args[0].target).equals(div.dom) + o(redraw.callCount).equals(1) + o(redraw.this).equals(undefined) + o(redraw.args.length).equals(0) + o(e.$defaultPrevented).equals(false) + o(e.$propagationStopped).equals(false) + }) + + o("handles propagated onclick", function() { + var spy = o.spy() + var child = {tag: "div"} + var parent = {tag: "div", attrs: {onclick: spy}, children: [child]} + var e = $window.document.createEvent("MouseEvents") + e.initEvent("click", true, true) + + render(root, [parent]) + child.dom.dispatchEvent(e) + + o(spy.callCount).equals(1) + o(spy.this).equals(parent.dom) + o(spy.args[0].type).equals("click") + o(spy.args[0].target).equals(child.dom) + o(redraw.callCount).equals(1) + o(redraw.this).equals(undefined) + o(redraw.args.length).equals(0) o(e.$defaultPrevented).equals(false) o(e.$propagationStopped).equals(false) }) @@ -254,10 +271,9 @@ o.spec("event", function() { o(spy.this).equals(div.dom) o(spy.args[0].type).equals("click") o(spy.args[0].target).equals(div.dom) - o(onevent.callCount).equals(1) - o(onevent.this).equals(div.dom) - o(onevent.args[0].type).equals("click") - o(onevent.args[0].target).equals(div.dom) + o(redraw.callCount).equals(1) + o(redraw.this).equals(undefined) + o(redraw.args.length).equals(0) o(div.dom).equals(updated.dom) o(div.dom.attributes["id"].value).equals("b") }) @@ -278,10 +294,9 @@ o.spec("event", function() { o(spy.this).equals(listener) o(spy.args[0].type).equals("click") o(spy.args[0].target).equals(div.dom) - o(onevent.callCount).equals(1) - o(onevent.this).equals(div.dom) - o(onevent.args[0].type).equals("click") - o(onevent.args[0].target).equals(div.dom) + o(redraw.callCount).equals(1) + o(redraw.this).equals(undefined) + o(redraw.args.length).equals(0) o(div.dom).equals(updated.dom) o(div.dom.attributes["id"].value).equals("b") }) @@ -299,10 +314,9 @@ o.spec("event", function() { o(spy.this).equals(div.dom) o(spy.args[0].type).equals("transitionend") o(spy.args[0].target).equals(div.dom) - o(onevent.callCount).equals(1) - o(onevent.this).equals(div.dom) - o(onevent.args[0].type).equals("transitionend") - o(onevent.args[0].target).equals(div.dom) + o(redraw.callCount).equals(1) + o(redraw.this).equals(undefined) + o(redraw.args.length).equals(0) }) o("handles transitionend EventListener object", function() { @@ -319,9 +333,8 @@ o.spec("event", function() { o(spy.this).equals(listener) o(spy.args[0].type).equals("transitionend") o(spy.args[0].target).equals(div.dom) - o(onevent.callCount).equals(1) - o(onevent.this).equals(div.dom) - o(onevent.args[0].type).equals("transitionend") - o(onevent.args[0].target).equals(div.dom) + o(redraw.callCount).equals(1) + o(redraw.this).equals(undefined) + o(redraw.args.length).equals(0) }) }) diff --git a/render/tests/test-fragment.js b/render/tests/test-fragment.js index b10fc850..2883cd44 100644 --- a/render/tests/test-fragment.js +++ b/render/tests/test-fragment.js @@ -32,4 +32,166 @@ o.spec("fragment", function() { o(frag.key).equals(7) }) + o.spec("children with no attrs", function() { + o("handles string single child", function() { + var vnode = fragment(["a"]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals("a") + }) + o("handles falsy string single child", function() { + var vnode = fragment([""]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals("") + }) + o("handles number single child", function() { + var vnode = fragment([1]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals(1) + }) + o("handles falsy number single child", function() { + var vnode = fragment([0]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals(0) + }) + o("handles boolean single child", function() { + var vnode = fragment([true]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals(true) + }) + o("handles falsy boolean single child", function() { + var vnode = fragment([false]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals("") + }) + o("handles null single child", function() { + var vnode = fragment([null]) + + o(vnode.children[0]).equals(null) + }) + o("handles undefined single child", function() { + var vnode = fragment([undefined]) + + o(vnode.children[0]).equals(undefined) + }) + o("handles multiple string children", function() { + var vnode = fragment(["", "a"]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals("") + o(vnode.children[1].tag).equals("#") + o(vnode.children[1].children).equals("a") + }) + o("handles multiple number children", function() { + var vnode = fragment([0, 1]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals(0) + o(vnode.children[1].tag).equals("#") + o(vnode.children[1].children).equals(1) + }) + o("handles multiple boolean children", function() { + var vnode = fragment([false, true]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals("") + o(vnode.children[1].tag).equals("#") + o(vnode.children[1].children).equals(true) + }) + o("handles multiple null/undefined child", function() { + var vnode = fragment([null, undefined]) + + o(vnode.children[0]).equals(null) + o(vnode.children[1]).equals(undefined) + }) + o("handles falsy number single child without attrs", function() { + var vnode = fragment(0) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals(0) + }) + }) + o.spec("children with attrs", function() { + o("handles string single child", function() { + var vnode = fragment({}, ["a"]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals("a") + }) + o("handles falsy string single child", function() { + var vnode = fragment({}, [""]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals("") + }) + o("handles number single child", function() { + var vnode = fragment({}, [1]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals(1) + }) + o("handles falsy number single child", function() { + var vnode = fragment({}, [0]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals(0) + }) + o("handles boolean single child", function() { + var vnode = fragment({}, [true]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals(true) + }) + o("handles falsy boolean single child", function() { + var vnode = fragment({}, [false]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals("") + }) + o("handles null single child", function() { + var vnode = fragment({}, [null]) + + o(vnode.children[0]).equals(null) + }) + o("handles undefined single child", function() { + var vnode = fragment({}, [undefined]) + + o(vnode.children[0]).equals(undefined) + }) + o("handles multiple string children", function() { + var vnode = fragment({}, ["", "a"]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals("") + o(vnode.children[1].tag).equals("#") + o(vnode.children[1].children).equals("a") + }) + o("handles multiple number children", function() { + var vnode = fragment({}, [0, 1]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals(0) + o(vnode.children[1].tag).equals("#") + o(vnode.children[1].children).equals(1) + }) + o("handles multiple boolean children", function() { + var vnode = fragment({}, [false, true]) + + o(vnode.children[0].tag).equals("#") + o(vnode.children[0].children).equals("") + o(vnode.children[1].tag).equals("#") + o(vnode.children[1].children).equals(true) + }) + o("handles multiple null/undefined child", function() { + var vnode = fragment({}, [null, undefined]) + + o(vnode.children[0]).equals(null) + o(vnode.children[1]).equals(undefined) + }) + }) }) diff --git a/render/tests/test-hyperscript.js b/render/tests/test-hyperscript.js index bb4a0f62..c2b6c98d 100644 --- a/render/tests/test-hyperscript.js +++ b/render/tests/test-hyperscript.js @@ -358,6 +358,15 @@ o.spec("hyperscript", function() { o(vnode.attrs.className).equals("a") }) + o("casts className using toString like browsers", function() { + const className = { + valueOf: () => ".valueOf", + toString: () => "toString" + } + var vnode = m("custom-element" + className, {className: className}) + + o(vnode.attrs.className).equals("valueOf toString") + }) }) o.spec("children", function() { o("handles string single child", function() { diff --git a/render/tests/test-onbeforeupdate.js b/render/tests/test-onbeforeupdate.js index d0b1ecab..9ff828b6 100644 --- a/render/tests/test-onbeforeupdate.js +++ b/render/tests/test-onbeforeupdate.js @@ -186,7 +186,7 @@ o.spec("onbeforeupdate", function() { o(root.firstChild.attributes["id"].value).equals("b") }) - o("does not prevent update if returning false in component but true in vnode", function() { + o("prevents update if returning false in component but true in vnode", function() { var component = createComponent({ onbeforeupdate: function() {return false}, view: function(vnode) { @@ -199,10 +199,10 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].value).equals("b") + o(root.firstChild.attributes["id"].value).equals("a") }) - o("does not prevent update if returning true in component but false in vnode", function() { + o("prevents update if returning true in component but false in vnode", function() { var component = createComponent({ onbeforeupdate: function() {return true}, view: function(vnode) { @@ -215,7 +215,7 @@ o.spec("onbeforeupdate", function() { render(root, [vnode]) render(root, [updated]) - o(root.firstChild.attributes["id"].value).equals("b") + o(root.firstChild.attributes["id"].value).equals("a") }) o("does not prevent update if returning true from component", function() { diff --git a/render/tests/test-updateElement.js b/render/tests/test-updateElement.js index 8a190a4c..c1eb573f 100644 --- a/render/tests/test-updateElement.js +++ b/render/tests/test-updateElement.js @@ -179,19 +179,6 @@ o.spec("updateElement", function() { o(updated.dom.style.backgroundColor).equals("red") o(updated.dom.style.border).equals("") }) - o("updates style when it's same object but mutated", function() { - var style = {backgroundColor: "red", color: "gold"} - var vnode = {tag: "a", attrs: {style: style}} - - render(root, [vnode]) - - delete style.backgroundColor - var updated = {tag: "a", attrs: {style: style}} - render(root, [updated]) - - o(updated.dom.style.backgroundColor).equals("") - o(updated.dom.style.color).equals("gold") - }) o("does not re-render element styles for equivalent style objects", function() { var style = {color: "gold"} var vnode = {tag: "a", attrs: {style: style}} diff --git a/request/request.js b/request/request.js index d40f8d06..d5980332 100644 --- a/request/request.js +++ b/request/request.js @@ -2,85 +2,108 @@ var buildQueryString = require("../querystring/build") -var FILE_PROTOCOL_REGEX = new RegExp("^file://", "i") - module.exports = function($window, Promise) { var callbackCount = 0 - var oncompletion - function setCompletionCallback(callback) {oncompletion = callback} - function finalizer() { - var count = 0 - function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()} - - return function finalize(promise) { - var then = promise.then - promise.then = function() { - count++ - var next = then.apply(promise, arguments) - next.then(complete, function(e) { - complete() - if (count === 0) throw e - }) - return finalize(next) + function makeRequest(factory) { + return function(url, args) { + if (typeof url !== "string") { args = url; url = url.url } + else if (args == null) args = {} + var promise = new Promise(function(resolve, reject) { + factory(url, args, function (data) { + if (typeof args.type === "function") { + if (Array.isArray(data)) { + for (var i = 0; i < data.length; i++) { + data[i] = new args.type(data[i]) + } + } + else data = new args.type(data) + } + resolve(data) + }, reject) + }) + if (args.background === true) return promise + var count = 0 + function complete() { + if (--count === 0 && typeof oncompletion === "function") oncompletion() + } + + return wrap(promise) + + function wrap(promise) { + var then = promise.then + promise.then = function() { + count++ + var next = then.apply(promise, arguments) + next.then(complete, function(e) { + complete() + if (count === 0) throw e + }) + return wrap(next) + } + return promise } - return promise } } - function normalize(args, extra) { - if (typeof args === "string") { - var url = args - args = extra || {} - if (args.url == null) args.url = url + + function hasHeader(args, name) { + for (var key in args.headers) { + if ({}.hasOwnProperty.call(args.headers, key) && name.test(key)) return true } - return args + return false } - function request(args, extra) { - var finalize = finalizer() - args = normalize(args, extra) + function interpolate(url, data, assemble) { + if (data == null) return url + url = url.replace(/:([^\/]+)/gi, function (m, key) { + return data[key] != null ? data[key] : m + }) + if (assemble && data != null) { + var querystring = buildQueryString(data) + if (querystring) url += (url.indexOf("?") < 0 ? "?" : "&") + querystring + } + return url + } - var promise = new Promise(function(resolve, reject) { - if (args.method == null) args.method = "GET" - args.method = args.method.toUpperCase() + return { + request: makeRequest(function(url, args, resolve, reject) { + var method = args.method != null ? args.method.toUpperCase() : "GET" + var useBody = method !== "GET" && method !== "TRACE" && + (typeof args.useBody !== "boolean" || args.useBody) - var useBody = (args.method === "GET" || args.method === "TRACE") ? false : (typeof args.useBody === "boolean" ? args.useBody : true) - - if (typeof args.serialize !== "function") args.serialize = typeof FormData !== "undefined" && args.data instanceof FormData ? function(value) {return value} : JSON.stringify - if (typeof args.deserialize !== "function") args.deserialize = deserialize - if (typeof args.extract !== "function") args.extract = extract - - args.url = interpolate(args.url, args.data) - if (useBody) args.data = args.serialize(args.data) - else args.url = assemble(args.url, args.data) + var data = args.data + var assumeJSON = (args.serialize == null || args.serialize === JSON.serialize) && !(data instanceof $window.FormData) + if (useBody) { + if (typeof args.serialize === "function") data = args.serialize(data) + else if (!(data instanceof $window.FormData)) data = JSON.stringify(data) + } var xhr = new $window.XMLHttpRequest(), aborted = false, _abort = xhr.abort - xhr.abort = function abort() { aborted = true _abort.call(xhr) } - xhr.open(args.method, args.url, typeof args.async === "boolean" ? args.async : true, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) + xhr.open(method, interpolate(url, args.data, !useBody), typeof args.async !== "boolean" || args.async, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) - if (args.serialize === JSON.stringify && useBody && !(args.headers && args.headers.hasOwnProperty("Content-Type"))) { + if (assumeJSON && useBody && !hasHeader(args, /^content-type$/i)) { xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") } - if (args.deserialize === deserialize && !(args.headers && args.headers.hasOwnProperty("Accept"))) { + if (typeof args.deserialize !== "function" && !hasHeader(args, /^accept$/i)) { xhr.setRequestHeader("Accept", "application/json, text/*") } if (args.withCredentials) xhr.withCredentials = args.withCredentials - if (args.timeout) xhr.timeout = args.timeout - if (args.responseType) xhr.responseType = args.responseType - for (var key in args.headers) if ({}.hasOwnProperty.call(args.headers, key)) { - xhr.setRequestHeader(key, args.headers[key]) + for (var key in args.headers) { + if ({}.hasOwnProperty.call(args.headers, key)) { + xhr.setRequestHeader(key, args.headers[key]) + } } if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr @@ -91,10 +114,18 @@ module.exports = function($window, Promise) { if (xhr.readyState === 4) { try { - var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args)) - if (args.extract !== extract || (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) { - resolve(cast(args.type, response)) + var success = (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || (/^file:\/\//i).test(url) + var response = xhr.responseText + if (typeof args.extract === "function") { + response = args.extract(xhr, args) + success = true + } else if (typeof args.deserialize === "function") { + response = args.deserialize(response) + } else { + try {response = response ? JSON.parse(response) : null} + catch (e) {throw new Error("Invalid JSON: " + response)} } + if (success) resolve(response) else { var error = new Error(xhr.responseText) error.code = xhr.status @@ -108,22 +139,15 @@ module.exports = function($window, Promise) { } } - if (useBody && (args.data != null)) xhr.send(args.data) + if (useBody && data != null) xhr.send(data) else xhr.send() - }) - return args.background === true ? promise : finalize(promise) - } - - function jsonp(args, extra) { - var finalize = finalizer() - args = normalize(args, extra) - - var promise = new Promise(function(resolve, reject) { + }), + jsonp: makeRequest(function(url, args, resolve, reject) { var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ var script = $window.document.createElement("script") $window[callbackName] = function(data) { script.parentNode.removeChild(script) - resolve(cast(args.type, data)) + resolve(data) delete $window[callbackName] } script.onerror = function() { @@ -131,55 +155,14 @@ module.exports = function($window, Promise) { reject(new Error("JSONP request failed")) delete $window[callbackName] } - if (args.data == null) args.data = {} - args.url = interpolate(args.url, args.data) - args.data[args.callbackKey || "callback"] = callbackName - script.src = assemble(args.url, args.data) + url = interpolate(url, args.data, true) + script.src = url + (url.indexOf("?") < 0 ? "?" : "&") + + encodeURIComponent(args.callbackKey || "callback") + "=" + + encodeURIComponent(callbackName) $window.document.documentElement.appendChild(script) - }) - return args.background === true? promise : finalize(promise) + }), + setCompletionCallback: function(callback) { + oncompletion = callback + }, } - - function interpolate(url, data) { - if (data == null) return url - - var tokens = url.match(/:[^\/]+/gi) || [] - for (var i = 0; i < tokens.length; i++) { - var key = tokens[i].slice(1) - if (data[key] != null) { - url = url.replace(tokens[i], data[key]) - } - } - return url - } - - function assemble(url, data) { - var querystring = buildQueryString(data) - if (querystring !== "") { - var prefix = url.indexOf("?") < 0 ? "?" : "&" - url += prefix + querystring - } - return url - } - - function deserialize(data) { - try {return data !== "" ? JSON.parse(data) : null} - catch (e) {throw new Error("Invalid JSON: " + data)} - } - - function extract(xhr) {return xhr.responseText} - - function cast(type, data) { - if (typeof type === "function") { - if (Array.isArray(data)) { - for (var i = 0; i < data.length; i++) { - data[i] = new type(data[i]) - } - } - else return new type(data) - } - return data - } - - return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback} } diff --git a/request/tests/test-request.js b/request/tests/test-request.js index e69a93d7..bb52f6b3 100644 --- a/request/tests/test-request.js +++ b/request/tests/test-request.js @@ -415,9 +415,9 @@ o.spec("xhr", function() { xhr({method: "GET", url: "/item", config: handleAbort}).catch(function() { failed = true }) - .then(function() { - resolved = true - }) + .then(function() { + resolved = true + }) }) o("doesn't fail on file:// status 0", function(done) { mock.$defineRoutes({ diff --git a/stream/change-log.md b/stream/change-log.md index 45ccd0da..a7c5bf7b 100644 --- a/stream/change-log.md +++ b/stream/change-log.md @@ -1,6 +1,8 @@ # Change log for stream ## 2.0.0 +- renamed HALT to SKIP [#2207](https://github.com/MithrilJS/mithril.js/pull/2207) +- rewrote implementation [#2207](https://github.com/MithrilJS/mithril.js/pull/2207) - stream: Removed `valueOf` & `toString` methods ([#2150](https://github.com/MithrilJS/mithril.js/pull/2150) ## 1.1.0 diff --git a/stream/package.json b/stream/package.json index f5f4f86c..d78616b6 100644 --- a/stream/package.json +++ b/stream/package.json @@ -3,6 +3,7 @@ "version": "1.1.0", "description": "Streaming data, mithril-style", "main": "stream.js", + "module": "stream.mjs", "directories": { "test": "tests" }, diff --git a/stream/stream.js b/stream/stream.js index 9bf1fed5..4a703ce9 100644 --- a/stream/stream.js +++ b/stream/stream.js @@ -2,161 +2,156 @@ ;(function() { "use strict" /* eslint-enable */ +Stream.SKIP = {} +Stream.lift = lift +Stream.scan = scan +Stream.merge = merge +Stream.combine = combine +Stream.scanMerge = scanMerge +Stream["fantasy-land/of"] = Stream -var guid = 0, HALT = {} -function createStream() { - function stream() { - if (arguments.length > 0 && arguments[0] !== HALT) updateStream(stream, arguments[0]) - return stream._state.value +let warnedHalt = false +Object.defineProperty(Stream, "HALT", { + get: function() { + warnedHalt && console.log("HALT is deprecated and has been renamed to SKIP"); + warnedHalt = true + return Stream.SKIP } - initStream(stream) +}) - if (arguments.length > 0 && arguments[0] !== HALT) updateStream(stream, arguments[0]) +function Stream(value) { + var dependentStreams = [] + var dependentFns = [] + + function stream(v) { + if (arguments.length && v !== Stream.SKIP && open(stream)) { + value = v + stream.changing() + stream.state = "active" + dependentStreams.forEach(function(s, i) { s(dependentFns[i](value)) }) + } + + return value + } + + stream.constructor = Stream + stream.state = arguments.length && value !== Stream.SKIP ? "active" : "pending" + + stream.changing = function() { + open(stream) && (stream.state = "changing") + dependentStreams.forEach(function(s) { + s.dependent && s.dependent.changing() + s.changing() + }) + } + + stream.map = function(fn, ignoreInitial) { + var target = stream.state === "active" && ignoreInitial !== Stream.SKIP + ? Stream(fn(value)) + : Stream() + + dependentStreams.push(target) + dependentFns.push(fn) + return target + } + + let end + function createEnd() { + end = Stream() + end.map(function(value) { + if (value === true) { + stream.state = "ended" + dependentStreams.length = dependentFns.length = 0 + } + return value + }) + return end + } + + stream.toJSON = function() { return value != null && typeof value.toJSON === "function" ? value.toJSON() : value } + + stream["fantasy-land/map"] = stream.map + stream["fantasy-land/ap"] = function(x) { return combine(function(s1, s2) { return s1()(s2()) }, [x, stream]) } + + Object.defineProperty(stream, "end", { + get: function() { return end || createEnd() } + }) return stream } -function initStream(stream) { - stream.constructor = createStream - stream._state = {id: guid++, value: undefined, state: 0, derive: undefined, recover: undefined, deps: {}, parents: [], endStream: undefined, unregister: undefined} - stream.map = stream["fantasy-land/map"] = map, stream["fantasy-land/ap"] = ap, stream["fantasy-land/of"] = createStream - stream.toJSON = toJSON - - Object.defineProperties(stream, { - end: {get: function() { - if (!stream._state.endStream) { - var endStream = createStream() - endStream.map(function(value) { - if (value === true) { - unregisterStream(stream) - endStream._state.unregister = function(){unregisterStream(endStream)} - } - return value - }) - stream._state.endStream = endStream - } - return stream._state.endStream - }} - }) -} -function updateStream(stream, value) { - updateState(stream, value) - for (var id in stream._state.deps) updateDependency(stream._state.deps[id], false) - if (stream._state.unregister != null) stream._state.unregister() - finalize(stream) -} -function updateState(stream, value) { - stream._state.value = value - stream._state.changed = true - if (stream._state.state !== 2) stream._state.state = 1 -} -function updateDependency(stream, mustSync) { - var state = stream._state, parents = state.parents - if (parents.length > 0 && parents.every(active) && (mustSync || parents.some(changed))) { - var value = stream._state.derive() - if (value === HALT) return unregisterStream(stream) - updateState(stream, value) - } -} -function finalize(stream) { - stream._state.changed = false - for (var id in stream._state.deps) stream._state.deps[id]._state.changed = false -} function combine(fn, streams) { - if (!streams.every(valid)) throw new Error("Ensure that each item passed to stream.combine/stream.merge is a stream") - return initDependency(createStream(), streams, function() { - return fn.apply(this, streams.concat([streams.filter(changed)])) + var ready = streams.every(function(s) { + if (s.constructor !== Stream) + throw new Error("Ensure that each item passed to stream.combine/stream.merge/lift is a stream") + return s.state === "active" }) + var stream = ready + ? Stream(fn.apply(null, streams.concat([streams]))) + : Stream() + + let changed = [] + + streams.forEach(function(s) { + s.map(function(value) { + changed.push(s) + if (ready || streams.every(function(s) { return s.state !== "pending" })) { + ready = true + stream(fn.apply(null, streams.concat([changed]))) + changed = [] + } + return value + }, Stream.SKIP).parent = stream + }) + + return stream } -function initDependency(dep, streams, derive) { - var state = dep._state - state.derive = derive - state.parents = streams.filter(notEnded) - - registerDependency(dep, state.parents) - updateDependency(dep, true) - - return dep -} -function registerDependency(stream, parents) { - for (var i = 0; i < parents.length; i++) { - parents[i]._state.deps[stream._state.id] = stream - registerDependency(stream, parents[i]._state.parents) - } -} -function unregisterStream(stream) { - for (var i = 0; i < stream._state.parents.length; i++) { - var parent = stream._state.parents[i] - delete parent._state.deps[stream._state.id] - } - for (var id in stream._state.deps) { - var dependent = stream._state.deps[id] - var index = dependent._state.parents.indexOf(stream) - if (index > -1) dependent._state.parents.splice(index, 1) - } - stream._state.state = 2 //ended - stream._state.deps = {} -} - -function map(fn) {return combine(function(stream) {return fn(stream())}, [this])} -function ap(stream) {return combine(function(s1, s2) {return s1()(s2())}, [stream, this])} -function toJSON() {return this._state.value != null && typeof this._state.value.toJSON === "function" ? this._state.value.toJSON() : this._state.value} - -function valid(stream) {return stream._state } -function active(stream) {return stream._state.state === 1} -function changed(stream) {return stream._state.changed} -function notEnded(stream) {return stream._state.state !== 2} - function merge(streams) { - return combine(function() { - return streams.map(function(s) {return s()}) - }, streams) + return combine(function() { return streams.map(function(s) { return s() }) }, streams) } -function scan(reducer, seed, stream) { - var newStream = combine(function (s) { - var next = reducer(seed, s._state.value) - if (next !== HALT) return seed = next - return HALT - }, [stream]) - - if (newStream._state.state === 0) newStream(seed) - - return newStream +function scan(fn, acc, origin) { + var stream = origin.map(function(v) { + acc = fn(acc, v) + return acc + }) + stream(acc) + return stream } function scanMerge(tuples, seed) { - var streams = tuples.map(function(tuple) { - var stream = tuple[0] - if (stream._state.state === 0) stream(undefined) - return stream - }) + var streams = tuples.map(function(tuple) { return tuple[0] }) - var newStream = combine(function() { + var stream = combine(function() { var changed = arguments[arguments.length - 1] - - streams.forEach(function(stream, idx) { - if (changed.indexOf(stream) > -1) { - seed = tuples[idx][1](seed, stream._state.value) - } + streams.forEach(function(stream, i) { + if (changed.indexOf(stream) > -1) + seed = tuples[i][1](seed, stream()) }) return seed }, streams) - return newStream + stream(seed) + + return stream } -createStream["fantasy-land/of"] = createStream -createStream.merge = merge -createStream.combine = combine -createStream.scan = scan -createStream.scanMerge = scanMerge -createStream.HALT = HALT +function lift() { + var fn = arguments[0] + var streams = Array.prototype.slice.call(arguments, 1) + return merge(streams).map(function(streams) { + return fn.apply(undefined, streams) + }) +} -if (typeof module !== "undefined") module["exports"] = createStream -else if (typeof window.m === "function" && !("stream" in window.m)) window.m.stream = createStream -else window.m = {stream : createStream} +function open(s) { + return s.state === "pending" || s.state === "active" || s.state === "changing" +} + +if (typeof module !== "undefined") module["exports"] = Stream +else if (typeof window.m === "function" && !("stream" in window.m)) window.m.stream = Stream +else window.m = {stream : Stream} }()); diff --git a/stream/stream.mjs b/stream/stream.mjs new file mode 100644 index 00000000..5e233d76 --- /dev/null +++ b/stream/stream.mjs @@ -0,0 +1,151 @@ +/* eslint-enable */ +Stream.SKIP = {} +Stream.lift = lift +Stream.scan = scan +Stream.merge = merge +Stream.combine = combine +Stream.scanMerge = scanMerge +Stream["fantasy-land/of"] = Stream + +let warnedHalt = false +Object.defineProperty(Stream, "HALT", { + get: function() { + warnedHalt && console.log("HALT is deprecated and has been renamed to SKIP"); + warnedHalt = true + return Stream.SKIP + } +}) + +function Stream(value) { + var dependentStreams = [] + var dependentFns = [] + + function stream(v) { + if (arguments.length && v !== Stream.SKIP && open(stream)) { + value = v + stream.changing() + stream.state = "active" + dependentStreams.forEach(function(s, i) { s(dependentFns[i](value)) }) + } + + return value + } + + stream.constructor = Stream + stream.state = arguments.length && value !== Stream.SKIP ? "active" : "pending" + + stream.changing = function() { + open(stream) && (stream.state = "changing") + dependentStreams.forEach(function(s) { + s.dependent && s.dependent.changing() + s.changing() + }) + } + + stream.map = function(fn, ignoreInitial) { + var target = stream.state === "active" && ignoreInitial !== Stream.SKIP + ? Stream(fn(value)) + : Stream() + + dependentStreams.push(target) + dependentFns.push(fn) + return target + } + + let end + function createEnd() { + end = Stream() + end.map(function(value) { + if (value === true) { + stream.state = "ended" + dependentStreams.length = dependentFns.length = 0 + } + return value + }) + return end + } + + stream.toJSON = function() { return value != null && typeof value.toJSON === "function" ? value.toJSON() : value } + + stream["fantasy-land/map"] = stream.map + stream["fantasy-land/ap"] = function(x) { return combine(function(s1, s2) { return s1()(s2()) }, [x, stream]) } + + Object.defineProperty(stream, "end", { + get: function() { return end || createEnd() } + }) + + return stream +} + +function combine(fn, streams) { + var ready = streams.every(function(s) { + if (s.constructor !== Stream) + throw new Error("Ensure that each item passed to stream.combine/stream.merge/lift is a stream") + return s.state === "active" + }) + var stream = ready + ? Stream(fn.apply(null, streams.concat([streams]))) + : Stream() + + let changed = [] + + streams.forEach(function(s) { + s.map(function(value) { + changed.push(s) + if (ready || streams.every(function(s) { return s.state !== "pending" })) { + ready = true + stream(fn.apply(null, streams.concat([changed]))) + changed = [] + } + return value + }, Stream.SKIP).parent = stream + }) + + return stream +} + +function merge(streams) { + return combine(function() { return streams.map(function(s) { return s() }) }, streams) +} + +function scan(fn, acc, origin) { + var stream = origin.map(function(v) { + acc = fn(acc, v) + return acc + }) + stream(acc) + return stream +} + +function scanMerge(tuples, seed) { + var streams = tuples.map(function(tuple) { return tuple[0] }) + + var stream = combine(function() { + var changed = arguments[arguments.length - 1] + streams.forEach(function(stream, i) { + if (changed.indexOf(stream) > -1) + seed = tuples[i][1](seed, stream()) + }) + + return seed + }, streams) + + stream(seed) + + return stream +} + +function lift() { + var fn = arguments[0] + var streams = Array.prototype.slice.call(arguments, 1) + return merge(streams).map(function(streams) { + return fn.apply(undefined, streams) + }) +} + +function open(s) { + return s.state === "pending" || s.state === "active" || s.state === "changing" +} + + +export default Stream \ No newline at end of file diff --git a/stream/tests/test-scan.js b/stream/tests/test-scan.js index 2f2c2bb2..04423729 100644 --- a/stream/tests/test-scan.js +++ b/stream/tests/test-scan.js @@ -31,7 +31,7 @@ o.spec("scan", function() { o(result[3]).deepEquals({a: 1}) }) - o("reducer can return HALT to prevent child updates", function() { + o("reducer can return SKIP to prevent child updates", function() { var count = 0 var action = stream() var store = stream.scan(function (arr, value) { @@ -39,7 +39,7 @@ o.spec("scan", function() { case "number": return arr.concat(value) default: - return stream.HALT + return stream.SKIP } }, [], action) var child = store.map(function (p) { diff --git a/stream/tests/test-stream.js b/stream/tests/test-stream.js index 69ca811d..99e59b5c 100644 --- a/stream/tests/test-stream.js +++ b/stream/tests/test-stream.js @@ -30,6 +30,43 @@ o.spec("stream", function() { o(stream()()).equals(1) }) + o("can SKIP", function() { + var a = Stream(2) + var b = a.map(function(value) { + return value === 5 + ? Stream.SKIP + : value + }) + + a(5) + + o(b()).equals(2) + }) + o("can HALT", function() { + var a = Stream(2) + var b = a.map(function(value) { + return value === 5 + ? Stream.HALT + : value + }) + + a(5) + + o(b()).equals(2) + }) + o("warns HALT deprecated", function() { + var log = console.log + var warning = "" + console.log = function(a) { + warning = a + } + + Stream.HALT + + console.log = log + + o(warning).equals("HALT is deprecated and has been renamed to SKIP") + }) }) o.spec("combine", function() { o("transforms value", function() { @@ -87,6 +124,7 @@ o.spec("stream", function() { o(d()).equals(15) o(count).equals(1) }) + o("combines default value atomically", function() { var count = 0 var a = Stream(3) @@ -100,6 +138,21 @@ o.spec("stream", function() { o(d()).equals(15) o(count).equals(1) }) + o("combines and maps nested streams atomically", function() { + var count = 0 + var a = Stream(3) + var b = Stream.combine(function(a) {return a() * 2}, [a]) + var c = Stream.combine(function(a) {return a() * a()}, [a]) + var d = c.map(function(x){return x}) + var e = Stream.combine(function(x) {return x()}, [d]) + var f = Stream.combine(function(b, e) { + count++ + return b() + e() + }, [b, e]) + + o(f()).equals(15) + o(count).equals(1) + }) o("combine lists only changed upstreams in last arg", function() { var streams = [] var a = Stream() @@ -111,8 +164,22 @@ o.spec("stream", function() { a(3) b(5) - o(streams.length).equals(1) - o(streams[0]).equals(b) + o(streams.length).equals(2) + o(streams[0]).equals(a) + o(streams[1]).equals(b) + }) + o("combine continues with ended streams", function() { + var a = Stream() + var b = Stream() + var combined = Stream.combine(function(a, b) { + return a() + b() + }, [a, b]) + + a(3) + a.end(true) + b(5) + + o(combined()).equals(8) }) o("combine lists only changed upstreams in last arg with default value", function() { var streams = [] @@ -151,11 +218,11 @@ o.spec("stream", function() { o(b()()).equals(undefined) }) - o("combine can halt", function() { + o("combine can skip", function() { var count = 0 var a = Stream(1) var b = Stream.combine(function() { - return Stream.HALT + return Stream.SKIP }, [a])["fantasy-land/map"](function() { count++ return 1 @@ -164,13 +231,13 @@ o.spec("stream", function() { o(b()).equals(undefined) o(count).equals(0) }) - o("combine can conditionaly halt", function() { + o("combine can conditionaly skip", function() { var count = 0 - var halt = false + var skip = false var a = Stream(1) var b = Stream.combine(function(a) { - if (halt) { - return Stream.HALT + if (skip) { + return Stream.SKIP } return a() }, [a])["fantasy-land/map"](function(a) { @@ -179,7 +246,7 @@ o.spec("stream", function() { }) o(b()).equals(1) o(count).equals(1) - halt = true + skip = true count = 0 a(2) o(b()).equals(1) @@ -200,6 +267,127 @@ o.spec("stream", function() { o(spy.callCount).equals(0) }) }) + o.spec("lift", function() { + o("transforms value", function() { + var stream = Stream() + var doubled = Stream.lift(function(s) {return s * 2}, stream) + + stream(2) + + o(doubled()).equals(4) + }) + o("transforms default value", function() { + var stream = Stream(2) + var doubled = Stream.lift(function(s) {return s * 2}, stream) + + o(doubled()).equals(4) + }) + o("transforms multiple values", function() { + var s1 = Stream() + var s2 = Stream() + var added = Stream.lift(function(s1, s2) {return s1 + s2}, s1, s2) + + s1(2) + s2(3) + + o(added()).equals(5) + }) + o("transforms multiple default values", function() { + var s1 = Stream(2) + var s2 = Stream(3) + var added = Stream.lift(function(s1, s2) {return s1 + s2}, s1, s2) + + o(added()).equals(5) + }) + o("transforms mixed default and late-bound values", function() { + var s1 = Stream(2) + var s2 = Stream() + var added = Stream.lift(function(s1, s2) {return s1 + s2}, s1, s2) + + s2(3) + + o(added()).equals(5) + }) + o("lifts atomically", function() { + var count = 0 + var a = Stream() + var b = Stream.lift(function(a) {return a * 2}, a) + var c = Stream.lift(function(a) {return a * a}, a) + var d = Stream.lift(function(b, c) { + count++ + return b + c + }, b, c) + + a(3) + + o(d()).equals(15) + o(count).equals(1) + }) + o("lifts default value atomically", function() { + var count = 0 + var a = Stream(3) + var b = Stream.lift(function(a) {return a * 2}, a) + var c = Stream.lift(function(a) {return a * a}, a) + var d = Stream.lift(function(b, c) { + count++ + return b + c + }, b, c) + + o(d()).equals(15) + o(count).equals(1) + }) + o("lift can return undefined", function() { + var a = Stream(1) + var b = Stream.lift(function() { + return undefined + }, a) + + o(b()).equals(undefined) + }) + o("lift can return stream", function() { + var a = Stream(1) + var b = Stream.lift(function() { + return Stream(2) + }, a) + + o(b()()).equals(2) + }) + o("lift can return pending stream", function() { + var a = Stream(1) + var b = Stream.lift(function() { + return Stream() + }, a) + + o(b()()).equals(undefined) + }) + o("lift can halt", function() { + var count = 0 + var a = Stream(1) + var b = Stream.lift(function() { + return Stream.HALT + }, a)["fantasy-land/map"](function() { + count++ + return 1 + }) + + o(b()).equals(undefined) + o(count).equals(0) + }) + o("lift will throw with a helpful error if given non-stream values", function () { + var spy = o.spy() + var a = Stream(1) + var thrown = null; + try { + Stream.lift(spy, a, "") + } catch (e) { + thrown = e + } + + o(thrown).notEquals(null) + o(thrown.constructor === TypeError).equals(false) + o(spy.callCount).equals(0) + }) + }) o.spec("merge", function() { o("transforms an array of streams to an array of values", function() { var all = Stream.merge([ @@ -433,7 +621,7 @@ o.spec("stream", function() { }) o.spec("applicative", function() { o("identity", function() { - var a = Stream()["fantasy-land/of"](function(value) {return value}) + var a = Stream["fantasy-land/of"](function(value) {return value}) var v = Stream(5) o(v["fantasy-land/ap"](a)()).equals(5) @@ -444,16 +632,16 @@ o.spec("stream", function() { var f = function(value) {return value * 2} var x = 3 - o(a["fantasy-land/of"](x)["fantasy-land/ap"](a["fantasy-land/of"](f))()).equals(6) - o(a["fantasy-land/of"](x)["fantasy-land/ap"](a["fantasy-land/of"](f))()).equals(a["fantasy-land/of"](f(x))()) + o(a.constructor["fantasy-land/of"](x)["fantasy-land/ap"](a.constructor["fantasy-land/of"](f))()).equals(6) + o(a.constructor["fantasy-land/of"](x)["fantasy-land/ap"](a.constructor["fantasy-land/of"](f))()).equals(a.constructor["fantasy-land/of"](f(x))()) }) o("interchange", function() { var u = Stream(function(value) {return value * 2}) var a = Stream() var y = 3 - o(a["fantasy-land/of"](y)["fantasy-land/ap"](u)()).equals(6) - o(a["fantasy-land/of"](y)["fantasy-land/ap"](u)()).equals(u["fantasy-land/ap"](a["fantasy-land/of"](function(f) {return f(y)}))()) + o(a.constructor["fantasy-land/of"](y)["fantasy-land/ap"](u)()).equals(6) + o(a.constructor["fantasy-land/of"](y)["fantasy-land/ap"](u)()).equals(u["fantasy-land/ap"](a.constructor["fantasy-land/of"](function(f) {return f(y)}))()) }) }) }) diff --git a/test-utils/domMock.js b/test-utils/domMock.js index ce494683..840fda08 100644 --- a/test-utils/domMock.js +++ b/test-utils/domMock.js @@ -220,6 +220,9 @@ module.exports = function(options) { parseMarkup(value, root, [], "http://www.w3.org/2000/svg") return {documentElement: root} } + function camelCase(string) { + return string.replace(/-\D/g, function(match) {return match[1].toUpperCase()}) + } var activeElement var $window = { DOMParser: DOMParser, @@ -227,29 +230,40 @@ module.exports = function(options) { createElement: function(tag) { var cssText = "" var style = {} - Object.defineProperty(style, "cssText", { - get: function() {return cssText}, - set: function (value) { - var buf = [] - if (typeof value === "string") { - for (var key in style) style[key] = "" - var rules = splitDeclList(value) - for (var i = 0; i < rules.length; i++) { - var rule = rules[i] - var colonIndex = rule.indexOf(":") - if (colonIndex > -1) { - var rawKey = rule.slice(0, colonIndex).trim() - var key = rawKey.replace(/-\D/g, function(match) {return match[1].toUpperCase()}) - var value = rule.slice(colonIndex + 1).trim() - if (key !== "cssText") { - style[key] = value - buf.push(rawKey + ": " + value + ";") + Object.defineProperties(style, { + cssText: { + get: function() {return cssText}, + set: function (value) { + var buf = [] + if (typeof value === "string") { + for (var key in style) style[key] = "" + var rules = splitDeclList(value) + for (var i = 0; i < rules.length; i++) { + var rule = rules[i] + var colonIndex = rule.indexOf(":") + if (colonIndex > -1) { + var rawKey = rule.slice(0, colonIndex).trim() + var key = camelCase(rawKey) + var value = rule.slice(colonIndex + 1).trim() + if (key !== "cssText") { + style[key] = style[rawKey] = value + buf.push(rawKey + ": " + value + ";") + } } } + element.setAttribute("style", cssText = buf.join(" ")) } - element.setAttribute("style", cssText = buf.join(" ")) } - } + }, + getPropertyValue: {value: function(key){ + return style[key] + }}, + removeProperty: {value: function(key){ + style[key] = style[camelCase(key)] = "" + }}, + setProperty: {value: function(key, value){ + style[key] = style[camelCase(key)] = value + }} }) var events = {} var element = { @@ -459,9 +473,9 @@ module.exports = function(options) { if (!this.hasAttribute("type")) return "text" var type = this.getAttribute("type") return (/^(?:radio|button|checkbox|color|date|datetime|datetime-local|email|file|hidden|month|number|password|range|research|search|submit|tel|text|url|week|image)$/) - .test(type) - ? type - : "text" + .test(type) + ? type + : "text" }, set: typeSetter, enumerable: true, diff --git a/test-utils/tests/test-components.js b/test-utils/tests/test-components.js index e0ede703..3094cff2 100644 --- a/test-utils/tests/test-components.js +++ b/test-utils/tests/test-components.js @@ -37,7 +37,7 @@ o.spec("test-utils/components", function() { if (component.kind !== "constructible") { o(cmp2).deepEquals(methods) } else { - // deepEquals doesn't search the prototype, do it manually + // deepEquals doesn't search the prototype, do it manually o(cmp2 != null).equals(true) o(cmp2.view).equals(methods.view) o(cmp2.oninit).equals(methods.oninit) diff --git a/test-utils/tests/test-domMock.js b/test-utils/tests/test-domMock.js index 805f7b92..5c22c53d 100644 --- a/test-utils/tests/test-domMock.js +++ b/test-utils/tests/test-domMock.js @@ -1430,22 +1430,22 @@ o.spec("domMock", function() { o(input.type).equals("text") }) "radio|button|checkbox|color|date|datetime|datetime-local|email|file|hidden|month|number|password|range|research|search|submit|tel|text|url|week|image" - .split("|").forEach(function(type) { - o("can be set to " + type, function(){ - var input = $document.createElement("input") - input.type = type + .split("|").forEach(function(type) { + o("can be set to " + type, function(){ + var input = $document.createElement("input") + input.type = type - o(input.getAttribute("type")).equals(type) - o(input.type).equals(type) - }) - o("bad values set the attribute, but the getter corrects to 'text', " + type, function(){ - var input = $document.createElement("input") - input.type = "badbad" + type + o(input.getAttribute("type")).equals(type) + o(input.type).equals(type) + }) + o("bad values set the attribute, but the getter corrects to 'text', " + type, function(){ + var input = $document.createElement("input") + input.type = "badbad" + type - o(input.getAttribute("type")).equals("badbad" + type) - o(input.type).equals("text") + o(input.getAttribute("type")).equals("badbad" + type) + o(input.type).equals("text") + }) }) - }) }) o.spec("textarea[value]", function() { o("reads from child if no value was ever set", function() { diff --git a/test-utils/xhrMock.js b/test-utils/xhrMock.js index 379bce28..71c2f5b7 100644 --- a/test-utils/xhrMock.js +++ b/test-utils/xhrMock.js @@ -11,7 +11,9 @@ module.exports = function() { return {status: 500, responseText: "server error, most likely the URL was not defined " + url} } + function FormData() {} var $window = { + FormData: FormData, XMLHttpRequest: function XMLHttpRequest() { var args = {} var headers = {} diff --git a/tests/test-api.js b/tests/test-api.js index 49938d82..1304542f 100644 --- a/tests/test-api.js +++ b/tests/test-api.js @@ -24,7 +24,7 @@ o.spec("api", function() { o("works", function() { o(typeof m.version).equals("string") o(m.version.indexOf(".") > -1).equals(true) - o(/\d/.test(m.version)).equals(true) + o((/\d/).test(m.version)).equals(true) }) }) o.spec("m.trust", function() { @@ -45,16 +45,6 @@ o.spec("api", function() { o(vnode.children[0].tag).equals("div") }) }) - o.spec("m.withAttr", function() { - o("works", function() { - var spy = o.spy() - var handler = m.withAttr("value", spy) - - handler({currentTarget: {value: 10}}) - - o(spy.args[0]).equals(10) - }) - }) o.spec("m.parseQueryString", function() { o("works", function() { var query = m.parseQueryString("?a=1&b=2") diff --git a/util/prop.js b/util/prop.js deleted file mode 100644 index 41b2b354..00000000 --- a/util/prop.js +++ /dev/null @@ -1,9 +0,0 @@ -"use strict" - -module.exports = function (store) { - return { - get: function() { return store }, - toJSON: function() { return store }, - set: function(value) { return store = value } - } -} diff --git a/util/tests/index.html b/util/tests/index.html deleted file mode 100644 index 44c8c0b8..00000000 --- a/util/tests/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/util/tests/test-prop.js b/util/tests/test-prop.js deleted file mode 100644 index 38950071..00000000 --- a/util/tests/test-prop.js +++ /dev/null @@ -1,16 +0,0 @@ -"use strict" - -var o = require("../../ospec/ospec") -var prop = require("../../util/prop") - -o.spec("prop", function() { - o("works", function() { - var p = prop(1) - - o(p.get()).equals(1) - o(p.toJSON()).equals(1) - o(p.set(2)).equals(2) - o(p.get()).equals(2) - o(p.toJSON()).equals(2) - }) -}) diff --git a/util/tests/test-withAttr.js b/util/tests/test-withAttr.js deleted file mode 100644 index e9d21d5d..00000000 --- a/util/tests/test-withAttr.js +++ /dev/null @@ -1,38 +0,0 @@ -"use strict" - -var o = require("../../ospec/ospec") -var withAttr = require("../../util/withAttr") - -o.spec("withAttr", function() { - o("works", function() { - var spy = o.spy() - var context = { - handler: withAttr("value", spy) - } - context.handler({currentTarget: {value: 1}}) - - o(spy.args).deepEquals([1]) - o(spy.this).equals(context) - }) - o("works with attribute", function() { - var target = { - getAttribute: function() {return "readonly"} - } - var spy = o.spy() - var context = { - handler: withAttr("readonly", spy) - } - context.handler({currentTarget: target}) - - o(spy.args).deepEquals(["readonly"]) - o(spy.this).equals(context) - }) - o("context arg works", function() { - var spy = o.spy() - var context = {} - var handler = withAttr("value", spy, context) - handler({currentTarget: {value: 1}}) - - o(spy.this).equals(context) - }) -}) diff --git a/util/withAttr.js b/util/withAttr.js deleted file mode 100644 index 5df4332a..00000000 --- a/util/withAttr.js +++ /dev/null @@ -1,7 +0,0 @@ -"use strict" - -module.exports = function(attrName, callback, context) { - return function(e) { - callback.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName)) - } -}