├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── dist ├── mithril.animate.js ├── mithril.animate.min.js ├── mithril.bindings.js ├── nobind │ ├── mithril.animate.nobind.js │ └── mithril.animate.nobind.min.js └── version │ ├── mithril.animate-0.0.1.js │ ├── mithril.animate-0.0.1.min.js │ ├── mithril.animate-0.0.1.nobind.js │ └── mithril.animate-0.0.1.nobind.min.js ├── examples ├── all.example.js ├── all.htm ├── box.htm ├── catspin.css ├── dizzycats.htm ├── examplestyle.css ├── icons.png ├── max.jpg ├── misc.htm ├── mishka.jpg ├── mithril.js ├── pickle.jpg └── timed.htm ├── package.json └── src ├── _mithril.animate.js ├── mithril.animate.bindings.js └── mithril.animate.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // Concatenation file order 4 | var concatFiles = ['node_modules/mithril.bindings/dist/mithril.bindings.js', 'src/mithril.animate.js', 'src/mithril.animate.bindings.js']; 5 | concatNobindFiles = ['src/mithril.animate.js']; 6 | 7 | grunt.initConfig({ 8 | pkg: grunt.file.readJSON('package.json'), 9 | concat: { 10 | options: { 11 | separator: ';' 12 | }, 13 | dist: { 14 | // We'd prefer to fail on missing files, but at least this will warn: https://github.com/gruntjs/grunt-contrib-concat/issues/15 15 | nonull: true, 16 | files: { 17 | 'dist/version/<%= pkg.name %>-<%= pkg.version %>.js': concatFiles, 18 | 'dist/<%= pkg.name %>.js': concatFiles, 19 | 'dist/version/<%= pkg.name %>-<%= pkg.version %>.nobind.js': concatNobindFiles, 20 | 'dist/nobind/<%= pkg.name %>.nobind.js': concatNobindFiles 21 | } 22 | } 23 | }, 24 | qunit: { 25 | files: ['test/**/*.htm'] 26 | }, 27 | uglify: { 28 | options: { 29 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n' 30 | }, 31 | dist: { 32 | files: { 33 | 'dist/version/<%= pkg.name %>-<%= pkg.version %>.min.js': 'dist/version/<%= pkg.name %>-<%= pkg.version %>.js', 34 | 'dist/<%= pkg.name %>.min.js': 'dist/<%= pkg.name %>.js', 35 | 'dist/version/<%= pkg.name %>-<%= pkg.version %>.nobind.min.js': 'dist/version/<%= pkg.name %>-<%= pkg.version %>.nobind.js', 36 | 'dist/nobind/<%= pkg.name %>.nobind.min.js': 'dist/nobind/<%= pkg.name %>.nobind.js' 37 | } 38 | } 39 | }, 40 | jshint: { 41 | files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'], 42 | options: { 43 | ignores: [], 44 | // options here to override JSHint defaults 45 | globals: { 46 | jQuery: true, 47 | console: true, 48 | module: true, 49 | document: true 50 | }, 51 | // Ignore specific errors 52 | '-W015': true, // Indentation of } 53 | '-W099': true, // Mixed spaces and tabs 54 | '-W032': true // Unnecessary semicolon 55 | } 56 | }, 57 | watch: { 58 | files: ['<%= jshint.files %>'], 59 | // Just build when watching 60 | tasks: ['concat'] 61 | } 62 | }); 63 | 64 | grunt.loadNpmTasks('grunt-contrib-uglify'); 65 | grunt.loadNpmTasks('grunt-contrib-jshint'); 66 | grunt.loadNpmTasks('grunt-contrib-qunit'); 67 | grunt.loadNpmTasks('grunt-contrib-watch'); 68 | grunt.loadNpmTasks('grunt-contrib-concat'); 69 | 70 | grunt.registerTask('test', ['jshint', 'qunit']); 71 | grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']); 72 | grunt.registerTask('justbuild', ['concat', 'uglify']); 73 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 jsguy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mithril.animate 2 | =============== 3 | 4 | [Documentation and examples](http://jsguy.github.io/mithril.animate/) 5 | 6 | A compact library that allows you to bind CSS3 animations to properties on your Mithril elements - has a fallback to jQuery.animate for older browsers. -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mithril.animate", 3 | "main": "dist/mithril.animate.min.js", 4 | "version": "0.1.1", 5 | "description": "A compact library that allows you to bind CSS3 animations to properties on your Mithril elements.", 6 | "keywords": [ 7 | "animation", 8 | "css3", 9 | "transition", 10 | "transform", 11 | "keyframe", 12 | "mithril", 13 | "awesome" 14 | ], 15 | "authors": [ 16 | "Mikkel Bergmann (jsguy)" 17 | ], 18 | "license": "MIT", 19 | "homepage": "http://jsguy.github.io/mithril.animate/" 20 | } -------------------------------------------------------------------------------- /dist/mithril.animate.js: -------------------------------------------------------------------------------- 1 | // Mithril bindings. 2 | // Copyright (C) 2014 jsguy (Mikkel Bergmann) 3 | // MIT licensed 4 | (function(){ 5 | var mithrilBindings = function(m){ 6 | m.bindings = m.bindings || {}; 7 | 8 | // Pub/Sub based extended properties 9 | m.p = function(value) { 10 | var self = this, 11 | subs = [], 12 | prevValue, 13 | delay = false, 14 | // Send notifications to subscribers 15 | notify = function (value, prevValue) { 16 | var i; 17 | for (i = 0; i < subs.length; i += 1) { 18 | subs[i].func.apply(subs[i].context, [value, prevValue]); 19 | } 20 | }, 21 | prop = function() { 22 | if (arguments.length) { 23 | value = arguments[0]; 24 | if (prevValue !== value) { 25 | var tmpPrev = prevValue; 26 | prevValue = value; 27 | notify(value, tmpPrev); 28 | } 29 | } 30 | return value; 31 | }; 32 | 33 | // Allow push on arrays 34 | prop.push = function(val) { 35 | if(value.push && typeof value.length !== "undefined") { 36 | value.push(val); 37 | } 38 | prop(value); 39 | }; 40 | 41 | // Subscribe for when the value changes 42 | prop.subscribe = function (func, context) { 43 | subs.push({ func: func, context: context || self }); 44 | return prop; 45 | }; 46 | 47 | // Allow property to not automatically render 48 | prop.delay = function(value) { 49 | delay = !!value; 50 | return prop; 51 | }; 52 | 53 | // Automatically update rendering when a value changes 54 | // As mithril waits for a request animation frame, this should be ok. 55 | // You can use .delay(true) to be able to manually handle updates 56 | prop.subscribe(function(val){ 57 | if(!delay) { 58 | m.startComputation(); 59 | m.endComputation(); 60 | } 61 | return prop; 62 | }); 63 | 64 | return prop; 65 | }; 66 | 67 | // Element function that applies our extended bindings 68 | // Note: 69 | // . Some attributes can be removed when applied, eg: custom attributes 70 | // 71 | m.e = function(element, attrs, children) { 72 | for (var name in attrs) { 73 | if (m.bindings[name]) { 74 | m.bindings[name].func.apply(attrs, [attrs[name]]); 75 | if(m.bindings[name].removeable) { 76 | delete attrs[name]; 77 | } 78 | } 79 | } 80 | return m(element, attrs, children); 81 | }; 82 | 83 | // Add bindings method 84 | // Non-standard attributes do not need to be rendered, eg: valueInput 85 | // so they are set as removable 86 | m.addBinding = function(name, func, removeable){ 87 | m.bindings[name] = { 88 | func: func, 89 | removeable: removeable 90 | }; 91 | }; 92 | 93 | // Get the underlying value of a property 94 | m.unwrap = function(prop) { 95 | return (typeof prop == "function")? prop(): prop; 96 | }; 97 | 98 | // Bi-directional binding of value 99 | m.addBinding("value", function(prop) { 100 | if (typeof prop == "function") { 101 | this.value = prop(); 102 | this.onchange = m.withAttr("value", prop); 103 | } else { 104 | this.value = prop; 105 | } 106 | }); 107 | 108 | // Bi-directional binding of checked property 109 | m.addBinding("checked", function(prop) { 110 | if (typeof prop == "function") { 111 | this.checked = prop(); 112 | this.onchange = m.withAttr("checked", prop); 113 | } else { 114 | this.checked = prop; 115 | } 116 | }); 117 | 118 | // Hide node 119 | m.addBinding("hide", function(prop){ 120 | this.style = { 121 | display: m.unwrap(prop)? "none" : "" 122 | }; 123 | }, true); 124 | 125 | // Toggle value(s) on click 126 | m.addBinding('toggle', function(prop){ 127 | this.onclick = function(){ 128 | // Toggle allows an enum list to be toggled, eg: [prop, value2, value2] 129 | var isFunc = typeof prop === 'function', tmp, i, vals = [], val, tVal; 130 | 131 | // Toggle boolean 132 | if(isFunc) { 133 | value = prop(); 134 | prop(!value); 135 | } else { 136 | // Toggle enumeration 137 | tmp = prop[0]; 138 | val = tmp(); 139 | vals = prop.slice(1); 140 | tVal = vals[0]; 141 | 142 | for(i = 0; i < vals.length; i += 1) { 143 | if(val == vals[i]) { 144 | if(typeof vals[i+1] !== 'undefined') { 145 | tVal = vals[i+1]; 146 | } 147 | break; 148 | } 149 | } 150 | tmp(tVal); 151 | } 152 | }; 153 | }, true); 154 | 155 | // Set hover states, a'la jQuery pattern 156 | m.addBinding('hover', function(prop){ 157 | this.onmouseover = prop[0]; 158 | if(prop[1]) { 159 | this.onmouseout = prop[1]; 160 | } 161 | }, true ); 162 | 163 | // Add value bindings for various event types 164 | var events = ["Input", "Keyup", "Keypress"], 165 | createBinding = function(name, eve){ 166 | // Bi-directional binding of value 167 | m.addBinding(name, function(prop) { 168 | if (typeof prop == "function") { 169 | this.value = prop(); 170 | this[eve] = m.withAttr("value", prop); 171 | } else { 172 | this.value = prop; 173 | } 174 | }, true); 175 | }; 176 | 177 | for(var i = 0; i < events.length; i += 1) { 178 | var eve = events[i]; 179 | createBinding("value" + eve, "on" + eve.toLowerCase()); 180 | } 181 | 182 | 183 | // Set a value on a property 184 | m.set = function(prop, value){ 185 | return function() { 186 | prop(value); 187 | }; 188 | }; 189 | 190 | /* Returns a function that can trigger a binding 191 | Usage: onclick: m.trigger('binding', prop) 192 | */ 193 | m.trigger = function(){ 194 | var args = Array.prototype.slice.call(arguments); 195 | return function(){ 196 | var name = args[0], 197 | argList = args.slice(1); 198 | if (m.bindings[name]) { 199 | m.bindings[name].func.apply(this, argList); 200 | } 201 | }; 202 | }; 203 | 204 | return m.bindings; 205 | }; 206 | 207 | if (typeof module != "undefined" && module !== null && module.exports) { 208 | module.exports = mithrilBindings; 209 | } else if (typeof define === "function" && define.amd) { 210 | define(function() { 211 | return mithrilBindings; 212 | }); 213 | } else { 214 | mithrilBindings(typeof window != "undefined"? window.m || {}: {}); 215 | } 216 | 217 | }());;/* 218 | mithril.animate - Copyright 2014 jsguy 219 | MIT Licensed. 220 | */ 221 | (function (m) { 222 | // Known prefiex 223 | var prefixes = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'], 224 | transitionProps = ['TransitionProperty', 'TransitionTimingFunction', 'TransitionDelay', 'TransitionDuration', 'TransitionEnd'], 225 | transformProps = ['rotate', 'rotatex', 'rotatey', 'scale', 'skew', 'translate', 'translatex', 'translatey', 'matrix'], 226 | 227 | defaultDuration = 400, 228 | 229 | err = function(msg){ 230 | window.console && console.error && console.error(msg); 231 | }, 232 | 233 | // Capitalise 234 | cap = function(str){ 235 | return str.charAt(0).toUpperCase() + str.substr(1); 236 | }, 237 | 238 | // For checking what vendor prefixes are native 239 | div = document.createElement('div'), 240 | 241 | // vendor prefix, ie: transitionDuration becomes MozTransitionDuration 242 | vp = function (prop, dashed) { 243 | var pf; 244 | // Handle unprefixed 245 | if (prop in div.style) { 246 | return prop; 247 | } 248 | 249 | // Handle keyframes 250 | if(prop == "@keyframes") { 251 | for (var i = 0; i < prefixes.length; i += 1) { 252 | // Testing using transition 253 | pf = prefixes[i] + "Transition"; 254 | if (pf in div.style) { 255 | return "@-" + prefixes[i].toLowerCase() + "-keyframes"; 256 | } 257 | } 258 | return prop; 259 | } 260 | 261 | for (var i = 0; i < prefixes.length; i += 1) { 262 | if(dashed) { 263 | pf = "-" +(prefixes[i] + "-" + prop).toLowerCase(); 264 | } else { 265 | pf = prefixes[i] + cap(prop); 266 | } 267 | if (pf in div.style) { 268 | return pf; 269 | } 270 | } 271 | // Can't find it - return original property. 272 | return prop; 273 | }, 274 | 275 | // See if we can use native transitions 276 | supportsTransitions = function() { 277 | var b = document.body || document.documentElement, 278 | s = b.style, 279 | p = 'transition'; 280 | 281 | if (typeof s[p] == 'string') { return true; } 282 | 283 | // Tests for vendor specific prop 284 | p = p.charAt(0).toUpperCase() + p.substr(1); 285 | 286 | for (var i=0; i 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Mithril.animate - bindable animations

