@lodev09/react-native-true-sheet 4.0.0-beta.14 → 4.0.0-beta.15

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 (54) hide show
  1. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +28 -6
  2. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +99 -7
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +12 -0
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +18 -7
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +40 -39
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +18 -8
  7. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +14 -8
  8. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +8 -1
  9. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +32 -5
  10. package/ios/TrueSheetContainerView.h +17 -4
  11. package/ios/TrueSheetContainerView.mm +23 -6
  12. package/ios/TrueSheetContentView.h +13 -1
  13. package/ios/TrueSheetContentView.mm +93 -35
  14. package/ios/TrueSheetFooterView.mm +19 -5
  15. package/ios/TrueSheetView.mm +29 -28
  16. package/ios/TrueSheetViewController.h +21 -1
  17. package/ios/TrueSheetViewController.mm +54 -16
  18. package/ios/core/TrueSheetDetentCalculator.h +6 -1
  19. package/ios/core/TrueSheetDetentCalculator.mm +9 -4
  20. package/ios/core/TrueSheetKeyboardObserver.mm +2 -0
  21. package/lib/module/TrueSheet.js +8 -5
  22. package/lib/module/TrueSheet.js.map +1 -1
  23. package/lib/module/TrueSheet.web.js +135 -32
  24. package/lib/module/TrueSheet.web.js.map +1 -1
  25. package/lib/module/TrueSheetPeek.web.js +20 -15
  26. package/lib/module/TrueSheetPeek.web.js.map +1 -1
  27. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +5 -3
  28. package/lib/module/navigation/expo-router/index.js +5 -4
  29. package/lib/module/navigation/expo-router/index.js.map +1 -1
  30. package/lib/module/web/constants.js +1 -1
  31. package/lib/module/web/constants.js.map +1 -1
  32. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  33. package/lib/typescript/src/TrueSheet.types.d.ts +64 -25
  34. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  35. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  36. package/lib/typescript/src/TrueSheetPeek.web.d.ts +8 -4
  37. package/lib/typescript/src/TrueSheetPeek.web.d.ts.map +1 -1
  38. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +5 -3
  39. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  40. package/lib/typescript/src/navigation/expo-router/index.d.ts +5 -3
  41. package/lib/typescript/src/navigation/expo-router/index.d.ts.map +1 -1
  42. package/lib/typescript/src/navigation/types.d.ts +1 -1
  43. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  44. package/lib/typescript/src/web/constants.d.ts +1 -1
  45. package/lib/typescript/src/web/constants.d.ts.map +1 -1
  46. package/package.json +7 -6
  47. package/src/TrueSheet.tsx +7 -4
  48. package/src/TrueSheet.types.ts +67 -25
  49. package/src/TrueSheet.web.tsx +154 -30
  50. package/src/TrueSheetPeek.web.tsx +25 -23
  51. package/src/fabric/TrueSheetViewNativeComponent.ts +5 -3
  52. package/src/navigation/expo-router/index.ts +8 -11
  53. package/src/navigation/types.ts +3 -2
  54. package/src/web/constants.ts +1 -1
@@ -35,12 +35,16 @@ import type {
35
35
  WillFocusEvent,
36
36
  WillPresentEvent,
37
37
  } from './TrueSheet.types';
38
- import { measurePeekContentHeight, TrueSheetPeekContext } from './TrueSheetPeek.web';
38
+ import {
39
+ measurePeekContentHeight,
40
+ TrueSheetPeekContext,
41
+ type TrueSheetPeekContextValue,
42
+ } from './TrueSheetPeek.web';
39
43
  import { usePortalContainer, useRegisterSheet, useSheetStack } from './TrueSheetProvider.web';
