Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(deps): update dependency unpoly to v3 #195

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

renovate[bot]
Copy link
Contributor

@renovate renovate bot commented Apr 18, 2023

This PR contains the following updates:

Package Change Age Adoption Passing Confidence
unpoly (source) ^2.7.2 -> ^3.0.0 age adoption passing confidence

Release Notes

unpoly/unpoly (unpoly)

v3.9.3

Compare Source

This is a maintenance release that addresses some bugs while we're working on the next major feature update.

Changes
  • Fix an error being thrown when a caching request is tracking an existing request to the same URL, and that existing request responds with an error status (issue #​676).
  • Fix a bug where a modal overlay could not be closed when a child popup would be open below the screen fold.
  • Focus is no longer trapped in popup overlays. Focus remains trapped in all other overlay modes, but this can be disabled by setting up.layer.config.overlay.trapFocus = false.
  • The dismiss button in overlays now has a hand cursor (by @​apollo13).
  • Fix a bug where links with relative URLs were sometimes revalidated against the wrong base URL (issue #​669).

v3.9.2

Compare Source

  • Fix a bug where up:fragment:loaded listeners could not open a new layer by setting event.renderOptions.layer = "new".

v3.9.1

Compare Source

  • Fix a bug where any form[up-target] would receive a [role=button] attribute (issue #​668).

v3.9.0

Compare Source

This release brings many fixes and quality-of-life improvements that were requested by the community.

The vast majority of these changes are backward compatible. One breaking change can be found with making links followable. Existing usage is polyfilled by unpoly-migrate.js.

Emitting events on buttons
  • You can now use [up-emit] to emit an event when any element is clicked. In particular this works with a <button> or any faux-interactive element (issue #​416).
Improvements to faux-interactive elements

Sometimes you need to add a click listener to non-interactive elements (like <span>). Unpoly helps you prevent accessibility issues with such "faux-interactive" elements, by offering the [up-clickable] attribute and up.link.config.clickableSelectors configuration.
Unpoly also leverages this for its own faux-interactive elements, such as [up-emit] or [up-dismiss].
This release improves the handling of faux-interactive elements:

  • A new documentation guide Clicking non-interactive elements details all the methods to emulate interactivity on non-interactive elements like <span> or <div>.
  • You can now define exceptions to up.link.config.clickableSelectors, by setting an [up-clickable=false] attribute or configuring up.link.config.noClickableSelectors.
  • Adjusted the handling of keyboard input to better match the behavior of real buttons and links. In particular, faux-interactive elements with a button role (default) can be activated with both Space and Enter keys. Faux-interactive elements with a [role=link] can only be activated with the Enter key.
  • Faux-interactive elements that also have the [up-follow] attribute now default to [role=link] (instead of the default [role=button]).
  • Faux-interactive elements with a button role no longer have the "hand" (or "pointer") cursor.
  • Fix a bug where faux-interactive elements inside popups could not be activated with the keyboard (#​653).
Making links followable
  • Links with only an [up-href] attribute are no longer followable by default. They also require an [up-follow] attribute or a match in up.link.config.followSelectors. This change was made to remove confusion with other features that use [up-href], such as [up-defer] and (since this release) [up-poll].
  • Links with only an [up-instant] attribute are no longer followable by default. They also require an [up-follow] attribute or a match in up.link.config.followSelectors. This change was made to remove confusion with other features that use [up-instant], in particular up:click on faux-interactive elements.
Polling
  • Listeners to the up:fragment:poll event can now inspect or mutate event.renderOptions. This allows more control over the polling request and sub-sequent render passes.
  • [up-poll] elements can now use the [up-href] attribute to poll from a different URL. By default Unpoly will poll the URL from which the element was originally loaded. The old method over overriding [up-source] is still supported, but [up-href] is the preferred way of doing this going forward.
  • [up-poll] elements can now use the [up-method] attribute to choose a different HTTP method for polling requests.
  • [up-poll] elements can now use the [up-params] attribute to add custom params to polling requests.
  • [up-poll] elements can now use the [up-headers] attribute to add custom headers to polling requests.
Forms
  • Focus is now preserved when submitting a form by pressing Enter from a focused field (discussion #​658).
  • The up.submit() now includes the [name] and [value] of the default submit button in the submitted params. By default the form's first submit button will be assumed. You can prevent this with { submitButton: false }, or pass a different button element as { submitButton }.
  • Fix an interop issue with the Shoelace web component library, where a failed response could not be processed when the form was submitted with an <sl-button> (discussion #​643).
Smooth scrolling
Various
  • Fix: up-alias not matching URL query string with asterix after shash (#​542)
  • Fix a bug where an overlay with viewport would not correctly shift multiple right-fixed elements
  • [up-defer] elements no longer have a hand cursor
  • Events like up:link:follow can now open a layer with a given mode using the shorthand notation event.renderOptions.layer = "new drawer".
  • Avoid logging Uncaught AbortError when the user presses the back button, but a script prevents the up:location:restore event.
  • Aboid logging Uncaught AbortError when the user closes the overlay, but a script prevents the up:layer:dismiss or up:layer:accept event.
  • Reduce the number of layer lookups during a render pass.

v3.8.0

Compare Source

This release brings many improvements that were requested by the community.

The vast majority of these changes are backward compatible. Some breaking changes can be found with the Reworked style helpers. Existing calls are polyfilled by unpoly-migrate.js.

Lazy loading content

You can now lazy load additional fragments when a placeholder enters the DOM or viewport. By deferring the loading of non-critical fragments with a separate URL, you can paint important content earlier.

For example, you may have a large navigation menu that only appears once the user clicks a menu icon:

<div id="menu">
  Hundreds of links here
</div>

To remove the menu from the initial render pass, extract its contents to its own route, like /menu.

In the initial view, only leave a placeholder element and mark it with an [up-defer] attribute. Also set an [up-href] attribute with the URL from which to load the deferred content:

<div id="menu" up-defer up-href="/menu"> <!-- mark-phrase "up-defer" -->
  Loading...
</div>

When the [up-defer] placeholder is rendered, it will immediately make a request to fetch its content from /menu. You may also delay the request until the placeholder is scrolled into the viewport or control the timing from JavaScript.

See lazy loading content for a full example and more details.

Preloading links eagerly or lazily

For many years Unpoly has supported the [up-preload] attribute. This would preload a link when the user hovers over it:

<a href="/path" up-preload>Hover over me to preload my content</a>

You can now preload a link as soon as it appears in the DOM, by setting an [up-preload="insert"] attribute. This is useful for links with a high probability of being clicked, like a navigation menu:

<a href="/menu" up-layer="new drawer" up-preload="insert">≡ Menu</a> <!-- mark-phrase "insert" -->

To "lazy preload" a link when it is scrolled into the viewport, you can now set an [up-preload="reveal"] attribute. This is useful when an element is below the fold and is unlikely to be clicked until the the user scrolls:

<a href="/stories/106" up-preload="reveal">Full story</a> <!-- mark-phrase "reveal" -->
Infinite scrolling

Deferred fragments that load when revealed can implement infinite scrolling without custom JavaScript.

All you need is an HTML structure like this:

<div id="pages">
  <div class="page">items for page 1</div>
</div>

<a id="next-page" href="/items?page=2" up-defer="reveal" up-target="#next-page, #pages:after">
  load next page
</div>

See infinite scrolling for a full example and more details.

Enabling or disabling Unpoly features with boolean attributes

Most Unpoly attributes can now be enabled with a value "true" and be disabled with a value "false":

<a href="/path" up-follow="true">Click for single-page navigation</a> <!-- mark-phrase "true" -->
<a href="/path" up-follow="false">Click for full page load</a> <!-- mark-phrase "false" -->

Instead of setting a true you can also set an empty value:

<a href="/path" up-follow>Click for single-page navigation</a>
<a href="/path" up-follow="">Click for single-page navigation</a>
<a href="/path" up-follow="true">Click for single-page navigation</a>

Boolean values can be helpful with a server-side templating language like ERB, Liquid or Haml, when the attribute value is set from a boolean variable:

<a href="/path" up-follow="<%= is_signed_in %>">Click me</a> <%# mark-phrase "is_signed_in" %>

This can also help when you're generating HTML from a different programming language and want to pass a true literal as an attribute value:

link_to 'Click me', '/path', 'up-follow': true

This behavior is available for most attributes:

  • [up-follow]
  • [up-submit]
  • [up-instant]
  • [up-preload]
  • [up-nav]
  • [up-expand]
  • [up-keep]
  • [up-hungry]
  • [up-poll]
  • [up-defer]
  • [up-validate]
  • [up-autosubmit]
  • [up-watch]
Request batching

When queueing multiple requests to the same URL, Unpoly will now send a single request with a merged X-Up-Target header.

For example, these two render passes render different selectors from /path:

up.render('.foo', { url: '/path', cache: true })
up.render('.bar', { url: '/path', cache: true })

Unpoly will send a single request with both targets:

GET /path HTTP/1.1
X-Up-Target: .foo, .bar

This allows you to have multiple deferred placeholders that load from the same URL efficiently.

More cache hits for tailored responses

The following is a change for server routes that use the Vary header to optimize their responses to only include the requested X-Up-Target.

When requests target multiple fragments and the server responds with a Vary header, that response is now a cache hit for each individual selector:

🠦 X-Up-Target: .foo, .bar
🠤 Vary: X-Up-Target
🠦 X-Up-Target: .foo ✔️ cache hit
🠦 X-Up-Target: .bar ✔️ cache hit
🠦 X-Up-Target: .foo, .bar ✔️ cache hit
🠦 X-Up-Target: .bar, .foo ✔️ cache hit
🠦 X-Up-Target: .baz ❌ cache miss
🠦 X-Up-Target: .foo, .baz ❌ cache miss
🠦 No X-Up-Target ❌ cache miss

See how cache entries are matched for a detailed example.

Cached content is retained while offline

This release fixes some long-standing issues where the cache was evicted when a request failed due to network issues, or when the server responds with an empty response.

This fix restores the indented behavior that, even without a connection, cached content will remain navigatable for 90 minutes. This means that an offline user can instantly access pages that they already visited this session.

Quick access to the form element in form events

Form-related events like up:form:submit and up:form:validate are emitted on the element that caused the event. For example, up:form:submit is emitted on the submit button that was pressed.

This made it somewhat inconvenient to access the form element:

up.on('up:form:submit', function(event) {
  let form = event.target.closest('form')
  console.log("form is", form)
})

You can now access the form element through a { form } property on the event object:

up.on('up:form:submit', function({ form }) {
  console.log("form is", form)
})
Improvements to history restoration

Several improvements have been made to the way Unpoly handles the browser's "back" button.

Ensuring fresh content

In earlier versions, when the user pressed the back button, Unpoly would sometimes restore the page with stale content.

Starting with 3.8.0, restored content is now revalidated with the server. This ensures that content is shown with the most recent data.

Custom restoration behavior

Listeners to up:location:restore may now mutate the event.renderOptions event to customize the render pass that is about to restore content:

up.on('up:location:restore', function(event) {
  // Update a different fragment when restoring /special-path  
  if (event.location === '/special-path') {
    event.renderOptions.target = '#other'
  }
})

As a reminder, you can also completely substitute Unpoly's render pass with your own restoration behavior, by preventing up:location:restore. This will prevent Unpoly from changing any element. Your event handler can then restore the page with your own custom code:

up.on('up:location:restore', function(event) {
  // Stop Unpoly from rendering anything
  event.preventDefault()
  
  // We will render ourselves
  document.body.innerText = `Restored content for ${event.location}!`
})
Reworked style helpers

This release reworks all functions that work with CSS properties:

  • up.element.setStyle(element, props)
  • up.element.styleNumber(element, prop)
  • up.element.style(element, propOrProps)
  • up.element.createFromSelector(selector, { style })
  • up.element.affix(container, selector, { style })
  • up.animate(element, lastFrameProps)
Support for custom properties

All functions that work with CSS properties now also support custom properties ("CSS variables"):

// Returns the computed value of the `--custom-prop` property.
up.element.style(div, '--custom-prop')

// Sets the `--custom-prop` property as an inline `[style]` attribute
up.element.setStyle(div, { '--custom-prop': 'value' })
Property names must be in kebab-case

In earlier versions Unpoly functions accepted property names in either camelCase or kebab-case.

As custom properties don't have a camelCase equivalent, now only kebab-case is supported:

// ❌ camelCase property names are no longer supported
up.element.setStyle(div, { backgroundColor: 'red' })

// ✔️ Property names must now be in kebab-case
up.element.setStyle(div, { 'background-color': 'red' })

To help with upgrading, unpoly-migrate.js Unpoly will rename camelCase keys for you.

Length values must have a unit

CSS requires length values (like width, top or margin) to have a unit, e.g. width: 200px. In earlier versions Unpoly silently added a px unit to length values that were missing a unit.

This approach required Unpoly to keep a list of CSS properties that denote lengths, which was unsustainable. You now always need to pass length values with a unit:

// ❌ Length values without unit is uo longer supported
up.element.setStyle(div, { height: 50 })

// ✔️ Length values now require a unit
up.element.setStyle(div, { height: '50px' })

To help with upgrading, unpoly-migrate.js Unpoly will add px units to unit-less length values.

Rebrushed unpoly.com

The design of unpoly.com was reworked with fresh colors, better spacing and clearer fonts.

All documentation pages now have a table of contents to quickly find the section you're looking for.

Several new guides were also added:

Other changes
  • up.element.numberAttr() now parses negative numbers.
  • When updating history, the html[lang] is now also updated. This can be prevented by setting an [up-lang=false] attribute or passing a { lang: false } option.
  • The function up.util.microtask() was deprecated. Use the browser's built-in queueMicrotask() instead.
  • Right-anchored can now control their appearance while a scrolling overlay is open, by styling the .up-scrollbar-away class.
  • Fix a bug where the back button did not work after following a link that contains an anchor starting with a number (fixes #​603).
  • Clickable elements now get an ARIA role of button. In earlier versions these elements received a link link role.
  • Fix a bug where animating with { duration: 0 } would apply the default duration instead of skipping the animation (fixes #​588).
  • You can now exclude navigational containers from applying .up-current by adding a selector to up.feedback.config.noNavSelectors.

v3.7.3

Compare Source

  • Fix a bug where, when rendering multiple fragments from a cached response, the new fragments would not be revalidated.
    This also affected render passes with [up-hungry] fragments.
  • Targeting sibling elements now supports union selectors like .parent .foo, .parent .bar.

v3.7.2

Compare Source

Validation

This change addresses multiple edge cases with concurrent user input during form validations:

  • It is now possible to queue a validation for a fragment while a validation request for the same target is still loading.
  • Validations no longer throw an error if a targeted fragment is destroyed while a validation request is loading. Instead Unpoly will only update the fragments that are still present on the page (if any).
  • Validations are now aborted if the entire <form> element is aborted. Previously individual validations were aborted when their target was aborted.
  • up.validate() now rejects with an up.Aborted error if a debounce delay was aborted (by aborting the <form> element).
  • When a new validation is queued while a previous validation request is still loading, the full debounce delay of the new validation is now honored.
Autosubmit fixes

This change fixes two more regressions for [up-autosubmit], introduced by 3.7.0:

  • When the user changes a form field while a previous autosubmission is still loading, prevent that new change from being lost.
  • A debounce delay is now aborted if the entire <form> element is aborted. It no longer aborts the delay when the form's target is aborted.
Fragment API
  • Optional target selectors (with :maybe suffix) are now included in the X-Up-Target header if they match in the current page. Previously optional selector parts were always omitted from X-Up-Target.
  • The event up:fragment:aborted now has a new { reason } property. Its a value is a string describing the reason for the fragment being aborted.

v3.7.1

Compare Source

This change fixes two regressions for form field watchers, introduced by 3.7.0:

  • When a change is detected while waiting for an async callback, prevent the new callback from crashing with Cannot destructure property { disable } of null.
  • When a change is detected while waiting for an async callback, the full debounce delay of that new change is honored.

v3.7.0

Compare Source

Focus ring visibility

You can now control whether a focused fragment shows a visible focus ring.

Because Unpoly often focuses new content, you may see focus outline appear in unexpected places.
Focus rings are important for users of keyboards and screen readers to be able to orient themselves
as the focus moves on the page. However, mouse and touch users often dislike the visual effect of a focus ring.

To help your CSS show or hide focus rings in the right situation, Unpoly assigns CSS classes
to the elements it focuses:

  • If the user interacted with the keyboard or if the focused element is a form field, Unpoly will set
    an .up-focus-visible class.
  • If the user interacted with
    via mouse, touch or stylus, Unpoly will set an .up-focus-hidden class instead.

You can use these classes to hide unwanted focus rings, or style focus rings on new components.

The following supporting changes have also been made:

  • You can set up.viewport.config.autoFocusVisible to a function that decides if an element should get a .up-focus-visible or .up-focus-hidden class.
  • Added a new property up.event.inputDevice. Its value is a string describing the class of input device used for the current task.
  • Unpoly will try to force or unset :focus-visible as it sets focus classes, but can only do so in some browsers.
  • The up.focus() function accepts a new { focusVisible } option to control whether .up-focus-hidden or .up-focus-visible is set on a focused element.

See Focus ring visibility for more details and examples.

Reacting to form changes

This release addresses many edge cases with features that watch form fields for changes, in particular [up-watch], [up-autosubmit] and up.watch():

  • Watchers now detect changes in fields that were inserted dynamically later. This regression was introduced by Unpoly 3.0.
  • Watchers now detect changes when the form is reset.
  • Fix an issue where [up-autosubmit] would not work on forms that also have dependent fields using [up-validate].
  • Watchers no longer run callbacks if the form was aborted or detached while waiting for a previous async callback.
  • Watchers now abort their debounce delay if the entire form is aborted. Previously it would abort the delay if any watched field was aborted.
  • [up-autosubmit] now aborts a debounce delay if either the form element or the form's target are aborted. It no longer aborts the delay if any watched field is aborted.
Other changes
  • up.on() takes a { capture: true } option to register a listener that runs before the event is emitted on the element.
  • Scrolling now defaults to { behavior: 'instant' } to prevent picking up a scroll-behavior CSS property. To do pick up the property, pass { behavior: 'auto' }.
  • New function up.form.isField(). It returns whether the given element is a form field.

v3.6.1

Compare Source

v3.6.0

Compare Source

Targeting fragments
  • Unpoly-specific pseudo selectors like :main or :layer can now be used in a compound target, e.g. :main .child.
  • Targeting :main will no longer match in the region of the interaction origin). It will now always use the first matching selector in up.fragment.config.mainTargets.
  • Fix a bug where following a navigation item outside a main element would focus the <body> instead of the main element.
Performance improvements
  • Unpoy now uses the native :has() selector where available. Unpoly's polyfill for :has() will remain included for the time being. It will be removed as Firefox' :has() support has reached the majority of users (available on Nightly now).
  • Improve performance of many element lookups, by finding elements via CSS selectors (vs. filtering lists with JavaScript).
Support for structured data markup
  • Structured data in script[type="application/ld+json"] elements is considered a meta tag that will be updated with history changes.
  • script[type="application/ld+json"] elements in are now preserved in new fragments with up.fragment.config.runScripts = false.
Bugfixes and minor improvements
  • CSP nonces embedded into attribute callbacks now work with Content-Security-Policy-Report-Only.
  • When the X-Up-Validate header value exceeds 2048 characters, it is now set to :unknown.
    This is to prevent web infrastructure from rejecting an overly long request line with an 413 Entity Too Large error.
  • Fix a bug where up:assets:changed would be emitted for every response when configuring up.fragment.config.runScripts = false.
  • up.form.isSubmittable() now returns false for forms with a cross-origin URL in their [action] attribute.
  • up.util.contains() now works on NodeList objects.
  • You can now configure which elements are removed by up.fragment.config.runScripts = false.

v3.5.2

Compare Source

Continuing our focus on stability, this release addresses some long-standing issues:

  • Fix a bug where <video> and <audio> elements would render incorrectly in Safari (#​432).
  • Fix a bug where <script up-keep> elements would re-run during subsequent render passes.
  • Fix a bug where <script> elements would not run when targeted directly.
  • Fix a bug where <noscript up-keep> elements would not be persisted during fragment updated.
  • Fix a bug where <noscript> elements would lose their text content when targeted directly.

v3.5.1

Compare Source

This releases fixes two regressions introduced by 3.5.0:

  • Fix a bug where a new overlay would immediately close if the parent layer's location
    happened to match the overlay's location-based close condition.
  • When a new overlay's initial location matches its location-based close condition,
    the overlay again immediately closes without rendering its initial content.

v3.5.0

Compare Source

Unpoly 3.5 brings major quality-of-life improvements and addresses numerous edge cases in existing functionality.

Notification flashes

You can now use an [up-flashes] element to render confirmations, alerts or warnings:

A confirmation flash, an error flash and a warning flash{:width='480'}

To render a flash message, include an [up-flashes] element in your response.
The element's content should be the messages you want to render:

<div up-flashes>
  <strong>User was updated!</strong> <!-- mark-line -->
</div>

<main>
  Main response content ...
</main>

An [up-flashes] element comes with useful default behavior for rendering notifications:

  • Flashes will always be updated when rendering, even if they aren't targeted directly (like [up-hungry]).
  • Flashes are kept until new messages are rendered. They will not be cleared by an empty [up-flashes] container.
    You can use a compiler to clear messages after a delay.
  • You are free to place the flashes anywhere in your layout, inside or outside the main element you're usually updating.
  • You can have a single flashes container on your root layer, or one on each layer.
  • When a response causes an overlay to close, the flashes from the discarded response
    will be shown on a parent layer.

See notification flashes for more details and examples.

Detection of changed scripts and styles

Unpoly now detects changes in your JavaScripts and stylesheets after deploying a new version of your application.
While rendering new content, Unpoly compares script and style elements in the <head> and emits an up:assets:changed event if anything changed.

It is up to you to handle new frontend code revisions, e.g. by loading new assets or notifying the user:

Notification for a new app version{:width='305'}

See handling asset changes for more details and examples.

Automatic update of meta tags {#meta-tags}

Render passes that update history now synchronize meta tags in the <head>, such as meta[name=description] or link[rel=canonical].

In the document below, the highlighted elements will be updated when history is changed, in additional to the location URL:

<head>
  <title>AcmeCorp</title> <!-- mark-line -->
  <link rel="canonical" href="https://example.com/dresses/green-dresses"> <!-- mark-line -->
  <meta name="description" content="About the AcmeCorp team"> <!-- mark-line -->
  <meta prop="og:image" content="https://app.com/og.jpg"> <!-- mark-line -->
  <script src="/assets/app.js"></script>
  <link rel="stylesheet" href="/assets/app.css">  
</head>

The linked JavaScript and stylesheet are not part of history state and will not be updated.

Consistent behavior in overlays

Overlays with history now update meta tags when opening. When the overlay closes the parent layer's meta tags are restored.

Deprecating [up-hungry] in the <head>

Existing solutions using [up-hungry] to update meta tags can be removed from your application code.

Other than [up-hungry] the new implementation can deal with meta tags that only exist on some pages.

Opting in or out

See [up-meta] for ways to include or exclude head elements from synchronization.

You can disable the synchronization of meta tags globally or per render pass:

up.render('.element', { url: '/path', history: true, metaTags: false }) // mark-phrase "metaTags"
Forgiving error handling

In earlier versions, errors in user code would often crash Unpoly. This would sometimes leave the page in a corrupted state. For example,
a render pass would only update some fragments, fail to scroll, or fail to run destuctors.

This version changes how Unpoly handles exceptions thrown from user code, like compilers, transition functions or callbacks like { onAccepted }.

User errors are no longer thrown

Starting with this version, Unpoly functions generally succeed despite exceptions from user code.

The code below will successfully compile an element despite a broken compiler:

up.compiler('.element', () => { throw new Error('broken compiler') })
let element = up.element.affix(document.body, '.element')
up.hello(element) // no error is thrown

Instead an error event on window is emitted:

window.addEventListener('error', function(event) {
  alert("Got an error " + event.error.name)
})

This behavior is consistent with how the web platform handles errors in event listeners
and custom elements.

Debugging and testing

Exceptions in user code are also logged to the browser's error console.
This way you can still access the stack trace or detect JavaScript errors in E2E tests.

Some test runners like Jasmine already listen to the error event and fail your test if any uncaught exception is observed.
In Jasmine you may use jasmine.spyOnGlobalErrorsAsync() to make assertions on the unhandled error.

Hungry elements

Element with an [up-hungry] attribute are updated whenever the server
sends a matching element, even if the element isn't targeted.
This release addresses many issues and requests concerning hungry elements:

Conflict resolution

There is now defined behavior when multiple targets want to render the same new fragments from a server response:

  • When both a target selector and a hungry elements target the same fragment in the response, only the direct render target will be updated.
  • Hungry elements can be be nested. The outer element will be updated. Note that we recommend to not over-use the hungry mechanism, and prefer to explicit render targets instead.
Rendering in multiple layers

Many edge cases have been addressed for render passes that affect multiple layers:

  • When a server response reaches a close condition and causes an overlay to close,
    the discarded response can now be rendered into matching hungry elements on other layers.
  • When hungry elements on different layers target the same fragment in the response,
    the layer closest to the rendering layer will be chosen.
  • Hungry elements can use arbitrary layer references in [up-if-layer].
    For example, [up-if-layer="current child"] would only piggy-back on render passes for the current layer or its direct overlay.
More control over updates

You can now freely control when an hungry element is updated:

  • Before a hungry element is added to a render pass, a new event up:fragment:hungry is now emitted on the element.
    The event has properties for the old and new element, and information about the current render pass.

    You may prevent this event to exclude the hungry element from the render pass. Use this to define arbitrary conditions
    for when an hungry element should be updated:

    element.addEventListener('up:fragment:hungry', function(event) {
      if (event.newFragment.classList.contains('is-empty')) {
        console.log('Ignoring a fragment with an .is-empty class')
        event.preventDefault()
      }
    })
  • Hungry elements can now set an [up-on-hungry] attribute. It contains a code snippet that receives an up:fragment:hungry event.
    Calling event.preventDefault() will prevent the hungry fragment from being updated.

  • Deprecated the [up-if-history] modifier for hungry elements.

    This functionality is now covered by the more generic [up-on-hungry] attribute. Also its main use case was synchronizing meta tags,
    and that is now supported out of the box.

Animation

Some improvements have been to hungry elements with animated transitions:

  • Hungry elements can now control their transition using [up-duration] and [up-easing] attributes.
  • Hungry elements with transitions now delay the up.render().finished promise.
Polling

This release ships many improvements for the [up-poll] attribute.

Pausing and resuming

Unpoly has always paused polling when the user minimizes the window or switches to another tab.
This behavior has been improved by the following:

  • When at least one poll interval was spent paused in the background and the user then returns to the tab, Unpoly will now immediately reload the fragment.

    You can use this to load recent data when the user returns to your app after working on something else for a while. For example, the following
    would reload your main element after an absence of 5 minutes or more:

    <main up-poll up-interval="300_000">
      ...
    </main>
  • Polling now unschedules all JavaScript timers while polling is paused. This allows browser to keep the inactive window suspended, saving battery life.

Unpoly also pauses polling for fragments that are covered by an overlay. This behavior has been improved by the following:

  • When at least one poll interval was spent paused on a background layer and the layer is then brought to the front again,
    Unpoly will now immediately reload the fragment.
  • You can now keep polling on a background layer by setting an [up-if-layer="any"] attribute on an [up-poll] fragment.
  • Fix a bug where polling on a background layer would not resume when the layer was brought to the front again.
Disabling polling
  • The server can now stop polling by rendering a new fragment with [up-poll=false]. The previous method of omitting the [up-poll] attribute remains supported.
  • Deprecated the configuration up.radio.config.pollEnabled. To disable polling, prevent the up:fragment:poll event instead.
Rendering

Unpoly's rendering engine has been reworked to address many edge cases found in production use.

More practical callback order
  • Compilers now see updated navigation feedback for the current render pass. In particular .up-current classes are updated before compilers are called.
  • Hungry elements on other layers are now updated before { onAccepted } and { onDismissed } callbacks fire.
    This allows callbacks to observe all fragment changes made by a closing overlay.
Matching in destroyed elements

This release addresses many many errors when matching fragments in closed layers, detached elements or destroyed elements in their exit animation:

  • Rendering successful responses no longer crashes if a { failTarget } or { failLayer } cannot be resolved.
  • up.fragment.toTarget() no longer crashes when deriving targets for destroyed elements that are still in their exit animation.
  • Fragment lookup functions now crash with a better error message when the given { layer } does not exist or has been closed.
  • Revalidation now succeeds when the { failLayer } is no longer open.
General improvements
  • Unpoly now logs when rendering was aborted or threw an internal error.
  • Cache revalidation now updates the correct element when the initial render pass matched in the region of the clicked link and that link has since been detached.
  • Rendering no longer forces a full page load when the initial page was loaded with non-GET, but the render pass does not change history.
    This allows to use [up-validate] in forms that are not submitted through Unpoly.
  • Updates for [up-keep] no longer need to also be [up-keep]. You can prevent keeping by setting [up-keep=false]. This allows you to set [up-keep] via a macro.
  • Fix a bug where reloading a fragment that was rendered from local content would be reloaded from path "/true" (sic).
  • Fix a bug where, when revalidating a fallback target, we would log that we're "revalidating undefined"
Network quality is no longer measured

Previous versions of Unpoly adapted the behavior some features when it detected high latency or low network throughput.
Due to cross-browser support for the Network Information API,
measuring of network quality was removed:

  • Unpoly no longer doubles poll intervals on slow connections. The configuration up.radio.config.stretchPollInterval was removed.

  • Unpoly no longer prevents preloading on slow connections. The configuration up.link.config.preloadEnabled = 'auto' was removed.

    To disable preloading based on your own metrics, you can still prevent the up:link:preload event.

  • The configuration up.network.config.badDownlink was removed.

  • The configuration up.network.config.badRTT was removed.

  • The function up.network.shouldReduceRequests() was removed.

Unpoly retains all other functionality for dealing with network issues.

Fragment API
More control over region-aware fragment matching

When targeting fragments, Unpoly will prefer to
match fragments in the region of the user interaction. For example, when
a link's [up-target] could match multiple fragments, the fragment closest to the link is updated.
In cases where you don't want this behavior, you now have more options:

  • You can now disable region-aware fragment matching for individual function calls or elements:
    • Pass a { match: 'first' } option to any function that matches or renders a fragment.
    • Set an [up-match=first] option on a link or form that matches or renders a fragment.
  • The boolean configuration up.fragment.config.matchAroundOrigin has been replaced by up.fragment.config.match. Its values are 'region' (default) and 'first'.
General improvements
  • New experimental function up.fragment.contains(). It returns whether the given root matches or contains the given selector or element.

    Other than Element#contains() it only matches fragments on the same layer. It also ignores destroyed fragments in an exit animation.

  • The event up:fragment:keep received a new property { renderOptions }. It contains the render options for the current render pass.

  • The event up:fragment:aborted received new experimental property { newLayer }. It returns whether the fragment was aborted by a new overlay opening.

  • Many functions in the fragment API now also support a Document as the search root:

    • up.fragment.get()
    • up.fragment.all()
    • up.fragment.contains()
  • Passing an element to up.fragment.get() now returns that element unchanged.

Scripting
  • Destructors are now called with the element being destroyed.

    This allows you to reuse the same destructor function for multiple elements:

    let fn = (element) => console.log('Element %o was destroyed', element)
    
    for (let element of document.querySelector('div')) {
      up.destructor(element, fn)
    }  
  • Unpoly 3.0.0 introduced a third meta argument for compilers
    containing information about the current render pass:

    up.compiler('.user', function(element, data, meta) {
      console.log(meta.response.text.length)        // => 160232
      console.log(meta.response.header('X-Course')) // => "advanced-ruby"
      console.log(meta.layer.mode)                  // => "root"
      console.log(meta.revalidating)                // => boolean
    })

    Unfortunately we realized that access to the response this would to bad patterns where fragments would compile
    differently for the initial page load vs. subsequent fragment updates.

    In Unpoly 3.5 compilers can no longer access the current response via the { response } of that meta argument.
    The { layer } and { revalidating } property remains available.

  • The up.syntax package has been renamed to up.script.

Layers
  • You may now use a new layer reference subtree in your { layer } options or [up-layer] attributes.
    This matches fragments in either the current layer or its descendant overlays.
  • up.Layer objects now support a new method #subtree(). It returns an array of up.Layer containing this layer and its descendant overlays.
  • Fix a bug where the layer stack would sometimes be corrupted by after looking up ancestors or descendants.
  • Fix a visual issue where, when fixed elements were created after an overlay was opened, the fixed element would be position too far to the right.
Links
  • The up:link:preload event received a new property { renderOptions }. It contains the render options for the current render pass.
  • The [up-on-offline] attribute now supports a CSP nonce.
  • The function up.link.followOptions() now takes an Object as a second argument. It will override any options parsed from the link attributes.
  • The configuration up.link.config.preloadEnabled was deprecated. To disable preloading, prevent up:link:preload.
DOM helpers
  • A new experimental function up.element.isEmpty() was added. It returns whether an element has neither child elements nor non-whitespace text.
Viewports
  • Renamed configuration up.viewport.config.anchoredRight to up.viewport.config.anchoredRightSelectors
  • Renamed configuration up.viewport.config.fixedTop to up.viewport.config.fixedTopSelectors
  • Renamed configuration up.viewport.config.fixedBottom to up.viewport.config.fixedBottomSelectors
unpoly-migrate.js
  • The polyfills for the up.element.isAttached() and up.element.isDetached() functions were changed so they behave
    like their implementation in Unpoly 2.x. In particular the functions now only consider attachment in window.document, but not to other Document instances.
Build
  • unpoly.js is now compiled using ES2021 (up from ES2020). The ES6 build for legacy browsers remains available.

  • Improve compression of minified builds. In particular private object properties are now prefixed with an underscore (_) so they can be mangled safely.

    If you are re-bundling the unminified build of Unpoly you can configure your minifier
    to do the same.

v3.3.0

Compare Source

Elements with an [up-hungry] attribute are updated whenever the server sends a matching element, even if the element isn't targeted explicitly.

By default hungry elements only update from responses that target their own layer. Unpoly 3.0 introduced a modifying attribute [up-if-layer="any"] that tells the element to also update from responses from other layers. Unpoly 3.3.0 addresses two edge cases:

  • Hungry elements with [up-if-layer="any"] are also updated from responses that open an overlay.
  • Hungry elements with [up-if-layer="any"] are also updated from responses that cause an overlay to close.

v3.2.2

Compare Source

  • Fix a bug where rendering on the root layer while a focused overlay is closing would crash with an error like this:

    up.Error: Must pass an up.Layer as { layer } option, but got undefined
    

v3.2.1

Compare Source

This is a bugfix release with many contributions from the community.

  • Click event handlers added via up.on() no longer fire when clicking on a child of a disabled button. By @​adam12.
  • Fix a crash when targeting elements with class names containing special characters, e.g. dynamic Tailwind CSS classes. By @​adam12.
  • Submit buttons outside a form are now included in the request params. By @​mordae.
  • Documentation for URL patterns has been expanded with many examples. By @​jmoppel.
  • Fix a bug where forms with a field named "contains" could not be submitted. By @​adam12.
  • Fix a bug where the { location } property of the up:location:changed would sometimes be Location object instead of a string. By @​triskweline.
  • When layers are closed during a fragment update, Unpoly no longer adds a history entry for the revealed layer. By @​triskweline.
  • Animations that fly in an element from the screen edge (move-from-top, move-from-left, etc.) no longer leave a transform style on the animated element. By @​triskweline.
  • New experimental option { history: false } for all functions that close layers. This prevents Unpoly from restoring history from the revealed parent layer. By @​triskweline.
  • To help with future contributions to Unpoly, development dependencies were upgraded to Jasmine 5, TypeScript 5, and Node.js 20. By @​triskweline.

v3.2.0

Compare Source

Addressing an important caching issue

Unpoly 3.2.0 no longer cache responses with an empty body (fixes #​497). In particular responses with 304 Not Modified are no longer cached when using conditional requests.

As this issue could cause errors when rendering, we recommend all Unpoly 3 users to upgrade.

Using the server response that closed an overlay

When an overlay closes in reaction to a server response, no content from that response is rendered.

Sometimes you do need to access the discarded response, e.g. to render its content in another layer.
For this you can now access response via the { response } property of the up:layer:accepted and up:layer:dismissed events.

For example, the link link opens an overlay with a form to create a new company (/companies/new).
After successful creation the form redirects to the list of companies (/companies). In that case
we can use the HTML from the response and render it into the parent layer:

<a href="/companies/new"
   up-layer="new"
   up-accept-location="/companies"
   up-on-accepted="up.render('.companies', { response: event.response }"> <!-- mark-phrase "event.response" -->
  New company
</a>

The { response } property is available whenever a server response causes an overlay to close:

Rendering up.Response objects

If you have manually fetched content from the server, you can now pass an up.Response object as a { response } option to render its contents:

let response = await up.request('/path')
up.render({ target: '.target', response })

The various ways to provide HTML to rendering functions are now summarized on a new documentation page.

Other changes
  • You can now use [up-href] without also setting [up-follow] or [up-target] (fixes #​489).

  • Date inputs are again validated on change instead of blur.

    In Unpoly 3.0 this defaulted to blur because desktop date pickers emit a change


Configuration

📅 Schedule: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

0 participants