- Focus on removal and simplification.
- Use browser-native functionality where possible.
- Focus on cleaning up mistakes.
- Don't do [up-frame] in V4. Users deserve a break from disrupting API changes.
- But do consider [up-zone], maybe even V3!
- Can we replace the "validate" terminology with a more generic "redraw" or similiar?
- X-Up-Validate should use comma-separated string
- For up:request:loaded, remove { layer, origin } properties
- Clean up history terminology
- up:location:changed props
- { reason: 'push' } => { direction: 'advance' }
- { reason: 'replace' } => { direction: 'stay' }
- { reason: 'pop' } => { direction: 'restore' }
- up.history.push() => up.history.advance()
- up:location:changed props
- Be more explicit about request props
- request.layer => request.renderLayer
- request.mode => request.renderLayerMode, X-Up-Layer-Mode
- Remove remaining jQuery support
- Consider moving .up-scrollbar-away to html instead of body (v4?)
- Maybe get rid of up.off() and unsubscribe using { signal }
- Replace usage of e.paint() with @starting-style
- Consider replacing DOMParser with Document.parseHTMLUnsafe()
- This may have solved edge case
- This can expand declarative custom elements
- Move hungry elements with moveBefore()
- chrome://flags/#atomic-move
- whatwg/dom#1255
- Can we use [inert] for focus trapping?
- Is
scrollbar-gutter: stable
a replacement for up.BodyShifter? - Drop
{ batch: true }
option forup.watch()
- Rework and simplify form data parsing
- Goals
- Delete our own form data parsing in up.Params.fromFields() and .fromContainer()
- Support custom form elements using the
formdata
event - Support custom form elements using
ElementInternals
- Maybe we could replace it all with
new FormData(form)
- The one place where we need to parse form data for a field or container is when watching a container using
up.watch()
or[up-watch]
- Another approach is to weaken the
watch()
contract and just rely on input / change events - We could still compare values of the entire form to de-dup multiple events
- We could keep the old signature (changed value is passed) by comparing only the values of fields whose [name] we see in the watched container
- For this we would need to know a selector for all inputs
- Which we also need for [up-disable]
- Or we could go after [name]
- For this we would need to know a selector for all inputs
- There's a small change that an unwatched part of the form changes
- The one place where we need to parse form data for a field or container is when watching a container using
- We might get rid of
up.form.config.fieldSelectors
- We could again rely on just event delegation in
up.FieldWatcher
- Goals
- Check if we can replace
{ submitButton }
withnew FormData(form, submitter)
- Consider removing all up.migrate polyfills (and library hooks!) except changes from v3 => v4
- Users would need to upgrade to v3 before upgrading to v4
- Users could no longer stay on unpoly-migrate forever without updating their code => Check impact before we do this
- Deprecate
[up-id]
in favor of just[id]
- This is required if we ever want to use something like idiomorph, which relies on
[id]
heavily
- This is required if we ever want to use something like idiomorph, which relies on
- Replace
up.URLPattern
with standardURLPattern
- Not yet supported by Firefox and Safari: https://caniuse.com/mdn-api_urlpattern
- Webkit: Not even a bug yet
- Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=1731418
- Polyfillable: https://github.com/kenchris/urlpattern-polyfill
- Existing dollar
$syntax
for matching digits would need to be replaced with regexes - A migrate warning would be possible
- At least adjust our syntax to the syntax that is going to be supported
- Not yet supported by Firefox and Safari: https://caniuse.com/mdn-api_urlpattern
- Remove
up.rails
package- This was only needed for co-existence with rails-ujs. Modern Rails apps no longer include this.
- We could also offer this as an optional file (unpoly-rails-ujs.js)
- Unpoly core already parses
[data-confirm]
and[data-method]
. No additional support is needed just to use { method } and { confirm } options from Rails helpers.
- Internals: Consider replacing up.
Record with
custom constructors - Consider removing
up.copy.key
&&up.isEqual.key
- Poll users
- This is currently used by
up.Params
- Consider an { onUpdate } callback so users can setup view transitions or implement morphins
- See view-transitions.md
- Consider removing layer context
- I think users find it sufficient to look at X-Up-Mode instead
- Poll users
- Remove
:has()
polyfill- Firefox ESR should get :has() support in Q4/2024
- We can also do this later as it's not a breaking change
- Also many docs mention this as a key difference between up.element and up.fragment
- Once we use native :has(), can we get replace up.Selector with CSS selector expansion?
- Excluding .up-destroying would just be :is(original-selector):not(.up-destroying, .up-destroying *)
- Matching layers would require a longer selector
- :is(original-selector):is([up-layer-index=3], [up-layer-index=3] * [up-layer-index=4])
- We could also run the lookup once per layer, which would also honor layer priority when looking up a single fragment in mulitple layers
- Consider making secondary targets optional by default
- Remove event mapping
- Remove up.form.config.watchInputEvents
- Remove up.form.config.watchChangeEvents
- Why do we still have a test for date inputs being watched on blur instead of change?
- Remove doc section "Normalizing non-standard events"
- Implement up.motion with the web animations API
- We can also remove
up.CSSTransition
- We can also remove
- Replace up.util.reverse() with Array#toReversed()
- https://caniuse.com/mdn-javascript_builtins_array_toreversed
- Safari has this since September 2022, so Summer 2024 should be fine
- Firefox ESR already has this
- https://caniuse.com/mdn-javascript_builtins_array_toreversed
- Async compiler functions
- Many edge cases: See separate doc
2023-08-08 Async Compilers.txt
- This might change some method signatures
- Made somewhat easier since we no longer report compiler errors
- See separate doc
- Many edge cases: See separate doc
- Remove
up.reveal()
and most of theup.viewport
module- Use
Element#scrollIntoView()
- config.revealSnap and config.revealPadding could be solved by setting scroll-padding on the scrolling container
- This also lets us remove up.RevealMotion, up.Rect, [up-fixed]
- We still need [up-anchored=right] to do Bootstrap-style scrollbars
- Do we still need viewport awareness when we're no longer scrolling ourselves?
- How many apps really have multiple viewports?
- Maybe for resetting?
- Could we just reset the main viewport of that layer?
- Could we offer events or config options so users can implement custom reveal / reset ?
- Use
- Replace
.up-focus-visible
and.up-focus-hidden
classes withElement#focus({ focusVisible })
- Browser support is still poor
- https://caniuse.com/mdn-api_htmlelement_focus_options_focusvisible_parameter
- What
{ focusVisible }
cannot do is stickiness: Switch to other window, switch back- Maybe that's OK
- Browser support is still poor
- Remove selector/element/jQuery argument normalization
- Consider requiring elements for functions that work with elements, not selectors or jQuery collections
- We still want selectors for anything I would use from an HTML attribute
- Remove all remaining jQuery support
- We could warn in
up.element.get()
- We could warn in
- Consider requiring elements for functions that work with elements, not selectors or jQuery collections
[up-instant]
and[up-preload]
should listen onpointerdown
instead ofmousedown
- This could break some tests that only send
mousedown
- Maybe migrate should print a warning when such an element receives a synthetic
mousedown
event, but nopointerdown
within the same task.
- This could break some tests that only send
- Publish and document thrown exceptions
- But consider waiting until async compilers, as we're changing some method signatures
- How do we document async rejections?
up.link.preload()
documents in@return
- I'd much rather a
@throw
or@throws
declaration
- Consider removing
X-Up-Title
,[up-title]
and{ title }
since we now auto-update head elements - Check if we can replace
up.Params
withFormData
and a few helper methods- I guess we would use a
FormData
value in{ params }
instead of up.Params - Migrate could check if we're using up.Params methods on this thing
- I guess we would use a
- Consider using native
<dialog>
element for overlays- Example with nested dialogs: https://codepen.io/triskweline/pen/WNWEjQx
- We would save the focus trap code
- We would no longer need to mess with
z-index
values - This would simplify layer lookup to
element.closest('dialog:open, html')
- We could potentially lose
up.LayerStack
and store all layer information in the dialog elements- This would allow users to render a page with an existing modal already open, and it would immmediately go into the layer stack
- We would need to parse that layer to initialize it
- E.g. implement light-dismiss
- There is currently no way to default-open a modal!
<dialog open>
opens a non-modal dialog. - We might also conflict with other libraries that want to use and now occupy the frontmost layer
- => Don't do this now
- We would need to parse that layer to initialize it
- This would allow users to render a page with an existing modal already open, and it would immmediately go into the layer stack
- This would mean another breaking change for users, especially for the markup.
- We need the markup to move away from custom elements because we need to attach to
- It's better to completely change the markup rather than change the meaning of the existing elements
-
dialog.up-drawer div.up-drawer-viewport # keep this around so the box doesn't need a margin. but how are we going to do this with viewport-less types, like lightbox? div.up-drawer-box button.up-drawer-dismiss # we can now use propert buttons div.up-drawer-content ::backdrop
- It would a lot of work to sync the dialog API with the Unpoly layer API
- If we use the DOM as our LayerStack, vanilla-opened dialogs are Unpoly dialogs.
- Their APIs either need to be interchangable.
- Or we ignore non-Unpoly dialogs
- E.g. you can close a dialog with
HTMLDialogElement.close()
, but this does not change Unpoly's layer stack - Dialogs have
close()
, but Unpoly layers have accept/dismiss - Dialogs have
close(resultValue)
and closeEvent.resultValue. The default value here is "default".- Maybe dismissal could be encoded in that value, e.g. :dismiss(key), :dismiss(button) and anything else is acceptance
- Or we get rid of the destinction
- Poll users
- Maybe a compromise would be: We keep most of our API, but also don't get into an undefined state if someone uses native dialog methods on us (in particular
close()
) - Would we keep our own events?
- No!
- There is no
open
event- Opening non-Unpoly overlays can only be detected with MutationObserver
- There is no
opened
event - There is a
close
event, but it cannot be prevented- Would be a good signal for "external close"
- There is no
closed
event
- There is no
- No!
- If we use the DOM as our LayerStack, vanilla-opened dialogs are Unpoly dialogs.
- Would we replace
up.Layer
with just theHTMLDialogElement
?- Rather not.
- There is a lot of state and API that we currently store in
up.Layer
- E.g. it's nice to say
layer.accept()
orlayer.contentElement
- E.g. it's nice to say
- Check if we still need
config.foreignOverlaySelectors
- Address maintenance burden with popup overlays
- Problems with popups
- Are the only non-modal overlay option, so we cannot share as much code with the other popup options
- Our homegrown tethering takes a lot of code and only supports basic positioning. It would take even more code to do it well.
- There's more popup-only code in up.Layer.OverlayWithTether
- Consider dropping
- Poll need with users
- Local-content popups can be done well by
[popover]
, Bootstrap or other libraries - Remote-content popups, or multi-page content are super painful without Unpoly
- Consider separating from layer
- Consider implementing as non-modal dialogs
- Consider implementing with
[popover]
- https://caniuse.com/mdn-api_htmlelement_popover
- There is not much we want from this API except maybe native light-dismiss. But we already need to roll our own light-dismiss for our dialogs.
- Consider extraction into its own plugin
- Replace
up.Tether
with CSS anchor positioning / [anchor-name]- https://blog.logrocket.com/use-css-anchor-positioning/
- Browser support is still poor
- https://caniuse.com/css-anchor-positioning
- Firefox will get [popover] before it gets anchor positioning, but without anchored positions
- There is a polyfill, but we need to check how well this works from JS: https://github.com/oddbird/css-anchor-positioning
- Consider requiring an external library for tethering (e.g. "Floating UI")
- Consider just adding hooks for your own positioning
- E.g. events like
up:tether:start
,up:tether:update
orup:tether:stop
- Users would be responsible for parsing their own options from
event.target
orevent.anchor
- E.g. events like
- Problems with popups
- Implement the cache in a service worker
- Replace target merging in up.FormValidator with a generic request merging facility that merges headers
- This is not only about merging requests. FormValidator does a lot of work to merge render options.
up.animate()
andup.morph()
could return anAnimation
object- For what?
- If people manually call
Animate#finish()
etc. this may make it harder to enforce our motion logic
- Reconsider objects that also serve as their own promises
- Instead the promise could be returned by a property like #loaded or #rendered
up.Request
: Instead ofawait up.request()
users would sayawait up.request().loaded
up.RenderJob
: Instead ofawait up.render()
users would sayawait up.render().rendered
- This would be more in sync with how the Animation API works
- But otherwise it's worse econonomics
- And we're not even going to return
Animation
objects from motion functions
- This might make it easier to compose with standard promises
- Challenge that with actual code, since supporting
await up.render().finished
also already a PITA now
- Challenge that with actual code, since supporting
- We could polyfill it
- Instead the promise could be returned by a property like #loaded or #rendered
- Consider moving history-related methods from
up.Layer.Base
toup.history
- All other packages work by taking a
{ layer }
option, why not history-related? up.history
is currently a low-level module, likeup.element
- This change would require a lot more design.
- All other packages work by taking a
- Automatic
up.hello()
withMutationObserver
- This seems to make more problems than solutions whenever another DOM manipulating library is involved :(
- Needs to be async?
- Effects of insertion could not be observed until 1 task later
- This would make up.destroy() async
- Performance?
- Possibly disable observing while we are rendering content ourselves
- Compilers should still insert elements and see them auto-compiled
- Maybe like this:
disableObserver() insertElements() enableObserver() compileElements
- Edge cases
- Macros can insert elements with other macros
- Do we want this?
- Do we still need macros with auto-compilation?
- Compilers can insert elements and see them auto-compiled
- React
- Compilers that change the DOM should not cause React to permanently re-render a subtree (the VDOM didn't change, but the DOM did)
- Compilers that change the DOM should not cause React to undo these DOM changes
- We should not cause hydration errors when pre-rendering on the server
- The compiler might only run on the client
- https://legacy.reactjs.org/docs/integrating-with-other-libraries.html
- https://preactjs.com/guide/v10/external-dom-mutations/
- Maybe we need a way to disable Unpoly for a subtree ([up-ignore])
- Macros can insert elements with other macros
- Consider removing transitions/morphing with ViewTransitions (single-document)
- See
view-transitions.md
- See