From 8bfdc407a1959842a364d979210a00cb8a13be0f Mon Sep 17 00:00:00 2001 From: Radek Stepan Date: Tue, 30 Sep 2014 21:47:41 -0700 Subject: [PATCH] upgrade ractive to deal with memory issues --- bower.json | 2 +- public/js/app.bundle.js | 2787 ++++++++++++++++++++++----------------- 2 files changed, 1572 insertions(+), 1217 deletions(-) diff --git a/bower.json b/bower.json index 3e5ce5b..b87f9eb 100644 --- a/bower.json +++ b/bower.json @@ -4,7 +4,7 @@ "dependencies": { "lodash": "2.3.0", "normalize-css": "2.1.3", - "ractive": "~0.5.5", + "ractive": "~0.6.0", "ractive-adaptor": "radekstepan/ractive-adaptor-ractive", "firebase": "~1.0.21", "firebase-simple-login": "~1.6.3", diff --git a/public/js/app.bundle.js b/public/js/app.bundle.js index 07f5ce6..00463cc 100644 --- a/public/js/app.bundle.js +++ b/public/js/app.bundle.js @@ -6794,8 +6794,8 @@ } }.call(this)); ;/* - ractive.js v0.5.8 - 2014-09-18 - commit 2e726021 + ractive.js v0.6.0 + 2014-10-08 - commit 150e82d0 http://ractivejs.org http://twitter.com/RactiveJS @@ -6998,124 +6998,132 @@ return svg; }(); - /* utils/getPotentialWildcardMatches.js */ - var getPotentialWildcardMatches = function() { + /* utils/warn.js */ + var warn = function() { - var __export; - var starMaps = {}; - // This function takes a keypath such as 'foo.bar.baz', and returns - // all the variants of that keypath that include a wildcard in place - // of a key, such as 'foo.bar.*', 'foo.*.baz', 'foo.*.*' and so on. - // These are then checked against the dependants map (ractive.viewmodel.depsMap) - // to see if any pattern observers are downstream of one or more of - // these wildcard keypaths (e.g. 'foo.bar.*.status') - __export = function getPotentialWildcardMatches( keypath ) { - var keys, starMap, mapper, i, result, wildcardKeypath; - keys = keypath.split( '.' ); - if ( !( starMap = starMaps[ keys.length ] ) ) { - starMap = getStarMap( keys.length ); - } - result = []; - mapper = function( star, i ) { - return star ? '*' : keys[ i ]; - }; - i = starMap.length; - while ( i-- ) { - wildcardKeypath = starMap[ i ].map( mapper ).join( '.' ); - if ( !result.hasOwnProperty( wildcardKeypath ) ) { - result.push( wildcardKeypath ); - result[ wildcardKeypath ] = true; - } - } - return result; - }; - // This function returns all the possible true/false combinations for - // a given number - e.g. for two, the possible combinations are - // [ true, true ], [ true, false ], [ false, true ], [ false, false ]. - // It does so by getting all the binary values between 0 and e.g. 11 - function getStarMap( num ) { - var ones = '', - max, binary, starMap, mapper, i; - if ( !starMaps[ num ] ) { - starMap = []; - while ( ones.length < num ) { - ones += 1; - } - max = parseInt( ones, 2 ); - mapper = function( digit ) { - return digit === '1'; - }; - for ( i = 0; i <= max; i += 1 ) { - binary = i.toString( 2 ); - while ( binary.length < num ) { - binary = '0' + binary; + /* global console */ + var warn, warned = {}; + if ( typeof console !== 'undefined' && typeof console.warn === 'function' && typeof console.warn.apply === 'function' ) { + warn = function( message, allowDuplicates ) { + if ( !allowDuplicates ) { + if ( warned[ message ] ) { + return; } - starMap[ i ] = Array.prototype.map.call( binary, mapper ); + warned[ message ] = true; } - starMaps[ num ] = starMap; - } - return starMaps[ num ]; + console.warn( message ); + }; + } else { + warn = function() {}; } - return __export; + return warn; }(); - /* Ractive/prototype/shared/fireEvent.js */ - var Ractive$shared_fireEvent = function( getPotentialWildcardMatches ) { + /* config/errors.js */ + var errors = { + missingParser: 'Missing Ractive.parse - cannot parse template. Either preparse or use the version that includes the parser', + mergeComparisonFail: 'Merge operation: comparison failed. Falling back to identity checking', + noComponentEventArguments: 'Components currently only support simple events - you cannot include arguments. Sorry!', + noTemplateForPartial: 'Could not find template for partial "{name}"', + noNestedPartials: 'Partials ({{>{name}}}) cannot contain nested inline partials', + evaluationError: 'Error evaluating "{uniqueString}": {err}', + badArguments: 'Bad arguments "{arguments}". I\'m not allowed to argue unless you\'ve paid.', + failedComputation: 'Failed to compute "{key}": {err}', + missingPlugin: 'Missing "{name}" {plugin} plugin. You may need to download a {plugin} via http://docs.ractivejs.org/latest/plugins#{plugin}s', + badRadioInputBinding: 'A radio input can have two-way binding on its name attribute, or its checked attribute - not both', + noRegistryFunctionReturn: 'A function was specified for "{name}" {registry}, but no {registry} was returned', + defaultElSpecified: 'The <{name}/> component has a default `el` property; it has been disregarded', + noElementProxyEventWildcards: 'Only component proxy-events may contain "*" wildcards, <{element} on-{event}/> is not valid.', + methodDepricated: 'The method "{depricated}" has been depricated in favor of "{replacement}" and will likely be removed in a future release.' + }; - var __export; - __export = function fireEvent( ractive, eventName ) { - var options = arguments[ 2 ]; - if ( options === void 0 ) - options = {}; - if ( !eventName ) { - return; + /* utils/log.js */ + var log = function( consolewarn, errors ) { + + var log = { + warn: function( options, passthru ) { + if ( !options.debug && !passthru ) { + return; + } + this.logger( getMessage( options ), options.allowDuplicates ); + }, + error: function( options ) { + this.errorOnly( options ); + if ( !options.debug ) { + this.warn( options, true ); + } + }, + errorOnly: function( options ) { + if ( options.debug ) { + this.critical( options ); + } + }, + critical: function( options ) { + var err = options.err || new Error( getMessage( options ) ); + this.thrower( err ); + }, + logger: consolewarn, + thrower: function( err ) { + throw err; } - var eventNames = getPotentialWildcardMatches( eventName ); - fireEventAs( ractive, eventNames, options.event, options.args, true ); }; - function fireEventAs( ractive, eventNames, event, args ) { - var initialFire = arguments[ 4 ]; - if ( initialFire === void 0 ) - initialFire = false; - var subscribers, i, bubble = true; - for ( i = eventNames.length; i >= 0; i-- ) { - subscribers = ractive._subs[ eventNames[ i ] ]; - if ( subscribers ) { - bubble = notifySubscribers( ractive, subscribers, event, args ) && bubble; - } - } - if ( ractive._parent && bubble ) { - if ( initialFire && ractive.component ) { - var fullName = ractive.component.name + '.' + eventNames[ eventNames.length - 1 ]; - eventNames = getPotentialWildcardMatches( fullName ); - if ( event ) { - event.component = ractive; - } - } - fireEventAs( ractive._parent, eventNames, event, args ); - } + function getMessage( options ) { + var message = errors[ options.message ] || options.message || ''; + return interpolate( message, options.args ); } + // simple interpolation. probably quicker (and better) out there, + // but log is not in golden path of execution, only exceptions + function interpolate( message, args ) { + return message.replace( /{([^{}]*)}/g, function( a, b ) { + return args[ b ]; + } ); + } + return log; + }( warn, errors ); - function notifySubscribers( ractive, subscribers, event, args ) { - var originalEvent = null, - stopEvent = false; - if ( event ) { - args = [ event ].concat( args ); + /* Ractive/prototype/shared/hooks/Hook.js */ + var Ractive$shared_hooks_Hook = function( log ) { + + var deprications = { + construct: { + depricated: 'beforeInit', + replacement: 'onconstruct' + }, + render: { + depricated: 'init', + replacement: 'onrender' + }, + complete: { + depricated: 'complete', + replacement: 'oncomplete' } - for ( var i = 0, len = subscribers.length; i < len; i += 1 ) { - if ( subscribers[ i ].apply( ractive, args ) === false ) { - stopEvent = true; + }; + + function Hook( event ) { + this.event = event; + this.method = 'on' + event; + this.depricate = deprications[ event ]; + } + Hook.prototype.fire = function( ractive, arg ) { + function call( method ) { + if ( ractive[ method ] ) { + arg ? ractive[ method ]( arg ) : ractive[ method ](); + return true; } } - if ( event && stopEvent && ( originalEvent = event.original ) ) { - originalEvent.preventDefault && originalEvent.preventDefault(); - originalEvent.stopPropagation && originalEvent.stopPropagation(); + call( this.method ); + if ( this.depricate && call( this.depricate.depricated ) ) { + log.warn( { + debug: ractive.debug, + message: 'methodDepricated', + args: this.depricate + } ); } - return !stopEvent; - } - return __export; - }( getPotentialWildcardMatches ); + arg ? ractive.fire( this.event, arg ) : ractive.fire( this.event ); + }; + return Hook; + }( log ); /* utils/removeFromArray.js */ var removeFromArray = function( array, member ) { @@ -7333,46 +7341,51 @@ }; /* shared/createComponentBinding.js */ - var createComponentBinding = function( circular, isArray, isEqual ) { + var createComponentBinding = function( circular, isEqual ) { var runloop; circular.push( function() { return runloop = circular.runloop; } ); - var Binding = function( ractive, keypath, otherInstance, otherKeypath, priority ) { + var Binding = function( ractive, keypath, otherInstance, otherKeypath ) { + var this$0 = this; this.root = ractive; this.keypath = keypath; - this.priority = priority; this.otherInstance = otherInstance; this.otherKeypath = otherKeypath; + this.unlock = function() { + return this$0.updating = false; + }; this.bind(); this.value = this.root.viewmodel.get( this.keypath ); }; Binding.prototype = { + shuffle: function( newIndices, value ) { + this.propagateChange( value, newIndices ); + }, setValue: function( value ) { - var this$0 = this; + this.propagateChange( value ); + }, + propagateChange: function( value, newIndices ) { // Only *you* can prevent infinite loops if ( this.updating || this.counterpart && this.counterpart.updating ) { this.value = value; return; } - // Is this a smart array update? If so, it'll update on its - // own, we shouldn't do anything - if ( isArray( value ) && value._ractive && value._ractive.setting ) { - return; - } if ( !isEqual( value, this.value ) ) { this.updating = true; // TODO maybe the case that `value === this.value` - should that result // in an update rather than a set? runloop.addViewmodel( this.otherInstance.viewmodel ); - this.otherInstance.viewmodel.set( this.otherKeypath, value ); + if ( newIndices ) { + this.otherInstance.viewmodel.smartUpdate( this.otherKeypath, value, newIndices ); + } else { + this.otherInstance.viewmodel.set( this.otherKeypath, value ); + } this.value = value; // TODO will the counterpart update after this line, during // the runloop end cycle? may be a problem... - runloop.scheduleTask( function() { - return this$0.updating = false; - } ); + runloop.scheduleTask( this.unlock ); } }, bind: function() { @@ -7389,7 +7402,7 @@ } }; return function createComponentBinding( component, parentInstance, parentKeypath, childKeypath ) { - var hash, childInstance, bindings, priority, parentToChildBinding, childToParentBinding; + var hash, childInstance, bindings, parentToChildBinding, childToParentBinding; hash = parentKeypath + '=' + childKeypath; bindings = component.bindings; if ( bindings[ hash ] ) { @@ -7397,18 +7410,17 @@ return; } childInstance = component.instance; - priority = component.parentFragment.priority; - parentToChildBinding = new Binding( parentInstance, parentKeypath, childInstance, childKeypath, priority ); + parentToChildBinding = new Binding( parentInstance, parentKeypath, childInstance, childKeypath ); bindings.push( parentToChildBinding ); if ( childInstance.twoway ) { - childToParentBinding = new Binding( childInstance, childKeypath, parentInstance, parentKeypath, 1 ); + childToParentBinding = new Binding( childInstance, childKeypath, parentInstance, parentKeypath ); bindings.push( childToParentBinding ); parentToChildBinding.counterpart = childToParentBinding; childToParentBinding.counterpart = parentToChildBinding; } bindings[ hash ] = parentToChildBinding; }; - }( circular, isArray, isEqual ); + }( circular, isEqual ); /* shared/resolveRef.js */ var resolveRef = function( normaliseRef, getInnerContext, createComponentBinding ) { @@ -7419,7 +7431,7 @@ getOptions = { evaluateWrapped: true }; - __export = function resolveRef( ractive, ref, fragment ) { + __export = function resolveRef( ractive, ref, fragment, isParentLookup ) { var context, key, index, keypath, parentValue, hasContextChain, parentKeys, childKeys, parentKeypath, childKeypath; ref = normaliseRef( ref ); // If a reference begins '~/', it's a top-level reference @@ -7433,6 +7445,8 @@ } // ...otherwise we need to find the keypath key = ref.split( '.' )[ 0 ]; + // get() in viewmodel creation means no fragment (yet) + fragment = fragment || {}; do { context = fragment.context; if ( !context ) { @@ -7451,6 +7465,7 @@ // If this is an inline component, and it's not isolated, we // can try going up the scope chain if ( ractive._parent && !ractive.isolated ) { + hasContextChain = true; fragment = ractive.component.parentFragment; // Special case - index refs if ( fragment.indexRefs && ( index = fragment.indexRefs[ ref ] ) !== undefined ) { @@ -7460,7 +7475,7 @@ ractive.viewmodel.set( ref, index, true ); return; } - keypath = resolveRef( ractive._parent, ref, fragment ); + keypath = resolveRef( ractive._parent, ref, fragment, true ); if ( keypath ) { // We need to create an inter-component binding // If parent keypath is 'one.foo' and child is 'two.foo', we bind @@ -7480,7 +7495,10 @@ } // If there's no context chain, and the instance is either a) isolated or // b) an orphan, then we know that the keypath is identical to the reference - if ( !hasContextChain ) { + if ( !isParentLookup && !hasContextChain ) { + // the data object needs to have a property by this name, + // to prevent future failed lookups + ractive.viewmodel.set( ref, undefined ); return ref; } if ( ractive.viewmodel.get( ref ) !== undefined ) { @@ -7601,10 +7619,11 @@ }( removeFromArray ); /* global/runloop.js */ - var runloop = function( circular, fireEvent, removeFromArray, Promise, resolveRef, TransitionManager ) { + var runloop = function( circular, Hook, removeFromArray, Promise, resolveRef, TransitionManager ) { var __export; - var batch, runloop, unresolved = []; + var batch, runloop, unresolved = [], + changeHook = new Hook( 'change' ); runloop = { start: function( instance, returnPromise ) { var promise, fulfilPromise; @@ -7673,9 +7692,7 @@ thing = batch.viewmodels[ i ]; changeHash = thing.applyChanges(); if ( changeHash ) { - fireEvent( thing.ractive, 'change', { - args: [ changeHash ] - } ); + changeHook.fire( thing.ractive, changeHash ); } } batch.viewmodels.length = 0; @@ -7725,7 +7742,7 @@ resolved.item.resolve( resolved.keypath ); } return __export; - }( circular, Ractive$shared_fireEvent, removeFromArray, Promise, resolveRef, TransitionManager ); + }( circular, Ractive$shared_hooks_Hook, removeFromArray, Promise, resolveRef, TransitionManager ); /* utils/createBranch.js */ var createBranch = function() { @@ -8154,27 +8171,6 @@ return animations; }( requestAnimationFrame, getTime, runloop ); - /* utils/warn.js */ - var warn = function() { - - /* global console */ - var warn, warned = {}; - if ( typeof console !== 'undefined' && typeof console.warn === 'function' && typeof console.warn.apply === 'function' ) { - warn = function( message, allowDuplicates ) { - if ( !allowDuplicates ) { - if ( warned[ message ] ) { - return; - } - warned[ message ] = true; - } - console.warn( message ); - }; - } else { - warn = function() {}; - } - return warn; - }(); - /* config/options/css/transform.js */ var transform = function() { @@ -8310,6 +8306,9 @@ function combine( Parent, target, options ) { var value = options.data || {}, parentValue = getAddedKeys( Parent.prototype.data ); + if ( typeof value !== 'object' && typeof value !== 'function' ) { + throw new TypeError( 'data option must be an object or a function, "' + value + '" is not valid' ); + } return dispatch( parentValue, value ); } @@ -8425,23 +8424,6 @@ return __export; }( wrapMethod ); - /* config/errors.js */ - var errors = { - missingParser: 'Missing Ractive.parse - cannot parse template. Either preparse or use the version that includes the parser', - mergeComparisonFail: 'Merge operation: comparison failed. Falling back to identity checking', - noComponentEventArguments: 'Components currently only support simple events - you cannot include arguments. Sorry!', - noTemplateForPartial: 'Could not find template for partial "{name}"', - noNestedPartials: 'Partials ({{>{name}}}) cannot contain nested inline partials', - evaluationError: 'Error evaluating "{uniqueString}": {err}', - badArguments: 'Bad arguments "{arguments}". I\'m not allowed to argue unless you\'ve paid.', - failedComputation: 'Failed to compute "{key}": {err}', - missingPlugin: 'Missing "{name}" {plugin} plugin. You may need to download a {plugin} via http://docs.ractivejs.org/latest/plugins#{plugin}s', - badRadioInputBinding: 'A radio input can have two-way binding on its name attribute, or its checked attribute - not both', - noRegistryFunctionReturn: 'A function was specified for "{name}" {registry}, but no {registry} was returned', - defaultElSpecified: 'The <{name}/> component has a default `el` property; it has been disregarded', - noElementProxyEventWildcards: 'Only component proxy-events may contain "*" wildcards, <{element} on-{event}/> is not valid.' - }; - /* config/types.js */ var types = { TEXT: 1, @@ -8477,7 +8459,8 @@ SECTION_IF: 50, SECTION_UNLESS: 51, SECTION_EACH: 52, - SECTION_WITH: 53 + SECTION_WITH: 53, + SECTION_IF_WITH: 54 }; /* utils/create.js */ @@ -9304,7 +9287,8 @@ columnNum = char - lineStart; return [ lineNum + 1, - columnNum + 1 + columnNum + 1, + char ]; }, error: function( message ) { @@ -9446,10 +9430,11 @@ var handlebarsBlockCodes = function( types ) { return { + 'each': types.SECTION_EACH, 'if': types.SECTION_IF, - 'unless': types.SECTION_UNLESS, + 'if-with': types.SECTION_IF_WITH, 'with': types.SECTION_WITH, - 'each': types.SECTION_EACH + 'unless': types.SECTION_UNLESS }; }( types ); @@ -9717,16 +9702,12 @@ } // {{else}} tags require special treatment if ( child.t === types.INTERPOLATOR && child.r === 'else' ) { - switch ( mustache.n ) { - case 'unless': - parser.error( '{{else}} not allowed in {{#unless}}' ); - break; - case 'with': - parser.error( '{{else}} not allowed in {{#with}}' ); - break; - default: - currentChildren = elseChildren = []; - continue; + // no {{else}} allowed in {{#unless}} + if ( mustache.n === 'unless' ) { + parser.error( '{{else}} not allowed in {{#unless}}' ); + } else { + currentChildren = elseChildren = []; + continue; } } currentChildren.push( child ); @@ -9741,6 +9722,9 @@ } if ( elseChildren && elseChildren.length ) { mustache.l = elseChildren; + if ( mustache.n === 'with' ) { + mustache.n = 'if-with'; + } } } if ( parser.includeLinePositions ) { @@ -10670,7 +10654,7 @@ validTagNameFollower = /^[\s\n\/>]/, onPattern = /^on/, proxyEventPattern = /^on-([a-zA-Z\\*\\.$_][a-zA-Z\\*\\.$_0-9\-]+)$/, - reservedEventNames = /^(?:change|reset|teardown|update)$/, + reservedEventNames = /^(?:change|reset|teardown|update|construct|config|init|render|unrender|detach|insert)$/, directives = { 'intro-outro': 't0', intro: 't1', @@ -10775,27 +10759,36 @@ var directiveName = directive.n || directive; if ( reservedEventNames.test( directiveName ) ) { parser.pos -= directiveName.length; - parser.error( 'Cannot use reserved event names (change, reset, teardown, update)' ); + parser.error( 'Cannot use reserved event names (change, reset, teardown, update, construct, config, init, render, unrender, detach, insert)' ); } element.v[ name ] = directive; }; + parser.allowWhitespace(); // directives and attributes - while ( attribute = getAttribute( parser ) ) { - // intro, outro, decorator - if ( directiveName = directives[ attribute.name ] ) { - element[ directiveName ] = processDirective( attribute.value ); - } else if ( match = proxyEventPattern.exec( attribute.name ) ) { - if ( !element.v ) - element.v = {}; - directive = processDirective( attribute.value ); - addProxyEvent( match[ 1 ], directive ); - } else { - if ( !parser.sanitizeEventAttributes || !onPattern.test( attribute.name ) ) { - if ( !element.a ) - element.a = {}; - element.a[ attribute.name ] = attribute.value || 0; + while ( attribute = getMustache( parser ) || getAttribute( parser ) ) { + // regular attributes + if ( attribute.name ) { + // intro, outro, decorator + if ( directiveName = directives[ attribute.name ] ) { + element[ directiveName ] = processDirective( attribute.value ); + } else if ( match = proxyEventPattern.exec( attribute.name ) ) { + if ( !element.v ) + element.v = {}; + directive = processDirective( attribute.value ); + addProxyEvent( match[ 1 ], directive ); + } else { + if ( !parser.sanitizeEventAttributes || !onPattern.test( attribute.name ) ) { + if ( !element.a ) + element.a = {}; + element.a[ attribute.name ] = attribute.value || 0; + } } + } else { + if ( !element.m ) + element.m = []; + element.m.push( attribute ); } + parser.allowWhitespace(); } // allow whitespace before closing solidus parser.allowWhitespace(); @@ -11520,9 +11513,18 @@ }( warn, isArray ); /* config/config.js */ - var config = function( css, data, defaults, template, parseOptions, registries, wrap, deprecate ) { + var config = function( css, data, defaults, template, parseOptions, registries, wrapPrototype, deprecate ) { - var custom, options, config; + var custom, options, config, blacklisted; + // would be nice to not have these here, + // they get added during initialise, so for now we have + // to make sure not to try and extend them. + // Possibly, we could re-order and not add till later + // in process. + blacklisted = { + '_parent': true, + '_component': true + }; custom = { data: data, template: template, @@ -11540,6 +11542,10 @@ config.keys = Object.keys( defaults ).concat( registries.map( function( r ) { return r.name; } ) ).concat( [ 'css' ] ); + // add these to blacklisted key's that we don't double extend + config.keys.forEach( function( key ) { + return blacklisted[ key ] = true; + } ); config.parseOptions = parseOptions; config.registries = registries; @@ -11551,11 +11557,12 @@ }; config.init = function( Parent, ractive, options ) { configure( 'init', Parent, ractive, options ); - if ( ractive._config ) { - ractive._config.options = options; - } }; + function isStandardDefaultKey( key ) { + return key in defaults && !( key in config.parseOptions ) && !( key in custom ); + } + function configure( method, Parent, instance, options ) { deprecate( options ); customConfig( method, 'data', Parent, instance, options ); @@ -11565,9 +11572,9 @@ } } ); for ( var key in options ) { - if ( key in defaults && !( key in config.parseOptions ) && !( key in custom ) ) { + if ( isStandardDefaultKey( key ) ) { var value = options[ key ]; - instance[ key ] = typeof value === 'function' ? wrap( Parent.prototype, key, value ) : value; + instance[ key ] = typeof value === 'function' ? wrapPrototype( Parent.prototype, key, value ) : value; } } config.registries.forEach( function( registry ) { @@ -11575,6 +11582,20 @@ } ); customConfig( method, 'template', Parent, instance, options ); customConfig( method, 'css', Parent, instance, options ); + extendOtherMethods( Parent.prototype, instance, options ); + } + + function extendOtherMethods( parent, instance, options ) { + for ( var key in options ) { + if ( !( key in blacklisted ) && options.hasOwnProperty( key ) ) { + var member = options[ key ]; + // if this is a method that overwrites a method, wrap it: + if ( typeof member === 'function' ) { + member = wrapPrototype( parent, key, member ); + } + instance[ key ] = member; + } + } } config.reset = function( ractive ) { return config.filter( function( c ) { @@ -11583,6 +11604,17 @@ return c.name; } ); }; + config.getConstructTarget = function( ractive, options ) { + if ( options.onconstruct ) { + // pretend this object literal is the ractive instance + return { + onconstruct: wrapPrototype( ractive, 'onconstruct', options.onconstruct ).bind( ractive ), + fire: ractive.fire.bind( ractive ) + }; + } else { + return ractive; + } + }; return config; }( css, data, options, template, parseOptions, registries, wrapPrototypeMethod, deprecate ); @@ -11829,8 +11861,9 @@ }( isEqual, Promise, normaliseKeypath, animations, Ractive$animate_Animation ); /* Ractive/prototype/detach.js */ - var Ractive$detach = function( removeFromArray ) { + var Ractive$detach = function( Hook, removeFromArray ) { + var detachHook = new Hook( 'detach' ); return function Ractive$detach() { if ( this.detached ) { return this.detached; @@ -11839,9 +11872,10 @@ removeFromArray( this.el.__ractive_instances__, this ); } this.detached = this.fragment.detach(); + detachHook.fire( this ); return this.detached; }; - }( removeFromArray ); + }( Ractive$shared_hooks_Hook, removeFromArray ); /* Ractive/prototype/find.js */ var Ractive$find = function Ractive$find( selector ) { @@ -12155,6 +12189,142 @@ return this.fragment.findComponent( selector ); }; + /* utils/getPotentialWildcardMatches.js */ + var getPotentialWildcardMatches = function() { + + var __export; + var starMaps = {}; + // This function takes a keypath such as 'foo.bar.baz', and returns + // all the variants of that keypath that include a wildcard in place + // of a key, such as 'foo.bar.*', 'foo.*.baz', 'foo.*.*' and so on. + // These are then checked against the dependants map (ractive.viewmodel.depsMap) + // to see if any pattern observers are downstream of one or more of + // these wildcard keypaths (e.g. 'foo.bar.*.status') + __export = function getPotentialWildcardMatches( keypath ) { + var keys, starMap, mapper, i, result, wildcardKeypath; + keys = keypath.split( '.' ); + if ( !( starMap = starMaps[ keys.length ] ) ) { + starMap = getStarMap( keys.length ); + } + result = []; + mapper = function( star, i ) { + return star ? '*' : keys[ i ]; + }; + i = starMap.length; + while ( i-- ) { + wildcardKeypath = starMap[ i ].map( mapper ).join( '.' ); + if ( !result.hasOwnProperty( wildcardKeypath ) ) { + result.push( wildcardKeypath ); + result[ wildcardKeypath ] = true; + } + } + return result; + }; + // This function returns all the possible true/false combinations for + // a given number - e.g. for two, the possible combinations are + // [ true, true ], [ true, false ], [ false, true ], [ false, false ]. + // It does so by getting all the binary values between 0 and e.g. 11 + function getStarMap( num ) { + var ones = '', + max, binary, starMap, mapper, i; + if ( !starMaps[ num ] ) { + starMap = []; + while ( ones.length < num ) { + ones += 1; + } + max = parseInt( ones, 2 ); + mapper = function( digit ) { + return digit === '1'; + }; + for ( i = 0; i <= max; i += 1 ) { + binary = i.toString( 2 ); + while ( binary.length < num ) { + binary = '0' + binary; + } + starMap[ i ] = Array.prototype.map.call( binary, mapper ); + } + starMaps[ num ] = starMap; + } + return starMaps[ num ]; + } + return __export; + }(); + + /* Ractive/prototype/shared/fireEvent.js */ + var Ractive$shared_fireEvent = function( getPotentialWildcardMatches ) { + + var __export; + __export = function fireEvent( ractive, eventName ) { + var options = arguments[ 2 ]; + if ( options === void 0 ) + options = {}; + if ( !eventName ) { + return; + } + if ( !options.event ) { + options.event = { + name: eventName, + context: ractive.data, + keypath: '', + // until event not included as argument default + _noArg: true + }; + } else { + options.event.name = eventName; + } + var eventNames = getPotentialWildcardMatches( eventName ); + fireEventAs( ractive, eventNames, options.event, options.args, true ); + }; + + function fireEventAs( ractive, eventNames, event, args ) { + var initialFire = arguments[ 4 ]; + if ( initialFire === void 0 ) + initialFire = false; + var subscribers, i, bubble = true; + if ( event ) { + ractive.event = event; + } + for ( i = eventNames.length; i >= 0; i-- ) { + subscribers = ractive._subs[ eventNames[ i ] ]; + if ( subscribers ) { + bubble = notifySubscribers( ractive, subscribers, event, args ) && bubble; + } + } + if ( event ) { + delete ractive.event; + } + if ( ractive._parent && bubble ) { + if ( initialFire && ractive.component ) { + var fullName = ractive.component.name + '.' + eventNames[ eventNames.length - 1 ]; + eventNames = getPotentialWildcardMatches( fullName ); + if ( event ) { + event.component = ractive; + } + } + fireEventAs( ractive._parent, eventNames, event, args ); + } + } + + function notifySubscribers( ractive, subscribers, event, args ) { + var originalEvent = null, + stopEvent = false; + if ( event && !event._noArg ) { + args = [ event ].concat( args ); + } + for ( var i = 0, len = subscribers.length; i < len; i += 1 ) { + if ( subscribers[ i ].apply( ractive, args ) === false ) { + stopEvent = true; + } + } + if ( event && !event._noArg && stopEvent && ( originalEvent = event.original ) ) { + originalEvent.preventDefault && originalEvent.preventDefault(); + originalEvent.stopPropagation && originalEvent.stopPropagation(); + } + return !stopEvent; + } + return __export; + }( getPotentialWildcardMatches ); + /* Ractive/prototype/fire.js */ var Ractive$fire = function( fireEvent ) { @@ -12167,17 +12337,26 @@ }( Ractive$shared_fireEvent ); /* Ractive/prototype/get.js */ - var Ractive$get = function( normaliseKeypath ) { + var Ractive$get = function( normaliseKeypath, resolveRef ) { var options = { capture: true }; // top-level calls should be intercepted return function Ractive$get( keypath ) { + var value; keypath = normaliseKeypath( keypath ); - return this.viewmodel.get( keypath, options ); + value = this.viewmodel.get( keypath, options ); + // Create inter-component binding, if necessary + if ( value === undefined && this._parent && !this.isolated ) { + if ( resolveRef( this, keypath, this.fragment ) ) { + // creates binding as side-effect, if appropriate + value = this.viewmodel.get( keypath ); + } + } + return value; }; - }( normaliseKeypath ); + }( normaliseKeypath, resolveRef ); /* utils/getElement.js */ var getElement = function getElement( input ) { @@ -12213,10 +12392,12 @@ }; /* Ractive/prototype/insert.js */ - var Ractive$insert = function( getElement ) { + var Ractive$insert = function( Hook, getElement ) { - return function Ractive$insert( target, anchor ) { - if ( !this.rendered ) { + var __export; + var insertHook = new Hook( 'insert' ); + __export = function Ractive$insert( target, anchor ) { + if ( !this.fragment.rendered ) { // TODO create, and link to, documentation explaining this throw new Error( 'The API has changed - you must call `ractive.render(target[, anchor])` to render your Ractive instance. Once rendered you can use `ractive.insert()`.' ); } @@ -12229,8 +12410,17 @@ this.el = target; ( target.__ractive_instances__ || ( target.__ractive_instances__ = [] ) ).push( this ); this.detached = null; + fireInsertHook( this ); }; - }( getElement ); + + function fireInsertHook( ractive ) { + insertHook.fire( ractive ); + ractive.findAllComponents( '*' ).forEach( function( child ) { + fireInsertHook( child.instance ); + } ); + } + return __export; + }( Ractive$shared_hooks_Hook, getElement ); /* Ractive/prototype/merge.js */ var Ractive$merge = function( runloop, isArray, normaliseKeypath ) { @@ -12264,15 +12454,12 @@ this.keypath = keypath; this.callback = callback; this.defer = options.defer; - // Observers are notified before any DOM changes take place (though - // they can defer execution until afterwards) - this.priority = 0; // default to root as context, but allow it to be overridden this.context = options && options.context ? options.context : ractive; }; Observer.prototype = { init: function( immediate ) { - this.value = this.root.viewmodel.get( this.keypath ); + this.value = this.root.get( this.keypath ); if ( immediate !== false ) { this.update(); } else { @@ -12378,9 +12565,6 @@ if ( this.defer ) { this.proxies = []; } - // Observers are notified before any DOM changes take place (though - // they can defer execution until afterwards) - this.priority = 'pattern'; // default to root as context, but allow it to be overridden this.context = options && options.context ? options.context : ractive; }; @@ -12639,112 +12823,130 @@ }; }( Ractive$shared_trim, Ractive$shared_notEmptyString ); - /* shared/getSpliceEquivalent.js */ - var getSpliceEquivalent = function( array, methodName, args ) { - switch ( methodName ) { - case 'splice': - return args; - case 'sort': - case 'reverse': - return null; - case 'pop': - if ( array.length ) { - return [ -1 ]; - } - return null; - case 'push': - return [ - array.length, - 0 - ].concat( args ); - case 'shift': - return [ - 0, - 1 - ]; - case 'unshift': - return [ - 0, - 0 - ].concat( args ); - } - }; + /* shared/getNewIndices.js */ + var getNewIndices = function() { - /* shared/summariseSpliceOperation.js */ - var summariseSpliceOperation = function( array, args ) { - var rangeStart, rangeEnd, newLength, addedItems, removedItems, balance; - if ( !args ) { - return null; - } - // figure out where the changes started... - rangeStart = +( args[ 0 ] < 0 ? array.length + args[ 0 ] : args[ 0 ] ); - // make sure we don't get out of bounds... - if ( rangeStart < 0 ) { - rangeStart = 0; - } else if ( rangeStart > array.length ) { - rangeStart = array.length; - } - // ...and how many items were added to or removed from the array - addedItems = Math.max( 0, args.length - 2 ); - removedItems = args[ 1 ] !== undefined ? args[ 1 ] : array.length - rangeStart; - // It's possible to do e.g. [ 1, 2, 3 ].splice( 2, 2 ) - i.e. the second argument - // means removing more items from the end of the array than there are. In these - // cases we need to curb JavaScript's enthusiasm or we'll get out of sync - removedItems = Math.min( removedItems, array.length - rangeStart ); - balance = addedItems - removedItems; - newLength = array.length + balance; - // We need to find the end of the range affected by the splice - if ( !balance ) { - rangeEnd = rangeStart + addedItems; - } else { - rangeEnd = Math.max( array.length, newLength ); - } - return { - rangeStart: rangeStart, - rangeEnd: rangeEnd, - balance: balance, - added: addedItems, - removed: removedItems + var __export; + // This function takes an array, the name of a mutator method, and the + // arguments to call that mutator method with, and returns an array that + // maps the old indices to their new indices. + // So if you had something like this... + // + // array = [ 'a', 'b', 'c', 'd' ]; + // array.push( 'e' ); + // + // ...you'd get `[ 0, 1, 2, 3 ]` - in other words, none of the old indices + // have changed. If you then did this... + // + // array.unshift( 'z' ); + // + // ...the indices would be `[ 1, 2, 3, 4, 5 ]` - every item has been moved + // one higher to make room for the 'z'. If you removed an item, the new index + // would be -1... + // + // array.splice( 2, 2 ); + // + // ...this would result in [ 0, 1, -1, -1, 2, 3 ]. + // + // This information is used to enable fast, non-destructive shuffling of list + // sections when you do e.g. `ractive.splice( 'items', 2, 2 ); + __export = function getNewIndices( array, methodName, args ) { + var spliceArguments, len, newIndices = [], + removeStart, removeEnd, balance, i; + spliceArguments = getSpliceEquivalent( array, methodName, args ); + if ( !spliceArguments ) { + return null; + } + len = array.length; + balance = spliceArguments.length - 2 - spliceArguments[ 1 ]; + removeStart = Math.min( len, spliceArguments[ 0 ] ); + removeEnd = removeStart + spliceArguments[ 1 ]; + for ( i = 0; i < removeStart; i += 1 ) { + newIndices.push( i ); + } + for ( ; i < removeEnd; i += 1 ) { + newIndices.push( -1 ); + } + for ( ; i < len; i += 1 ) { + newIndices.push( i + balance ); + } + return newIndices; }; - }; + // The pop, push, shift an unshift methods can all be represented + // as an equivalent splice + function getSpliceEquivalent( array, methodName, args ) { + switch ( methodName ) { + case 'splice': + if ( args[ 0 ] !== undefined && args[ 0 ] < 0 ) { + args[ 0 ] = array.length + Math.max( args[ 0 ], -array.length ); + } + while ( args.length < 2 ) { + args.push( 0 ); + } + // ensure we only remove elements that exist + args[ 1 ] = Math.min( args[ 1 ], array.length - args[ 0 ] ); + return args; + case 'sort': + case 'reverse': + return null; + case 'pop': + if ( array.length ) { + return [ + array.length - 1, + 1 + ]; + } + return null; + case 'push': + return [ + array.length, + 0 + ].concat( args ); + case 'shift': + return [ + 0, + 1 + ]; + case 'unshift': + return [ + 0, + 0 + ].concat( args ); + } + } + return __export; + }(); /* Ractive/prototype/shared/makeArrayMethod.js */ - var Ractive$shared_makeArrayMethod = function( isArray, runloop, getSpliceEquivalent, summariseSpliceOperation ) { + var Ractive$shared_makeArrayMethod = function( isArray, runloop, getNewIndices ) { var arrayProto = Array.prototype; return function( methodName ) { return function( keypath ) { var SLICE$0 = Array.prototype.slice; var args = SLICE$0.call( arguments, 1 ); - var array, spliceEquivalent, spliceSummary, promise, change; + var array, newIndices = [], + len, promise, result; array = this.get( keypath ); + len = array.length; if ( !isArray( array ) ) { throw new Error( 'Called ractive.' + methodName + '(\'' + keypath + '\'), but \'' + keypath + '\' does not refer to an array' ); } - spliceEquivalent = getSpliceEquivalent( array, methodName, args ); - spliceSummary = summariseSpliceOperation( array, spliceEquivalent ); - if ( spliceSummary ) { - change = arrayProto.splice.apply( array, spliceEquivalent ); - } else { - change = arrayProto[ methodName ].apply( array, args ); - } - promise = runloop.start( this, true ); - if ( spliceSummary ) { - this.viewmodel.splice( keypath, spliceSummary ); + newIndices = getNewIndices( array, methodName, args ); + result = arrayProto[ methodName ].apply( array, args ); + promise = runloop.start( this, true ).then( function() { + return result; + } ); + if ( !!newIndices ) { + this.viewmodel.smartUpdate( keypath, array, newIndices ); } else { this.viewmodel.mark( keypath ); } runloop.end(); - // resolve the promise with removals if applicable - if ( methodName === 'splice' || methodName === 'pop' || methodName === 'shift' ) { - promise = promise.then( function() { - return change; - } ); - } return promise; }; }; - }( isArray, runloop, getSpliceEquivalent, summariseSpliceOperation ); + }( isArray, runloop, getNewIndices ); /* Ractive/prototype/pop.js */ var Ractive$pop = function( makeArrayMethod ) { @@ -12806,7 +13008,7 @@ // removed, the style is too componentsInPage[ Component._guid ] = 0; styles.push( Component.css ); - runloop.scheduleTask( update ); + update(); } componentsInPage[ Component._guid ] += 1; }, @@ -12825,23 +13027,65 @@ return css; }( circular, isClient, removeFromArray ); - /* Ractive/prototype/render.js */ - var Ractive$render = function( runloop, css, getElement ) { + /* Ractive/prototype/shared/hooks/HookQueue.js */ + var Ractive$shared_hooks_HookQueue = function( Hook ) { - var __export; - var queues = {}, - rendering = {}; - __export = function Ractive$render( target, anchor ) { + function HookQueue( event ) { + this.hook = new Hook( event ); + this.inProcess = {}; + this.queue = {}; + } + HookQueue.prototype = { + constructor: HookQueue, + begin: function( ractive ) { + this.inProcess[ ractive._guid ] = true; + }, + end: function( ractive ) { + var parent = ractive._parent; + // If this is *isn't* a child of a component that's in process, + // it should call methods or fire at this point + if ( !parent || !this.inProcess[ parent._guid ] ) { + fire( this, ractive ); + } else { + getChildQueue( this.queue, parent ).push( ractive ); + } + delete this.inProcess[ ractive._guid ]; + } + }; + + function getChildQueue( queue, ractive ) { + return queue[ ractive._guid ] || ( queue[ ractive._guid ] = [] ); + } + + function fire( hookQueue, ractive ) { + var childQueue = getChildQueue( hookQueue.queue, ractive ); + hookQueue.hook.fire( ractive ); + // queue is "live" because components can end up being + // added while hooks fire on parents that modify data values. + while ( childQueue.length ) { + fire( hookQueue, childQueue.shift() ); + } + delete hookQueue.queue[ ractive._guid ]; + } + return HookQueue; + }( Ractive$shared_hooks_Hook ); + + /* Ractive/prototype/render.js */ + var Ractive$render = function( css, Hook, HookQueue, getElement, runloop ) { + + var renderHook = new HookQueue( 'render' ), + completeHook = new Hook( 'complete' ); + return function Ractive$render( target, anchor ) { var this$0 = this; var promise, instances, transitionsEnabled; - rendering[ this._guid ] = true; + renderHook.begin( this ); // if `noIntro` is `true`, temporarily disable transitions transitionsEnabled = this.transitionsEnabled; if ( this.noIntro ) { this.transitionsEnabled = false; } promise = runloop.start( this, true ); - if ( this.rendered ) { + if ( this.fragment.rendered ) { throw new Error( 'You cannot call ractive.render() on an already rendered instance! Call ractive.unrender() first' ); } target = getElement( target ) || this.el; @@ -12864,45 +13108,20 @@ target.appendChild( this.fragment.render() ); } } - // Only init once, until we rework lifecycle events - if ( !this._hasInited ) { - this._hasInited = true; - // If this is *isn't* a child of a component that's in the process of rendering, - // it should call any `init()` methods at this point - if ( !this._parent || !rendering[ this._parent._guid ] ) { - init( this ); - } else { - getChildInitQueue( this._parent ).push( this ); - } - } - rendering[ this._guid ] = false; + renderHook.end( this ); runloop.end(); - this.rendered = true; this.transitionsEnabled = transitionsEnabled; - if ( this.complete ) { - promise.then( function() { - return this$0.complete(); - } ); - } + // It is now more problematic to know if the complete hook + // would fire. Method checking is straight-forward, but would + // also require preflighting event subscriptions. Which seems + // like more work then just letting the promise happen. + // But perhaps I'm wrong about that... + promise.then( function() { + return completeHook.fire( this$0 ); + } ); return promise; }; - - function init( instance ) { - var childQueue = getChildInitQueue( instance ); - if ( instance.init ) { - instance.init( instance._config.options ); - } - while ( childQueue.length ) { - init( childQueue.shift() ); - } - queues[ instance._guid ] = null; - } - - function getChildInitQueue( instance ) { - return queues[ instance._guid ] || ( queues[ instance._guid ] = [] ); - } - return __export; - }( runloop, global_css, getElement ); + }( global_css, Ractive$shared_hooks_Hook, Ractive$shared_hooks_HookQueue, getElement, runloop ); /* virtualdom/Fragment/prototype/bubble.js */ var virtualdom_Fragment$bubble = function Fragment$bubble() { @@ -12920,7 +13139,15 @@ } docFrag = document.createDocumentFragment(); this.items.forEach( function( item ) { - docFrag.appendChild( item.detach() ); + var node = item.detach(); + // TODO The if {...} wasn't previously required - it is now, because we're + // forcibly detaching everything to reorder sections after an update. That's + // a non-ideal brute force approach, implemented to get all the tests to pass + // - as soon as it's replaced with something more elegant, this should + // revert to `docFrag.appendChild( item.detach() )` + if ( node ) { + docFrag.appendChild( node ); + } } ); return docFrag; }; @@ -13192,51 +13419,6 @@ }; }( startsWithKeypath ); - /* utils/log.js */ - var log = function( consolewarn, errors ) { - - var log = { - warn: function( options, passthru ) { - if ( !options.debug && !passthru ) { - return; - } - this.logger( getMessage( options ), options.allowDuplicates ); - }, - error: function( options ) { - this.errorOnly( options ); - if ( !options.debug ) { - this.warn( options, true ); - } - }, - errorOnly: function( options ) { - if ( options.debug ) { - this.critical( options ); - } - }, - critical: function( options ) { - var err = options.err || new Error( getMessage( options ) ); - this.thrower( err ); - }, - logger: consolewarn, - thrower: function( err ) { - throw err; - } - }; - - function getMessage( options ) { - var message = errors[ options.message ] || options.message || ''; - return interpolate( message, options.args ); - } - // simple interpolation. probably quicker (and better) out there, - // but log is not in golden path of execution, only exceptions - function interpolate( message, args ) { - return message.replace( /{([^{}]*)}/g, function( a, b ) { - return args[ b ]; - } ); - } - return log; - }( warn, errors ); - /* shared/getFunctionFromString.js */ var getFunctionFromString = function() { @@ -13256,156 +13438,12 @@ }; }(); - /* viewmodel/Computation/diff.js */ - var diff = function diff( computation, dependencies, newDependencies ) { - var i, keypath; - // remove dependencies that are no longer used - i = dependencies.length; - while ( i-- ) { - keypath = dependencies[ i ]; - if ( newDependencies.indexOf( keypath ) === -1 ) { - computation.viewmodel.unregister( keypath, computation, 'computed' ); - } - } - // create references for any new dependencies - i = newDependencies.length; - while ( i-- ) { - keypath = newDependencies[ i ]; - if ( dependencies.indexOf( keypath ) === -1 ) { - computation.viewmodel.register( keypath, computation, 'computed' ); - } - } - computation.dependencies = newDependencies.slice(); - }; - - /* virtualdom/items/shared/Evaluator/Evaluator.js */ - var Evaluator = function( log, isEqual, defineProperty, getFunctionFromString, diff ) { - - var __export; - var Evaluator, bind = Function.prototype.bind; - Evaluator = function( root, keypath, uniqueString, functionStr, args, priority ) { - var evaluator = this, - viewmodel = root.viewmodel; - evaluator.root = root; - evaluator.viewmodel = viewmodel; - evaluator.uniqueString = uniqueString; - evaluator.keypath = keypath; - evaluator.priority = priority; - evaluator.fn = getFunctionFromString( functionStr, args.length ); - evaluator.explicitDependencies = []; - evaluator.dependencies = []; - // created by `this.get()` within functions - evaluator.argumentGetters = args.map( function( arg ) { - var keypath, index; - if ( !arg ) { - return void 0; - } - if ( arg.indexRef ) { - index = arg.value; - return index; - } - keypath = arg.keypath; - evaluator.explicitDependencies.push( keypath ); - viewmodel.register( keypath, evaluator, 'computed' ); - return function() { - var value = viewmodel.get( keypath ); - return typeof value === 'function' ? wrap( value, root ) : value; - }; - } ); - }; - Evaluator.prototype = { - wake: function() { - this.awake = true; - }, - sleep: function() { - this.awake = false; - }, - getValue: function() { - var args, value, newImplicitDependencies; - args = this.argumentGetters.map( call ); - if ( this.updating ) { - // Prevent infinite loops caused by e.g. in-place array mutations - return; - } - this.updating = true; - this.viewmodel.capture(); - try { - value = this.fn.apply( null, args ); - } catch ( err ) { - if ( this.root.debug ) { - log.warn( { - debug: this.root.debug, - message: 'evaluationError', - args: { - uniqueString: this.uniqueString, - err: err.message || err - } - } ); - } - value = undefined; - } - newImplicitDependencies = this.viewmodel.release(); - diff( this, this.dependencies, newImplicitDependencies ); - this.updating = false; - return value; - }, - update: function() { - var value = this.getValue(); - if ( !isEqual( value, this.value ) ) { - this.value = value; - this.root.viewmodel.mark( this.keypath ); - } - return this; - }, - // TODO should evaluators ever get torn down? At present, they don't... - teardown: function() { - var this$0 = this; - this.explicitDependencies.concat( this.dependencies ).forEach( function( keypath ) { - return this$0.viewmodel.unregister( keypath, this$0, 'computed' ); - } ); - this.root.viewmodel.evaluators[ this.keypath ] = null; - } - }; - __export = Evaluator; - - function wrap( fn, ractive ) { - var wrapped, prop, key; - if ( fn._noWrap ) { - return fn; - } - prop = '__ractive_' + ractive._guid; - wrapped = fn[ prop ]; - if ( wrapped ) { - return wrapped; - } else if ( /this/.test( fn.toString() ) ) { - defineProperty( fn, prop, { - value: bind.call( fn, ractive ) - } ); - // Add properties/methods to wrapped function - for ( key in fn ) { - if ( fn.hasOwnProperty( key ) ) { - fn[ prop ][ key ] = fn[ key ]; - } - } - return fn[ prop ]; - } - defineProperty( fn, '__ractive_nowrap', { - value: fn - } ); - return fn.__ractive_nowrap; - } - - function call( arg ) { - return typeof arg === 'function' ? arg() : arg; - } - return __export; - }( log, isEqual, defineProperty, getFunctionFromString, diff, legacy ); - /* virtualdom/items/shared/Resolvers/ExpressionResolver.js */ - var ExpressionResolver = function( removeFromArray, resolveRef, Unresolved, Evaluator, getNewKeypath ) { + var ExpressionResolver = function( removeFromArray, defineProperty, resolveRef, Unresolved, getFunctionFromString, getNewKeypath ) { var __export; - var ExpressionResolver = function( owner, parentFragment, expression, callback ) { + var ExpressionResolver, bind = Function.prototype.bind; + ExpressionResolver = function( owner, parentFragment, expression, callback ) { var expressionResolver = this, ractive, indexRefs, args; ractive = owner.root; @@ -13485,13 +13523,54 @@ this.resolved = !--this.pending; }, createEvaluator: function() { - var evaluator = this.root.viewmodel.evaluators[ this.keypath ]; + var this$0 = this; + var self = this, + computation, valueGetters, signature, keypaths = [], + i, arg, fn; + computation = this.root.viewmodel.computations[ this.keypath ]; // only if it doesn't exist yet! - if ( !evaluator ) { - evaluator = new Evaluator( this.root, this.keypath, this.uniqueString, this.str, this.args, this.owner.priority ); - this.root.viewmodel.evaluators[ this.keypath ] = evaluator; + if ( !computation ) { + i = this.args.length; + while ( i-- ) { + arg = this.args[ i ]; + if ( arg && arg.keypath ) { + keypaths.push( arg.keypath ); + } + } + fn = getFunctionFromString( this.str, this.args.length ); + valueGetters = this.args.map( function( arg ) { + var keypath, value; + if ( !arg ) { + return function() { + return undefined; + }; + } + if ( arg.indexRef ) { + value = arg.value; + return function() { + return value; + }; + } + keypath = arg.keypath; + return function() { + var value = this$0.root.viewmodel.get( keypath ); + if ( typeof value === 'function' ) { + value = wrapFunction( value, self.root ); + } + return value; + }; + } ); + signature = { + deps: keypaths, + get: function() { + var args = valueGetters.map( call ); + return fn.apply( null, args ); + } + }; + computation = this.root.viewmodel.compute( this.keypath, signature ); + } else { + this.root.viewmodel.mark( this.keypath ); } - evaluator.update(); }, rebind: function( indexRef, newIndex, oldKeypath, newKeypath ) { var changed; @@ -13514,6 +13593,10 @@ }; __export = ExpressionResolver; + function call( value ) { + return value.call(); + } + function getUniqueString( str, args ) { // get string that is unique to this expression return str.replace( /_([0-9]+)/g, function( match, $1 ) { @@ -13531,8 +13614,35 @@ // we can't split the keypath into keys! return '${' + uniqueString.replace( /[\.\[\]]/g, '-' ) + '}'; } + + function wrapFunction( fn, ractive ) { + var wrapped, prop, key; + if ( fn._noWrap ) { + return fn; + } + prop = '__ractive_' + ractive._guid; + wrapped = fn[ prop ]; + if ( wrapped ) { + return wrapped; + } else if ( /this/.test( fn.toString() ) ) { + defineProperty( fn, prop, { + value: bind.call( fn, ractive ) + } ); + // Add properties/methods to wrapped function + for ( key in fn ) { + if ( fn.hasOwnProperty( key ) ) { + fn[ prop ][ key ] = fn[ key ]; + } + } + return fn[ prop ]; + } + defineProperty( fn, '__ractive_nowrap', { + value: fn + } ); + return fn.__ractive_nowrap; + } return __export; - }( removeFromArray, resolveRef, Unresolved, Evaluator, getNewKeypath ); + }( removeFromArray, defineProperty, resolveRef, Unresolved, getFunctionFromString, getNewKeypath, legacy ); /* virtualdom/items/shared/Resolvers/ReferenceExpressionResolver/MemberResolver.js */ var MemberResolver = function( types, resolveRef, Unresolved, getNewKeypath, ExpressionResolver ) { @@ -13630,7 +13740,6 @@ parentFragment = mustache.parentFragment; resolver.root = ractive = mustache.root; resolver.mustache = mustache; - resolver.priority = mustache.priority; resolver.ref = ref = template.r; resolver.callback = callback; resolver.unresolved = []; @@ -13718,7 +13827,6 @@ mustache.pElement = parentFragment.pElement; mustache.template = options.template; mustache.index = options.index || 0; - mustache.priority = parentFragment.priority; mustache.isStatic = options.template.s; mustache.type = options.template.t; // if this is a simple mustache, with a reference, we just need to resolve @@ -13964,19 +14072,26 @@ return this.parentFragment.findNextNode( this ); }; - /* virtualdom/items/Section/prototype/merge.js */ - var virtualdom_items_Section$merge = function( runloop, circular ) { + /* virtualdom/items/Section/prototype/shuffle.js */ + var virtualdom_items_Section$shuffle = function( types, runloop, circular ) { var Fragment; circular.push( function() { Fragment = circular.Fragment; } ); - return function Section$merge( newIndices ) { + return function Section$shuffle( newIndices ) { + var this$0 = this; var section = this, - parentFragment, firstChange, i, newLength, reboundFragments, fragmentOptions, fragment, nextNode; - if ( this.unbound ) { + parentFragment, firstChange, i, newLength, reboundFragments, fragmentOptions, fragment; + // short circuit any double-updates, and ensure that this isn't applied to + // non-list sections + if ( this.shuffling || this.unbound || this.subtype && this.subtype !== types.SECTION_EACH ) { return; } + this.shuffling = true; + runloop.scheduleTask( function() { + return this$0.shuffling = false; + } ); parentFragment = this.parentFragment; reboundFragments = []; // first, rebind existing fragments @@ -14001,6 +14116,7 @@ oldKeypath = section.keypath + '.' + oldIndex; newKeypath = section.keypath + '.' + newIndex; fragment.rebind( section.template.i, newIndex, oldKeypath, newKeypath ); + fragment.index = newIndex; reboundFragments[ newIndex ] = fragment; } ); newLength = this.root.get( this.keypath ).length; @@ -14014,7 +14130,9 @@ firstChange = this.length; } this.length = this.fragments.length = newLength; - runloop.addView( this ); + if ( this.rendered ) { + runloop.addView( this ); + } // Prepare new fragment options fragmentOptions = { template: this.template.f, @@ -14027,21 +14145,14 @@ // Add as many new fragments as we need to, or add back existing // (detached) fragments for ( i = firstChange; i < newLength; i += 1 ) { - // is this an existing fragment? - if ( fragment = reboundFragments[ i ] ) { - this.docFrag.appendChild( fragment.detach( false ) ); - } else { - // Fragment will be created when changes are applied - // by the runloop + fragment = reboundFragments[ i ]; + if ( !fragment ) { this.fragmentsToCreate.push( i ); } this.fragments[ i ] = fragment; } - // reinsert fragment - nextNode = parentFragment.findNextNode( this ); - this.parentFragment.getNode().insertBefore( this.docFrag, nextNode ); }; - }( runloop, circular ); + }( types, runloop, circular ); /* virtualdom/items/Section/prototype/render.js */ var virtualdom_items_Section$render = function Section$render() { @@ -14052,8 +14163,18 @@ return docFrag; }; + /* utils/isArrayLike.js */ + var isArrayLike = function() { + + var pattern = /^\[object (?:Array|FileList)\]$/, + toString = Object.prototype.toString; + return function isArrayLike( obj ) { + return pattern.test( toString.call( obj ) ); + }; + }(); + /* virtualdom/items/Section/prototype/setValue.js */ - var virtualdom_items_Section$setValue = function( types, isArray, isObject, runloop, circular ) { + var virtualdom_items_Section$setValue = function( types, isArrayLike, isObject, runloop, circular ) { var __export; var Fragment; @@ -14121,6 +14242,8 @@ return reevaluateConditionalSection( section, value, true, fragmentOptions ); case types.SECTION_WITH: return reevaluateContextSection( section, fragmentOptions ); + case types.SECTION_IF_WITH: + return reevaluateConditionalContextSection( section, value, fragmentOptions ); case types.SECTION_EACH: if ( isObject( value ) ) { return reevaluateListObjectSection( section, value, fragmentOptions ); @@ -14128,7 +14251,7 @@ } } // Otherwise we need to work out what sort of section we're dealing with - section.ordered = !!isArray( value ); + section.ordered = !!isArrayLike( value ); // Ordered list section if ( section.ordered ) { return reevaluateListSection( section, value, fragmentOptions ); @@ -14210,6 +14333,14 @@ return changed; } + function reevaluateConditionalContextSection( section, value, fragmentOptions ) { + if ( value ) { + return reevaluateContextSection( section, fragmentOptions ); + } else { + return removeSectionFragments( section ); + } + } + function reevaluateContextSection( section, fragmentOptions ) { var fragment; // ...then if it isn't rendered, render it, adding section.keypath to the context stack @@ -14227,12 +14358,20 @@ } function reevaluateConditionalSection( section, value, inverted, fragmentOptions ) { - var doRender, emptyArray, fragment; - emptyArray = isArray( value ) && value.length === 0; + var doRender, emptyArray, emptyObject, fragment, name; + emptyArray = isArrayLike( value ) && value.length === 0; + emptyObject = false; + if ( !isArrayLike( value ) && isObject( value ) ) { + emptyObject = true; + for ( name in value ) { + emptyObject = false; + break; + } + } if ( inverted ) { - doRender = emptyArray || !value; + doRender = emptyArray || emptyObject || !value; } else { - doRender = value && !emptyArray; + doRender = value && !emptyArray && !emptyObject; } if ( doRender ) { if ( !section.length ) { @@ -14248,7 +14387,13 @@ section.fragmentsToUnrender.forEach( unbind ); return true; } - } else if ( section.length ) { + } else { + return removeSectionFragments( section ); + } + } + + function removeSectionFragments( section ) { + if ( section.length ) { section.fragmentsToUnrender = section.fragments.splice( 0, section.fragments.length ).filter( isRendered ); section.fragmentsToUnrender.forEach( unbind ); section.length = section.fragmentsToRender.length = 0; @@ -14264,89 +14409,7 @@ return fragment.rendered; } return __export; - }( types, isArray, isObject, runloop, circular ); - - /* virtualdom/items/Section/prototype/splice.js */ - var virtualdom_items_Section$splice = function( runloop, circular ) { - - var __export; - var Fragment; - circular.push( function() { - Fragment = circular.Fragment; - } ); - __export = function Section$splice( spliceSummary ) { - var section = this, - balance, start, insertStart, insertEnd, spliceArgs; - // In rare cases, a section will receive a splice instruction after it has - // been unbound (see https://github.com/ractivejs/ractive/issues/967). This - // prevents errors arising from those situations - if ( this.unbound ) { - return; - } - balance = spliceSummary.balance; - if ( !balance ) { - // The array length hasn't changed - we don't need to add or remove anything - return; - } - // Register with the runloop, so we can (un)render with the - // next batch of DOM changes - runloop.addView( section ); - start = spliceSummary.rangeStart; - section.length += balance; - // If more items were removed from the array than added, we tear down - // the excess fragments and remove them... - if ( balance < 0 ) { - section.fragmentsToUnrender = section.fragments.splice( start, -balance ); - section.fragmentsToUnrender.forEach( unbind ); - // Reassign fragments after the ones we've just removed - rebindFragments( section, start, section.length, balance ); - // Nothing more to do - return; - } - // ...otherwise we need to add some things to the DOM. - insertStart = start + spliceSummary.removed; - insertEnd = start + spliceSummary.added; - // Make room for the new fragments by doing a splice that simulates - // what happened to the data array - spliceArgs = [ - insertStart, - 0 - ]; - spliceArgs.length += balance; - section.fragments.splice.apply( section.fragments, spliceArgs ); - // Rebind existing fragments at the end of the array - rebindFragments( section, insertEnd, section.length, balance ); - // Schedule new fragments to be created - section.fragmentsToCreate = range( insertStart, insertEnd ); - }; - - function unbind( fragment ) { - fragment.unbind(); - } - - function range( start, end ) { - var array = [], - i; - for ( i = start; i < end; i += 1 ) { - array.push( i ); - } - return array; - } - - function rebindFragments( section, start, end, by ) { - var i, fragment, indexRef, oldKeypath, newKeypath; - indexRef = section.template.i; - for ( i = start; i < end; i += 1 ) { - fragment = section.fragments[ i ]; - oldKeypath = section.keypath + '.' + ( i - by ); - newKeypath = section.keypath + '.' + i; - // change the fragment index - fragment.index = i; - fragment.rebind( indexRef, i, oldKeypath, newKeypath ); - } - } - return __export; - }( runloop, circular ); + }( types, isArrayLike, isObject, runloop, circular ); /* virtualdom/items/Section/prototype/toString.js */ var virtualdom_items_Section$toString = function Section$toString( escape ) { @@ -14397,40 +14460,53 @@ /* virtualdom/items/Section/prototype/update.js */ var virtualdom_items_Section$update = function Section$update() { - var fragment, rendered, nextFragment, anchor, target; + var fragment, renderIndex, renderedFragments, anchor, target, i, len; + // `this.renderedFragments` is in the order of the previous render. + // If fragments have shuffled about, this allows us to quickly + // reinsert them in the correct place + renderedFragments = this.renderedFragments; + // Remove fragments that have been marked for destruction while ( fragment = this.fragmentsToUnrender.pop() ) { fragment.unrender( true ); + renderedFragments.splice( renderedFragments.indexOf( fragment ), 1 ); } - // If we have no new nodes to insert (i.e. the section length stayed the - // same, or shrank), we don't need to go any further - if ( !this.fragmentsToRender.length ) { - return; + // Render new fragments (but don't insert them yet) + while ( fragment = this.fragmentsToRender.shift() ) { + fragment.render(); } if ( this.rendered ) { target = this.parentFragment.getNode(); } - // Render new fragments to our docFrag - while ( fragment = this.fragmentsToRender.shift() ) { - rendered = fragment.render(); - this.docFrag.appendChild( rendered ); - // If this is an ordered list, and it's already rendered, we may - // need to insert content into the appropriate place - if ( this.rendered && this.ordered ) { - // If the next fragment is already rendered, use it as an anchor... - nextFragment = this.fragments[ fragment.index + 1 ]; - if ( nextFragment && nextFragment.rendered ) { - target.insertBefore( this.docFrag, nextFragment.firstNode() || null ); + len = this.fragments.length; + for ( i = 0; i < len; i += 1 ) { + fragment = this.fragments[ i ]; + renderIndex = renderedFragments.indexOf( fragment, i ); + // search from current index - it's guaranteed to be the same or higher + if ( renderIndex === i ) { + // already in the right place. insert accumulated nodes (if any) and carry on + if ( this.docFrag.childNodes.length ) { + anchor = fragment.firstNode(); + target.insertBefore( this.docFrag, anchor ); } + continue; } + this.docFrag.appendChild( fragment.detach() ); + // update renderedFragments + if ( renderIndex !== -1 ) { + renderedFragments.splice( renderIndex, 1 ); + } + renderedFragments.splice( i, 0, fragment ); } if ( this.rendered && this.docFrag.childNodes.length ) { anchor = this.parentFragment.findNextNode( this ); target.insertBefore( this.docFrag, anchor ); } + // Save the rendering order for next time + this.renderedFragments = this.fragments.slice(); }; /* virtualdom/items/Section/_Section.js */ - var Section = function( types, Mustache, bubble, detach, find, findAll, findAllComponents, findComponent, findNextNode, firstNode, merge, render, setValue, splice, toString, unbind, unrender, update ) { + var Section = function( types, Mustache, bubble, detach, find, findAll, findAllComponents, findComponent, findNextNode, firstNode, shuffle, render, setValue, toString, unbind, unrender, update ) { var Section = function( options ) { this.type = types.SECTION; @@ -14441,6 +14517,7 @@ this.fragmentsToCreate = []; this.fragmentsToRender = []; this.fragmentsToUnrender = []; + this.renderedFragments = []; this.length = 0; // number of times this section is rendered Mustache.init( this, options ); @@ -14455,19 +14532,18 @@ findNextNode: findNextNode, firstNode: firstNode, getValue: Mustache.getValue, - merge: merge, + shuffle: shuffle, rebind: Mustache.rebind, render: render, resolve: Mustache.resolve, setValue: setValue, - splice: splice, toString: toString, unbind: unbind, unrender: unrender, update: update }; return Section; - }( types, Mustache, virtualdom_items_Section$bubble, virtualdom_items_Section$detach, virtualdom_items_Section$find, virtualdom_items_Section$findAll, virtualdom_items_Section$findAllComponents, virtualdom_items_Section$findComponent, virtualdom_items_Section$findNextNode, virtualdom_items_Section$firstNode, virtualdom_items_Section$merge, virtualdom_items_Section$render, virtualdom_items_Section$setValue, virtualdom_items_Section$splice, virtualdom_items_Section$toString, virtualdom_items_Section$unbind, virtualdom_items_Section$unrender, virtualdom_items_Section$update ); + }( types, Mustache, virtualdom_items_Section$bubble, virtualdom_items_Section$detach, virtualdom_items_Section$find, virtualdom_items_Section$findAll, virtualdom_items_Section$findAllComponents, virtualdom_items_Section$findComponent, virtualdom_items_Section$findNextNode, virtualdom_items_Section$firstNode, virtualdom_items_Section$shuffle, virtualdom_items_Section$render, virtualdom_items_Section$setValue, virtualdom_items_Section$toString, virtualdom_items_Section$unbind, virtualdom_items_Section$unrender, virtualdom_items_Section$update ); /* virtualdom/items/Triple/prototype/detach.js */ var virtualdom_items_Triple$detach = function Triple$detach() { @@ -14854,13 +14930,13 @@ }(); /* virtualdom/items/Element/Attribute/prototype/bubble.js */ - var virtualdom_items_Element_Attribute$bubble = function( runloop ) { + var virtualdom_items_Element_Attribute$bubble = function( runloop, isEqual ) { return function Attribute$bubble() { var value = this.fragment.getValue(); // TODO this can register the attribute multiple times (see render test // 'Attribute with nested mustaches') - if ( value !== this.value ) { + if ( !isEqual( value, this.value ) ) { // Need to clear old id from ractive.nodes if ( this.name === 'id' && this.value ) { delete this.root.nodes[ this.value ]; @@ -14876,7 +14952,7 @@ } } }; - }( runloop ); + }( runloop, isEqual ); /* config/booleanAttributes.js */ var booleanAttributes = function() { @@ -14902,6 +14978,7 @@ name = name.substring( colonIndex + 1 ); attribute.name = enforceCase( name ); attribute.namespace = namespaces[ namespacePrefix.toLowerCase() ]; + attribute.namespacePrefix = namespacePrefix; if ( !attribute.namespace ) { throw 'Unknown namespace ("' + namespacePrefix + '")'; } @@ -15067,6 +15144,7 @@ var __export; __export = function Attribute$toString() { var name = ( fragment = this ).name, + namespacePrefix = fragment.namespacePrefix, value = fragment.value, interpolator = fragment.interpolator, fragment = fragment.fragment; @@ -15089,6 +15167,9 @@ if ( fragment ) { value = fragment.toString(); } + if ( namespacePrefix ) { + name = namespacePrefix + ':' + name; + } return value ? name + '="' + escape( value ) + '"' : name; }; @@ -15104,6 +15185,9 @@ if ( this.fragment ) { this.fragment.unbind(); } + if ( this.name === 'id' ) { + delete this.root.nodes[ this.value ]; + } }; /* virtualdom/items/Element/Attribute/prototype/update/updateSelectValue.js */ @@ -15203,13 +15287,21 @@ var virtualdom_items_Element_Attribute$update_updateCheckboxName = function( isArray ) { return function Attribute$updateCheckboxName() { - var node, value; - node = this.node; - value = this.value; + var element = ( value = this ).element, + node = value.node, + value = value.value, + valueAttribute, i; + valueAttribute = element.getAttribute( 'value' ); if ( !isArray( value ) ) { - node.checked = value == node._ractive.value; + node.checked = value == valueAttribute; } else { - node.checked = value.indexOf( node._ractive.value ) !== -1; + i = value.length; + while ( i-- ) { + if ( valueAttribute == value[ i ] ) { + node.checked = true; + return; + } + } } }; }( isArray ); @@ -15227,12 +15319,8 @@ /* virtualdom/items/Element/Attribute/prototype/update/updateIdAttribute.js */ var virtualdom_items_Element_Attribute$update_updateIdAttribute = function Attribute$updateIdAttribute() { - var node, value; - node = this.node; - value = this.value; - if ( value !== undefined ) { - this.root.nodes[ value ] = undefined; - } + var node = ( value = this ).node, + value = value.value; this.root.nodes[ value ] = node; node.id = value; }; @@ -15392,6 +15480,100 @@ }; }( Attribute ); + /* virtualdom/items/Element/ConditionalAttribute/_ConditionalAttribute.js */ + var ConditionalAttribute = function( circular, namespaces, createElement, toArray ) { + + var __export; + var Fragment, div; + if ( typeof document !== 'undefined' ) { + div = createElement( 'div' ); + } + circular.push( function() { + Fragment = circular.Fragment; + } ); + var ConditionalAttribute = function( element, template ) { + this.element = element; + this.root = element.root; + this.parentFragment = element.parentFragment; + this.attributes = []; + this.fragment = new Fragment( { + root: element.root, + owner: this, + template: [ template ] + } ); + }; + ConditionalAttribute.prototype = { + bubble: function() { + if ( this.node ) { + this.update(); + } + this.element.bubble(); + }, + rebind: function( indexRef, newIndex, oldKeypath, newKeypath ) { + this.fragment.rebind( indexRef, newIndex, oldKeypath, newKeypath ); + }, + render: function( node ) { + this.node = node; + this.isSvg = node.namespaceURI === namespaces.svg; + this.update(); + }, + unbind: function() { + this.fragment.unbind(); + }, + update: function() { + var this$0 = this; + var str, attrs; + str = this.fragment.toString(); + attrs = parseAttributes( str, this.isSvg ); + // any attributes that previously existed but no longer do + // must be removed + this.attributes.filter( function( a ) { + return notIn( attrs, a ); + } ).forEach( function( a ) { + this$0.node.removeAttribute( a.name ); + } ); + attrs.forEach( function( a ) { + this$0.node.setAttribute( a.name, a.value ); + } ); + this.attributes = attrs; + }, + toString: function() { + return this.fragment.toString(); + } + }; + __export = ConditionalAttribute; + + function parseAttributes( str, isSvg ) { + var tag = isSvg ? 'svg' : 'div'; + div.innerHTML = '<' + tag + ' ' + str + '>'; + return toArray( div.childNodes[ 0 ].attributes ); + } + + function notIn( haystack, needle ) { + var i = haystack.length; + while ( i-- ) { + if ( haystack[ i ].name === needle.name ) { + return false; + } + } + return true; + } + return __export; + }( circular, namespaces, createElement, toArray ); + + /* virtualdom/items/Element/prototype/init/createConditionalAttributes.js */ + var virtualdom_items_Element$init_createConditionalAttributes = function( ConditionalAttribute ) { + + return function( element, attributes ) { + if ( !attributes ) { + return []; + } + return attributes.map( function( a ) { + return new ConditionalAttribute( element, a ); + } ); + }; + }( ConditionalAttribute ); + /* utils/extend.js */ var extend = function( target ) { var SLICE$0 = Array.prototype.slice; @@ -15656,6 +15838,8 @@ var existingValue, bindingValue, noInitialValue; this.checkboxName = true; // so that ractive.updateModel() knows what to do with this + this.attribute.twoway = true; + // we set this property so that the attribute gets the correct update method // Each input has a reference to an array containing it and its // siblings, as two-way binding depends on being able to ascertain // the status of all inputs within the group @@ -15673,14 +15857,30 @@ existingValue.push( bindingValue ); } } else { - this.isChecked = isArray( existingValue ) ? existingValue.indexOf( bindingValue ) !== -1 : existingValue === bindingValue; + this.shouldOverride = true; } }, unbind: function() { removeFromArray( this.siblings, this ); }, render: function() { - var node = this.element.node; + var node = this.element.node, + existingValue, bindingValue, i; + if ( this.shouldOverride ) { + existingValue = this.root.viewmodel.get( this.keypath ); + bindingValue = this.element.getAttribute( 'value' ); + if ( isArray( existingValue ) ) { + i = existingValue.length; + while ( i-- ) { + if ( existingValue[ i ] == bindingValue ) { + this.isChecked = true; + break; + } + } + } else { + this.isChecked = existingValue == bindingValue; + } + } node.name = '{{' + this.keypath + '}}'; node.checked = this.isChecked; node.addEventListener( 'change', handleDomEvent, false ); @@ -16099,7 +16299,6 @@ handler.args = args = []; handler.unresolved = []; handler.refs = template.a.r; - // TODO need to resolve these! handler.fn = getFunctionFromString( template.a.s, handler.refs.length ); parentFragment = element.parentFragment; indexRefs = parentFragment.indexRefs; @@ -16235,7 +16434,15 @@ var virtualdom_items_Element_EventHandler$listen = function( config, genericHandler, log ) { var __export; - var customHandlers = {}; + var customHandlers = {}, + touchEvents = { + touchstart: true, + touchmove: true, + touchend: true, + touchcancel: true, + //not w3c, but supported in some browsers + touchleave: true + }; __export = function EventHandler$listen() { var definition, name = this.name; if ( this.invalid ) { @@ -16246,14 +16453,18 @@ } else { // Looks like we're dealing with a standard DOM event... but let's check if ( !( 'on' + name in this.node ) && !( window && 'on' + name in window ) ) { - log.error( { - debug: this.root.debug, - message: 'missingPlugin', - args: { - plugin: 'event', - name: name - } - } ); + // okay to use touch events if this browser doesn't support them + if ( !touchEvents[ name ] ) { + log.error( { + debug: this.root.debug, + message: 'missingPlugin', + args: { + plugin: 'event', + name: name + } + } ); + } + return; } this.node.addEventListener( name, genericHandler, false ); } @@ -16591,7 +16802,7 @@ }( findParentSelect ); /* virtualdom/items/Element/prototype/init.js */ - var virtualdom_items_Element$init = function( types, enforceCase, createAttributes, createTwowayBinding, createEventHandlers, Decorator, bubbleSelect, initOption, circular ) { + var virtualdom_items_Element$init = function( types, enforceCase, createAttributes, createConditionalAttributes, createTwowayBinding, createEventHandlers, Decorator, bubbleSelect, initOption, circular ) { var Fragment; circular.push( function() { @@ -16618,6 +16829,7 @@ } // create attributes this.attributes = createAttributes( this, template.a ); + this.conditionalAttributes = createConditionalAttributes( this, template.m ); // append children, if there are any if ( template.f ) { this.fragment = new Fragment( { @@ -16646,7 +16858,7 @@ this.intro = template.t0 || template.t1; this.outro = template.t0 || template.t2; }; - }( types, enforceCase, virtualdom_items_Element$init_createAttributes, virtualdom_items_Element$init_createTwowayBinding, virtualdom_items_Element$init_createEventHandlers, Decorator, bubble, init, circular ); + }( types, enforceCase, virtualdom_items_Element$init_createAttributes, virtualdom_items_Element$init_createConditionalAttributes, virtualdom_items_Element$init_createTwowayBinding, virtualdom_items_Element$init_createEventHandlers, Decorator, bubble, init, circular ); /* virtualdom/items/shared/utils/startsWith.js */ var startsWith = function( startsWithKeypath ) { @@ -16676,6 +16888,9 @@ if ( this.attributes ) { this.attributes.forEach( rebind ); } + if ( this.conditionalAttributes ) { + this.conditionalAttributes.forEach( rebind ); + } if ( this.eventHandlers ) { this.eventHandlers.forEach( rebind ); } @@ -17250,13 +17465,16 @@ }( legacy, isClient, warn, Promise, prefix, virtualdom_items_Element_Transition$animateStyle_createTransitions, virtualdom_items_Element_Transition$animateStyle_visibility ); /* utils/fillGaps.js */ - var fillGaps = function( target, source ) { - var key; - for ( key in source ) { - if ( source.hasOwnProperty( key ) && !( key in target ) ) { - target[ key ] = source[ key ]; + var fillGaps = function( target ) { + var SLICE$0 = Array.prototype.slice; + var sources = SLICE$0.call( arguments, 1 ); + sources.forEach( function( s ) { + for ( var key in s ) { + if ( s.hasOwnProperty( key ) && !( key in target ) ) { + target[ key ] = s[ key ]; + } } - } + } ); return target; }; @@ -17285,7 +17503,7 @@ } else if ( !params ) { params = {}; } - return fillGaps( params, defaults ); + return fillGaps( {}, params, defaults ); }; }( fillGaps ); @@ -17295,18 +17513,22 @@ var __export; __export = function Transition$start() { var t = this, - node, originalStyle; + node, originalStyle, completed; node = t.node = t.element.node; originalStyle = node.getAttribute( 'style' ); // create t.complete() - we don't want this on the prototype, // because we don't want `this` silliness when passing it as // an argument t.complete = function( noReset ) { + if ( completed ) { + return; + } if ( !noReset && t.isIntro ) { resetStyle( node, originalStyle ); } node._ractive.transition = null; t._manager.remove( t ); + completed = true; }; // If the transition function doesn't exist, abort if ( !t._fn ) { @@ -17358,6 +17580,10 @@ updateCss = function() { var node = this.node, content = this.fragment.toString( false ); + // IE8 has no styleSheet unless there's a type text/css + if ( window && window.appearsToBeIELessEqual8 ) { + node.type = 'text/css'; + } if ( node.styleSheet ) { node.styleSheet.cssText = content; } else { @@ -17402,6 +17628,9 @@ this.attributes.forEach( function( a ) { return a.render( node ); } ); + this.conditionalAttributes.forEach( function( a ) { + return a.render( node ); + } ); // Render children if ( this.fragment ) { // Special case -