unpoly-rails 0.24.1 → 0.25.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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README_RAILS.md +8 -1
  4. data/dist/unpoly.css +22 -10
  5. data/dist/unpoly.js +406 -196
  6. data/dist/unpoly.min.css +1 -1
  7. data/dist/unpoly.min.js +3 -3
  8. data/lib/assets/javascripts/unpoly/flow.js.coffee +36 -19
  9. data/lib/assets/javascripts/unpoly/form.js.coffee +1 -2
  10. data/lib/assets/javascripts/unpoly/link.js.coffee +2 -2
  11. data/lib/assets/javascripts/unpoly/modal.js.coffee +174 -81
  12. data/lib/assets/javascripts/unpoly/navigation.js.coffee +3 -1
  13. data/lib/assets/javascripts/unpoly/popup.js.coffee +62 -37
  14. data/lib/assets/javascripts/unpoly/proxy.js.coffee +1 -0
  15. data/lib/assets/javascripts/unpoly/syntax.js.coffee +12 -4
  16. data/lib/assets/javascripts/unpoly/util.js.coffee +55 -13
  17. data/lib/assets/stylesheets/unpoly/modal.css.sass +28 -12
  18. data/lib/unpoly/rails/inspector.rb +26 -0
  19. data/lib/unpoly/rails/version.rb +1 -1
  20. data/spec_app/Gemfile.lock +1 -1
  21. data/spec_app/app/controllers/binding_test_controller.rb +6 -0
  22. data/spec_app/spec/controllers/binding_test_controller_spec.rb +82 -11
  23. data/spec_app/spec/javascripts/up/flow_spec.js.coffee +21 -7
  24. data/spec_app/spec/javascripts/up/form_spec.js.coffee +15 -0
  25. data/spec_app/spec/javascripts/up/link_spec.js.coffee +11 -10
  26. data/spec_app/spec/javascripts/up/modal_spec.js.coffee +232 -30
  27. data/spec_app/spec/javascripts/up/motion_spec.js.coffee +33 -27
  28. data/spec_app/spec/javascripts/up/popup_spec.js.coffee +72 -0
  29. data/spec_app/spec/javascripts/up/syntax_spec.js.coffee +51 -13
  30. metadata +2 -2
data/dist/unpoly.js CHANGED
@@ -29,7 +29,7 @@ that might save you from loading something like [Underscore.js](http://underscor
29
29
  @function up.util.noop
30
30
  @experimental
31
31
  */
32
- var $createElementFromSelector, $createPlaceholder, ANIMATION_DEFERRED_KEY, all, any, appendRequestData, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, cssAnimate, detect, each, error, escapePressed, except, extend, extractOptions, findWithSelf, finishCssAnimate, fixedToAbsolute, forceCompositing, forceRepaint, intersect, isArray, isBlank, isDeferred, isDefined, isDetached, isElement, isFormData, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isNumber, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, last, locationFromXhr, map, measure, memoize, merge, methodFromXhr, multiSelector, nextFrame, nonUpClasses, noop, normalizeMethod, normalizeUrl, nullJQuery, offsetParent, once, only, option, options, parseUrl, pluckData, pluckKey, presence, presentAttr, reject, remove, requestDataAsArray, requestDataAsQuery, requestDataFromForm, resolvableWhen, resolvedDeferred, resolvedPromise, scrollbarWidth, select, selectorForElement, setMissingAttrs, setTimer, temporaryCss, times, titleFromXhr, toArray, trim, unJQuery, uniq, unresolvableDeferred, unresolvablePromise, unwrapElement;
32
+ var $createElementFromSelector, $createPlaceholder, ANIMATION_DEFERRED_KEY, all, any, appendRequestData, cache, castedAttr, clientSize, compact, config, contains, copy, copyAttributes, createElement, createElementFromHtml, cssAnimate, detect, documentHasVerticalScrollbar, each, error, escapePressed, except, extend, extractOptions, findWithSelf, finishCssAnimate, fixedToAbsolute, forceCompositing, forceRepaint, intersect, isArray, isBlank, isDeferred, isDefined, isDetached, isElement, isFormData, isFunction, isGiven, isHash, isJQuery, isMissing, isNull, isNumber, isObject, isPresent, isPromise, isStandardPort, isString, isUndefined, isUnmodifiedKeyEvent, isUnmodifiedMouseEvent, last, locationFromXhr, map, measure, memoize, merge, methodFromXhr, multiSelector, nextFrame, nonUpClasses, noop, normalizeMethod, normalizeUrl, nullJQuery, offsetParent, once, only, 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;
33
33
  noop = $.noop;
34
34
 
35
35
  /**
@@ -989,6 +989,23 @@ that might save you from loading something like [Underscore.js](http://underscor
989
989
  return width;
990
990
  });
991
991
 
992
+ /**
993
+ Returns whether the given element is currently showing a vertical scrollbar.
994
+
995
+ @function up.util.documentHasVerticalScrollbar
996
+ @internal
997
+ */
998
+ documentHasVerticalScrollbar = function() {
999
+ var $body, body, bodyOverflow, forcedHidden, forcedScroll, html;
1000
+ body = document.body;
1001
+ $body = $(body);
1002
+ html = document.documentElement;
1003
+ bodyOverflow = $body.css('overflow-y');
1004
+ forcedScroll = bodyOverflow === 'scroll';
1005
+ forcedHidden = bodyOverflow === 'hidden';
1006
+ return forcedScroll || (!forcedHidden && html.scrollHeight > html.clientHeight);
1007
+ };
1008
+
992
1009
  /**
993
1010
  Modifies the given function so it only runs once.
994
1011
  Subsequent calls will return the previous return value.
@@ -1100,7 +1117,7 @@ that might save you from loading something like [Underscore.js](http://underscor
1100
1117
  @internal
1101
1118
  */
1102
1119
  cssAnimate = function(elementOrSelector, lastFrame, opts) {
1103
- var $element, animationEnd, deferred, endTimeout, oldTransition, transition, withoutCompositing;
1120
+ var $element, deferred, oldTransition, onTransitionEnd, transition, transitionFinished, transitionProperties, withoutCompositing;
1104
1121
  $element = $(elementOrSelector);
1105
1122
  opts = options(opts, {
1106
1123
  duration: 300,
@@ -1108,14 +1125,29 @@ that might save you from loading something like [Underscore.js](http://underscor
1108
1125
  easing: 'ease'
1109
1126
  });
1110
1127
  deferred = $.Deferred();
1128
+ transitionProperties = Object.keys(lastFrame);
1111
1129
  transition = {
1112
- 'transition-property': Object.keys(lastFrame).join(', '),
1130
+ 'transition-property': transitionProperties.join(', '),
1113
1131
  'transition-duration': opts.duration + "ms",
1114
1132
  'transition-delay': opts.delay + "ms",
1115
1133
  'transition-timing-function': opts.easing
1116
1134
  };
1117
1135
  oldTransition = $element.css(Object.keys(transition));
1118
1136
  $element.addClass('up-animating');
1137
+ transitionFinished = function() {
1138
+ $element.removeClass('up-animating');
1139
+ return $element.off('transitionend', onTransitionEnd);
1140
+ };
1141
+ onTransitionEnd = function(event) {
1142
+ var completedProperty;
1143
+ completedProperty = event.originalEvent.propertyName;
1144
+ if (contains(transitionProperties, completedProperty)) {
1145
+ deferred.resolve();
1146
+ return transitionFinished();
1147
+ }
1148
+ };
1149
+ $element.on('transitionend', onTransitionEnd);
1150
+ deferred.then(transitionFinished);
1119
1151
  withoutCompositing = forceCompositing($element);
1120
1152
  $element.css(transition);
1121
1153
  $element.css(lastFrame);
@@ -1133,16 +1165,6 @@ that might save you from loading something like [Underscore.js](http://underscor
1133
1165
  return $element.css(oldTransition);
1134
1166
  }
1135
1167
  });
1136
- animationEnd = opts.duration + opts.delay;
1137
- endTimeout = setTimer(animationEnd, function() {
1138
- $element.removeClass('up-animating');
1139
- if (!isDetached($element)) {
1140
- return deferred.resolve();
1141
- }
1142
- });
1143
- deferred.then(function() {
1144
- return clearTimeout(endTimeout);
1145
- });
1146
1168
  return deferred;
1147
1169
  };
