Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/uikit-paging-values.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: UIKit's paging values and math for touch presentations, measured on iOS 26.2: the thresholds that pick the page a release settles on, the settle that carries it there and `setContentOffset(animated:)`. The settle and `setContentOffset(animated:)` replay the recorded frames to under half a pixel. The unused projection rule (`selectPage` and `scroll.paging`) is gone, so the measured thresholds are the one paging rule. Not exported from the package entry point.

@vjeux
50 changes: 48 additions & 2 deletions docs/specs/AST-094-touch-presentations/spec.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/specs/components/Lightbox/Lightbox.spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ FR21). Consumer props and usage remain documented in `Lightbox.doc.mjs`.
| FR4 | The open viewer is a focus region through Core's shared focus-region module (`component:Dialog/DEC-2`): Tab from its last control moves focus to its first and Shift+Tab from its first moves focus to its last, so focus never leaves the viewer for the browser's own interface. | `component:Dialog/DEC-2` | settled; implementation gap |
| FR5 | Under a coarse primary pointer, after hydration, Lightbox presents FR6–FR10 with no opt-in. Every spring, duration, and curve they play is a named motion role (zoom settle, page settle, dismiss, pinch close, controls fade, zoom transition) read through the motion role table, never a literal. Under reduced motion they follow AR6. | `spec:AST-094` FR4, FR13 | settled; implementation gap |
| FR6 | With `hasZoom`, on an image, a pinch zooms about the fingers and pans while it zooms; past the minimum or maximum the photo resists and eases back without overshoot. A double tap zooms to the larger of filling the viewer and 2×, about the tapped point; a second double tap returns to the fitted size. A zoomed photo pans with momentum and rubber-band edges. A pinch on the controls or the caption still zooms the page. | `spec:AST-094` FR3, FR10 | settled; implementation gap |
| FR7 | In a gallery, a horizontal drag moves the row of items, which sit apart by the measured gap. The release lands on at most one neighbouring item by the projection rule. Paging applies over a video item's frame as over a photo; a drag that starts on the video's own controls, such as its scrubber or volume, stays the video's. A zoomed photo pans to its edge before a new drag pages. A pinch never pages. Previous, Next, and the arrow keys keep paging. A controlled `index` the caller refuses springs back. `onIndexChange` reports a swipe. | `spec:AST-094` FR14, FR36–FR39 | settled; implementation gap |
| FR7 | In a gallery, a horizontal drag moves the row of items, which sit apart by the measured gap. The release lands on at most one neighbouring item by `spec:AST-094` FR3's paging rule (DEC-25). Paging applies over a video item's frame as over a photo; a drag that starts on the video's own controls, such as its scrubber or volume, stays the video's. A zoomed photo pans to its edge before a new drag pages. A pinch never pages. Previous, Next, and the arrow keys keep paging. A controlled `index` the caller refuses springs back. `onIndexChange` reports a swipe. | `spec:AST-094` FR3, FR14, FR36–FR39, DEC-25 | settled; implementation gap |
| FR8 | On a fitted image, a downward drag moves the photo with the finger, shrinks it, and fades the backdrop. The release decides once: a projected travel past the commit point closes the viewer and reports `onOpenChange(false)` at the lift; a release moving back springs back. Video is unchanged. | `spec:AST-094` FR36–FR40 | settled; implementation gap |
| FR9 | With `hasZoom`, a pinch in from the fitted size is a release decision: below the measured fraction of the fitted size the release closes the viewer; opening the fingers again by the measured amount before the lift is moving back and cancels. A pinch that starts zoomed in never closes the viewer. | `spec:AST-094` FR36, FR37 | settled; implementation gap |
| FR10 | With `hasZoom`, under a coarse pointer only, a tap on the photo fades the controls and caption and darkens the backdrop. While they are hidden, the next tap on the photo, a tap beside the photo, or a cancelled drag brings them back, and that tap beside the photo does not close the viewer (ORD3). AR5 governs keyboard and assistive-technology access while they are hidden. | `architecture:public-component-api` INV16 | settled; implementation gap |
Expand Down
9 changes: 8 additions & 1 deletion packages/core/src/motion/MEASUREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ installed); rows marked † also on iOS 18 for the pre-glass delta. iPhone
| M-S2 | Momentum coast | A long list (Contacts, or a probe table of 500 rows): flicks released at 1000, 2500, 5000 pt/s | contentOffset(t) from release to rest; decay constant; per-millisecond vs per-frame model; the speed at which it stops | C | `scroll.decelerationRate` |
| M-S3 | Bounce-back spring | Same list: drag 120 pt past the top, hold still, release; a flick into the top edge at 3000 pt/s | contentOffset(t) after release and after edge contact; spring response and damping ratio; velocity hand-off | C | `scroll.bounce` |
| M-S5 | Rubber band | Same list on an 852 pt view: drag 100, 300, 600 pt past the top; a horizontal pager | displayed overflow against finger overflow; confirm c = 0.55 and d = the view's size on the axis | C | `scroll.rubberBand` |
| M-PG1 | Paging | Photos full-screen swipes; a probe paging scroll view | page chosen per release position × velocity (thresholds; at most one page?); contentOffset(t) onto the page | B, C | `scroll.paging` |
| M-PG1 | Paging | Photos full-screen swipes; a probe paging scroll view | page chosen per release position × velocity (thresholds; at most one page?); contentOffset(t) onto the page | B, C | `paging` |
| M-ST1 | Scroll to top | Settings scrolled down 500, 2000, 8000 pt; tap the status bar | contentOffset(t); duration against distance; curve or spring fit | B, C | `scroll.scrollToTopRate` |
| M-N1 | Navigation push | Settings: tap General | incoming leading edge x(t), outgoing x(t) (parallax fraction), dimming alpha(t); animation parameters | A, B | `springs.navigationPush` |
| M-N2 | Navigation pop | General: back button | same as M-N1 | A, B | `springs.navigationPop` |
Expand Down Expand Up @@ -72,6 +72,13 @@ initial velocity's sign flipped, a curve that overshoots. Push and pop are
not Core Animation animations on iOS 26.2: UIKit writes presentation
modifiers every frame from the main thread.

