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/core-internal-touch-entry-lab-animator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[chore] Internal: Core's internal motion engine factory is renamed `createMotionEngine` (from `createMotionAnimator`), with its types `MotionEngine` and `MotionEngineOptions`, at its source and in every internal caller. Core's internal touch entry also re-exports, by name, the UIKit gesture values, the right-to-left check, and the touch-presentation hook and query that Lab components read. Lab's `useMotionEngine` hook wraps the engine, and Lab's refresh control uses it. Apps import nothing new.

@vjeux
4 changes: 2 additions & 2 deletions packages/core/src/DateInput/wheelStrip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

import {canVibrate, playHaptic} from '../gestures/haptics';
import {createMotionAnimator, type MotionCurve} from '../motion/animator';
import {createMotionEngine, type MotionCurve} from '../motion/animator';
import {reducedMotionFade} from '../motion/reducedMotion';
import {picker} from '../motion/uikit/picker';
import {
Expand Down Expand Up @@ -132,7 +132,7 @@ function now(): number {
* ```
*/
export function createWheelStrip(initialLayout: WheelLayout): WheelStrip {
const animator = createMotionAnimator(null);
const animator = createMotionEngine(null);
let layout = initialLayout;
let running: Running | null = null;
let nearestListener: ((position: number) => void) | null = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
import {motionRoleOf} from '../motion/roles';
import {createMotionAnimator, type Animator} from '../motion/animator';
import {createMotionEngine, type Animator} from '../motion/animator';
import {solveSpring, toSpring} from '../motion/spring';
import {cubicBezier} from '../motion/bezier';
import {segmentedControl, springs} from '../motion/uikit';
Expand Down Expand Up @@ -105,12 +105,12 @@ function setup(selected: string, {coarse = true}: {coarse?: boolean} = {}) {
document.head.append(style);
document.body.append(track);
// A finger by default: Reduced Motion fades (design:reduced-motion DR2).
const animator: Animator = createMotionAnimator(null, {
const animator: Animator = createMotionEngine(null, {
now: () => clock,
pseudoElement: '::before',
});
// The ::after: the lifted lens's shadow, or the surface fading out.
const afterAnimator: Animator = createMotionAnimator(null, {
const afterAnimator: Animator = createMotionEngine(null, {
now: () => clock,
pseudoElement: '::after',
initial: {opacity: 0},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
type RefObject,
} from 'react';
import {
createMotionAnimator,
createMotionEngine,
type AnimateOptions,
type Animator,
type MotionCurve,
Expand Down Expand Up @@ -539,7 +539,7 @@ export class SegmentedSelectionMotion {
private readonly after: Animator;

constructor(
private readonly animator: Animator = createMotionAnimator(null, {
private readonly animator: Animator = createMotionEngine(null, {
pseudoElement: '::before',
}),
options: SegmentedSelectionMotionOptions = {},
Expand All @@ -552,7 +552,7 @@ export class SegmentedSelectionMotion {
this.now = options.now ?? defaultNow;
this.after =
options.afterAnimator ??
createMotionAnimator(null, {
createMotionEngine(null, {
pseudoElement: '::after',
initial: {opacity: 0},
});
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/Slider/sliderTouchController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {TapGestureRecognizer} from '../gestures/TapGestureRecognizer';
import type {PointerKind} from '../gestures/types';
import {isRtlElement} from '../hooks/isRtlElement';
import {
createMotionAnimator,
createMotionEngine,
type Animator,
type MotionCurve,
type MotionHandle,
Expand Down Expand Up @@ -1186,7 +1186,7 @@ export class SliderTouchController {
private animatorFor(element: HTMLElement): Animator {
let animator = this.animators.get(element);
if (animator == null) {
animator = createMotionAnimator(element);
animator = createMotionEngine(element);
this.animators.set(element, animator);
}
return animator;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/Switch/useSwitchTouch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ vi.mock('../motion/animator', async importOriginal => {
const actual = await importOriginal<typeof AnimatorModule>();
return {
...actual,
createMotionAnimator: (
createMotionEngine: (
element: Element,
options: {pseudoElement?: string} = {},
) => {
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/Switch/useSwitchTouch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type {GestureTouch} from '../gestures/types';
import {isRtlElement} from '../hooks/isRtlElement';
import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
import {
createMotionAnimator,
createMotionEngine,
type Animator,
type MotionCurve,
} from '../motion/animator';
Expand Down Expand Up @@ -282,15 +282,15 @@ export class SwitchTouchController {
return;
}
registerSwitchTable();
this.thumb = createMotionAnimator(elements.thumb);
this.thumb = createMotionEngine(elements.thumb);
// The layer rests clear (its class's opacity 0) while the visuals show
// the value; from here its opacity is the animator's.
this.fade = createMotionAnimator(elements.track, {
this.fade = createMotionEngine(elements.track, {
pseudoElement: '::after',
initial: {opacity: 0},
});
this.fade.set({opacity: 0});
this.shadow = createMotionAnimator(elements.thumb, {
this.shadow = createMotionEngine(elements.thumb, {
pseudoElement: '::after',
initial: {opacity: 0},
});
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/animator/createAnimator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
*/

import {
createMotionAnimator,
createMotionEngine,
SETTLE_THRESHOLD,
type Animator as MotionAnimator,
type Animator as MotionEngine,
type MotionCurve,
type MotionProperty,
type MotionValues,
Expand Down Expand Up @@ -199,7 +199,7 @@ export function createAttachableAnimator(): {
readonly animator: Animator;
readonly attach: (element: HTMLElement | null) => void;
} {
const motion: MotionAnimator = createMotionAnimator(null);
const motion: MotionEngine = createMotionEngine(null);
// Calls do nothing while no element is attached, before the ref attaches
// or after it lets go. They also do nothing after destroy, and animate and
// decay then resolve at once.
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/animator/publicSurface.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const SRC = path.join(__dirname, '..');

/** Names of the motion module beneath the public animator. */
const INTERNAL = [
'createMotionAnimator',
'createMotionEngine',
'createAttachableAnimator',
'animatorFor',
'coast',
Expand Down Expand Up @@ -67,6 +67,6 @@ describe('the public animator surface', () => {
'utf8',
);
expect(motion).not.toMatch(/export function createAnimator\b/);
expect(motion).toMatch(/export function createMotionAnimator\b/);
expect(motion).toMatch(/export function createMotionEngine\b/);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* @position Browser fixture of presentedHitTesting.interaction.browser.spec.ts; test-only
*/

import {createMotionAnimator} from '../../../motion/animator';
import {createMotionEngine} from '../../../motion/animator';
import {presentedOffsetOf} from '../../../motion/presentation';
import {GestureHost} from '../../GestureHost';
import {PanGestureRecognizer} from '../../PanGestureRecognizer';
Expand Down Expand Up @@ -101,7 +101,7 @@ const TRAVEL = 444;
*/
const SPRING = {response: 6, dampingRatio: 1} as const;

const animator = createMotionAnimator(sheet);
const animator = createMotionEngine(sheet);
const drawn = (target: Element): DOMRect =>
Element.prototype.getBoundingClientRect.call(target);

Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/hooks/usePager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
import {GestureHost} from '../gestures/GestureHost';
import {PanGestureRecognizer} from '../gestures/PanGestureRecognizer';
import {ManualClock} from '../gestures/__tests__/harness';
import {createMotionAnimator, type Animator} from '../motion/animator';
import {createMotionEngine, type Animator} from '../motion/animator';
import {solveAnimatedPageScroll, solvePaging} from '../motion/paging';
import type * as motionResolution from '../motion/resolveMotion';
import {resolveMotion} from '../motion/resolveMotion';
Expand Down Expand Up @@ -305,7 +305,7 @@ function setup(options: Partial<PagerOptions> = {}, rtl = false): Fixture {
value: WIDTH,
configurable: true,
});
const animator = createMotionAnimator(track, {
const animator = createMotionEngine(track, {
now: () => now,
requestFrame: callback => {
frameQueue.push(callback);
Expand Down Expand Up @@ -1259,7 +1259,7 @@ describe('createPager', () => {
// (DR3). The drag itself follows the pointer.
function reducedPager(f: Fixture, defaultIndex: number, coarse: boolean) {
stubMedia(true, coarse);
const reduced = createMotionAnimator(f.track, {
const reduced = createMotionEngine(f.track, {
now: () => now,
requestFrame: callback => {
frameQueue.push(callback);
Expand Down Expand Up @@ -1928,7 +1928,7 @@ function setupChildren(
value: WIDTH,
configurable: true,
});
const animator = createMotionAnimator(track, {
const animator = createMotionEngine(track, {
now: () => now,
requestFrame: callback => {
frameQueue.push(callback);
Expand Down Expand Up @@ -2461,7 +2461,7 @@ describe('pre-review fixes', () => {
configurable: true,
});
const host = {attach: () => () => {}} as unknown as GestureHost;
const animator = createMotionAnimator(track, {
const animator = createMotionEngine(track, {
now: () => now,
requestFrame: callback => {
frameQueue.push(callback);
Expand Down
32 changes: 29 additions & 3 deletions packages/core/src/internal/touch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('internal/touch', () => {
const scroll = await import('../motion/uikit/scroll');
expect(entry.PanGestureRecognizer).toBe(pan.PanGestureRecognizer);
expect(entry.useGestureRecognizer).toBe(hook.useGestureRecognizer);
expect(entry.createMotionAnimator).toBe(animator.createMotionAnimator);
expect(entry.createMotionEngine).toBe(animator.createMotionEngine);
expect(entry.cubicBezier).toBe(bezier.cubicBezier);
expect(entry.mapProgress).toBe(progress.mapProgress);
expect(entry.parseCssEasing).toBe(roles.parseCssEasing);
Expand All @@ -54,8 +54,34 @@ describe('internal/touch', () => {
expect(entry.rubberBandInverse).toBe(band.rubberBandInverse);
expect(entry.rubberBandSlope).toBe(band.rubberBandSlope);
expect(entry.scroll).toBe(scroll.scroll);
// No internal factory shares a public factory's name.
expect(entry).not.toHaveProperty('createAnimator');
// No internal factory shares a public factory's name, or a planned one's.
for (const name of [
'createAnimator',
'createPanGesture',
'createDragToSnapPoints',
]) {
expect(entry, name).not.toHaveProperty(name);
}
});

it('re-exports by name what other Lab components read', async () => {
const entry: Record<string, unknown> = await import('./touch');
const presentation = await import('../utils/touchPresentation');
const direction = await import('../hooks/isRtlElement');
const uikitGestures = await import('../motion/uikit/gestures');
const touchQueries = await import('../utils/touchQueries');
expect(entry.useTouchPresentation).toBe(presentation.useTouchPresentation);
expect(entry.isRtlElement).toBe(direction.isRtlElement);
expect(entry.UIKIT_GESTURES).toBe(uikitGestures.UIKIT_GESTURES);
expect(entry.TOUCH_QUERY).toBe(touchQueries.TOUCH_QUERY);
// An identity check passes when both sides are undefined, so each name
// must also be defined.
for (const name of ['useTouchPresentation', 'isRtlElement']) {
expect(typeof entry[name], name).toBe('function');
}
for (const name of ['UIKIT_GESTURES', 'TOUCH_QUERY']) {
expect(entry[name], name).toBeDefined();
}
});

it('attaches no window or document listeners when it loads', async () => {
Expand Down
24 changes: 16 additions & 8 deletions packages/core/src/internal/touch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
/**
* @file touch.ts
* @input The gesture core: the arena, the recognizers, and their geometry;
* the pan, the motion animator and the motion math that Lab's
* useRefreshControl reads
* the pan, the motion engine and math, and the touch-presentation and
* direction helpers that Lab components read
* @output `@astryxdesign/core/internal/touch`: the gesture core's primitives
* for Lab components, re-exported by name
* and the motion, touch-presentation and direction modules for Lab
* components, re-exported by name
Comment on lines 5 to +10

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional (source-comment guide): this header now runs 22 lines, past the guide's "about 12 lines at most". @input and @output fit on one line each:

 * @input The gesture core and Core's motion, touch and direction modules
 * @output `@astryxdesign/core/internal/touch`: those modules for Lab, by name

* @position An internal entry reserved for Astryx's own packages:
* undocumented, outside the public API and its compatibility promise
* (spec:AST-017 FR21), versioned in lockstep with Core, and allowed only in
Expand Down Expand Up @@ -43,7 +44,7 @@ export {TapGestureRecognizer} from '../gestures/TapGestureRecognizer';
export {DEFAULT_ENVIRONMENT} from '../gestures/types';

// Lab's useRefreshControl reads these: its pull's pan and the hook that
// starts it. It also reads the motion animator and math, and the scroll
// starts it. It also reads the motion engine and math, and the scroll
// view's bounce spring, for its pull, settle and end. Its indicator fades
// along the settle's curve, and the motion-role parsers read the theme's
// tokens for its end.
Expand All @@ -55,12 +56,12 @@ export type {
PointerKind,
} from '../gestures/types';
export {useGestureRecognizer} from '../gestures/useGestureRecognizer';
// The animator's internal factory. The public one, `createAnimator`, plays
// The motion engine's factory. The public one, `createAnimator`, plays
// only the theme's state-change motion.
Comment on lines +59 to 60

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional (source-comment guide): "The motion engine's factory." restates the export below. The second sentence is the reason, so it can stand alone:

// The public `createAnimator` plays only the theme's state-change motion.

export {createMotionAnimator} from '../motion/animator';
export {createMotionEngine} from '../motion/animator';
export type {
Animator as MotionAnimator,
AnimatorOptions as MotionAnimatorOptions,
Animator as MotionEngine,
AnimatorOptions as MotionEngineOptions,
MotionCurve,
} from '../motion/animator';
export {cubicBezier} from '../motion/bezier';
Expand All @@ -72,3 +73,10 @@ export {
rubberBandSlope,
} from '../motion/rubberBand';
export {scroll} from '../motion/uikit/scroll';

// Lab components also read the UIKit gesture table, the touch presentation
// and its query, and an element's direction.
export {isRtlElement} from '../hooks/isRtlElement';
export {UIKIT_GESTURES} from '../motion/uikit/gestures';
export {useTouchPresentation} from '../utils/touchPresentation';
export {TOUCH_QUERY} from '../utils/touchQueries';
6 changes: 3 additions & 3 deletions packages/core/src/motion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ the timings that decide who gets a touch are not roles.
## Animator

```
import {createMotionAnimator} from '../motion/animator';
import {createMotionEngine} from '../motion/animator';
import {coast} from '../motion/coast';
import {solveSpringTowardRamp, toSpring} from '../motion/spring';
import {resolveMotion} from '../motion/resolveMotion';
Expand All @@ -78,7 +78,7 @@ import {registerSpringsTable, springs} from '../motion/uikit';
// Once, so the springs are registered as their roles:
registerSpringsTable();

const animator = createMotionAnimator(sheet);
const animator = createMotionEngine(sheet);
// The animator plays a motion role (spec:AST-094 FR4), its table value, as the
// per-role tables stay internal (DEC-15); math on a spring reads the role with
// resolveMotion.
Expand Down Expand Up @@ -175,7 +175,7 @@ await handle.finished; // true if it ran to the end, false if interrupted
- `animatorFor(element)` returns the one animator for an element, so code
that animates it from several places (a swipe on x, a removal on y)
composes in one `translate` track.
- `createMotionAnimator(dialog, {pseudoElement: '::backdrop'})` drives a
- `createMotionEngine(dialog, {pseudoElement: '::backdrop'})` drives a
pseudo-element; with no inline style to hold it, a static filling animation
holds its end value until the next animation replaces it, while the
pseudo-element exists. A dialog's `::backdrop` is made again each time the
Expand Down
Loading
Loading