40
44
  import {
41
45
  COLOR_SURFACE_CONTAINER_LOW_DARK,
42
46
  COLOR_SURFACE_CONTAINER_LOW_LIGHT,
43
- DEFAULT_ANCHOR_OFFSET,
47
+ DEFAULT_PLACEMENT_OFFSET,
44
48
  DEFAULT_CORNER_RADIUS,
45
49
  DEFAULT_DETACHED_OFFSET,
46
50
  DEFAULT_FORM_SHEET_HEIGHT_RATIO,
@@ -78,8 +82,8 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
78
82
  backgroundColor: backgroundColorProp,
79
83
  maxContentHeight,
80
84
  maxContentWidth,
81
- anchor = 'center',
82
- anchorOffset = DEFAULT_ANCHOR_OFFSET,
85
+ placement = 'automatic',
86
+ placementOffset = DEFAULT_PLACEMENT_OFFSET,
83
87
  scrollableRef,
84
88
  scrollableOptions,
85
89
  grabber = true,
@@ -291,13 +295,9 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
291
295
  // within the content — measured by the peek against `contentRef`.
292
296
  const [peekContentHeight, setPeekContentHeight] = useState(0);
293
297
  const contentRef = useRef<View>(null);
294
- const peekElRef = useRef<View>(null);
295
- const peekContext = useMemo(() => ({ contentRef, peekRef: peekElRef, setPeekContentHeight }), []);
298
+ const peekElRef = useRef<View | null>(null);
296
299
 
297
- // The peek's own onLayout only fires when *it* resizes. Content added above
298
- // it moves it without resizing it, so re-measure its offset whenever the
299
- // content view's layout changes.
300
- const handleContentLayout = useCallback(() => {
300
+ const measurePeek = useCallback(() => {
301
301
  const peekEl = getDOMElement(peekElRef.current);
302
302
  const contentEl = getDOMElement(contentRef.current);
303
303
  if (peekEl && contentEl) {
@@ -305,6 +305,32 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
305
305
  }
306
306
  }, []);
307
307
 
308
+ // Last-attached wins — during a screen swap the incoming peek mounts before
309
+ // the outgoing one unmounts, so the outgoing detach must not clear it.
310
+ const peekContext = useMemo<TrueSheetPeekContextValue>(
311
+ () => ({
312
+ contentRef,
313
+ attachPeek: (view) => {
314
+ peekElRef.current = view;
315
+ measurePeek();
316
+ },
317
+ detachPeek: (view) => {
318
+ if (peekElRef.current !== view) return;
319
+ peekElRef.current = null;
320
+ setPeekContentHeight(0);
321
+ },
322
+ measurePeek: (view) => {
323
+ if (peekElRef.current === view) measurePeek();
324
+ },
325
+ }),
326
+ [measurePeek]
327
+ );
328
+
329
+ // The peek's own onLayout only fires when *it* resizes. Content added above
330
+ // it moves it without resizing it, so re-measure its offset whenever the
331
+ // content view's layout changes.
332
+ const handleContentLayout = measurePeek;
333
+
308
334
  // An absolute (floating) header overlaps the content, so it contributes no
309
335
  // height to the 'auto' detent measurement.
310
336
  const absoluteHeader = headerOptions?.position === 'absolute';
@@ -354,10 +380,15 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
354
380
  const [hasBoundedScrollable, setHasBoundedScrollable] = useState(false);
355
381
  const [scrollableAutoHeight, setScrollableAutoHeight] = useState(0);
356
382
  const detectedScrollerRef = useRef<HTMLElement | null>(null);
383
+ // Bottom padding applied to the plugged scrollable for an absolute footer
384
+ // (see the footer inset effect below)
385
+ const footerInsetRef = useRef(0);
357
386
 
358
387
  // Natural content height (header + content + footer, with a detected
359
388
  // scrollable's viewport replaced by its content size) — the height the
360
- // content wants regardless of the sheet's bounds.
389
+ // content wants regardless of the sheet's bounds. An absolute footer's inset
390
+ // on the scrollable counts like a relative footer's height, so the content
391
+ // ends above the footer (mirrors native's auto detent).
361
392
  const measureNaturalHeight = useCallback(() => {
362
393
  const contentEl = getDOMElement(contentRef.current);
363
394
  if (!contentEl || !contentEl.isConnected) return 0;
@@ -368,7 +399,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
368
399
  const scroller = detectedScrollerRef.current;
369
400
  const scrollContent = scroller?.firstElementChild;
370
401
  if (scroller?.isConnected && scrollContent instanceof HTMLElement) {
371
- height += scrollContent.offsetHeight - scroller.clientHeight;
402
+ height += scrollContent.offsetHeight - scroller.clientHeight + footerInsetRef.current;
372
403
  }
373
404
  return Math.max(0, height);
374
405
  }, []);
@@ -731,6 +762,99 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
731
762
  [interpolateFromPosition, updateSizedLayoutHeight]
732
763
  );
733
764
 
765
+ // Web mirror of native's footer inset (`contentInsetAdjustment`
766
+ // 'automatic' | 'footer'): pad the plugged scrollable by how much the
767
+ // absolute footer covers it. Measured from layout offsets (not client rects)
768
+ // so in-flight transforms don't skew it — the footer's bottom sits at the
769
+ // visible bottom of the layout container, so only a scroller reaching that
770
+ // edge gets an inset, and a safe-area lift (or a short scroller) shrinks it.
771
+ // Padding on the scroll container extends its scrollable overflow, like a
772
+ // native content inset.
773
+ const insetBehavior = scrollableOptions?.contentInsetAdjustment ?? 'automatic';
774
+ const footerInsetAdjustment =
775
+ Boolean(footer) &&
776
+ absoluteFooter &&
777
+ (insetBehavior === 'automatic' || insetBehavior === 'footer');
778
+
779
+ useEffect(() => {
780
+ if (!isOpen || !footerInsetAdjustment || !scrollableRef) return undefined;
781
+
782
+ let canceled = false;
783
+ let rafId = 0;
784
+ let mutationObserver: MutationObserver | null = null;
785
+ let resizeObserver: ResizeObserver | null = null;
786
+ let scroller: HTMLElement | null = null;
787
+
788
+ const reset = () => {
789
+ if (scroller) scroller.style.paddingBottom = '';
790
+ footerInsetRef.current = 0;
791
+ };
792
+
793
+ const apply = () => {
794
+ const footerEl = getDOMElement(footerElRef.current);
795
+ const container = sizedLayoutRef.current ?? drawerContentRef.current;
796
+ if (!scroller?.isConnected || !footerEl || !container) return;
797
+
798
+ // Measure without our own padding so an unbounded scroller (sized by its
799
+ // content) doesn't grow by the inset and feed back into the overlap.
800
+ scroller.style.paddingBottom = '';
801
+ let bottom = scroller.offsetTop + scroller.offsetHeight;
802
+ let el = scroller.offsetParent as HTMLElement | null;
803
+ while (el && el !== container) {
804
+ bottom += el.offsetTop;
805
+ el = el.offsetParent as HTMLElement | null;
806
+ }
807
+ const footerTop = container.offsetHeight - footerEl.offsetHeight;
808
+ const inset = el ? Math.max(0, bottom - footerTop) : 0;
809
+ footerInsetRef.current = inset;
810
+ scroller.style.paddingBottom = inset > 0 ? `${inset}px` : '';
811
+ };
812
+
813
+ // (Re)resolve the plugged scrollable — a conditional remount swaps the node
814
+ const observe = () => {
815
+ const resolved = getScrollableElement(scrollableRef.current);
816
+ const next = resolved?.isConnected ? resolved : null;
817
+ if (next === scroller && resizeObserver) return;
818
+
819
+ reset();
820
+ resizeObserver?.disconnect();
821
+ resizeObserver = null;
822
+ scroller = next;
823
+ if (!scroller) return;
824
+
825
+ resizeObserver = new ResizeObserver(apply);
826
+ resizeObserver.observe(scroller);
827
+ const footerEl = getDOMElement(footerElRef.current);
828
+ if (footerEl) resizeObserver.observe(footerEl);
829
+ const container = sizedLayoutRef.current ?? drawerContentRef.current;
830
+ if (container) resizeObserver.observe(container);
831
+ apply();
832
+ };
833
+
834
+ const attach = () => {
835
+ if (canceled) return;
836
+ const drawerEl = drawerContentRef.current;
837
+ if (!drawerEl || !drawerEl.isConnected) {
838
+ // Radix Presence defers the portal mount; poll until the drawer is live.
839
+ rafId = window.requestAnimationFrame(attach);
840
+ return;
841
+ }
842
+ observe();
843
+ mutationObserver = new MutationObserver(observe);
844
+ mutationObserver.observe(drawerEl, { childList: true, subtree: true });
845
+ };
846
+
847
+ rafId = window.requestAnimationFrame(attach);
848
+
849
+ return () => {
850
+ canceled = true;
851
+ window.cancelAnimationFrame(rafId);
852
+ mutationObserver?.disconnect();
853
+ resizeObserver?.disconnect();
854
+ reset();
855
+ };
856
+ }, [isOpen, footerInsetAdjustment, scrollableRef]);
857
+
734
858
  // Fire onMount once after first render. React-mount is the earliest point
735
859
  // the component is ready for imperative calls, matching the native
736
860
  // "ready for present" contract. Declared before the present/dismiss effect
@@ -1128,13 +1252,13 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
1128
1252
  // Lift content above iOS home indicator / bottom safe area when enabled.
1129
1253
  // A relative footer owns the inset instead (see resolvedFooterStyle); an
1130
1254
  // absolute footer floats over the content, so the content keeps its lift.
1131
- // A plugged scrollable keeps the lift too (contentInsetAdjustmentBehavior,
1132
- // default on) — disable it to scroll edge-to-edge behind the indicator
1133
- // with user-applied content padding.
1255
+ // A plugged scrollable keeps the lift too (contentInsetAdjustment
1256
+ // 'automatic' | 'safe-area') — 'footer' | 'never' scroll edge-to-edge
1257
+ // behind the indicator with user-applied content padding.
1134
1258
  paddingBottom:
1135
1259
  insetAdjustment === 'automatic' &&
1136
1260
  !(footerOwnsInset && !absoluteFooter) &&
1137
- (!scrollableRef || (scrollableOptions?.contentInsetAdjustmentBehavior ?? true))
1261
+ (!scrollableRef || insetBehavior === 'automatic' || insetBehavior === 'safe-area')
1138
1262
  ? 'env(safe-area-inset-bottom, 0px)'
1139
1263
  : 0,
1140
1264
  }),
