@flemo/core 2.3.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/engine/__tests__/createSwipeController.declaredSwipe.test.d.ts +1 -0
- package/dist/core/engine/__tests__/createSwipeController.decoratorReplacement.test.d.ts +1 -0
- package/dist/core/engine/riderSwipe.d.ts +28 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +1546 -1481
- package/dist/transition/__tests__/resolveSwipeOptions.test.d.ts +1 -0
- package/dist/transition/resolveSwipeOptions.d.ts +76 -0
- package/dist/transition/swipeSettle.d.ts +32 -12
- package/dist/transition/typing.d.ts +158 -19
- package/dist/transition/variantMotion.d.ts +4 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { SwipeInfo, SwipeOptions, Transition } from './typing';
|
|
2
|
+
/**
|
|
3
|
+
* The swipe a transition declares, with its defaults filled in.
|
|
4
|
+
*
|
|
5
|
+
* A transition states as little as `{ direction }` and every caller downstream
|
|
6
|
+
* needs a complete answer: how far to commit, how fast, where the two sides
|
|
7
|
+
* are. Resolving that at each call site is how the defaults drift, so it
|
|
8
|
+
* happens once, here, and "a direction is a complete swipe" is a fact about
|
|
9
|
+
* this function rather than a promise made in a doc comment.
|
|
10
|
+
*/
|
|
11
|
+
export interface ResolvedSwipe {
|
|
12
|
+
direction: "x" | "y";
|
|
13
|
+
/** How far the gesture must carry the screen to navigate, in px. */
|
|
14
|
+
commitDistance: (span: number) => number;
|
|
15
|
+
/** How fast the finger must still be going to navigate regardless. */
|
|
16
|
+
commitVelocity: number;
|
|
17
|
+
/**
|
|
18
|
+
* Where the drag itself carries each side, when that is not where the pop
|
|
19
|
+
* does. `undefined` means walk the pop, which is the case for most.
|
|
20
|
+
*/
|
|
21
|
+
dragTo: {
|
|
22
|
+
current: SwipeOptions["current"];
|
|
23
|
+
prev: SwipeOptions["prev"];
|
|
24
|
+
};
|
|
25
|
+
/** Where the two sides are along their travel, 0 to 1. */
|
|
26
|
+
progress: (info: SwipeInfo, span: number, travelled: number) => {
|
|
27
|
+
current: number;
|
|
28
|
+
prev: number;
|
|
29
|
+
};
|
|
30
|
+
onStart: SwipeOptions["onStart"] | undefined;
|
|
31
|
+
onMove: SwipeOptions["onMove"] | undefined;
|
|
32
|
+
onEnd: SwipeOptions["onEnd"] | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Whether flemo drives the screens itself.
|
|
35
|
+
*
|
|
36
|
+
* Two drivers on one transform is how a bar drifts from the screen it
|
|
37
|
+
* rides, so exactly one of the two owns them, and NAMING WHERE THEY GO IS
|
|
38
|
+
* HOW A TRANSITION CLAIMS THEM. Declaring `current` or `prev` keeps the
|
|
39
|
+
* screens on the scrub whatever hooks are also written, and the hook's own
|
|
40
|
+
* writes to them are refused while everything else it animates goes
|
|
41
|
+
* through.
|
|
42
|
+
*
|
|
43
|
+
* That combination is the point rather than a leniency. A gesture that
|
|
44
|
+
* carries a morphing element about freely needs a hook for the element and
|
|
45
|
+
* has no reason to give up the screens for it — and the screens are the
|
|
46
|
+
* expensive half, being two full-screen layers. Writing a hook without
|
|
47
|
+
* naming a destination still hands them over, which is what a drag that
|
|
48
|
+
* moves the screens themselves to arbitrary places has to do.
|
|
49
|
+
*
|
|
50
|
+
* `onStart` only answers whether the gesture may begin, so it never costs
|
|
51
|
+
* the screens.
|
|
52
|
+
*/
|
|
53
|
+
drivesScreens: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The distance at which a release navigates, as a fraction of the screen's own
|
|
57
|
+
* span.
|
|
58
|
+
*
|
|
59
|
+
* 50px on the 390px screen it was chosen on — the number cupertino carried
|
|
60
|
+
* before this was shared — expressed as a fraction so a wider screen asks for
|
|
61
|
+
* proportionally more rather than the same 50px.
|
|
62
|
+
*/
|
|
63
|
+
export declare const DEFAULT_COMMIT_FRACTION: number;
|
|
64
|
+
/**
|
|
65
|
+
* The speed at which a release navigates however little it travelled, unless
|
|
66
|
+
* the transition names its own.
|
|
67
|
+
*
|
|
68
|
+
* 20 is what all three presets had written for themselves, and it is their
|
|
69
|
+
* taste rather than a law: a consumer transition asks for 300, a fifteen times
|
|
70
|
+
* harder flick. Without a way to name it, a declarative drag that wanted a
|
|
71
|
+
* different number would have to take over `onEnd` and give up the scrub for
|
|
72
|
+
* it.
|
|
73
|
+
*/
|
|
74
|
+
export declare const DEFAULT_COMMIT_VELOCITY = 20;
|
|
75
|
+
export declare const resolveSwipeOptions: (transition: Transition) => ResolvedSwipe | null;
|
|
76
|
+
export default resolveSwipeOptions;
|
|
@@ -33,20 +33,40 @@ export declare const MIN_REVERSAL_SECONDS = 0.28;
|
|
|
33
33
|
*
|
|
34
34
|
* So the floor scales with what is left to travel: a release may outrun the
|
|
35
35
|
* authored motion, because the finger genuinely was faster, but not without
|
|
36
|
-
* limit.
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* rather than as pixels per second, so it means the same thing on any screen
|
|
40
|
-
* and under any transition.
|
|
36
|
+
* limit. Expressed against the author's own clock and span rather than as
|
|
37
|
+
* pixels per second, so it means the same thing on any screen and under any
|
|
38
|
+
* transition.
|
|
41
39
|
*
|
|
42
|
-
*
|
|
43
|
-
* tops out around six or seven screen-widths a second
|
|
44
|
-
* produced the
|
|
45
|
-
*
|
|
46
|
-
* the
|
|
47
|
-
*
|
|
40
|
+
* IT WAS 3x, AND 3x WAS CHOSEN SO THAT IT WOULD NOT BIND. The argument ran: a
|
|
41
|
+
* human flick tops out around six or seven screen-widths a second, the
|
|
42
|
+
* readings that produced the vanishing screen were several times that, so a
|
|
43
|
+
* ceiling above the hand "only clips what no finger did". Both halves were
|
|
44
|
+
* true and the conclusion was backwards. A ceiling no hand can reach is not a
|
|
45
|
+
* ceiling: on cupertino's 390px over 0.7s the floor only starts to bind above
|
|
46
|
+
* 2674 px/s, so the by-speed term alone decides every release a person can
|
|
47
|
+
* actually make, and it lets one run at up to 2.9x the authored motion. That
|
|
48
|
+
* band IS the hand. Device-reported against the 3x floor itself: "조금 빠르게
|
|
49
|
+
* 하면 휙 그냥 씹히듯 지나간다".
|
|
50
|
+
*
|
|
51
|
+
* 1.2 WAS PICKED ON GLASS, from a ladder of builds identical but for this
|
|
52
|
+
* number (3 / 2 / 1.5 / 1.2, judged on a device against the same gesture). It
|
|
53
|
+
* is the first value that reaches the speeds a hand produces: 2 moves nothing
|
|
54
|
+
* below 1783 px/s and 1.5 nothing below 1337, while 1.2 binds from 1070 up. A
|
|
55
|
+
* full-width cupertino landing then bottoms out at 0.583s against its authored
|
|
56
|
+
* 0.7s, and a fast release with 30% travelled lands in 0.408s where it used to
|
|
57
|
+
* take 0.163s.
|
|
58
|
+
*
|
|
59
|
+
* WHAT IT DOES NOT TOUCH. A release slower than the ceiling is unchanged — the
|
|
60
|
+
* by-distance and by-speed terms already ask for longer than the floor, and
|
|
61
|
+
* that is most of them. A CANCEL is unchanged outright: a reversal contributes
|
|
62
|
+
* no speed, so its length is the by-distance term, which is `authored x
|
|
63
|
+
* remaining` and therefore above this floor by construction at any multiplier
|
|
64
|
+
* over 1.
|
|
65
|
+
*
|
|
66
|
+
* A RELEASE STILL OUTRUNS THE BUTTON, by a fifth. What it no longer does is
|
|
67
|
+
* outrun it threefold.
|
|
48
68
|
*/
|
|
49
|
-
export declare const MAX_RELEASE_SPEEDUP =
|
|
69
|
+
export declare const MAX_RELEASE_SPEEDUP = 1.2;
|
|
50
70
|
export interface SwipeSettleInput {
|
|
51
71
|
remainingPx: number;
|
|
52
72
|
spanPx: number;
|
|
@@ -28,43 +28,182 @@ export interface SwipeInfo {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
export type SwipeAnimate = (target: HTMLElement, value: TransitionTarget, options?: AnimationOptions) => Promise<void>;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
/**
|
|
32
|
+
* One pose along a drag, and where the drag reaches it.
|
|
33
|
+
*
|
|
34
|
+
* A single destination makes every property travel at one rate, because one
|
|
35
|
+
* progress walks one keyframe. Some drags do not: a screen may finish fading
|
|
36
|
+
* a third of the way across while it is still sliding out for the rest of it.
|
|
37
|
+
* Naming the pose at that third is what lets the two rates coexist without
|
|
38
|
+
* handing the whole drag to a hook.
|
|
39
|
+
*
|
|
40
|
+
* `at` is where along the drag this pose is reached, 0 to 1, and the last stop
|
|
41
|
+
* is the end whether or not it says so. It is spelled `at` rather than
|
|
42
|
+
* `offset` because `offset` is already a CSS property and `TransitionTarget`
|
|
43
|
+
* carries the whole CSS vocabulary.
|
|
44
|
+
*/
|
|
45
|
+
export interface SwipeStop {
|
|
46
|
+
at?: number;
|
|
47
|
+
value: TransitionTarget;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Everything a transition says about its swipe, in one place.
|
|
51
|
+
*
|
|
52
|
+
* WRITING NOTHING BUT A DIRECTION IS A COMPLETE SWIPE. The drag is this
|
|
53
|
+
* transition's own pop keyframes walked by the gesture, the release decides on
|
|
54
|
+
* the distance travelled and the speed at which the finger left, and the
|
|
55
|
+
* landing runs on the clock the controller already computes. The hooks below
|
|
56
|
+
* exist for the drag that is NOT that — a screen that shrinks and is carried
|
|
57
|
+
* about freely, say — and taking one over is what tells flemo to stand aside.
|
|
58
|
+
*/
|
|
59
|
+
export interface SwipeOptions {
|
|
60
|
+
/**
|
|
61
|
+
* The axis the gesture travels. Required, and deliberately without a
|
|
62
|
+
* default: a stack of pages swipes on x and a sheet on y, both are ordinary,
|
|
63
|
+
* and a silent guess would be wrong half the time. A transition with no
|
|
64
|
+
* swipe leaves `swipe` off altogether.
|
|
65
|
+
*/
|
|
66
|
+
direction: "x" | "y";
|
|
67
|
+
/**
|
|
68
|
+
* How far the gesture must carry the screen before the release navigates
|
|
69
|
+
* rather than returning it, in px. A number is that distance; a function is
|
|
70
|
+
* handed the screen's own span so a transition can ask for a fraction of it.
|
|
71
|
+
*
|
|
72
|
+
* The default is the iOS-derived 50px on a 390px screen, expressed as a
|
|
73
|
+
* fraction so it means the same thing on any width.
|
|
74
|
+
*
|
|
75
|
+
* The SPEED half of the same question is not authorable: every preset used
|
|
76
|
+
* the same "or the finger was still moving" floor, so it is the controller's
|
|
77
|
+
* and stays out of the way.
|
|
78
|
+
*/
|
|
79
|
+
threshold?: number | ((span: number) => number);
|
|
80
|
+
/**
|
|
81
|
+
* Where the drag carries the screen under the finger, when that is not where
|
|
82
|
+
* this
|
|
83
|
+
* transition's pop takes it.
|
|
84
|
+
*
|
|
85
|
+
* The drag is this transition's own pop walked by the finger, and for most
|
|
86
|
+
* transitions that is exactly right. For some it is not: a drag may pull a
|
|
87
|
+
* screen down that the pop only fades, or dip an opacity the pop takes all
|
|
88
|
+
* the way to zero. Those differ in SHAPE rather than in rate, so `progress`
|
|
89
|
+
* cannot express them, and before this the only way to author one was to
|
|
90
|
+
* take over `onMove` and give up the scrub.
|
|
91
|
+
*
|
|
92
|
+
* Only the destination is named. The drag starts where the screen already
|
|
93
|
+
* is, which is the same pose the pop starts from, so it is read from the
|
|
94
|
+
* same table (see FROM_VARIANT).
|
|
95
|
+
*
|
|
96
|
+
* An empty target means the side does not move at all. A LIST of stops (see
|
|
97
|
+
* `SwipeStop`) says the drag passes through poses on the way, which is how
|
|
98
|
+
* two properties travel at different rates.
|
|
99
|
+
*/
|
|
100
|
+
current?: TransitionTarget | readonly SwipeStop[];
|
|
101
|
+
/** Where the drag carries the screen underneath. Terms as `current`. */
|
|
102
|
+
prev?: TransitionTarget | readonly SwipeStop[];
|
|
103
|
+
/**
|
|
104
|
+
* The speed at which a release navigates however little it travelled, in the
|
|
105
|
+
* units of `SwipeInfo.velocity`.
|
|
106
|
+
*
|
|
107
|
+
* The distance half of the verdict asks whether the gesture went far enough;
|
|
108
|
+
* this asks whether it was still going when the finger left. A flick that
|
|
109
|
+
* covers 20px and lets go at speed reads as "go", and a slow drag parked
|
|
110
|
+
* short of `threshold` reads as "come back".
|
|
111
|
+
*
|
|
112
|
+
* The default is the 20 all three presets had written for themselves, which
|
|
113
|
+
* is their taste rather than a law: a consumer transition in this repo's own
|
|
114
|
+
* app asks for 300, a fifteen times harder flick. Without this a declarative
|
|
115
|
+
* drag has no way to ask for a different one, and taking over `onEnd` to
|
|
116
|
+
* carry a single number costs it the scrub.
|
|
117
|
+
*/
|
|
118
|
+
velocity?: number;
|
|
119
|
+
/**
|
|
120
|
+
* Where the gesture is along its own travel, 0 to 1, per side.
|
|
121
|
+
*
|
|
122
|
+
* The default is geometric: how far the screen has been carried over its own
|
|
123
|
+
* width or height. A drag that resists, eases or clamps says so here, so the
|
|
124
|
+
* FEEL stays the author's while the keyframes stay the transition's.
|
|
125
|
+
*
|
|
126
|
+
* TWO NUMBERS, NOT ONE, because the two sides legitimately walk at different
|
|
127
|
+
* rates. `material` is the case that proves it: the screen being pushed away
|
|
128
|
+
* travels its own height, so its progress keeps growing as the rubber band
|
|
129
|
+
* stretches, while the screen arriving underneath travels 56px and stops
|
|
130
|
+
* there. One scalar cannot say both, and a drag driven by the wrong one is a
|
|
131
|
+
* drag that no longer resists.
|
|
132
|
+
*
|
|
133
|
+
* `current` is the screen under the finger and `prev` is the one coming out
|
|
134
|
+
* from under it, which are the same two the hooks are handed as
|
|
135
|
+
* `currentScreen` and `prevScreen`.
|
|
136
|
+
*/
|
|
137
|
+
progress?: (info: SwipeInfo, span: number) => number | {
|
|
138
|
+
current: number;
|
|
139
|
+
prev: number;
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Whether the gesture may begin at all. Returning false abandons it before
|
|
143
|
+
* anything moves.
|
|
144
|
+
*/
|
|
145
|
+
onStart?: (event: PointerEvent, info: SwipeInfo, options: {
|
|
36
146
|
animate: SwipeAnimate;
|
|
37
147
|
currentScreen: HTMLDivElement;
|
|
38
148
|
prevScreen: HTMLDivElement;
|
|
39
149
|
onStart?: (triggered: boolean) => void;
|
|
40
150
|
}) => Promise<boolean>;
|
|
41
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Move the screens yourself, once per follow frame.
|
|
153
|
+
*
|
|
154
|
+
* TAKING THIS OVER MEANS FLEMO DOES NOT. The screens are yours for the whole
|
|
155
|
+
* drag, so the two of you can never be writing one transform between you —
|
|
156
|
+
* which is what makes a bar drift from the screen it rides. The cost is that
|
|
157
|
+
* the drag is a style write per frame rather than an animation the
|
|
158
|
+
* compositor owns, and the release then has to commit one (see the note on
|
|
159
|
+
* the scrub in createSwipeController for what that measures).
|
|
160
|
+
*/
|
|
161
|
+
onMove?: (event: PointerEvent, info: SwipeInfo, options: {
|
|
42
162
|
animate: SwipeAnimate;
|
|
43
163
|
currentScreen: HTMLDivElement;
|
|
44
164
|
prevScreen: HTMLDivElement;
|
|
45
165
|
/**
|
|
46
|
-
* Report the gesture's VERDICT so the decorator and the parts can
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* It used to be. A transition's own progress is in the transition's
|
|
52
|
-
* own unit — material's is a pull in pixels, layout's is a constant
|
|
53
|
-
* — so what reached a decorator depended on which preset it was
|
|
54
|
-
* paired with, and could not honour the 0-100 those hooks document.
|
|
166
|
+
* Report the gesture's VERDICT so the decorator and the parts can follow
|
|
167
|
+
* it. The controller supplies their progress itself, against the box the
|
|
168
|
+
* screen is actually dragged over, so a second argument is accepted for
|
|
169
|
+
* source compatibility and is not read.
|
|
55
170
|
*/
|
|
56
171
|
onProgress?: (triggered: boolean, progress?: number) => void;
|
|
57
172
|
}) => number;
|
|
58
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Land what the drag was carrying. Same terms as `onMove`.
|
|
175
|
+
*
|
|
176
|
+
* WHOEVER OWNS THE SCREENS OWNS THE VERDICT. A transition that took them
|
|
177
|
+
* over answers the commit question here, and its answer is the navigation's.
|
|
178
|
+
* One written beside a declared destination is told the answer instead: it
|
|
179
|
+
* arrives as `triggered`, the clock every participant lands on is already
|
|
180
|
+
* settled, and whatever this returns is not read — so a hook that only wants
|
|
181
|
+
* to land an element of its own does not have to restate a rule it has no
|
|
182
|
+
* opinion about.
|
|
183
|
+
*/
|
|
184
|
+
onEnd?: (event: PointerEvent, info: SwipeInfo, options: {
|
|
59
185
|
animate: SwipeAnimate;
|
|
60
186
|
currentScreen: HTMLDivElement;
|
|
61
187
|
prevScreen: HTMLDivElement;
|
|
188
|
+
/** The verdict, when flemo owns the screens and has already decided. */
|
|
189
|
+
triggered?: boolean;
|
|
62
190
|
onStart?: (triggered: boolean) => void;
|
|
63
|
-
}) => Promise<boolean>;
|
|
64
|
-
}
|
|
191
|
+
}) => Promise<boolean | void>;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* A transition's non-keyframe options.
|
|
195
|
+
*
|
|
196
|
+
* IT USED TO BE A UNION discriminated on a flat `swipeDirection`, with the
|
|
197
|
+
* three hooks required alongside it. Every option added to the swipe then had
|
|
198
|
+
* to be declared absent on the other arm as well (`swipeDirection?: never`) or
|
|
199
|
+
* a caller holding the union could not read it without narrowing first, and
|
|
200
|
+
* the padding grew with the surface. One optional object says the same thing,
|
|
201
|
+
* present is a swipe and absent is not, and says it once.
|
|
202
|
+
*/
|
|
203
|
+
export type TransitionOptions = {
|
|
65
204
|
decoratorName?: DecoratorName;
|
|
66
205
|
driver?: "native";
|
|
67
|
-
|
|
206
|
+
swipe?: SwipeOptions;
|
|
68
207
|
};
|
|
69
208
|
export interface BaseTransition {
|
|
70
209
|
name: TransitionName;
|
|
@@ -6,6 +6,10 @@ export type MotionTarget = TransitionVariantValue["value"] | InitialTarget;
|
|
|
6
6
|
export interface VariantMotion {
|
|
7
7
|
from: MotionTarget;
|
|
8
8
|
to: MotionTarget;
|
|
9
|
+
via?: readonly {
|
|
10
|
+
at: number;
|
|
11
|
+
value: MotionTarget;
|
|
12
|
+
}[];
|
|
9
13
|
duration: number;
|
|
10
14
|
delay: number;
|
|
11
15
|
ease: AnimationOptions["ease"] | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flemo/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Framework-agnostic primitives for flemo: history, navigation, transitions, task manager.",
|
|
5
5
|
"main": "./dist/index.mjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"jsdom": "^29.1.1",
|
|
46
46
|
"typescript": "^6.0.3",
|
|
47
47
|
"vite": "^8.2.2",
|
|
48
|
-
"vite-plugin-dts": "^5.0
|
|
48
|
+
"vite-plugin-dts": "^5.1.0",
|
|
49
49
|
"vitest": "^4.1.11"
|
|
50
50
|
},
|
|
51
51
|
"publishConfig": {
|