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