12 |

13 | A compact library that allows you to bind CSS3 animations to properties on your mithril elements. 14 |

15 | 16 |
17 | 18 |

Source

19 | 20 | Click here for the source for these examples 21 | 22 | 23 | 24 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/box.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 |

Mithril.animate - bindable animations

17 |

18 | This example shows you how to create a silly spinning box with cats on it.
19 | Click the cat to see it in action. Uses a little CSS 20 |

21 | 22 |
23 | 24 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /examples/catspin.css: -------------------------------------------------------------------------------- 1 | .box { margin:4em auto; width:16em; height:16em; transform-style:preserve-3d; transform-origin:50% 50% -5em; position:relative; } 2 | .box div { position:absolute; width:16em; height:16em; background-color:#4c4c4c; transform-style:preserve-3d; backface-visibility:hidden; } 3 | .boxFace1 { background-image: url(mishka.jpg); background-repeat: no-repeat; } 4 | .boxFace2 { transform:rotateX(180deg) translateZ(16em); } 5 | 6 | .boxFace1::before, 7 | .boxFace1::after, 8 | .boxFace2::before, 9 | .boxFace2::after { position:absolute; content:''; display:block; width:100%; height:100%; transform-style:preserve-3d; } 10 | 11 | .boxFace1::before, 12 | .boxFace2::before { transform:rotateY(90deg); transform-origin:0 50%; background-color:#444; background-image: url(pickle.jpg); background-repeat: no-repeat; } 13 | .boxFace2::before { transform-origin:100% 50%; transform:rotateY(-90deg); } 14 | .boxFace1::after, 15 | .boxFace2::after { transform:rotateX(-90deg); transform-origin:50% 0; background-color:#555; background-image: url(max.jpg); background-repeat: no-repeat; } -------------------------------------------------------------------------------- /examples/dizzycats.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Mithril.animate - bindable animations

10 |

11 | This example shows you how to create a silly spinning box with cats on it that spins forever.
12 | Click the button to see it in action. Uses a little CSS 13 |

14 | 15 |
16 | 17 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /examples/examplestyle.css: -------------------------------------------------------------------------------- 1 | #content { font-family: sans-serif; } 2 | .eBox { width: 2em; height: 2em; background-color: #7b7; border: 1px solid #70bb70; margin-top: 1em;} 3 | .eBox.alt1 { background-color: #cfc; border: 1px solid #c0ffc0; opacity: 0; } 4 | .eBox.abs { top: 72px; position: absolute; } 5 | 6 | .eBox.icon { position: absolute; background-color: inherit; border: none; background-image: url(icons.png); background-repeat: no-repeat; background-position: 0 -32px; } 7 | .eBox.icon.alt1 { background-position: 0 0; } 8 | .exampleBox:before, .exampleBox:after {content: " "; display: table; } 9 | .exampleBox:after {clear: both; } 10 | .exampleBox { *zoom: 1; min-height: 7em; position: relative; border: 2px solid #333; border-radius: 3px; float: left; margin: 0 0 1em 1em; min-height: 7em; padding: 1em; position: relative; width: 8em; } 11 | h2 { font-size: 1.2em; clear: both; } 12 | .exampleBox h3 { font-size: 1em; margin-top: 0; font-family: sans-serif; } 13 | .exampleBox a { font-size: 0.7em } -------------------------------------------------------------------------------- /examples/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsguy/mithril.animate/7810d79e9c24cb89d6052dde31c68ccafd35f970/examples/icons.png -------------------------------------------------------------------------------- /examples/max.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsguy/mithril.animate/7810d79e9c24cb89d6052dde31c68ccafd35f970/examples/max.jpg -------------------------------------------------------------------------------- /examples/misc.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 19 | 20 | 21 |

Mithril.animate - bindable animations

22 |

23 | This example shows you how to create miscellaneous animations and bindings, including a custom keyframe animation.
24 | Note: One downside to using property bound animations is that they will animate each time there are changes in the vDOM. 25 |

26 | 27 |
28 | 29 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /examples/mishka.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsguy/mithril.animate/7810d79e9c24cb89d6052dde31c68ccafd35f970/examples/mishka.jpg -------------------------------------------------------------------------------- /examples/mithril.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | "use strict" 3 | function Vnode(tag, key, attrs0, children, text, dom) { 4 | return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: undefined, _state: undefined, events: undefined, instance: undefined, skip: false} 5 | } 6 | Vnode.normalize = function(node) { 7 | if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined) 8 | if (node != null && typeof node !== "object") return Vnode("#", undefined, undefined, node === false ? "" : node, undefined, undefined) 9 | return node 10 | } 11 | Vnode.normalizeChildren = function normalizeChildren(children) { 12 | for (var i = 0; i < children.length; i++) { 13 | children[i] = Vnode.normalize(children[i]) 14 | } 15 | return children 16 | } 17 | var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g 18 | var selectorCache = {} 19 | var hasOwn = {}.hasOwnProperty 20 | function compileSelector(selector) { 21 | var match, tag = "div", classes = [], attrs = {} 22 | while (match = selectorParser.exec(selector)) { 23 | var type = match[1], value = match[2] 24 | if (type === "" && value !== "") tag = value 25 | else if (type === "#") attrs.id = value 26 | else if (type === ".") classes.push(value) 27 | else if (match[3][0] === "[") { 28 | var attrValue = match[6] 29 | if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\") 30 | if (match[4] === "class") classes.push(attrValue) 31 | else attrs[match[4]] = attrValue === "" ? attrValue : attrValue || true 32 | } 33 | } 34 | if (classes.length > 0) attrs.className = classes.join(" ") 35 | return selectorCache[selector] = {tag: tag, attrs: attrs} 36 | } 37 | function execSelector(state, attrs, children) { 38 | var hasAttrs = false, childList, text 39 | var className = attrs.className || attrs.class 40 | for (var key in state.attrs) { 41 | if (hasOwn.call(state.attrs, key)) { 42 | attrs[key] = state.attrs[key] 43 | } 44 | } 45 | if (className !== undefined) { 46 | if (attrs.class !== undefined) { 47 | attrs.class = undefined 48 | attrs.className = className 49 | } 50 | if (state.attrs.className != null) { 51 | attrs.className = state.attrs.className + " " + className 52 | } 53 | } 54 | for (var key in attrs) { 55 | if (hasOwn.call(attrs, key) && key !== "key") { 56 | hasAttrs = true 57 | break 58 | } 59 | } 60 | if (Array.isArray(children) && children.length === 1 && children[0] != null && children[0].tag === "#") { 61 | text = children[0].children 62 | } else { 63 | childList = children 64 | } 65 | return Vnode(state.tag, attrs.key, hasAttrs ? attrs : undefined, childList, text) 66 | } 67 | function hyperscript(selector) { 68 | // Because sloppy mode sucks 69 | var attrs = arguments[1], start = 2, children 70 | if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") { 71 | throw Error("The selector must be either a string or a component."); 72 | } 73 | if (typeof selector === "string") { 74 | var cached = selectorCache[selector] || compileSelector(selector) 75 | } 76 | if (attrs == null) { 77 | attrs = {} 78 | } else if (typeof attrs !== "object" || attrs.tag != null || Array.isArray(attrs)) { 79 | attrs = {} 80 | start = 1 81 | } 82 | if (arguments.length === start + 1) { 83 | children = arguments[start] 84 | if (!Array.isArray(children)) children = [children] 85 | } else { 86 | children = [] 87 | while (start < arguments.length) children.push(arguments[start++]) 88 | } 89 | var normalized = Vnode.normalizeChildren(children) 90 | if (typeof selector === "string") { 91 | return execSelector(cached, attrs, normalized) 92 | } else { 93 | return Vnode(selector, attrs.key, attrs, normalized) 94 | } 95 | } 96 | hyperscript.trust = function(html) { 97 | if (html == null) html = "" 98 | return Vnode("<", undefined, undefined, html, undefined, undefined) 99 | } 100 | hyperscript.fragment = function(attrs1, children) { 101 | return Vnode("[", attrs1.key, attrs1, Vnode.normalizeChildren(children), undefined, undefined) 102 | } 103 | var m = hyperscript 104 | /** @constructor */ 105 | var PromisePolyfill = function(executor) { 106 | if (!(this instanceof PromisePolyfill)) throw new Error("Promise must be called with `new`") 107 | if (typeof executor !== "function") throw new TypeError("executor must be a function") 108 | var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false) 109 | var instance = self._instance = {resolvers: resolvers, rejectors: rejectors} 110 | var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout 111 | function handler(list, shouldAbsorb) { 112 | return function execute(value) { 113 | var then 114 | try { 115 | if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") { 116 | if (value === self) throw new TypeError("Promise can't be resolved w/ itself") 117 | executeOnce(then.bind(value)) 118 | } 119 | else { 120 | callAsync(function() { 121 | if (!shouldAbsorb && list.length === 0) console.error("Possible unhandled promise rejection:", value) 122 | for (var i = 0; i < list.length; i++) list[i](value) 123 | resolvers.length = 0, rejectors.length = 0 124 | instance.state = shouldAbsorb 125 | instance.retry = function() {execute(value)} 126 | }) 127 | } 128 | } 129 | catch (e) { 130 | rejectCurrent(e) 131 | } 132 | } 133 | } 134 | function executeOnce(then) { 135 | var runs = 0 136 | function run(fn) { 137 | return function(value) { 138 | if (runs++ > 0) return 139 | fn(value) 140 | } 141 | } 142 | var onerror = run(rejectCurrent) 143 | try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)} 144 | } 145 | executeOnce(executor) 146 | } 147 | PromisePolyfill.prototype.then = function(onFulfilled, onRejection) { 148 | var self = this, instance = self._instance 149 | function handle(callback, list, next, state) { 150 | list.push(function(value) { 151 | if (typeof callback !== "function") next(value) 152 | else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)} 153 | }) 154 | if (typeof instance.retry === "function" && state === instance.state) instance.retry() 155 | } 156 | var resolveNext, rejectNext 157 | var promise = new PromisePolyfill(function(resolve, reject) {resolveNext = resolve, rejectNext = reject}) 158 | handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false) 159 | return promise 160 | } 161 | PromisePolyfill.prototype.catch = function(onRejection) { 162 | return this.then(null, onRejection) 163 | } 164 | PromisePolyfill.resolve = function(value) { 165 | if (value instanceof PromisePolyfill) return value 166 | return new PromisePolyfill(function(resolve) {resolve(value)}) 167 | } 168 | PromisePolyfill.reject = function(value) { 169 | return new PromisePolyfill(function(resolve, reject) {reject(value)}) 170 | } 171 | PromisePolyfill.all = function(list) { 172 | return new PromisePolyfill(function(resolve, reject) { 173 | var total = list.length, count = 0, values = [] 174 | if (list.length === 0) resolve([]) 175 | else for (var i = 0; i < list.length; i++) { 176 | (function(i) { 177 | function consume(value) { 178 | count++ 179 | values[i] = value 180 | if (count === total) resolve(values) 181 | } 182 | if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") { 183 | list[i].then(consume, reject) 184 | } 185 | else consume(list[i]) 186 | })(i) 187 | } 188 | }) 189 | } 190 | PromisePolyfill.race = function(list) { 191 | return new PromisePolyfill(function(resolve, reject) { 192 | for (var i = 0; i < list.length; i++) { 193 | list[i].then(resolve, reject) 194 | } 195 | }) 196 | } 197 | if (typeof window !== "undefined") { 198 | if (typeof window.Promise === "undefined") window.Promise = PromisePolyfill 199 | var PromisePolyfill = window.Promise 200 | } else if (typeof global !== "undefined") { 201 | if (typeof global.Promise === "undefined") global.Promise = PromisePolyfill 202 | var PromisePolyfill = global.Promise 203 | } else { 204 | } 205 | var buildQueryString = function(object) { 206 | if (Object.prototype.toString.call(object) !== "[object Object]") return "" 207 | var args = [] 208 | for (var key0 in object) { 209 | destructure(key0, object[key0]) 210 | } 211 | return args.join("&") 212 | function destructure(key0, value) { 213 | if (Array.isArray(value)) { 214 | for (var i = 0; i < value.length; i++) { 215 | destructure(key0 + "[" + i + "]", value[i]) 216 | } 217 | } 218 | else if (Object.prototype.toString.call(value) === "[object Object]") { 219 | for (var i in value) { 220 | destructure(key0 + "[" + i + "]", value[i]) 221 | } 222 | } 223 | else args.push(encodeURIComponent(key0) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : "")) 224 | } 225 | } 226 | var FILE_PROTOCOL_REGEX = new RegExp("^file://", "i") 227 | var _8 = function($window, Promise) { 228 | var callbackCount = 0 229 | var oncompletion 230 | function setCompletionCallback(callback) {oncompletion = callback} 231 | function finalizer() { 232 | var count = 0 233 | function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()} 234 | return function finalize(promise0) { 235 | var then0 = promise0.then 236 | promise0.then = function() { 237 | count++ 238 | var next = then0.apply(promise0, arguments) 239 | next.then(complete, function(e) { 240 | complete() 241 | if (count === 0) throw e 242 | }) 243 | return finalize(next) 244 | } 245 | return promise0 246 | } 247 | } 248 | function normalize(args, extra) { 249 | if (typeof args === "string") { 250 | var url = args 251 | args = extra || {} 252 | if (args.url == null) args.url = url 253 | } 254 | return args 255 | } 256 | function request(args, extra) { 257 | var finalize = finalizer() 258 | args = normalize(args, extra) 259 | var promise0 = new Promise(function(resolve, reject) { 260 | if (args.method == null) args.method = "GET" 261 | args.method = args.method.toUpperCase() 262 | var useBody = (args.method === "GET" || args.method === "TRACE") ? false : (typeof args.useBody === "boolean" ? args.useBody : true) 263 | if (typeof args.serialize !== "function") args.serialize = typeof FormData !== "undefined" && args.data instanceof FormData ? function(value) {return value} : JSON.stringify 264 | if (typeof args.deserialize !== "function") args.deserialize = deserialize 265 | if (typeof args.extract !== "function") args.extract = extract 266 | args.url = interpolate(args.url, args.data) 267 | if (useBody) args.data = args.serialize(args.data) 268 | else args.url = assemble(args.url, args.data) 269 | var xhr = new $window.XMLHttpRequest(), 270 | aborted = false, 271 | _abort = xhr.abort 272 | xhr.abort = function abort() { 273 | aborted = true 274 | _abort.call(xhr) 275 | } 276 | 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) 277 | if (args.serialize === JSON.stringify && useBody) { 278 | xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") 279 | } 280 | if (args.deserialize === deserialize) { 281 | xhr.setRequestHeader("Accept", "application/json, text/*") 282 | } 283 | if (args.withCredentials) xhr.withCredentials = args.withCredentials 284 | for (var key in args.headers) if ({}.hasOwnProperty.call(args.headers, key)) { 285 | xhr.setRequestHeader(key, args.headers[key]) 286 | } 287 | if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr 288 | xhr.onreadystatechange = function() { 289 | // Don't throw errors on xhr.abort(). 290 | if(aborted) return 291 | if (xhr.readyState === 4) { 292 | try { 293 | var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args)) 294 | if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) { 295 | resolve(cast(args.type, response)) 296 | } 297 | else { 298 | var error = new Error(xhr.responseText) 299 | for (var key in response) error[key] = response[key] 300 | reject(error) 301 | } 302 | } 303 | catch (e) { 304 | reject(e) 305 | } 306 | } 307 | } 308 | if (useBody && (args.data != null)) xhr.send(args.data) 309 | else xhr.send() 310 | }) 311 | return args.background === true ? promise0 : finalize(promise0) 312 | } 313 | function jsonp(args, extra) { 314 | var finalize = finalizer() 315 | args = normalize(args, extra) 316 | var promise0 = new Promise(function(resolve, reject) { 317 | var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ 318 | var script = $window.document.createElement("script") 319 | $window[callbackName] = function(data) { 320 | script.parentNode.removeChild(script) 321 | resolve(cast(args.type, data)) 322 | delete $window[callbackName] 323 | } 324 | script.onerror = function() { 325 | script.parentNode.removeChild(script) 326 | reject(new Error("JSONP request failed")) 327 | delete $window[callbackName] 328 | } 329 | if (args.data == null) args.data = {} 330 | args.url = interpolate(args.url, args.data) 331 | args.data[args.callbackKey || "callback"] = callbackName 332 | script.src = assemble(args.url, args.data) 333 | $window.document.documentElement.appendChild(script) 334 | }) 335 | return args.background === true? promise0 : finalize(promise0) 336 | } 337 | function interpolate(url, data) { 338 | if (data == null) return url 339 | var tokens = url.match(/:[^\/]+/gi) || [] 340 | for (var i = 0; i < tokens.length; i++) { 341 | var key = tokens[i].slice(1) 342 | if (data[key] != null) { 343 | url = url.replace(tokens[i], data[key]) 344 | } 345 | } 346 | return url 347 | } 348 | function assemble(url, data) { 349 | var querystring = buildQueryString(data) 350 | if (querystring !== "") { 351 | var prefix = url.indexOf("?") < 0 ? "?" : "&" 352 | url += prefix + querystring 353 | } 354 | return url 355 | } 356 | function deserialize(data) { 357 | try {return data !== "" ? JSON.parse(data) : null} 358 | catch (e) {throw new Error(data)} 359 | } 360 | function extract(xhr) {return xhr.responseText} 361 | function cast(type0, data) { 362 | if (typeof type0 === "function") { 363 | if (Array.isArray(data)) { 364 | for (var i = 0; i < data.length; i++) { 365 | data[i] = new type0(data[i]) 366 | } 367 | } 368 | else return new type0(data) 369 | } 370 | return data 371 | } 372 | return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback} 373 | } 374 | var requestService = _8(window, PromisePolyfill) 375 | var coreRenderer = function($window) { 376 | var $doc = $window.document 377 | var $emptyFragment = $doc.createDocumentFragment() 378 | var nameSpace = { 379 | svg: "http://www.w3.org/2000/svg", 380 | math: "http://www.w3.org/1998/Math/MathML" 381 | } 382 | var onevent 383 | function setEventCallback(callback) {return onevent = callback} 384 | function getNameSpace(vnode) { 385 | return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag] 386 | } 387 | //create 388 | function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) { 389 | for (var i = start; i < end; i++) { 390 | var vnode = vnodes[i] 391 | if (vnode != null) { 392 | createNode(parent, vnode, hooks, ns, nextSibling) 393 | } 394 | } 395 | } 396 | function createNode(parent, vnode, hooks, ns, nextSibling) { 397 | var tag = vnode.tag 398 | if (typeof tag === "string") { 399 | vnode.state = {} 400 | if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks) 401 | switch (tag) { 402 | case "#": return createText(parent, vnode, nextSibling) 403 | case "<": return createHTML(parent, vnode, nextSibling) 404 | case "[": return createFragment(parent, vnode, hooks, ns, nextSibling) 405 | default: return createElement(parent, vnode, hooks, ns, nextSibling) 406 | } 407 | } 408 | else return createComponent(parent, vnode, hooks, ns, nextSibling) 409 | } 410 | function createText(parent, vnode, nextSibling) { 411 | vnode.dom = $doc.createTextNode(vnode.children) 412 | insertNode(parent, vnode.dom, nextSibling) 413 | return vnode.dom 414 | } 415 | function createHTML(parent, vnode, nextSibling) { 416 | var match1 = vnode.children.match(/^\s*?<(\w+)/im) || [] 417 | var parent1 = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}[match1[1]] || "div" 418 | var temp = $doc.createElement(parent1) 419 | temp.innerHTML = vnode.children 420 | vnode.dom = temp.firstChild 421 | vnode.domSize = temp.childNodes.length 422 | var fragment = $doc.createDocumentFragment() 423 | var child 424 | while (child = temp.firstChild) { 425 | fragment.appendChild(child) 426 | } 427 | insertNode(parent, fragment, nextSibling) 428 | return fragment 429 | } 430 | function createFragment(parent, vnode, hooks, ns, nextSibling) { 431 | var fragment = $doc.createDocumentFragment() 432 | if (vnode.children != null) { 433 | var children = vnode.children 434 | createNodes(fragment, children, 0, children.length, hooks, null, ns) 435 | } 436 | vnode.dom = fragment.firstChild 437 | vnode.domSize = fragment.childNodes.length 438 | insertNode(parent, fragment, nextSibling) 439 | return fragment 440 | } 441 | function createElement(parent, vnode, hooks, ns, nextSibling) { 442 | var tag = vnode.tag 443 | var attrs2 = vnode.attrs 444 | var is = attrs2 && attrs2.is 445 | ns = getNameSpace(vnode) || ns 446 | var element = ns ? 447 | is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) : 448 | is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag) 449 | vnode.dom = element 450 | if (attrs2 != null) { 451 | setAttrs(vnode, attrs2, ns) 452 | } 453 | insertNode(parent, element, nextSibling) 454 | if (vnode.attrs != null && vnode.attrs.contenteditable != null) { 455 | setContentEditable(vnode) 456 | } 457 | else { 458 | if (vnode.text != null) { 459 | if (vnode.text !== "") element.textContent = vnode.text 460 | else vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)] 461 | } 462 | if (vnode.children != null) { 463 | var children = vnode.children 464 | createNodes(element, children, 0, children.length, hooks, null, ns) 465 | setLateAttrs(vnode) 466 | } 467 | } 468 | return element 469 | } 470 | function initComponent(vnode, hooks) { 471 | var sentinel 472 | if (typeof vnode.tag.view === "function") { 473 | vnode.state = Object.create(vnode.tag) 474 | sentinel = vnode.state.view 475 | if (sentinel.$$reentrantLock$$ != null) return $emptyFragment 476 | sentinel.$$reentrantLock$$ = true 477 | } else { 478 | vnode.state = void 0 479 | sentinel = vnode.tag 480 | if (sentinel.$$reentrantLock$$ != null) return $emptyFragment 481 | sentinel.$$reentrantLock$$ = true 482 | vnode.state = (vnode.tag.prototype != null && typeof vnode.tag.prototype.view === "function") ? new vnode.tag(vnode) : vnode.tag(vnode) 483 | } 484 | vnode._state = vnode.state 485 | if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks) 486 | initLifecycle(vnode._state, vnode, hooks) 487 | vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode)) 488 | if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument") 489 | sentinel.$$reentrantLock$$ = null 490 | } 491 | function createComponent(parent, vnode, hooks, ns, nextSibling) { 492 | initComponent(vnode, hooks) 493 | if (vnode.instance != null) { 494 | var element = createNode(parent, vnode.instance, hooks, ns, nextSibling) 495 | vnode.dom = vnode.instance.dom 496 | vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0 497 | insertNode(parent, element, nextSibling) 498 | return element 499 | } 500 | else { 501 | vnode.domSize = 0 502 | return $emptyFragment 503 | } 504 | } 505 | //update 506 | function updateNodes(parent, old, vnodes, recycling, hooks, nextSibling, ns) { 507 | if (old === vnodes || old == null && vnodes == null) return 508 | else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns) 509 | else if (vnodes == null) removeNodes(old, 0, old.length, vnodes) 510 | else { 511 | if (old.length === vnodes.length) { 512 | var isUnkeyed = false 513 | for (var i = 0; i < vnodes.length; i++) { 514 | if (vnodes[i] != null && old[i] != null) { 515 | isUnkeyed = vnodes[i].key == null && old[i].key == null 516 | break 517 | } 518 | } 519 | if (isUnkeyed) { 520 | for (var i = 0; i < old.length; i++) { 521 | if (old[i] === vnodes[i]) continue 522 | else if (old[i] == null && vnodes[i] != null) createNode(parent, vnodes[i], hooks, ns, getNextSibling(old, i + 1, nextSibling)) 523 | else if (vnodes[i] == null) removeNodes(old, i, i + 1, vnodes) 524 | else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), recycling, ns) 525 | } 526 | return 527 | } 528 | } 529 | recycling = recycling || isRecyclable(old, vnodes) 530 | if (recycling) { 531 | var pool = old.pool 532 | old = old.concat(old.pool) 533 | } 534 | var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map 535 | while (oldEnd >= oldStart && end >= start) { 536 | var o = old[oldStart], v = vnodes[start] 537 | if (o === v && !recycling) oldStart++, start++ 538 | else if (o == null) oldStart++ 539 | else if (v == null) start++ 540 | else if (o.key === v.key) { 541 | var shouldRecycle = (pool != null && oldStart >= old.length - pool.length) || ((pool == null) && recycling) 542 | oldStart++, start++ 543 | updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), shouldRecycle, ns) 544 | if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) 545 | } 546 | else { 547 | var o = old[oldEnd] 548 | if (o === v && !recycling) oldEnd--, start++ 549 | else if (o == null) oldEnd-- 550 | else if (v == null) start++ 551 | else if (o.key === v.key) { 552 | var shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling) 553 | updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns) 554 | if (recycling || start < end) insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling)) 555 | oldEnd--, start++ 556 | } 557 | else break 558 | } 559 | } 560 | while (oldEnd >= oldStart && end >= start) { 561 | var o = old[oldEnd], v = vnodes[end] 562 | if (o === v && !recycling) oldEnd--, end-- 563 | else if (o == null) oldEnd-- 564 | else if (v == null) end-- 565 | else if (o.key === v.key) { 566 | var shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling) 567 | updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns) 568 | if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) 569 | if (o.dom != null) nextSibling = o.dom 570 | oldEnd--, end-- 571 | } 572 | else { 573 | if (!map) map = getKeyMap(old, oldEnd) 574 | if (v != null) { 575 | var oldIndex = map[v.key] 576 | if (oldIndex != null) { 577 | var movable = old[oldIndex] 578 | var shouldRecycle = (pool != null && oldIndex >= old.length - pool.length) || ((pool == null) && recycling) 579 | updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) 580 | insertNode(parent, toFragment(movable), nextSibling) 581 | old[oldIndex].skip = true 582 | if (movable.dom != null) nextSibling = movable.dom 583 | } 584 | else { 585 | var dom = createNode(parent, v, hooks, ns, nextSibling) 586 | nextSibling = dom 587 | } 588 | } 589 | end-- 590 | } 591 | if (end < start) break 592 | } 593 | createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns) 594 | removeNodes(old, oldStart, oldEnd + 1, vnodes) 595 | } 596 | } 597 | function updateNode(parent, old, vnode, hooks, nextSibling, recycling, ns) { 598 | var oldTag = old.tag, tag = vnode.tag 599 | if (oldTag === tag) { 600 | vnode.state = old.state 601 | vnode._state = old._state 602 | vnode.events = old.events 603 | if (!recycling && shouldNotUpdate(vnode, old)) return 604 | if (typeof oldTag === "string") { 605 | if (vnode.attrs != null) { 606 | if (recycling) { 607 | vnode.state = {} 608 | initLifecycle(vnode.attrs, vnode, hooks) 609 | } 610 | else updateLifecycle(vnode.attrs, vnode, hooks) 611 | } 612 | switch (oldTag) { 613 | case "#": updateText(old, vnode); break 614 | case "<": updateHTML(parent, old, vnode, nextSibling); break 615 | case "[": updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns); break 616 | default: updateElement(old, vnode, recycling, hooks, ns) 617 | } 618 | } 619 | else updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) 620 | } 621 | else { 622 | removeNode(old, null) 623 | createNode(parent, vnode, hooks, ns, nextSibling) 624 | } 625 | } 626 | function updateText(old, vnode) { 627 | if (old.children.toString() !== vnode.children.toString()) { 628 | old.dom.nodeValue = vnode.children 629 | } 630 | vnode.dom = old.dom 631 | } 632 | function updateHTML(parent, old, vnode, nextSibling) { 633 | if (old.children !== vnode.children) { 634 | toFragment(old) 635 | createHTML(parent, vnode, nextSibling) 636 | } 637 | else vnode.dom = old.dom, vnode.domSize = old.domSize 638 | } 639 | function updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns) { 640 | updateNodes(parent, old.children, vnode.children, recycling, hooks, nextSibling, ns) 641 | var domSize = 0, children = vnode.children 642 | vnode.dom = null 643 | if (children != null) { 644 | for (var i = 0; i < children.length; i++) { 645 | var child = children[i] 646 | if (child != null && child.dom != null) { 647 | if (vnode.dom == null) vnode.dom = child.dom 648 | domSize += child.domSize || 1 649 | } 650 | } 651 | if (domSize !== 1) vnode.domSize = domSize 652 | } 653 | } 654 | function updateElement(old, vnode, recycling, hooks, ns) { 655 | var element = vnode.dom = old.dom 656 | ns = getNameSpace(vnode) || ns 657 | if (vnode.tag === "textarea") { 658 | if (vnode.attrs == null) vnode.attrs = {} 659 | if (vnode.text != null) { 660 | vnode.attrs.value = vnode.text //FIXME handle0 multiple children 661 | vnode.text = undefined 662 | } 663 | } 664 | updateAttrs(vnode, old.attrs, vnode.attrs, ns) 665 | if (vnode.attrs != null && vnode.attrs.contenteditable != null) { 666 | setContentEditable(vnode) 667 | } 668 | else if (old.text != null && vnode.text != null && vnode.text !== "") { 669 | if (old.text.toString() !== vnode.text.toString()) old.dom.firstChild.nodeValue = vnode.text 670 | } 671 | else { 672 | if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)] 673 | if (vnode.text != null) vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)] 674 | updateNodes(element, old.children, vnode.children, recycling, hooks, null, ns) 675 | } 676 | } 677 | function updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) { 678 | if (recycling) { 679 | initComponent(vnode, hooks) 680 | } else { 681 | vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode)) 682 | if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument") 683 | if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks) 684 | updateLifecycle(vnode._state, vnode, hooks) 685 | } 686 | if (vnode.instance != null) { 687 | if (old.instance == null) createNode(parent, vnode.instance, hooks, ns, nextSibling) 688 | else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling, ns) 689 | vnode.dom = vnode.instance.dom 690 | vnode.domSize = vnode.instance.domSize 691 | } 692 | else if (old.instance != null) { 693 | removeNode(old.instance, null) 694 | vnode.dom = undefined 695 | vnode.domSize = 0 696 | } 697 | else { 698 | vnode.dom = old.dom 699 | vnode.domSize = old.domSize 700 | } 701 | } 702 | function isRecyclable(old, vnodes) { 703 | if (old.pool != null && Math.abs(old.pool.length - vnodes.length) <= Math.abs(old.length - vnodes.length)) { 704 | var oldChildrenLength = old[0] && old[0].children && old[0].children.length || 0 705 | var poolChildrenLength = old.pool[0] && old.pool[0].children && old.pool[0].children.length || 0 706 | var vnodesChildrenLength = vnodes[0] && vnodes[0].children && vnodes[0].children.length || 0 707 | if (Math.abs(poolChildrenLength - vnodesChildrenLength) <= Math.abs(oldChildrenLength - vnodesChildrenLength)) { 708 | return true 709 | } 710 | } 711 | return false 712 | } 713 | function getKeyMap(vnodes, end) { 714 | var map = {}, i = 0 715 | for (var i = 0; i < end; i++) { 716 | var vnode = vnodes[i] 717 | if (vnode != null) { 718 | var key2 = vnode.key 719 | if (key2 != null) map[key2] = i 720 | } 721 | } 722 | return map 723 | } 724 | function toFragment(vnode) { 725 | var count0 = vnode.domSize 726 | if (count0 != null || vnode.dom == null) { 727 | var fragment = $doc.createDocumentFragment() 728 | if (count0 > 0) { 729 | var dom = vnode.dom 730 | while (--count0) fragment.appendChild(dom.nextSibling) 731 | fragment.insertBefore(dom, fragment.firstChild) 732 | } 733 | return fragment 734 | } 735 | else return vnode.dom 736 | } 737 | function getNextSibling(vnodes, i, nextSibling) { 738 | for (; i < vnodes.length; i++) { 739 | if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom 740 | } 741 | return nextSibling 742 | } 743 | function insertNode(parent, dom, nextSibling) { 744 | if (nextSibling && nextSibling.parentNode) parent.insertBefore(dom, nextSibling) 745 | else parent.appendChild(dom) 746 | } 747 | function setContentEditable(vnode) { 748 | var children = vnode.children 749 | if (children != null && children.length === 1 && children[0].tag === "<") { 750 | var content = children[0].children 751 | if (vnode.dom.innerHTML !== content) vnode.dom.innerHTML = content 752 | } 753 | else if (vnode.text != null || children != null && children.length !== 0) throw new Error("Child node of a contenteditable must be trusted") 754 | } 755 | //remove 756 | function removeNodes(vnodes, start, end, context) { 757 | for (var i = start; i < end; i++) { 758 | var vnode = vnodes[i] 759 | if (vnode != null) { 760 | if (vnode.skip) vnode.skip = false 761 | else removeNode(vnode, context) 762 | } 763 | } 764 | } 765 | function removeNode(vnode, context) { 766 | var expected = 1, called = 0 767 | if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") { 768 | var result = vnode.attrs.onbeforeremove.call(vnode.state, vnode) 769 | if (result != null && typeof result.then === "function") { 770 | expected++ 771 | result.then(continuation, continuation) 772 | } 773 | } 774 | if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeremove === "function") { 775 | var result = vnode._state.onbeforeremove.call(vnode.state, vnode) 776 | if (result != null && typeof result.then === "function") { 777 | expected++ 778 | result.then(continuation, continuation) 779 | } 780 | } 781 | continuation() 782 | function continuation() { 783 | if (++called === expected) { 784 | onremove(vnode) 785 | if (vnode.dom) { 786 | var count0 = vnode.domSize || 1 787 | if (count0 > 1) { 788 | var dom = vnode.dom 789 | while (--count0) { 790 | removeNodeFromDOM(dom.nextSibling) 791 | } 792 | } 793 | removeNodeFromDOM(vnode.dom) 794 | if (context != null && vnode.domSize == null && !hasIntegrationMethods(vnode.attrs) && typeof vnode.tag === "string") { //TODO test custom elements 795 | if (!context.pool) context.pool = [vnode] 796 | else context.pool.push(vnode) 797 | } 798 | } 799 | } 800 | } 801 | } 802 | function removeNodeFromDOM(node) { 803 | var parent = node.parentNode 804 | if (parent != null) parent.removeChild(node) 805 | } 806 | function onremove(vnode) { 807 | if (vnode.attrs && typeof vnode.attrs.onremove === "function") vnode.attrs.onremove.call(vnode.state, vnode) 808 | if (typeof vnode.tag !== "string" && typeof vnode._state.onremove === "function") vnode._state.onremove.call(vnode.state, vnode) 809 | if (vnode.instance != null) onremove(vnode.instance) 810 | else { 811 | var children = vnode.children 812 | if (Array.isArray(children)) { 813 | for (var i = 0; i < children.length; i++) { 814 | var child = children[i] 815 | if (child != null) onremove(child) 816 | } 817 | } 818 | } 819 | } 820 | //attrs2 821 | function setAttrs(vnode, attrs2, ns) { 822 | for (var key2 in attrs2) { 823 | setAttr(vnode, key2, null, attrs2[key2], ns) 824 | } 825 | } 826 | function setAttr(vnode, key2, old, value, ns) { 827 | var element = vnode.dom 828 | if (key2 === "key" || key2 === "is" || (old === value && !isFormAttribute(vnode, key2)) && typeof value !== "object" || typeof value === "undefined" || isLifecycleMethod(key2)) return 829 | var nsLastIndex = key2.indexOf(":") 830 | if (nsLastIndex > -1 && key2.substr(0, nsLastIndex) === "xlink") { 831 | element.setAttributeNS("http://www.w3.org/1999/xlink", key2.slice(nsLastIndex + 1), value) 832 | } 833 | else if (key2[0] === "o" && key2[1] === "n" && typeof value === "function") updateEvent(vnode, key2, value) 834 | else if (key2 === "style") updateStyle(element, old, value) 835 | else if (key2 in element && !isAttribute(key2) && ns === undefined && !isCustomElement(vnode)) { 836 | if (key2 === "value") { 837 | var normalized0 = "" + value // eslint-disable-line no-implicit-coercion 838 | //setting input[value] to same value by typing on focused element moves cursor to end in Chrome 839 | if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === normalized0 && vnode.dom === $doc.activeElement) return 840 | //setting select[value] to same value while having select open blinks select dropdown in Chrome 841 | if (vnode.tag === "select") { 842 | if (value === null) { 843 | if (vnode.dom.selectedIndex === -1 && vnode.dom === $doc.activeElement) return 844 | } else { 845 | if (old !== null && vnode.dom.value === normalized0 && vnode.dom === $doc.activeElement) return 846 | } 847 | } 848 | //setting option[value] to same value while having select open blinks select dropdown in Chrome 849 | if (vnode.tag === "option" && old != null && vnode.dom.value === normalized0) return 850 | } 851 | // If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error0 will occur. 852 | if (vnode.tag === "input" && key2 === "type") { 853 | element.setAttribute(key2, value) 854 | return 855 | } 856 | element[key2] = value 857 | } 858 | else { 859 | if (typeof value === "boolean") { 860 | if (value) element.setAttribute(key2, "") 861 | else element.removeAttribute(key2) 862 | } 863 | else element.setAttribute(key2 === "className" ? "class" : key2, value) 864 | } 865 | } 866 | function setLateAttrs(vnode) { 867 | var attrs2 = vnode.attrs 868 | if (vnode.tag === "select" && attrs2 != null) { 869 | if ("value" in attrs2) setAttr(vnode, "value", null, attrs2.value, undefined) 870 | if ("selectedIndex" in attrs2) setAttr(vnode, "selectedIndex", null, attrs2.selectedIndex, undefined) 871 | } 872 | } 873 | function updateAttrs(vnode, old, attrs2, ns) { 874 | if (attrs2 != null) { 875 | for (var key2 in attrs2) { 876 | setAttr(vnode, key2, old && old[key2], attrs2[key2], ns) 877 | } 878 | } 879 | if (old != null) { 880 | for (var key2 in old) { 881 | if (attrs2 == null || !(key2 in attrs2)) { 882 | if (key2 === "className") key2 = "class" 883 | if (key2[0] === "o" && key2[1] === "n" && !isLifecycleMethod(key2)) updateEvent(vnode, key2, undefined) 884 | else if (key2 !== "key") vnode.dom.removeAttribute(key2) 885 | } 886 | } 887 | } 888 | } 889 | function isFormAttribute(vnode, attr) { 890 | return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode.dom === $doc.activeElement 891 | } 892 | function isLifecycleMethod(attr) { 893 | return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate" 894 | } 895 | function isAttribute(attr) { 896 | return attr === "href" || attr === "list" || attr === "form" || attr === "width" || attr === "height"// || attr === "type" 897 | } 898 | function isCustomElement(vnode){ 899 | return vnode.attrs.is || vnode.tag.indexOf("-") > -1 900 | } 901 | function hasIntegrationMethods(source) { 902 | return source != null && (source.oncreate || source.onupdate || source.onbeforeremove || source.onremove) 903 | } 904 | //style 905 | function updateStyle(element, old, style) { 906 | if (old === style) element.style.cssText = "", old = null 907 | if (style == null) element.style.cssText = "" 908 | else if (typeof style === "string") element.style.cssText = style 909 | else { 910 | if (typeof old === "string") element.style.cssText = "" 911 | for (var key2 in style) { 912 | element.style[key2] = style[key2] 913 | } 914 | if (old != null && typeof old !== "string") { 915 | for (var key2 in old) { 916 | if (!(key2 in style)) element.style[key2] = "" 917 | } 918 | } 919 | } 920 | } 921 | //event 922 | function updateEvent(vnode, key2, value) { 923 | var element = vnode.dom 924 | var callback = typeof onevent !== "function" ? value : function(e) { 925 | var result = value.call(element, e) 926 | onevent.call(element, e) 927 | return result 928 | } 929 | if (key2 in element) element[key2] = typeof value === "function" ? callback : null 930 | else { 931 | var eventName = key2.slice(2) 932 | if (vnode.events === undefined) vnode.events = {} 933 | if (vnode.events[key2] === callback) return 934 | if (vnode.events[key2] != null) element.removeEventListener(eventName, vnode.events[key2], false) 935 | if (typeof value === "function") { 936 | vnode.events[key2] = callback 937 | element.addEventListener(eventName, vnode.events[key2], false) 938 | } 939 | } 940 | } 941 | //lifecycle 942 | function initLifecycle(source, vnode, hooks) { 943 | if (typeof source.oninit === "function") source.oninit.call(vnode.state, vnode) 944 | if (typeof source.oncreate === "function") hooks.push(source.oncreate.bind(vnode.state, vnode)) 945 | } 946 | function updateLifecycle(source, vnode, hooks) { 947 | if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode)) 948 | } 949 | function shouldNotUpdate(vnode, old) { 950 | var forceVnodeUpdate, forceComponentUpdate 951 | if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") forceVnodeUpdate = vnode.attrs.onbeforeupdate.call(vnode.state, vnode, old) 952 | if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeupdate === "function") forceComponentUpdate = vnode._state.onbeforeupdate.call(vnode.state, vnode, old) 953 | if (!(forceVnodeUpdate === undefined && forceComponentUpdate === undefined) && !forceVnodeUpdate && !forceComponentUpdate) { 954 | vnode.dom = old.dom 955 | vnode.domSize = old.domSize 956 | vnode.instance = old.instance 957 | return true 958 | } 959 | return false 960 | } 961 | function render(dom, vnodes) { 962 | if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.") 963 | var hooks = [] 964 | var active = $doc.activeElement 965 | var namespace = dom.namespaceURI 966 | // First time0 rendering into a node clears it out 967 | if (dom.vnodes == null) dom.textContent = "" 968 | if (!Array.isArray(vnodes)) vnodes = [vnodes] 969 | updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace) 970 | dom.vnodes = vnodes 971 | for (var i = 0; i < hooks.length; i++) hooks[i]() 972 | if ($doc.activeElement !== active) active.focus() 973 | } 974 | return {render: render, setEventCallback: setEventCallback} 975 | } 976 | function throttle(callback) { 977 | //60fps translates to 16.6ms, round it down since setTimeout requires int 978 | var time = 16 979 | var last = 0, pending = null 980 | var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout 981 | return function() { 982 | var now = Date.now() 983 | if (last === 0 || now - last >= time) { 984 | last = now 985 | callback() 986 | } 987 | else if (pending === null) { 988 | pending = timeout(function() { 989 | pending = null 990 | callback() 991 | last = Date.now() 992 | }, time - (now - last)) 993 | } 994 | } 995 | } 996 | var _11 = function($window) { 997 | var renderService = coreRenderer($window) 998 | renderService.setEventCallback(function(e) { 999 | if (e.redraw === false) e.redraw = undefined 1000 | else redraw() 1001 | }) 1002 | var callbacks = [] 1003 | function subscribe(key1, callback) { 1004 | unsubscribe(key1) 1005 | callbacks.push(key1, throttle(callback)) 1006 | } 1007 | function unsubscribe(key1) { 1008 | var index = callbacks.indexOf(key1) 1009 | if (index > -1) callbacks.splice(index, 2) 1010 | } 1011 | function redraw() { 1012 | for (var i = 1; i < callbacks.length; i += 2) { 1013 | callbacks[i]() 1014 | } 1015 | } 1016 | return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render} 1017 | } 1018 | var redrawService = _11(window) 1019 | requestService.setCompletionCallback(redrawService.redraw) 1020 | var _16 = function(redrawService0) { 1021 | return function(root, component) { 1022 | if (component === null) { 1023 | redrawService0.render(root, []) 1024 | redrawService0.unsubscribe(root) 1025 | return 1026 | } 1027 | 1028 | if (component.view == null && typeof component !== "function") throw new Error("m.mount(element, component) expects a component, not a vnode") 1029 | 1030 | var run0 = function() { 1031 | redrawService0.render(root, Vnode(component)) 1032 | } 1033 | redrawService0.subscribe(root, run0) 1034 | redrawService0.redraw() 1035 | } 1036 | } 1037 | m.mount = _16(redrawService) 1038 | var Promise = PromisePolyfill 1039 | var parseQueryString = function(string) { 1040 | if (string === "" || string == null) return {} 1041 | if (string.charAt(0) === "?") string = string.slice(1) 1042 | var entries = string.split("&"), data0 = {}, counters = {} 1043 | for (var i = 0; i < entries.length; i++) { 1044 | var entry = entries[i].split("=") 1045 | var key5 = decodeURIComponent(entry[0]) 1046 | var value = entry.length === 2 ? decodeURIComponent(entry[1]) : "" 1047 | if (value === "true") value = true 1048 | else if (value === "false") value = false 1049 | var levels = key5.split(/\]\[?|\[/) 1050 | var cursor = data0 1051 | if (key5.indexOf("[") > -1) levels.pop() 1052 | for (var j = 0; j < levels.length; j++) { 1053 | var level = levels[j], nextLevel = levels[j + 1] 1054 | var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10)) 1055 | var isValue = j === levels.length - 1 1056 | if (level === "") { 1057 | var key5 = levels.slice(0, j).join() 1058 | if (counters[key5] == null) counters[key5] = 0 1059 | level = counters[key5]++ 1060 | } 1061 | if (cursor[level] == null) { 1062 | cursor[level] = isValue ? value : isNumber ? [] : {} 1063 | } 1064 | cursor = cursor[level] 1065 | } 1066 | } 1067 | return data0 1068 | } 1069 | var coreRouter = function($window) { 1070 | var supportsPushState = typeof $window.history.pushState === "function" 1071 | var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout 1072 | function normalize1(fragment0) { 1073 | var data = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent) 1074 | if (fragment0 === "pathname" && data[0] !== "/") data = "/" + data 1075 | return data 1076 | } 1077 | var asyncId 1078 | function debounceAsync(callback0) { 1079 | return function() { 1080 | if (asyncId != null) return 1081 | asyncId = callAsync0(function() { 1082 | asyncId = null 1083 | callback0() 1084 | }) 1085 | } 1086 | } 1087 | function parsePath(path, queryData, hashData) { 1088 | var queryIndex = path.indexOf("?") 1089 | var hashIndex = path.indexOf("#") 1090 | var pathEnd = queryIndex > -1 ? queryIndex : hashIndex > -1 ? hashIndex : path.length 1091 | if (queryIndex > -1) { 1092 | var queryEnd = hashIndex > -1 ? hashIndex : path.length 1093 | var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd)) 1094 | for (var key4 in queryParams) queryData[key4] = queryParams[key4] 1095 | } 1096 | if (hashIndex > -1) { 1097 | var hashParams = parseQueryString(path.slice(hashIndex + 1)) 1098 | for (var key4 in hashParams) hashData[key4] = hashParams[key4] 1099 | } 1100 | return path.slice(0, pathEnd) 1101 | } 1102 | var router = {prefix: "#!"} 1103 | router.getPath = function() { 1104 | var type2 = router.prefix.charAt(0) 1105 | switch (type2) { 1106 | case "#": return normalize1("hash").slice(router.prefix.length) 1107 | case "?": return normalize1("search").slice(router.prefix.length) + normalize1("hash") 1108 | default: return normalize1("pathname").slice(router.prefix.length) + normalize1("search") + normalize1("hash") 1109 | } 1110 | } 1111 | router.setPath = function(path, data, options) { 1112 | var queryData = {}, hashData = {} 1113 | path = parsePath(path, queryData, hashData) 1114 | if (data != null) { 1115 | for (var key4 in data) queryData[key4] = data[key4] 1116 | path = path.replace(/:([^\/]+)/g, function(match2, token) { 1117 | delete queryData[token] 1118 | return data[token] 1119 | }) 1120 | } 1121 | var query = buildQueryString(queryData) 1122 | if (query) path += "?" + query 1123 | var hash = buildQueryString(hashData) 1124 | if (hash) path += "#" + hash 1125 | if (supportsPushState) { 1126 | var state = options ? options.state : null 1127 | var title = options ? options.title : null 1128 | $window.onpopstate() 1129 | if (options && options.replace) $window.history.replaceState(state, title, router.prefix + path) 1130 | else $window.history.pushState(state, title, router.prefix + path) 1131 | } 1132 | else $window.location.href = router.prefix + path 1133 | } 1134 | router.defineRoutes = function(routes, resolve, reject) { 1135 | function resolveRoute() { 1136 | var path = router.getPath() 1137 | var params = {} 1138 | var pathname = parsePath(path, params, params) 1139 | var state = $window.history.state 1140 | if (state != null) { 1141 | for (var k in state) params[k] = state[k] 1142 | } 1143 | for (var route0 in routes) { 1144 | var matcher = new RegExp("^" + route0.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$") 1145 | if (matcher.test(pathname)) { 1146 | pathname.replace(matcher, function() { 1147 | var keys = route0.match(/:[^\/]+/g) || [] 1148 | var values = [].slice.call(arguments, 1, -2) 1149 | for (var i = 0; i < keys.length; i++) { 1150 | params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i]) 1151 | } 1152 | resolve(routes[route0], params, path, route0) 1153 | }) 1154 | return 1155 | } 1156 | } 1157 | reject(path, params) 1158 | } 1159 | if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute) 1160 | else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute 1161 | resolveRoute() 1162 | } 1163 | return router 1164 | } 1165 | var _20 = function($window, redrawService0) { 1166 | var routeService = coreRouter($window) 1167 | var identity = function(v) {return v} 1168 | var render1, component, attrs3, currentPath, lastUpdate 1169 | var route = function(root, defaultRoute, routes) { 1170 | if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined") 1171 | var run1 = function() { 1172 | if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3))) 1173 | } 1174 | var bail = function(path) { 1175 | if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true}) 1176 | else throw new Error("Could not resolve default route " + defaultRoute) 1177 | } 1178 | routeService.defineRoutes(routes, function(payload, params, path) { 1179 | var update = lastUpdate = function(routeResolver, comp) { 1180 | if (update !== lastUpdate) return 1181 | component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div" 1182 | attrs3 = params, currentPath = path, lastUpdate = null 1183 | render1 = (routeResolver.render || identity).bind(routeResolver) 1184 | run1() 1185 | } 1186 | if (payload.view || typeof payload === "function") update({}, payload) 1187 | else { 1188 | if (payload.onmatch) { 1189 | Promise.resolve(payload.onmatch(params, path)).then(function(resolved) { 1190 | update(payload, resolved) 1191 | }, bail) 1192 | } 1193 | else update(payload, "div") 1194 | } 1195 | }, bail) 1196 | redrawService0.subscribe(root, run1) 1197 | } 1198 | route.set = function(path, data, options) { 1199 | if (lastUpdate != null) { 1200 | options = options || {} 1201 | options.replace = true 1202 | } 1203 | lastUpdate = null 1204 | routeService.setPath(path, data, options) 1205 | } 1206 | route.get = function() {return currentPath} 1207 | route.prefix = function(prefix0) {routeService.prefix = prefix0} 1208 | route.link = function(vnode1) { 1209 | vnode1.dom.setAttribute("href", routeService.prefix + vnode1.attrs.href) 1210 | vnode1.dom.onclick = function(e) { 1211 | if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return 1212 | e.preventDefault() 1213 | e.redraw = false 1214 | var href = this.getAttribute("href") 1215 | if (href.indexOf(routeService.prefix) === 0) href = href.slice(routeService.prefix.length) 1216 | route.set(href, undefined, undefined) 1217 | } 1218 | } 1219 | route.param = function(key3) { 1220 | if(typeof attrs3 !== "undefined" && typeof key3 !== "undefined") return attrs3[key3] 1221 | return attrs3 1222 | } 1223 | return route 1224 | } 1225 | m.route = _20(window, redrawService) 1226 | m.withAttr = function(attrName, callback1, context) { 1227 | return function(e) { 1228 | callback1.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName)) 1229 | } 1230 | } 1231 | var _28 = coreRenderer(window) 1232 | m.render = _28.render 1233 | m.redraw = redrawService.redraw 1234 | m.request = requestService.request 1235 | m.jsonp = requestService.jsonp 1236 | m.parseQueryString = parseQueryString 1237 | m.buildQueryString = buildQueryString 1238 | m.version = "1.1.3" 1239 | m.vnode = Vnode 1240 | if (typeof module !== "undefined") module["exports"] = m 1241 | else window.m = m 1242 | }()); -------------------------------------------------------------------------------- /examples/pickle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsguy/mithril.animate/7810d79e9c24cb89d6052dde31c68ccafd35f970/examples/pickle.jpg -------------------------------------------------------------------------------- /examples/timed.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Mithril.animate - timed animations

12 |

13 | Basic example that toggles an animation every 2 seconds 14 |

15 | 16 |
17 | 18 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mithril.animate", 3 | "version": "0.1.1", 4 | "description": "Mithril animate - bind CSS3 animations to properties on your Mithril elements.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/jsguy/mithril.animate.git" 8 | }, 9 | "dependencies": { 10 | "mithril.bindings": "git://github.com/jsguy/mithril.bindings#master" 11 | }, 12 | "devDependencies": { 13 | "grunt": "0.4.x", 14 | "grunt-cli": "0.1.9", 15 | "grunt-contrib-uglify": "0.2.x", 16 | "grunt-contrib-jshint": "0.6.x", 17 | "grunt-contrib-qunit": "0.2.x", 18 | "grunt-contrib-watch": "0.4.x", 19 | "grunt-contrib-concat": "0.3.x" 20 | }, 21 | "license": "MIT" 22 | } 23 | -------------------------------------------------------------------------------- /src/_mithril.animate.js: -------------------------------------------------------------------------------- 1 | /* 2 | mithril.animate - Copyright 2014 jsguy 3 | MIT Licensed. 4 | */ 5 | (function (m) { 6 | // Known prefiex 7 | var prefixes = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'], 8 | transitionProps = ['TransitionProperty', 'TransitionTimingFunction', 'TransitionDelay', 'TransitionDuration', 'TransitionEnd'], 9 | transformProps = ['rotate', 'rotatex', 'rotatey', 'scale', 'skew', 'translate', 'translatex', 'translatey', 'matrix'], 10 | 11 | defaultDuration = 400, 12 | 13 | // Capitalise 14 | cap = function(str){ 15 | return str.charAt(0).toUpperCase() + str.substr(1); 16 | }, 17 | 18 | // For checking what vendor prefixes are native 19 | div = document.createElement('div'), 20 | 21 | // vendor prefix, ie: transitionDuration becomes MozTransitionDuration 22 | vp = function (prop) { 23 | var pf; 24 | // Handle unprefixed 25 | if (prop in div.style) { 26 | return prop; 27 | } 28 | 29 | // Handle keyframes 30 | if(prop == "@keyframes") { 31 | for (var i = 0; i < prefixes.length; i += 1) { 32 | pf = prefixes[i] + "Transition"; 33 | if (pf in div.style) { 34 | return "@-" + prefixes[i].toLowerCase() + "-keyframes"; 35 | } 36 | } 37 | return prop; 38 | } 39 | 40 | for (var i = 0; i < prefixes.length; i += 1) { 41 | pf = prefixes[i] + cap(prop); 42 | if (pf in div.style) { 43 | return pf; 44 | } 45 | } 46 | // Can't find it - return original property. 47 | return prop; 48 | }, 49 | 50 | // See if we can use native transitions 51 | supportsTransitions = function() { 52 | var b = document.body || document.documentElement, 53 | s = b.style, 54 | p = 'transition'; 55 | 56 | if (typeof s[p] == 'string') { return true; } 57 | 58 | // Tests for vendor specific prop 59 | p = p.charAt(0).toUpperCase() + p.substr(1); 60 | 61 | for (var i=0; i