unpoly-rails 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of unpoly-rails might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +68 -0
- data/dist/unpoly-bootstrap3.js +1 -1
- data/dist/unpoly-bootstrap3.min.js +1 -1
- data/dist/unpoly.css +18 -8
- data/dist/unpoly.js +498 -265
- data/dist/unpoly.min.css +1 -1
- data/dist/unpoly.min.js +3 -3
- data/lib/assets/javascripts/unpoly-bootstrap3/modal-ext.js.coffee +5 -2
- data/lib/assets/javascripts/unpoly/flow.js.coffee +3 -1
- data/lib/assets/javascripts/unpoly/form.js.coffee +3 -6
- data/lib/assets/javascripts/unpoly/layout.js.coffee +1 -1
- data/lib/assets/javascripts/unpoly/link.js.coffee +4 -2
- data/lib/assets/javascripts/unpoly/log.js.coffee +2 -2
- data/lib/assets/javascripts/unpoly/modal.js.coffee +125 -36
- data/lib/assets/javascripts/unpoly/motion.js.coffee +75 -37
- data/lib/assets/javascripts/unpoly/navigation.js.coffee +38 -26
- data/lib/assets/javascripts/unpoly/proxy.js.coffee +77 -52
- data/lib/assets/javascripts/unpoly/syntax.js.coffee +1 -0
- data/lib/assets/javascripts/unpoly/tooltip.js.coffee +2 -0
- data/lib/assets/javascripts/unpoly/util.js.coffee +138 -46
- data/lib/assets/stylesheets/unpoly/link.css.sass +1 -1
- data/lib/assets/stylesheets/unpoly/modal.css.sass +28 -15
- data/lib/unpoly/rails/version.rb +1 -1
- data/spec_app/Gemfile.lock +7 -7
- data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +2 -0
- data/spec_app/spec/javascripts/helpers/to_contain.js.coffee +5 -0
- data/spec_app/spec/javascripts/up/flow_spec.js.coffee +2 -2
- data/spec_app/spec/javascripts/up/history_spec.js.coffee +6 -4
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +114 -5
- data/spec_app/spec/javascripts/up/modal_spec.js.coffee +28 -18
- data/spec_app/spec/javascripts/up/motion_spec.js.coffee +112 -7
- data/spec_app/spec/javascripts/up/navigation_spec.js.coffee +157 -121
- data/spec_app/spec/javascripts/up/popup_spec.js.coffee +1 -1
- data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +42 -3
- data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +1 -2
- data/spec_app/spec/javascripts/up/util_spec.js.coffee +26 -1
- data/spec_app/vendor/assets/bower_components/jquery/.bower.json +1 -1
- metadata +3 -3
- data/spec_app/spec/javascripts/helpers/set_timer.js.coffee +0 -3
| @@ -36,11 +36,8 @@ up.navigation = (($) -> | |
| 36 36 | 
             
                classes.join(' ')
         | 
| 37 37 |  | 
| 38 38 | 
             
              CLASS_ACTIVE = 'up-active'
         | 
| 39 | 
            -
               | 
| 40 | 
            -
             | 
| 41 | 
            -
              SELECTOR_SECTION_INSTANT = ("#{selector}[up-instant]" for selector in SELECTORS_SECTION).join(', ')
         | 
| 42 | 
            -
              SELECTOR_ACTIVE = ".#{CLASS_ACTIVE}"
         | 
| 43 | 
            -
              
         | 
| 39 | 
            +
              SELECTOR_SECTION = 'a, [up-href]'
         | 
| 40 | 
            +
             | 
| 44 41 | 
             
              normalizeUrl = (url) ->
         | 
| 45 42 | 
             
                if u.isPresent(url)
         | 
| 46 43 | 
             
                  u.normalizeUrl(url,
         | 
| @@ -102,6 +99,22 @@ up.navigation = (($) -> | |
| 102 99 | 
             
                  else if $section.hasClass(klass) && $section.closest('.up-destroying').length == 0
         | 
| 103 100 | 
             
                    $section.removeClass(klass)
         | 
| 104 101 |  | 
| 102 | 
            +
              ###*
         | 
| 103 | 
            +
              @function findClickArea
         | 
| 104 | 
            +
              @param {String|Element|jQuery} elementOrSelector
         | 
| 105 | 
            +
              @param {Boolean} options.enlarge
         | 
| 106 | 
            +
                If `true`, tries to find a containing link that has expanded the link's click area.
         | 
| 107 | 
            +
                If we find one, we prefer to mark the larger area as active.
         | 
| 108 | 
            +
              @internal
         | 
| 109 | 
            +
              ###
         | 
| 110 | 
            +
              findClickArea = (elementOrSelector, options) ->
         | 
| 111 | 
            +
                $area = $(elementOrSelector)
         | 
| 112 | 
            +
                options = u.options(options, enlarge: false)
         | 
| 113 | 
            +
                if options.enlarge
         | 
| 114 | 
            +
                  u.presence($area.parent(SELECTOR_SECTION)) || $area
         | 
| 115 | 
            +
                else
         | 
| 116 | 
            +
                  $area
         | 
| 117 | 
            +
             | 
| 105 118 | 
             
              ###*
         | 
| 106 119 | 
             
              Links that are currently loading are assigned the `up-active`
         | 
| 107 120 | 
             
              class automatically. Style `.up-active` in your CSS to improve the
         | 
| @@ -129,24 +142,23 @@ up.navigation = (($) -> | |
| 129 142 | 
             
              @selector .up-active
         | 
| 130 143 | 
             
              @stable
         | 
| 131 144 | 
             
              ###
         | 
| 132 | 
            -
               | 
| 133 | 
            -
                 | 
| 134 | 
            -
                $ | 
| 135 | 
            -
             | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 138 | 
            -
                 | 
| 139 | 
            -
             | 
| 140 | 
            -
               | 
| 141 | 
            -
                $( | 
| 142 | 
            -
             | 
| 143 | 
            -
             | 
| 144 | 
            -
                if u. | 
| 145 | 
            -
                   | 
| 146 | 
            -
             | 
| 147 | 
            -
             | 
| 148 | 
            -
                 | 
| 149 | 
            -
                  sectionClicked($section)
         | 
| 145 | 
            +
              markActive = (elementOrSelector, options) ->
         | 
| 146 | 
            +
                $element = findClickArea(elementOrSelector, options)
         | 
| 147 | 
            +
                $element.addClass(CLASS_ACTIVE)
         | 
| 148 | 
            +
             | 
| 149 | 
            +
              unmarkActive = (elementOrSelector, options) ->
         | 
| 150 | 
            +
                $element = findClickArea(elementOrSelector, options)
         | 
| 151 | 
            +
                $element.removeClass(CLASS_ACTIVE)
         | 
| 152 | 
            +
             | 
| 153 | 
            +
              withActiveMark = (elementOrSelector, options, block) ->
         | 
| 154 | 
            +
                $element = $(elementOrSelector)
         | 
| 155 | 
            +
                markActive($element, options)
         | 
| 156 | 
            +
                promise = block()
         | 
| 157 | 
            +
                if u.isPromise(promise)
         | 
| 158 | 
            +
                  promise.always -> unmarkActive($element, options)
         | 
| 159 | 
            +
                else
         | 
| 160 | 
            +
                  up.warn('Expected block to return a promise, but got %o', promise)
         | 
| 161 | 
            +
                promise
         | 
| 150 162 |  | 
| 151 163 | 
             
              ###*
         | 
| 152 164 | 
             
              Links that point to the current location are assigned
         | 
| @@ -193,9 +205,6 @@ up.navigation = (($) -> | |
| 193 205 | 
             
              @stable
         | 
| 194 206 | 
             
              ###
         | 
| 195 207 | 
             
              up.on 'up:fragment:inserted', ->
         | 
| 196 | 
            -
                # If a new fragment is inserted, it's likely to be the result
         | 
| 197 | 
            -
                # of the active action. So we can remove the active marker.
         | 
| 198 | 
            -
                unmarkActive()
         | 
| 199 208 | 
             
                # When a fragment is inserted it might either have brought a location change
         | 
| 200 209 | 
             
                # with it, or it might have opened a modal / popup which we consider
         | 
| 201 210 | 
             
                # to be secondary location sources (the primary being the browser's
         | 
| @@ -215,5 +224,8 @@ up.navigation = (($) -> | |
| 215 224 |  | 
| 216 225 | 
             
              config: config
         | 
| 217 226 | 
             
              defaults: -> u.error('up.navigation.defaults(...) no longer exists. Set values on he up.navigation.config property instead.')
         | 
| 227 | 
            +
              markActive: markActive
         | 
| 228 | 
            +
              unmarkActive: unmarkActive
         | 
| 229 | 
            +
              withActiveMark: withActiveMark
         | 
| 218 230 |  | 
| 219 231 | 
             
            )(jQuery)
         | 
| @@ -112,67 +112,26 @@ up.proxy = (($) -> | |
| 112 112 | 
             
                  if response = cache.get(candidate)
         | 
| 113 113 | 
             
                    return response
         | 
| 114 114 |  | 
| 115 | 
            -
              ###*
         | 
| 116 | 
            -
              Manually stores a promise for the response to the given request.
         | 
| 117 | 
            -
             | 
| 118 | 
            -
              @function up.proxy.set
         | 
| 119 | 
            -
              @param {String} request.url
         | 
| 120 | 
            -
              @param {String} [request.method='GET']
         | 
| 121 | 
            -
              @param {String} [request.target='body']
         | 
| 122 | 
            -
              @param {Promise} response
         | 
| 123 | 
            -
                A promise for the response that is API-compatible with the
         | 
| 124 | 
            -
                promise returned by [`jQuery.ajax`](http://api.jquery.com/jquery.ajax/).
         | 
| 125 | 
            -
              @experimental
         | 
| 126 | 
            -
              ###
         | 
| 127 | 
            -
              set = cache.set
         | 
| 128 | 
            -
             | 
| 129 | 
            -
              ###*
         | 
| 130 | 
            -
              Manually removes the given request from the cache.
         | 
| 131 | 
            -
             | 
| 132 | 
            -
              You can also [configure](/up.proxy.config) when the proxy
         | 
| 133 | 
            -
              automatically removes cache entries.
         | 
| 134 | 
            -
             | 
| 135 | 
            -
              @function up.proxy.remove
         | 
| 136 | 
            -
              @param {String} request.url
         | 
| 137 | 
            -
              @param {String} [request.method='GET']
         | 
| 138 | 
            -
              @param {String} [request.target='body']
         | 
| 139 | 
            -
              @experimental
         | 
| 140 | 
            -
              ###
         | 
| 141 | 
            -
              remove = cache.remove
         | 
| 142 | 
            -
             | 
| 143 | 
            -
              ###*
         | 
| 144 | 
            -
              Removes all cache entries.
         | 
| 145 | 
            -
             | 
| 146 | 
            -
              Unpoly also automatically clears the cache whenever it processes
         | 
| 147 | 
            -
              a request with a non-GET HTTP method.
         | 
| 148 | 
            -
             | 
| 149 | 
            -
              @function up.proxy.clear
         | 
| 150 | 
            -
              @stable
         | 
| 151 | 
            -
              ###
         | 
| 152 | 
            -
              clear = cache.clear
         | 
| 153 | 
            -
             | 
| 154 115 | 
             
              cancelPreloadDelay = ->
         | 
| 155 116 | 
             
                clearTimeout(preloadDelayTimer)
         | 
| 156 117 | 
             
                preloadDelayTimer = null
         | 
| 157 118 |  | 
| 158 | 
            -
               | 
| 119 | 
            +
              cancelSlowDelay = ->
         | 
| 159 120 | 
             
                clearTimeout(slowDelayTimer)
         | 
| 160 121 | 
             
                slowDelayTimer = null
         | 
| 161 122 |  | 
| 162 123 | 
             
              reset = ->
         | 
| 163 124 | 
             
                $waitingLink = null
         | 
| 164 125 | 
             
                cancelPreloadDelay()
         | 
| 165 | 
            -
                 | 
| 126 | 
            +
                cancelSlowDelay()
         | 
| 166 127 | 
             
                pendingCount = 0
         | 
| 167 128 | 
             
                config.reset()
         | 
| 168 | 
            -
                slowEventEmitted = false
         | 
| 169 129 | 
             
                cache.clear()
         | 
| 130 | 
            +
                slowEventEmitted = false
         | 
| 170 131 | 
             
                queuedRequests = []
         | 
| 171 132 |  | 
| 172 133 | 
             
              reset()
         | 
| 173 134 |  | 
| 174 | 
            -
              alias = cache.alias
         | 
| 175 | 
            -
             | 
| 176 135 | 
             
              normalizeRequest = (request) ->
         | 
| 177 136 | 
             
                unless request._normalized
         | 
| 178 137 | 
             
                  request.method = u.normalizeMethod(request.method)
         | 
| @@ -190,13 +149,23 @@ up.proxy = (($) -> | |
| 190 149 | 
             
              Only requests with a method of `GET`, `OPTIONS` and `HEAD`
         | 
| 191 150 | 
             
              are considered to be read-only.
         | 
| 192 151 |  | 
| 152 | 
            +
              \#\#\#\# Example
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                  up.ajax('/search', data: { query: 'sunshine' }).then(function(data, status, xhr) {
         | 
| 155 | 
            +
                    console.log('The response body is %o', data);
         | 
| 156 | 
            +
                  }).fail(function(xhr, status, error) {
         | 
| 157 | 
            +
                    console.error('The request failed');
         | 
| 158 | 
            +
                  });
         | 
| 159 | 
            +
             | 
| 160 | 
            +
              \#\#\#\# Events
         | 
| 161 | 
            +
             | 
| 193 162 | 
             
              If a network connection is attempted, the proxy will emit
         | 
| 194 | 
            -
              a `up:proxy:load` event with the `request` as its argument.
         | 
| 195 | 
            -
              Once the response is received, a `up:proxy:receive` event will
         | 
| 163 | 
            +
              a [`up:proxy:load`](/up:proxy:load) event with the `request` as its argument.
         | 
| 164 | 
            +
              Once the response is received, a [`up:proxy:receive`](/up:proxy:receive) event will
         | 
| 196 165 | 
             
              be emitted.
         | 
| 197 166 |  | 
| 198 167 | 
             
              @function up.ajax
         | 
| 199 | 
            -
              @param {String}  | 
| 168 | 
            +
              @param {String} url
         | 
| 200 169 | 
             
              @param {String} [request.method='GET']
         | 
| 201 170 | 
             
              @param {String} [request.target='body']
         | 
| 202 171 | 
             
              @param {Boolean} [request.cache]
         | 
| @@ -207,12 +176,18 @@ up.proxy = (($) -> | |
| 207 176 | 
             
                with the request.
         | 
| 208 177 | 
             
              @param {Object} [request.data={}]
         | 
| 209 178 | 
             
                An object of request parameters.
         | 
| 179 | 
            +
              @param {String} [request.url]
         | 
| 180 | 
            +
                You can omit the first string argument and pass the URL as
         | 
| 181 | 
            +
                a `request` property instead.
         | 
| 210 182 | 
             
              @return
         | 
| 211 183 | 
             
                A promise for the response that is API-compatible with the
         | 
| 212 184 | 
             
                promise returned by [`jQuery.ajax`](http://api.jquery.com/jquery.ajax/).
         | 
| 213 185 | 
             
              @stable
         | 
| 214 186 | 
             
              ###
         | 
| 215 | 
            -
              ajax = ( | 
| 187 | 
            +
              ajax = (args...) ->
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                options = u.extractOptions(args)
         | 
| 190 | 
            +
                options.url = args[0] if u.isGiven(args[0])
         | 
| 216 191 |  | 
| 217 192 | 
             
                forceCache = (options.cache == true)
         | 
| 218 193 | 
             
                ignoreCache = (options.cache == false)
         | 
| @@ -293,10 +268,7 @@ up.proxy = (($) -> | |
| 293 268 | 
             
                    if isBusy() # a fast response might have beaten the delay
         | 
| 294 269 | 
             
                      up.emit('up:proxy:slow', message: 'Proxy is busy')
         | 
| 295 270 | 
             
                      slowEventEmitted = true
         | 
| 296 | 
            -
                   | 
| 297 | 
            -
                    slowDelayTimer = setTimeout(emission, config.slowDelay)
         | 
| 298 | 
            -
                  else
         | 
| 299 | 
            -
                    emission()
         | 
| 271 | 
            +
                  slowDelayTimer = u.setTimer(config.slowDelay, emission)
         | 
| 300 272 |  | 
| 301 273 | 
             
              ###*
         | 
| 302 274 | 
             
              This event is [emitted]/(up.emit) when [AJAX requests](/up.ajax)
         | 
| @@ -423,6 +395,59 @@ up.proxy = (($) -> | |
| 423 395 | 
             
                  promise.done (args...) -> entry.deferred.resolve(args...)
         | 
| 424 396 | 
             
                  promise.fail (args...) -> entry.deferred.reject(args...)
         | 
| 425 397 |  | 
| 398 | 
            +
              ###*
         | 
| 399 | 
            +
              Makes the proxy assume that `newRequest` has the same response as the
         | 
| 400 | 
            +
              already cached `oldRequest`.
         | 
| 401 | 
            +
             | 
| 402 | 
            +
              Unpoly uses this internally when the user redirects from `/old` to `/new`.
         | 
| 403 | 
            +
              In that case, both `/old` and `/new` will cache the same response from `/new`.
         | 
| 404 | 
            +
             | 
| 405 | 
            +
              @function up.proxy.alias
         | 
| 406 | 
            +
              @param {Object} oldRequest
         | 
| 407 | 
            +
              @param {Object} newRequest
         | 
| 408 | 
            +
              @experimental
         | 
| 409 | 
            +
              ###
         | 
| 410 | 
            +
              alias = cache.alias
         | 
| 411 | 
            +
             | 
| 412 | 
            +
              ###*
         | 
| 413 | 
            +
              Manually stores a promise for the response to the given request.
         | 
| 414 | 
            +
             | 
| 415 | 
            +
              @function up.proxy.set
         | 
| 416 | 
            +
              @param {String} request.url
         | 
| 417 | 
            +
              @param {String} [request.method='GET']
         | 
| 418 | 
            +
              @param {String} [request.target='body']
         | 
| 419 | 
            +
              @param {Promise} response
         | 
| 420 | 
            +
                A promise for the response that is API-compatible with the
         | 
| 421 | 
            +
                promise returned by [`jQuery.ajax`](http://api.jquery.com/jquery.ajax/).
         | 
| 422 | 
            +
              @experimental
         | 
| 423 | 
            +
              ###
         | 
| 424 | 
            +
              set = cache.set
         | 
| 425 | 
            +
             | 
| 426 | 
            +
              ###*
         | 
| 427 | 
            +
              Manually removes the given request from the cache.
         | 
| 428 | 
            +
             | 
| 429 | 
            +
              You can also [configure](/up.proxy.config) when the proxy
         | 
| 430 | 
            +
              automatically removes cache entries.
         | 
| 431 | 
            +
             | 
| 432 | 
            +
              @function up.proxy.remove
         | 
| 433 | 
            +
              @param {String} request.url
         | 
| 434 | 
            +
              @param {String} [request.method='GET']
         | 
| 435 | 
            +
              @param {String} [request.target='body']
         | 
| 436 | 
            +
              @experimental
         | 
| 437 | 
            +
              ###
         | 
| 438 | 
            +
              remove = cache.remove
         | 
| 439 | 
            +
             | 
| 440 | 
            +
              ###*
         | 
| 441 | 
            +
              Removes all cache entries.
         | 
| 442 | 
            +
             | 
| 443 | 
            +
              Unpoly also automatically clears the cache whenever it processes
         | 
| 444 | 
            +
              a request with a non-GET HTTP method.
         | 
| 445 | 
            +
             | 
| 446 | 
            +
              @function up.proxy.clear
         | 
| 447 | 
            +
              @stable
         | 
| 448 | 
            +
              ###
         | 
| 449 | 
            +
              clear = cache.clear
         | 
| 450 | 
            +
             | 
| 426 451 | 
             
              ###*
         | 
| 427 452 | 
             
              This event is [emitted]/(up.emit) before an [AJAX request](/up.ajax)
         | 
| 428 453 | 
             
              is starting to load.
         | 
| @@ -9,6 +9,14 @@ that might save you from loading something like [Underscore.js](http://underscor | |
| 9 9 | 
             
            ###
         | 
| 10 10 | 
             
            up.util = (($) ->
         | 
| 11 11 |  | 
| 12 | 
            +
              ###*
         | 
| 13 | 
            +
              A function that does nothing.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              @function up.util.noop
         | 
| 16 | 
            +
              @experimental
         | 
| 17 | 
            +
              ###
         | 
| 18 | 
            +
              noop = $.noop
         | 
| 19 | 
            +
             | 
| 12 20 | 
             
              ###*
         | 
| 13 21 | 
             
              @function up.util.memoize
         | 
| 14 22 | 
             
              @internal
         | 
| @@ -755,6 +763,24 @@ up.util = (($) -> | |
| 755 763 | 
             
                values = ($element.attr(attrName) for attrName in attrNames)
         | 
| 756 764 | 
             
                detect(values, isPresent)
         | 
| 757 765 |  | 
| 766 | 
            +
              ###*
         | 
| 767 | 
            +
              Waits for the given number of milliseconds, the nruns the given callback.
         | 
| 768 | 
            +
             | 
| 769 | 
            +
              If the number of milliseconds is zero, the callback is run in the current execution frame.
         | 
| 770 | 
            +
              See [`up.util.nextFrame`] for running a function in the next executation frame.
         | 
| 771 | 
            +
             | 
| 772 | 
            +
              @function up.util.setTimer
         | 
| 773 | 
            +
              @param {Number} millis
         | 
| 774 | 
            +
              @param {Function} callback
         | 
| 775 | 
            +
              @experimental
         | 
| 776 | 
            +
              ###
         | 
| 777 | 
            +
              setTimer = (millis, callback) ->
         | 
| 778 | 
            +
                if millis > 0
         | 
| 779 | 
            +
                  setTimeout(callback, millis)
         | 
| 780 | 
            +
                else
         | 
| 781 | 
            +
                  callback()
         | 
| 782 | 
            +
             | 
| 783 | 
            +
             | 
| 758 784 | 
             
              ###*
         | 
| 759 785 | 
             
              Schedules the given function to be called in the
         | 
| 760 786 | 
             
              next Javascript execution frame.
         | 
| @@ -868,6 +894,16 @@ up.util = (($) -> | |
| 868 894 | 
             
                  memo = ->
         | 
| 869 895 | 
             
                memo
         | 
| 870 896 |  | 
| 897 | 
            +
              ###*
         | 
| 898 | 
            +
              Forces a repaint of the given element.
         | 
| 899 | 
            +
             | 
| 900 | 
            +
              @function up.util.forceRepaint
         | 
| 901 | 
            +
              @internal
         | 
| 902 | 
            +
              ###
         | 
| 903 | 
            +
              forceRepaint = (element) ->
         | 
| 904 | 
            +
                element = unJQuery(element)
         | 
| 905 | 
            +
                element.offsetHeight
         | 
| 906 | 
            +
             | 
| 871 907 | 
             
              ###*
         | 
| 872 908 | 
             
              Animates the given element's CSS properties using CSS transitions.
         | 
| 873 909 |  | 
| @@ -901,23 +937,49 @@ up.util = (($) -> | |
| 901 937 | 
             
                  delay: 0,
         | 
| 902 938 | 
             
                  easing: 'ease'
         | 
| 903 939 | 
             
                )
         | 
| 904 | 
            -
             | 
| 905 | 
            -
                #  | 
| 940 | 
            +
             | 
| 941 | 
            +
                # We don't finish an existing animation here, since the public API
         | 
| 942 | 
            +
                # we expose as `up.motion.animate` already does this.
         | 
| 906 943 | 
             
                deferred = $.Deferred()
         | 
| 907 944 | 
             
                transition =
         | 
| 908 945 | 
             
                  'transition-property': Object.keys(lastFrame).join(', ')
         | 
| 909 946 | 
             
                  'transition-duration': "#{opts.duration}ms"
         | 
| 910 947 | 
             
                  'transition-delay': "#{opts.delay}ms"
         | 
| 911 948 | 
             
                  'transition-timing-function': opts.easing
         | 
| 949 | 
            +
                oldTransition = $element.css(Object.keys(transition))
         | 
| 950 | 
            +
             | 
| 951 | 
            +
                $element.addClass('up-animating')
         | 
| 912 952 | 
             
                withoutCompositing = forceCompositing($element)
         | 
| 913 | 
            -
                 | 
| 953 | 
            +
                $element.css(transition)
         | 
| 914 954 | 
             
                $element.css(lastFrame)
         | 
| 915 | 
            -
                deferred.then(withoutCompositing)
         | 
| 916 | 
            -
                deferred.then(withoutTransition)
         | 
| 917 955 | 
             
                $element.data(ANIMATION_DEFERRED_KEY, deferred)
         | 
| 918 | 
            -
             | 
| 919 | 
            -
                 | 
| 920 | 
            -
             | 
| 956 | 
            +
             | 
| 957 | 
            +
                deferred.then ->
         | 
| 958 | 
            +
                  $element.removeData(ANIMATION_DEFERRED_KEY)
         | 
| 959 | 
            +
                  withoutCompositing()
         | 
| 960 | 
            +
             | 
| 961 | 
            +
                  # To interrupt the running transition we *must* set it to 'none' exactly.
         | 
| 962 | 
            +
                  # We cannot simply restore the old transition properties because browsers
         | 
| 963 | 
            +
                  # would simply keep transitioning the old properties.
         | 
| 964 | 
            +
                  $element.css('transition': 'none')
         | 
| 965 | 
            +
             | 
| 966 | 
            +
                  # Restoring a previous transition involves some work, so we only do it if
         | 
| 967 | 
            +
                  # we know the element was transitioning before.
         | 
| 968 | 
            +
                  hadTransitionBefore = !(oldTransition['transition-property'] == 'none' || (oldTransition['transition-property'] == 'all' && oldTransition['transition-duration'][0] == '0'))
         | 
| 969 | 
            +
                  if hadTransitionBefore
         | 
| 970 | 
            +
                    forceRepaint($element) # :(
         | 
| 971 | 
            +
                    $element.css(oldTransition)
         | 
| 972 | 
            +
             | 
| 973 | 
            +
                # Since listening to transitionEnd events is painful, we wait for a timeout
         | 
| 974 | 
            +
                # and then resolve our deferred. Maybe revisit that decision some day.
         | 
| 975 | 
            +
                animationEnd = opts.duration + opts.delay
         | 
| 976 | 
            +
                endTimeout = setTimer animationEnd, ->
         | 
| 977 | 
            +
                  $element.removeClass('up-animating')
         | 
| 978 | 
            +
                  deferred.resolve() unless isDetached($element)
         | 
| 979 | 
            +
                # Clean up in case we're canceled through some other code that
         | 
| 980 | 
            +
                # resolves our deferred.
         | 
| 981 | 
            +
                deferred.then(-> clearTimeout(endTimeout))
         | 
| 982 | 
            +
             | 
| 921 983 | 
             
                # Return the whole deferred and not just return a thenable.
         | 
| 922 984 | 
             
                # Other code will need the possibility to cancel the animation
         | 
| 923 985 | 
             
                # by resolving the deferred.
         | 
| @@ -1299,49 +1361,46 @@ up.util = (($) -> | |
| 1299 1361 |  | 
| 1300 1362 | 
             
                store = undefined
         | 
| 1301 1363 |  | 
| 1364 | 
            +
                optionEvaluator = (name) ->
         | 
| 1365 | 
            +
                  ->
         | 
| 1366 | 
            +
                    value = config[name]
         | 
| 1367 | 
            +
                    if isNumber(value)
         | 
| 1368 | 
            +
                      value
         | 
| 1369 | 
            +
                    else if isFunction(value)
         | 
| 1370 | 
            +
                      value()
         | 
| 1371 | 
            +
                    else
         | 
| 1372 | 
            +
                      undefined
         | 
| 1373 | 
            +
             | 
| 1374 | 
            +
                maxKeys = optionEvaluator('size')
         | 
| 1375 | 
            +
             | 
| 1376 | 
            +
                expiryMillis = optionEvaluator('expiry')
         | 
| 1377 | 
            +
             | 
| 1378 | 
            +
                normalizeStoreKey = (key) ->
         | 
| 1379 | 
            +
                  if config.key
         | 
| 1380 | 
            +
                    config.key(key)
         | 
| 1381 | 
            +
                  else
         | 
| 1382 | 
            +
                    key.toString()
         | 
| 1383 | 
            +
             | 
| 1384 | 
            +
                isEnabled = ->
         | 
| 1385 | 
            +
                  maxKeys() isnt 0 && expiryMillis() isnt 0
         | 
| 1386 | 
            +
             | 
| 1302 1387 | 
             
                clear = ->
         | 
| 1303 1388 | 
             
                  store = {}
         | 
| 1304 1389 |  | 
| 1305 1390 | 
             
                clear()
         | 
| 1306 1391 |  | 
| 1307 1392 | 
             
                log = (args...) ->
         | 
| 1308 | 
            -
                  if config. | 
| 1309 | 
            -
                    args[0] = "[#{config. | 
| 1393 | 
            +
                  if config.logPrefix
         | 
| 1394 | 
            +
                    args[0] = "[#{config.logPrefix}] #{args[0]}"
         | 
| 1310 1395 | 
             
                    up.puts(args...)
         | 
| 1311 1396 |  | 
| 1312 1397 | 
             
                keys = ->
         | 
| 1313 1398 | 
             
                  Object.keys(store)
         | 
| 1314 1399 |  | 
| 1315 | 
            -
                 | 
| 1316 | 
            -
                  if isMissing(config.size)
         | 
| 1317 | 
            -
                    undefined
         | 
| 1318 | 
            -
                  else if isFunction(config.size)
         | 
| 1319 | 
            -
                    config.size()
         | 
| 1320 | 
            -
                  else if isNumber(config.size)
         | 
| 1321 | 
            -
                    config.size
         | 
| 1322 | 
            -
                  else
         | 
| 1323 | 
            -
                    error("Invalid size config: %o", config.size)
         | 
| 1324 | 
            -
             | 
| 1325 | 
            -
                expiryMilis = ->
         | 
| 1326 | 
            -
                  if isMissing(config.expiry)
         | 
| 1327 | 
            -
                    undefined
         | 
| 1328 | 
            -
                  else if isFunction(config.expiry)
         | 
| 1329 | 
            -
                    config.expiry()
         | 
| 1330 | 
            -
                  else if isNumber(config.expiry)
         | 
| 1331 | 
            -
                    config.expiry
         | 
| 1332 | 
            -
                  else
         | 
| 1333 | 
            -
                    error("Invalid expiry config: %o", config.expiry)
         | 
| 1334 | 
            -
             | 
| 1335 | 
            -
                normalizeStoreKey = (key) ->
         | 
| 1336 | 
            -
                  if config.key
         | 
| 1337 | 
            -
                    config.key(key)
         | 
| 1338 | 
            -
                  else
         | 
| 1339 | 
            -
                    key.toString()
         | 
| 1340 | 
            -
             | 
| 1341 | 
            -
                trim = ->
         | 
| 1400 | 
            +
                makeRoomForAnotherKey = ->
         | 
| 1342 1401 | 
             
                  storeKeys = copy(keys())
         | 
| 1343 | 
            -
                   | 
| 1344 | 
            -
                  if  | 
| 1402 | 
            +
                  max = maxKeys()
         | 
| 1403 | 
            +
                  if max && storeKeys.length >= max
         | 
| 1345 1404 | 
             
                    oldestKey = null
         | 
| 1346 1405 | 
             
                    oldestTimestamp = null
         | 
| 1347 1406 | 
             
                    each storeKeys, (key) ->
         | 
| @@ -1361,20 +1420,22 @@ up.util = (($) -> | |
| 1361 1420 | 
             
                  (new Date()).valueOf()
         | 
| 1362 1421 |  | 
| 1363 1422 | 
             
                set = (key, value) ->
         | 
| 1364 | 
            -
                   | 
| 1365 | 
            -
             | 
| 1366 | 
            -
                     | 
| 1367 | 
            -
                     | 
| 1423 | 
            +
                  if isEnabled()
         | 
| 1424 | 
            +
                    makeRoomForAnotherKey()
         | 
| 1425 | 
            +
                    storeKey = normalizeStoreKey(key)
         | 
| 1426 | 
            +
                    store[storeKey] =
         | 
| 1427 | 
            +
                      timestamp: timestamp()
         | 
| 1428 | 
            +
                      value: value
         | 
| 1368 1429 |  | 
| 1369 1430 | 
             
                remove = (key) ->
         | 
| 1370 1431 | 
             
                  storeKey = normalizeStoreKey(key)
         | 
| 1371 1432 | 
             
                  delete store[storeKey]
         | 
| 1372 1433 |  | 
| 1373 1434 | 
             
                isFresh = (entry) ->
         | 
| 1374 | 
            -
                   | 
| 1375 | 
            -
                  if  | 
| 1435 | 
            +
                  millis = expiryMillis()
         | 
| 1436 | 
            +
                  if millis
         | 
| 1376 1437 | 
             
                    timeSinceTouch = timestamp() - entry.timestamp
         | 
| 1377 | 
            -
                    timeSinceTouch <  | 
| 1438 | 
            +
                    timeSinceTouch < millis
         | 
| 1378 1439 | 
             
                  else
         | 
| 1379 1440 | 
             
                    true
         | 
| 1380 1441 |  | 
| @@ -1570,12 +1631,37 @@ up.util = (($) -> | |
| 1570 1631 | 
             
                $error.text(asString)
         | 
| 1571 1632 | 
             
                throw new Error(asString)
         | 
| 1572 1633 |  | 
| 1634 | 
            +
              pluckKey = (object, key) ->
         | 
| 1635 | 
            +
                value = object[key]
         | 
| 1636 | 
            +
                delete object[key]
         | 
| 1637 | 
            +
                value
         | 
| 1638 | 
            +
             | 
| 1573 1639 | 
             
              pluckData = (elementOrSelector, key) ->
         | 
| 1574 1640 | 
             
                $element = $(elementOrSelector)
         | 
| 1575 1641 | 
             
                value = $element.data(key)
         | 
| 1576 1642 | 
             
                $element.removeData(key)
         | 
| 1577 1643 | 
             
                value
         | 
| 1578 1644 |  | 
| 1645 | 
            +
              extractOptions = (args) ->
         | 
| 1646 | 
            +
                lastArg = last(args)
         | 
| 1647 | 
            +
                if isObject(lastArg)
         | 
| 1648 | 
            +
                  args.pop()
         | 
| 1649 | 
            +
                else
         | 
| 1650 | 
            +
                  {}
         | 
| 1651 | 
            +
             | 
| 1652 | 
            +
              ###*
         | 
| 1653 | 
            +
              Returns whether the given element has been detached from the DOM
         | 
| 1654 | 
            +
              (or whether it was never attached).
         | 
| 1655 | 
            +
             | 
| 1656 | 
            +
              @function up.util.isDetached
         | 
| 1657 | 
            +
              @internal
         | 
| 1658 | 
            +
              ###
         | 
| 1659 | 
            +
              isDetached = (element) ->
         | 
| 1660 | 
            +
                element = unJQuery(element)
         | 
| 1661 | 
            +
                # This is by far the fastest way to do this
         | 
| 1662 | 
            +
                not jQuery.contains(document.documentElement, element)
         | 
| 1663 | 
            +
             | 
| 1664 | 
            +
              isDetached: isDetached
         | 
| 1579 1665 | 
             
              requestDataAsArray: requestDataAsArray
         | 
| 1580 1666 | 
             
              requestDataAsQuery: requestDataAsQuery
         | 
| 1581 1667 | 
             
              appendRequestData: appendRequestData
         | 
| @@ -1631,12 +1717,14 @@ up.util = (($) -> | |
| 1631 1717 | 
             
              isUnmodifiedMouseEvent: isUnmodifiedMouseEvent
         | 
| 1632 1718 | 
             
              nullJQuery: nullJQuery
         | 
| 1633 1719 | 
             
              unJQuery: unJQuery
         | 
| 1720 | 
            +
              setTimer: setTimer
         | 
| 1634 1721 | 
             
              nextFrame: nextFrame
         | 
| 1635 1722 | 
             
              measure: measure
         | 
| 1636 1723 | 
             
              temporaryCss: temporaryCss
         | 
| 1637 1724 | 
             
              cssAnimate: cssAnimate
         | 
| 1638 1725 | 
             
              finishCssAnimate: finishCssAnimate
         | 
| 1639 1726 | 
             
              forceCompositing: forceCompositing
         | 
| 1727 | 
            +
              forceRepaint: forceRepaint
         | 
| 1640 1728 | 
             
              escapePressed: escapePressed
         | 
| 1641 1729 | 
             
              copyAttributes: copyAttributes
         | 
| 1642 1730 | 
             
              findWithSelf: findWithSelf
         | 
| @@ -1667,6 +1755,10 @@ up.util = (($) -> | |
| 1667 1755 | 
             
              multiSelector: multiSelector
         | 
| 1668 1756 | 
             
              error: error
         | 
| 1669 1757 | 
             
              pluckData: pluckData
         | 
| 1758 | 
            +
              pluckKey: pluckKey
         | 
| 1759 | 
            +
              extractOptions: extractOptions
         | 
| 1760 | 
            +
              isDetached: isDetached
         | 
| 1761 | 
            +
              noop: noop
         | 
| 1670 1762 |  | 
| 1671 1763 | 
             
            )($)
         | 
| 1672 1764 |  |