panelui-native 0.93.0 → 0.95.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/bottom-sheet/index.js +19 -2
- package/lib/module/components/bottom-sheet/index.js.map +1 -1
- package/lib/module/components/bubble-chart/index.js +23 -9
- package/lib/module/components/bubble-chart/index.js.map +1 -1
- package/lib/module/components/questionnaire/index.js +242 -22
- package/lib/module/components/questionnaire/index.js.map +1 -1
- package/lib/module/components/scroll-header/index.js +650 -0
- package/lib/module/components/scroll-header/index.js.map +1 -0
- package/lib/module/components/scroll-header/scroll-header-math.js +144 -0
- package/lib/module/components/scroll-header/scroll-header-math.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
- package/lib/typescript/src/components/bubble-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/questionnaire/index.d.ts +19 -5
- package/lib/typescript/src/components/questionnaire/index.d.ts.map +1 -1
- package/lib/typescript/src/components/scroll-header/index.d.ts +237 -0
- package/lib/typescript/src/components/scroll-header/index.d.ts.map +1 -0
- package/lib/typescript/src/components/scroll-header/scroll-header-math.d.ts +99 -0
- package/lib/typescript/src/components/scroll-header/scroll-header-math.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 +1 -1
- package/src/components/bottom-sheet/index.tsx +29 -3
- package/src/components/bubble-chart/index.tsx +29 -9
- package/src/components/questionnaire/index.tsx +249 -19
- package/src/components/scroll-header/index.tsx +814 -0
- package/src/components/scroll-header/scroll-header-math.ts +147 -0
- package/src/index.ts +9 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three numbers a collapsing header is made of, kept apart from the views
|
|
3
|
+
* that draw them so each one can be checked on its own.
|
|
4
|
+
*
|
|
5
|
+
* All three are worklets: they run inside the scroll handler and the animated
|
|
6
|
+
* styles, on the UI thread, and none of them touches anything but its
|
|
7
|
+
* arguments.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** How close to an end a band has to be before it is treated as settled. */
|
|
11
|
+
export const SNAP_EPSILON = 1;
|
|
12
|
+
|
|
13
|
+
/** Below this, a lifted finger is not going to start any momentum. */
|
|
14
|
+
export const RESTING_VELOCITY = 0.05;
|
|
15
|
+
|
|
16
|
+
/** A large block shorter than this is treated as no large block at all. */
|
|
17
|
+
export const MINIMUM_SPAN = 1;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* How long the hand-over takes when there is no large block to cross.
|
|
21
|
+
*
|
|
22
|
+
* With a block, the change is scroll-linked and lasts exactly as long as the
|
|
23
|
+
* finger says. With none there is no distance to interpolate over, so the step
|
|
24
|
+
* is timed instead — otherwise the bar's surface appears between one frame and
|
|
25
|
+
* the next, on the first point of scroll.
|
|
26
|
+
*/
|
|
27
|
+
export const HANDOVER_DURATION = 200;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The hand-over runs in three steps rather than one crossfade, and these are
|
|
31
|
+
* where each step starts and ends along the collapse.
|
|
32
|
+
*
|
|
33
|
+
* A single linear crossfade puts both titles on the screen at once, and the
|
|
34
|
+
* large block travels to exactly where the bar's title sits — so the two words
|
|
35
|
+
* land on the same pixels, half-opaque, and the screen reads as a printing
|
|
36
|
+
* error. Worse, a bar whose surface is still arriving is see-through, so the
|
|
37
|
+
* block slides up *through* whatever else the bar is carrying.
|
|
38
|
+
*
|
|
39
|
+
* Ordering them fixes both: the block is gone before it reaches the bar, the
|
|
40
|
+
* surface closes behind it, and only then does the bar's own title arrive.
|
|
41
|
+
* Nothing is ever legible on top of anything else.
|
|
42
|
+
*/
|
|
43
|
+
export const LARGE_EXIT: readonly [number, number] = [0, 0.45];
|
|
44
|
+
export const SURFACE_ARRIVE: readonly [number, number] = [0.35, 0.75];
|
|
45
|
+
export const BAR_TITLE_ARRIVE: readonly [number, number] = [0.6, 1];
|
|
46
|
+
|
|
47
|
+
/** Whether a header has a large block to cross at all. */
|
|
48
|
+
export function hasSpan(largeHeight: number): boolean {
|
|
49
|
+
'worklet';
|
|
50
|
+
return largeHeight >= MINIMUM_SPAN;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* How far through the change from large block to bar the header is, 0 to 1.
|
|
55
|
+
*
|
|
56
|
+
* `threshold` shortens the distance the crossfade runs over without changing
|
|
57
|
+
* how far the block actually travels, so a tall block can finish handing over
|
|
58
|
+
* before its last few points have left. It is floored rather than allowed to
|
|
59
|
+
* reach zero: a crossfade with no distance is a jump cut.
|
|
60
|
+
*
|
|
61
|
+
* With no large block there is no distance to interpolate over, so this is the
|
|
62
|
+
* bar's only cue that the content has moved at all — and it answers on the
|
|
63
|
+
* first point of scroll rather than waiting for a span that will never come.
|
|
64
|
+
*/
|
|
65
|
+
export function collapseProgress(offset: number, largeHeight: number, threshold: number): number {
|
|
66
|
+
'worklet';
|
|
67
|
+
const span = largeHeight * Math.min(Math.max(threshold, 0.05), 1);
|
|
68
|
+
if (span < MINIMUM_SPAN) return offset > 0 ? 1 : 0;
|
|
69
|
+
return Math.min(Math.max(offset / span, 0), 1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The band's height at a given scroll position.
|
|
74
|
+
*
|
|
75
|
+
* The bar's share is fixed and the large block's is whatever it has left, so
|
|
76
|
+
* the band and the content inset can never disagree about where the header
|
|
77
|
+
* ends. An over-scroll makes the remainder larger than the block ever was,
|
|
78
|
+
* which is what stretches a cover: it is laid out taller rather than scaled
|
|
79
|
+
* up, so a photograph in it does not go soft.
|
|
80
|
+
*/
|
|
81
|
+
export function bandHeight(
|
|
82
|
+
barBand: number,
|
|
83
|
+
largeHeight: number,
|
|
84
|
+
offset: number,
|
|
85
|
+
stretch: boolean
|
|
86
|
+
): number {
|
|
87
|
+
'worklet';
|
|
88
|
+
const remaining = largeHeight - offset;
|
|
89
|
+
const bounded = stretch ? remaining : Math.min(remaining, largeHeight);
|
|
90
|
+
return barBand + Math.max(bounded, 0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Where a lifted finger should leave the scroller, or `null` to leave it
|
|
95
|
+
* where it is.
|
|
96
|
+
*
|
|
97
|
+
* Only a band caught part-way is moved, and only once nothing else is going to
|
|
98
|
+
* move it: a fling has momentum still to run, and settling under it would
|
|
99
|
+
* fight the gesture. Past the halfway point it closes, before it opens, so the
|
|
100
|
+
* shorter journey always wins.
|
|
101
|
+
*/
|
|
102
|
+
export function snapTarget(
|
|
103
|
+
offset: number,
|
|
104
|
+
largeHeight: number,
|
|
105
|
+
velocity: number
|
|
106
|
+
): number | null {
|
|
107
|
+
'worklet';
|
|
108
|
+
if (Math.abs(velocity) > RESTING_VELOCITY) return null;
|
|
109
|
+
if (largeHeight < MINIMUM_SPAN) return null;
|
|
110
|
+
if (offset <= SNAP_EPSILON || offset >= largeHeight - SNAP_EPSILON) return null;
|
|
111
|
+
return offset >= largeHeight / 2 ? largeHeight : 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Whether a reaction's new value is a crossing worth reporting.
|
|
116
|
+
*
|
|
117
|
+
* A reaction's first run carries `null` as the previous value, and a mount is
|
|
118
|
+
* not a crossing: reporting it would hand `onCollapsedChange` a `false` for a
|
|
119
|
+
* header that has never been anywhere else. After that, only a change counts.
|
|
120
|
+
*/
|
|
121
|
+
export function isCrossing(next: boolean, previous: boolean | null): boolean {
|
|
122
|
+
'worklet';
|
|
123
|
+
return previous !== null && next !== previous;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The content inset the scrollable needs: the band's resting height, plus any
|
|
128
|
+
* top padding the caller asked for.
|
|
129
|
+
*
|
|
130
|
+
* A style array does not add, it overrides — so a caller who writes
|
|
131
|
+
* `contentContainerStyle={{ padding: 16 }}` on the child would otherwise lose
|
|
132
|
+
* the inset and start their content underneath the header. They cannot write
|
|
133
|
+
* the sum themselves either, because the band's height is measured rather than
|
|
134
|
+
* known. So their padding is read and added here, and the total is what goes
|
|
135
|
+
* on the child.
|
|
136
|
+
*
|
|
137
|
+
* Only a real number can be added to. A percentage is left out of the sum
|
|
138
|
+
* rather than guessed at, and the inset alone is used.
|
|
139
|
+
*/
|
|
140
|
+
export function contentInset(band: number, ownTopPadding: unknown): number {
|
|
141
|
+
'worklet';
|
|
142
|
+
const own =
|
|
143
|
+
typeof ownTopPadding === 'number' && Number.isFinite(ownTopPadding) && ownTopPadding > 0
|
|
144
|
+
? ownTopPadding
|
|
145
|
+
: 0;
|
|
146
|
+
return band + own;
|
|
147
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1086,6 +1086,15 @@ export {
|
|
|
1086
1086
|
ScrollFade,
|
|
1087
1087
|
type ScrollFadeProps,
|
|
1088
1088
|
} from './components/scroll-fade';
|
|
1089
|
+
export {
|
|
1090
|
+
ScrollHeader,
|
|
1091
|
+
type ScrollHeaderProps,
|
|
1092
|
+
type ScrollHeaderBarProps,
|
|
1093
|
+
type ScrollHeaderLargeProps,
|
|
1094
|
+
type ScrollHeaderActionsProps,
|
|
1095
|
+
type ScrollHeaderCoverProps,
|
|
1096
|
+
type ScrollHeaderSurface,
|
|
1097
|
+
} from './components/scroll-header';
|
|
1089
1098
|
export { Separator, type SeparatorProps } from './components/separator';
|
|
1090
1099
|
export {
|
|
1091
1100
|
Signature,
|