1148
1170
  ANIMATION_DEFERRED_KEY = 'up-animation-deferred';
@@ -1941,6 +1963,15 @@ that might save you from loading something like [Underscore.js](http://underscor
1941
1963
  return {};
1942
1964
  }
1943
1965
  };
1966
+ opacity = function(element) {
1967
+ var rawOpacity;
1968
+ rawOpacity = $(element).css('opacity');
1969
+ if (isGiven(rawOpacity)) {
1970
+ return parseFloat(rawOpacity);
1971
+ } else {
1972
+ return void 0;
1973
+ }
1974
+ };
1944
1975
 
1945
1976
  /**
1946
1977
  Returns whether the given element has been detached from the DOM
@@ -2040,6 +2071,7 @@ that might save you from loading something like [Underscore.js](http://underscor
2040
2071
  remove: remove,
2041
2072
  memoize: memoize,
2042
2073
  scrollbarWidth: scrollbarWidth,
2074
+ documentHasVerticalScrollbar: documentHasVerticalScrollbar,
2043
2075
  config: config,
2044
2076
  cache: cache,
2045
2077
  unwrapElement: unwrapElement,
@@ -2049,7 +2081,8 @@ that might save you from loading something like [Underscore.js](http://underscor
2049
2081
  pluckKey: pluckKey,
2050
2082
  extractOptions: extractOptions,
2051
2083
  isDetached: isDetached,
2052
- noop: noop
2084
+ noop: noop,
2085
+ opacity: opacity
2053
2086
  };
2054
2087
  })($);
2055
2088
 
@@ -3203,6 +3236,11 @@ later.
3203
3236
  options = u.options(args[0], {
3204
3237
  priority: 0
3205
3238
  });
3239
+ if (options.priority === 'first') {
3240
+ options.priority = Number.POSITIVE_INFINITY;
3241
+ } else if (options.priority === 'last') {
3242
+ options.priority = Number.NEGATIVE_INFINITY;
3243
+ }
3206
3244
  return {
3207
3245
  selector: selector,
3208
3246
  callback: callback,
@@ -3219,7 +3257,7 @@ later.
3219
3257
  }
3220
3258
  newCompiler = buildCompiler.apply(null, args);
3221
3259
  index = 0;
3222
- while ((oldCompiler = queue[index]) && (oldCompiler.priority <= newCompiler.priority)) {
3260
+ while ((oldCompiler = queue[index]) && (oldCompiler.priority >= newCompiler.priority)) {
3223
3261
  index += 1;
3224
3262
  }
3225
3263
  return queue.splice(index, 0, newCompiler);
@@ -3304,6 +3342,7 @@ later.
3304
3342
  var $element, destroyer;
3305
3343
  $element = $(this);
3306
3344
  destroyer = $element.data(DESTROYER_KEY);
3345
+ $element.removeClass(DESTROYABLE_CLASS);
3307
3346
  return destroyer();
3308
3347
  });
3309
3348
  };
@@ -3394,13 +3433,12 @@ later.
3394
3433
  @internal
3395
3434
  */
3396
3435
  snapshot = function() {
3397
- var i, len, results;
3398
- results = [];
3399
- for (i = 0, len = compilers.length; i < len; i++) {
3400
- compiler = compilers[i];
3401
- results.push(compiler.isDefault = true);
3402
- }
3403
- return results;
3436
+ var setDefault;
3437
+ setDefault = function(compiler) {
3438
+ return compiler.isDefault = true;
3439
+ };
3440
+ u.each(compilers, setDefault);
3441
+ return u.each(macros, setDefault);
3404
3442
  };
3405
3443
 
3406
3444
  /**
@@ -3410,9 +3448,12 @@ later.
3410
3448
  @internal
3411
3449
  */
3412
3450
  reset = function() {
3413
- return compilers = u.select(compilers, function(compiler) {
3451
+ var isDefault;
3452
+ isDefault = function(compiler) {
3414
3453
  return compiler.isDefault;
3415
- });
3454
+ };
3455
+ compilers = u.select(compilers, isDefault);
3456
+ return macros = u.select(macros, isDefault);
3416
3457
  };
3417
3458
  up.on('up:framework:boot', snapshot);
3418
3459
  up.on('up:framework:reset', reset);
@@ -4495,7 +4536,7 @@ are based on this module.
4495
4536
  @stable
4496
4537
  */
4497
4538
  replace = function(selectorOrElement, url, options) {
4498
- var failTarget, promise, request, target;
4539
+ var failTarget, onFailure, onSuccess, promise, request, target;
4499
4540
  up.puts("Replacing %s from %s (%o)", selectorOrElement, url, options);
4500
4541
  options = u.options(options);
4501
4542
  target = resolveSelector(selectorOrElement, options.origin);
@@ -4518,12 +4559,13 @@ are based on this module.
4518
4559
  headers: options.headers
4519
4560
  };
4520
4561
  promise = up.ajax(request);
