panelui-native 0.97.0 → 0.98.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/README.md +1 -0
- package/lib/module/components/stack-card/index.js +1204 -0
- package/lib/module/components/stack-card/index.js.map +1 -0
- package/lib/module/components/stack-card/stack-card-geometry.js +261 -0
- package/lib/module/components/stack-card/stack-card-geometry.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/stack-card/index.d.ts +472 -0
- package/lib/typescript/src/components/stack-card/index.d.ts.map +1 -0
- package/lib/typescript/src/components/stack-card/stack-card-geometry.d.ts +126 -0
- package/lib/typescript/src/components/stack-card/stack-card-geometry.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +8 -1
- package/src/components/stack-card/index.tsx +1423 -0
- package/src/components/stack-card/stack-card-geometry.ts +279 -0
- package/src/index.ts +14 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The numbers a deck is made of, kept apart from the views that draw them so
|
|
3
|
+
* each rule can be checked on its own.
|
|
4
|
+
*
|
|
5
|
+
* Every function here is a worklet: they run inside the pan handler and the
|
|
6
|
+
* animated styles, on the UI thread, and none of them touches anything but
|
|
7
|
+
* its arguments.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Which way a card leaves the deck. */
|
|
11
|
+
export type StackCardDirection = 'left' | 'right' | 'up' | 'down';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* iOS's scroll deceleration, and the constant behind `project`.
|
|
15
|
+
*
|
|
16
|
+
* Momentum is what lets a short fast flick count and a long slow drag not.
|
|
17
|
+
* Without it the only question a release can answer is how far the finger
|
|
18
|
+
* travelled, and a deck read that way feels heavy in the hand.
|
|
19
|
+
*/
|
|
20
|
+
export const DECELERATION = 0.998;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Where a flick would come to rest if it kept decelerating, in points.
|
|
24
|
+
*
|
|
25
|
+
* The exponential-decay form the platform uses, not the `v² / 2a` from
|
|
26
|
+
* physics: the two disagree most at exactly the speeds a thumb produces.
|
|
27
|
+
*/
|
|
28
|
+
export function project(velocity: number, deceleration?: number): number {
|
|
29
|
+
'worklet';
|
|
30
|
+
/*
|
|
31
|
+
* The fallback is read here and not written as a default parameter.
|
|
32
|
+
*
|
|
33
|
+
* A worklet carries copies of the outside names it uses, and they are
|
|
34
|
+
* collected from its body — a default in the signature is not part of that
|
|
35
|
+
* body, so the constant never travels and the name is simply absent on the
|
|
36
|
+
* UI thread. That throws a ReferenceError inside a gesture handler, where
|
|
37
|
+
* nothing catches it: it leaves the worklet runtime as a C++ exception and
|
|
38
|
+
* aborts the process, with no JavaScript frames and no red screen.
|
|
39
|
+
*/
|
|
40
|
+
const rate = deceleration ?? DECELERATION;
|
|
41
|
+
return ((velocity / 1000) * rate) / (1 - rate);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* How far a finger gets on an axis the deck does not accept, given how far it
|
|
46
|
+
* pushed.
|
|
47
|
+
*
|
|
48
|
+
* Asymptotic rather than clamped. A card that stops dead at a limit reads as
|
|
49
|
+
* broken; one that keeps moving a little further for a lot more travel reads
|
|
50
|
+
* as held.
|
|
51
|
+
*/
|
|
52
|
+
export function resist(travel: number, span: number, give: number): number {
|
|
53
|
+
'worklet';
|
|
54
|
+
if (span <= 0) return 0;
|
|
55
|
+
return (travel * span * give) / (span + give * Math.abs(travel));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** How far a card has to be taken for a release to send it away, in points. */
|
|
59
|
+
export function commitReach(span: number, threshold: number): number {
|
|
60
|
+
'worklet';
|
|
61
|
+
return Math.max(span * threshold, 1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* How far a drag has carried a card toward leaving in one direction, 0 to 1.
|
|
66
|
+
*
|
|
67
|
+
* One number, and three things read it: the stamp for that direction fades in
|
|
68
|
+
* on it, the card behind climbs into place on it, and the haptic fires when it
|
|
69
|
+
* first reaches 1.
|
|
70
|
+
*/
|
|
71
|
+
export function directionProgress(
|
|
72
|
+
direction: StackCardDirection,
|
|
73
|
+
x: number,
|
|
74
|
+
y: number,
|
|
75
|
+
width: number,
|
|
76
|
+
height: number,
|
|
77
|
+
threshold: number
|
|
78
|
+
): number {
|
|
79
|
+
'worklet';
|
|
80
|
+
const sideways = direction === 'left' || direction === 'right';
|
|
81
|
+
const reach = commitReach(sideways ? width : height, threshold);
|
|
82
|
+
const travel =
|
|
83
|
+
direction === 'left' ? -x : direction === 'right' ? x : direction === 'up' ? -y : y;
|
|
84
|
+
return Math.min(Math.max(travel / reach, 0), 1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The furthest any accepted direction has been carried, 0 to 1. */
|
|
88
|
+
export function releaseProgress(
|
|
89
|
+
allowed: readonly StackCardDirection[],
|
|
90
|
+
x: number,
|
|
91
|
+
y: number,
|
|
92
|
+
width: number,
|
|
93
|
+
height: number,
|
|
94
|
+
threshold: number
|
|
95
|
+
): number {
|
|
96
|
+
'worklet';
|
|
97
|
+
let furthest = 0;
|
|
98
|
+
for (let index = 0; index < allowed.length; index += 1) {
|
|
99
|
+
const direction = allowed[index];
|
|
100
|
+
if (!direction) continue;
|
|
101
|
+
const progress = directionProgress(direction, x, y, width, height, threshold);
|
|
102
|
+
if (progress > furthest) furthest = progress;
|
|
103
|
+
}
|
|
104
|
+
return furthest;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Which way a lifted finger sent the card, or `null` to put it back.
|
|
109
|
+
*
|
|
110
|
+
* Distance and momentum are added before the comparison rather than tested one
|
|
111
|
+
* after the other, so there is a single rule: a card leaves when the finger
|
|
112
|
+
* was going to take it past the reach, whether it got there or not.
|
|
113
|
+
*
|
|
114
|
+
* Where two directions both qualify — a diagonal fling in a deck that takes
|
|
115
|
+
* all four — the one carried furthest wins, so the card goes where it was
|
|
116
|
+
* actually thrown.
|
|
117
|
+
*/
|
|
118
|
+
export function releasedDirection(
|
|
119
|
+
allowed: readonly StackCardDirection[],
|
|
120
|
+
x: number,
|
|
121
|
+
y: number,
|
|
122
|
+
velocityX: number,
|
|
123
|
+
velocityY: number,
|
|
124
|
+
width: number,
|
|
125
|
+
height: number,
|
|
126
|
+
threshold: number
|
|
127
|
+
): StackCardDirection | null {
|
|
128
|
+
'worklet';
|
|
129
|
+
const projectedX = x + project(velocityX);
|
|
130
|
+
const projectedY = y + project(velocityY);
|
|
131
|
+
|
|
132
|
+
let sent: StackCardDirection | null = null;
|
|
133
|
+
// Starting at one is the threshold itself: a direction has to reach the
|
|
134
|
+
// whole reach before it is a candidate at all.
|
|
135
|
+
let furthest = 1;
|
|
136
|
+
for (let index = 0; index < allowed.length; index += 1) {
|
|
137
|
+
const direction = allowed[index];
|
|
138
|
+
if (!direction) continue;
|
|
139
|
+
const sideways = direction === 'left' || direction === 'right';
|
|
140
|
+
const reach = commitReach(sideways ? width : height, threshold);
|
|
141
|
+
const travel =
|
|
142
|
+
direction === 'left'
|
|
143
|
+
? -projectedX
|
|
144
|
+
: direction === 'right'
|
|
145
|
+
? projectedX
|
|
146
|
+
: direction === 'up'
|
|
147
|
+
? -projectedY
|
|
148
|
+
: projectedY;
|
|
149
|
+
// Compared on the raw ratio rather than on `directionProgress`, which is
|
|
150
|
+
// capped at one: a straight fling and a nudge that barely qualifies would
|
|
151
|
+
// otherwise tie, and the nudge would win on order.
|
|
152
|
+
const ratio = travel / reach;
|
|
153
|
+
if (ratio >= furthest) {
|
|
154
|
+
furthest = ratio;
|
|
155
|
+
sent = direction;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return sent;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** How far past its own edge a card is sent, as a multiple of the card. */
|
|
162
|
+
const EXIT_OVERSHOOT = 1.35;
|
|
163
|
+
|
|
164
|
+
/** How much of a diagonal a card keeps on its way out. */
|
|
165
|
+
const EXIT_CARRY = 1.4;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Where a card is animated to on its way off.
|
|
169
|
+
*
|
|
170
|
+
* The off-axis is carried rather than zeroed, so a card thrown up and to the
|
|
171
|
+
* right leaves up and to the right. Zeroing it straightens the throw out
|
|
172
|
+
* mid-flight, which is the one moment the animation is most obviously not the
|
|
173
|
+
* finger any more.
|
|
174
|
+
*/
|
|
175
|
+
export function exitTarget(
|
|
176
|
+
direction: StackCardDirection,
|
|
177
|
+
width: number,
|
|
178
|
+
height: number,
|
|
179
|
+
x: number,
|
|
180
|
+
y: number
|
|
181
|
+
): { x: number; y: number } {
|
|
182
|
+
'worklet';
|
|
183
|
+
switch (direction) {
|
|
184
|
+
case 'left':
|
|
185
|
+
return { x: -width * EXIT_OVERSHOOT, y: y * EXIT_CARRY };
|
|
186
|
+
case 'right':
|
|
187
|
+
return { x: width * EXIT_OVERSHOOT, y: y * EXIT_CARRY };
|
|
188
|
+
case 'up':
|
|
189
|
+
return { x: x * EXIT_CARRY, y: -height * EXIT_OVERSHOOT };
|
|
190
|
+
default:
|
|
191
|
+
return { x: x * EXIT_CARRY, y: height * EXIT_OVERSHOOT };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* How long a card takes to leave, in milliseconds.
|
|
197
|
+
*
|
|
198
|
+
* Long enough that the card starts out at the speed the finger let go of it.
|
|
199
|
+
* An ease-out starts `slope` times faster than its average, so covering
|
|
200
|
+
* `distance` from `speed` takes `slope × distance / speed`. Faster than that
|
|
201
|
+
* and the card leaps ahead of the finger; slower, and it drags as though it
|
|
202
|
+
* had caught on something.
|
|
203
|
+
*
|
|
204
|
+
* Clamped both ways. A hard flick would otherwise be gone in a frame or two,
|
|
205
|
+
* which reads as the card vanishing rather than being thrown; and a card
|
|
206
|
+
* released barely moving would drift off for a second. `speed` is only what
|
|
207
|
+
* was travelling toward the way out — none at all, for a card sent by a
|
|
208
|
+
* button, gives the longest.
|
|
209
|
+
*/
|
|
210
|
+
export function exitDuration(
|
|
211
|
+
distance: number,
|
|
212
|
+
speed: number,
|
|
213
|
+
slope: number,
|
|
214
|
+
shortest: number,
|
|
215
|
+
longest: number
|
|
216
|
+
): number {
|
|
217
|
+
'worklet';
|
|
218
|
+
if (speed <= 0) return longest;
|
|
219
|
+
const matched = ((slope * Math.abs(distance)) / speed) * 1000;
|
|
220
|
+
return Math.min(Math.max(matched, shortest), longest);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Which way the card pivots, from where it was taken hold of.
|
|
225
|
+
*
|
|
226
|
+
* A card pulled by its top corner turns one way and one pulled by its bottom
|
|
227
|
+
* corner turns the other, because that is what a piece of card on a table
|
|
228
|
+
* does. Read once when the finger lands and held for the whole gesture: a
|
|
229
|
+
* lever that changed sign mid-drag would flip the card over in the hand.
|
|
230
|
+
*/
|
|
231
|
+
export function lever(grabY: number, height: number): number {
|
|
232
|
+
'worklet';
|
|
233
|
+
if (height <= 0) return 1;
|
|
234
|
+
return grabY <= height / 2 ? 1 : -1;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The card's tilt, in degrees.
|
|
239
|
+
*
|
|
240
|
+
* Measured against half the card rather than all of it: a card commits at
|
|
241
|
+
* roughly a third of its width, and scaled across the full width the tilt at
|
|
242
|
+
* the moment of release would be a couple of degrees — present in the numbers
|
|
243
|
+
* and invisible on the screen.
|
|
244
|
+
*/
|
|
245
|
+
export function tiltAngle(x: number, width: number, maxTilt: number, pivot: number): number {
|
|
246
|
+
'worklet';
|
|
247
|
+
if (width <= 0) return 0;
|
|
248
|
+
const raw = (x / (width * 0.5)) * maxTilt * pivot;
|
|
249
|
+
return Math.max(-maxTilt, Math.min(maxTilt, raw));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Where a card sits in the pile, counting the top one as 0, once the drag on
|
|
254
|
+
* the card in front of it is taken into account.
|
|
255
|
+
*
|
|
256
|
+
* This is what makes the pile continuous across a dismissal. By the time the
|
|
257
|
+
* top card has been carried far enough to leave, the one behind it has already
|
|
258
|
+
* arrived at the top position — so when it becomes the top card there is
|
|
259
|
+
* nothing left to move.
|
|
260
|
+
*/
|
|
261
|
+
export function effectiveDepth(depth: number, release: number): number {
|
|
262
|
+
'worklet';
|
|
263
|
+
return Math.max(0, depth - release);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* How opaque a card at a given depth is drawn, where `visible` is how many the
|
|
268
|
+
* pile shows behind its top card.
|
|
269
|
+
*
|
|
270
|
+
* One more card is kept than the pile shows, and this is what fades it in: one
|
|
271
|
+
* place further back than the pile goes it is invisible, and it reaches full
|
|
272
|
+
* strength exactly as it takes the last visible place. Without it a card
|
|
273
|
+
* appears out of nothing at the bottom of the pile every time one leaves the
|
|
274
|
+
* top.
|
|
275
|
+
*/
|
|
276
|
+
export function depthOpacity(depth: number, visible: number): number {
|
|
277
|
+
'worklet';
|
|
278
|
+
return Math.min(1, Math.max(0, visible + 1 - depth));
|
|
279
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1251,6 +1251,20 @@ export {
|
|
|
1251
1251
|
type SplitViewHandleProps,
|
|
1252
1252
|
type SplitViewVariant,
|
|
1253
1253
|
} from './components/split-view';
|
|
1254
|
+
export {
|
|
1255
|
+
StackCard,
|
|
1256
|
+
useStackCard,
|
|
1257
|
+
type StackCardProps,
|
|
1258
|
+
type StackCardHandle,
|
|
1259
|
+
type StackCardCardProps,
|
|
1260
|
+
type StackCardStampProps,
|
|
1261
|
+
type StackCardEmptyProps,
|
|
1262
|
+
type StackCardActionsProps,
|
|
1263
|
+
type StackCardActionProps,
|
|
1264
|
+
type StackCardDirection,
|
|
1265
|
+
type StackCardLayout,
|
|
1266
|
+
type StackCardStampColor,
|
|
1267
|
+
} from './components/stack-card';
|
|
1254
1268
|
export {
|
|
1255
1269
|
Steps,
|
|
1256
1270
|
type StepsProps,
|