unpoly-rails 0.24.1 → 0.25.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 (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();