@butternutbox/pawprint-native 0.10.8 → 0.10.10
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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +44 -0
- package/dist/index.cjs +732 -748
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +229 -245
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/molecules/Drawer/Drawer.stories.tsx +49 -110
- package/src/components/molecules/Drawer/DrawerBody.tsx +15 -35
- package/src/components/molecules/Drawer/DrawerContent.tsx +80 -71
- package/src/components/molecules/Drawer/DrawerMeasureContext.ts +11 -15
package/package.json
CHANGED
|
@@ -205,6 +205,55 @@ export const LongContent = () => (
|
|
|
205
205
|
</View>
|
|
206
206
|
)
|
|
207
207
|
|
|
208
|
+
// Exercises the panel re-fit: toggling the extra paragraphs grows/shrinks the
|
|
209
|
+
// body after the drawer has opened. The panel should track the content —
|
|
210
|
+
// growing (up to its max, then scrolling) and shrinking back — never leaving a
|
|
211
|
+
// gap or clipping. Regression guard for the 0.10.8 re-measure feedback loop.
|
|
212
|
+
export const DynamicContent = () => {
|
|
213
|
+
const [expanded, setExpanded] = useState(false)
|
|
214
|
+
return (
|
|
215
|
+
<View style={styles.container}>
|
|
216
|
+
<Drawer.Root>
|
|
217
|
+
<Drawer.Trigger>
|
|
218
|
+
<Button variant="filled" colour="primary">
|
|
219
|
+
Open dynamic drawer
|
|
220
|
+
</Button>
|
|
221
|
+
</Drawer.Trigger>
|
|
222
|
+
<Drawer.Portal>
|
|
223
|
+
<Drawer.Overlay />
|
|
224
|
+
<Drawer.Content>
|
|
225
|
+
<Drawer.Header variant="titleAndText">
|
|
226
|
+
<Drawer.Title>Delivery details</Drawer.Title>
|
|
227
|
+
<Drawer.Close />
|
|
228
|
+
</Drawer.Header>
|
|
229
|
+
<Drawer.Body>
|
|
230
|
+
<Typography>{bodyText}</Typography>
|
|
231
|
+
<Button
|
|
232
|
+
variant="outlined"
|
|
233
|
+
colour="secondary"
|
|
234
|
+
onPress={() => setExpanded((prev) => !prev)}
|
|
235
|
+
>
|
|
236
|
+
{expanded ? "Show less" : "Show more"}
|
|
237
|
+
</Button>
|
|
238
|
+
{expanded &&
|
|
239
|
+
Array.from({ length: 12 }, (_, i) => (
|
|
240
|
+
<Typography
|
|
241
|
+
key={i}
|
|
242
|
+
>{`More detail ${i + 1}: ${bodyText}`}</Typography>
|
|
243
|
+
))}
|
|
244
|
+
</Drawer.Body>
|
|
245
|
+
<Drawer.Footer>
|
|
246
|
+
<Button variant="filled" colour="primary">
|
|
247
|
+
Confirm
|
|
248
|
+
</Button>
|
|
249
|
+
</Drawer.Footer>
|
|
250
|
+
</Drawer.Content>
|
|
251
|
+
</Drawer.Portal>
|
|
252
|
+
</Drawer.Root>
|
|
253
|
+
</View>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
|
|
208
257
|
export const Controlled = () => {
|
|
209
258
|
const [open, setOpen] = useState(false)
|
|
210
259
|
return (
|
|
@@ -321,116 +370,6 @@ export const WithTopContent = () => {
|
|
|
321
370
|
)
|
|
322
371
|
}
|
|
323
372
|
|
|
324
|
-
/**
|
|
325
|
-
* Exercises the panel re-measuring its height when body content changes.
|
|
326
|
-
* Use "Add"/"Remove" to grow and shrink the content: the drawer should re-fit
|
|
327
|
-
* its height each time, and once the content exceeds ~90% of the screen it
|
|
328
|
-
* should cap and the body should scroll instead of growing further.
|
|
329
|
-
*/
|
|
330
|
-
export const DynamicContent = () => {
|
|
331
|
-
const [count, setCount] = useState(1)
|
|
332
|
-
|
|
333
|
-
return (
|
|
334
|
-
<View style={styles.container}>
|
|
335
|
-
<Drawer.Root defaultOpen>
|
|
336
|
-
<Drawer.Trigger>
|
|
337
|
-
<Button variant="filled" colour="primary">
|
|
338
|
-
Open resizable drawer
|
|
339
|
-
</Button>
|
|
340
|
-
</Drawer.Trigger>
|
|
341
|
-
<Drawer.Portal>
|
|
342
|
-
<Drawer.Overlay />
|
|
343
|
-
<Drawer.Content>
|
|
344
|
-
<Drawer.Header variant="titleAndText">
|
|
345
|
-
<Drawer.Title>Resizable content</Drawer.Title>
|
|
346
|
-
<Drawer.Description>{`${count} paragraph${
|
|
347
|
-
count === 1 ? "" : "s"
|
|
348
|
-
}`}</Drawer.Description>
|
|
349
|
-
<Drawer.Close />
|
|
350
|
-
</Drawer.Header>
|
|
351
|
-
<Drawer.Body>
|
|
352
|
-
{Array.from({ length: count }, (_, i) => (
|
|
353
|
-
<Typography
|
|
354
|
-
key={i}
|
|
355
|
-
>{`Paragraph ${i + 1}: ${bodyText}`}</Typography>
|
|
356
|
-
))}
|
|
357
|
-
</Drawer.Body>
|
|
358
|
-
<Drawer.Footer>
|
|
359
|
-
<View style={styles.row}>
|
|
360
|
-
<Button
|
|
361
|
-
variant="filled"
|
|
362
|
-
colour="primary"
|
|
363
|
-
onPress={() => setCount((c) => c + 1)}
|
|
364
|
-
>
|
|
365
|
-
Add
|
|
366
|
-
</Button>
|
|
367
|
-
<Button
|
|
368
|
-
variant="outlined"
|
|
369
|
-
colour="secondary"
|
|
370
|
-
onPress={() => setCount((c) => Math.max(0, c - 1))}
|
|
371
|
-
>
|
|
372
|
-
Remove
|
|
373
|
-
</Button>
|
|
374
|
-
</View>
|
|
375
|
-
</Drawer.Footer>
|
|
376
|
-
</Drawer.Content>
|
|
377
|
-
</Drawer.Portal>
|
|
378
|
-
</Drawer.Root>
|
|
379
|
-
</View>
|
|
380
|
-
)
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Simulates async content loading: the drawer opens compact with a loading
|
|
385
|
-
* message, then re-fits taller once the "loaded" content arrives. Tap "Reload"
|
|
386
|
-
* to replay.
|
|
387
|
-
*/
|
|
388
|
-
export const AsyncContent = () => {
|
|
389
|
-
const [loaded, setLoaded] = useState(false)
|
|
390
|
-
|
|
391
|
-
const load = () => {
|
|
392
|
-
setLoaded(false)
|
|
393
|
-
setTimeout(() => setLoaded(true), 1200)
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return (
|
|
397
|
-
<View style={styles.container}>
|
|
398
|
-
<Drawer.Root onOpenChange={(open) => open && load()}>
|
|
399
|
-
<Drawer.Trigger>
|
|
400
|
-
<Button variant="filled" colour="primary">
|
|
401
|
-
Open async drawer
|
|
402
|
-
</Button>
|
|
403
|
-
</Drawer.Trigger>
|
|
404
|
-
<Drawer.Portal>
|
|
405
|
-
<Drawer.Overlay />
|
|
406
|
-
<Drawer.Content>
|
|
407
|
-
<Drawer.Header variant="titleAndText">
|
|
408
|
-
<Drawer.Title>Delivery details</Drawer.Title>
|
|
409
|
-
<Drawer.Close />
|
|
410
|
-
</Drawer.Header>
|
|
411
|
-
<Drawer.Body>
|
|
412
|
-
{loaded ? (
|
|
413
|
-
Array.from({ length: 5 }, (_, i) => (
|
|
414
|
-
<Typography
|
|
415
|
-
key={i}
|
|
416
|
-
>{`Detail ${i + 1}: ${bodyText}`}</Typography>
|
|
417
|
-
))
|
|
418
|
-
) : (
|
|
419
|
-
<Typography>Loading…</Typography>
|
|
420
|
-
)}
|
|
421
|
-
</Drawer.Body>
|
|
422
|
-
<Drawer.Footer>
|
|
423
|
-
<Button variant="filled" colour="primary" onPress={load}>
|
|
424
|
-
Reload
|
|
425
|
-
</Button>
|
|
426
|
-
</Drawer.Footer>
|
|
427
|
-
</Drawer.Content>
|
|
428
|
-
</Drawer.Portal>
|
|
429
|
-
</Drawer.Root>
|
|
430
|
-
</View>
|
|
431
|
-
)
|
|
432
|
-
}
|
|
433
|
-
|
|
434
373
|
const styles = StyleSheet.create({
|
|
435
374
|
container: {
|
|
436
375
|
padding: 16
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import React, { useCallback } from "react"
|
|
2
|
-
import {
|
|
3
|
-
type LayoutChangeEvent,
|
|
4
|
-
ScrollView,
|
|
5
|
-
ScrollViewProps
|
|
6
|
-
} from "react-native"
|
|
2
|
+
import { ScrollView, ScrollViewProps, useWindowDimensions } from "react-native"
|
|
7
3
|
import styled from "@emotion/native"
|
|
8
4
|
import { useTheme } from "@emotion/react"
|
|
9
5
|
import { useDrawerHeaderContext } from "./DrawerHeaderContext"
|
|
@@ -18,22 +14,21 @@ const StyledScrollView = styled(ScrollView)<{
|
|
|
18
14
|
bodyPaddingHorizontal: number
|
|
19
15
|
bodyPaddingRight: number
|
|
20
16
|
bodyGap: number
|
|
17
|
+
bodyMaxHeight: number
|
|
21
18
|
bodyPaddingBottom: number
|
|
22
19
|
}>(
|
|
23
20
|
({
|
|
24
21
|
bodyPaddingHorizontal,
|
|
25
22
|
bodyPaddingRight,
|
|
26
23
|
bodyGap,
|
|
24
|
+
bodyMaxHeight,
|
|
27
25
|
bodyPaddingBottom
|
|
28
26
|
}) => ({
|
|
29
|
-
// flex: 1 fills the bounded panel so the ScrollView can scroll. The panel
|
|
30
|
-
// (DrawerContent) is the single height authority — the body must NOT set
|
|
31
|
-
// its own maxHeight, or it would cap independently of the panel and corrupt
|
|
32
|
-
// the panel's chrome measurement.
|
|
33
27
|
flex: 1,
|
|
34
28
|
paddingLeft: bodyPaddingHorizontal,
|
|
35
29
|
paddingRight: bodyPaddingRight,
|
|
36
30
|
gap: bodyGap,
|
|
31
|
+
maxHeight: bodyMaxHeight,
|
|
37
32
|
paddingBottom: bodyPaddingBottom
|
|
38
33
|
})
|
|
39
34
|
)
|
|
@@ -43,16 +38,7 @@ const StyledScrollView = styled(ScrollView)<{
|
|
|
43
38
|
* between the header and footer.
|
|
44
39
|
*/
|
|
45
40
|
export const DrawerBody = React.forwardRef<ScrollView, DrawerBodyProps>(
|
|
46
|
-
(
|
|
47
|
-
{
|
|
48
|
-
children,
|
|
49
|
-
contentContainerStyle,
|
|
50
|
-
onLayout,
|
|
51
|
-
onContentSizeChange,
|
|
52
|
-
...props
|
|
53
|
-
},
|
|
54
|
-
ref
|
|
55
|
-
) => {
|
|
41
|
+
({ children, contentContainerStyle, onContentSizeChange, ...props }, ref) => {
|
|
56
42
|
const theme = useTheme()
|
|
57
43
|
const { spacing } = theme.tokens.components.drawer
|
|
58
44
|
const { buttons } = theme.tokens.components
|
|
@@ -60,29 +46,23 @@ export const DrawerBody = React.forwardRef<ScrollView, DrawerBodyProps>(
|
|
|
60
46
|
const headerContext = useDrawerHeaderContext()
|
|
61
47
|
const footerContext = useDrawerFooterContext()
|
|
62
48
|
const measureContext = useDrawerMeasureContext()
|
|
49
|
+
const { height: windowHeight } = useWindowDimensions()
|
|
63
50
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const topPadding = parseTokenValue(content.slot.verticalPadding)
|
|
67
|
-
|
|
68
|
-
// Report the ScrollView's frame and content heights so DrawerContent can
|
|
69
|
-
// re-fit the panel when the body content changes.
|
|
70
|
-
const handleLayout = useCallback(
|
|
71
|
-
(event: LayoutChangeEvent) => {
|
|
72
|
-
measureContext?.setBodyFrameHeight(event.nativeEvent.layout.height)
|
|
73
|
-
onLayout?.(event)
|
|
74
|
-
},
|
|
75
|
-
[measureContext, onLayout]
|
|
76
|
-
)
|
|
77
|
-
|
|
51
|
+
// Report the scroll content's natural height so DrawerContent can re-fit
|
|
52
|
+
// the panel when the body grows or shrinks.
|
|
78
53
|
const handleContentSizeChange = useCallback(
|
|
79
|
-
(width: number, height: number) => {
|
|
54
|
+
(width: number, height: number): void => {
|
|
80
55
|
measureContext?.setBodyContentHeight(height)
|
|
81
56
|
onContentSizeChange?.(width, height)
|
|
82
57
|
},
|
|
83
58
|
[measureContext, onContentSizeChange]
|
|
84
59
|
)
|
|
85
60
|
|
|
61
|
+
const gap = parseTokenValue(content.slot.gap)
|
|
62
|
+
const horizontalPadding = parseTokenValue(content.slot.horizontalPadding)
|
|
63
|
+
const topPadding = parseTokenValue(content.slot.verticalPadding)
|
|
64
|
+
const bodyMaxHeight = windowHeight - 300
|
|
65
|
+
|
|
86
66
|
// When there's no header the close button floats in the top-right corner.
|
|
87
67
|
// Reserve space on the right so body content doesn't slide under it.
|
|
88
68
|
const paddingRight =
|
|
@@ -101,8 +81,8 @@ export const DrawerBody = React.forwardRef<ScrollView, DrawerBodyProps>(
|
|
|
101
81
|
bodyPaddingHorizontal={horizontalPadding}
|
|
102
82
|
bodyPaddingRight={paddingRight}
|
|
103
83
|
bodyGap={gap}
|
|
84
|
+
bodyMaxHeight={bodyMaxHeight}
|
|
104
85
|
bodyPaddingBottom={bodyPaddingBottom}
|
|
105
|
-
onLayout={handleLayout}
|
|
106
86
|
onContentSizeChange={handleContentSizeChange}
|
|
107
87
|
contentContainerStyle={[
|
|
108
88
|
{
|
|
@@ -74,6 +74,14 @@ const StyledPanel = styled(View)<{
|
|
|
74
74
|
* the screen. Uses React Native `Animated` for entrance/exit and `PanResponder`
|
|
75
75
|
* on the grabber for drag-to-dismiss.
|
|
76
76
|
*
|
|
77
|
+
* The panel is given an explicit height (needed so DrawerBody's `flex: 1`
|
|
78
|
+
* ScrollView has a bounded parent and doesn't collapse). That height re-fits
|
|
79
|
+
* when the body content changes: DrawerBody reports its natural scroll-content
|
|
80
|
+
* height, and the panel is sized to `chrome + min(content, bodyMax)` (capped at
|
|
81
|
+
* 90% of the window). `chrome` (header + footer + padding + borders) is
|
|
82
|
+
* measured once from the first, unbounded layout and is invariant thereafter,
|
|
83
|
+
* so there is no measurement feedback loop.
|
|
84
|
+
*
|
|
77
85
|
* Must be rendered inside `Drawer.Portal`.
|
|
78
86
|
*
|
|
79
87
|
* @example
|
|
@@ -100,9 +108,9 @@ export const DrawerContent = React.forwardRef<View, DrawerContentProps>(
|
|
|
100
108
|
|
|
101
109
|
const { height: windowHeight } = useWindowDimensions()
|
|
102
110
|
const maxHeight = windowHeight * 0.9
|
|
103
|
-
// Tallest the body content
|
|
104
|
-
//
|
|
105
|
-
//
|
|
111
|
+
// Tallest the body content grows before it scrolls. Kept in sync with the
|
|
112
|
+
// `maxHeight` DrawerBody sets on its own ScrollView, so the panel's re-fit
|
|
113
|
+
// cap and the body's scroll cap agree.
|
|
106
114
|
const bodyMaxHeight = windowHeight - 300
|
|
107
115
|
|
|
108
116
|
// Keep a ref so the PanResponder (created once) always calls the latest
|
|
@@ -119,12 +127,19 @@ export const DrawerContent = React.forwardRef<View, DrawerContentProps>(
|
|
|
119
127
|
const isOpenRef = useRef(isOpen)
|
|
120
128
|
const activeAnim = useRef<Animated.CompositeAnimation | null>(null)
|
|
121
129
|
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
// and
|
|
125
|
-
// ScrollView gets a bounded container and can scroll correctly.
|
|
130
|
+
// Explicit panel height (capped at maxHeight). Applied to the wrapper +
|
|
131
|
+
// flex:1 on the panel so DrawerBody's ScrollView gets a bounded container
|
|
132
|
+
// and can scroll correctly.
|
|
126
133
|
const [panelHeight, setPanelHeight] = useState(0)
|
|
127
134
|
|
|
135
|
+
// Re-fit inputs. `chrome` is the invariant non-body height (header + footer
|
|
136
|
+
// + padding + borders); `bodyContent` is the body's natural content height;
|
|
137
|
+
// `firstNatural` is the panel's natural height at the first unbounded layout
|
|
138
|
+
// (before an explicit height is applied), used to derive chrome exactly.
|
|
139
|
+
const chromeRef = useRef(0)
|
|
140
|
+
const bodyContentRef = useRef(0)
|
|
141
|
+
const firstNaturalRef = useRef(0)
|
|
142
|
+
|
|
128
143
|
useEffect(() => {
|
|
129
144
|
const id = translateY.addListener(({ value }) => {
|
|
130
145
|
translateYValue.current = value
|
|
@@ -188,85 +203,79 @@ export const DrawerContent = React.forwardRef<View, DrawerContentProps>(
|
|
|
188
203
|
}
|
|
189
204
|
}, [isOpen, runEntry, runExit])
|
|
190
205
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// After entry has run, applyDesiredHeight (driven by the body's
|
|
195
|
-
// content size) is the SOLE authority for panel height — this handler
|
|
196
|
-
// must not keep overriding it, or the two fight and the panel gets
|
|
197
|
-
// pinned to chrome + bodyFrame (leaving a gap) instead of
|
|
198
|
-
// chrome + content.
|
|
199
|
-
if (entryRanRef.current) return
|
|
200
|
-
|
|
201
|
-
const h = nativeEvent.layout.height
|
|
202
|
-
const capped = Math.min(h, maxHeight)
|
|
203
|
-
if (capped <= 0) return
|
|
204
|
-
|
|
205
|
-
if (capped !== panelHeightRef.current) {
|
|
206
|
-
setPanelHeight(capped)
|
|
207
|
-
panelHeightRef.current = capped
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (isOpenRef.current) {
|
|
211
|
-
runEntry()
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
[runEntry, maxHeight]
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
// Re-fit the panel height whenever the body content changes. Desired height
|
|
218
|
-
// is the absolute `chrome + content` (clamped to bodyMax, then maxHeight).
|
|
219
|
-
// `chrome` (header + footer + padding + borders) is invariant for a given
|
|
220
|
-
// drawer, so it is measured ONCE and locked. Deriving it repeatedly from
|
|
221
|
-
// `panelHeight - bodyFrame` is unsafe: those two are only consistent at a
|
|
222
|
-
// settled layout, and mid-resize the frame lags, which inflates chrome and
|
|
223
|
-
// pins the panel to chrome + frame (a gap) instead of chrome + content.
|
|
224
|
-
const chromeRef = useRef(0)
|
|
225
|
-
const bodyContentRef = useRef(0)
|
|
226
|
-
|
|
227
|
-
const applyDesiredHeight = useCallback(() => {
|
|
206
|
+
// Desired panel height = chrome + body content (clamped to bodyMax, then
|
|
207
|
+
// maxHeight). Only sets state when it actually changes, to avoid loops.
|
|
208
|
+
const applyRefit = useCallback(() => {
|
|
228
209
|
const chrome = chromeRef.current
|
|
229
210
|
const content = bodyContentRef.current
|
|
230
|
-
|
|
231
|
-
if (chrome <= 0 || content <= 0 || current <= 0) return
|
|
211
|
+
if (chrome <= 0 || content <= 0) return
|
|
232
212
|
const desired = Math.min(
|
|
233
213
|
chrome + Math.min(content, bodyMaxHeight),
|
|
234
214
|
maxHeight
|
|
235
215
|
)
|
|
236
|
-
if (Math.abs(desired - current) > 1) {
|
|
216
|
+
if (Math.abs(desired - panelHeightRef.current) > 1) {
|
|
237
217
|
panelHeightRef.current = desired
|
|
238
218
|
setPanelHeight(desired)
|
|
239
219
|
}
|
|
240
|
-
}, [
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
applyDesiredHeight()
|
|
254
|
-
},
|
|
255
|
-
[applyDesiredHeight]
|
|
256
|
-
)
|
|
220
|
+
}, [bodyMaxHeight, maxHeight])
|
|
221
|
+
|
|
222
|
+
// Lock chrome once, from the first unbounded layout: there the panel wraps
|
|
223
|
+
// its content, so natural height = chrome + min(content, bodyMax). chrome is
|
|
224
|
+
// layout-invariant, so it never needs re-deriving (which is what made the
|
|
225
|
+
// earlier frame-subtraction approach fragile).
|
|
226
|
+
const lockChrome = useCallback(() => {
|
|
227
|
+
if (chromeRef.current > 0) return
|
|
228
|
+
const natural = firstNaturalRef.current
|
|
229
|
+
const content = bodyContentRef.current
|
|
230
|
+
if (natural <= 0 || content <= 0) return
|
|
231
|
+
chromeRef.current = natural - Math.min(content, bodyMaxHeight)
|
|
232
|
+
}, [bodyMaxHeight])
|
|
257
233
|
|
|
258
234
|
const setBodyContentHeight = useCallback(
|
|
259
|
-
(
|
|
260
|
-
if (
|
|
261
|
-
bodyContentRef.current =
|
|
262
|
-
|
|
235
|
+
(height: number) => {
|
|
236
|
+
if (height <= 0) return
|
|
237
|
+
bodyContentRef.current = height
|
|
238
|
+
lockChrome()
|
|
239
|
+
applyRefit()
|
|
263
240
|
},
|
|
264
|
-
[
|
|
241
|
+
[lockChrome, applyRefit]
|
|
265
242
|
)
|
|
266
243
|
|
|
267
244
|
const measureContextValue = useMemo(
|
|
268
|
-
() => ({
|
|
269
|
-
[
|
|
245
|
+
() => ({ setBodyContentHeight }),
|
|
246
|
+
[setBodyContentHeight]
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
const onLayout = useCallback(
|
|
250
|
+
({ nativeEvent }: { nativeEvent: { layout: { height: number } } }) => {
|
|
251
|
+
const h = nativeEvent.layout.height
|
|
252
|
+
const capped = Math.min(h, maxHeight)
|
|
253
|
+
if (capped <= 0) return
|
|
254
|
+
|
|
255
|
+
// First measurement only: the panel is still wrapping its content
|
|
256
|
+
// (no explicit height yet), so `h` is the natural height. Capture it to
|
|
257
|
+
// derive chrome, fix the panel height, and run the entry animation.
|
|
258
|
+
// Afterwards the panel echoes its fixed height, so re-fitting is driven
|
|
259
|
+
// solely by the body's content size (setBodyContentHeight).
|
|
260
|
+
if (entryRanRef.current) return
|
|
261
|
+
|
|
262
|
+
if (chromeRef.current <= 0) {
|
|
263
|
+
firstNaturalRef.current = h
|
|
264
|
+
lockChrome()
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (capped !== panelHeightRef.current) {
|
|
268
|
+
panelHeightRef.current = capped
|
|
269
|
+
setPanelHeight(capped)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (isOpenRef.current) {
|
|
273
|
+
runEntry()
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
applyRefit()
|
|
277
|
+
},
|
|
278
|
+
[runEntry, maxHeight, lockChrome, applyRefit]
|
|
270
279
|
)
|
|
271
280
|
|
|
272
281
|
const panResponder = useRef(
|
|
@@ -1,22 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createContext, useContext } from "react"
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Lets `DrawerBody` report its scroll content's natural height up to
|
|
5
|
+
* `DrawerContent`, which uses it to re-fit the panel when the content grows or
|
|
6
|
+
* shrinks after open. A ScrollView's content size is measured in an unbounded
|
|
7
|
+
* container, so it reflects the true content height independently of the
|
|
8
|
+
* ScrollView's own (bounded) frame — no measurement feedback loop.
|
|
9
|
+
*/
|
|
3
10
|
export type DrawerMeasureContextValue = {
|
|
4
|
-
/**
|
|
5
|
-
* The drawer body's ScrollView frame height (its laid-out size). Together
|
|
6
|
-
* with the panel height this yields the fixed "chrome" (header + footer +
|
|
7
|
-
* padding), since panel height always equals chrome + body frame.
|
|
8
|
-
*/
|
|
9
|
-
setBodyFrameHeight: (height: number) => void
|
|
10
|
-
/**
|
|
11
|
-
* The drawer body's content height (full scrollable content). Drives the
|
|
12
|
-
* panel's desired height as `chrome + content`, so the drawer re-fits when
|
|
13
|
-
* content changes.
|
|
14
|
-
*/
|
|
15
11
|
setBodyContentHeight: (height: number) => void
|
|
16
12
|
}
|
|
17
13
|
|
|
18
14
|
export const DrawerMeasureContext =
|
|
19
|
-
|
|
15
|
+
createContext<DrawerMeasureContextValue | null>(null)
|
|
20
16
|
|
|
21
|
-
export const useDrawerMeasureContext = () =>
|
|
22
|
-
|
|
17
|
+
export const useDrawerMeasureContext = (): DrawerMeasureContextValue | null =>
|
|
18
|
+
useContext(DrawerMeasureContext)
|