Skip to content
Closed
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/click-guard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the gesture recognizers and the menu press model share one guard for the browser's click after a gesture that already acted, swallowing it for 400ms or until the next press. Keyboard and assistive-technology clicks (`detail` 0) always pass.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-hook-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the gesture hooks call nothing back after unmount; `usePressTracker` brakes with the host by default and keeps one tracker, so an option change or a new ref never drops a press in progress; the recognizer's arena plumbing is `protected`. Not exported from the package entry point.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-host-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: gesture host fixes from review. A claimed pen holds only its own stylus touches, and a finger is matched to its scroller, its target and a moving element along the flat tree, through slots and shadow roots. Not exported from the package entry point.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-host-owner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the gesture host becomes the one document-level touch owner: the finger count, touch gates, and a kept press for continuous-tracking controls, whose element gets `touch-action: manipulation`. A scroll lock's page hold and the finger-scroll watch keep their own listeners and ask the host only about the touches it owns. A second finger fails a one-finger pan that has not claimed, so the page can pinch. A scroll-edge pan holds a lone finger from the first move that pulls away from an edge every scroller under it is at, so those moves are cancelled before it begins; with a second finger anywhere on the screen the page keeps its pinch. A press tracker whose hold acts (a stepper's hold-repeat) can bring a selection lock for its presses, as a recognizer can. Internal, for the touch components that adopt them; not exported from the package entry point.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-recognizer-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: gesture recognizer and arena fixes from review; a pan claims nothing before it begins at 10 pt; one velocity tracker for every gesture (`spec:AST-100`: UIKit's measured blend, a scrolling surface's release through the pan's `scrollVelocity()`, and FR2's release rules). Not exported from the package entry point.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gesture-watcher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: a recognizer created with `claimsTouches: false` is a watcher: passive listeners only, simultaneous with every recognizer, and it follows a touch the browser takes for a scroll to the lift. Not exported from the package entry point.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gestures-every-pointer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: with the gesture host serving every pointer, a press tracker takes a mouse press as a press, painting at once with no scroll brake (AST-094 FR11); a finger's or a pen's hold that does something keeps text unselected under any primary pointer, a touchscreen laptop's included, while a mouse keeps its released behavior (FR41); a finger is read once where the browser sends Touch Events without exposing `ontouchstart`; and `perEventPointer`, from before FR43, is accepted and does nothing. Not exported from the package entry point.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/gestures-wheel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: a pan follows a trackpad's two-finger scroll where its owner allows it (`allowedScrollTypes: ['continuous']`, UIKit's `allowedScrollTypesMask`; none by default), along its own axis and after the scrollers under the pointer, and never a mouse wheel's notches, which scroll what is under the pointer; the screen-edge pan takes none; and a pinch takes a ctrl-wheel from a trackpad or a mouse (AST-094 FR43, DEC-24). A watcher pan never takes the wheel, and a wheel another handler already prevented stays the page's. Input the deltas cannot place, Shift+wheel included, reads as a mouse wheel; a trackpad scroll a pan follows keeps moving at the fingers' speed until their lift is read, ends there still moving, and the momentum never moves it. Not exported from the package entry point.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/input-stream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[fix] The gesture recognizers now hear page-wide input through one shared stream on the window's capture phase, where the touch press feedback's pointer listeners already were, so an app's window-capture `stopPropagation`, or a document-capture `stopImmediatePropagation` registered before Core, no longer hides a touch from them. The press feedback's scroll and visibility listeners join the same stream. A recognizer that must claim a touch mid-gesture still needs the touch to reach its element. Each event type's listener is passive unless a subscriber cancels it (the gesture host's `touchend` and `contextmenu` while a claiming recognizer or press tracker is attached, the page hold's `touchmove` while it holds, and the click guard's `click` while it is armed, and from the menu press model's first arm on), so a page of pressables registers no listener that can hold a scroll.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/long-press-one-source.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: `useLongPress` takes its default duration and tolerance, 500ms and 10px on either axis, from the same source as the long-press gesture recognizer, so ContextMenu's long press behaves as before. A finger held on a DropdownMenu trigger passes its own 13px allowance instead (see the trigger drift fix).

@vjeux
7 changes: 7 additions & 0 deletions .changeset/menu-trigger-drift.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[fix] A finger held on a DropdownMenu trigger may drift up to 13px on each axis and still open the menu under every primary pointer, a phone's, a tablet's and a touchscreen laptop's alike, as AST-094 FR29 allows; it cancelled past 10px. No menu, row or trigger reads the pointer query.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/presented-hit-testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] On iOS, a touch on an element the compositor animator is moving reaches what is drawn: the animator reports each moving element's offset from its layout box, and the gesture host sends the touch, press bounds and the tap's click there instead of to the layout position iOS WebKit hit-tests.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/touch-selection-lock-gestures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the gesture host applies a recognizer's own `selectionLock` to that recognizer's contact and imports no lock itself; a long press brings one only when it has a hold (a `minimumPressDuration` above 0), and `selectionLock: null` opts out. Not exported from the package entry point.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/touch-selection-lock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[fix] Where the primary pointer is fine (a mouse, a trackpad, a touchscreen laptop, a desktop pen tablet), the press feedback controller no longer registers a touch listener: it reads the primary pointer at the first finger or pen press and keeps its `touchcancel` listener only while that pointer is coarse.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/uikit-pager-every-pointer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: the pager takes every pointer (`spec:AST-094` FR43). A mouse drags it as a finger does, and its pan follows a trackpad's horizontal scroll, at most one page from the page the swipe began on: a fling releases where the fingers lift, at their speed, while a scroll that stops, or a swipe that moved under a tenth of a page, releases still. A swipe that lands while the last one settles begins from that settle's page. A mouse wheel's scroll stays the page's.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/uikit-pager.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: a pager for touch presentations that pages like a UIKit paging scroll view. A horizontal pan moves the pages one to one and rubber-bands at the ends. The release picks the page with UIKit's rule and settles on UIKit's paging curve on the compositor, and a finger can catch it mid-flight. Its motion values are internal constants. Not exported from the package entry point.

@vjeux
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. The thresholds that pick the page a release settles on, the settle that carries it there and `setContentOffset(animated:)` are measured on iOS 26.2 and replayed against the recorded frames to under half a pixel. Not exported from the package entry point.

@vjeux
7 changes: 7 additions & 0 deletions .changeset/use-finger-scroll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: `useFingerScroll(scroller)` tells a finger's scroll from the program's, and the gestures README gains a guide to migrating a raw-touch drag onto the recognizers. Internal to the touch components: not exported from the package entry point or the gestures barrel.

@vjeux
2 changes: 1 addition & 1 deletion .github/scripts/ci-interaction-webkit.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ describe('WebKit interaction proof routing', () => {
for (const untouched of [
'packages/core/src/Toast/Toast.tsx',
'packages/core/src/Selector/useSelectorPresentation.ts',
'packages/core/src/utils/clickGuard.ts',
'packages/core/src/utils/focusReturn.ts',
'packages/core/src/internal/focus.ts',
'packages/core/src/DateInput/DateInput.tsx',
'packages/core/locales/en.json',
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1976,7 +1976,7 @@ jobs:
id: scope
uses: ./.test-scope/.github/actions/test-scope
with:
paths: '^(packages/core/src/gestures/|packages/core/src/motion/|packages/core/src/internal/touch|packages/core/src/utils/(press|touch|primaryClick|isApplePlatform)|packages/core/src/hooks/(useMenuPress|menuPress|useLongPress|usePressFeedback)|internal/interaction-harness/|internal/a11y-spec/|playwright\.interaction\.config\.ts$|packages/themes/[^/]+/src/|apps/storybook/|\.github/workflows/ci\.yml$|package\.json$|pnpm-lock\.yaml$|\.github/actions/test-scope/)'
paths: '^(packages/core/src/gestures/|packages/core/src/motion/|packages/core/src/internal/touch|packages/core/src/utils/(press|touch|primaryClick|isApplePlatform|flatTree|inputStream|clickGuard|selectionLock|coarsePointer)|packages/core/src/privateAttributes/hapticSwitch\.stylex\.ts$|packages/core/src/hooks/(useMenuPress|menuPress|useLongPress|usePressFeedback|isRtlElement)|internal/interaction-harness/|internal/a11y-spec/|playwright\.interaction\.config\.ts$|packages/themes/[^/]+/src/|apps/storybook/|\.github/workflows/ci\.yml$|package\.json$|pnpm-lock\.yaml$|\.github/actions/test-scope/)'
outside-pull-request: skip
requires-head-file: playwright.interaction.config.ts

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/react-component-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ those contracts.
the modules through an internal hook only. A component's own touch logic is
small glue in its own build over these modules. The gesture host attaches for
every pointer, with no touch-only query; under a fine pointer only a component that
uses a gesture listens (`spec:AST-094` FR19, FR24, FR43). The modules are not public exports and are not
uses a gesture listens (`spec:AST-094` FR19, FR24, FR43). The gesture host, press feedback, page hold, finger-scroll watch and click guard hear document-wide input through one capture-phase listener per event type on the window, present while a subscriber needs it. They run in subscription order, before any document or element listener; it is passive unless a subscriber cancels the event. The modules are not public exports and are not
documented for consumers; Core and Lab components reach them through
`@astryxdesign/core/internal/touch`, a subpath reserved for Astryx's own
packages: undocumented, outside the compatibility promise, versioned in
Expand Down
4 changes: 2 additions & 2 deletions docs/specs/AST-094-touch-presentations/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ a measured, sourced difference exists.
activation stays the browser's click, except that an action a
`family:action-groups` member tracks activates at the lift (FR26), and a toggle
under `spec:AST-020` FR9's slide-off exception (Switch, Checkbox,
CheckboxListItem) may also complete on a lift after the finger slid off.
CheckboxListItem) may also complete on a lift after the finger slid off. Where the engine hit-tests an element a compositor animation moves at the motion's start (iOS WebKit), a touch on moving content goes to what was drawn under the finger at touch-down, and a tap there (no recognizer claimed it, the browser did not take it, and it stayed within 45 pt) MUST activate it: the browser's own click for that touch is cancelled or swallowed, and that element receives one click, an editable focused first.
Under a fine pointer a control uses a third profile with no delay: its
press paint starts at once on `pointerdown`, through the same press
controller and release fade as a finger's, in place of CSS `:active`; the
Expand Down Expand Up @@ -312,7 +312,7 @@ a measured, sourced difference exists.
recognizer that must claim mid-gesture registers that blocking listener; an
element whose recognizers decide only at the lift keeps its `touchmove`
listener passive, and its `touchend` listener stays cancelable so the
gesture can cancel the click (FR21). Known limit: a native scroll that starts on an element with a
gesture can cancel the click (FR21). While an element that holds, or sits inside, an element with a recognizer that claims moving touches moves on the compositor (iOS WebKit), the document MAY carry a non-passive `touchmove` listener; it cancels only the moves of a touch a recognizer owns under this rule and comes off when the motion ends. While it is on, a native scroll that starts anywhere on the page waits for a free main thread. Known limit: a native scroll that starts on an element with a
blocking listener waits for a free main thread in iOS Safari and Chromium.
That is never worse than UIKit, whose scrolling stops on a busy main thread,
and a passive listener, or one added at `touchstart`, would hand diagonal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@
"data-astryx-press",
"data-astryx-press"
],
"packages/core/src/gestures/haptics.ts": ["data-astryx-haptic-switch"],
"packages/core/src/gestures/touchActionRule.ts": ["data-astryx-touch-action"],
"packages/core/src/hooks/useAnnounce.ts": [
"data-astryx-live-region",
Expand Down
11 changes: 11 additions & 0 deletions internal/test-utils/src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
/// <reference types="@testing-library/jest-dom" />
import '@testing-library/jest-dom/vitest';
import {configure} from '@testing-library/react';
import {afterEach} from 'vitest';
import {__resetClickGuardForTest} from '../../../packages/core/src/utils/clickGuard';

// Text queries (getByText/findByText/…) target VISIBLE text. Astryx's
// `useAnnounce` renders a visually-hidden aria-live region
Expand All @@ -25,6 +27,15 @@ import {configure} from '@testing-library/react';
// Keeps the jsdom defaults (script, style).
configure({defaultIgnore: 'script, style, [data-astryx-live-region]'});

// The click guard (packages/core/src/utils/clickGuard.ts) is armed per
// document by a gesture that already acted and disarms itself 400 ms later.
// A test that ends inside that window would leave the timer to fire after
// the environment tears its window down, during a later file. Disarm every
// guard as each test ends.
afterEach(() => {
__resetClickGuardForTest();
});

// Polyfill for matchMedia (not supported in jsdom).
//
// `(hover: hover)` reports TRUE: jsdom models a desktop browser driven by a
Expand Down
Loading
Loading