4521
- promise.done(function(html, textStatus, xhr) {
4562
+ onSuccess = function(html, textStatus, xhr) {
4522
4563
  return processResponse(true, target, url, request, xhr, options);
4523
- });
4524
- promise.fail(function(xhr, textStatus, errorThrown) {
4564
+ };
4565
+ onFailure = function(xhr, textStatus, errorThrown) {
4525
4566
  return processResponse(false, failTarget, url, request, xhr, options);
4526
- });
4567
+ };
4568
+ promise = promise.then(onSuccess, onFailure);
4527
4569
  return promise;
4528
4570
  };
4529
4571
 
@@ -4631,7 +4673,7 @@ are based on this module.
4631
4673
  */
4632
4674
  extract = function(selectorOrElement, html, options) {
4633
4675
  return up.log.group('Extracting %s from %d bytes of HTML', selectorOrElement, html != null ? html.length : void 0, function() {
4634
- var deferreds, j, len, ref, ref1, response, selector, step;
4676
+ var promise, response, selector;
4635
4677
  options = u.options(options, {
4636
4678
  historyMethod: 'push',
4637
4679
  requireMatch: true,
@@ -4643,28 +4685,35 @@ are based on this module.
4643
4685
  if (options.saveScroll !== false) {
4644
4686
  up.layout.saveScroll();
4645
4687
  }
4646
- if (typeof options.beforeSwap === "function") {
4647
- options.beforeSwap();
4648
- }
4649
- deferreds = [];
4650
- updateHistory(options);
4651
- ref = parseImplantSteps(selector, options);
4652
- for (j = 0, len = ref.length; j < len; j++) {
4653
- step = ref[j];
4654
- up.log.group('Updating %s', step.selector, function() {
4655
- var $new, $old, deferred, ref1;
4656
- $old = findOldFragment(step.selector, options);
4657
- $new = (ref1 = response.find(step.selector)) != null ? ref1.first() : void 0;
4658
- if ($old && $new) {
4659
- deferred = swapElements($old, $new, step.pseudoClass, step.transition, options);
4660
- return deferreds.push(deferred);
4661
- }
4662
- });
4688
+ promise = u.resolvedPromise();
4689
+ if (options.beforeSwap) {
4690
+ promise = promise.then(options.beforeSwap);
4663
4691
  }
4664
- if (typeof options.afterSwap === "function") {
4665
- options.afterSwap();
4692
+ promise = promise.then(function() {
4693
+ return updateHistory(options);
4694
+ });
4695
+ promise = promise.then(function() {
4696
+ var j, len, ref, step, swapPromises;
4697
+ swapPromises = [];
4698
+ ref = parseImplantSteps(selector, options);
4699
+ for (j = 0, len = ref.length; j < len; j++) {
4700
+ step = ref[j];
4701
+ up.log.group('Updating %s', step.selector, function() {
4702
+ var $new, $old, ref1, swapPromise;
4703
+ $old = findOldFragment(step.selector, options);
4704
+ $new = (ref1 = response.find(step.selector)) != null ? ref1.first() : void 0;
4705
+ if ($old && $new) {
4706
+ swapPromise = swapElements($old, $new, step.pseudoClass, step.transition, options);
4707
+ return swapPromises.push(swapPromise);
4708
+ }
4709
+ });
4710
+ }
4711
+ return $.when.apply($, swapPromises);
4712
+ });
4713
+ if (options.afterSwap) {
4714
+ promise = promise.then(options.afterSwap);
4666
4715
  }
4667
- return (ref1 = up.motion).when.apply(ref1, deferreds);
4716
+ return promise;
4668
4717
  });
4669
4718
  };
4670
4719
  findOldFragment = function(selector, options) {
@@ -4736,7 +4785,12 @@ are based on this module.
4736
4785
  } else {
4737
4786
  replacement = function() {
4738
4787
  options.keepPlans = transferKeepableElements($old, $new, options);
4739
- $new.insertBefore($old);
4788
+ if ($old.is('body')) {
4789
+ up.syntax.clean($old);
4790
+ $old.replaceWith($new);
4791
+ } else {
4792
+ $new.insertBefore($old);
4793
+ }
4740
4794
  if (options.source !== false) {
4741
4795
  setSource($new, options.source);
4742
4796
  }
@@ -6344,7 +6398,7 @@ the user performs the click.
6344
6398
  args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
6345
6399
  return (ref = entry.deferred).resolve.apply(ref, args);
6346
6400
  });
6347
- return promise.fail(function() {
6401
+ promise.fail(function() {
6348
6402
  var args, ref;
6349
6403
  args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
6350
6404
  return (ref = entry.deferred).reject.apply(ref, args);
@@ -6952,7 +7006,9 @@ Read on
6952
7006
  @selector [up-dash]
6953
7007
  @stable
6954
7008
  */
6955
- up.macro('[up-dash]', function($element) {
7009
+ up.macro('[up-dash]', {
7010
+ priority: 'last'
7011
+ }, function($element) {
6956
7012
  var newAttrs, target;
6957
7013
  target = u.castedAttr($element, 'up-dash');
6958
7014
  $element.removeAttr('up-dash');
@@ -7005,7 +7061,9 @@ Read on
7005
7061
  If omitted, the first contained link will be expanded.
7006
7062
  @stable
7007
7063
  */
7008
- up.macro('[up-expand]', function($area) {
7064
+ up.macro('[up-expand]', {
7065
+ priority: 'last'
7066
+ }, function($area) {
7009
7067
  var $childLinks, attribute, i, len, link, name, newAttrs, ref, selector, upAttributePattern;
7010
7068
  $childLinks = $area.find('a, [up-href]');
7011
7069
  if (selector = $area.attr('up-expand')) {
@@ -7376,9 +7434,8 @@ open dialogs with sub-forms, etc. all without losing form state.
7376
7434
  return observe(selectorOrElement, options, function(value, $field) {
7377
7435
  var $form;
7378
7436
  $form = $field.closest('form');
7379
- $field.addClass('up-active');
7380
- return submit($form).always(function() {
7381
- return $field.removeClass('up-active');
7437
+ return up.navigation.withActiveMark($field, function() {
7438
+ return submit($form);
7382
7439
  });
7383
7440
  });
7384
7441
  };
@@ -7963,7 +8020,7 @@ Pop-up overlays
7963
8020
  Instead of [linking to a page fragment](/up.link), you can choose
7964
8021
  to show a fragment in a popup overlay that rolls down from an anchoring element.
7965
8022
 
7966
- To open a popup, add an [`up-popup` attribute](/a-up-popup) to a link,
8023
+ To open a popup, add an [`up-popup` attribute](/up-popup) to a link,
7967
8024
  or call the Javascript function [`up.popup.attach`](/up.popup.attach).
7968
8025
 
7969
8026
  For modal dialogs see [up.modal](/up.modal) instead.
@@ -7989,7 +8046,7 @@ By default the popup uses the following DOM structure:
7989
8046
  The popup closes when the user clicks anywhere outside the popup area.
7990
8047
 
7991
8048
  By default the popup also closes
7992
- *whenever a page fragment behind the popup is updated*.
8049
+ *when a link within the popup changes a fragment behind the popup*.
7993
8050
  This is useful to have the popup interact with the page that
7994
8051
  opened it, e.g. by updating parts of a larger form or by signing in a user
7995
8052
  and revealing additional information.
@@ -8032,21 +8089,36 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8032
8089
  Sets default options for future popups.
8033
8090
 
8034
8091
  @property up.popup.config
8035
- @param {String} [config.openAnimation='fade-in']
8036
- The animation used to open a popup.
8037
- @param {String} [config.closeAnimation='fade-out']
8038
- The animation used to close a popup.
8039
8092
  @param {String} [config.position='bottom-right']
8040
8093
  Defines where the popup is attached to the opening element.
8041
8094
 
8042
8095
  Valid values are `bottom-right`, `bottom-left`, `top-right` and `top-left`.
8043
8096
  @param {String} [config.history=false]
8044
8097
  Whether opening a popup will add a browser history entry.
8098
+ @param {String} [config.openAnimation='fade-in']
8099
+ The animation used to open a popup.
8100
+ @param {String} [config.closeAnimation='fade-out']
8101
+ The animation used to close a popup.
8102
+ @param {String} [config.openDuration]
8103
+ The duration of the open animation (in milliseconds).
8104
+ @param {String} [config.closeDuration]
8105
+ The duration of the close animation (in milliseconds).
8106
+ @param {String} [config.openEasing]
8107
+ The timing function controlling the acceleration of the opening animation.
8108
+ @param {String} [config.closeEasing]
8109
+ The timing function controlling the acceleration of the closing animation.
8110
+ @param {Boolean} [options.sticky=false]
8111
+ If set to `true`, the popup remains
8112
+ open even it changes the page in the background.
8045
8113
  @stable
8046
8114
  */
8047
8115
  config = u.config({
8048
8116
  openAnimation: 'fade-in',
8049
8117
  closeAnimation: 'fade-out',
8118
+ openDuration: null,
8119
+ closeDuration: null,
8120
+ openEasing: null,
8121
+ closeEasing: null,
8050
8122
  position: 'bottom-right',
8051
8123
  history: false
8052
8124
  });
@@ -8133,16 +8205,26 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8133
8205
  return $popup.removeAttr('up-covered-title');
8134
8206
  };
8135
8207
  createFrame = function(target, options) {
8136
- var $popup;
8137
- $popup = u.$createElementFromSelector('.up-popup');
8138
- if (options.sticky) {
8139
- $popup.attr('up-sticky', '');
8208
+ var promise;
8209
+ promise = u.resolvedPromise();
8210
+ if (isOpen()) {
8211
+ promise = promise.then(function() {
8212
+ return close();
8213
+ });
8140
8214
  }
8141
- $popup.attr('up-covered-url', up.browser.url());
8142
- $popup.attr('up-covered-title', document.title);
8143
- u.$createPlaceholder(target, $popup);
8144
- $popup.appendTo(document.body);
8145
- return $popup;
8215
+ promise = promise.then(function() {
8216
+ var $popup;
8217
+ $popup = u.$createElementFromSelector('.up-popup');
8218
+ if (options.sticky) {
8219
+ $popup.attr('up-sticky', '');
8220
+ }
8221
+ $popup.attr('up-covered-url', up.browser.url());
8222
+ $popup.attr('up-covered-title', document.title);
8223
+ u.$createPlaceholder(target, $popup);
8224
+ $popup.appendTo(document.body);
8225
+ return $popup;
8226
+ });
8227
+ return promise;
8146
8228
  };
8147
8229
 
8148
8230
  /**
@@ -8163,6 +8245,8 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8163
8245
  @function up.popup.attach
8164
8246
  @param {Element|jQuery|String} elementOrSelector
8165
8247
  @param {String} [options.url]
8248
+ @param {String} [options.target]
8249
+ A CSS selector that will be extracted from the response and placed into the popup.
8166
8250
  @param {String} [options.position='bottom-right']
8167
8251
  Defines where the popup is attached to the opening element.
8168
8252
 
@@ -8188,44 +8272,45 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8188
8272
  @stable
8189
8273
  */
8190
8274
  attach = function(linkOrSelector, options) {
8191
- var $link, animateOptions, target, url;
8275
+ var $link, animateOptions, html, target, url;
8192
8276
  $link = $(linkOrSelector);
8193
8277
  $link.length || u.error('Cannot attach popup to non-existing element %o', linkOrSelector);
8194
8278
  options = u.options(options);
8195
- url = u.option(options.url, $link.attr('href'));
8196
- target = u.option(options.target, $link.attr('up-popup'), 'body');
8279
+ url = u.option(u.pluckKey(options, 'url'), $link.attr('up-href'), $link.attr('href'));
8280
+ html = u.option(u.pluckKey(options, 'html'));
8281
+ target = u.option(u.pluckKey(options, 'target'), $link.attr('up-popup'), 'body');
8197
8282
  options.position = u.option(options.position, $link.attr('up-position'), config.position);
8198
8283
  options.animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation);
8199
- options.sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'));
8284
+ options.sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'), config.sticky);
8200
8285
  options.history = up.browser.canPushState() ? u.option(options.history, u.castedAttr($link, 'up-history'), config.history) : false;
8201
8286
  options.confirm = u.option(options.confirm, $link.attr('up-confirm'));
8202
- animateOptions = up.motion.animateOptions(options, $link);
8287
+ animateOptions = up.motion.animateOptions(options, $link, {
8288
+ duration: config.openDuration,
8289
+ easing: config.openEasing
8290
+ });
8203
8291
  return up.browser.confirm(options).then(function() {
8204
- var promise, wasOpen;
8292
+ var extractOptions, promise;
8205
8293
  if (up.bus.nobodyPrevents('up:popup:open', {
8206
8294
  url: url,
8207
8295
  message: 'Opening popup'
8208
8296
  })) {
8209
- wasOpen = isOpen();
8210
- if (wasOpen) {
8211
- close({
8212
- animation: false
8213
- });
8214
- }
8215
8297
  options.beforeSwap = function() {
8216
8298
  return createFrame(target, options);
8217
8299
  };
8218
- promise = up.replace(target, url, u.merge(options, {
8300
+ extractOptions = u.merge(options, {
8219
8301
  animation: false
8220
- }));
8302
+ });
8303
+ if (html) {
8304
+ promise = up.extract(target, html, extractOptions);
8305
+ } else {
8306
+ promise = up.replace(target, url, extractOptions);
8307
+ }
8221
8308
  promise = promise.then(function() {
8222
8309
  return setPosition($link, options.position);
8223
8310
  });
8224
- if (!wasOpen) {
8225
- promise = promise.then(function() {
8226
- return up.animate($('.up-popup'), options.animation, animateOptions);
8227
- });
8228
- }
8311
+ promise = promise.then(function() {
8312
+ return up.animate($('.up-popup'), options.animation, animateOptions);
8313
+ });
8229
8314
  promise = promise.then(function() {
8230
8315
  return up.emit('up:popup:opened', {
8231
8316
  message: 'Popup opened'
@@ -8233,7 +8318,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8233
8318
  });
8234
8319
  return promise;
8235
8320
  } else {
8236
- return u.unresolvableDeferred();
8321
+ return u.unresolvablePromise();
8237
8322
  }
8238
8323
  });
8239
8324
  };
@@ -8264,13 +8349,13 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8264
8349
  @function up.popup.close
8265
8350
  @param {Object} options
8266
8351
  See options for [`up.animate`](/up.animate).
8267
- @return {Deferred}
8352
+ @return {Promise}
8268
8353
  A promise that will be resolved once the modal's close
8269
8354
  animation has finished.
8270
8355
  @stable
8271
8356
  */
8272
8357
  close = function(options) {
8273
- var $popup, deferred;
8358
+ var $popup, animateOptions, promise;
8274
8359
  $popup = $('.up-popup');
8275
8360
  if ($popup.length) {
8276
8361
  if (up.bus.nobodyPrevents('up:popup:close', {
@@ -8281,19 +8366,24 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8281
8366
  url: $popup.attr('up-covered-url'),
8282
8367
  title: $popup.attr('up-covered-title')
8283
8368
  });
8369
+ animateOptions = up.motion.animateOptions(options, {
8370
+ duration: config.closeDuration,
8371
+ easing: config.closeEasing
8372
+ });
8373
+ u.extend(options, animateOptions);
8284
8374
  currentUrl = void 0;
8285
- deferred = up.destroy($popup, options);
8286
- deferred.then(function() {
8375
+ promise = up.destroy($popup, options);
8376
+ promise = promise.then(function() {
8287
8377
  return up.emit('up:popup:closed', {
8288
8378
  message: 'Popup closed'
8289
8379
  });
8290
8380
  });
8291
- return deferred;
8381
+ return promise;
8292
8382
  } else {
8293
- return u.unresolvableDeferred();
8383
+ return u.unresolvablePromise();
8294
8384
  }
8295
8385
  } else {
8296
- return u.resolvedDeferred();
8386
+ return u.resolvedPromise();
8297
8387
  }
8298
8388
  };
8299
8389
 
@@ -8346,7 +8436,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8346
8436
  <a href="/decks" up-popup=".deck_list">Switch deck</a>
8347
8437
  <a href="/settings" up-popup=".options" up-sticky>Settings</a>
8348
8438
 
8349
- @selector a[up-popup]
8439
+ @selector [up-popup]
8350
8440
  @param [up-position]
8351
8441
  Defines where the popup is attached to the opening element.
8352
8442
 
@@ -8441,7 +8531,7 @@ Instead of [linking to a page fragment](/up.link), you can choose
8441
8531
  to show a fragment in a modal dialog. The existing page will remain
8442
8532
  open in the background and reappear once the modal is closed.
8443
8533
 
8444
- To open a modal, add an [`up-modal` attribute](/a-up-modal) to a link,
8534
+ To open a modal, add an [`up-modal` attribute](/up-modal) to a link,
8445
8535
  or call the Javascript functions [`up.modal.follow`](/up.modal.follow)
8446
8536
  and [`up.modal.visit`](/up.modal.visit).
8447
8537
 
@@ -8481,7 +8571,7 @@ configure Unpoly to [use a different HTML structure](/up.modal.config).
8481
8571
  \#\#\#\# Closing behavior
8482
8572
 
8483
8573
  By default the dialog automatically closes
8484
- *whenever a page fragment behind the dialog is updated*.
8574
+ *when a link inside a modal changes a fragment behind the modal*.
8485
8575
  This is useful to have the dialog interact with the page that
8486
8576
  opened it, e.g. by updating parts of a larger form or by signing in a user
8487
8577
  and revealing additional information.
@@ -8496,7 +8586,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8496
8586
 
8497
8587
  (function() {
8498
8588
  up.modal = (function($) {
8499
- var autoclose, close, config, contains, coveredUrl, createFrame, currentUrl, discardHistory, extract, follow, isOpen, open, reset, shiftElements, templateHtml, u, unshiftElements, unshifters, visit;
8589
+ 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;
8500
8590
  u = up.util;
8501
8591
 
8502
8592
  /**
@@ -8546,6 +8636,9 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8546
8636
  The timing function controlling the acceleration of the opening animation.
8547
8637
  @param {String} [config.closeEasing]
8548
8638
  The timing function controlling the acceleration of the closing animation.
8639
+ @param {Boolean} [options.sticky=false]
8640
+ If set to `true`, the modal remains
8641
+ open even it changes the page in the background.
8549
8642
  @stable
8550
8643
  */
8551
8644
  config = u.config({
@@ -8556,15 +8649,18 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8556
8649
  history: true,
8557
8650
  openAnimation: 'fade-in',
8558
8651
  closeAnimation: 'fade-out',
8559
- closeDuration: null,
8560
- closeEasing: null,
8561
8652
  openDuration: null,
8653
+ closeDuration: null,
8562
8654
  openEasing: null,
8655
+ closeEasing: null,
8563
8656
  backdropOpenAnimation: 'fade-in',
8564
8657
  backdropCloseAnimation: 'fade-out',
8565
8658
  closeLabel: '×',
8659
+ flavors: {
8660
+ "default": {}
8661
+ },
8566
8662
  template: function(config) {
8567
- return "<div class=\"up-modal\">\n <div class=\"up-modal-backdrop\"></div>\n <div class=\"up-modal-viewport\">\n <div class=\"up-modal-dialog\">\n <div class=\"up-modal-close\" up-close>" + config.closeLabel + "</div>\n <div class=\"up-modal-content\"></div>\n </div>\n </div>\n</div>";
8663
+ return "<div class=\"up-modal\">\n <div class=\"up-modal-backdrop\"></div>\n <div class=\"up-modal-viewport\">\n <div class=\"up-modal-dialog\">\n <div class=\"up-modal-content\"></div>\n <div class=\"up-modal-close\" up-close>" + (flavorDefault('closeLabel')) + "</div>\n </div>\n </div>\n</div>";
8568
8664
  }
8569
8665
  });
8570
8666
 
@@ -8578,6 +8674,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8578
8674
  @stable
8579
8675
  */
8580
8676
  currentUrl = void 0;
8677
+ currentFlavor = void 0;
8581
8678
 
8582
8679
  /**
8583
8680
  Returns the URL of the page behind the modal overlay.
@@ -8594,11 +8691,12 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8594
8691
  animation: false
8595
8692
  });
8596
8693
  currentUrl = void 0;
8694
+ currentFlavor = void 0;
8597
8695
  return config.reset();
8598
8696
  };
8599
8697
  templateHtml = function() {
8600
8698
  var template;
8601
- template = config.template;
8699
+ template = flavorDefault('template');
8602
8700
  if (u.isFunction(template)) {
8603
8701
  return template(config);
8604
8702
  } else {
@@ -8612,57 +8710,69 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8612
8710
  return $modal.removeAttr('up-covered-title');
8613
8711
  };
8614
8712
  createFrame = function(target, options) {
8615
- var $content, $dialog, $modal;
8616
- $modal = $(templateHtml());
8617
- if (options.sticky) {
8618
- $modal.attr('up-sticky', '');
8619
- }
8620
- $modal.attr('up-covered-url', up.browser.url());
8621
- $modal.attr('up-covered-title', document.title);
8622
- $dialog = $modal.find('.up-modal-dialog');
8623
- if (u.isPresent(options.width)) {
8624
- $dialog.css('width', options.width);
8625
- }
8626
- if (u.isPresent(options.maxWidth)) {
8627
- $dialog.css('max-width', options.maxWidth);
8628
- }
8629
- if (u.isPresent(options.height)) {
8630
- $dialog.css('height', options.height);
8713
+ var promise;
8714
+ promise = u.resolvedPromise();
8715
+ if (isOpen()) {
8716
+ promise = promise.then(function() {
8717
+ return close();
8718
+ });
8631
8719
  }
8632
- $content = $modal.find('.up-modal-content');
8633
- u.$createPlaceholder(target, $content);
8634
- $modal.appendTo(document.body);
8635
- return $modal;
8720
+ promise = promise.then(function() {
8721
+ var $content, $dialog, $modal;
8722
+ currentFlavor = options.flavor;
8723
+ $modal = $(templateHtml());
8724
+ $modal.attr('up-flavor', currentFlavor);
8725
+ if (options.sticky) {
8726
+ $modal.attr('up-sticky', '');
8727
+ }
8728
+ $modal.attr('up-covered-url', up.browser.url());
8729
+ $modal.attr('up-covered-title', document.title);
8730
+ $dialog = $modal.find('.up-modal-dialog');
8731
+ if (u.isPresent(options.width)) {
8732
+ $dialog.css('width', options.width);
8733
+ }
8734
+ if (u.isPresent(options.maxWidth)) {
8735
+ $dialog.css('max-width', options.maxWidth);
8736
+ }
8737
+ if (u.isPresent(options.height)) {
8738
+ $dialog.css('height', options.height);
8739
+ }
8740
+ $content = $modal.find('.up-modal-content');
8741
+ u.$createPlaceholder(target, $content);
8742
+ return $modal.appendTo(document.body);
8743
+ });
8744
+ return promise;
8636
8745
  };
8637
8746
  unshifters = [];
8638
8747
  shiftElements = function() {
8639
- var bodyRightPadding, bodyRightShift, scrollbarWidth, unshiftBody;
8640
- if (unshifters.length) {
8641
- u.error('Tried to call shiftElements multiple times %o', unshifters.length);
8642
- }
8643
- $('.up-modal').addClass('up-modal-ready');
8644
- scrollbarWidth = u.scrollbarWidth();
8645
- bodyRightPadding = parseInt($('body').css('padding-right'));
8646
- bodyRightShift = scrollbarWidth + bodyRightPadding;
8647
- unshiftBody = u.temporaryCss($('body'), {
8648
- 'padding-right': bodyRightShift + "px",
8649
- 'overflow-y': 'hidden'
8650
- });
8651
- unshifters.push(unshiftBody);
8652
- return up.layout.anchoredRight().each(function() {
8653
- var $element, elementRight, elementRightShift, unshifter;
8654
- $element = $(this);
8655
- elementRight = parseInt($element.css('right'));
8656
- elementRightShift = scrollbarWidth + elementRight;
8657
- unshifter = u.temporaryCss($element, {
8658
- 'right': elementRightShift
8748
+ var $body, bodyRightPadding, bodyRightShift, scrollbarWidth, unshiftBody;
8749
+ if (unshifters.length > 0) {
8750
+ return;
8751
+ }
8752
+ if (u.documentHasVerticalScrollbar()) {
8753
+ $body = $('body');
8754
+ scrollbarWidth = u.scrollbarWidth();
8755
+ bodyRightPadding = parseInt($body.css('padding-right'));
8756
+ bodyRightShift = scrollbarWidth + bodyRightPadding;
8757
+ unshiftBody = u.temporaryCss($body, {
8758
+ 'padding-right': bodyRightShift + "px",
8759
+ 'overflow-y': 'hidden'
8659
8760
  });
8660
- return unshifters.push(unshifter);
8661
- });
8761
+ unshifters.push(unshiftBody);
8762
+ return up.layout.anchoredRight().each(function() {
8763
+ var $element, elementRight, elementRightShift, unshifter;
8764
+ $element = $(this);
8765
+ elementRight = parseInt($element.css('right'));
8766
+ elementRightShift = scrollbarWidth + elementRight;
8767
+ unshifter = u.temporaryCss($element, {
8768
+ 'right': elementRightShift
8769
+ });
8770
+ return unshifters.push(unshifter);
8771
+ });
8772
+ }
8662
8773
  };
8663
8774
  unshiftElements = function() {
8664
8775
  var results, unshifter;
8665
- $('.up-modal').removeClass('up-modal-ready');
8666
8776
  results = [];
8667
8777
  while (unshifter = unshifters.pop()) {
8668
8778
  results.push(unshifter());
@@ -8673,6 +8783,8 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8673
8783
  /**
8674
8784
  Returns whether a modal is currently open.
8675
8785
 
8786
+ This also returns `true` if the modal is in an opening or closing animation.
8787
+
8676
8788
  @function up.modal.isOpen
8677
8789
  @stable
8678
8790
  */
@@ -8703,7 +8815,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8703
8815
  By [default](/up.modal.config) the dialog will grow to fit its contents.
8704
8816
  @param {Boolean} [options.sticky=false]
8705
8817
  If set to `true`, the modal remains
8706
- open even if the page changes in the background.
8818
+ open even it changes the page in the background.
8707
8819
  @param {String} [options.confirm]
8708
8820
  A message that will be displayed in a cancelable confirmation dialog
8709
8821
  before the modal is being opened.
@@ -8801,53 +8913,48 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8801
8913
  options = u.options(options);
8802
8914
  $link = u.option(u.pluckKey(options, '$link'), u.nullJQuery());
8803
8915
  url = u.option(u.pluckKey(options, 'url'), $link.attr('up-href'), $link.attr('href'));
8804
- html = u.pluckKey(options, 'html');
8916
+ html = u.option(u.pluckKey(options, 'html'));
8805
8917
  target = u.option(u.pluckKey(options, 'target'), $link.attr('up-modal'), 'body');
8806
- options.width = u.option(options.width, $link.attr('up-width'), config.width);
8807
- options.maxWidth = u.option(options.maxWidth, $link.attr('up-max-width'), config.maxWidth);
8808
- options.height = u.option(options.height, $link.attr('up-height'), config.height);
8809
- options.animation = u.option(options.animation, $link.attr('up-animation'), config.openAnimation);
8810
- options.backdropAnimation = u.option(options.backdropAnimation, $link.attr('up-backdrop-animation'), config.backdropOpenAnimation);
8811
- options.sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'));
8918
+ options.flavor = u.option(options.flavor, $link.attr('up-flavor'));
8919
+ options.width = u.option(options.width, $link.attr('up-width'), flavorDefault('width', options.flavor));
8920
+ options.maxWidth = u.option(options.maxWidth, $link.attr('up-max-width'), flavorDefault('maxWidth', options.flavor));
8921
+ options.height = u.option(options.height, $link.attr('up-height'), flavorDefault('height'));
8922
+ options.animation = u.option(options.animation, $link.attr('up-animation'), flavorDefault('openAnimation', options.flavor));
8923
+ options.backdropAnimation = u.option(options.backdropAnimation, $link.attr('up-backdrop-animation'), flavorDefault('backdropOpenAnimation', options.flavor));
8924
+ options.sticky = u.option(options.sticky, u.castedAttr($link, 'up-sticky'), flavorDefault('sticky', options.flavor));
8812
8925
  options.confirm = u.option(options.confirm, $link.attr('up-confirm'));
8813
8926
  animateOptions = up.motion.animateOptions(options, $link, {
8814
- duration: config.openDuration,
8815
- easing: config.openEasing
8927
+ duration: flavorDefault('openDuration', options.flavor),
8928
+ easing: flavorDefault('openEasing', options.flavor)
8816
8929
  });
8817
- options.history = u.option(options.history, u.castedAttr($link, 'up-history'), config.history);
8930
+ options.history = u.option(options.history, u.castedAttr($link, 'up-history'), flavorDefault('history', options.flavor));
8818
8931
  if (!up.browser.canPushState()) {
8819
8932
  options.history = false;
8820
8933
  }
8821
8934
  return up.browser.confirm(options).then(function() {
8822
- var extractOptions, promise, wasOpen;
8935
+ var extractOptions, promise;
8823
8936
  if (up.bus.nobodyPrevents('up:modal:open', {
8824
8937
  url: url,
8825
8938
  message: 'Opening modal'
8826
8939
  })) {
8827
- wasOpen = isOpen();
8828
- if (wasOpen) {
8829
- close({
8830
- animation: false
8831
- });
8832
- }
8833
8940
  options.beforeSwap = function() {
8834
8941
  return createFrame(target, options);
8835
8942
  };
8836
8943
  extractOptions = u.merge(options, {
8837
8944
  animation: false
8838
8945
  });
8839
- if (url) {
8840
- promise = up.replace(target, url, extractOptions);
8841
- } else {
8946
+ if (html) {
8842
8947
  promise = up.extract(target, html, extractOptions);
8948
+ } else {
8949
+ promise = up.replace(target, url, extractOptions);
8843
8950
  }
8844
- if (!(wasOpen || up.motion.isNone(options.animation))) {
8845
- promise = promise.then(function() {
8846
- return $.when(up.animate($('.up-modal-backdrop'), options.backdropAnimation, animateOptions), up.animate($('.up-modal-viewport'), options.animation, animateOptions));
8847
- });
8848
- }
8849
8951
  promise = promise.then(function() {
8850
- shiftElements();
8952
+ return shiftElements();
8953
+ });
8954
+ promise = promise.then(function() {
8955
+ return animate(options.animation, options.backdropAnimation, animateOptions);
8956
+ });
8957
+ promise = promise.then(function() {
8851
8958
  return up.emit('up:modal:opened', {
8852
8959
  message: 'Modal opened'
8853
8960
  });
@@ -8899,18 +9006,16 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8899
9006
  $element: $modal,
8900
9007
  message: 'Closing modal'
8901
9008
  })) {
8902
- unshiftElements();
8903
- viewportCloseAnimation = u.option(options.animation, config.closeAnimation);
8904
- backdropCloseAnimation = u.option(options.backdropAnimation, config.backdropCloseAnimation);
9009
+ viewportCloseAnimation = u.option(options.animation, flavorDefault('closeAnimation'));
9010
+ backdropCloseAnimation = u.option(options.backdropAnimation, flavorDefault('backdropCloseAnimation'));
8905
9011
  animateOptions = up.motion.animateOptions(options, {
8906
- duration: config.closeDuration,
8907
- easing: config.closeEasing
9012
+ duration: flavorDefault('closeDuration'),
9013
+ easing: flavorDefault('closeEasing')
9014
+ });
9015
+ promise = u.resolvedPromise();
9016
+ promise = promise.then(function() {
9017
+ return animate(viewportCloseAnimation, backdropCloseAnimation, animateOptions);
8908
9018
  });
8909
- if (up.motion.isNone(viewportCloseAnimation)) {
8910
- promise = u.resolvedPromise();
8911
- } else {
8912
- promise = $.when(up.animate($('.up-modal-viewport'), viewportCloseAnimation, animateOptions), up.animate($('.up-modal-backdrop'), backdropCloseAnimation, animateOptions));
8913
- }
8914
9019
  promise = promise.then(function() {
8915
9020
  var destroyOptions;
8916
9021
  destroyOptions = u.options(u.except(options, 'animation', 'duration', 'easing', 'delay'), {
@@ -8918,17 +9023,40 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8918
9023
  title: $modal.attr('up-covered-title')
8919
9024
  });
8920
9025
  currentUrl = void 0;
8921
- up.destroy($modal, destroyOptions);
9026
+ return up.destroy($modal, destroyOptions);
9027
+ });
9028
+ promise = promise.then(function() {
9029
+ unshiftElements();
9030
+ currentFlavor = void 0;
8922
9031
  return up.emit('up:modal:closed', {
8923
9032
  message: 'Modal closed'
8924
9033
  });
8925
9034
  });
8926
9035
  return promise;
8927
9036
  } else {
8928
- return u.unresolvableDeferred();
9037
+ return u.unresolvablePromise();
8929
9038
  }
8930
9039
  } else {
8931
- return u.resolvedDeferred();
9040
+ return u.resolvedPromise();
9041
+ }
9042
+ };
9043
+ markAsAnimating = function(state) {
9044
+ if (state == null) {
9045
+ state = true;
9046
+ }
9047
+ return $('.up-modal').toggleClass('up-modal-animating', state);
9048
+ };
9049
+ animate = function(viewportAnimation, backdropAnimation, animateOptions) {
9050
+ var promise;
9051
+ if (up.motion.isNone(viewportAnimation)) {
9052
+ return u.resolvedPromise();
9053
+ } else {
9054
+ markAsAnimating();
9055
+ promise = $.when(up.animate($('.up-modal-viewport'), viewportAnimation, animateOptions), up.animate($('.up-modal-backdrop'), backdropAnimation, animateOptions));
9056
+ promise = promise.then(function() {
9057
+ return markAsAnimating(false);
9058
+ });
9059
+ return promise;
8932
9060
  }
8933
9061
  };
8934
9062
 
@@ -8970,6 +9098,81 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8970
9098
  return $element.closest('.up-modal').length > 0;
8971
9099
  };
8972
9100
 
9101
+ /**
9102
+ Register a new modal variant with its own default configuration, CSS or HTML template.
9103
+
9104
+ \#\#\#\# Example
9105
+
9106
+ Let's implement a drawer that slides in from the right:
9107
+
9108
+ up.modal.flavor('drawer', {
9109
+ openAnimation: 'move-from-right',
9110
+ closeAnimation: 'move-to-right',
9111
+ maxWidth: 400
9112
+ }
9113
+
9114
+ Modals with that flavor will have a container `<div class='up-modal' up-flavor='drawer'>...</div>`.
9115
+ We can target the `up-flavor` attribute override the default dialog styles:
9116
+
9117
+ .up-modal[up-flavor='drawer'] {
9118
+
9119
+ // Align drawer on the right
9120
+ .up-modal-viewport { text-align: right; }
9121
+
9122
+ // Remove margin so the drawer starts at the screen edge
9123
+ .up-modal-dialog { margin: 0; }
9124
+
9125
+ // Stretch drawer background to full window height
9126
+ .up-modal-content { min-height: 100vh; }
9127
+ }
9128
+
9129
+ @function up.modal.flavor
9130
+ @param {String} name
9131
+ The name of the new flavor.
9132
+ @param {Object} [overrideConfig]
9133
+ An object whose properties override the defaults in [`/up.modal.config`](/up.modal.config).
9134
+ @experimental
9135
+ */
9136
+ flavor = function(name, overrideConfig) {
9137
+ if (overrideConfig == null) {
9138
+ overrideConfig = {};
9139
+ }
9140
+ return u.extend(flavorOverrides(name), overrideConfig);
9141
+ };
9142
+
9143
+ /**
9144
+ Returns a config object for the given flavor.
9145
+ Properties in that config should be preferred to the defaults in
9146
+ [`/up.modal.config`](/up.modal.config).
9147
+
9148
+ @function flavorOverrides
9149
+ @internal
9150
+ */
9151
+ flavorOverrides = function(flavor) {
9152
+ var base;
9153
+ return (base = config.flavors)[flavor] || (base[flavor] = {});
9154
+ };
9155
+
9156
+ /**
9157
+ Returns the config option for the current flavor.
9158
+
9159
+ @function flavorDefault
9160
+ @internal
9161
+ */
9162
+ flavorDefault = function(key, flavorName) {
9163
+ var value;
9164
+ if (flavorName == null) {
9165
+ flavorName = currentFlavor;
9166
+ }
9167
+ if (flavorName) {
9168
+ value = flavorOverrides(flavorName)[key];
9169
+ }
9170
+ if (u.isMissing(value)) {
9171
+ value = config[key];
9172
+ }
9173
+ return value;
9174
+ };
9175
+
8973
9176
  /**
8974
9177
  Clicking this link will load the destination via AJAX and open
8975
9178
  the given selector in a modal dialog.
@@ -8983,7 +9186,7 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8983
9186
  and place the matching `.blog-list` tag will be placed in
8984
9187
  a modal dialog.
8985
9188
 
8986
- @selector a[up-modal]
9189
+ @selector [up-modal]
8987
9190
  @param {String} [up-confirm]
8988
9191
  A message that will be displayed in a cancelable confirmation dialog
8989
9192
  before the modal is opened.
@@ -8997,11 +9200,12 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
8997
9200
  @param {String} [up-height]
8998
9201
  The width of the dialog in pixels.
8999
9202
  By [default](/up.modal.config) the dialog will grow to fit its contents.
9000
- @param [up-width]
9203
+ @param {String} [up-width]
9001
9204
  The width of the dialog in pixels.
9002
9205
  By [default](/up.modal.config) the dialog will grow to fit its contents.
9003
- @param [up-history="true"]
9206
+ @param {String} [up-history="true"]
9004
9207
  Whether to add a browser history entry for the modal's source URL.
9208
+
9005
9209
  @stable
9006
9210
  */
9007
9211
  up.link.onAction('[up-modal]', function($link) {
@@ -9069,7 +9273,8 @@ To disable this behavior, give the opening link an `up-sticky` attribute:
9069
9273
  source: function() {
9070
9274
  return up.error('up.modal.source no longer exists. Please use up.popup.url instead.');
9071
9275
  },
9072
- isOpen: isOpen
9276
+ isOpen: isOpen,
9277
+ flavor: flavor
9073
9278
  };
9074
9279
  })(jQuery);
9075
9280
 
@@ -9313,6 +9518,8 @@ by providing instant feedback for user interactions.
9313
9518
  */
9314
9519
 
9315
9520
  (function() {
9521
+ var slice = [].slice;
9522
+
9316
9523
  up.navigation = (function($) {
9317
9524
  var CLASS_ACTIVE, SELECTOR_SECTION, config, currentClass, findClickArea, locationChanged, markActive, normalizeUrl, reset, sectionUrls, u, unmarkActive, urlSet, withActiveMark;
9318
9525
  u = up.util;
@@ -9466,8 +9673,11 @@ by providing instant feedback for user interactions.
9466
9673
  $element = findClickArea(elementOrSelector, options);
9467
9674
  return $element.removeClass(CLASS_ACTIVE);
9468
9675
  };
9469
- withActiveMark = function(elementOrSelector, options, block) {
9470
- var $element, promise;
9676
+ withActiveMark = function() {
9677
+ var $element, args, block, elementOrSelector, options, promise;
9678
+ elementOrSelector = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
9679
+ block = args.pop();
9680
+ options = u.options(args.pop());
9471
9681
  $element = $(elementOrSelector);
9472
9682
  markActive($element, options);
9473
9683
  promise = block();