unpoly-rails 0.55.1 → 0.56.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +59 -2
- data/dist/unpoly-bootstrap3.js +6 -4
- data/dist/unpoly-bootstrap3.min.js +1 -1
- data/dist/unpoly.js +1323 -805
- data/dist/unpoly.min.js +4 -3
- data/lib/assets/javascripts/unpoly-bootstrap3/{navigation-ext.coffee → feedback-ext.coffee} +2 -0
- data/lib/assets/javascripts/unpoly/browser.coffee.erb +7 -7
- data/lib/assets/javascripts/unpoly/bus.coffee.erb +5 -6
- data/lib/assets/javascripts/unpoly/classes/css_transition.coffee +127 -0
- data/lib/assets/javascripts/unpoly/classes/extract_plan.coffee +1 -1
- data/lib/assets/javascripts/unpoly/classes/motion_tracker.coffee +62 -32
- data/lib/assets/javascripts/unpoly/classes/url_set.coffee +27 -0
- data/lib/assets/javascripts/unpoly/dom.coffee.erb +78 -99
- data/lib/assets/javascripts/unpoly/feedback.coffee +147 -96
- data/lib/assets/javascripts/unpoly/form.coffee.erb +26 -2
- data/lib/assets/javascripts/unpoly/history.coffee +2 -1
- data/lib/assets/javascripts/unpoly/layout.coffee.erb +68 -12
- data/lib/assets/javascripts/unpoly/link.coffee.erb +10 -4
- data/lib/assets/javascripts/unpoly/modal.coffee.erb +11 -9
- data/lib/assets/javascripts/unpoly/{motion.coffee → motion.coffee.erb} +184 -322
- data/lib/assets/javascripts/unpoly/popup.coffee.erb +13 -12
- data/lib/assets/javascripts/unpoly/radio.coffee +1 -1
- data/lib/assets/javascripts/unpoly/syntax.coffee +8 -17
- data/lib/assets/javascripts/unpoly/tooltip.coffee +11 -11
- data/lib/assets/javascripts/unpoly/util.coffee +332 -145
- data/lib/unpoly/rails/version.rb +1 -1
- data/package.json +1 -1
- data/spec_app/Gemfile.lock +1 -1
- data/spec_app/app/assets/javascripts/integration_test.coffee +1 -0
- data/spec_app/app/assets/stylesheets/integration_test.sass +1 -0
- data/spec_app/app/assets/stylesheets/jasmine_specs.sass +4 -0
- data/spec_app/app/views/motion_test/transitions.erb +13 -0
- data/spec_app/app/views/pages/start.erb +1 -0
- data/spec_app/spec/javascripts/helpers/to_be_attached.coffee +5 -0
- data/spec_app/spec/javascripts/helpers/to_be_detached.coffee +5 -0
- data/spec_app/spec/javascripts/helpers/to_contain.js.coffee +1 -1
- data/spec_app/spec/javascripts/helpers/to_have_opacity.coffee +11 -0
- data/spec_app/spec/javascripts/helpers/to_have_own_property.js.coffee +5 -0
- data/spec_app/spec/javascripts/up/dom_spec.js.coffee +217 -102
- data/spec_app/spec/javascripts/up/feedback_spec.js.coffee +162 -44
- data/spec_app/spec/javascripts/up/layout_spec.js.coffee +97 -10
- data/spec_app/spec/javascripts/up/link_spec.js.coffee +3 -3
- data/spec_app/spec/javascripts/up/modal_spec.js.coffee +22 -20
- data/spec_app/spec/javascripts/up/motion_spec.js.coffee +344 -228
- data/spec_app/spec/javascripts/up/popup_spec.js.coffee +1 -1
- data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +1 -1
- data/spec_app/spec/javascripts/up/tooltip_spec.js.coffee +1 -1
- data/spec_app/spec/javascripts/up/util_spec.js.coffee +194 -0
- metadata +11 -4
@@ -283,10 +283,16 @@ up.link = (($) ->
|
|
283
283
|
$link.attr('up-follow', '')
|
284
284
|
|
285
285
|
shouldProcessEvent = (event, $link) ->
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
286
|
+
target = event.target
|
287
|
+
# We never handle events for the right mouse button, or when Shift/CTRL/Meta is pressed
|
288
|
+
return false unless u.isUnmodifiedMouseEvent(event)
|
289
|
+
# If we actually targeted $link, save ourselves the expensive DOM traversal below
|
290
|
+
return true if target == $link.get(0)
|
291
|
+
# If user clicked on a child link of $link, or in an <input> within an [up-expand][up-href]
|
292
|
+
# we want those other elements handle the click.
|
293
|
+
$betterTarget = $(target).closest("a, [up-href], #{up.form.fieldSelector()}").not($link)
|
294
|
+
return false if $betterTarget.length
|
295
|
+
return true
|
290
296
|
|
291
297
|
###**
|
292
298
|
Returns whether the given link has a [safe](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1)
|
@@ -245,10 +245,11 @@ up.modal = (($) ->
|
|
245
245
|
$modal = $(templateHtml())
|
246
246
|
$modal.attr('up-flavor', state.flavor)
|
247
247
|
$modal.attr('up-position', state.position) if u.isPresent(state.position)
|
248
|
+
|
248
249
|
$dialog = $modal.find('.up-modal-dialog')
|
249
|
-
|
250
|
-
$dialog
|
251
|
-
|
250
|
+
dialogStyles = u.only(options, 'width', 'maxWidth', 'height')
|
251
|
+
u.writeInlineStyle($dialog, dialogStyles)
|
252
|
+
|
252
253
|
$modal.find('.up-modal-close').remove() unless state.closable
|
253
254
|
$content = $modal.find('.up-modal-content')
|
254
255
|
# Create an empty element that will match the
|
@@ -272,18 +273,18 @@ up.modal = (($) ->
|
|
272
273
|
if u.documentHasVerticalScrollbar()
|
273
274
|
$body = $('body')
|
274
275
|
scrollbarWidth = u.scrollbarWidth()
|
275
|
-
bodyRightPadding =
|
276
|
+
bodyRightPadding = u.readComputedStyleNumber($body, 'paddingRight')
|
276
277
|
bodyRightShift = scrollbarWidth + bodyRightPadding
|
277
|
-
unshiftBody = u.
|
278
|
-
|
279
|
-
|
278
|
+
unshiftBody = u.writeTemporaryStyle($body,
|
279
|
+
paddingRight: bodyRightShift
|
280
|
+
overflowY: 'hidden'
|
280
281
|
)
|
281
282
|
state.unshifters.push(unshiftBody)
|
282
283
|
up.layout.anchoredRight().each ->
|
283
284
|
$element = $(this)
|
284
|
-
elementRight =
|
285
|
+
elementRight = u.readComputedStyleNumber($element, 'right')
|
285
286
|
elementRightShift = scrollbarWidth + elementRight
|
286
|
-
unshifter = u.
|
287
|
+
unshifter = u.writeTemporaryStyle($element, right: elementRightShift)
|
287
288
|
state.unshifters.push(unshifter)
|
288
289
|
|
289
290
|
|
@@ -456,6 +457,7 @@ up.modal = (($) ->
|
|
456
457
|
options.layer = 'modal'
|
457
458
|
options.failTarget = u.option(options.failTarget, $link.attr('up-fail-target'))
|
458
459
|
options.failLayer = u.option(options.failLayer, $link.attr('up-fail-layer'), 'auto')
|
460
|
+
|
459
461
|
animateOptions = up.motion.animateOptions(options, $link, duration: flavorDefault('openDuration', options.flavor), easing: flavorDefault('openEasing', options.flavor))
|
460
462
|
|
461
463
|
# Although we usually fall back to full page loads if a browser doesn't support pushState,
|
@@ -72,7 +72,7 @@ up.motion = (($) ->
|
|
72
72
|
enabled: true
|
73
73
|
|
74
74
|
reset = ->
|
75
|
-
|
75
|
+
motionTracker.reset()
|
76
76
|
namedAnimations = u.copy(defaultNamedAnimations)
|
77
77
|
namedTransitions = u.copy(defaultNamedTransitions)
|
78
78
|
config.reset()
|
@@ -165,41 +165,41 @@ up.motion = (($) ->
|
|
165
165
|
$element = $(elementOrSelector)
|
166
166
|
options = animateOptions(options)
|
167
167
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
animateWithCss($element, animation, options)
|
177
|
-
else
|
178
|
-
# Error will be converted to rejected promise in a then() callback
|
179
|
-
up.fail('Animation must be a function, animation name or object of CSS properties, but it was %o', animation)
|
168
|
+
animationFn = findAnimationFn(animation)
|
169
|
+
willRun = willAnimate($element, animation, options)
|
170
|
+
|
171
|
+
if willRun
|
172
|
+
runNow = -> animationFn($element, options)
|
173
|
+
motionTracker.claim($element, runNow, options)
|
174
|
+
else
|
175
|
+
skipAnimate($element, animation)
|
180
176
|
|
181
177
|
willAnimate = ($elements, animationOrTransition, options) ->
|
182
178
|
options = animateOptions(options)
|
183
|
-
isEnabled() && !isNone(animationOrTransition) && options.duration > 0 &&
|
179
|
+
isEnabled() && !isNone(animationOrTransition) && options.duration > 0 && !isSingletonElement($elements)
|
180
|
+
|
181
|
+
isSingletonElement = ($element) ->
|
182
|
+
# jQuery's is() returns true if at least one element in the collection matches the selector
|
183
|
+
$element.is('body')
|
184
184
|
|
185
185
|
skipAnimate = ($element, animation) ->
|
186
186
|
if u.isOptions(animation)
|
187
187
|
# If we are given the final animation frame as an object of CSS properties,
|
188
188
|
# the best we can do is to set the final frame without animation.
|
189
|
-
$element
|
189
|
+
u.writeInlineStyle($element, animation)
|
190
190
|
# Signal that the animation is already done.
|
191
191
|
Promise.resolve()
|
192
192
|
|
193
|
+
animCount = 0
|
194
|
+
|
193
195
|
###**
|
194
196
|
Animates the given element's CSS properties using CSS transitions.
|
195
197
|
|
196
|
-
|
197
|
-
|
198
|
+
Does not track the animation, nor does it finishes existing animations
|
199
|
+
(use `up.motion.animate()` for that). It does, however, listen to the motionTracker's
|
200
|
+
finish event.
|
198
201
|
|
199
|
-
|
200
|
-
the duration of the animation.
|
201
|
-
|
202
|
-
@function up.util.cssAnimate
|
202
|
+
@function animateNow
|
203
203
|
@param {Element|jQuery|string} elementOrSelector
|
204
204
|
The element to animate.
|
205
205
|
@param {Object} lastFrame
|
@@ -216,84 +216,10 @@ up.motion = (($) ->
|
|
216
216
|
A promise that fulfills when the animation ends.
|
217
217
|
@internal
|
218
218
|
###
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
'transition-property': transitionProperties.join(', ')
|
224
|
-
'transition-duration': "#{options.duration}ms"
|
225
|
-
'transition-delay': "#{options.delay}ms"
|
226
|
-
'transition-timing-function': options.easing
|
227
|
-
oldTransition = $element.css(Object.keys(transition))
|
228
|
-
|
229
|
-
deferred = u.newDeferred()
|
230
|
-
# May not call this finish() since this would override the global finish()
|
231
|
-
# function in this scope. We really need `let`, which CoffeeScript will never get.
|
232
|
-
fulfill = -> deferred.resolve()
|
233
|
-
|
234
|
-
onTransitionEnd = (event) ->
|
235
|
-
# Check if the transitionend event was caused by our own transition,
|
236
|
-
# and not by some other transition that happens to live on the same element.
|
237
|
-
completedProperty = event.originalEvent.propertyName
|
238
|
-
fulfill() if u.contains(transitionProperties, completedProperty)
|
239
|
-
|
240
|
-
# Animating code is expected to listen to this event to enable external code
|
241
|
-
# to fulfil the animation.
|
242
|
-
onFinish = fulfill
|
243
|
-
|
244
|
-
$element.on(motionTracker.finishEvent, onFinish)
|
245
|
-
|
246
|
-
# Ideally, we want to fulfil when we receive the `transitionend` event
|
247
|
-
$element.on('transitionend', onTransitionEnd)
|
248
|
-
|
249
|
-
# The `transitionend` event might not fire reliably if other transitions
|
250
|
-
# are interfering on the same element. This is why we register a fallback
|
251
|
-
# timeout that forces the animation to fulfil a few ms later.
|
252
|
-
transitionTimingTolerance = 5
|
253
|
-
cancelFallbackTimer = u.setTimer(options.duration + transitionTimingTolerance, fulfill)
|
254
|
-
|
255
|
-
# All clean-up is handled in the following then() handler.
|
256
|
-
# This way it will be run both when the animation finishAnimatees naturally and
|
257
|
-
# when it is finishAnimateed externally.
|
258
|
-
deferred.then ->
|
259
|
-
# Disable all three triggers that would fulfil the motion:
|
260
|
-
$element.off(motionTracker.finishEvent, onFinish)
|
261
|
-
$element.off('transitionend', onTransitionEnd)
|
262
|
-
clearTimeout(cancelFallbackTimer)
|
263
|
-
|
264
|
-
# Elements with compositing might look blurry, so undo that.
|
265
|
-
undoCompositing()
|
266
|
-
|
267
|
-
# To interrupt the running transition we *must* set it to 'none' exactly.
|
268
|
-
# We cannot simply restore the old transition properties because browsers
|
269
|
-
# would simply keep transitioning.
|
270
|
-
$element.css('transition': 'none')
|
271
|
-
|
272
|
-
# Restoring a previous transition involves forcing a repaint, so we only do it if
|
273
|
-
# we know the element was transitioning before.
|
274
|
-
# Note that the default transition for elements is actually "all 0s ease 0s"
|
275
|
-
# instead of "none", although that has the same effect as "none".
|
276
|
-
hadTransitionBefore = !(oldTransition['transition-property'] == 'none' || (oldTransition['transition-property'] == 'all' && oldTransition['transition-duration'][0] == '0'))
|
277
|
-
if hadTransitionBefore
|
278
|
-
# If there is no repaint between the "none" transition and restoring the previous
|
279
|
-
# transition, the browser will simply keep transitioning. I'm sorry.
|
280
|
-
u.forceRepaint($element)
|
281
|
-
$element.css(oldTransition)
|
282
|
-
|
283
|
-
# Push the element into its own compositing layer before we are going
|
284
|
-
# to massively change the element against background.
|
285
|
-
undoCompositing = u.forceCompositing($element)
|
286
|
-
|
287
|
-
# CSS will start animating when we set the `transition-*` properties and then change
|
288
|
-
# the animating properties to the last frame.
|
289
|
-
$element.css(transition)
|
290
|
-
$element.css(lastFrame)
|
291
|
-
|
292
|
-
# Return a promise that fulfills when either the animation ends
|
293
|
-
# or someone finishes the animation.
|
294
|
-
deferred.promise()
|
295
|
-
|
296
|
-
motionTracker.start($element, startCssTransition)
|
219
|
+
animateNow = ($element, lastFrame, options) ->
|
220
|
+
options = u.merge(options, finishEvent: motionTracker.finishEvent)
|
221
|
+
cssTransition = new up.CssTransition($element, lastFrame, options)
|
222
|
+
return cssTransition.start()
|
297
223
|
|
298
224
|
###**
|
299
225
|
Extracts animation-related options from the given options hash.
|
@@ -311,76 +237,12 @@ up.motion = (($) ->
|
|
311
237
|
consolidatedOptions.easing = u.option(userOptions.easing, u.presentAttr($element, 'up-easing'), moduleDefaults.easing, config.easing)
|
312
238
|
consolidatedOptions.duration = Number(u.option(userOptions.duration, u.presentAttr($element, 'up-duration'), moduleDefaults.duration, config.duration))
|
313
239
|
consolidatedOptions.delay = Number(u.option(userOptions.delay, u.presentAttr($element, 'up-delay'), moduleDefaults.delay, config.delay))
|
314
|
-
consolidatedOptions.
|
240
|
+
consolidatedOptions.trackMotion = userOptions.trackMotion # required by up.MotionTracker
|
315
241
|
consolidatedOptions
|
316
242
|
|
317
243
|
findNamedAnimation = (name) ->
|
318
244
|
namedAnimations[name] or up.fail("Unknown animation %o", name)
|
319
245
|
|
320
|
-
###**
|
321
|
-
@function withGhosts
|
322
|
-
@return {Promise}
|
323
|
-
@internal
|
324
|
-
###
|
325
|
-
withGhosts = ($old, $new, options, transitionFn) ->
|
326
|
-
# Don't create ghosts of ghosts in case a transition function calling `morph` recursively.
|
327
|
-
if options.copy == false || $old.is('.up-ghost') || $new.is('.up-ghost')
|
328
|
-
return transitionFn($old, $new, options)
|
329
|
-
|
330
|
-
oldCopy = undefined
|
331
|
-
newCopy = undefined
|
332
|
-
oldScrollTop = undefined
|
333
|
-
newScrollTop = undefined
|
334
|
-
|
335
|
-
$viewport = up.layout.viewportOf($old)
|
336
|
-
|
337
|
-
# Right now $old and $new are visible siblings in the DOM.
|
338
|
-
# Temporarily hide $new while we copy $old and take some measurements.
|
339
|
-
u.temporaryCss $new, display: 'none', ->
|
340
|
-
oldCopy = prependCopy($old, $viewport)
|
341
|
-
# Remember the previous scroll position in case we will reveal $new below.
|
342
|
-
oldScrollTop = $viewport.scrollTop()
|
343
|
-
|
344
|
-
# Hide $old. We will never re-show it.
|
345
|
-
# It's not our job to remove $old from the DOM.
|
346
|
-
$old.hide()
|
347
|
-
|
348
|
-
# Don't animate the scrolling.
|
349
|
-
# We just want to scroll $new into position before we start the enter animation.
|
350
|
-
scrollOptions = u.merge(options, { duration: 0})
|
351
|
-
up.layout.revealOrRestoreScroll($new, scrollOptions).then ->
|
352
|
-
newCopy = prependCopy($new, $viewport)
|
353
|
-
newScrollTop = $viewport.scrollTop()
|
354
|
-
|
355
|
-
# Since we have scrolled the viewport (containing both $old and $new),
|
356
|
-
# we must shift the old copy so it looks like it it is still sitting
|
357
|
-
# in the same position.
|
358
|
-
oldCopy.moveTop(newScrollTop - oldScrollTop)
|
359
|
-
|
360
|
-
# We will let $new take up space in the element flow, but hide it.
|
361
|
-
# The user will only see the two animated ghosts until the transition
|
362
|
-
# is over.
|
363
|
-
# Note that we must **not** use `visibility: hidden` to hide the new
|
364
|
-
# element. This would delay browser painting until the element is
|
365
|
-
# shown again, causing a flicker while the browser is painting.
|
366
|
-
restoreNewOpacity = u.temporaryCss($new, opacity: '0')
|
367
|
-
|
368
|
-
# Perform the transition on the ghosts.
|
369
|
-
transitionDone = transitionFn(oldCopy.$ghost, newCopy.$ghost, options)
|
370
|
-
|
371
|
-
# The animations on both ghosts should finish if someone calls finish()
|
372
|
-
# on either of the original elements.
|
373
|
-
$bothGhosts = oldCopy.$ghost.add(newCopy.$ghost)
|
374
|
-
$bothOriginals = $old.add($new)
|
375
|
-
motionTracker.forwardFinishEvent($bothOriginals, $bothGhosts, transitionDone)
|
376
|
-
|
377
|
-
transitionDone.then ->
|
378
|
-
# This will be called when the transition in the block is either done
|
379
|
-
# or when it is finished by triggering up:motion:finish on either element.
|
380
|
-
restoreNewOpacity()
|
381
|
-
oldCopy.$bounds.remove()
|
382
|
-
newCopy.$bounds.remove()
|
383
|
-
|
384
246
|
###**
|
385
247
|
Completes [animations](/up.animate) and [transitions](/up.morph).
|
386
248
|
|
@@ -402,12 +264,16 @@ up.motion = (($) ->
|
|
402
264
|
motionTracker.finish(elementOrSelector)
|
403
265
|
|
404
266
|
###**
|
405
|
-
Performs an animated transition between
|
267
|
+
Performs an animated transition between the `source` and `target` elements.
|
268
|
+
|
406
269
|
Transitions are implement by performing two animations in parallel,
|
407
|
-
causing
|
270
|
+
causing `source` to disappear and the `target` to appear.
|
408
271
|
|
409
|
-
|
410
|
-
|
272
|
+
- `target` is [inserted before](https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore) `source`
|
273
|
+
- `source` is removed from the [document flow](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Positioning) with `position: absolute`.
|
274
|
+
It will be positioned over its original place in the flow that is now occupied by `target`.
|
275
|
+
- Both `source` and `target` are animated in parallel
|
276
|
+
- `source` is removed from the DOM
|
411
277
|
|
412
278
|
\#\#\# Named transitions
|
413
279
|
|
@@ -463,7 +329,7 @@ up.motion = (($) ->
|
|
463
329
|
Whether to reveal the new element by scrolling its parent viewport.
|
464
330
|
@return {Promise}
|
465
331
|
A promise that fulfills when the transition ends.
|
466
|
-
@
|
332
|
+
@experimental
|
467
333
|
###
|
468
334
|
morph = (source, target, transitionObject, options) ->
|
469
335
|
options = u.options(options)
|
@@ -474,26 +340,66 @@ up.motion = (($) ->
|
|
474
340
|
$both = $old.add($new)
|
475
341
|
|
476
342
|
transitionFn = findTransitionFn(transitionObject)
|
477
|
-
willMorph = willAnimate($
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
343
|
+
willMorph = willAnimate($old, transitionFn, options)
|
344
|
+
|
345
|
+
options.afterInsert ||= u.noop
|
346
|
+
options.beforeDetach ||= u.noop
|
347
|
+
options.afterDetach ||= u.noop
|
348
|
+
|
349
|
+
scrollNew = ->
|
350
|
+
# Don't animate the scrolling. The { duration } option was meant for the transition.
|
351
|
+
scrollOptions = u.merge(options, duration: 0)
|
352
|
+
# Scroll $new into position before we start the enter animation.
|
353
|
+
up.layout.revealOrRestoreScroll($new, scrollOptions)
|
354
|
+
|
355
|
+
if willMorph
|
356
|
+
if motionTracker.isActive($old) && options.trackMotion is false
|
357
|
+
return transitionFn($old, $new, options)
|
358
|
+
|
359
|
+
up.puts 'Morphing %o to %o with transition %o', $old.get(0), $new.get(0), transitionObject
|
360
|
+
|
361
|
+
$viewport = up.layout.viewportOf($old)
|
362
|
+
scrollTopBeforeReveal = $viewport.scrollTop()
|
363
|
+
|
364
|
+
oldRemote = up.layout.absolutize $old,
|
365
|
+
# Because the insertion will shift elements visually, we must delay insertion
|
366
|
+
# until absolutize() has measured the bounding box of the old element.
|
367
|
+
afterMeasure: ->
|
368
|
+
$new.insertBefore($old)
|
369
|
+
options.afterInsert()
|
370
|
+
|
371
|
+
trackable = ->
|
372
|
+
# Scroll $new into position before we start the enter animation.
|
373
|
+
promise = scrollNew()
|
374
|
+
|
375
|
+
promise = promise.then ->
|
376
|
+
# Since we have scrolled the viewport (containing both $old and $new),
|
377
|
+
# we must shift the old copy so it looks like it it is still sitting
|
378
|
+
# in the same position.
|
379
|
+
scrollTopAfterReveal = $viewport.scrollTop()
|
380
|
+
oldRemote.moveTop(scrollTopAfterReveal - scrollTopBeforeReveal)
|
381
|
+
|
382
|
+
transitionFn($old, $new, options)
|
383
|
+
|
384
|
+
promise = promise.then ->
|
385
|
+
options.beforeDetach()
|
386
|
+
$old.detach()
|
387
|
+
oldRemote.$bounds.remove()
|
388
|
+
options.afterDetach()
|
389
|
+
|
390
|
+
return promise
|
391
|
+
|
392
|
+
motionTracker.claim($both, trackable, options)
|
393
|
+
|
493
394
|
else
|
494
|
-
|
495
|
-
|
496
|
-
|
395
|
+
options.beforeDetach()
|
396
|
+
# Swapping the elements directly with replaceWith() will cause
|
397
|
+
# jQuery to remove all data attributes, which we use to store destructors
|
398
|
+
swapElementsDirectly($old, $new)
|
399
|
+
options.afterInsert()
|
400
|
+
options.afterDetach()
|
401
|
+
promise = scrollNew()
|
402
|
+
return promise
|
497
403
|
|
498
404
|
findTransitionFn = (object) ->
|
499
405
|
if isNone(object)
|
@@ -501,90 +407,45 @@ up.motion = (($) ->
|
|
501
407
|
else if u.isFunction(object)
|
502
408
|
object
|
503
409
|
else if u.isArray(object)
|
504
|
-
|
505
|
-
# A composition of two "none" animations is again a "none" animation
|
506
|
-
undefined
|
507
|
-
else
|
508
|
-
($old, $new, options) -> Promise.all([
|
509
|
-
animate($old, object[0], options),
|
510
|
-
animate($new, object[1], options)
|
511
|
-
])
|
410
|
+
composeTransitionFn(object...)
|
512
411
|
else if u.isString(object)
|
513
412
|
if object.indexOf('/') >= 0 # Compose a transition from two animation names
|
514
|
-
|
413
|
+
composeTransitionFn(object.split('/')...)
|
515
414
|
else if namedTransition = namedTransitions[object]
|
516
415
|
findTransitionFn(namedTransition)
|
416
|
+
else
|
417
|
+
up.fail("Unknown transition %o", object)
|
517
418
|
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
# We just want to scroll $new into position before we start the enter animation.
|
532
|
-
scrollOptions = u.merge(options, { duration: 0})
|
419
|
+
composeTransitionFn = (oldAnimation, newAnimation) ->
|
420
|
+
if isNone(oldAnimation) && isNone(oldAnimation)
|
421
|
+
# A composition of two null-animations is a null-transform
|
422
|
+
# and should be skipped.
|
423
|
+
undefined
|
424
|
+
else
|
425
|
+
oldAnimationFn = findAnimationFn(oldAnimation) || u.asyncNoop
|
426
|
+
newAnimationFn = findAnimationFn(newAnimation) || u.asyncNoop
|
427
|
+
($old, $new, options) ->
|
428
|
+
Promise.all([
|
429
|
+
oldAnimationFn($old, options),
|
430
|
+
newAnimationFn($new, options)
|
431
|
+
])
|
533
432
|
|
534
|
-
|
535
|
-
|
536
|
-
|
433
|
+
findAnimationFn = (object) ->
|
434
|
+
if isNone(object)
|
435
|
+
undefined
|
436
|
+
else if u.isFunction(object)
|
437
|
+
object
|
438
|
+
else if u.isString(object)
|
439
|
+
findNamedAnimation(object)
|
440
|
+
else if u.isOptions(object)
|
441
|
+
($element, options) -> animateNow($element, object, options)
|
442
|
+
else
|
443
|
+
up.fail('Unknown animation %o', object)
|
537
444
|
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
elementDims = u.measure($element, relative: true, inner: true)
|
543
|
-
|
544
|
-
$ghost = $element.clone()
|
545
|
-
$ghost.find('script').remove()
|
546
|
-
$ghost.css
|
547
|
-
# If the element had a layout context before, make sure the
|
548
|
-
# ghost will have layout context as well (and vice versa).
|
549
|
-
position: if $element.css('position') == 'static' then 'static' else 'relative'
|
550
|
-
top: 'auto'
|
551
|
-
right: 'auto'
|
552
|
-
bottom: 'auto'
|
553
|
-
left: 'auto'
|
554
|
-
width: '100%'
|
555
|
-
height: '100%'
|
556
|
-
$ghost.addClass('up-ghost')
|
557
|
-
|
558
|
-
# Wrap the ghost in another container so its margin can expand
|
559
|
-
# freely. If we would position the element directly (old implementation),
|
560
|
-
# it would gain a layout context which cannot be crossed by margins.
|
561
|
-
$bounds = $('<div class="up-bounds"></div>')
|
562
|
-
$bounds.css(position: 'absolute')
|
563
|
-
$bounds.css(elementDims)
|
564
|
-
|
565
|
-
top = elementDims.top
|
566
|
-
|
567
|
-
moveTop = (diff) ->
|
568
|
-
if diff != 0
|
569
|
-
top += diff
|
570
|
-
$bounds.css(top: top)
|
571
|
-
|
572
|
-
$ghost.appendTo($bounds)
|
573
|
-
$bounds.insertBefore($element)
|
574
|
-
|
575
|
-
# In theory, $ghost should now sit over $element perfectly.
|
576
|
-
# However, $element might collapse its margin against a previous sibling
|
577
|
-
# element, and $ghost does not have the same sibling.
|
578
|
-
# So we manually correct $ghost's top position so it aligns with $element.
|
579
|
-
moveTop($element.offset().top - $ghost.offset().top)
|
580
|
-
|
581
|
-
$fixedElements = up.layout.fixedChildren($ghost)
|
582
|
-
for fixedElement in $fixedElements
|
583
|
-
u.fixedToAbsolute(fixedElement, $viewport)
|
584
|
-
|
585
|
-
$ghost: $ghost
|
586
|
-
$bounds: $bounds
|
587
|
-
moveTop: moveTop
|
445
|
+
swapElementsDirectly = ($old, $new) ->
|
446
|
+
# jQuery will actually let us .insertBefore the new <body> tag,
|
447
|
+
# but that's probably bad Karma.
|
448
|
+
$old.replaceWith($new)
|
588
449
|
|
589
450
|
###**
|
590
451
|
Defines a named transition.
|
@@ -620,7 +481,7 @@ up.motion = (($) ->
|
|
620
481
|
@stable
|
621
482
|
###
|
622
483
|
registerTransition = (name, transition) ->
|
623
|
-
namedTransitions[name] = transition
|
484
|
+
namedTransitions[name] = findTransitionFn(transition)
|
624
485
|
|
625
486
|
###**
|
626
487
|
Defines a named animation.
|
@@ -629,7 +490,7 @@ up.motion = (($) ->
|
|
629
490
|
|
630
491
|
up.animation('fade-in', function($element, options) {
|
631
492
|
$element.css(opacity: 0);
|
632
|
-
up.animate($
|
493
|
+
up.animate($element, { opacity: 1 }, options);
|
633
494
|
})
|
634
495
|
|
635
496
|
It is recommended that your definitions always end by calling
|
@@ -655,7 +516,7 @@ up.motion = (($) ->
|
|
655
516
|
@stable
|
656
517
|
###
|
657
518
|
registerAnimation = (name, animation) ->
|
658
|
-
namedAnimations[name] = animation
|
519
|
+
namedAnimations[name] = findAnimationFn(animation)
|
659
520
|
|
660
521
|
snapshot = ->
|
661
522
|
defaultNamedAnimations = u.copy(namedAnimations)
|
@@ -669,112 +530,113 @@ up.motion = (($) ->
|
|
669
530
|
@internal
|
670
531
|
###
|
671
532
|
isNone = (animationOrTransition) ->
|
672
|
-
# false, undefined, null and the string "none" are all ways to skip animations
|
673
|
-
!animationOrTransition || animationOrTransition == 'none' ||
|
533
|
+
# false, undefined, '', null and the string "none" are all ways to skip animations
|
534
|
+
!animationOrTransition || animationOrTransition == 'none' || u.isBlank(animationOrTransition)
|
674
535
|
|
675
|
-
registerAnimation('fade-in', ($
|
676
|
-
|
677
|
-
|
536
|
+
registerAnimation('fade-in', ($element, options) ->
|
537
|
+
u.writeInlineStyle($element, opacity: 0)
|
538
|
+
animateNow($element, { opacity: 1 }, options)
|
678
539
|
)
|
679
540
|
|
680
|
-
registerAnimation('fade-out', ($
|
681
|
-
|
682
|
-
|
541
|
+
registerAnimation('fade-out', ($element, options) ->
|
542
|
+
u.writeInlineStyle($element, opacity: 1)
|
543
|
+
animateNow($element, { opacity: 0 }, options)
|
683
544
|
)
|
684
545
|
|
685
546
|
translateCss = (x, y) ->
|
686
547
|
{ transform: "translate(#{x}px, #{y}px)" }
|
687
548
|
|
688
|
-
registerAnimation('move-to-top', ($
|
689
|
-
|
690
|
-
box = u.measure($
|
549
|
+
registerAnimation('move-to-top', ($element, options) ->
|
550
|
+
u.writeInlineStyle($element, translateCss(0, 0))
|
551
|
+
box = u.measure($element)
|
691
552
|
travelDistance = box.top + box.height
|
692
|
-
|
553
|
+
animateNow($element, translateCss(0, -travelDistance), options)
|
693
554
|
)
|
694
555
|
|
695
|
-
registerAnimation('move-from-top', ($
|
696
|
-
|
697
|
-
box = u.measure($
|
556
|
+
registerAnimation('move-from-top', ($element, options) ->
|
557
|
+
u.writeInlineStyle($element, translateCss(0, 0))
|
558
|
+
box = u.measure($element)
|
698
559
|
travelDistance = box.top + box.height
|
699
|
-
|
700
|
-
|
560
|
+
u.writeInlineStyle($element, translateCss(0, -travelDistance))
|
561
|
+
animateNow($element, translateCss(0, 0), options)
|
701
562
|
)
|
702
563
|
|
703
|
-
registerAnimation('move-to-bottom', ($
|
704
|
-
|
705
|
-
box = u.measure($
|
564
|
+
registerAnimation('move-to-bottom', ($element, options) ->
|
565
|
+
u.writeInlineStyle($element, translateCss(0, 0))
|
566
|
+
box = u.measure($element)
|
706
567
|
travelDistance = u.clientSize().height - box.top
|
707
|
-
|
568
|
+
animateNow($element, translateCss(0, travelDistance), options)
|
708
569
|
)
|
709
570
|
|
710
|
-
registerAnimation('move-from-bottom', ($
|
711
|
-
|
712
|
-
box = u.measure($
|
571
|
+
registerAnimation('move-from-bottom', ($element, options) ->
|
572
|
+
u.writeInlineStyle($element, translateCss(0, 0))
|
573
|
+
box = u.measure($element)
|
713
574
|
travelDistance = u.clientSize().height - box.top
|
714
|
-
|
715
|
-
|
575
|
+
u.writeInlineStyle($element, translateCss(0, travelDistance))
|
576
|
+
animateNow($element, translateCss(0, 0), options)
|
716
577
|
)
|
717
578
|
|
718
|
-
registerAnimation('move-to-left', ($
|
719
|
-
|
720
|
-
box = u.measure($
|
579
|
+
registerAnimation('move-to-left', ($element, options) ->
|
580
|
+
u.writeInlineStyle($element, translateCss(0, 0))
|
581
|
+
box = u.measure($element)
|
721
582
|
travelDistance = box.left + box.width
|
722
|
-
|
583
|
+
animateNow($element, translateCss(-travelDistance, 0), options)
|
723
584
|
)
|
724
585
|
|
725
|
-
registerAnimation('move-from-left', ($
|
726
|
-
|
727
|
-
box = u.measure($
|
586
|
+
registerAnimation('move-from-left', ($element, options) ->
|
587
|
+
u.writeInlineStyle($element, translateCss(0, 0))
|
588
|
+
box = u.measure($element)
|
728
589
|
travelDistance = box.left + box.width
|
729
|
-
|
730
|
-
|
590
|
+
u.writeInlineStyle($element, translateCss(-travelDistance, 0))
|
591
|
+
animateNow($element, translateCss(0, 0), options)
|
731
592
|
)
|
732
593
|
|
733
|
-
registerAnimation('move-to-right', ($
|
734
|
-
|
735
|
-
box = u.measure($
|
594
|
+
registerAnimation('move-to-right', ($element, options) ->
|
595
|
+
u.writeInlineStyle($element, translateCss(0, 0))
|
596
|
+
box = u.measure($element)
|
736
597
|
travelDistance = u.clientSize().width - box.left
|
737
|
-
|
598
|
+
animateNow($element, translateCss(travelDistance, 0), options)
|
738
599
|
)
|
739
600
|
|
740
|
-
registerAnimation('move-from-right', ($
|
741
|
-
|
742
|
-
box = u.measure($
|
601
|
+
registerAnimation('move-from-right', ($element, options) ->
|
602
|
+
u.writeInlineStyle($element, translateCss(0, 0))
|
603
|
+
box = u.measure($element)
|
743
604
|
travelDistance = u.clientSize().width - box.left
|
744
|
-
|
745
|
-
|
605
|
+
u.writeInlineStyle($element, translateCss(travelDistance, 0))
|
606
|
+
animateNow($element, translateCss(0, 0), options)
|
746
607
|
)
|
747
608
|
|
748
|
-
registerAnimation('roll-down', ($
|
749
|
-
fullHeight = $
|
750
|
-
styleMemo = u.
|
609
|
+
registerAnimation('roll-down', ($element, options) ->
|
610
|
+
fullHeight = $element.height()
|
611
|
+
styleMemo = u.writeTemporaryStyle($element,
|
751
612
|
height: '0px'
|
752
613
|
overflow: 'hidden'
|
753
614
|
)
|
754
|
-
deferred = animate($
|
615
|
+
deferred = animate($element, { height: "#{fullHeight}px" }, options)
|
755
616
|
deferred.then(styleMemo)
|
756
617
|
deferred
|
757
618
|
)
|
758
619
|
|
759
|
-
registerTransition('move-left', 'move-to-left
|
760
|
-
registerTransition('move-right', 'move-to-right
|
761
|
-
registerTransition('move-up', 'move-to-top
|
762
|
-
registerTransition('move-down', 'move-to-bottom
|
763
|
-
registerTransition('cross-fade', 'fade-out
|
620
|
+
registerTransition('move-left', ['move-to-left', 'move-from-right'])
|
621
|
+
registerTransition('move-right', ['move-to-right', 'move-from-left'])
|
622
|
+
registerTransition('move-up', ['move-to-top', 'move-from-bottom'])
|
623
|
+
registerTransition('move-down', ['move-to-bottom', 'move-from-top'])
|
624
|
+
registerTransition('cross-fade', ['fade-out', 'fade-in'])
|
764
625
|
|
765
626
|
up.on 'up:framework:booted', snapshot
|
766
627
|
up.on 'up:framework:reset', reset
|
767
628
|
|
629
|
+
<% if ENV['JS_KNIFE'] %>knife: eval(Knife.point)<% end %>
|
768
630
|
morph: morph
|
769
631
|
animate: animate
|
770
632
|
animateOptions: animateOptions
|
771
633
|
willAnimate: willAnimate
|
772
634
|
finish: finish
|
635
|
+
finishCount: -> motionTracker.finishCount
|
773
636
|
transition: registerTransition
|
774
637
|
animation: registerAnimation
|
775
638
|
config: config
|
776
639
|
isEnabled: isEnabled
|
777
|
-
prependCopy: prependCopy
|
778
640
|
isNone: isNone
|
779
641
|
|
780
642
|
)(jQuery)
|