unpoly-rails 0.23.0 → 0.24.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of unpoly-rails might be problematic. Click here for more details.

Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/dist/unpoly-bootstrap3.js +1 -1
  4. data/dist/unpoly-bootstrap3.min.js +1 -1
  5. data/dist/unpoly.css +18 -8
  6. data/dist/unpoly.js +498 -265
  7. data/dist/unpoly.min.css +1 -1
  8. data/dist/unpoly.min.js +3 -3
  9. data/lib/assets/javascripts/unpoly-bootstrap3/modal-ext.js.coffee +5 -2
  10. data/lib/assets/javascripts/unpoly/flow.js.coffee +3 -1
  11. data/lib/assets/javascripts/unpoly/form.js.coffee +3 -6
  12. data/lib/assets/javascripts/unpoly/layout.js.coffee +1 -1
  13. data/lib/assets/javascripts/unpoly/link.js.coffee +4 -2
  14. data/lib/assets/javascripts/unpoly/log.js.coffee +2 -2
  15. data/lib/assets/javascripts/unpoly/modal.js.coffee +125 -36
  16. data/lib/assets/javascripts/unpoly/motion.js.coffee +75 -37
  17. data/lib/assets/javascripts/unpoly/navigation.js.coffee +38 -26
  18. data/lib/assets/javascripts/unpoly/proxy.js.coffee +77 -52
  19. data/lib/assets/javascripts/unpoly/syntax.js.coffee +1 -0
  20. data/lib/assets/javascripts/unpoly/tooltip.js.coffee +2 -0
  21. data/lib/assets/javascripts/unpoly/util.js.coffee +138 -46
  22. data/lib/assets/stylesheets/unpoly/link.css.sass +1 -1
  23. data/lib/assets/stylesheets/unpoly/modal.css.sass +28 -15
  24. data/lib/unpoly/rails/version.rb +1 -1
  25. data/spec_app/Gemfile.lock +7 -7
  26. data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +2 -0
  27. data/spec_app/spec/javascripts/helpers/to_contain.js.coffee +5 -0
  28. data/spec_app/spec/javascripts/up/flow_spec.js.coffee +2 -2
  29. data/spec_app/spec/javascripts/up/history_spec.js.coffee +6 -4
  30. data/spec_app/spec/javascripts/up/link_spec.js.coffee +114 -5
  31. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +28 -18
  32. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +112 -7
  33. data/spec_app/spec/javascripts/up/navigation_spec.js.coffee +157 -121
  34. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +1 -1
  35. data/spec_app/spec/javascripts/up/proxy_spec.js.coffee +42 -3
  36. data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +1 -2
  37. data/spec_app/spec/javascripts/up/util_spec.js.coffee +26 -1
  38. data/spec_app/vendor/assets/bower_components/jquery/.bower.json +1 -1
  39. metadata +3 -3
  40. 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
- SELECTORS_SECTION = ['a', '[up-href]', '[up-alias]']
40
- SELECTOR_SECTION = SELECTORS_SECTION.join(', ')
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
- sectionClicked = ($section) ->
133
- unmarkActive()
134
- $section = enlargeClickArea($section)
135
- $section.addClass(CLASS_ACTIVE)
136
-
137
- enlargeClickArea = ($section) ->
138
- u.presence($section.parents(SELECTOR_SECTION)) || $section
139
-
140
- unmarkActive = ->
141
- $(SELECTOR_ACTIVE).removeClass(CLASS_ACTIVE)
142
-
143
- up.on 'click', SELECTOR_SECTION, (event, $section) ->
144
- if u.isUnmodifiedMouseEvent(event) && !$section.is('[up-instant]')
145
- sectionClicked($section)
146
-
147
- up.on 'mousedown', SELECTOR_SECTION_INSTANT, (event, $section) ->
148
- if u.isUnmodifiedMouseEvent(event)
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
- cancelBusyDelay = ->
119
+ cancelSlowDelay = ->
159
120
  clearTimeout(slowDelayTimer)
160
121
  slowDelayTimer = null
161
122
 
162
123
  reset = ->
163
124
  $waitingLink = null
164
125
  cancelPreloadDelay()
165
- cancelBusyDelay()
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} request.url
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 = (options) ->
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
- if config.slowDelay > 0
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.
@@ -239,6 +239,7 @@ up.syntax = (($) ->
239
239
 
240
240
  Examples for built-in macros are [`up-dash`](/up-dash) and [`up-expand`](/up-expand).
241
241
 
242
+ @function up.macro
242
243
  @param {String} selector
243
244
  The selector to match.
244
245
  @param {Object} options
@@ -52,6 +52,8 @@ up.tooltip = (($) ->
52
52
  closeAnimation: 'fade-out'
53
53
 
54
54
  reset = ->
55
+ # Destroy the tooltip container regardless whether it's currently in a closing animation
56
+ close(animation: false)
55
57
  config.reset()
56
58
 
57
59
  setPosition = ($link, $tooltip, position) ->
@@ -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
- # We don't finish an existing animation here, since
905
- # the public API `up.motion.animate` already does this.
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
- withoutTransition = temporaryCss($element, transition)
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
- deferred.then(-> $element.removeData(ANIMATION_DEFERRED_KEY))
919
- endTimeout = setTimeout((-> deferred.resolve()), opts.duration + opts.delay)
920
- deferred.then(-> clearTimeout(endTimeout)) # clean up in case we're canceled
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.log
1309
- args[0] = "[#{config.log}] #{args[0]}"
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
- maxSize = ->
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
- size = maxSize()
1344
- if size && storeKeys.length > size
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
- storeKey = normalizeStoreKey(key)
1365
- store[storeKey] =
1366
- timestamp: timestamp()
1367
- value: value
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
- expiry = expiryMilis()
1375
- if expiry
1435
+ millis = expiryMillis()
1436
+ if millis
1376
1437
  timeSinceTouch = timestamp() - entry.timestamp
1377
- timeSinceTouch < expiryMilis()
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