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.
Files changed (30) hide show
  1. package/README.md +1 -0
  2. package/lib/module/components/bottom-sheet/index.js +19 -2
  3. package/lib/module/components/bottom-sheet/index.js.map +1 -1
  4. package/lib/module/components/bubble-chart/index.js +23 -9
  5. package/lib/module/components/bubble-chart/index.js.map +1 -1
  6. package/lib/module/components/questionnaire/index.js +242 -22
  7. package/lib/module/components/questionnaire/index.js.map +1 -1
  8. package/lib/module/components/scroll-header/index.js +650 -0
  9. package/lib/module/components/scroll-header/index.js.map +1 -0
  10. package/lib/module/components/scroll-header/scroll-header-math.js +144 -0
  11. package/lib/module/components/scroll-header/scroll-header-math.js.map +1 -0
  12. package/lib/module/index.js +1 -0
  13. package/lib/module/index.js.map +1 -1
  14. package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
  15. package/lib/typescript/src/components/bubble-chart/index.d.ts.map +1 -1
  16. package/lib/typescript/src/components/questionnaire/index.d.ts +19 -5
  17. package/lib/typescript/src/components/questionnaire/index.d.ts.map +1 -1
  18. package/lib/typescript/src/components/scroll-header/index.d.ts +237 -0
  19. package/lib/typescript/src/components/scroll-header/index.d.ts.map +1 -0
  20. package/lib/typescript/src/components/scroll-header/scroll-header-math.d.ts +99 -0
  21. package/lib/typescript/src/components/scroll-header/scroll-header-math.d.ts.map +1 -0
  22. package/lib/typescript/src/index.d.ts +1 -0
  23. package/lib/typescript/src/index.d.ts.map +1 -1
  24. package/package.json +1 -1
  25. package/src/components/bottom-sheet/index.tsx +29 -3
  26. package/src/components/bubble-chart/index.tsx +29 -9
  27. package/src/components/questionnaire/index.tsx +249 -19
  28. package/src/components/scroll-header/index.tsx +814 -0
  29. package/src/components/scroll-header/scroll-header-math.ts +147 -0
  30. 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,