unpoly-rails 0.26.2 → 0.27.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.

Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -1
  3. data/dist/unpoly.js +704 -446
  4. data/dist/unpoly.min.js +3 -3
  5. data/lib/assets/javascripts/unpoly/browser.js.coffee +18 -9
  6. data/lib/assets/javascripts/unpoly/bus.js.coffee +28 -1
  7. data/lib/assets/javascripts/unpoly/flow.js.coffee +1 -1
  8. data/lib/assets/javascripts/unpoly/history.js.coffee +54 -22
  9. data/lib/assets/javascripts/unpoly/link.js.coffee +1 -1
  10. data/lib/assets/javascripts/unpoly/log.js.coffee +19 -12
  11. data/lib/assets/javascripts/unpoly/modal.js.coffee +119 -124
  12. data/lib/assets/javascripts/unpoly/motion.js.coffee +1 -0
  13. data/lib/assets/javascripts/unpoly/navigation.js.coffee +2 -6
  14. data/lib/assets/javascripts/unpoly/popup.js.coffee +136 -126
  15. data/lib/assets/javascripts/unpoly/proxy.js.coffee +0 -2
  16. data/lib/assets/javascripts/unpoly/syntax.js.coffee +1 -1
  17. data/lib/assets/javascripts/unpoly/tooltip.js.coffee +101 -46
  18. data/lib/assets/javascripts/unpoly/util.js.coffee +76 -7
  19. data/lib/unpoly/rails/version.rb +1 -1
  20. data/spec_app/Gemfile.lock +1 -1
  21. data/spec_app/app/assets/stylesheets/integration_test.sass +4 -0
  22. data/spec_app/app/assets/stylesheets/jasmine_specs.sass +5 -0
  23. data/spec_app/app/views/css_test/modal.erb +3 -0
  24. data/spec_app/app/views/css_test/modal_contents.erb +5 -0
  25. data/spec_app/app/views/css_test/popup.erb +11 -11
  26. data/spec_app/app/views/css_test/tooltip.erb +12 -5
  27. data/spec_app/app/views/pages/start.erb +4 -0
  28. data/spec_app/spec/javascripts/helpers/reset_up.js.coffee +2 -3
  29. data/spec_app/spec/javascripts/up/flow_spec.js.coffee +97 -88
  30. data/spec_app/spec/javascripts/up/history_spec.js.coffee +100 -1
  31. data/spec_app/spec/javascripts/up/link_spec.js.coffee +18 -16
  32. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +102 -97
  33. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +89 -75
  34. data/spec_app/spec/javascripts/up/navigation_spec.js.coffee +17 -5
  35. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +89 -70
  36. data/spec_app/spec/javascripts/up/util_spec.js.coffee +23 -0
  37. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cedb595bc61aa389ebd36b8d72488b1323051cce
4
- data.tar.gz: e5f0712f8d65821539cb6841d7e2fc8323630533
3
+ metadata.gz: f17543cc24f100d214a24c6e95a4b2797f4c3219
4
+ data.tar.gz: 07acf715ae8eec39928dd4a1d56c5e5a5d551c1a
5
5
  SHA512:
6
- metadata.gz: eeee5c15b68fb9eb764cb33c863c109dffceffa881a6fa791f478454feba794500823337b24fedf8f5d6f77b5e11d90e3567d6e1a0fa8034d06520aa4b10235e
7
- data.tar.gz: f062f010d33baa4086e83cffa994b48b79b2db93b7d4560a8126cdcc7569276afcc4e2b2549f6f47d2ab1da4360d3e06e628efefd5617ad6c67e421ce7ca4a81
6
+ metadata.gz: a8483d33d591d5e41e750819b6ccb839486000bd9755a9800e5c6fc1a1b9d5022921021cc3cb7f70b27ef8d498d3269e2f24f46a6332966c4328d8595e7c5365
7
+ data.tar.gz: 730a812cbf165a4c7af3ca8aa359cee55972b6bb2db1cf0d354fbb7e36a4bf12172c91e067e229787e940551dcd9600022a98d5552af446a01d63a2ba170c3fd
data/CHANGELOG.md CHANGED
@@ -5,7 +5,6 @@ All notable changes to this project will be documented in this file.
5
5
  This project mostly adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
7
 
8
-
9
8
  Unreleased
10
9
  ----------
11
10
 
@@ -16,6 +15,39 @@ Unreleased
16
15
 
17
16
 
18
17
 
18
+ 0.27.0
19
+ ------
20
+
21
+ ### Compatible changes
22
+
23
+ - Calling [`up.log.enable`](/up.log.enable) will now keep logging enabled for the remainder of this
24
+ browser session (and persist through page reloads).
25
+ - Added experimental events to observe history changes: [`up:history:push`](/up:history:push) (preventable), [`up:history:pushed`](/up:history:pushed) and [`up:history:restored`](/up:history:restored)
26
+ - Fix a bug where prepending or appending multiple elements with `:before` / `:after` pseudo-classes
27
+ would not work correctly in tables.
28
+ - Fix a bug where calling [`up.animate`](/up.animate) with `{ duration: 0 }` would return a promise
29
+ that never resolved.
30
+ - A click on the page body now closes the popup on `mousedown` instead of `click`.
31
+ This fixes the case where an `[up-instant]` link removes its parent and thus a `click` event never bubbles up to the body.
32
+ - When opening a modal, elements behind the dialog can now be moved correctly when scrollbars have custom styles on `::-webkit-scrollbar`.
33
+ To take advantage of this, make sure to also style scrollbars on elements with an [`[up-viewport]`](/up-viewport) attribute.
34
+ - Fix a bug where [`up.tooltip.config`](/up.tooltip.config) was not publicly acccessible.
35
+ - Fix a bug where [`up.tooltip.isOpen`](/up.tooltip.isOpen) was not publicly acccessible.
36
+ - New [tooltip configuration options](/up.tooltip.config): `config.openDuration`, `config.closeDuration`, `config.openEasing`, `config.closeEasing`
37
+ - Opening/closing many tooltips concurrently now behaves deterministically.
38
+ - Opening/closing many popups concurrently now behaves deterministically.
39
+ - Opening/closing many modals concurrently now behaves deterministically.
40
+ - IE9 fixes: Polyfill `window.console` and several properties (`log`, `debug`, `info`, `warn`, `error`, `group`, `groupCollapsed`, `groupEnd`)
41
+
42
+
43
+ ### Breaking changes
44
+
45
+ - Tooltips now open and close much quicker.
46
+ - Popups now open and close much quicker.
47
+ - [`.up-current`](/up-current) now considers two URLs different if they have different query strings.
48
+
49
+
50
+
19
51
  0.26.2
20
52
  ------
21
53
 
data/dist/unpoly.js CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  (function() {
7
7
  window.up = {
8
- version: "0.26.2"
8
+ version: "0.27.0"
9
9
  };
10
10
 
11
11
  }).call(this);