@@ -1145,7 +1269,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
1145
1269
  footerOwnsInset,
1146
1270
  absoluteFooter,
1147
1271
  scrollableRef,
1148
- scrollableOptions,
1272
+ insetBehavior,
1149
1273
  ]
1150
1274
  );
1151
1275
 
@@ -1184,7 +1308,7 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
1184
1308
  // `maxContentWidth` is ignored ('form' is absolute).
1185
1309
  // - presentation='page' → `maxContentWidth` (any viewport) or
1186
1310
  // DEFAULT_MAX_WIDTH (tablet/landscape readability cap).
1187
- // Detached without a width constraint applies anchorOffset on both edges so
1311
+ // Detached without a width constraint applies placementOffset on both edges so
1188
1312
  // the floating card breathes from the viewport sides.
1189
1313
  const wrapperStyle = useMemo<React.CSSProperties | undefined>(() => {
1190
1314
  // Mobile portrait ignores width sizing entirely (matches iOS/Android:
@@ -1203,30 +1327,30 @@ const TrueSheetComponent = forwardRef<TrueSheetMethods, TrueSheetProps>((props,
1203
1327
  if (dropShadow) next.filter = dropShadow;
1204
1328
  if (!needsMargins) return next;
1205
1329
 
1206
- let marginLeft: number | string;
1207
- let marginRight: number | string;
1330
+ let marginInlineStart: number | string;
1331
+ let marginInlineEnd: number | string;
1208
1332
  if (isFormSheet) {
1209
- marginLeft = 'auto';
1210
- marginRight = 'auto';
1333
+ marginInlineStart = 'auto';
1334
+ marginInlineEnd = 'auto';
1211
1335
  } else if (maxWidth == null) {
1212
- marginLeft = anchorOffset;
1213
- marginRight = anchorOffset;
1336
+ marginInlineStart = placementOffset;
1337
+ marginInlineEnd = placementOffset;
1214
1338
  } else {
1215
- marginLeft = anchor === 'left' ? anchorOffset : 'auto';
1216
- marginRight = anchor === 'right' ? anchorOffset : 'auto';
1339
+ marginInlineStart = placement === 'leading' ? placementOffset : 'auto';
1340
+ marginInlineEnd = placement === 'trailing' ? placementOffset : 'auto';
1217
1341
  }
1218
1342
 
1219
1343
  if (maxWidth != null) next.maxWidth = maxWidth;
1220
- next.marginLeft = marginLeft;
1221
- next.marginRight = marginRight;
1344
+ next.marginInlineStart = marginInlineStart;
1345
+ next.marginInlineEnd = marginInlineEnd;
1222
1346
  return next;
1223
1347
  }, [
1224
1348
  isLandscapeOrTablet,
1225
1349
  isFormSheet,
1226
1350
  maxContentWidth,
1227
1351
  presentation,
1228
- anchor,
1229
- anchorOffset,
1352
+ placement,
1353
+ placementOffset,
1230
1354
  effectiveDetached,
1231
1355
  dropShadow,
1232
1356
  ]);
@@ -1,16 +1,15 @@
1
- import { createContext, type RefObject, useContext, useEffect, useRef } from 'react';
1
+ import { createContext, type RefObject, useContext, useLayoutEffect, useRef } from 'react';
2
2
  import { type LayoutChangeEvent, View, type ViewProps } from 'react-native';
3
3
 
4
- import { getDOMElement } from './web/dom';
5
-
6
4
  /**
7
- * Reports the measured peek content height to the owning TrueSheet.
5
+ * Registers a peek with the owning TrueSheet so it can measure the peek live.
8
6
  * @internal
9
7
  */
10
8
  export interface TrueSheetPeekContextValue {
11
9
  contentRef: RefObject<View | null>;
12
- peekRef: RefObject<View | null>;
13
- setPeekContentHeight: (height: number) => void;
10
+ attachPeek: (view: View) => void;
11
+ detachPeek: (view: View) => void;
12
+ measurePeek: (view: View) => void;
14
13
  }
15
14
 
16
15
  export const TrueSheetPeekContext = createContext<TrueSheetPeekContextValue | null>(null);
@@ -19,14 +18,19 @@ export const TrueSheetPeekContext = createContext<TrueSheetPeekContextValue | nu
19
18
  * Distance from the top of the content view to the bottom of the peek view,
20
19
  * so the peek view's offset within the content (padding, views above it)
21
20
  * counts toward the peek detent. Both rects live in the drawer's transformed
22
- * subtree, so an in-flight translate cancels out of the delta.
21
+ * subtree, so an in-flight translate cancels out of the delta. Rounded so the
22
+ * sub-pixel noise of a mid-transition measurement doesn't register as a
23
+ * change — the content re-measures every frame while the snap animates, and
24
+ * a new value would restart the snap.
23
25
  * @internal
24
26
  */
25
27
  export const measurePeekContentHeight = (
26
28
  peekElement: HTMLElement,
27
29
  contentElement: HTMLElement
28
30
  ): number =>
29
- peekElement.getBoundingClientRect().bottom - contentElement.getBoundingClientRect().top;
31
+ Math.round(
32
+ peekElement.getBoundingClientRect().bottom - contentElement.getBoundingClientRect().top
33
+ );
30
34
 
31
35
  /**
32
36
  * Wrapper component that marks its children as the sheet's peek content.
@@ -36,25 +40,23 @@ export const measurePeekContentHeight = (
36
40
  */
37
41
  export const TrueSheetPeek = ({ onLayout, ...rest }: ViewProps) => {
38
42
  const context = useContext(TrueSheetPeekContext);
39
- const localRef = useRef<View>(null);
43
+ const viewRef = useRef<View>(null);
40
44
 
41
- // Register into the sheet's peek ref so it can measure the peek live
42
- // during detent geometry computation (state lags the first willPresent).
43
- const viewRef = context?.peekRef ?? localRef;
45
+ // Attach by instance rather than sharing a ref — during a screen swap the
46
+ // incoming peek mounts before the outgoing one unmounts, and a shared ref
47
+ // would be nulled by the outgoing unmount.
48
+ useLayoutEffect(() => {
49
+ const view = viewRef.current;
50
+ if (!context || !view) return;
44
51
 
45
- useEffect(() => () => context?.setPeekContentHeight(0), [context]);
52
+ context.attachPeek(view);
53
+ return () => context.detachPeek(view);
54
+ }, [context]);
46
55
 
47
56
  const handleLayout = (event: LayoutChangeEvent) => {
48
- if (context) {
49
- const peekElement = getDOMElement(viewRef.current);
50
- const contentElement = getDOMElement(context.contentRef.current);
51
-
52
- const bottom =
53
- peekElement && contentElement
54
- ? measurePeekContentHeight(peekElement, contentElement)
55
- : event.nativeEvent.layout.height;
56
-
57
- context.setPeekContentHeight(bottom);
57
+ const view = viewRef.current;
58
+ if (context && view) {
59
+ context.measurePeek(view);
58
60
  }
59
61
 
60
62
  onLayout?.(event);
@@ -35,7 +35,7 @@ type BlurOptionsType = Readonly<{
35
35
  type ScrollEdgeEffect = 'automatic' | 'hard' | 'soft' | 'hidden';
36
36
 
37
37
  type ScrollableOptionsType = Readonly<{
38
- contentInsetAdjustmentBehavior?: WithDefault<boolean, true>;
38
+ contentInsetAdjustment?: WithDefault<'automatic' | 'safe-area' | 'footer' | 'never', 'automatic'>;
39
39
  keyboardScrollOffset?: WithDefault<Double, 0>;
40
40
  keyboardOffset?: WithDefault<Double, 0>;
41
41
  scrollingExpandsSheet?: WithDefault<boolean, true>;
@@ -48,6 +48,7 @@ type FooterOptionsType = Readonly<{
48
48
  // the field name, so a second `position` would redefine TrueSheetViewPosition
49
49
  footerPosition?: WithDefault<'relative' | 'absolute', 'relative'>;
50
50
  keyboardOffset?: WithDefault<Double, 0>;
51
+ avoidKeyboard?: WithDefault<boolean, true>;
51
52
  }>;
52
53
 
53
54
  type HeaderOptionsType = Readonly<{
@@ -109,14 +110,15 @@ export interface NativeProps extends ViewProps {
109
110
  'none'
110
111
  >;
111
112
 
112
- anchor?: WithDefault<'left' | 'center' | 'right', 'center'>;
113
- anchorOffset?: WithDefault<Double, 16>;
113
+ placement?: WithDefault<'automatic' | 'leading' | 'center' | 'trailing', 'automatic'>;
114
+ placementOffset?: WithDefault<Double, 16>;
114
115
  insetAdjustment?: WithDefault<'automatic' | 'never', 'automatic'>;
115
116
 
116
117
  // Blur options
117
118
  blurOptions?: BlurOptionsType;
118
119
 
119
120
  // Boolean properties - match defaults from TrueSheet.types.ts
121
+ glass?: WithDefault<boolean, true>;
120
122
  grabber?: WithDefault<boolean, true>;
121
123
  grabberOptions?: GrabberOptionsType;
122
124
  accessibilityOptions?: AccessibilityOptionsType;
@@ -22,12 +22,15 @@ const sheetRouter: TrueSheetRouterFactory = createTrueSheetRouter(
22
22
  StackRouter as StackRouterFactory
23
23
  );
24
24
 
25
+ // `routes`/`dispatch` are injected by `createProps`, so they are declared as
26
+ // CreateProps (last generic) and excluded from the public navigator props
25
27
  const SheetNavigator = unstable_integrateWithRouter<
26
28
  TrueSheetNavigationOptions,
27
29
  TrueSheetNavigationState<ParamListBase>,
28
30
  TrueSheetStandardEventMap,
29
- TrueSheetNavigatorContentExtraProps,
30
- TrueSheetRouterOptions
31
+ object,
32
+ TrueSheetRouterOptions,
33
+ TrueSheetNavigatorContentExtraProps
31
34
  >(trueSheetNavigator, sheetRouter, {
32
35
  createProps: ({ state, dispatch }) => ({
33
36
  routes: state.routes,
@@ -35,15 +38,9 @@ const SheetNavigator = unstable_integrateWithRouter<
35
38
  }),
36
39
  });
37
40
 
38
- // Public props: `routes`/`dispatch` are injected by `createProps` (optional here
39
- // so the navigator type stays comparable), and expo-router intersects the event
40
- // map with an index signature which breaks precisely typed `screenListeners`
41
- // callbacks - replace it with the exact map
42
- type SheetProps = Omit<
43
- React.ComponentProps<typeof SheetNavigator>,
44
- 'screenListeners' | 'routes' | 'dispatch'
45
- > &
46
- Partial<Pick<React.ComponentProps<typeof SheetNavigator>, 'routes' | 'dispatch'>> &
41
+ // expo-router intersects the event map with an index signature which breaks
42
+ // precisely typed `screenListeners` callbacks - replace it with the exact map
43
+ type SheetProps = Omit<React.ComponentProps<typeof SheetNavigator>, 'screenListeners'> &
47
44
  Pick<TrueSheetNavigatorProps, 'screenListeners'>;
48
45
 
49
46
  /**
@@ -216,6 +216,7 @@ export type TrueSheetNavigationSheetProps = Pick<
216
216
  | 'dimmedDetentIndex'
217
217
  | 'backgroundBlur'
218
218
  | 'blurOptions'
219
+ | 'glass'
219
220
  | 'maxContentHeight'
220
221
  | 'maxContentWidth'
221
222
  | 'presentation'
@@ -229,8 +230,8 @@ export type TrueSheetNavigationSheetProps = Pick<
229
230
  | 'scrollableRef'
230
231
  | 'scrollableOptions'
231
232
  | 'insetAdjustment'
232
- | 'anchor'
233
- | 'anchorOffset'
233
+ | 'placement'
234
+ | 'placementOffset'
234
235
  | 'elevation'
235
236
  | 'detached'
236
237
  | 'detachedOffset'
@@ -4,7 +4,7 @@ export const DEFAULT_FORM_SHEET_WIDTH = 580;
4
4
  export const DEFAULT_FORM_SHEET_HEIGHT_RATIO = 0.5;
5
5
  export const COLOR_SURFACE_CONTAINER_LOW_LIGHT = '#F7F2FA';
6
6
  export const COLOR_SURFACE_CONTAINER_LOW_DARK = '#1D1B20';
7
- export const DEFAULT_ANCHOR_OFFSET = 16;
7
+ export const DEFAULT_PLACEMENT_OFFSET = 16;
8
8
  export const DEFAULT_DETACHED_OFFSET = 16;
9
9
  export const DEFAULT_GRABBER_COLOR_LIGHT = 'rgba(0, 0, 0, 0.3)';
10
10
  export const DEFAULT_GRABBER_COLOR_DARK = 'rgba(255, 255, 255, 0.3)';