Skip to content

Latest commit

 

History

History
284 lines (269 loc) · 16 KB

v4.md

File metadata and controls

284 lines (269 loc) · 16 KB

Draft for Unpoly 4

Focus

  • 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!

Big bets

  • Can we replace the "validate" terminology with a more generic "redraw" or similiar?

Clean up

  • 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()
  • 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?)

Strong candidates

  • 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()
  • Can we use [inert] for focus trapping?
  • Is scrollbar-gutter: stable a replacement for up.BodyShifter?
  • Drop { batch: true } option for up.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]
      • There's a small change that an unwatched part of the form changes
    • We might get rid of up.form.config.fieldSelectors
    • We could again rely on just event delegation in up.FieldWatcher
  • Check if we can replace { submitButton } with new 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
  • Replace up.URLPattern with standard 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
  • Replace up.util.reverse() with 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
  • Remove up.reveal() and most of the up.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 ?
  • Replace .up-focus-visible and .up-focus-hidden classes with Element#focus({ focusVisible })
  • 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()
  • [up-instant] and [up-preload] should listen on pointerdown instead of mousedown
    • 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 no pointerdown within the same task.
  • 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 with FormData 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
  • 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
    • 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
    • Would we replace up.Layer with just the HTMLDialogElement?
      • 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() or layer.contentElement
    • 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]
    • Consider extraction into its own plugin
    • Replace up.Tether with CSS anchor positioning / [anchor-name]
    • 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 or up:tether:stop
      • Users would be responsible for parsing their own options from event.target or event.anchor

Low confidence / Much design work needed

  • 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() and up.morph() could return an Animation 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 of await up.request() users would say await up.request().loaded
      • up.RenderJob: Instead of await up.render() users would say await 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
    • We could polyfill it
  • Consider moving history-related methods from up.Layer.Base to up.history
    • All other packages work by taking a { layer } option, why not history-related?
    • up.history is currently a low-level module, like up.element
    • This change would require a lot more design.
  • Automatic up.hello() with MutationObserver
    • 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
      • Maybe we need a way to disable Unpoly for a subtree ([up-ignore])
  • Consider removing transitions/morphing with ViewTransitions (single-document)
    • See view-transitions.md