@@ -21,7 +21,8 @@ that might save you from loading something like [Underscore.js](http://underscor
21
21
  */
22
22
 
23
23
  (function() {
24
- var slice = [].slice;
24
+ var slice = [].slice,
25
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
25
26
 
26
27
  up.util = (function($) {
27
28
 
@@ -31,7 +32,7 @@ that might save you from loading something like [Underscore.js](http://underscor
31
32
  @function up.util.noop
32
33
  @experimental
33
34
  */
34
- var $createElementFromSelector, $createPlaceholder, ANIMATION_DEFERRED_KEY, ESCAPE_HTML_ENTITY_MAP, all, any, appendRequestData, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, cssAnimate, detect, documentHasVerticalScrollbar, each, error, escapeHtml, escapePressed, except, extend, extractOptions, findWithSelf, finishCssAnimate, fixedToAbsolute, forceCompositing, forceRepaint, identity, intersect, isArray, isBlank, isDeferred, isDefined, isDetached, isElement, isFixed, 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, opacity, 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, whenReady;
35
+ var $createElementFromSelector, $createPlaceholder, ANIMATION_DEFERRED_KEY, DivertibleChain, ESCAPE_HTML_ENTITY_MAP, all, any, appendRequestData, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, cssAnimate, detect, documentHasVerticalScrollbar, each, error, escapeHtml, escapePressed, except, extend, extractOptions, findWithSelf, finishCssAnimate, fixedToAbsolute, forceCompositing, forceRepaint, identity, intersect, isArray, isBlank, isDeferred, isDefined, isDetached, isElement, isFixed, 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, opacity, option, options, parseUrl, pluckData, pluckKey, presence, presentAttr, previewable, reject, remove, requestDataAsArray, requestDataAsQuery, requestDataFromForm, resolvableWhen, resolvedDeferred, resolvedPromise, scrollbarWidth, select, selectorForElement, setMissingAttrs, setTimer, temporaryCss, times, titleFromXhr, toArray, trim, unJQuery, uniq, unresolvableDeferred, unresolvablePromise, unwrapElement, whenReady;
35
36
  noop = $.noop;
36
37
 
37
38
  /**
@@ -76,7 +77,7 @@ that might save you from loading something like [Underscore.js](http://underscor
76
77
  Whether to include an `#hash` anchor in the normalized URL
77
78
  @param {Boolean} [options.search=true]
78
79
  Whether to include a `?query` string in the normalized URL
79
- @param {Boolean} [options.stripTrailingSlash=false]
80
+ @param {Boolean} [options.stripTrailingSlash=true]
80
81
  Whether to strip a trailing slash from the pathname
81
82
  @internal
82
83
  */
@@ -91,7 +92,7 @@ that might save you from loading something like [Underscore.js](http://underscor
91
92
  if (pathname[0] !== '/') {
92
93
  pathname = "/" + pathname;
93
94
  }
94
- if ((options != null ? options.stripTrailingSlash : void 0) === true) {
95
+ if ((options != null ? options.stripTrailingSlash : void 0) !== false) {
95
96
  pathname = pathname.replace(/\/$/, '');
96
97
  }
97
98
  normalized += pathname;
@@ -653,7 +654,7 @@ that might save you from loading something like [Underscore.js](http://underscor
653
654
  copy = function(object) {
654
655
  if (isArray(object)) {
655
656
  return object.slice();
656
- } else {
657
+ } else if (isHash(object)) {
657
658
  return extend({}, object);
658
659
  }
659
660
  };
@@ -976,7 +977,9 @@ that might save you from loading something like [Underscore.js](http://underscor
976
977
  */
977
978
  scrollbarWidth = memoize(function() {
978
979
  var $outer, outer, width;
979
- $outer = $('<div>').css({
980
+ $outer = $('<div>');
981
+ $outer.attr('up-viewport', '');
982
+ $outer.css({
980
983
  position: 'absolute',
981
984
  top: '0',
982
985
  left: '0',
@@ -1115,7 +1118,7 @@ that might save you from loading something like [Underscore.js](http://underscor
1115
1118
  The timing function that controls the animation's acceleration.
1116
1119
  See [W3C documentation](http://www.w3.org/TR/css3-transitions/#transition-timing-function)
1117
1120
  for a list of pre-defined timing functions.
1118
- @return
1121
+ @return {Deferred}
1119
1122
  A promise for the animation's end.
1120
1123
  @internal
1121
1124
  */
@@ -1127,6 +1130,10 @@ that might save you from loading something like [Underscore.js](http://underscor
1127
1130
  delay: 0,
1128
1131
  easing: 'ease'
1129
1132
  });
1133
+ if (opts.duration === 0) {
1134
+ $element.css(lastFrame);
1135
+ return resolvedDeferred();
1136
+ }
1130
1137
  deferred = $.Deferred();
1131
1138
  transitionProperties = Object.keys(lastFrame);
1132
1139
  transition = {
@@ -1741,14 +1748,19 @@ that might save you from loading something like [Underscore.js](http://underscor
1741
1748
  @function up.util.config
1742
1749
  @internal
1743
1750
  */
1744
- config = function(factoryOptions) {
1751
+ config = function(blueprint) {
1745
1752
  var hash;
1746
- if (factoryOptions == null) {
1747
- factoryOptions = {};
1753
+ if (blueprint == null) {
1754
+ blueprint = {};
1748
1755
  }
1749
1756
  hash = {};
1750
1757
  hash.reset = function() {
1751
- return extend(hash, factoryOptions);
1758
+ var newOptions;
1759
+ newOptions = blueprint;
1760
+ if (isFunction(newOptions)) {
1761
+ newOptions = newOptions();
1762
+ }
1763
+ return extend(hash, newOptions);
1752
1764
  };
1753
1765
  hash.reset();
1754
1766
  Object.preventExtensions(hash);
@@ -2044,6 +2056,96 @@ that might save you from loading something like [Underscore.js](http://underscor
2044
2056
  element = unJQuery(element);
2045
2057
  return !jQuery.contains(document.documentElement, element);
2046
2058
  };
2059
+
2060
+ /**
2061
+ Given a function that will return a promise, returns a proxy function
2062
+ with an additional `.promise` attribute.
2063
+
2064
+ When the proxy is called, the inner function is called.
2065
+ The proxy's `.promise` attribute is available even before the function is called
2066
+ and will resolve when the inner function's returned promise resolves.
2067
+
2068
+ @function up.util.previewable
2069
+ @internal
2070
+ */
2071
+ previewable = function(fun) {
2072
+ var deferred, preview;
2073
+ deferred = $.Deferred();
2074
+ preview = function() {
2075
+ var args;
2076
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
2077
+ return fun.apply(null, args).then(function() {
2078
+ return deferred.resolve();
2079
+ });
2080
+ };
2081
+ preview.promise = deferred.promise();
2082
+ return preview;
2083
+ };
2084
+
2085
+ /**
2086
+ A linear task queue whose (2..n)th tasks can be changed at any time.
2087
+
2088
+ @function up.util.DivertibleChain
2089
+ @internal
2090
+ */
2091
+ DivertibleChain = (function() {
2092
+ function DivertibleChain() {
2093
+ this.asap = bind(this.asap, this);
2094
+ this.poke = bind(this.poke, this);
2095
+ this.allTasks = bind(this.allTasks, this);
2096
+ this.promise = bind(this.promise, this);
2097
+ this.reset = bind(this.reset, this);
2098
+ this.reset();
2099
+ }
2100
+
2101
+ DivertibleChain.prototype.reset = function() {
2102
+ this.queue = [];
2103
+ return this.currentTask = void 0;
2104
+ };
2105
+
2106
+ DivertibleChain.prototype.promise = function() {
2107
+ var promises;
2108
+ promises = map(this.allTasks(), function(task) {
2109
+ return task.promise;
2110
+ });
2111
+ return $.when.apply($, promises);
2112
+ };
2113
+
2114
+ DivertibleChain.prototype.allTasks = function() {
2115
+ var tasks;
2116
+ tasks = [];
2117
+ if (this.currentTask) {
2118
+ tasks.push(this.currentTask);
2119
+ }
2120
+ tasks = tasks.concat(this.queue);
2121
+ return tasks;
2122
+ };
2123
+
2124
+ DivertibleChain.prototype.poke = function() {
2125
+ var promise;
2126
+ if (!this.currentTask) {
2127
+ if (this.currentTask = this.queue.shift()) {
2128
+ promise = this.currentTask();
2129
+ return promise.always((function(_this) {
2130
+ return function() {
2131
+ _this.currentTask = void 0;
2132
+ return _this.poke();
2133
+ };
2134
+ })(this));
2135
+ }
2136
+ }
2137
+ };
2138
+
2139
+ DivertibleChain.prototype.asap = function() {
2140
+ var newTasks;
2141
+ newTasks = 1 <= arguments.length ? slice.call(arguments, 0) : [];
2142
+ this.queue = map(newTasks, previewable);
2143
+ return this.poke();
2144
+ };
2145
+
2146
+ return DivertibleChain;
2147
+
2148
+ })();
2047
2149
  return {
2048
2150
  isDetached: isDetached,
2049
2151
  requestDataAsArray: requestDataAsArray,
@@ -2147,7 +2249,8 @@ that might save you from loading something like [Underscore.js](http://underscor
2147
2249
  opacity: opacity,
2148
2250
  whenReady: whenReady,
2149
2251
  identity: identity,
2150
- escapeHtml: escapeHtml
2252
+ escapeHtml: escapeHtml,
2253
+ DivertibleChain: DivertibleChain
2151
2254
  };
2152
2255
  })($);
2153
2256
 
@@ -2169,7 +2272,7 @@ we can't currently get rid off.
2169
2272
  var slice = [].slice;
2170
2273
 
2171
2274
  up.browser = (function($) {
2172
- var CONSOLE_PLACEHOLDERS, canCssTransition, canFormData, canInputEvent, canLogSubstitution, canPushState, confirm, initialRequestMethod, installPolyfills, isIE8OrWorse, isIE9OrWorse, isRecentJQuery, isSupported, loadPage, popCookie, puts, sprintf, sprintfWithFormattedArgs, stringifyArg, u, url;
2275
+ var CONSOLE_PLACEHOLDERS, canCssTransition, canFormData, canInputEvent, canLogSubstitution, canPushState, initialRequestMethod, installPolyfills, isIE8OrWorse, isIE9OrWorse, isRecentJQuery, isSupported, loadPage, popCookie, puts, sessionStorage, sprintf, sprintfWithFormattedArgs, stringifyArg, u, url, whenConfirmed;
2173
2276
  u = up.util;
2174
2277
 
2175
2278
  /**
@@ -2230,12 +2333,11 @@ we can't currently get rid off.
2230
2333
  puts = function() {
2231
2334
  var args, message, stream;
2232
2335
  stream = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
2233
- u.isDefined(console[stream]) || (stream = 'log');
2234
2336
  if (canLogSubstitution()) {
2235
- return typeof console[stream] === "function" ? console[stream].apply(console, args) : void 0;
2337
+ return console[stream].apply(console, args);
2236
2338
  } else {
2237
2339
  message = sprintf.apply(null, args);
2238
- return typeof console[stream] === "function" ? console[stream](message) : void 0;
2340
+ return console[stream](message);
2239
2341
  }
2240
2342
  };
2241
2343
  CONSOLE_PLACEHOLDERS = /\%[odisf]/g;
@@ -2300,6 +2402,9 @@ we can't currently get rid off.
2300
2402
  sprintfWithFormattedArgs = function() {
2301
2403
  var args, formatter, i, message;
2302
2404
  formatter = arguments[0], message = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
2405
+ if (u.isBlank(message)) {
2406
+ return '';
2407
+ }
2303
2408
  i = 0;
2304
2409
  return message.replace(CONSOLE_PLACEHOLDERS, function() {
2305
2410
  var arg;
@@ -2408,13 +2513,13 @@ we can't currently get rid off.
2408
2513
  };
2409
2514
 
2410
2515
  /**
2411
- @function up,browser.confirm
2516
+ @function up,browser.whenConfirmed
2412
2517
  @return {Promise}
2413
2518
  @param {String} options.confirm
2414
2519
  @param {Boolean} options.preload
2415
2520
  @internal
2416
2521
  */
2417
- confirm = function(options) {
2522
+ whenConfirmed = function(options) {
2418
2523
  if (options.preload || u.isBlank(options.confirm) || window.confirm(options.confirm)) {
2419
2524
  return u.resolvedPromise();
2420
2525
  } else {
@@ -2449,26 +2554,38 @@ we can't currently get rid off.
2449
2554
  @internal
2450
2555
  */
2451
2556
  installPolyfills = function() {
2452
- console.group || (console.group = function() {
2453
- var args;
2454
- args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
2455
- return puts.apply(null, ['group'].concat(slice.call(args)));
2456
- });
2457
- console.groupCollapsed || (console.groupCollapsed = function() {
2458
- var args;
2459
- args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
2460
- return puts.apply(null, ['groupCollapsed'].concat(slice.call(args)));
2461
- });
2462
- return console.groupEnd || (console.groupEnd = function() {
2463
- var args;
2464
- args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
2465
- return puts.apply(null, ['groupEnd'].concat(slice.call(args)));
2466
- });
2557
+ var j, len, method, ref;
2558
+ if (window.console == null) {
2559
+ window.console = {};
2560
+ }
2561
+ ref = ['debug', 'info', 'warn', 'error', 'group', 'groupCollapsed', 'groupEnd'];
2562
+ for (j = 0, len = ref.length; j < len; j++) {
2563
+ method = ref[j];
2564
+ if (console[method] == null) {
2565
+ console[method] = function() {
2566
+ var args;
2567
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
2568
+ return puts.apply(null, ['log'].concat(slice.call(args)));
2569
+ };
2570
+ }
2571
+ }
2572
+ return console.log != null ? console.log : console.log = u.noop;
2467
2573
  };
2574
+
2575
+ /**
2576
+ @internal
2577
+ */
2578
+ sessionStorage = u.memoize(function() {
2579
+ return window.sessionStorage || {
2580
+ getItem: u.noop,
2581
+ setItem: u.noop,
2582
+ removeItem: u.noop
2583
+ };
2584
+ });
2468
2585
  return {
2469
2586
  url: url,
2470
2587
  loadPage: loadPage,
2471
- confirm: confirm,
2588
+ whenConfirmed: whenConfirmed,
2472
2589
  canPushState: canPushState,
2473
2590
  canCssTransition: canCssTransition,
2474
2591
  canInputEvent: canInputEvent,
@@ -2478,7 +2595,8 @@ we can't currently get rid off.
2478
2595
  installPolyfills: installPolyfills,
2479
2596
  puts: puts,
2480
2597
  sprintf: sprintf,
2481
- sprintfWithFormattedArgs: sprintfWithFormattedArgs
2598
+ sprintfWithFormattedArgs: sprintfWithFormattedArgs,
2599
+ sessionStorage: sessionStorage
2482
2600
  };
2483
2601
  })(jQuery);
2484
2602
 
@@ -2536,7 +2654,7 @@ This improves jQuery's [`on`](http://api.jquery.com/on/) in multiple ways:
2536
2654
  var slice = [].slice;
2537
2655
 
2538
2656
  up.bus = (function($) {
2539
- var boot, emit, emitReset, forgetUpDescription, live, liveUpDescriptions, logEmission, nextUpDescriptionNumber, nobodyPrevents, onEscape, rememberUpDescription, restoreSnapshot, snapshot, u, unbind, upDescriptionNumber, upDescriptionToJqueryDescription, upListenerToJqueryListener;
2657
+ var boot, emit, emitReset, forgetUpDescription, live, liveUpDescriptions, logEmission, nextUpDescriptionNumber, nobodyPrevents, onEscape, rememberUpDescription, restoreSnapshot, snapshot, u, unbind, upDescriptionNumber, upDescriptionToJqueryDescription, upListenerToJqueryListener, whenEmitted;
2540
2658
  u = up.util;
2541
2659
  liveUpDescriptions = {};
2542
2660
  nextUpDescriptionNumber = 0;
@@ -2801,13 +2919,15 @@ This improves jQuery's [`on`](http://api.jquery.com/on/) in multiple ways:
2801
2919
  };
2802
2920
 
2803
2921
  /**
2804
- [Emits an event](/up.emit) and returns whether any listener
2922
+ [Emits an event](/up.emit) and returns whether no listener
2805
2923
  has prevented the default action.
2806
2924
 
2807
2925
  @function up.bus.nobodyPrevents
2808
2926
  @param {String} eventName
2809
2927
  @param {Object} eventProps
2810
2928
  @param {String|Array} [eventProps.message]
2929
+ @return {Boolean}
2930
+ whether no listener has prevented the default action
2811
2931
  @experimental
2812
2932
  */
2813
2933
  nobodyPrevents = function() {
@@ -2822,6 +2942,30 @@ This improves jQuery's [`on`](http://api.jquery.com/on/) in multiple ways:
2822
2942
  }
2823
2943
  };
2824
2944
 
2945
+ /**
2946
+ [Emits](/up.emit) the given event and returns a promise
2947
+ that will be resolved if no listener has prevented the default action.
2948
+
2949
+ If any listener prevented the default listener
2950
+ the returned promise will never be resolved.
2951
+
2952
+ @function up.bus.whenEmitted
2953
+ @param {String} eventName
2954
+ @param {Object} eventProps
2955
+ @param {String|Array} [eventProps.message]
2956
+ @return {Promise}
2957
+ @experimental
2958
+ */
2959
+ whenEmitted = function() {
2960
+ var args, deferred;
2961
+ args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
2962
+ deferred = $.Deferred();
2963
+ if (nobodyPrevents.apply(null, args)) {
2964
+ deferred.resolve();
2965
+ }
2966
+ return deferred.promise();
2967
+ };
2968
+
2825
2969
  /**
2826
2970
  Registers an event listener to be called when the user
2827
2971
  presses the `Escape` key.
@@ -2884,6 +3028,8 @@ This improves jQuery's [`on`](http://api.jquery.com/on/) in multiple ways:
2884
3028
  This is an internal method for to enable unit testing.
2885
3029
  Don't use this in production.
2886
3030
 
3031
+ Emits event [`up:framework:reset`](/up:framework:reset).
3032
+
2887
3033
  @function up.reset
2888
3034
  @experimental
2889
3035
  */
@@ -2949,6 +3095,7 @@ This improves jQuery's [`on`](http://api.jquery.com/on/) in multiple ways:
2949
3095
  off: unbind,
2950
3096
  emit: emit,
2951
3097
  nobodyPrevents: nobodyPrevents,
3098
+ whenEmitted: whenEmitted,
2952
3099
  onEscape: onEscape,
2953
3100
  emitReset: emitReset,
2954
3101
  boot: boot
@@ -2987,8 +3134,10 @@ The output can be configured using the [`up.log.config`](/up.log.config) propert
2987
3134
  var slice = [].slice;
2988
3135
 
2989
3136
  up.log = (function($) {
2990
- var config, debug, disable, enable, error, group, prefix, printBanner, puts, reset, u, warn;
3137
+ var SESSION_KEY_ENABLED, b, config, debug, disable, enable, error, group, prefix, printBanner, puts, reset, setEnabled, u, warn;
2991
3138
  u = up.util;
3139
+ b = up.browser;
3140
+ SESSION_KEY_ENABLED = 'up.log.enabled';
2992
3141
 
2993
3142
  /**
2994
3143
  Configures the logging output on the developer console.
@@ -3011,7 +3160,7 @@ The output can be configured using the [`up.log.config`](/up.log.config) propert
3011
3160
  */
3012
3161
  config = u.config({
3013
3162
  prefix: '[UP] ',
3014
- enabled: false,
3163
+ enabled: u.option(b.sessionStorage().getItem(SESSION_KEY_ENABLED), false),
3015
3164
  collapse: false
3016
3165
  });
3017
3166
  reset = function() {
@@ -3030,10 +3179,10 @@ The output can be configured using the [`up.log.config`](/up.log.config) propert
3030
3179
  @internal
3031
3180
  */
3032
3181
  debug = function() {
3033
- var args, message, ref;
3182
+ var args, message;
3034
3183
  message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
3035
3184
  if (config.enabled && message) {
3036
- return (ref = up.browser).puts.apply(ref, ['debug', prefix(message)].concat(slice.call(args)));
3185
+ return b.puts.apply(b, ['debug', prefix(message)].concat(slice.call(args)));
3037
3186
  }
3038
3187
  };
3039
3188
 
@@ -3046,10 +3195,10 @@ The output can be configured using the [`up.log.config`](/up.log.config) propert
3046
3195
  @internal
3047
3196
  */
3048
3197
  puts = function() {
3049
- var args, message, ref;
3198
+ var args, message;
3050
3199
  message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
3051
3200
  if (config.enabled && message) {
3052
- return (ref = up.browser).puts.apply(ref, ['log', prefix(message)].concat(slice.call(args)));
3201
+ return b.puts.apply(b, ['log', prefix(message)].concat(slice.call(args)));
3053
3202
  }
3054
3203
  };
3055
3204
 
@@ -3058,10 +3207,10 @@ The output can be configured using the [`up.log.config`](/up.log.config) propert
3058
3207
  @internal
3059
3208
  */
3060
3209
  warn = function() {
3061
- var args, message, ref;
3210
+ var args, message;
3062
3211
  message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
3063
3212
  if (config.enabled && message) {
3064
- return (ref = up.browser).puts.apply(ref, ['warn', prefix(message)].concat(slice.call(args)));
3213
+ return b.puts.apply(b, ['warn', prefix(message)].concat(slice.call(args)));
3065
3214
  }
3066
3215
  };
3067
3216
 
@@ -3073,17 +3222,17 @@ The output can be configured using the [`up.log.config`](/up.log.config) propert
3073
3222
  @internal
3074
3223
  */
3075
3224
  group = function() {
3076
- var args, block, message, ref, stream;
3225
+ var args, block, message, stream;
3077
3226
  message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
3078
3227
  block = args.pop();
3079
3228
  if (config.enabled && message) {
3080
3229
  stream = config.collapse ? 'groupCollapsed' : 'group';
3081
- (ref = up.browser).puts.apply(ref, [stream, prefix(message)].concat(slice.call(args)));
3230
+ b.puts.apply(b, [stream, prefix(message)].concat(slice.call(args)));
3082
3231
  try {
3083
3232
  return block();
3084
3233
  } finally {
3085
3234
  if (message) {
3086
- console.groupEnd();
3235
+ b.puts('groupEnd');
3087
3236
  }
3088
3237
  }
3089
3238
  } else {
@@ -3096,24 +3245,28 @@ The output can be configured using the [`up.log.config`](/up.log.config) propert
3096
3245
  @internal
3097
3246
  */
3098
3247
  error = function() {
3099
- var args, message, ref;
3248
+ var args, message;
3100
3249
  message = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
3101
3250
  if (message) {
3102
- return (ref = up.browser).puts.apply(ref, ['error', prefix(message)].concat(slice.call(args)));
3251
+ return b.puts.apply(b, ['error', prefix(message)].concat(slice.call(args)));
3103
3252
  }
3104
3253
  };
3105
3254
  printBanner = function() {
3106
3255
  var banner;
3107
3256
  banner = " __ _____ ___ ___ / /_ __\n" + ("/ // / _ \\/ _ \\/ _ \\/ / // / " + up.version + "\n") + "\\___/_//_/ .__/\\___/_/\\_. / \n" + " / / / /\n" + "\n";
3108
3257
  if (config.enabled) {
3109
- banner += "Call `up.log.disable()` to disable debugging output.";
3258
+ banner += "Call `up.log.disable()` to disable logging for this session.";
3110
3259
  } else {
3111
- banner += "Call `up.log.enable()` to enable debugging output.";
3260
+ banner += "Call `up.log.enable()` to enable logging for this session.";
3112
3261
  }
3113
- return up.browser.puts('log', banner);
3262
+ return b.puts('log', banner);
3114
3263
  };
3115
3264
  up.on('up:framework:boot', printBanner);
3116
3265
  up.on('up:framework:reset', reset);
3266
+ setEnabled = function(value) {
3267
+ b.sessionStorage().setItem(SESSION_KEY_ENABLED, value);
3268
+ return config.enabled = value;
3269
+ };
3117
3270
 
3118
3271
  /**
3119
3272
  Makes future Unpoly events print vast amounts of debugging information to the developer console.
@@ -3125,7 +3278,7 @@ The output can be configured using the [`up.log.config`](/up.log.config) propert
3125
3278
  @stable
3126
3279
  */
3127
3280
  enable = function() {
3128
- return config.enabled = true;
3281
+ return setEnabled(true);
3129
3282
  };
3130
3283
 
3131
3284
  /**
@@ -3137,7 +3290,7 @@ The output can be configured using the [`up.log.config`](/up.log.config) propert
3137
3290
  @stable
3138
3291
  */
3139
3292
  disable = function() {
3140
- return config.enabled = false;
3293
+ return setEnabled(false);
3141
3294
  };
3142
3295
  return {
3143
3296
  puts: puts,
@@ -3209,7 +3362,7 @@ later.
3209
3362
  // your code here
3210
3363
  });
3211
3364
 
3212
- The functions will be called on elements maching `.action` when
3365
+ The functions will be called on elements matching `.action` when
3213
3366
  the page loads, or whenever a matching fragment is [updated through Unpoly](/up.replace)
3214
3367
  later.
3215
3368
 
@@ -3769,11 +3922,10 @@ We need to work on this page:
3769
3922
 
3770
3923
  @function up.history.replace
3771
3924
  @param {String} url
3772
- @param {Boolean} [options.force=false]
3773
3925
  @experimental
3774
3926
  */
3775
- replace = function(url, options) {
3776
- return manipulate('replace', url, options);
3927
+ replace = function(url) {
3928
+ return manipulate('replaceState', url);
3777
3929
  };
3778
3930
 
3779
3931
  /**
@@ -3788,28 +3940,57 @@ We need to work on this page:
3788
3940
  [`up.submit`](/up.submit) will automatically update the
3789
3941
  browser's location bar for you.
3790
3942
 
3943
+ Emits events [`up:history:push`](/up:history:push) and [`up:history:pushed`](/up:history:pushed).
3944
+
3791
3945
  @function up.history.push
3792
3946
  @param {String} url
3947
+ The URL for the history entry to be added.
3793
3948
  @experimental
3794
3949
  */
3795
3950
  push = function(url, options) {
3796
- up.puts("Current location is now %s", url);
3797
- return manipulate('push', url, options);
3798
- };
3799
- manipulate = function(method, url, options) {
3800
- var fullMethod, state;
3801
3951
  options = u.options(options, {
3802
3952
  force: false
3803
3953
  });
3804
- if (options.force || !isCurrentUrl(url)) {
3805
- if (up.browser.canPushState()) {
3806
- fullMethod = method + "State";
3807
- state = buildState();
3808
- window.history[fullMethod](state, '', url);
3809
- return observeNewUrl(currentUrl());
3810
- } else {
3811
- return u.error("This browser doesn't support history.pushState");
3812
- }
3954
+ url = normalizeUrl(url);
3955
+ if ((options.force || !isCurrentUrl(url)) && up.bus.nobodyPrevents('up:history:push', {
3956
+ url: url,
3957
+ message: "Adding history entry for " + url
3958
+ })) {
3959
+ manipulate('pushState', url);
3960
+ return up.emit('up:history:pushed', {
3961
+ url: url,
3962
+ message: "Advanced to location " + url
3963
+ });
3964
+ }
3965
+ };
3966
+
3967
+ /**
3968
+ This event is [emitted](/up.emit) before a new history entry is added.
3969
+
3970
+ @event up:history:push
3971
+ @param {String} event.url
3972
+ The URL for the history entry that is going to be added.
3973
+ @param event.preventDefault()
3974
+ Event listeners may call this method to prevent the history entry from being added.
3975
+ @experimental
3976
+ */
3977
+
3978
+ /**
3979
+ This event is [emitted](/up.emit) after a new history entry has been added.
3980
+
3981
+ @event up:history:pushed
3982
+ @param {String} event.url
3983
+ The URL for the history entry that has been added.
3984
+ @experimental
3985
+ */
3986
+ manipulate = function(method, url) {
3987
+ var state;
3988
+ if (up.browser.canPushState()) {
3989
+ state = buildState();
3990
+ window.history[method](state, '', url);
3991
+ return observeNewUrl(currentUrl());
3992
+ } else {
3993
+ return u.error("This browser doesn't support history." + method);
3813
3994
  }
3814
3995
  };
3815
3996
  buildState = function() {
@@ -3838,16 +4019,30 @@ We need to work on this page:
3838
4019
  }
3839
4020
  };
3840
4021
  pop = function(event) {
3841
- return up.log.group("History state popped to URL %s", currentUrl(), function() {
3842
- var state;
3843
- observeNewUrl(currentUrl());
3844
- up.layout.saveScroll({
3845
- url: previousUrl
3846
- });
3847
- state = event.originalEvent.state;
3848
- return restoreStateOnPop(state);
4022
+ var state, url;
4023
+ observeNewUrl(currentUrl());
4024
+ up.layout.saveScroll({
4025
+ url: previousUrl
4026
+ });
4027
+ state = event.originalEvent.state;
4028
+ restoreStateOnPop(state);
4029
+ url = currentUrl();
4030
+ return up.emit('up:history:restored', {
4031
+ url: url,
4032
+ message: "Restored location " + url
3849
4033
  });
3850
4034
  };
4035
+
4036
+ /**
4037
+ This event is [emitted](/up.emit) after a history entry has been restored.
4038
+
4039
+ History entries are restored when the user uses the *Back* or *Forward* button.
4040
+
4041
+ @event up:history:restored
4042
+ @param {String} event.url
4043
+ The URL for the history entry that has been restored.
4044
+ @experimental
4045
+ */
3851
4046
  if (up.browser.canPushState()) {
3852
4047
  register = function() {
3853
4048
  $(window).on("popstate", pop);
@@ -5002,7 +5197,7 @@ are based on this module.
5002
5197
  }
5003
5198
  up.motion.finish($old);
5004
5199
  if (pseudoClass) {
5005
- $wrapper = $new.contents().wrap('<span class="up-insertion"></span>').parent();
5200
+ $wrapper = $new.contents().wrapAll('<span class="up-insertion"></span>').parent();
5006
5201
  if (pseudoClass === 'before') {
5007
5202
  $old.prepend($wrapper);
5008
5203
  } else {
@@ -5572,6 +5767,7 @@ or [transitions](/up.transition) using Javascript or CSS.
5572
5767
  enabled: true
5573
5768
  });
5574
5769
  reset = function() {
5770
+ finish();
5575
5771
  animations = u.copy(defaultAnimations);
5576
5772
  transitions = u.copy(defaultTransitions);
5577
5773
  return config.reset();
@@ -6458,7 +6654,6 @@ the user performs the click.
6458
6654
  loadStarted();
6459
6655
  promise.always(loadEnded);
6460
6656
  }
6461
- console.groupEnd();
6462
6657
  return promise;
6463
6658
  };
6464
6659
 
@@ -7006,7 +7201,7 @@ Read on
7006
7201
  options.origin = u.option(options.origin, $link);
7007
7202
  options.confirm = u.option(options.confirm, $link.attr('up-confirm'));
7008
7203
  options = u.merge(options, up.motion.animateOptions(options, $link));
7009
- return up.browser.confirm(options).then(function() {
7204
+ return up.browser.whenConfirmed(options).then(function() {
7010
7205
  return up.replace(target, url, options);
7011
7206
  });
7012
7207
  };
@@ -8332,31 +8527,9 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8332
8527
 
8333
8528
  (function() {
8334
8529
  up.popup = (function($) {
8335
- var attach, autoclose, close, config, contains, coveredUrl, createFrame, currentUrl, discardHistory, isOpen, reset, setPosition, u;
8530
+ var align, attachAsap, attachNow, autoclose, chain, closeAsap, closeNow, config, contains, createFrame, isOpen, reset, state, u;
8336
8531
  u = up.util;
8337
8532
 
8338
- /**
8339
- Returns the source URL for the fragment displayed
8340
- in the current popup, or `undefined` if no popup is open.
8341
-
8342
- @function up.popup.url
8343
- @return {String}
8344
- the source URL
8345
- @stable
8346
- */
8347
- currentUrl = void 0;
8348
-
8349
- /**
8350
- Returns the URL of the page or modal behind the popup.
8351
-
8352
- @function up.popup.coveredUrl
8353
- @return {String}
8354
- @experimental
8355
- */
8356
- coveredUrl = function() {
8357
- return $('.up-popup').attr('up-covered-url');
8358
- };
8359
-
8360
8533
  /**
8361
8534
  Sets default options for future popups.
8362
8535
 
@@ -8387,80 +8560,90 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8387
8560
  config = u.config({
8388
8561
  openAnimation: 'fade-in',
8389
8562
  closeAnimation: 'fade-out',
8390
- openDuration: null,
8391
- closeDuration: null,
8563
+ openDuration: 150,
8564
+ closeDuration: 100,
8392
8565
  openEasing: null,
8393
8566
  closeEasing: null,
8394
8567
  position: 'bottom-right',
8395
8568
  history: false
8396
8569
  });
8570
+
8571
+ /**
8572
+ Returns the source URL for the fragment displayed
8573
+ in the current popup, or `undefined` if no popup is open.
8574
+
8575
+ @function up.popup.url
8576
+ @return {String}
8577
+ the source URL
8578
+ @stable
8579
+ */
8580
+
8581
+ /**
8582
+ Returns the URL of the page or modal behind the popup.
8583
+
8584
+ @function up.popup.coveredUrl
8585
+ @return {String}
8586
+ @experimental
8587
+ */
8588
+ state = u.config({
8589
+ phase: 'closed',
8590
+ $anchor: null,
8591
+ $popup: null,
8592
+ position: null,
8593
+ sticky: null,
8594
+ url: null,
8595
+ coveredUrl: null,
8596
+ coveredTitle: null
8597
+ });
8598
+ chain = new u.DivertibleChain();
8397
8599
  reset = function() {
8398
- close({
8399
- animation: false
8400
- });
8600
+ var ref;
8601
+ if ((ref = state.$popup) != null) {
8602
+ ref.remove();
8603
+ }
8604
+ state.reset();
8605
+ chain.reset();
8401
8606
  return config.reset();
8402
8607
  };
8403
- setPosition = function($link, position) {
8404
- var $popup, css, linkBox, popupBox;
8608
+ align = function() {
8609
+ var css, linkBox, popupBox;
8405
8610
  css = {};
8406
- $popup = $('.up-popup');
8407
- popupBox = u.measure($popup);
8408
- if (u.isFixed($link)) {
8409
- linkBox = $link.get(0).getBoundingClientRect();
8611
+ popupBox = u.measure(state.$popup);
8612
+ if (u.isFixed(state.$anchor)) {
8613
+ linkBox = state.$anchor.get(0).getBoundingClientRect();
8410
8614
  css['position'] = 'fixed';
8411
8615
  } else {
8412
- linkBox = u.measure($link);
8616
+ linkBox = u.measure(state.$anchor);
8413
8617
  }
8414
- switch (position) {
8415
- case "bottom-right":
8618
+ switch (state.position) {
8619
+ case 'bottom-right':
8416
8620
  css['top'] = linkBox.top + linkBox.height;
8417
8621
  css['left'] = linkBox.left + linkBox.width - popupBox.width;
8418
8622
  break;
8419
- case "bottom-left":
8623
+ case 'bottom-left':
8420
8624
  css['top'] = linkBox.top + linkBox.height;
8421
8625
  css['left'] = linkBox.left;
8422
8626
  break;
8423
- case "top-right":
8627
+ case 'top-right':
8424
8628
  css['top'] = linkBox.top - popupBox.height;
8425
8629
  css['left'] = linkBox.left + linkBox.width - popupBox.width;
8426
8630
  break;
8427
- case "top-left":
8631
+ case 'top-left':
8428
8632
  css['top'] = linkBox.top - popupBox.height;
8429
8633
  css['left'] = linkBox.left;
8430
8634
  break;
8431
8635
  default:
8432
- u.error("Unknown position option '%s'", position);
8636
+ u.error("Unknown position option '%s'", state.position);
8433
8637
  }
8434
- $popup.attr('up-position', position);
8435
- return $popup.css(css);
8638
+ state.$popup.attr('up-position', state.position);
8639
+ return state.$popup.css(css);
8436
8640
  };
8437
- discardHistory = function() {
8641
+ createFrame = function(target) {
8438
8642
  var $popup;
8439
- $popup = $('.up-popup');
8440
- $popup.removeAttr('up-covered-url');
8441
- return $popup.removeAttr('up-covered-title');
8442
- };
8443
- createFrame = function(target, options) {
8444
- var promise;
8445
- promise = u.resolvedPromise();
8446
- if (isOpen()) {
8447
- promise = promise.then(function() {
8448
- return close();
8449
- });
8450
- }
8451
- promise = promise.then(function() {
8452
- var $popup;
8453
- $popup = u.$createElementFromSelector('.up-popup');
8454
- if (options.sticky) {
8455
- $popup.attr('up-sticky', '');
8456
- }
8457
- $popup.attr('up-covered-url', up.browser.url());
8458
- $popup.attr('up-covered-title', document.title);
8459
- u.$createPlaceholder(target, $popup);
8460
- $popup.appendTo(document.body);
8461
- return $popup;
8462
- });
8463
- return promise;
8643
+ $popup = u.$createElementFromSelector('.up-popup');
8644
+ u.$createPlaceholder(target, $popup);
8645
+ $popup.appendTo(document.body);
8646
+ return state.$popup = $popup;
8464
8647
  };
8465
8648
 
8466
8649
  /**
@@ -8470,7 +8653,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8470
8653
  @stable
8471
8654
  */
8472
8655
  isOpen = function() {
8473
- return $('.up-popup').length > 0;
8656
+ return state.phase === 'opened' || state.phase === 'opening';
8474
8657
  };
8475
8658
 
8476
8659
  /**
@@ -8509,32 +8692,50 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8509
8692
  the opening animation has completed.
8510
8693
  @stable
8511
8694
  */
8512
- attach = function(linkOrSelector, options) {
8513
- var $link, animateOptions, html, target, url;
8514
- $link = $(linkOrSelector);
8515
- $link.length || u.error('Cannot attach popup to non-existing element %o', linkOrSelector);
8695
+ attachAsap = function(elementOrSelector, options) {
8696
+ var curriedAttachNow;
8697
+ curriedAttachNow = function() {
8698
+ return attachNow(elementOrSelector, options);
8699
+ };
8700
+ if (isOpen()) {
8701
+ chain.asap(closeNow, curriedAttachNow);
8702
+ } else {
8703
+ chain.asap(curriedAttachNow);
8704
+ }
8705
+ return chain.promise();
8706
+ };
8707
+ attachNow = function(elementOrSelector, options) {
8708
+ var $anchor, animateOptions, html, position, target, url;
8709
+ $anchor = $(elementOrSelector);
8710
+ $anchor.length || u.error('Cannot attach popup to non-existing element %o', elementOrSelector);
8516
8711
  options = u.options(options);
8517
- url = u.option(u.pluckKey(options, 'url'), $link.attr('up-href'), $link.attr('href'));
8712
+ url = u.option(u.pluckKey(options, 'url'), $anchor.attr('up-href'), $anchor.attr('href'));
8518
8713
  html = u.option(u.pluckKey(options, 'html'));
8519
- target = u.option(u.pluckKey(options, 'target'), $link.attr('up-popup'), 'body');
8520
- options.position = u.option(options.position, $link.attr('up-position'), config.position);
8521
- options.animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation);
8522
- options.sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'), config.sticky);
8523
- options.history = up.browser.canPushState() ? u.option(options.history, u.castedAttr($link, 'up-history'), config.history) : false;
8524
- options.confirm = u.option(options.confirm, $link.attr('up-confirm'));
8525
- options.method = up.link.followMethod($link, options);
8526
- animateOptions = up.motion.animateOptions(options, $link, {
8714
+ target = u.option(u.pluckKey(options, 'target'), $anchor.attr('up-popup'), 'body');
8715
+ position = u.option(options.position, $anchor.attr('up-position'), config.position);
8716
+ options.animation = u.option(options.animation, $anchor.attr('up-animation'), config.openAnimation);
8717
+ options.sticky = u.option(options.sticky, u.castedAttr($anchor, 'up-sticky'), config.sticky);
8718
+ options.history = up.browser.canPushState() ? u.option(options.history, u.castedAttr($anchor, 'up-history'), config.history) : false;
8719
+ options.confirm = u.option(options.confirm, $anchor.attr('up-confirm'));
8720
+ options.method = up.link.followMethod($anchor, options);
8721
+ animateOptions = up.motion.animateOptions(options, $anchor, {
8527
8722
  duration: config.openDuration,
8528
8723
  easing: config.openEasing
8529
8724
  });
8530
- return up.browser.confirm(options).then(function() {
8531
- var extractOptions, promise;
8532
- if (up.bus.nobodyPrevents('up:popup:open', {
8725
+ return up.browser.whenConfirmed(options).then(function() {
8726
+ return up.bus.whenEmitted('up:popup:open', {
8533
8727
  url: url,
8534
8728
  message: 'Opening popup'
8535
- })) {
8729
+ }).then(function() {
8730
+ var extractOptions, promise;
8731
+ state.phase = 'opening';
8732
+ state.$anchor = $anchor;
8733
+ state.position = position;
8734
+ state.coveredUrl = up.browser.url();
8735
+ state.coveredTitle = document.title;
8736
+ state.sticky = options.sticky;
8536
8737
  options.beforeSwap = function() {
8537
- return createFrame(target, options);
8738
+ return createFrame(target);
8538
8739
  };
8539
8740
  extractOptions = u.merge(options, {
8540
8741
  animation: false
@@ -8545,20 +8746,17 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8545
8746
  promise = up.replace(target, url, extractOptions);
8546
8747
  }
8547
8748
  promise = promise.then(function() {
8548
- return setPosition($link, options.position);
8549
- });
8550
- promise = promise.then(function() {
8551
- return up.animate($('.up-popup'), options.animation, animateOptions);
8749
+ align();
8750
+ return up.animate(state.$popup, options.animation, animateOptions);
8552
8751
  });
8553
8752
  promise = promise.then(function() {
8753
+ state.phase = 'opened';
8554
8754
  return up.emit('up:popup:opened', {
8555
8755
  message: 'Popup opened'
8556
8756
  });
8557
8757
  });
8558
8758
  return promise;
8559
- } else {
8560
- return u.unresolvablePromise();
8561
- }
8759
+ });
8562
8760
  });
8563
8761
  };
8564
8762
 
@@ -8593,37 +8791,47 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8593
8791
  animation has finished.
8594
8792
  @stable
8595
8793
  */
8596
- close = function(options) {
8597
- var $popup, animateOptions, promise;
8598
- $popup = $('.up-popup');
8599
- if ($popup.length) {
8600
- if (up.bus.nobodyPrevents('up:popup:close', {
8601
- $element: $popup
8602
- })) {
8603
- options = u.options(options, {
8604
- animation: config.closeAnimation,
8605
- url: $popup.attr('up-covered-url'),
8606
- title: $popup.attr('up-covered-title')
8607
- });
8608
- animateOptions = up.motion.animateOptions(options, {
8609
- duration: config.closeDuration,
8610
- easing: config.closeEasing
8611
- });
8612
- u.extend(options, animateOptions);
8613
- currentUrl = void 0;
8614
- promise = up.destroy($popup, options);
8615
- promise = promise.then(function() {
8616
- return up.emit('up:popup:closed', {
8617
- message: 'Popup closed'
8618
- });
8619
- });
8620
- return promise;
8621
- } else {
8622
- return u.unresolvablePromise();
8623
- }
8624
- } else {
8794
+ closeAsap = function(options) {
8795
+ if (isOpen()) {
8796
+ chain.asap(function() {
8797
+ return closeNow(options);
8798
+ });
8799
+ }
8800
+ return chain.promise();
8801
+ };
8802
+ closeNow = function(options) {
8803
+ var animateOptions;
8804
+ if (!isOpen()) {
8625
8805
  return u.resolvedPromise();
8626
8806
  }
8807
+ options = u.options(options, {
8808
+ animation: config.closeAnimation,
8809
+ url: state.coveredUrl,
8810
+ title: state.coveredTitle
8811
+ });
8812
+ animateOptions = up.motion.animateOptions(options, {
8813
+ duration: config.closeDuration,
8814
+ easing: config.closeEasing
8815
+ });
8816
+ u.extend(options, animateOptions);
8817
+ return up.bus.whenEmitted('up:popup:close', {
8818
+ message: 'Closing popup',
8819
+ $element: state.$popup
8820
+ }).then(function() {
8821
+ state.phase = 'closing';
8822
+ state.url = null;
8823
+ state.coveredUrl = null;
8824
+ state.coveredTitle = null;
8825
+ return up.destroy(state.$popup, options).then(function() {
8826
+ state.phase = 'closed';
8827
+ state.$popup = null;
8828
+ state.$anchor = null;
8829
+ state.sticky = null;
8830
+ return up.emit('up:popup:closed', {
8831
+ message: 'Popup closed'
8832
+ });
8833
+ });
8834
+ });
8627
8835
  };
8628
8836
 
8629
8837
  /**
@@ -8644,9 +8852,8 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8644
8852
  @stable
8645
8853
  */
8646
8854
  autoclose = function() {
8647
- if (!$('.up-popup').is('[up-sticky]')) {
8648
- discardHistory();
8649
- return close();
8855
+ if (!state.sticky) {
8856
+ return closeAsap();
8650
8857
  }
8651
8858
  };
8652
8859
 
@@ -8698,31 +8905,29 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8698
8905
  */
8699
8906
  up.link.onAction('[up-popup]', function($link) {
8700
8907
  if ($link.is('.up-current')) {
8701
- return close();
8908
+ return closeAsap();
8702
8909
  } else {
8703
- return attach($link);
8910
+ return attachAsap($link);
8704
8911
  }
8705
8912
  });
8706
- up.on('click', 'body', function(event, $body) {
8913
+ up.on('mousedown', 'body', function(event, $body) {
8707
8914
  var $target;
8708
8915
  $target = $(event.target);
8709
- if (!($target.closest('.up-popup').length || $target.closest('[up-popup]').length)) {
8710
- return close();
8916
+ if (!$target.closest('.up-popup, [up-popup]').length) {
8917
+ return closeAsap();
8711
8918
  }
8712
8919
  });
8713
8920
  up.on('up:fragment:inserted', function(event, $fragment) {
8714
8921
  var newSource;
8715
8922
  if (contains($fragment)) {
8716
8923
  if (newSource = $fragment.attr('up-source')) {
8717
- return currentUrl = newSource;
8924
+ return state.url = newSource;
8718
8925
  }
8719
8926
  } else if (contains(event.origin)) {
8720
8927
  return autoclose();
8721
8928
  }
8722
8929
  });
8723
- up.bus.onEscape(function() {
8724
- return close();
8725
- });
8930
+ up.bus.onEscape(closeAsap);
8726
8931
 
8727
8932
  /**
8728
8933
  When an element with this attribute is clicked,
@@ -8739,31 +8944,24 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8739
8944
  @stable
8740
8945
  */
8741
8946
  up.on('click', '[up-close]', function(event, $element) {
8742
- if ($element.closest('.up-popup').length) {
8743
- close();
8947
+ if (contains($element)) {
8948
+ closeAsap();
8744
8949
  return event.preventDefault();
8745
8950
  }
8746
8951
  });
8747
8952
  up.on('up:framework:reset', reset);
8748
8953
  return {
8749
8954
  knife: eval(typeof Knife !== "undefined" && Knife !== null ? Knife.point : void 0),
8750
- attach: attach,
8751
- close: close,
8955
+ attach: attachAsap,
8956
+ close: closeAsap,
8752
8957
  url: function() {
8753
- return currentUrl;
8958
+ return state.url;
8754
8959
  },
8755
- coveredUrl: coveredUrl,
8756
- config: config,
8757
- defaults: function() {
8758
- return u.error('up.popup.defaults(...) no longer exists. Set values on he up.popup.config property instead.');
8960
+ coveredUrl: function() {
8961
+ return state.coveredUrl;
8759
8962
  },
8963
+ config: config,
8760
8964
  contains: contains,
8761
- open: function() {
8762
- return up.error('up.popup.open no longer exists. Please use up.popup.attach instead.');
8763
- },
8764
- source: function() {
8765
- return up.error('up.popup.source no longer exists. Please use up.popup.url instead.');
8766
- },
8767
8965
  isOpen: isOpen
8768
8966
  };
8769
8967
  })(jQuery);
@@ -8833,7 +9031,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8833
9031
 
8834
9032
  (function() {
8835
9033
  up.modal = (function($) {
8836
- var animate, autoclose, close, config, contains, coveredUrl, createFrame, currentFlavor, currentUrl, discardHistory, extract, flavor, flavorDefault, flavorOverrides, follow, isOpen, markAsAnimating, open, reset, shiftElements, templateHtml, u, unshiftElements, unshifters, visit;
9034
+ var animate, autoclose, chain, closeAsap, closeNow, config, contains, createFrame, extractAsap, flavor, flavorDefault, flavorOverrides, followAsap, isOpen, markAsAnimating, openAsap, openNow, reset, shiftElements, state, templateHtml, u, unshiftElements, visitAsap;
8837
9035
  u = up.util;
8838
9036
 
8839
9037
  /**
@@ -8875,9 +9073,9 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8875
9073
  The animation used to open the backdrop that dims the page below the dialog.
8876
9074
  @param {String} [config.backdropCloseAnimation='fade-out']
8877
9075
  The animation used to close the backdrop that dims the page below the dialog.
8878
- @param {String} [config.openDuration]
9076
+ @param {Number} [config.openDuration]
8879
9077
  The duration of the open animation (in milliseconds).
8880
- @param {String} [config.closeDuration]
9078
+ @param {Number} [config.closeDuration]
8881
9079
  The duration of the close animation (in milliseconds).
8882
9080
  @param {String} [config.openEasing]
8883
9081
  The timing function controlling the acceleration of the opening animation.
@@ -8920,8 +9118,6 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8920
9118
  the source URL
8921
9119
  @stable
8922
9120
  */
8923
- currentUrl = void 0;
8924
- currentFlavor = void 0;
8925
9121
 
8926
9122
  /**
8927
9123
  Returns the URL of the page behind the modal overlay.
@@ -8930,15 +9126,28 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8930
9126
  @return {String}
8931
9127
  @experimental
8932
9128
  */
8933
- coveredUrl = function() {
8934
- return $('.up-modal').attr('up-covered-url');
8935
- };
9129
+ state = u.config(function() {
9130
+ return {
9131
+ phase: 'closed',
9132
+ $anchor: null,
9133
+ $modal: null,
9134
+ sticky: null,
9135
+ flavor: null,
9136
+ url: null,
9137
+ coveredUrl: null,
9138
+ coveredTitle: null,
9139
+ unshifters: []
9140
+ };
9141
+ });
9142
+ chain = new u.DivertibleChain();
8936
9143
  reset = function() {
8937
- close({
8938
- animation: false
8939
- });
8940
- currentUrl = void 0;
8941
- currentFlavor = void 0;
9144
+ var ref;
9145
+ if ((ref = state.$modal) != null) {
9146
+ ref.remove();
9147
+ }
9148
+ unshiftElements();
9149
+ state.reset();
9150
+ chain.reset();
8942
9151
  return config.reset();
8943
9152
  };
8944
9153
  templateHtml = function() {
@@ -8950,52 +9159,27 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8950
9159
  return template;
8951
9160
  }
8952
9161
  };
8953
- discardHistory = function() {
8954
- var $modal;
8955
- $modal = $('.up-modal');
8956
- $modal.removeAttr('up-covered-url');
8957
- return $modal.removeAttr('up-covered-title');
8958
- };
8959
9162
  createFrame = function(target, options) {
8960
- var promise;
8961
- promise = u.resolvedPromise();
8962
- if (isOpen()) {
8963
- promise = promise.then(function() {
8964
- return close();
8965
- });
9163
+ var $content, $dialog, $modal;
9164
+ $modal = $(templateHtml());
9165
+ $modal.attr('up-flavor', state.flavor);
9166
+ $dialog = $modal.find('.up-modal-dialog');
9167
+ if (u.isPresent(options.width)) {
9168
+ $dialog.css('width', options.width);
8966
9169
  }
8967
- promise = promise.then(function() {
8968
- var $content, $dialog, $modal;
8969
- currentFlavor = options.flavor;
8970
- $modal = $(templateHtml());
8971
- $modal.attr('up-flavor', currentFlavor);
8972
- if (options.sticky) {
8973
- $modal.attr('up-sticky', '');
8974
- }
8975
- $modal.attr('up-covered-url', up.browser.url());
8976
- $modal.attr('up-covered-title', document.title);
8977
- $dialog = $modal.find('.up-modal-dialog');
8978
- if (u.isPresent(options.width)) {
8979
- $dialog.css('width', options.width);
8980
- }
8981
- if (u.isPresent(options.maxWidth)) {
8982
- $dialog.css('max-width', options.maxWidth);
8983
- }
8984
- if (u.isPresent(options.height)) {
8985
- $dialog.css('height', options.height);
8986
- }
8987
- $content = $modal.find('.up-modal-content');
8988
- u.$createPlaceholder(target, $content);
8989
- return $modal.appendTo(document.body);
8990
- });
8991
- return promise;
9170
+ if (u.isPresent(options.maxWidth)) {
9171
+ $dialog.css('max-width', options.maxWidth);
9172
+ }
9173
+ if (u.isPresent(options.height)) {
9174
+ $dialog.css('height', options.height);
9175
+ }
9176
+ $content = $modal.find('.up-modal-content');
9177
+ u.$createPlaceholder(target, $content);
9178
+ $modal.appendTo(document.body);
9179
+ return state.$modal = $modal;
8992
9180
  };
8993
- unshifters = [];
8994
9181
  shiftElements = function() {
8995
9182
  var $body, bodyRightPadding, bodyRightShift, scrollbarWidth, unshiftBody;
8996
- if (unshifters.length > 0) {
8997
- return;
8998
- }
8999
9183
  if (u.documentHasVerticalScrollbar()) {
9000
9184
  $body = $('body');
9001
9185
  scrollbarWidth = u.scrollbarWidth();
@@ -9005,7 +9189,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9005
9189
  'padding-right': bodyRightShift + "px",
9006
9190
  'overflow-y': 'hidden'
9007
9191
  });
9008
- unshifters.push(unshiftBody);
9192
+ state.unshifters.push(unshiftBody);
9009
9193
  return up.layout.anchoredRight().each(function() {
9010
9194
  var $element, elementRight, elementRightShift, unshifter;
9011
9195
  $element = $(this);
@@ -9014,14 +9198,14 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9014
9198
  unshifter = u.temporaryCss($element, {
9015
9199
  'right': elementRightShift
9016
9200
  });
9017
- return unshifters.push(unshifter);
9201
+ return state.unshifters.push(unshifter);
9018
9202
  });
9019
9203
  }
9020
9204
  };
9021
9205
  unshiftElements = function() {
9022
9206
  var results, unshifter;
9023
9207
  results = [];
9024
- while (unshifter = unshifters.pop()) {
9208
+ while (unshifter = state.unshifters.pop()) {
9025
9209
  results.push(unshifter());
9026
9210
  }
9027
9211
  return results;
@@ -9036,7 +9220,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9036
9220
  @stable
9037
9221
  */
9038
9222
  isOpen = function() {
9039
- return $('.up-modal').length > 0;
9223
+ return state.phase === 'opened' || state.phase === 'opening';
9040
9224
  };
9041
9225
 
9042
9226
  /**
@@ -9083,10 +9267,10 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9083
9267
  the opening animation has completed.
9084
9268
  @stable
9085
9269
  */
9086
- follow = function(linkOrSelector, options) {
9270
+ followAsap = function(linkOrSelector, options) {
9087
9271
  options = u.options(options);
9088
9272
  options.$link = $(linkOrSelector);
9089
- return open(options);
9273
+ return openAsap(options);
9090
9274
  };
9091
9275
 
9092
9276
  /**
@@ -9114,10 +9298,10 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9114
9298
  animation has completed.
9115
9299
  @stable
9116
9300
  */
9117
- visit = function(url, options) {
9301
+ visitAsap = function(url, options) {
9118
9302
  options = u.options(options);
9119
9303
  options.url = url;
9120
- return open(options);
9304
+ return openAsap(options);
9121
9305
  };
9122
9306
 
9123
9307
  /**
@@ -9147,19 +9331,26 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9147
9331
  animation has completed.
9148
9332
  @stable
9149
9333
  */
9150
- extract = function(selector, html, options) {
9334
+ extractAsap = function(selector, html, options) {
9151
9335
  options = u.options(options);
9152
9336
  options.html = html;
9153
9337
  options.history = u.option(options.history, false);
9154
9338
  options.target = selector;
9155
- return open(options);
9339
+ return openAsap(options);
9156
9340
  };
9157
-
9158
- /**
9159
- @function open
9160
- @internal
9161
- */
9162
- open = function(options) {
9341
+ openAsap = function(options) {
9342
+ var curriedOpenNow;
9343
+ curriedOpenNow = function() {
9344
+ return openNow(options);
9345
+ };
9346
+ if (isOpen()) {
9347
+ chain.asap(closeNow, curriedOpenNow);
9348
+ } else {
9349
+ chain.asap(curriedOpenNow);
9350
+ }
9351
+ return chain.promise();
9352
+ };
9353
+ openNow = function(options) {
9163
9354
  var $link, animateOptions, html, target, url;
9164
9355
  options = u.options(options);
9165
9356
  $link = u.option(u.pluckKey(options, '$link'), u.nullJQuery());
@@ -9183,12 +9374,17 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9183
9374
  if (!up.browser.canPushState()) {
9184
9375
  options.history = false;
9185
9376
  }
9186
- return up.browser.confirm(options).then(function() {
9187
- var extractOptions, promise;
9188
- if (up.bus.nobodyPrevents('up:modal:open', {
9377
+ return up.browser.whenConfirmed(options).then(function() {
9378
+ return up.bus.whenEmitted('up:modal:open', {
9189
9379
  url: url,
9190
9380
  message: 'Opening modal'
9191
- })) {
9381
+ }).then(function() {
9382
+ var extractOptions, promise;
9383
+ state.phase = 'opening';
9384
+ state.flavor = options.flavor;
9385
+ state.sticky = options.sticky;
9386
+ state.coveredUrl = up.browser.url();
9387
+ state.coveredTitle = document.title;
9192
9388
  options.beforeSwap = function() {
9193
9389
  return createFrame(target, options);
9194
9390
  };
@@ -9201,20 +9397,17 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9201
9397
  promise = up.replace(target, url, extractOptions);
9202
9398
  }
9203
9399
  promise = promise.then(function() {
9204
- return shiftElements();
9205
- });
9206
- promise = promise.then(function() {
9400
+ shiftElements();
9207
9401
  return animate(options.animation, options.backdropAnimation, animateOptions);
9208
9402
  });
9209
9403
  promise = promise.then(function() {
9404
+ state.phase = 'opened';
9210
9405
  return up.emit('up:modal:opened', {
9211
9406
  message: 'Modal opened'
9212
9407
  });
9213
9408
  });
9214
9409
  return promise;
9215
- } else {
9216
- return u.unresolvablePromise();
9217
- }
9410
+ });
9218
9411
  });
9219
9412
  };
9220
9413
 
@@ -9249,54 +9442,63 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9249
9442
  animation has finished.
9250
9443
  @stable
9251
9444
  */
9252
- close = function(options) {
9253
- var $modal, animateOptions, backdropCloseAnimation, promise, viewportCloseAnimation;
9254
- options = u.options(options);
9255
- $modal = $('.up-modal');
9256
- if ($modal.length) {
9257
- if (up.bus.nobodyPrevents('up:modal:close', {
9258
- $element: $modal,
9259
- message: 'Closing modal'
9260
- })) {
9261
- viewportCloseAnimation = u.option(options.animation, flavorDefault('closeAnimation'));
9262
- backdropCloseAnimation = u.option(options.backdropAnimation, flavorDefault('backdropCloseAnimation'));
9263
- animateOptions = up.motion.animateOptions(options, {
9264
- duration: flavorDefault('closeDuration'),
9265
- easing: flavorDefault('closeEasing')
9266
- });
9267
- promise = u.resolvedPromise();
9268
- promise = promise.then(function() {
9269
- return animate(viewportCloseAnimation, backdropCloseAnimation, animateOptions);
9270
- });
9271
- promise = promise.then(function() {
9272
- var destroyOptions;
9273
- destroyOptions = u.options(u.except(options, 'animation', 'duration', 'easing', 'delay'), {
9274
- url: $modal.attr('up-covered-url'),
9275
- title: $modal.attr('up-covered-title')
9276
- });
9277
- currentUrl = void 0;
9278
- return up.destroy($modal, destroyOptions);
9279
- });
9280
- promise = promise.then(function() {
9281
- unshiftElements();
9282
- currentFlavor = void 0;
9283
- return up.emit('up:modal:closed', {
9284
- message: 'Modal closed'
9285
- });
9286
- });
9287
- return promise;
9288
- } else {
9289
- return u.unresolvablePromise();
9290
- }
9291
- } else {
9445
+ closeAsap = function(options) {
9446
+ if (isOpen()) {
9447
+ chain.asap(function() {
9448
+ return closeNow(options);
9449
+ });
9450
+ }
9451
+ return chain.promise();
9452
+ };
9453
+ closeNow = function(options) {
9454
+ var animateOptions, backdropCloseAnimation, destroyOptions, viewportCloseAnimation;
9455
+ if (!isOpen()) {
9292
9456
  return u.resolvedPromise();
9293
9457
  }
9458
+ options = u.options(options);
9459
+ viewportCloseAnimation = u.option(options.animation, flavorDefault('closeAnimation'));
9460
+ backdropCloseAnimation = u.option(options.backdropAnimation, flavorDefault('backdropCloseAnimation'));
9461
+ animateOptions = up.motion.animateOptions(options, {
9462
+ duration: flavorDefault('closeDuration'),
9463
+ easing: flavorDefault('closeEasing')
9464
+ });
9465
+ destroyOptions = u.options(u.except(options, 'animation', 'duration', 'easing', 'delay'), {
9466
+ url: state.coveredUrl,
9467
+ title: state.coveredTitle
9468
+ });
9469
+ return up.bus.whenEmitted('up:modal:close', {
9470
+ $element: state.$modal,
9471
+ message: 'Closing modal'
9472
+ }).then(function() {
9473
+ var promise;
9474
+ state.phase = 'closing';
9475
+ state.url = null;
9476
+ state.coveredUrl = null;
9477
+ state.coveredTitle = null;
9478
+ promise = animate(viewportCloseAnimation, backdropCloseAnimation, animateOptions);
9479
+ promise = promise.then(function() {
9480
+ state.url = null;
9481
+ console.debug("destroying!");
9482
+ return up.destroy(state.$modal, destroyOptions);
9483
+ });
9484
+ promise = promise.then(function() {
9485
+ unshiftElements();
9486
+ state.phase = 'closed';
9487
+ state.$modal = null;
9488
+ state.flavor = null;
9489
+ state.sticky = null;
9490
+ return up.emit('up:modal:closed', {
9491
+ message: 'Modal closed'
9492
+ });
9493
+ });
9494
+ return promise;
9495
+ });
9294
9496
  };
9295
- markAsAnimating = function(state) {
9296
- if (state == null) {
9297
- state = true;
9497
+ markAsAnimating = function(isAnimating) {
9498
+ if (isAnimating == null) {
9499
+ isAnimating = true;
9298
9500
  }
9299
- return $('.up-modal').toggleClass('up-modal-animating', state);
9501
+ return state.$modal.toggleClass('up-modal-animating', isAnimating);
9300
9502
  };
9301
9503
  animate = function(viewportAnimation, backdropAnimation, animateOptions) {
9302
9504
  var promise;
@@ -9304,7 +9506,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9304
9506
  return u.resolvedPromise();
9305
9507
  } else {
9306
9508
  markAsAnimating();
9307
- promise = $.when(up.animate($('.up-modal-viewport'), viewportAnimation, animateOptions), up.animate($('.up-modal-backdrop'), backdropAnimation, animateOptions));
9509
+ promise = $.when(up.animate(state.$modal.find('.up-modal-viewport'), viewportAnimation, animateOptions), up.animate(state.$modal.find('.up-modal-backdrop'), backdropAnimation, animateOptions));
9308
9510
  promise = promise.then(function() {
9309
9511
  return markAsAnimating(false);
9310
9512
  });
@@ -9330,9 +9532,8 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9330
9532
  @stable
9331
9533
  */
9332
9534
  autoclose = function() {
9333
- if (!$('.up-modal').is('[up-sticky]')) {
9334
- discardHistory();
9335
- return close();
9535
+ if (!state.sticky) {
9536
+ return closeAsap();
9336
9537
  }
9337
9538
  };
9338
9539
 
@@ -9414,7 +9615,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9414
9615
  flavorDefault = function(key, flavorName) {
9415
9616
  var value;
9416
9617
  if (flavorName == null) {
9417
- flavorName = currentFlavor;
9618
+ flavorName = state.flavor;
9418
9619
  }
9419
9620
  if (flavorName) {
9420
9621
  value = flavorOverrides(flavorName)[key];
@@ -9466,28 +9667,26 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9466
9667
  @stable
9467
9668
  */
9468
9669
  up.link.onAction('[up-modal]', function($link) {
9469
- return follow($link);
9670
+ return followAsap($link);
9470
9671
  });
9471
9672
  up.on('click', 'body', function(event, $body) {
9472
9673
  var $target;
9473
9674
  $target = $(event.target);
9474
9675
  if (!($target.closest('.up-modal-dialog').length || $target.closest('[up-modal]').length)) {
9475
- return close();
9676
+ return closeAsap();
9476
9677
  }
9477
9678
  });
9478
9679
  up.on('up:fragment:inserted', function(event, $fragment) {
9479
9680
  var newSource;
9480
9681
  if (contains($fragment)) {
9481
9682
  if (newSource = $fragment.attr('up-source')) {
9482
- return currentUrl = newSource;
9683
+ return state.url = newSource;
9483
9684
  }
9484
- } else if (!up.popup.contains($fragment) && contains(event.origin)) {
9685
+ } else if (contains(event.origin) && !up.popup.contains($fragment)) {
9485
9686
  return autoclose();
9486
9687
  }
9487
9688
  });
9488
- up.bus.onEscape(function() {
9489
- return close();
9490
- });
9689
+ up.bus.onEscape(closeAsap);
9491
9690
 
9492
9691
  /**
9493
9692
  When this element is clicked, closes a currently open dialog.
@@ -9503,33 +9702,26 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9503
9702
  @stable
9504
9703
  */
9505
9704
  up.on('click', '[up-close]', function(event, $element) {
9506
- if ($element.closest('.up-modal').length) {
9507
- close();
9705
+ if (contains($element)) {
9706
+ closeAsap();
9508
9707
  return event.preventDefault();
9509
9708
  }
9510
9709
  });
9511
9710
  up.on('up:framework:reset', reset);
9512
9711
  return {
9513
9712
  knife: eval(typeof Knife !== "undefined" && Knife !== null ? Knife.point : void 0),
9514
- visit: visit,
9515
- follow: follow,
9516
- extract: extract,
9517
- open: function() {
9518
- return up.error('up.modal.open no longer exists. Please use either up.modal.follow or up.modal.visit.');
9519
- },
9520
- close: close,
9713
+ visit: visitAsap,
9714
+ follow: followAsap,
9715
+ extract: extractAsap,
9716
+ close: closeAsap,
9521
9717
  url: function() {
9522
- return currentUrl;
9718
+ return state.url;
9523
9719
  },
9524
- coveredUrl: coveredUrl,
9525
- config: config,
9526
- defaults: function() {
9527
- return u.error('up.modal.defaults(...) no longer exists. Set values on he up.modal.config property instead.');
9720
+ coveredUrl: function() {
9721
+ return state.coveredUrl;
9528
9722
  },
9723
+ config: config,
9529
9724
  contains: contains,
9530
- source: function() {
9531
- return up.error('up.modal.source no longer exists. Please use up.popup.url instead.');
9532
- },
9533
9725
  isOpen: isOpen,
9534
9726
  flavor: flavor
9535
9727
  };
@@ -9571,7 +9763,7 @@ The tooltip element is appended to the end of `<body>`.
9571
9763
 
9572
9764
  (function() {
9573
9765
  up.tooltip = (function($) {
9574
- var attach, close, config, createElement, reset, setPosition, u;
9766
+ var align, attachAsap, attachNow, chain, closeAsap, closeNow, config, createElement, isOpen, reset, state, u;
9575
9767
  u = up.util;
9576
9768
 
9577
9769
  /**
@@ -9585,51 +9777,73 @@ The tooltip element is appended to the end of `<body>`.
9585
9777
  The animation used to open a tooltip.
9586
9778
  @param {String} [config.closeAnimation='fade-out']
9587
9779
  The animation used to close a tooltip.
9780
+ @param {Number} [config.openDuration]
9781
+ The duration of the open animation (in milliseconds).
9782
+ @param {Number} [config.closeDuration]
9783
+ The duration of the close animation (in milliseconds).
9784
+ @param {String} [config.openEasing]
9785
+ The timing function controlling the acceleration of the opening animation.
9786
+ @param {String} [config.closeEasing]
9787
+ The timing function controlling the acceleration of the closing animation.
9588
9788
  @stable
9589
9789
  */
9590
9790
  config = u.config({
9591
9791
  position: 'top',
9592
9792
  openAnimation: 'fade-in',
9593
- closeAnimation: 'fade-out'
9793
+ closeAnimation: 'fade-out',
9794
+ openDuration: 100,
9795
+ closeDuration: 50,
9796
+ openEasing: null,
9797
+ closeEasing: null
9594
9798
  });
9799
+ state = u.config({
9800
+ phase: 'closed',
9801
+ $anchor: null,
9802
+ $tooltip: null,
9803
+ position: null
9804
+ });
9805
+ chain = new u.DivertibleChain();
9595
9806
  reset = function() {
9596
- close({
9597
- animation: false
9598
- });
9807
+ var ref;
9808
+ if ((ref = state.$tooltip) != null) {
9809
+ ref.remove();
9810
+ }
9811
+ state.reset();
9812
+ chain.reset();
9599
9813
  return config.reset();
9600
9814
  };
9601
- setPosition = function($link, $tooltip, position) {
9815
+ align = function() {
9602
9816
  var css, linkBox, tooltipBox;
9603
9817
  css = {};
9604
- tooltipBox = u.measure($tooltip);
9605
- if (u.isFixed($link)) {
9606
- linkBox = $link.get(0).getBoundingClientRect();
9818
+ tooltipBox = u.measure(state.$tooltip);
9819
+ if (u.isFixed(state.$anchor)) {
9820
+ linkBox = state.$anchor.get(0).getBoundingClientRect();
9607
9821
  css['position'] = 'fixed';
9608
9822
  } else {
9609
- linkBox = u.measure($link);
9823
+ linkBox = u.measure(state.$anchor);
9610
9824
  }
9611
- switch (position) {
9612
- case "top":
9825
+ switch (state.position) {
9826
+ case 'top':
9613
9827
  css['top'] = linkBox.top - tooltipBox.height;
9614
9828
  css['left'] = linkBox.left + 0.5 * (linkBox.width - tooltipBox.width);
9615
9829
  break;
9616
- case "left":
9830
+ case 'left':
9617
9831
  css['top'] = linkBox.top + 0.5 * (linkBox.height - tooltipBox.height);
9618
9832
  css['left'] = linkBox.left - tooltipBox.width;
9619
9833
  break;
9620
- case "right":
9834
+ case 'right':
9621
9835
  css['top'] = linkBox.top + 0.5 * (linkBox.height - tooltipBox.height);
9622
9836
  css['left'] = linkBox.left + linkBox.width;
9623
9837
  break;
9624
- case "bottom":
9838
+ case 'bottom':
9625
9839
  css['top'] = linkBox.top + linkBox.height;
9626
9840
  css['left'] = linkBox.left + 0.5 * (linkBox.width - tooltipBox.width);
9627
9841
  break;
9628
9842
  default:
9629
- u.error("Unknown position option '%s'", position);
9843
+ u.error("Unknown position option '%s'", state.position);
9630
9844
  }
9631
- $tooltip.attr('up-position', position);
9632
- return $tooltip.css(css);
9845
+ state.$tooltip.attr('up-position', state.position);
9846
+ return state.$tooltip.css(css);
9633
9847
  };
9634
9848
  createElement = function(options) {
9635
9849
  var $element;
@@ -9640,7 +9854,7 @@ The tooltip element is appended to the end of `<body>`.
9640
9854
  $element.html(options.html);
9641
9855
  }
9642
9856
  $element.appendTo(document.body);
9643
- return $element;
9857
+ return state.$tooltip = $element;
9644
9858
  };
9645
9859
 
9646
9860
  /**
@@ -9663,24 +9877,44 @@ The tooltip element is appended to the end of `<body>`.
9663
9877
  A promise that will be resolved when the tooltip's opening animation has finished.
9664
9878
  @stable
9665
9879
  */
9666
- attach = function(linkOrSelector, options) {
9667
- var $link, $tooltip, animateOptions, animation, html, position, text;
9880
+ attachAsap = function(elementOrSelector, options) {
9881
+ var curriedAttachNow;
9668
9882
  if (options == null) {
9669
9883
  options = {};
9670
9884
  }
9671
- $link = $(linkOrSelector);
9672
- html = u.option(options.html, $link.attr('up-tooltip-html'));
9673
- text = u.option(options.text, $link.attr('up-tooltip'));
9674
- position = u.option(options.position, $link.attr('up-position'), config.position);
9675
- animation = u.option(options.animation, u.castedAttr($link, 'up-animation'), config.openAnimation);
9676
- animateOptions = up.motion.animateOptions(options, $link);
9677
- close();
9678
- $tooltip = createElement({
9885
+ curriedAttachNow = function() {
9886
+ return attachNow(elementOrSelector, options);
9887
+ };
9888
+ if (isOpen()) {
9889
+ chain.asap(closeNow, curriedAttachNow);
9890
+ } else {
9891
+ chain.asap(curriedAttachNow);
9892
+ }
9893
+ return chain.promise();
9894
+ };
9895
+ attachNow = function(elementOrSelector, options) {
9896
+ var $anchor, animateOptions, animation, html, position, text;
9897
+ $anchor = $(elementOrSelector);
9898
+ options = u.options(options);
9899
+ html = u.option(options.html, $anchor.attr('up-tooltip-html'));
9900
+ text = u.option(options.text, $anchor.attr('up-tooltip'));
9901
+ position = u.option(options.position, $anchor.attr('up-position'), config.position);
9902
+ animation = u.option(options.animation, u.castedAttr($anchor, 'up-animation'), config.openAnimation);
9903
+ animateOptions = up.motion.animateOptions(options, $anchor, {
9904
+ duration: config.openDuration,
9905
+ easing: config.openEasing
9906
+ });
9907
+ state.phase = 'opening';
9908
+ state.$anchor = $anchor;
9909
+ createElement({
9679
9910
  text: text,
9680
9911
  html: html
9681
9912
  });
9682
- setPosition($link, $tooltip, position);
9683
- return up.animate($tooltip, animation, animateOptions);
9913
+ state.position = position;
9914
+ align();
9915
+ return up.animate(state.$tooltip, animation, animateOptions).then(function() {
9916
+ return state.phase = 'opened';
9917
+ });
9684
9918
  };
9685
9919
 
9686
9920
  /**
@@ -9690,18 +9924,47 @@ The tooltip element is appended to the end of `<body>`.
9690
9924
  @function up.tooltip.close
9691
9925
  @param {Object} options
9692
9926
  See options for [`up.animate`](/up.animate).
9927
+ @return {Promise}
9928
+ A promise for the end of the closing animation.
9693
9929
  @stable
9694
9930
  */
9695
- close = function(options) {
9696
- var $tooltip;
9697
- $tooltip = $('.up-tooltip');
9698
- if ($tooltip.length) {
9699
- options = u.options(options, {
9700
- animation: config.closeAnimation
9931
+ closeAsap = function(options) {
9932
+ if (isOpen()) {
9933
+ chain.asap(function() {
9934
+ return closeNow(options);
9701
9935
  });
9702
- options = u.merge(options, up.motion.animateOptions(options));
9703
- return up.destroy($tooltip, options);
9704
9936
  }
9937
+ return chain.promise();
9938
+ };
9939
+ closeNow = function(options) {
9940
+ var animateOptions;
9941
+ if (!isOpen()) {
9942
+ return u.resolvedPromise();
9943
+ }
9944
+ options = u.options(options, {
9945
+ animation: config.closeAnimation
9946
+ });
9947
+ animateOptions = up.motion.animateOptions(options, {
9948
+ duration: config.closeDuration,
9949
+ easing: config.closeEasing
9950
+ });
9951
+ u.extend(options, animateOptions);
9952
+ state.phase = 'closing';
9953
+ return up.destroy(state.$tooltip, options).then(function() {
9954
+ state.phase = 'closed';
9955
+ state.$tooltip = null;
9956
+ return state.$anchor = null;
9957
+ });
9958
+ };
9959
+
9960
+ /**
9961
+ Returns whether a tooltip is currently showing.
9962
+
9963
+ @function up.tooltip.isOpen
9964
+ @stable
9965
+ */
9966
+ isOpen = function() {
9967
+ return state.phase === 'opening' || state.phase === 'opened';
9705
9968
  };
9706
9969
 
9707
9970
  /**
@@ -9733,28 +9996,26 @@ The tooltip element is appended to the end of `<body>`.
9733
9996
  @selector [up-tooltip-html]
9734
9997
  @stable
9735
9998
  */
9736
- up.compiler('[up-tooltip], [up-tooltip-html]', function($link) {
9737
- $link.on('mouseenter', function() {
9738
- return attach($link);
9999
+ up.compiler('[up-tooltip], [up-tooltip-html]', function($opener) {
10000
+ $opener.on('mouseenter', function() {
10001
+ return attachAsap($opener);
9739
10002
  });
9740
- return $link.on('mouseleave', function() {
9741
- return close();
10003
+ return $opener.on('mouseleave', function() {
10004
+ return closeAsap();
9742
10005
  });
9743
10006
  });
9744
10007
  up.on('click', 'body', function(event, $body) {
9745
- return close();
10008
+ return closeAsap();
9746
10009
  });
9747
- up.on('up:framework:reset', close);
10010
+ up.on('up:framework:reset', reset);
9748
10011
  up.bus.onEscape(function() {
9749
- return close();
10012
+ return closeAsap();
9750
10013
  });
9751
- up.on('up:framework:reset', reset);
9752
10014
  return {
9753
- attach: attach,
9754
- close: close,
9755
- open: function() {
9756
- return u.error('up.tooltip.open no longer exists. Use up.tooltip.attach instead.');
9757
- }
10015
+ config: config,
10016
+ attach: attachAsap,
10017
+ isOpen: isOpen,
10018
+ close: closeAsap
9758
10019
  };
9759
10020
  })(jQuery);
9760
10021
 
@@ -9806,10 +10067,7 @@ by providing instant feedback for user interactions.
9806
10067
  SELECTOR_SECTION = 'a, [up-href]';
9807
10068
  normalizeUrl = function(url) {
9808
10069
  if (u.isPresent(url)) {
9809
- return u.normalizeUrl(url, {
9810
- search: false,
9811
- stripTrailingSlash: true
9812
- });
10070
+ return u.normalizeUrl(url);
9813
10071
  }
9814
10072
  };
9815
10073
  sectionUrls = function($section) {