unpoly-rails 2.4.1 → 2.5.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.

@@ -8,7 +8,7 @@
8
8
  @module up
9
9
  */
10
10
  window.up = {
11
- version: '2.4.1'
11
+ version: '2.5.0'
12
12
  };
13
13
 
14
14
 
@@ -2963,8 +2963,12 @@ up.element = (function () {
2963
2963
  // innerHTML on Chrome. See https://jsben.ch/QQngJ
2964
2964
  const range = document.createRange();
2965
2965
  range.setStart(document.body, 0);
2966
- const fragment = range.createContextualFragment(html);
2967
- return fragment.childNodes[0];
2966
+ const fragment = range.createContextualFragment(html.trim());
2967
+ let elements = fragment.childNodes;
2968
+ if (elements.length !== 1) {
2969
+ throw new Error('HTML must have a single root element');
2970
+ }
2971
+ return elements[0];
2968
2972
  }
2969
2973
  /*-
2970
2974
  @function up.element.root
@@ -4341,6 +4345,9 @@ up.Change.UpdateLayer = class UpdateLayer extends up.Change.Addition {
4341
4345
  const lookupOpts = { layer: this.layer, origin: oldElement };
4342
4346
  let partner;
4343
4347
  if (options.descendantsOnly) {
4348
+ // Since newElement is from a freshly parsed HTML document, we could use
4349
+ // up.element functions to match the selector. However, since we also want
4350
+ // to use custom selectors like ":main" or "&" we use up.fragment.get().
4344
4351
  partner = up.fragment.get(newElement, partnerSelector, lookupOpts);
4345
4352
  }
4346
4353
  else {
@@ -4828,11 +4835,11 @@ up.Change.FromURL = class FromURL extends up.Change {
4828
4835
  }
4829
4836
  else {
4830
4837
  const log = ['Loaded fragment from failed response to %s (HTTP %d)', this.request.description, this.response.status];
4838
+ // Although updateContentFromResponse() will fulfill with a successful replacement of options.failTarget,
4839
+ // we still want to reject the promise that's returned to our API client. Hence we throw.
4831
4840
  throw this.updateContentFromResponse(log, this.failOptions);
4832
4841
  }
4833
4842
  }
4834
- // Although processResponse() will fulfill with a successful replacement of options.failTarget,
4835
- // we still want to reject the promise that's returned to our API client.
4836
4843
  isSuccessfulResponse() {
4837
4844
  return (this.successOptions.fail === false) || this.response.ok;
4838
4845
  }
@@ -10311,7 +10318,7 @@ const u = up.util;
10311
10318
  up.store || (up.store = {});
10312
10319
  up.store.Memory = class Memory {
10313
10320
  constructor() {
10314
- this.clear();
10321
+ this.data = {};
10315
10322
  }
10316
10323
  clear() {
10317
10324
  this.data = {};
@@ -10620,7 +10627,7 @@ up.URLPattern = class URLPattern {
10620
10627
  return '([^/?#]+)';
10621
10628
  }
10622
10629
  });
10623
- return new RegExp('^' + reCode + '$');
10630
+ return new RegExp('^(?:' + reCode + ')$');
10624
10631
  }
10625
10632
  // This method is performance-sensitive. It's called for every link in an [up-nav]
10626
10633
  // after every fragment update.
@@ -12261,11 +12268,14 @@ up.log = (function () {
12261
12268
  Note that errors will always be printed, regardless of this setting.
12262
12269
  @param {boolean} [config.banner=true]
12263
12270
  Print the Unpoly banner to the developer console.
12271
+ @param {boolean} [config.format=!isIE11]
12272
+ Format output using CSS.
12264
12273
  @stable
12265
12274
  */
12266
12275
  const config = new up.Config(() => ({
12267
12276
  enabled: sessionStore.get('enabled'),
12268
- banner: true
12277
+ banner: true,
12278
+ format: up.browser.canFormatLog()
12269
12279
  }));
12270
12280
  function reset() {
12271
12281
  config.reset();
@@ -12306,7 +12316,7 @@ up.log = (function () {
12306
12316
  const printToError = (...args) => printToStream('error', ...args);
12307
12317
  function printToStream(stream, trace, message, ...args) {
12308
12318
  if (message) {
12309
- if (up.browser.canFormatLog()) {
12319
+ if (config.format) {
12310
12320
  args.unshift(''); // Reset
12311
12321
  args.unshift('color: #666666; padding: 1px 3px; border: 1px solid #bbbbbb; border-radius: 2px; font-size: 90%; display: inline-block');
12312
12322
  message = `%c${trace}%c ${message}`;
@@ -12337,7 +12347,7 @@ up.log = (function () {
12337
12347
  text += "Call `up.log.enable()` to enable logging for this session.";
12338
12348
  }
12339
12349
  const color = 'color: #777777';
12340
- if (up.browser.canFormatLog()) {
12350
+ if (config.format) {
12341
12351
  console.log('%c' + logo + '%c' + text, 'font-family: monospace;' + color, color);
12342
12352
  }
12343
12353
  else {
@@ -12976,9 +12986,11 @@ up.history = (function () {
12976
12986
 
12977
12987
  @property up.history.config
12978
12988
  @param {Array} [config.restoreTargets=[]]
12979
- A list of possible CSS selectors to [replace](/up.render) when the user goes back in history.
12989
+ A list of possible CSS selectors to [replace](/up.render) when the user goes back or forward in history.
12990
+
12991
+ If more than one target is configured, the first selector matching both the current page and server response will be updated.
12980
12992
 
12981
- By default the [root layer's main target](/up.fragment.config#config.mainTargets).
12993
+ If nothing is configured, the `<body>` element will be replaced.
12982
12994
  @param {boolean} [config.enabled=true]
12983
12995
  Defines whether [fragment updates](/up.render) will update the browser's current URL.
12984
12996
 
@@ -13196,7 +13208,6 @@ up.history = (function () {
13196
13208
  layer: 'root',
13197
13209
  target: config.restoreTargets,
13198
13210
  cache: true,
13199
- keep: false,
13200
13211
  scroll: 'restore',
13201
13212
  // Since the URL was already changed by the browser, don't save scroll state.
13202
13213
  saveScroll: false
@@ -13342,7 +13353,8 @@ up.fragment = (function () {
13342
13353
  @param {Array<string>} [config.mainTargets=['[up-main]', 'main', ':layer']]
13343
13354
  An array of CSS selectors matching default render targets.
13344
13355
 
13345
- When no other render target is given, Unpoly will try to find and replace a main target.
13356
+ When no other render target is given, Unpoly will update the first selector matching both
13357
+ the current page and the server response.
13346
13358
 
13347
13359
  When [navigating](/navigation) to a main target, Unpoly will automatically
13348
13360
  [reset scroll positions](/scroll-option) and
@@ -16087,7 +16099,6 @@ up.motion = (function () {
16087
16099
 
16088
16100
  ```js
16089
16101
  up.animate('.warning', 'fade-in', {
16090
- delay: 1000,
16091
16102
  duration: 250,
16092
16103
  easing: 'linear'
16093
16104
  })
@@ -18085,7 +18096,7 @@ up.layer = (function () {
18085
18096
  @stable
18086
18097
  */
18087
18098
  /*-
18088
- This event is emitted after a new overlay has been placed into the DOM.
18099
+ This event is emitted after a new overlay was placed into the DOM.
18089
18100
 
18090
18101
  The event is emitted right before the opening animation starts. Because the overlay
18091
18102
  has not been rendered by the browser, this makes it a good occasion to
@@ -18172,7 +18183,7 @@ up.layer = (function () {
18172
18183
  }
18173
18184
  }
18174
18185
  /*-
18175
- [Follows](/a-up-follow) this link and opens the result in a new overlay.
18186
+ [Follows](/a-up-follow) this link and [opens the result in a new overlay](/opening-overlays).
18176
18187
 
18177
18188
  ### Example
18178
18189
 
@@ -18551,6 +18562,34 @@ up.layer = (function () {
18551
18562
  @stable
18552
18563
  */
18553
18564
  /*-
18565
+ This event is emitted before a layer is [accepted](/closing-overlays).
18566
+
18567
+ The event is emitted on the [element of the layer](/up.layer.element) that is about to close.
18568
+
18569
+ @event up:layer:accept
18570
+ @param {up.Layer} event.layer
18571
+ The layer that is about to close.
18572
+ @param {Element} [event.origin]
18573
+ The element that is causing the layer to close.
18574
+ @param event.preventDefault()
18575
+ Event listeners may call this method to prevent the overlay from closing.
18576
+ @stable
18577
+ */
18578
+ /*-
18579
+ This event is emitted after a layer was [accepted](/closing-overlays).
18580
+
18581
+ The event is emitted on the [layer's](/up.layer.element) when the close animation
18582
+ is starting. If the layer has no close animaton and was already removed from the DOM,
18583
+ the event is emitted a second time on the `document`.
18584
+
18585
+ @event up:layer:accepted
18586
+ @param {up.Layer} event.layer
18587
+ The layer that was closed.
18588
+ @param {Element} [event.origin]
18589
+ The element that has caused the layer to close.
18590
+ @stable
18591
+ */
18592
+ /*-
18554
18593
  [Dismisses](/closing-overlays) the [current layer](/up.layer.current).
18555
18594
 
18556
18595
  This is a shortcut for `up.layer.current.dismiss()`.
@@ -18562,6 +18601,34 @@ up.layer = (function () {
18562
18601
  @stable
18563
18602
  */
18564
18603
  /*-
18604
+ This event is emitted before a layer is [dismissed](/closing-overlays).
18605
+
18606
+ The event is emitted on the [element of the layer](/up.layer.element) that is about to close.
18607
+
18608
+ @event up:layer:dismiss
18609
+ @param {up.Layer} event.layer
18610
+ The layer that is about to close.
18611
+ @param {Element} [event.origin]
18612
+ The element that is causing the layer to close.
18613
+ @param event.preventDefault()
18614
+ Event listeners may call this method to prevent the overlay from closing.
18615
+ @stable
18616
+ */
18617
+ /*-
18618
+ This event is emitted after a layer was [dismissed](/closing-overlays).
18619
+
18620
+ The event is emitted on the [layer's](/up.layer.element) when the close animation
18621
+ is starting. If the layer has no close animaton and was already removed from the DOM,
18622
+ the event is emitted a second time on the `document`.
18623
+
18624
+ @event up:layer:dismissed
18625
+ @param {up.Layer} event.layer
18626
+ The layer that was closed.
18627
+ @param {Element} [event.origin]
18628
+ The element that has caused the layer to close.
18629
+ @stable
18630
+ */
18631
+ /*-
18565
18632
  Returns whether the [current layer](/up.layer.current) is the [root layer](/up.layer.root).
18566
18633
 
18567
18634
  This is a shortcut for `up.layer.current.isRoot()`.
@@ -19119,6 +19186,7 @@ up.link = (function () {
19119
19186
  function followOptions(link, options) {
19120
19187
  // If passed a selector, up.fragment.get() will prefer a match on the current layer.
19121
19188
  link = up.fragment.get(link);
19189
+ // Request options
19122
19190
  options = parseRequestOptions(link, options);
19123
19191
  const parser = new up.OptionsParser(options, link, { fail: true });
19124
19192
  // Feedback options
@@ -19335,7 +19403,8 @@ up.link = (function () {
19335
19403
  }
19336
19404
  e.setMissingAttrs(link, {
19337
19405
  tabindex: '0',
19338
- role: 'link' // Make screen readers pronounce "link"
19406
+ role: 'link',
19407
+ 'up-clickable': '' // Get pointer pointer from link.css
19339
19408
  });
19340
19409
  link.addEventListener('keydown', function (event) {
19341
19410
  if ((event.key === 'Enter') || (event.key === 'Space')) {
@@ -19866,6 +19935,10 @@ up.link = (function () {
19866
19935
  }
19867
19936
  e.setMissingAttrs(area, areaAttrs);
19868
19937
  makeFollowable(area);
19938
+ // We could also consider making the area clickable, via makeClickable().
19939
+ // However, since the original link is already present within the area,
19940
+ // we would not add accessibility benefits. We might also confuse screen readers
19941
+ // with a nested link.
19869
19942
  }
19870
19943
  });
19871
19944
  /*-
@@ -20140,21 +20213,40 @@ up.form = (function () {
20140
20213
  @stable
20141
20214
  */
20142
20215
  function submitOptions(form, options) {
20216
+ form = getForm(form);
20217
+ options = parseBasicOptions(form, options);
20218
+ let parser = new up.OptionsParser(options, form);
20219
+ parser.string('failTarget', { default: up.fragment.toTarget(form) });
20220
+ // The guardEvent will also be assigned an { renderOptions } property in up.render()
20221
+ options.guardEvent || (options.guardEvent = up.event.build('up:form:submit', {
20222
+ submitButton: options.submitButton,
20223
+ params: options.params,
20224
+ log: 'Submitting form'
20225
+ }));
20226
+ // Now that we have extracted everything form-specific into options, we can call
20227
+ // up.link.followOptions(). This will also parse the myriads of other options
20228
+ // that are possible on both <form> and <a> elements.
20229
+ u.assign(options, up.link.followOptions(form, options));
20230
+ return options;
20231
+ }
20232
+ // This was extracted from submitOptions().
20233
+ // Validation needs to submit a form without options intended for the final submission,
20234
+ // like [up-scroll], [up-confirm], etc.
20235
+ function parseBasicOptions(form, options) {
20143
20236
  options = u.options(options);
20144
- form = up.fragment.get(form);
20145
- form = e.closest(form, 'form');
20237
+ form = getForm(form);
20146
20238
  const parser = new up.OptionsParser(options, form);
20147
20239
  // Parse params from form fields.
20148
20240
  const params = up.Params.fromForm(form);
20149
- let submitButton = submittingButton(form);
20150
- if (submitButton) {
20241
+ options.submitButton || (options.submitButton = submittingButton(form));
20242
+ if (options.submitButton) {
20151
20243
  // Submit buttons with a [name] attribute will add to the params.
20152
20244
  // Note that addField() will only add an entry if the given button has a [name] attribute.
20153
- params.addField(submitButton);
20245
+ params.addField(options.submitButton);
20154
20246
  // Submit buttons may have [formmethod] and [formaction] attribute
20155
20247
  // that override [method] and [action] attribute from the <form> element.
20156
- options.method || (options.method = submitButton.getAttribute('formmethod'));
20157
- options.url || (options.url = submitButton.getAttribute('formaction'));
20248
+ options.method || (options.method = options.submitButton.getAttribute('formmethod'));
20249
+ options.url || (options.url = options.submitButton.getAttribute('formaction'));
20158
20250
  }
20159
20251
  params.addAll(options.params);
20160
20252
  options.params = params;
@@ -20171,13 +20263,6 @@ up.form = (function () {
20171
20263
  // a demo of vanilla browser behavior.
20172
20264
  options.url = up.Params.stripURL(options.url);
20173
20265
  }
20174
- parser.string('failTarget', { default: up.fragment.toTarget(form) });
20175
- // The guardEvent will also be assigned an { renderOptions } property in up.render()
20176
- options.guardEvent || (options.guardEvent = up.event.build('up:form:submit', { log: 'Submitting form' }));
20177
- // Now that we have extracted everything form-specific into options, we can call
20178
- // up.link.followOptions(). This will also parse the myriads of other options
20179
- // that are possible on both <form> and <a> elements.
20180
- u.assign(options, up.link.followOptions(form, options));
20181
20266
  return options;
20182
20267
  }
20183
20268
  /*-
@@ -20204,8 +20289,12 @@ up.form = (function () {
20204
20289
  @event up:form:submit
20205
20290
  @param {Element} event.target
20206
20291
  The `<form>` element that will be submitted.
20292
+ @param {up.Params} event.params
20293
+ The [form parameters](/up.Params) that will be send as the form's request payload.
20294
+ @param {Element} [event.submitButton]
20295
+ The button used to submit the form.
20207
20296
  @param {Object} event.renderOptions
20208
- An object with [render options](/up.render) for the fragment update
20297
+ An object with [render options](/up.render) for the fragment update.
20209
20298
 
20210
20299
  Listeners may inspect and modify these options.
20211
20300
  @param event.preventDefault()
@@ -20218,7 +20307,7 @@ up.form = (function () {
20218
20307
  up.on('up:click', submitButtonSelector, function (event, button) {
20219
20308
  // Don't mess with focus unless we know that we're going to handle the form.
20220
20309
  // https://groups.google.com/g/unpoly/c/wsiATxepVZk
20221
- const form = e.closest(button, 'form');
20310
+ const form = getForm(button);
20222
20311
  if (form && isSubmittable(form)) {
20223
20312
  button.focus();
20224
20313
  }
@@ -20287,7 +20376,7 @@ up.form = (function () {
20287
20376
  A destructor function that removes the observe watch when called.
20288
20377
  @stable
20289
20378
  */
20290
- const observe = function (elements, ...args) {
20379
+ function observe(elements, ...args) {
20291
20380
  elements = e.list(elements);
20292
20381
  const fields = u.flatMap(elements, findFields);
20293
20382
  const unnamedFields = u.reject(fields, 'name');
@@ -20305,7 +20394,7 @@ up.form = (function () {
20305
20394
  const observer = new up.FieldObserver(fields, options, callback);
20306
20395
  observer.start();
20307
20396
  return () => observer.stop();
20308
- };
20397
+ }
20309
20398
  function observeCallbackFromElement(element) {
20310
20399
  let rawCallback = element.getAttribute('up-observe');
20311
20400
  if (rawCallback) {
@@ -20399,10 +20488,8 @@ up.form = (function () {
20399
20488
  function validate(field, options) {
20400
20489
  // If passed a selector, up.fragment.get() will prefer a match on the current layer.
20401
20490
  field = up.fragment.get(field);
20402
- options = u.options(options);
20403
- options.navigate = false;
20491
+ options = parseBasicOptions(field, options);
20404
20492
  options.origin = field;
20405
- options.history = false;
20406
20493
  options.target = findValidateTarget(field, options);
20407
20494
  options.focus = 'keep';
20408
20495
  // The protocol doesn't define whether the validation results in a status code.
@@ -20414,7 +20501,7 @@ up.form = (function () {
20414
20501
  options.headers[up.protocol.headerize('validate')] = field.getAttribute('name') || ':unknown';
20415
20502
  // The guardEvent will also be assigned a { renderOptions } attribute in up.render()
20416
20503
  options.guardEvent = up.event.build('up:form:validate', { field, log: 'Validating form' });
20417
- return submit(field, options);
20504
+ return up.render(options);
20418
20505
  }
20419
20506
  /*-
20420
20507
  This event is emitted before a field is being [validated](/input-up-validate).
@@ -20514,7 +20601,7 @@ up.form = (function () {
20514
20601
  show = u.intersect(fieldValues, showValues).length > 0;
20515
20602
  }
20516
20603
  e.toggle(target, show);
20517
- return target.classList.add('up-switched');
20604
+ target.classList.add('up-switched');
20518
20605
  });
20519
20606
  function findSwitcherForTarget(target) {
20520
20607
  const form = getContainer(target);
@@ -20525,9 +20612,13 @@ up.form = (function () {
20525
20612
  });
20526
20613
  return switcher || up.fail('Could not find [up-switch] field for %o', target);
20527
20614
  }
20528
- function getContainer(element) {
20615
+ function getForm(elementOrTarget, fallbackSelector) {
20616
+ const element = up.fragment.get(elementOrTarget);
20529
20617
  // Element#form will also work if the element is outside the form with an [form=form-id] attribute
20530
- return element.form || e.closest(element, `form, ${up.layer.anySelector()}`);
20618
+ return element.form || e.closest(element, 'form') || (fallbackSelector && e.closest(element, fallbackSelector));
20619
+ }
20620
+ function getContainer(element) {
20621
+ return getForm(element, up.layer.anySelector());
20531
20622
  }
20532
20623
  function isField(element) {
20533
20624
  return e.matches(element, fieldSelector());
@@ -6,5 +6,5 @@ up-bounds{position:absolute}.up-focusable-content:focus,.up-focusable-content:fo
6
6
 
7
7
  up-focus-trap{position:fixed;top:0;left:0;width:0;height:0}up-modal,up-drawer,up-cover,up-modal-backdrop,up-drawer-backdrop,up-modal-viewport,up-drawer-viewport,up-cover-viewport{top:0;left:0;bottom:0;right:0}up-modal-box,up-drawer-box{box-shadow:0 0 10px 1px rgba(0,0,0,0.3)}up-popup{box-shadow:0 0 4px rgba(0,0,0,0.3)}up-modal:focus,up-drawer:focus,up-cover:focus,up-modal-box:focus,up-drawer-box:focus,up-cover-box:focus,up-popup:focus,up-modal:focus-visible,up-drawer:focus-visible,up-cover:focus-visible,up-modal-box:focus-visible,up-drawer-box:focus-visible,up-cover-box:focus-visible,up-popup:focus-visible{outline:none}up-modal,up-drawer,up-cover{z-index:2000;position:fixed}up-modal-backdrop,up-drawer-backdrop{position:absolute;background:rgba(0,0,0,0.4)}up-modal-viewport,up-drawer-viewport,up-cover-viewport{position:absolute;overflow-y:scroll;overflow-x:hidden;overscroll-behavior:contain;text-align:center}up-modal-box,up-drawer-box,up-cover-box,up-popup{display:inline-block;text-align:left;position:relative;box-sizing:border-box;max-width:100%;background-color:#fff;padding:20px;overflow-x:hidden}up-modal-content,up-drawer-content,up-cover-content,up-popup-content{display:block}up-popup{z-index:1000}up-modal-dismiss,up-drawer-dismiss,up-cover-dismiss,up-popup-dismiss{color:#888;position:absolute;top:10px;right:10px;font-size:1.7rem;line-height:0.5}up-modal-viewport{justify-content:center}up-modal[nesting="0"] up-modal-viewport{padding:25px 15px}up-modal[nesting="1"] up-modal-viewport{padding:50px 30px}up-modal[nesting="2"] up-modal-viewport{padding:75px 45px}up-modal[nesting="3"] up-modal-viewport{padding:100px 60px}up-modal[nesting="4"] up-modal-viewport{padding:125px 75px}up-modal[size=small] up-modal-box{width:350px}up-modal[size=medium] up-modal-box{width:650px}up-modal[size=large] up-modal-box{width:1000px}up-modal[size=grow] up-modal-box{width:auto}up-modal[size=full] up-modal-box{width:100%}up-drawer-viewport{text-align:left}up-drawer[position=right] up-drawer-viewport{text-align:right}up-drawer-box{min-height:100vh}up-drawer[size=small] up-drawer-box{width:150px}up-drawer[size=medium] up-drawer-box{width:340px}up-drawer[size=large] up-drawer-box{width:600px}up-drawer[size=grow] up-drawer-box{width:auto}up-drawer[size=full] up-drawer-box{width:100%}up-cover-box{width:100%;min-height:100vh;padding:0}up-popup{padding:15px}up-popup[size=small]{width:180px}up-popup[size=medium]{width:300px}up-popup[size=large]{width:550px}up-popup[size=grow] up-popup{width:auto}up-popup[size=full] up-popup{width:100%}
8
8
 
9
- a[up-content],a[up-fragment],a[up-document],[up-href],[up-clickable],[up-accept],[up-dismiss]{cursor:pointer}
9
+ [up-href],[up-clickable]{cursor:pointer}
10
10