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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ec9d0678d3c0fa042de8cb5e10f94014dfbe7f36
4
- data.tar.gz: 28e851c819c201c8e65fd1e1945a3b44f4310e56
3
+ metadata.gz: c68b539b44ddfe0d7ded37b84b03a668efcb4d81
4
+ data.tar.gz: ae1d157e125c1250b4f0324bc6b9b8861e5a9129
5
5
  SHA512:
6
- metadata.gz: 8a70cad251b68e888be109193209eea3101aab04efb98a62abd0e2a282ef10977f64569c208cbd9041c4f7c9bc74ea5147e140341badf15b5f58c91eb1e4efc8
7
- data.tar.gz: 6b13838de87ece08a23be26531c3bfa0258cffc958f3495d0a692288bba6bf3859c55e020cd63c15842c37227e5f618c3484415c9530e3dd19912797b6e001c3
6
+ metadata.gz: 14726bfdd87382037bc837893c7a62b889233d6b8904c72ba6131a3ffdfb9b13bdb70daa4e63af70e4755eafd85f4b5392bfefe9cc69c274ad1aa49dd20996ce
7
+ data.tar.gz: 5987b1663195c17b44b56b46323e1d38142518c0a52f614c69f0d77f197ba7f9a30af92a526d764122fd02fb8c2ef40c244df306b2031813300dc9bcb1abfcc4
data/CHANGELOG.md CHANGED
@@ -14,6 +14,73 @@ Unreleased
14
14
  ### Breaking changes
15
15
 
16
16
 
17
+ 0.24.0
18
+ ------
19
+
20
+ ### Compatible changes
21
+
22
+ - New function [`up.modal.extract`](/up.modal.extract) to open a modal from an
23
+ existing HTML string.
24
+ - [`up.ajax`](/up.ajax) now also accepts the URL as a first string argument.
25
+ - [Expanded](/up.expand) links to modals or popups now get a pointer cursor via CSS
26
+ - New options for [up.modal.config](/up.modal.config):
27
+ - `up.modal.config.openDuration`
28
+ - `up.modal.config.closeDuration`
29
+ - `up.modal.config.openEasing`
30
+ - `up.modal.config.closeEasing`
31
+ - `up.modal.config.backdropOpenAnimation`
32
+ - `up.modal.config.backdropCloseAnimation`
33
+ - Also see the breaking changes regarding modal structure below.
34
+ - Calling [`up.motion.finish`](/up.motion.finish) without arguments will now
35
+ complete all animations and transitions on the screen.
36
+ - Fix a bug where [`up.motion.finish`](/up.motion.finish) would not cancel CSS transitions that were still in progress.
37
+ - Fix a bug where [`up-active`](/up-active) classes where not removed from links when the destination
38
+ was already [preloaded](/up.preload).
39
+
40
+
41
+ ### Breaking changes
42
+
43
+ - Animations when opening or closing a [modal](/up.modal) now only affect the viewport around the dialog.
44
+ The backdrop is animated separately. This allows animations like "zoom in", which would look strange if
45
+ the backdrop would zoom in together with the dialog.
46
+ - The modal's HTML structure has been changed to include a `.up-modal-backdrop` element:
47
+
48
+ ```
49
+ <div class="up-modal">
50
+ <div class="up-modal-backdrop">
51
+ <div class="up-modal-viewport">
52
+ <div class="up-modal-dialog">
53
+ <div class="up-modal-content">
54
+ ...
55
+ </div>
56
+ <div class="up-modal-close" up-close>X</div>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ ```
61
+
62
+ - The `z-index` properties for modal elements have been [changed](https://github.com/unpoly/unpoly/blob/master/lib/assets/stylesheets/unpoly/modal.css.sass).
63
+ They might change again in the future.
64
+ - The modal will now take over the document's scrollbars after the open animation has finished.
65
+ In earlier versions the modal took over as soon as the animation had started.
66
+ - Calling [`up.motion.finish`](/up.motion.finish) with an element will now also
67
+ complete animations/transitions on children of the given element.
68
+
69
+
70
+ 0.23.1
71
+ ------
72
+
73
+ ### Compatible changes
74
+
75
+ - [Animations](/up.motion) `move-to-*` and `move-from-*` now use CSS transforms instead of manipulating the
76
+ bounding box margins.
77
+ - Fix [`up.util.trim`](/up.util.trim) not working properly.
78
+ - [`up.morph`](/up.morph) no longer throws an error if called without an `options` object
79
+ - Custom transitions can now call [`up.morph`](/up.morph) to refer to other transitions
80
+ - Fix a bug where following a link to a [preloaded](/up-preload) destination would keep the
81
+ link marked with a [up-active](/up-active) class forever.
82
+
83
+
17
84
  0.23.0
18
85
  ------
19
86
 
@@ -41,6 +108,7 @@ Unreleased
41
108
  - Fix a bug where a link would be followed multiple times if the link's
42
109
  click area was expanded using [`[up-expand]`](/up-expand) and if the
43
110
  link also had an [`up-dash`](/up-dash) attribute.
111
+ - [`up.destroy`](/up.destroy) now returns a resolved deferred if the given selector or jQuery collection does not exist
44
112
 
45
113
 
46
114
  0.22.0
@@ -15,7 +15,7 @@
15
15
 
16
16
  }).call(this);
17
17
  (function() {
18
- up.modal.config.template = "<div class=\"up-modal\">\n <div class=\"up-modal-dialog modal-dialog\">\n <div class=\"up-modal-content modal-content\"></div>\n </div>\n</div>";
18
+ up.modal.config.template = "<div class=\"up-modal\">\n <div class=\"up-modal-backdrop\"></div>\n <div class=\"up-modal-viewport\">\n <div class=\"up-modal-dialog modal-dialog\">\n <div class=\"up-modal-content modal-content\"></div>\n </div>\n </div>\n</div>";
19
19
 
20
20
  }).call(this);
