Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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

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: @input now names the motion, touch and direction modules, but @output still says "the gesture core's primitives". Suggested:

 * @output `@astryxdesign/core/internal/touch`: the gesture, motion,
 *   touch-presentation and direction modules Lab uses, re-exported by name

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 3946ad2: @output now reads "the gesture, motion, touch-presentation and direction modules Lab uses, re-exported by name".

* 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