## Paging (UIScrollView `isPagingEnabled`, UIPageViewController)

| ID | What | App and gesture | Extract | Method | Feeds |
| ------ | --------------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------ | ------------------------------ |
| M-PG12 | End of a settle | Same pager: releases at rest from 10 %, 49 %, 60 % and 90 % of a page, logged at 120 fps | remaining distance and step velocity at the frame the offset lands exactly on the page | C | `paging.integrator.restSpeed` |
| M-PG13 | Settle velocity floor | Same pager: steady releases from 200 to 250 pt/s in 5 pt/s steps, at 25 % of a page | the slowest release whose `scrollViewWillEndDragging` velocity is not 0 | C | `paging.release.velocityFloor` |

## Gestures (UIKit gesture recognizers)

Thresholds the gesture system reads (`uikit/gestures.ts`). Safari on iOS runs
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/motion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ so each keeps its measured value; no shipped component reads the roles yet.
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `uikit/` | THE constants table: every spring, duration, deceleration rate, rubber-band constant and curve, with its source. |
| `uikit/springs.ts` | Interaction springs (navigation, sheets, alerts, menus, controls, keyboard, banners) and the SwiftUI / UIKit presets. |
| `uikit/scroll.ts` | UIScrollView: deceleration rates, rubber band, bounce-back spring, paging, `delaysContentTouches`, scroll to top. |
| `uikit/scroll.ts` | UIScrollView: deceleration rates, rubber band, bounce-back spring, `delaysContentTouches`, scroll to top. |
| `uikit/timing.ts` | Non-spring clocks and curves: press highlight, Core Animation's named curves, keyboard, Reduced Motion cross-fade. |
| `uikit/paging.ts` | A paging UIScrollView: the integrator that settles a release on its page, the page rule, the catch, `setContentOffset(animated:)`. |
| `uikit/navigation.ts` | UINavigationController: push velocity, parallax, dimming, transition clock, interactive pop finish rule. |
| `uikit/gestures.ts` | Gesture recognizer thresholds, the velocity filter and haptic stand-ins, as `{value, unit}` entries (sources in JSDoc). |
| `roles.ts` | The internal motion roles: the springs, durations and curves `spec:AST-094` FR4 names, registered by the shared tables, and a role's CSS form read back. |
Expand All @@ -28,8 +29,8 @@ so each keeps its measured value; no shipped component reads the roles yet.
| `coast.ts` | A release's momentum curves for `animator.decay`: the coast, the edge spring, a release past an edge. |
| `rubberBand.ts` | UIScrollView's resistance past an edge, and its inverse. |
| `projection.ts` | Release targets: the rest point nearest the projected coast. |
| `paging.ts` | Paging: UIKit's paging settle as a playable curve, its velocity floor, the animated page scroll. |
| `resolveSnapPoint.ts` | Release policy: the snap point a release lands on (flick, back-out cancel, step limit), on `selectSnapPoint`. Internal. |
| `selectPage.ts` | Paging: at most one page per release, on `resolveSnapPoint`. |
| `bezier.ts` | Cubic-bézier timing functions (Core Animation's named curves). |
| `animator.ts` | Compositor animator: springs, coasts and any closed-form curve compiled to Web Animations, read back analytically. |
| `presentation.ts` | Where each moving element is drawn relative to where the engine hit-tests it, reported by the animator while translate runs; read by the gesture host at touch-down. |
Expand Down
Loading
Loading