unpoly-rails 0.55.1 → 0.56.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 +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)
|