21
21
  (function() {
@@ -1 +1 @@
1
- (function(){up.form.config.validateTargets.unshift(".form-group:has(&)")}).call(this),function(){up.layout.config.fixedTop.push(".navbar-fixed-top"),up.layout.config.fixedBottom.push(".navbar-fixed-bottom"),up.layout.config.anchoredRight.push(".navbar-fixed-top"),up.layout.config.anchoredRight.push(".navbar-fixed-bottom"),up.layout.config.anchoredRight.push(".footer")}.call(this),function(){up.modal.config.template='<div class="up-modal">\n <div class="up-modal-dialog modal-dialog">\n <div class="up-modal-content modal-content"></div>\n </div>\n</div>'}.call(this),function(){up.navigation.config.currentClasses.push("active")}.call(this),function(){}.call(this);
1
+ (function(){up.form.config.validateTargets.unshift(".form-group:has(&)")}).call(this),function(){up.layout.config.fixedTop.push(".navbar-fixed-top"),up.layout.config.fixedBottom.push(".navbar-fixed-bottom"),up.layout.config.anchoredRight.push(".navbar-fixed-top"),up.layout.config.anchoredRight.push(".navbar-fixed-bottom"),up.layout.config.anchoredRight.push(".footer")}.call(this),function(){up.modal.config.template='<div class="up-modal">\n <div class="up-modal-backdrop"></div>\n <div class="up-modal-viewport">\n <div class="up-modal-dialog modal-dialog">\n <div class="up-modal-content modal-content"></div>\n </div>\n </div>\n</div>'}.call(this),function(){up.navigation.config.currentClasses.push("active")}.call(this),function(){}.call(this);
data/dist/unpoly.css CHANGED
@@ -14,22 +14,32 @@
14
14
  max-width: 100%;
15
15
  box-sizing: border-box;
16
16
  z-index: 99999999; }
17
- [up-href][up-follow], [up-href][up-target] {
17
+ [up-href] {
18
18
  cursor: pointer; }
19
- .up-modal {
19
+ .up-modal-backdrop {
20
+ z-index: 10000;
21
+ background-color: rgba(90, 90, 90, 0.4);
22
+ position: fixed;
23
+ top: 0;
24
+ right: 0;
25
+ bottom: 0;
26
+ left: 0; }
27
+
28
+ .up-modal-viewport {
29
+ z-index: 11000;
20
30
  position: fixed;
21
31
  top: 0;
22
32
  left: 0;
23
33
  bottom: 0;
24
34
  right: 0;
25
- z-index: 10000;
26
- background-color: rgba(90, 90, 90, 0.4);
27
35
  overflow-x: hidden;
28
- overflow-y: scroll;
36
+ overflow-y: hidden;
29
37
  text-align: center; }
38
+ .up-modal.up-modal-ready .up-modal-viewport {
39
+ overflow-y: scroll; }
30
40
 
31
41
  .up-modal-dialog {
32
- z-index: 1000;
42
+ z-index: 12000;
33
43
  position: relative;
34
44
  box-sizing: border-box;
35
45
  margin: 30px 10px;
@@ -38,13 +48,13 @@
38
48
  text-align: left; }
39
49
 
40
50
  .up-modal-content {
41
- z-index: 2000;
51
+ z-index: 13000;
42
52
  padding: 20px;
43
53
  background-color: #fff;
44
54
  box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.3); }
45
55
 
46
56
  .up-modal-close {
47
- z-index: 3000;
57
+ z-index: 14000;
48
58
  position: absolute;
49
59
  right: 0;
50
60
  top: 0;
data/dist/unpoly.js CHANGED
@@ -23,11 +23,19 @@ that might save you from loading something like [Underscore.js](http://underscor
23
23
 
24
24
  up.util = (function($) {
25
25
 
26
+ /**
27
+ A function that does nothing.
28
+
29
+ @function up.util.noop
30
+ @experimental
31
+ */
32
+ var $createElementFromSelector, $createPlaceholder, ANIMATION_DEFERRED_KEY, all, any, appendRequestData, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, cssAnimate, detect, each, error, escapePressed, except, extend, extractOptions, findWithSelf, finishCssAnimate, fixedToAbsolute, forceCompositing, forceRepaint, intersect, isArray, isBlank, isDeferred, isDefined, isDetached, isElement, isFormData, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isNumber, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, last, locationFromXhr, map, measure, memoize, merge, methodFromXhr, multiSelector, nextFrame, nonUpClasses, noop, normalizeMethod, normalizeUrl, nullJQuery, offsetParent, once, only, option, options, parseUrl, pluckData, pluckKey, presence, presentAttr, reject, remove, requestDataAsArray, requestDataAsQuery, requestDataFromForm, resolvableWhen, resolvedDeferred, resolvedPromise, scrollbarWidth, select, selectorForElement, setMissingAttrs, setTimer, temporaryCss, times, titleFromXhr, toArray, trim, unJQuery, uniq, unresolvableDeferred, unresolvablePromise, unwrapElement;
33
+ noop = $.noop;
34
+
26
35
  /**
27
36
  @function up.util.memoize
28
37
  @internal
29
38
  */
30
- var $createElementFromSelector, $createPlaceholder, ANIMATION_DEFERRED_KEY, all, any, appendRequestData, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, cssAnimate, detect, each, error, escapePressed, except, extend, findWithSelf, finishCssAnimate, fixedToAbsolute, forceCompositing, intersect, isArray, isBlank, isDeferred, isDefined, isElement, isFormData, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isNumber, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, last, locationFromXhr, map, measure, memoize, merge, methodFromXhr, multiSelector, nextFrame, nonUpClasses, normalizeMethod, normalizeUrl, nullJQuery, offsetParent, once, only, option, options, parseUrl, pluckData, presence, presentAttr, reject, remove, requestDataAsArray, requestDataAsQuery, requestDataFromForm, resolvableWhen, resolvedDeferred, resolvedPromise, scrollbarWidth, select, selectorForElement, setMissingAttrs, temporaryCss, times, titleFromXhr, toArray, trim, unJQuery, uniq, unresolvableDeferred, unresolvablePromise, unwrapElement;
31
39
  memoize = function(func) {
32
40
  var cache, cached;
33
41
  cache = void 0;
@@ -899,6 +907,25 @@ that might save you from loading something like [Underscore.js](http://underscor
899
907
  return detect(values, isPresent);
900
908
  };
901
909
 
910
+ /**
911
+ Waits for the given number of milliseconds, the nruns the given callback.
912
+
913
+ If the number of milliseconds is zero, the callback is run in the current execution frame.
914
+ See [`up.util.nextFrame`] for running a function in the next executation frame.
915
+
916
+ @function up.util.setTimer
917
+ @param {Number} millis
918
+ @param {Function} callback
919
+ @experimental
920
+ */
921
+ setTimer = function(millis, callback) {
922
+ if (millis > 0) {
923
+ return setTimeout(callback, millis);
924
+ } else {
925
+ return callback();
926
+ }
927
+ };
928
+
902
929
  /**
903
930
  Schedules the given function to be called in the
904
931
  next Javascript execution frame.
@@ -1035,6 +1062,17 @@ that might save you from loading something like [Underscore.js](http://underscor
1035
1062
  return memo;
1036
1063
  };
1037
1064
 
1065
+ /**
1066
+ Forces a repaint of the given element.
1067
+
1068
+ @function up.util.forceRepaint
1069
+ @internal
1070
+ */
1071
+ forceRepaint = function(element) {
1072
+ element = unJQuery(element);
1073
+ return element.offsetHeight;
1074
+ };
1075
+
1038
1076
  /**
1039
1077
  Animates the given element's CSS properties using CSS transitions.
1040
1078
 
@@ -1062,7 +1100,7 @@ that might save you from loading something like [Underscore.js](http://underscor
1062
1100
  @internal
1063
1101
  */
1064
1102
  cssAnimate = function(elementOrSelector, lastFrame, opts) {
1065
- var $element, deferred, endTimeout, transition, withoutCompositing, withoutTransition;
1103
+ var $element, animationEnd, deferred, endTimeout, oldTransition, transition, withoutCompositing;
1066
1104
  $element = $(elementOrSelector);
1067
1105
  opts = options(opts, {
1068
1106
  duration: 300,
@@ -1076,18 +1114,32 @@ that might save you from loading something like [Underscore.js](http://underscor
1076
1114
  'transition-delay': opts.delay + "ms",
1077
1115
  'transition-timing-function': opts.easing
1078
1116
  };
1117
+ oldTransition = $element.css(Object.keys(transition));
1118
+ $element.addClass('up-animating');
1079
1119
  withoutCompositing = forceCompositing($element);
1080
- withoutTransition = temporaryCss($element, transition);
1120
+ $element.css(transition);
1081
1121
  $element.css(lastFrame);
1082
- deferred.then(withoutCompositing);
1083
- deferred.then(withoutTransition);
1084
1122
  $element.data(ANIMATION_DEFERRED_KEY, deferred);
1085
1123
  deferred.then(function() {
1086
- return $element.removeData(ANIMATION_DEFERRED_KEY);
1124
+ var hadTransitionBefore;
1125
+ $element.removeData(ANIMATION_DEFERRED_KEY);
1126
+ withoutCompositing();
1127
+ $element.css({
1128
+ 'transition': 'none'
1129
+ });
1130
+ hadTransitionBefore = !(oldTransition['transition-property'] === 'none' || (oldTransition['transition-property'] === 'all' && oldTransition['transition-duration'][0] === '0'));
1131
+ if (hadTransitionBefore) {
1132
+ forceRepaint($element);
1133
+ return $element.css(oldTransition);
1134
+ }
1135
+ });
1136
+ animationEnd = opts.duration + opts.delay;
1137
+ endTimeout = setTimer(animationEnd, function() {
1138
+ $element.removeClass('up-animating');
1139
+ if (!isDetached($element)) {
1140
+ return deferred.resolve();
1141
+ }
1087
1142
  });
1088
- endTimeout = setTimeout((function() {
1089
- return deferred.resolve();
1090
- }), opts.duration + opts.delay);
1091
1143
  deferred.then(function() {
1092
1144
  return clearTimeout(endTimeout);
1093
1145
  });
@@ -1526,11 +1578,36 @@ that might save you from loading something like [Underscore.js](http://underscor
1526
1578
  @internal
1527
1579
  */
1528
1580
  cache = function(config) {
1529
- var alias, clear, expiryMilis, get, isFresh, keys, log, maxSize, normalizeStoreKey, set, store, timestamp;
1581
+ var alias, clear, expiryMillis, get, isEnabled, isFresh, keys, log, makeRoomForAnotherKey, maxKeys, normalizeStoreKey, optionEvaluator, set, store, timestamp;
1530
1582
  if (config == null) {
1531
1583
  config = {};
1532
1584
  }
1533
1585
  store = void 0;
1586
+ optionEvaluator = function(name) {
1587
+ return function() {
1588
+ var value;
1589
+ value = config[name];
1590
+ if (isNumber(value)) {
1591
+ return value;
1592
+ } else if (isFunction(value)) {
1593
+ return value();
1594
+ } else {
1595
+ return void 0;
1596
+ }
1597
+ };
1598
+ };
1599
+ maxKeys = optionEvaluator('size');
1600
+ expiryMillis = optionEvaluator('expiry');
1601
+ normalizeStoreKey = function(key) {
1602
+ if (config.key) {
1603
+ return config.key(key);
1604
+ } else {
1605
+ return key.toString();
1606
+ }
1607
+ };
1608
+ isEnabled = function() {
1609
+ return maxKeys() !== 0 && expiryMillis() !== 0;
1610
+ };
1534
1611
  clear = function() {
1535
1612
  return store = {};
1536
1613
  };
@@ -1538,48 +1615,19 @@ that might save you from loading something like [Underscore.js](http://underscor
1538
1615
  log = function() {
1539
1616
  var args;
1540
1617
  args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
1541
- if (config.log) {
1542
- args[0] = "[" + config.log + "] " + args[0];
1618
+ if (config.logPrefix) {
1619
+ args[0] = "[" + config.logPrefix + "] " + args[0];
1543
1620
  return up.puts.apply(up, args);
1544
1621
  }
1545
1622
  };
1546
1623
  keys = function() {
1547
1624
  return Object.keys(store);
1548
1625
  };
1549
- maxSize = function() {
1550
- if (isMissing(config.size)) {
1551
- return void 0;
1552
- } else if (isFunction(config.size)) {
1553
- return config.size();
1554
- } else if (isNumber(config.size)) {
1555
- return config.size;
1556
- } else {
1557
- return error("Invalid size config: %o", config.size);
1558
- }
1559
- };
1560
- expiryMilis = function() {
1561
- if (isMissing(config.expiry)) {
1562
- return void 0;
1563
- } else if (isFunction(config.expiry)) {
1564
- return config.expiry();
1565
- } else if (isNumber(config.expiry)) {
1566
- return config.expiry;
1567
- } else {
1568
- return error("Invalid expiry config: %o", config.expiry);
1569
- }
1570
- };
1571
- normalizeStoreKey = function(key) {
1572
- if (config.key) {
1573
- return config.key(key);
1574
- } else {
1575
- return key.toString();
1576
- }
1577
- };
1578
- trim = function() {
1579
- var oldestKey, oldestTimestamp, size, storeKeys;
1626
+ makeRoomForAnotherKey = function() {
1627
+ var max, oldestKey, oldestTimestamp, storeKeys;
1580
1628
  storeKeys = copy(keys());
1581
- size = maxSize();
1582
- if (size && storeKeys.length > size) {
1629
+ max = maxKeys();
1630
+ if (max && storeKeys.length >= max) {
1583
1631
  oldestKey = null;
1584
1632
  oldestTimestamp = null;
1585
1633
  each(storeKeys, function(key) {
@@ -1610,11 +1658,14 @@ that might save you from loading something like [Underscore.js](http://underscor
1610
1658
  };
1611
1659
  set = function(key, value) {
1612
1660
  var storeKey;
1613
- storeKey = normalizeStoreKey(key);
1614
- return store[storeKey] = {
1615
- timestamp: timestamp(),
1616
- value: value
1617
- };
1661
+ if (isEnabled()) {
1662
+ makeRoomForAnotherKey();
1663
+ storeKey = normalizeStoreKey(key);
1664
+ return store[storeKey] = {
1665
+ timestamp: timestamp(),
1666
+ value: value
1667
+ };
1668
+ }
1618
1669
  };
1619
1670
  remove = function(key) {
1620
1671
  var storeKey;
@@ -1622,11 +1673,11 @@ that might save you from loading something like [Underscore.js](http://underscor
1622
1673
  return delete store[storeKey];
1623
1674
  };
1624
1675
  isFresh = function(entry) {
1625
- var expiry, timeSinceTouch;
1626
- expiry = expiryMilis();
1627
- if (expiry) {
1676
+ var millis, timeSinceTouch;
1677
+ millis = expiryMillis();
1678
+ if (millis) {
1628
1679
  timeSinceTouch = timestamp() - entry.timestamp;
1629
- return timeSinceTouch < expiryMilis();
1680
+ return timeSinceTouch < millis;
1630
1681
  } else {
1631
1682
  return true;
1632
1683
  }
@@ -1868,6 +1919,12 @@ that might save you from loading something like [Underscore.js](http://underscor
1868
1919
  $error.text(asString);
1869
1920
  throw new Error(asString);
1870
1921
  };
1922
+ pluckKey = function(object, key) {
1923
+ var value;
1924
+ value = object[key];
1925
+ delete object[key];
1926
+ return value;
1927
+ };
1871
1928
  pluckData = function(elementOrSelector, key) {
1872
1929
  var $element, value;
1873
1930
  $element = $(elementOrSelector);
@@ -1875,7 +1932,29 @@ that might save you from loading something like [Underscore.js](http://underscor
1875
1932
  $element.removeData(key);
1876
1933
  return value;
1877
1934
  };
1935
+ extractOptions = function(args) {
1936
+ var lastArg;
1937
+ lastArg = last(args);
1938
+ if (isObject(lastArg)) {
1939
+ return args.pop();
1940
+ } else {
1941
+ return {};
1942
+ }
1943
+ };
1944
+
1945
+ /**
1946
+ Returns whether the given element has been detached from the DOM
1947
+ (or whether it was never attached).
1948
+
1949
+ @function up.util.isDetached
1950
+ @internal
1951
+ */
1952
+ isDetached = function(element) {
1953
+ element = unJQuery(element);
1954
+ return !jQuery.contains(document.documentElement, element);
1955
+ };
1878
1956
  return {
1957
+ isDetached: isDetached,
1879
1958
  requestDataAsArray: requestDataAsArray,
1880
1959
  requestDataAsQuery: requestDataAsQuery,
1881
1960
  appendRequestData: appendRequestData,
@@ -1931,12 +2010,14 @@ that might save you from loading something like [Underscore.js](http://underscor
1931
2010
  isUnmodifiedMouseEvent: isUnmodifiedMouseEvent,
1932
2011
  nullJQuery: nullJQuery,
1933
2012
  unJQuery: unJQuery,
2013
+ setTimer: setTimer,
1934
2014
  nextFrame: nextFrame,
1935
2015
  measure: measure,
1936
2016
  temporaryCss: temporaryCss,
1937
2017
  cssAnimate: cssAnimate,
1938
2018
  finishCssAnimate: finishCssAnimate,
1939
2019
  forceCompositing: forceCompositing,
2020
+ forceRepaint: forceRepaint,
1940
2021
  escapePressed: escapePressed,
1941
2022
  copyAttributes: copyAttributes,
1942
2023
  findWithSelf: findWithSelf,
@@ -1964,7 +2045,11 @@ that might save you from loading something like [Underscore.js](http://underscor
1964
2045
  unwrapElement: unwrapElement,
1965
2046
  multiSelector: multiSelector,
1966
2047
  error: error,
1967
- pluckData: pluckData
2048
+ pluckData: pluckData,
2049
+ pluckKey: pluckKey,
2050
+ extractOptions: extractOptions,
2051
+ isDetached: isDetached,
2052
+ noop: noop
1968
2053
  };
1969
2054
  })($);
1970
2055
 
@@ -2046,7 +2131,7 @@ printed message.
2046
2131
  message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
2047
2132
  block = args.pop();
2048
2133
  if (message) {
2049
- (ref = up.browser).puts.apply(ref, ['groupCollapsed', prefix(message)].concat(slice.call(args)));
2134
+ (ref = up.browser).puts.apply(ref, ['group', prefix(message)].concat(slice.call(args)));
2050
2135
  try {
2051
2136
  return block();
2052
2137
  } finally {
@@ -3096,6 +3181,7 @@ later.
3096
3181
 
3097
3182
  Examples for built-in macros are [`up-dash`](/up-dash) and [`up-expand`](/up-expand).
3098
3183
 
3184
+ @function up.macro
3099
3185
  @param {String} selector
3100
3186
  The selector to match.
3101
3187
  @param {Object} options
@@ -3649,7 +3735,7 @@ Unpoly will automatically be aware of sticky Bootstrap components such as
3649
3735
  */
3650
3736
  config = u.config({
3651
3737
  duration: 0,
3652
- viewports: [document, '.up-modal', '[up-viewport]'],
3738
+ viewports: [document, '.up-modal-viewport', '[up-viewport]'],
3653
3739
  fixedTop: ['[up-fixed~=top]'],
3654
3740
  fixedBottom: ['[up-fixed~=bottom]'],
3655
3741
  anchoredRight: ['[up-anchored~=right]', '[up-fixed~=top]', '[up-fixed~=bottom]', '[up-fixed~=right]'],
@@ -4993,7 +5079,9 @@ are based on this module.
4993
5079
  destroyMessage = ['Destroying fragment %o', $element.get(0)];
4994
5080
  destroyedMessage = ['Destroyed fragment %o', $element.get(0)];
4995
5081
  }
4996
- if (up.bus.nobodyPrevents('up:fragment:destroy', {
5082
+ if ($element.length === 0) {
5083
+ return u.resolvedDeferred();
5084
+ } else if (up.bus.nobodyPrevents('up:fragment:destroy', {
4997
5085
  $element: $element,
4998
5086
  message: destroyMessage
4999
5087
  })) {
@@ -5151,8 +5239,10 @@ or [transitions](/up.transition) using Javascript or CSS.
5151
5239
  */
5152
5240
 
5153
5241
  (function() {
5242
+ var slice = [].slice;
5243
+
5154
5244
  up.motion = (function($) {
5155
- var GHOSTING_DEFERRED_KEY, animate, animateOptions, animation, animations, assertIsDeferred, config, defaultAnimations, defaultTransitions, ensureMorphable, findAnimation, finish, finishGhosting, isEnabled, morph, none, prependCopy, reset, resolvableWhen, skipMorph, snapshot, transition, transitions, u, withGhosts;
5245
+ var GHOSTING_CLASS, GHOSTING_DEFERRED_KEY, animate, animateOptions, animation, animations, assertIsDeferred, config, defaultAnimations, defaultTransitions, ensureMorphable, findAnimation, finish, finishGhosting, isEnabled, isNone, morph, none, prependCopy, reset, resolvableWhen, skipMorph, snapshot, transition, transitions, translateCss, u, withGhosts;
5156
5246
  u = up.util;
5157
5247
  animations = {};
5158
5248
  defaultAnimations = {};
@@ -5164,8 +5254,14 @@ or [transitions](/up.transition) using Javascript or CSS.
5164
5254
 
5165
5255
  @property up.motion.config
5166
5256
  @param {Number} [config.duration=300]
5257
+ The default duration for all animations and transitions (in milliseconds).
5167
5258
  @param {Number} [config.delay=0]
5259
+ The default delay for all animations and transitions (in milliseconds).
5168
5260
  @param {String} [config.easing='ease']
5261
+ The default timing function that controls the acceleration of animations and transitions.
5262
+
5263
+ See [W3C documentation](http://www.w3.org/TR/css3-transitions/#transition-timing-function)
5264
+ for a list of pre-defined timing functions.
5169
5265
  @param {Boolean} [config.enabled=true]
5170
5266
  Whether animation is enabled.
5171
5267
 
@@ -5268,6 +5364,7 @@ or [transitions](/up.transition) using Javascript or CSS.
5268
5364
  The delay before the animation starts, in milliseconds.
5269
5365
  @param {String} [options.easing='ease']
5270
5366
  The timing function that controls the animation's acceleration.
5367
+
5271
5368
  See [W3C documentation](http://www.w3.org/TR/css3-transitions/#transition-timing-function)
5272
5369
  for a list of pre-defined timing functions.
5273
5370
  @return {Promise}
@@ -5305,29 +5402,34 @@ or [transitions](/up.transition) using Javascript or CSS.
5305
5402
  @function up.motion.animateOptions
5306
5403
  @internal
5307
5404
  */
5308
- animateOptions = function(allOptions, $element) {
5309
- var options;
5310
- if ($element == null) {
5311
- $element = null;
5312
- }
5313
- allOptions = u.options(allOptions);
5405
+ animateOptions = function() {
5406
+ var $element, args, moduleDefaults, options, userOptions;
5407
+ userOptions = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
5408
+ userOptions = u.options(userOptions);
5409
+ moduleDefaults = u.extractOptions(args);
5410
+ $element = args.length > 1 ? args[1] : void 0;
5314
5411
  options = {};
5315
- options.easing = u.option(allOptions.easing, $element != null ? $element.attr('up-easing') : void 0, config.easing);
5316
- options.duration = Number(u.option(allOptions.duration, $element != null ? $element.attr('up-duration') : void 0, config.duration));
5317
- options.delay = Number(u.option(allOptions.delay, $element != null ? $element.attr('up-delay') : void 0, config.delay));
5412
+ options.easing = u.option(userOptions.easing, $element != null ? $element.attr('up-easing') : void 0, moduleDefaults.easing, config.easing);
5413
+ options.duration = Number(u.option(userOptions.duration, $element != null ? $element.attr('up-duration') : void 0, moduleDefaults.duration, config.duration));
5414
+ options.delay = Number(u.option(userOptions.delay, $element != null ? $element.attr('up-delay') : void 0, moduleDefaults.delay, config.delay));
5318
5415
  return options;
5319
5416
  };
5320
5417
  findAnimation = function(name) {
5321
5418
  return animations[name] || u.error("Unknown animation %o", name);
5322
5419
  };
5323
5420
  GHOSTING_DEFERRED_KEY = 'up-ghosting-deferred';
5421
+ GHOSTING_CLASS = 'up-ghosting';
5324
5422
  withGhosts = function($old, $new, options, block) {
5325
- var $viewport, deferred, newCopy, newScrollTop, oldCopy, oldScrollTop, showNew;
5423
+ var $both, $viewport, deferred, newCopy, newScrollTop, oldCopy, oldScrollTop, showNew;
5424
+ if (options.copy === false || $old.is('.up-ghost') || $new.is('.up-ghost')) {
5425
+ return block($old, $new);
5426
+ }
5326
5427
  oldCopy = void 0;
5327
5428
  newCopy = void 0;
5328
5429
  oldScrollTop = void 0;
5329
5430
  newScrollTop = void 0;
5330
5431
  $viewport = up.layout.viewportOf($old);
5432
+ $both = $old.add($new);
5331
5433
  u.temporaryCss($new, {
5332
5434
  display: 'none'
5333
5435
  }, function() {
@@ -5347,11 +5449,11 @@ or [transitions](/up.transition) using Javascript or CSS.
5347
5449
  opacity: '0'
5348
5450
  });
5349
5451
  deferred = block(oldCopy.$ghost, newCopy.$ghost);
5350
- $old.data(GHOSTING_DEFERRED_KEY, deferred);
5351
- $new.data(GHOSTING_DEFERRED_KEY, deferred);
5452
+ $both.data(GHOSTING_DEFERRED_KEY, deferred);
5453
+ $both.addClass(GHOSTING_CLASS);
5352
5454
  deferred.then(function() {
5353
- $old.removeData(GHOSTING_DEFERRED_KEY);
5354
- $new.removeData(GHOSTING_DEFERRED_KEY);
5455
+ $both.removeData(GHOSTING_DEFERRED_KEY);
5456
+ $both.removeClass(GHOSTING_CLASS);
5355
5457
  showNew();
5356
5458
  oldCopy.$bounds.remove();
5357
5459
  return newCopy.$bounds.remove();
@@ -5360,22 +5462,30 @@ or [transitions](/up.transition) using Javascript or CSS.
5360
5462
  };
5361
5463
 
5362
5464
  /**
5363
- Completes all [animations](/up.animate) and [transitions](/up.morph)
5364
- for the given element by jumping to the last animation frame instantly.
5465
+ Completes [animations](/up.animate) and [transitions](/up.morph).
5365
5466
 
5366
- All callbacks chained to the original animation's promise will be called.
5467
+ If called without arguments, all animations on the screen are completed.
5468
+ If given an element (or selector), animations on that element and its children
5469
+ are completed.
5367
5470
 
5368
- Does nothing if the given element is not currently animating.
5471
+ Animations are completed by jumping to the last animation frame instantly.
5472
+
5473
+ Does nothing if there are no animation to complete.
5369
5474
 
5370
5475
  @function up.motion.finish
5371
- @param {Element|jQuery|String} elementOrSelector
5476
+ @param {Element|jQuery|String} [elementOrSelector]
5372
5477
  @stable
5373
5478
  */
5374
5479
  finish = function(elementOrSelector) {
5375
- var $element;
5480
+ var $animatingSubtree, $element, $ghostingSubtree;
5481
+ if (elementOrSelector == null) {
5482
+ elementOrSelector = '.up-animating';
5483
+ }
5376
5484
  $element = $(elementOrSelector);
5377
- u.finishCssAnimate($element);
5378
- return finishGhosting($element);
5485
+ $animatingSubtree = u.findWithSelf($element, '.up-animating');
5486
+ u.finishCssAnimate($animatingSubtree);
5487
+ $ghostingSubtree = u.findWithSelf($element, "." + GHOSTING_CLASS);
5488
+ return finishGhosting($ghostingSubtree);
5379
5489
  };
5380
5490
  finishGhosting = function($collection) {
5381
5491
  return $collection.each(function() {
@@ -5449,6 +5559,7 @@ or [transitions](/up.transition) using Javascript or CSS.
5449
5559
  The delay before the animation starts, in milliseconds.
5450
5560
  @param {String} [options.easing='ease']
5451
5561
  The timing function that controls the transition's acceleration.
5562
+
5452
5563
  See [W3C documentation](http://www.w3.org/TR/css3-transitions/#transition-timing-function)
5453
5564
  for a list of pre-defined timing functions.
5454
5565
  @param {Boolean} [options.reveal=false]
@@ -5462,6 +5573,7 @@ or [transitions](/up.transition) using Javascript or CSS.
5462
5573
  if (transitionOrName === 'none') {
5463
5574
  transitionOrName = false;
5464
5575
  }
5576
+ options = u.options(options);
5465
5577
  $old = $(source);
5466
5578
  $new = $(target);
5467
5579
  ensureMorphable($old, transitionOrName);
@@ -5672,6 +5784,17 @@ or [transitions](/up.transition) using Javascript or CSS.
5672
5784
  @stable
5673
5785
  */
5674
5786
  none = u.resolvedDeferred;
5787
+
5788
+ /**
5789
+ Returns whether the given animation option will cause the animation
5790
+ to be skipped.
5791
+
5792
+ @function up.motion.isNone
5793
+ @internal
5794
+ */
5795
+ isNone = function(animation) {
5796
+ return animation === false || animation === 'none' || animation === none;
5797
+ };
5675
5798
  animation('none', none);
5676
5799
  animation('fade-in', function($ghost, options) {
5677
5800
  $ghost.css({
@@ -5689,104 +5812,79 @@ or [transitions](/up.transition) using Javascript or CSS.
5689
5812
  opacity: 0
5690
5813
  }, options);
5691
5814
  });
5815
+ translateCss = function(x, y) {
5816
+ return {
5817
+ transform: "translate(" + x + "px, " + y + "px)"
5818
+ };
5819
+ };
5692
5820
  animation('move-to-top', function($ghost, options) {
5693
5821
  var box, travelDistance;
5694
5822
  box = u.measure($ghost);
5695
5823
  travelDistance = box.top + box.height;
5696
- $ghost.css({
5697
- 'margin-top': '0px'
5698
- });
5699
- return animate($ghost, {
5700
- 'margin-top': "-" + travelDistance + "px"
5701
- }, options);
5824
+ $ghost.css(translateCss(0, 0));
5825
+ return animate($ghost, translateCss(0, -travelDistance), options);
5702
5826
  });
5703
5827
  animation('move-from-top', function($ghost, options) {
5704
5828
  var box, travelDistance;
5705
5829
  box = u.measure($ghost);
5706
5830
  travelDistance = box.top + box.height;
5707
- $ghost.css({
5708
- 'margin-top': "-" + travelDistance + "px"
5709
- });
5710
- return animate($ghost, {
5711
- 'margin-top': '0px'
5712
- }, options);
5831
+ $ghost.css(translateCss(0, -travelDistance));
5832
+ return animate($ghost, translateCss(0, 0), options);
5713
5833
  });
5714
5834
  animation('move-to-bottom', function($ghost, options) {
5715
5835
  var box, travelDistance;
5716
5836
  box = u.measure($ghost);
5717
5837
  travelDistance = u.clientSize().height - box.top;
5718
- $ghost.css({
5719
- 'margin-top': '0px'
5720
- });
5721
- return animate($ghost, {
5722
- 'margin-top': travelDistance + "px"
5723
- }, options);
5838
+ $ghost.css(translateCss(0, 0));
5839
+ return animate($ghost, translateCss(0, travelDistance), options);
5724
5840
  });
5725
5841
  animation('move-from-bottom', function($ghost, options) {
5726
5842
  var box, travelDistance;
5727
5843
  box = u.measure($ghost);
5728
5844
  travelDistance = u.clientSize().height - box.top;
5729
- $ghost.css({
5730
- 'margin-top': travelDistance + "px"
5731
- });
5732
- return animate($ghost, {
5733
- 'margin-top': '0px'
5734
- }, options);
5845
+ $ghost.css(translateCss(0, travelDistance));
5846
+ return animate($ghost, translateCss(0, 0), options);
5735
5847
  });
5736
5848
  animation('move-to-left', function($ghost, options) {
5737
5849
  var box, travelDistance;
5738
5850
  box = u.measure($ghost);
5739
5851
  travelDistance = box.left + box.width;
5740
- $ghost.css({
5741
- 'margin-left': '0px'
5742
- });
5743
- return animate($ghost, {
5744
- 'margin-left': "-" + travelDistance + "px"
5745
- }, options);
5852
+ $ghost.css(translateCss(0, 0));
5853
+ return animate($ghost, translateCss(-travelDistance, 0), options);
5746
5854
  });
5747
5855
  animation('move-from-left', function($ghost, options) {
5748
5856
  var box, travelDistance;
5749
5857
  box = u.measure($ghost);
5750
5858
  travelDistance = box.left + box.width;
5751
- $ghost.css({
5752
- 'margin-left': "-" + travelDistance + "px"
5753
- });
5754
- return animate($ghost, {
5755
- 'margin-left': '0px'
5756
- }, options);
5859
+ $ghost.css(translateCss(-travelDistance, 0));
5860
+ return animate($ghost, translateCss(0, 0), options);
5757
5861
  });
5758
5862
  animation('move-to-right', function($ghost, options) {
5759
5863
  var box, travelDistance;
5760
5864
  box = u.measure($ghost);
5761
5865
  travelDistance = u.clientSize().width - box.left;
5762
- $ghost.css({
5763
- 'margin-left': '0px'
5764
- });
5765
- return animate($ghost, {
5766
- 'margin-left': travelDistance + "px"
5767
- }, options);
5866
+ $ghost.css(translateCss(0, 0));
5867
+ return animate($ghost, translateCss(travelDistance, 0), options);
5768
5868
  });
5769
5869
  animation('move-from-right', function($ghost, options) {
5770
5870
  var box, travelDistance;
5771
5871
  box = u.measure($ghost);
5772
5872
  travelDistance = u.clientSize().width - box.left;
5773
- $ghost.css({
5774
- 'margin-left': travelDistance + "px"
5775
- });
5776
- return animate($ghost, {
5777
- 'margin-left': '0px'
5778
- }, options);
5873
+ $ghost.css(translateCss(travelDistance, 0));
5874
+ return animate($ghost, translateCss(0, 0), options);
5779
5875
  });
5780
5876
  animation('roll-down', function($ghost, options) {
5781
- var fullHeight, styleMemo;
5877
+ var deferred, fullHeight, styleMemo;
5782
5878
  fullHeight = $ghost.height();
5783
5879
  styleMemo = u.temporaryCss($ghost, {
5784
5880
  height: '0px',
5785
5881
  overflow: 'hidden'
5786
5882
  });
5787
- return animate($ghost, {
5883
+ deferred = animate($ghost, {
5788
5884
  height: fullHeight + "px"
5789
- }, options).then(styleMemo);
5885
+ }, options);
5886
+ deferred.then(styleMemo);
5887
+ return deferred;
5790
5888
  });
5791
5889
  transition('none', none);
5792
5890
  transition('move-left', function($old, $new, options) {
@@ -5820,7 +5918,8 @@ or [transitions](/up.transition) using Javascript or CSS.
5820
5918
  },
5821
5919
  none: none,
5822
5920
  when: resolvableWhen,
5823
- prependCopy: prependCopy
5921
+ prependCopy: prependCopy,
5922
+ isNone: isNone
5824
5923
  };
5825
5924
  })(jQuery);
5826
5925
 
@@ -5858,7 +5957,7 @@ the user performs the click.
5858
5957
  var slice = [].slice;
5859
5958
 
5860
5959
  up.proxy = (function($) {
5861
- var $waitingLink, ajax, alias, cache, cacheKey, cancelBusyDelay, cancelPreloadDelay, checkPreload, clear, config, get, isBusy, isIdempotent, isIdle, load, loadEnded, loadOrQueue, loadStarted, normalizeRequest, pendingCount, pokeQueue, preload, preloadDelayTimer, queue, queuedRequests, remove, reset, responseReceived, set, slowDelayTimer, slowEventEmitted, startPreloadDelay, u;
5960
+ var $waitingLink, ajax, alias, cache, cacheKey, cancelPreloadDelay, cancelSlowDelay, checkPreload, clear, config, get, isBusy, isIdempotent, isIdle, load, loadEnded, loadOrQueue, loadStarted, normalizeRequest, pendingCount, pokeQueue, preload, preloadDelayTimer, queue, queuedRequests, remove, reset, responseReceived, set, slowDelayTimer, slowEventEmitted, startPreloadDelay, u;
5862
5961
  u = up.util;
5863
5962
  $waitingLink = void 0;
5864
5963
  preloadDelayTimer = void 0;
@@ -5960,65 +6059,25 @@ the user performs the click.
5960
6059
  }
5961
6060
  }
5962
6061
  };
5963
-
5964
- /**
5965
- Manually stores a promise for the response to the given request.
5966
-
5967
- @function up.proxy.set
5968
- @param {String} request.url
5969
- @param {String} [request.method='GET']
5970
- @param {String} [request.target='body']
5971
- @param {Promise} response
5972
- A promise for the response that is API-compatible with the
5973
- promise returned by [`jQuery.ajax`](http://api.jquery.com/jquery.ajax/).
5974
- @experimental
5975
- */
5976
- set = cache.set;
5977
-
5978
- /**
5979
- Manually removes the given request from the cache.
5980
-
5981
- You can also [configure](/up.proxy.config) when the proxy
5982
- automatically removes cache entries.
5983
-
5984
- @function up.proxy.remove
5985
- @param {String} request.url
5986
- @param {String} [request.method='GET']
5987
- @param {String} [request.target='body']
5988
- @experimental
5989
- */
5990
- remove = cache.remove;
5991
-
5992
- /**
5993
- Removes all cache entries.
5994
-
5995
- Unpoly also automatically clears the cache whenever it processes
5996
- a request with a non-GET HTTP method.
5997
-
5998
- @function up.proxy.clear
5999
- @stable
6000
- */
6001
- clear = cache.clear;
6002
6062
  cancelPreloadDelay = function() {
6003
6063
  clearTimeout(preloadDelayTimer);
6004
6064
  return preloadDelayTimer = null;
6005
6065
  };
6006
- cancelBusyDelay = function() {
6066
+ cancelSlowDelay = function() {
6007
6067
  clearTimeout(slowDelayTimer);
6008
6068
  return slowDelayTimer = null;
6009
6069
  };
6010
6070
  reset = function() {
6011
6071
  $waitingLink = null;
6012
6072
  cancelPreloadDelay();
6013
- cancelBusyDelay();
6073
+ cancelSlowDelay();
6014
6074
  pendingCount = 0;
6015
6075
  config.reset();
6016
- slowEventEmitted = false;
6017
6076
  cache.clear();
6077
+ slowEventEmitted = false;
6018
6078
  return queuedRequests = [];
6019
6079
  };
6020
6080
  reset();
6021
- alias = cache.alias;
6022
6081
  normalizeRequest = function(request) {
6023
6082
  if (!request._normalized) {
6024
6083
  request.method = u.normalizeMethod(request.method);
@@ -6040,13 +6099,23 @@ the user performs the click.
6040
6099
  Only requests with a method of `GET`, `OPTIONS` and `HEAD`
6041
6100
  are considered to be read-only.
6042
6101
 
6102
+ \#\#\#\# Example
6103
+
6104
+ up.ajax('/search', data: { query: 'sunshine' }).then(function(data, status, xhr) {
6105
+ console.log('The response body is %o', data);
6106
+ }).fail(function(xhr, status, error) {
6107
+ console.error('The request failed');
6108
+ });
6109
+
6110
+ \#\#\#\# Events
6111
+
6043
6112
  If a network connection is attempted, the proxy will emit
6044
- a `up:proxy:load` event with the `request` as its argument.
6045
- Once the response is received, a `up:proxy:receive` event will
6113
+ a [`up:proxy:load`](/up:proxy:load) event with the `request` as its argument.
6114
+ Once the response is received, a [`up:proxy:receive`](/up:proxy:receive) event will
6046
6115
  be emitted.
6047
6116
 
6048
6117
  @function up.ajax
6049
- @param {String} request.url
6118
+ @param {String} url
6050
6119
  @param {String} [request.method='GET']
6051
6120
  @param {String} [request.target='body']
6052
6121
  @param {Boolean} [request.cache]
@@ -6057,13 +6126,21 @@ the user performs the click.
6057
6126
  with the request.
6058
6127
  @param {Object} [request.data={}]
6059
6128
  An object of request parameters.
6129
+ @param {String} [request.url]
6130
+ You can omit the first string argument and pass the URL as
6131
+ a `request` property instead.
6060
6132
  @return
6061
6133
  A promise for the response that is API-compatible with the
6062
6134
  promise returned by [`jQuery.ajax`](http://api.jquery.com/jquery.ajax/).
6063
6135
  @stable
6064
6136
  */
6065
- ajax = function(options) {
6066
- var forceCache, ignoreCache, pending, promise, request;
6137
+ ajax = function() {
6138
+ var args, forceCache, ignoreCache, options, pending, promise, request;
6139
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
6140
+ options = u.extractOptions(args);
6141
+ if (u.isGiven(args[0])) {
6142
+ options.url = args[0];
6143
+ }
6067
6144
  forceCache = options.cache === true;
6068
6145
  ignoreCache = options.cache === false;
6069
6146
  request = u.only(options, 'url', 'method', 'data', 'target', 'headers', '_normalized');
@@ -6128,11 +6205,7 @@ the user performs the click.
6128
6205
  return slowEventEmitted = true;
6129
6206
  }
6130
6207
  };
6131
- if (config.slowDelay > 0) {
6132
- return slowDelayTimer = setTimeout(emission, config.slowDelay);
6133
- } else {
6134
- return emission();
6135
- }
6208
+ return slowDelayTimer = u.setTimer(config.slowDelay, emission);
6136
6209
  }
6137
6210
  };
6138
6211
 
@@ -6279,6 +6352,59 @@ the user performs the click.
6279
6352
  }
6280
6353
  };
6281
6354
 
6355
+ /**
6356
+ Makes the proxy assume that `newRequest` has the same response as the
6357
+ already cached `oldRequest`.
6358
+
6359
+ Unpoly uses this internally when the user redirects from `/old` to `/new`.
6360
+ In that case, both `/old` and `/new` will cache the same response from `/new`.
6361
+
6362
+ @function up.proxy.alias
6363
+ @param {Object} oldRequest
6364
+ @param {Object} newRequest
6365
+ @experimental
6366
+ */
6367
+ alias = cache.alias;
6368
+
6369
+ /**
6370
+ Manually stores a promise for the response to the given request.
6371
+
6372
+ @function up.proxy.set
6373
+ @param {String} request.url
6374
+ @param {String} [request.method='GET']
6375
+ @param {String} [request.target='body']
6376
+ @param {Promise} response
6377
+ A promise for the response that is API-compatible with the
6378
+ promise returned by [`jQuery.ajax`](http://api.jquery.com/jquery.ajax/).
6379
+ @experimental
6380
+ */
6381
+ set = cache.set;
6382
+
6383
+ /**
6384
+ Manually removes the given request from the cache.
6385
+
6386
+ You can also [configure](/up.proxy.config) when the proxy
6387
+ automatically removes cache entries.
6388
+
6389
+ @function up.proxy.remove
6390
+ @param {String} request.url
6391
+ @param {String} [request.method='GET']
6392
+ @param {String} [request.target='body']
6393
+ @experimental
6394
+ */
6395
+ remove = cache.remove;
6396
+
6397
+ /**
6398
+ Removes all cache entries.
6399
+
6400
+ Unpoly also automatically clears the cache whenever it processes
6401
+ a request with a non-GET HTTP method.
6402
+
6403
+ @function up.proxy.clear
6404
+ @stable
6405
+ */
6406
+ clear = cache.clear;
6407
+
6282
6408
  /**
6283
6409
  This event is [emitted]/(up.emit) before an [AJAX request](/up.ajax)
6284
6410
  is starting to load.
@@ -6617,14 +6743,22 @@ Read on
6617
6743
  */
6618
6744
  allowDefault = function(event) {};
6619
6745
  onAction = function(selector, handler) {
6746
+ var handlerWithActiveMark;
6620
6747
  followVariantSelectors.push(selector);
6748
+ handlerWithActiveMark = function($link) {
6749
+ return up.navigation.withActiveMark($link, {
6750
+ enlarge: true
6751
+ }, function() {
6752
+ return handler($link);
6753
+ });
6754
+ };
6621
6755
  up.on('click', "a" + selector + ", [up-href]" + selector, function(event, $link) {
6622
6756
  if (shouldProcessLinkEvent(event, $link)) {
6623
6757
  if ($link.is('[up-instant]')) {
6624
6758
  return event.preventDefault();
6625
6759
  } else {
6626
6760
  event.preventDefault();
6627
- return handler($link);
6761
+ return handlerWithActiveMark($link);
6628
6762
  }
6629
6763
  } else {
6630
6764
  return allowDefault(event);
@@ -6633,7 +6767,7 @@ Read on
6633
6767
  return up.on('mousedown', "a" + selector + "[up-instant], [up-href]" + selector + "[up-instant]", function(event, $link) {
6634
6768
  if (shouldProcessLinkEvent(event, $link)) {
6635
6769
  event.preventDefault();
6636
- return handler($link);
6770
+ return handlerWithActiveMark($link);
6637
6771
  }
6638
6772
  });
6639
6773
  };
@@ -7056,14 +7190,14 @@ open dialogs with sub-forms, etc. all without losing form state.
7056
7190
  return u.unresolvablePromise();
7057
7191
  }
7058
7192
  }
7059
- $form.addClass('up-active');
7193
+ up.navigation.markActive($form);
7060
7194
  if (!(canAjaxSubmit && canHistoryOption)) {
7061
7195
  $form.get(0).submit();
7062
7196
  return u.unresolvablePromise();
7063
7197
  }
7064
7198
  promise = up.replace(target, url, options);
7065
7199
  promise.always(function() {
7066
- return $form.removeClass('up-active');
7200
+ return up.navigation.unmarkActive($form);
7067
7201
  });
7068
7202
  return promise;
7069
7203
  };
@@ -7184,11 +7318,7 @@ open dialogs with sub-forms, etc. all without losing form state.
7184
7318
  }
7185
7319
  });
7186
7320
  };
7187
- if (delay === 0) {
7188
- return runAndChain();
7189
- } else {
7190
- return setTimeout(runAndChain, delay);
7191
- }
7321
+ return u.setTimer(delay, runAndChain);
7192
7322
  }
7193
7323
  }
7194
7324
  };
@@ -8333,10 +8463,13 @@ The easiest way to change how the dialog looks is by overriding the [default CSS
8333
8463
  By default the dialog uses the following DOM structure:
8334
8464
 
8335
8465
  <div class="up-modal">
8336
- <div class="up-modal-dialog">
8337
- <div class="up-modal-close" up-close>X</div>
8338
- <div class="up-modal-content">
8339
- ...
8466
+ <div class="up-modal-backdrop">
8467
+ <div class="up-modal-viewport">
8468
+ <div class="up-modal-dialog">
8469
+ <div class="up-modal-content">
8470
+ ...
8471
+ </div>
8472
+ <div class="up-modal-close" up-close>X</div>
8340
8473
  </div>
8341
8474
  </div>
8342
8475
  </div>
@@ -8363,13 +8496,15 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8363
8496
 
8364
8497
  (function() {
8365
8498
  up.modal = (function($) {
8366
- var autoclose, close, config, contains, coveredUrl, createFrame, currentUrl, discardHistory, follow, isOpen, open, reset, shiftElements, templateHtml, u, unshiftElements, unshifters, visit;
8499
+ var autoclose, close, config, contains, coveredUrl, createFrame, currentUrl, discardHistory, extract, follow, isOpen, open, reset, shiftElements, templateHtml, u, unshiftElements, unshifters, visit;
8367
8500
  u = up.util;
8368
8501
 
8369
8502
  /**
8370
8503
  Sets default options for future modals.
8371
8504
 
8372
8505
  @property up.modal.config
8506
+ @param {String} [config.history=true]
8507
+ Whether opening a modal will add a browser history entry.
8373
8508
  @param {Number} [config.width]
8374
8509
  The width of the dialog as a CSS value like `'400px'` or `50%`.
8375
8510
 
@@ -8389,20 +8524,28 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8389
8524
  A string containing the HTML structure of the modal.
8390
8525
  You can supply an alternative template string, but make sure that it
8391
8526
  defines tag with the classes `up-modal`, `up-modal-dialog` and `up-modal-content`.
8392
-
8527
+ yy
8393
8528
  You can also supply a function that returns a HTML string.
8394
8529
  The function will be called with the modal options (merged from these defaults
8395
8530
  and any per-open overrides) whenever a modal opens.
8396
8531
  @param {String} [config.closeLabel='X']
8397
8532
  The label of the button that closes the dialog.
8398
8533
  @param {String} [config.openAnimation='fade-in']
8399
- The animation used to open the modal. The animation will be applied
8400
- to both the dialog box and the overlay dimming the page.
8534
+ The animation used to open the viewport around the dialog.
8401
8535
  @param {String} [config.closeAnimation='fade-out']
8402
- The animation used to close the modal. The animation will be applied
8403
- to both the dialog box and the overlay dimming the page.
8404
- @param {String} [config.history=true]
8405
- Whether opening a modal will add a browser history entry.
8536
+ The animation used to close the viewport the dialog.
8537
+ @param {String} [config.backdropOpenAnimation='fade-in']
8538
+ The animation used to open the backdrop that dims the page below the dialog.
8539
+ @param {String} [config.backdropCloseAnimation='fade-out']
8540
+ The animation used to close the backdrop that dims the page below the dialog.
8541
+ @param {String} [config.openDuration]
8542
+ The duration of the open animation (in milliseconds).
8543
+ @param {String} [config.closeDuration]
8544
+ The duration of the close animation (in milliseconds).
8545
+ @param {String} [config.openEasing]
8546
+ The timing function controlling the acceleration of the opening animation.
8547
+ @param {String} [config.closeEasing]
8548
+ The timing function controlling the acceleration of the closing animation.
8406
8549
  @stable
8407
8550
  */
8408
8551
  config = u.config({
@@ -8413,9 +8556,15 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8413
8556
  history: true,
8414
8557
  openAnimation: 'fade-in',
8415
8558
  closeAnimation: 'fade-out',
8559
+ closeDuration: null,
8560
+ closeEasing: null,
8561
+ openDuration: null,
8562
+ openEasing: null,
8563
+ backdropOpenAnimation: 'fade-in',
8564
+ backdropCloseAnimation: 'fade-out',
8416
8565
  closeLabel: '×',
8417
8566
  template: function(config) {
8418
- return "<div class=\"up-modal\">\n <div class=\"up-modal-dialog\">\n <div class=\"up-modal-close\" up-close>" + config.closeLabel + "</div>\n <div class=\"up-modal-content\"></div>\n </div>\n</div>";
8567
+ return "<div class=\"up-modal\">\n <div class=\"up-modal-backdrop\"></div>\n <div class=\"up-modal-viewport\">\n <div class=\"up-modal-dialog\">\n <div class=\"up-modal-close\" up-close>" + config.closeLabel + "</div>\n <div class=\"up-modal-content\"></div>\n </div>\n </div>\n</div>";
8419
8568
  }
8420
8569
  });
8421
8570
 
@@ -8464,7 +8613,6 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8464
8613
  };
8465
8614
  createFrame = function(target, options) {
8466
8615
  var $content, $dialog, $modal;
8467
- shiftElements();
8468
8616
  $modal = $(templateHtml());
8469
8617
  if (options.sticky) {
8470
8618
  $modal.attr('up-sticky', '');
@@ -8489,6 +8637,10 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8489
8637
  unshifters = [];
8490
8638
  shiftElements = function() {
8491
8639
  var bodyRightPadding, bodyRightShift, scrollbarWidth, unshiftBody;
8640
+ if (unshifters.length) {
8641
+ u.error('Tried to call shiftElements multiple times %o', unshifters.length);
8642
+ }
8643
+ $('.up-modal').addClass('up-modal-ready');
8492
8644
  scrollbarWidth = u.scrollbarWidth();
8493
8645
  bodyRightPadding = parseInt($('body').css('padding-right'));
8494
8646
  bodyRightShift = scrollbarWidth + bodyRightPadding;
@@ -8510,6 +8662,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8510
8662
  };
8511
8663
  unshiftElements = function() {
8512
8664
  var results, unshifter;
8665
+ $('.up-modal').removeClass('up-modal-ready');
8513
8666
  results = [];
8514
8667
  while (unshifter = unshifters.pop()) {
8515
8668
  results.push(unshifter());
@@ -8580,7 +8733,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8580
8733
 
8581
8734
  Example:
8582
8735
 
8583
- up.modal.visit('/foo', { target: '.list' })
8736
+ up.modal.visit('/foo', { target: '.list' });
8584
8737
 
8585
8738
  This will request `/foo`, extract the `.list` selector from the response
8586
8739
  and open the selected container in a modal dialog.
@@ -8606,26 +8759,67 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8606
8759
  return open(options);
8607
8760
  };
8608
8761
 
8762
+ /**
8763
+ [Extracts](/up.extract) the given CSS selector from the given HTML string and
8764
+ opens the results in a modal.
8765
+
8766
+ Example:
8767
+
8768
+ var html = 'before <div class="content">inner</div> after';
8769
+ up.modal.extract('/foo', '.content', html);
8770
+
8771
+ The would open a modal with the following contents:
8772
+
8773
+ <div class="content">inner</div>
8774
+
8775
+ Emits events [`up:modal:open`](/up:modal:open) and [`up:modal:opened`](/up:modal:opened).
8776
+
8777
+ @function up.modal.extract
8778
+ @param {String} url
8779
+ The URL to load.
8780
+ @param {Object} options
8781
+ See options for [`up.modal.follow`](/up.modal.follow).
8782
+ @return {Promise}
8783
+ A promise that will be resolved when the modal has been opened and the opening
8784
+ animation has completed.
8785
+ @stable
8786
+ */
8787
+ extract = function(selector, html, options) {
8788
+ options = u.options(options);
8789
+ options.html = html;
8790
+ options.history = u.option(options.history, false);
8791
+ options.target = selector;
8792
+ return open(options);
8793
+ };
8794
+
8609
8795
  /**
8610
8796
  @function open
8611
8797
  @internal
8612
8798
  */
8613
8799
  open = function(options) {
8614
- var $link, animateOptions, target, url;
8800
+ var $link, animateOptions, html, target, url;
8615
8801
  options = u.options(options);
8616
- $link = u.option(options.$link, u.nullJQuery());
8617
- url = u.option(options.url, $link.attr('up-href'), $link.attr('href'));
8618
- target = u.option(options.target, $link.attr('up-modal'), 'body');
8802
+ $link = u.option(u.pluckKey(options, '$link'), u.nullJQuery());
8803
+ url = u.option(u.pluckKey(options, 'url'), $link.attr('up-href'), $link.attr('href'));
8804
+ html = u.pluckKey(options, 'html');
8805
+ target = u.option(u.pluckKey(options, 'target'), $link.attr('up-modal'), 'body');
8619
8806
  options.width = u.option(options.width, $link.attr('up-width'), config.width);
8620
8807
  options.maxWidth = u.option(options.maxWidth, $link.attr('up-max-width'), config.maxWidth);
8621
8808
  options.height = u.option(options.height, $link.attr('up-height'), config.height);
8622
8809
  options.animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation);
8810
+ options.backdropAnimation = u.option(options.backdropAnimation, $link.attr('up-backdrop-animation'), config.backdropOpenAnimation);
8623
8811
  options.sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'));
8624
- options.history = up.browser.canPushState() ? u.option(options.history, u.castedAttr($link, 'up-history'), config.history) : false;
8625
8812
  options.confirm = u.option(options.confirm, $link.attr('up-confirm'));
8626
- animateOptions = up.motion.animateOptions(options, $link);
8813
+ animateOptions = up.motion.animateOptions(options, $link, {
8814
+ duration: config.openDuration,
8815
+ easing: config.openEasing
8816
+ });
8817
+ options.history = u.option(options.history, u.castedAttr($link, 'up-history'), config.history);
8818
+ if (!up.browser.canPushState()) {
8819
+ options.history = false;
8820
+ }
8627
8821
  return up.browser.confirm(options).then(function() {
8628
- var promise, wasOpen;
8822
+ var extractOptions, promise, wasOpen;
8629
8823
  if (up.bus.nobodyPrevents('up:modal:open', {
8630
8824
  url: url,
8631
8825
  message: 'Opening modal'
@@ -8639,15 +8833,21 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8639
8833
  options.beforeSwap = function() {
8640
8834
  return createFrame(target, options);
8641
8835
  };
8642
- promise = up.replace(target, url, u.merge(options, {
8836
+ extractOptions = u.merge(options, {
8643
8837
  animation: false
8644
- }));
8645
- if (!wasOpen) {
8838
+ });
8839
+ if (url) {
8840
+ promise = up.replace(target, url, extractOptions);
8841
+ } else {
8842
+ promise = up.extract(target, html, extractOptions);
8843
+ }
8844
+ if (!(wasOpen || up.motion.isNone(options.animation))) {
8646
8845
  promise = promise.then(function() {
8647
- return up.animate($('.up-modal'), options.animation, animateOptions);
8846
+ return $.when(up.animate($('.up-modal-backdrop'), options.backdropAnimation, animateOptions), up.animate($('.up-modal-viewport'), options.animation, animateOptions));
8648
8847
  });
8649
8848
  }
8650
8849
  promise = promise.then(function() {
8850
+ shiftElements();
8651
8851
  return up.emit('up:modal:opened', {
8652
8852
  message: 'Modal opened'
8653
8853
  });
@@ -8685,28 +8885,40 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8685
8885
  @function up.modal.close
8686
8886
  @param {Object} options
8687
8887
  See options for [`up.animate`](/up.animate)
8688
- @return {Deferred}
8888
+ @return {Promise}
8689
8889
  A promise that will be resolved once the modal's close
8690
8890
  animation has finished.
8691
8891
  @stable
8692
8892
  */
8693
8893
  close = function(options) {
8694
- var $modal, promise;
8894
+ var $modal, animateOptions, backdropCloseAnimation, promise, viewportCloseAnimation;
8895
+ options = u.options(options);
8695
8896
  $modal = $('.up-modal');
8696
8897
  if ($modal.length) {
8697
8898
  if (up.bus.nobodyPrevents('up:modal:close', {
8698
8899
  $element: $modal,
8699
8900
  message: 'Closing modal'
8700
8901
  })) {
8701
- options = u.options(options, {
8702
- animation: config.closeAnimation,
8703
- url: $modal.attr('up-covered-url'),
8704
- title: $modal.attr('up-covered-title')
8902
+ unshiftElements();
8903
+ viewportCloseAnimation = u.option(options.animation, config.closeAnimation);
8904
+ backdropCloseAnimation = u.option(options.backdropAnimation, config.backdropCloseAnimation);
8905
+ animateOptions = up.motion.animateOptions(options, {
8906
+ duration: config.closeDuration,
8907
+ easing: config.closeEasing
8705
8908
  });
8706
- currentUrl = void 0;
8707
- promise = up.destroy($modal, options);
8909
+ if (up.motion.isNone(viewportCloseAnimation)) {
8910
+ promise = u.resolvedPromise();
8911
+ } else {
8912
+ promise = $.when(up.animate($('.up-modal-viewport'), viewportCloseAnimation, animateOptions), up.animate($('.up-modal-backdrop'), backdropCloseAnimation, animateOptions));
8913
+ }
8708
8914
  promise = promise.then(function() {
8709
- unshiftElements();
8915
+ var destroyOptions;
8916
+ destroyOptions = u.options(u.except(options, 'animation', 'duration', 'easing', 'delay'), {
8917
+ url: $modal.attr('up-covered-url'),
8918
+ title: $modal.attr('up-covered-title')
8919
+ });
8920
+ currentUrl = void 0;
8921
+ up.destroy($modal, destroyOptions);
8710
8922
  return up.emit('up:modal:closed', {
8711
8923
  message: 'Modal closed'
8712
8924
  });
@@ -8779,7 +8991,9 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8779
8991
  If set to `"true"`, the modal remains
8780
8992
  open even if the page changes in the background.
8781
8993
  @param {String} [up-animation]
8782
- The animation to use when opening the modal.
8994
+ The animation to use when opening the viewport containing the dialog.
8995
+ @param {String} [up-backdrop-animation]
8996
+ The animation to use when opening the backdrop that dims the page below the dialog.
8783
8997
  @param {String} [up-height]
8784
8998
  The width of the dialog in pixels.
8785
8999
  By [default](/up.modal.config) the dialog will grow to fit its contents.
@@ -8838,6 +9052,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8838
9052
  knife: eval(typeof Knife !== "undefined" && Knife !== null ? Knife.point : void 0),
8839
9053
  visit: visit,
8840
9054
  follow: follow,
9055
+ extract: extract,
8841
9056
  open: function() {
8842
9057
  return up.error('up.modal.open no longer exists. Please use either up.modal.follow or up.modal.visit.');
8843
9058
  },
@@ -8916,6 +9131,9 @@ The tooltip element is appended to the end of `<body>`.
8916
9131
  closeAnimation: 'fade-out'
8917
9132
  });
8918
9133
  reset = function() {
9134
+ close({
9135
+ animation: false
9136
+ });
8919
9137
  return config.reset();
8920
9138
  };
8921
9139
  setPosition = function($link, $tooltip, position) {
@@ -9096,7 +9314,7 @@ by providing instant feedback for user interactions.
9096
9314
 
9097
9315
  (function() {
9098
9316
  up.navigation = (function($) {
9099
- var CLASS_ACTIVE, SELECTORS_SECTION, SELECTOR_ACTIVE, SELECTOR_SECTION, SELECTOR_SECTION_INSTANT, config, currentClass, enlargeClickArea, locationChanged, normalizeUrl, reset, sectionClicked, sectionUrls, selector, u, unmarkActive, urlSet;
9317
+ var CLASS_ACTIVE, SELECTOR_SECTION, config, currentClass, findClickArea, locationChanged, markActive, normalizeUrl, reset, sectionUrls, u, unmarkActive, urlSet, withActiveMark;
9100
9318
  u = up.util;
9101
9319
 
9102
9320
  /**
@@ -9121,18 +9339,7 @@ by providing instant feedback for user interactions.
9121
9339
  return classes.join(' ');
9122
9340
  };
9123
9341
  CLASS_ACTIVE = 'up-active';
9124
- SELECTORS_SECTION = ['a', '[up-href]', '[up-alias]'];
9125
- SELECTOR_SECTION = SELECTORS_SECTION.join(', ');
9126
- SELECTOR_SECTION_INSTANT = ((function() {
9127
- var i, len, results;
9128
- results = [];
9129
- for (i = 0, len = SELECTORS_SECTION.length; i < len; i++) {
9130
- selector = SELECTORS_SECTION[i];
9131
- results.push(selector + "[up-instant]");
9132
- }
9133
- return results;
9134
- })()).join(', ');
9135
- SELECTOR_ACTIVE = "." + CLASS_ACTIVE;
9342
+ SELECTOR_SECTION = 'a, [up-href]';
9136
9343
  normalizeUrl = function(url) {
9137
9344
  if (u.isPresent(url)) {
9138
9345
  return u.normalizeUrl(url, {
@@ -9201,6 +9408,27 @@ by providing instant feedback for user interactions.
9201
9408
  });
9202
9409
  };
9203
9410
 
9411
+ /**
9412
+ @function findClickArea
9413
+ @param {String|Element|jQuery} elementOrSelector
9414
+ @param {Boolean} options.enlarge
9415
+ If `true`, tries to find a containing link that has expanded the link's click area.
9416
+ If we find one, we prefer to mark the larger area as active.
9417
+ @internal
9418
+ */
9419
+ findClickArea = function(elementOrSelector, options) {
9420
+ var $area;
9421
+ $area = $(elementOrSelector);
9422
+ options = u.options(options, {
9423
+ enlarge: false
9424
+ });
9425
+ if (options.enlarge) {
9426
+ return u.presence($area.parent(SELECTOR_SECTION)) || $area;
9427
+ } else {
9428
+ return $area;
9429
+ }
9430
+ };
9431
+
9204
9432
  /**
9205
9433
  Links that are currently loading are assigned the `up-active`
9206
9434
  class automatically. Style `.up-active` in your CSS to improve the
@@ -9228,27 +9456,30 @@ by providing instant feedback for user interactions.
9228
9456
  @selector .up-active
9229
9457
  @stable
9230
9458
  */
9231
- sectionClicked = function($section) {
9232
- unmarkActive();
9233
- $section = enlargeClickArea($section);
9234
- return $section.addClass(CLASS_ACTIVE);
9235
- };
9236
- enlargeClickArea = function($section) {
9237
- return u.presence($section.parents(SELECTOR_SECTION)) || $section;
9459
+ markActive = function(elementOrSelector, options) {
9460
+ var $element;
9461
+ $element = findClickArea(elementOrSelector, options);
9462
+ return $element.addClass(CLASS_ACTIVE);
9238
9463
  };
9239
- unmarkActive = function() {
9240
- return $(SELECTOR_ACTIVE).removeClass(CLASS_ACTIVE);
9464
+ unmarkActive = function(elementOrSelector, options) {
9465
+ var $element;
9466
+ $element = findClickArea(elementOrSelector, options);
9467
+ return $element.removeClass(CLASS_ACTIVE);
9241
9468
  };
9242
- up.on('click', SELECTOR_SECTION, function(event, $section) {
9243
- if (u.isUnmodifiedMouseEvent(event) && !$section.is('[up-instant]')) {
9244
- return sectionClicked($section);
9245
- }
9246
- });
9247
- up.on('mousedown', SELECTOR_SECTION_INSTANT, function(event, $section) {
9248
- if (u.isUnmodifiedMouseEvent(event)) {
9249
- return sectionClicked($section);
9469
+ withActiveMark = function(elementOrSelector, options, block) {
9470
+ var $element, promise;
9471
+ $element = $(elementOrSelector);
9472
+ markActive($element, options);
9473
+ promise = block();
9474
+ if (u.isPromise(promise)) {
9475
+ promise.always(function() {
9476
+ return unmarkActive($element, options);
9477
+ });
9478
+ } else {
9479
+ up.warn('Expected block to return a promise, but got %o', promise);
9250
9480
  }
9251
- });
9481
+ return promise;
9482
+ };
9252
9483
 
9253
9484
  /**
9254
9485
  Links that point to the current location are assigned
@@ -9295,7 +9526,6 @@ by providing instant feedback for user interactions.
9295
9526
  @stable
9296
9527
  */
9297
9528
  up.on('up:fragment:inserted', function() {
9298
- unmarkActive();
9299
9529
  return locationChanged();
9300
9530
  });
9301
9531
  up.on('up:fragment:destroyed', function(event, $fragment) {
@@ -9308,7 +9538,10 @@ by providing instant feedback for user interactions.
9308
9538
  config: config,
9309
9539
  defaults: function() {
9310
9540
  return u.error('up.navigation.defaults(...) no longer exists. Set values on he up.navigation.config property instead.');
9311
- }
9541
+ },
9542
+ markActive: markActive,
9543
+ unmarkActive: unmarkActive,
9544
+ withActiveMark: withActiveMark
9312
9545
  };
9313
9546
  })(jQuery);
9314
9547