@rific/auto-paper 0.12.1 → 0.12.3

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 CHANGED
@@ -265,7 +265,7 @@ export default function App() {
265
265
  | `onChange` | `(settings: ThemeSettings) => void` | Called whenever settings change via `useThemeSettings().set()` |
266
266
  | `children` | `ReactNode` | |
267
267
  | `defaults` | `PaperDefaults` | Prop defaults for wrapper components (see below) |
268
- | `expoBlur` | `ExpoBlurModule` | Injects `expo-blur` (`import * as ExpoBlur from 'expo-blur'`) so `<BlurView>` renders the real frosted-glass effect. Omit to always render its solid fallback. |
268
+ | `expoBlur` | `ExpoBlurModule` | Injects `expo-blur` (`import * as ExpoBlur from 'expo-blur'`) so `<BlurView>` renders the real frosted-glass effect. Omit to always render its solid fallback. On Android, expo-blur only blurs when given a `blurTarget` and a `blurMethod` (`'dimezisBlurView'`, or `'dimezisBlurViewSdk31Plus'` on API 31+); without both, `<BlurView>` renders the solid fallback there too, rather than expo-blur's unblurred translucent tint. |
269
269
  | `fontFamily` | `string` | Applied to every Paper typography variant (`bodyLarge`, `headlineMedium`, `labelSmall`, ...) via `configureFonts`'s flat-config mode — each variant keeps its own MD3 fontSize/lineHeight/weight, only fontFamily changes. Omit to keep each platform's system font. |
270
270
  | `navigationBar` | `ExpoNavigationBarModule` | Injects `expo-navigation-bar` (`import * as ExpoNavigationBar from 'expo-navigation-bar'`) so the Android nav bar icon style auto-syncs with the theme while a `BottomNavigation` is mounted. Omit (and don't pass `onNavBarChange`) to skip nav bar syncing entirely. |
271
271
  | `onNavBarChange` | `(color: string, dark: boolean) => void` | Overrides the built-in nav bar sync: called on Android when the theme changes while a `BottomNavigation` is mounted, instead of the automatic `navigationBar`-driven icon-style sync |
@@ -274,6 +274,8 @@ export default function App() {
274
274
  | `statusBarProps` | `StatusBarProps` | Spread over the auto-derived `StatusBar` defaults |
275
275
  | `style` | `StyleProp<ViewStyle>` | Applied to the wrapper `View` |
276
276
 
277
+ `Provider` also renders its own `Portal.Host`, nested inside everything it injects, so anything rendered through `react-native-paper`'s `Portal` (this package's `Dialog`, Paper's `Menu` and `Tooltip`, or a `Modal`/`Snackbar`/anything else you wrap in `<Portal>` yourself) still sees `expoBlur`, `reanimated` and `defaults`. Paper's own host sits above all of those and its `Portal` only carries the theme across, so without this a portaled `Dialog` or `Menu` would silently render its solid fallback. Contexts your app provides *inside* `Provider` (a toast provider, say) are a separate matter: portal content can't see them unless you render another `<Portal.Host>` below them.
278
+
277
279
  `ThemeSettings` fields:
278
280
 
279
281
  | Field | Type | Default |
package/dist/index.js CHANGED
@@ -706,7 +706,7 @@ function ThemeProviderBody({ children, defaults, expoBlur, fontFamily, navigatio
706
706
  if (!theme) return null;
707
707
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native_paper4.Provider, { theme, children: [
708
708
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.StatusBar, { backgroundColor: theme.colors.background, barStyle: theme.dark ? "light-content" : "dark-content", ...statusBarProps }),
709
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PaperDefaultsContext.Provider, { value: defaults ?? {}, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NavBarContext.Provider, { value: { navigationBar, onNavBarChange }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BlurModuleContext.Provider, { value: expoBlur, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ReanimatedModuleContext.Provider, { value: reanimated, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: [styles.flex, { backgroundColor: theme.colors.background }, style], children }) }) }) }) })
709
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PaperDefaultsContext.Provider, { value: defaults ?? {}, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NavBarContext.Provider, { value: { navigationBar, onNavBarChange }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BlurModuleContext.Provider, { value: expoBlur, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ReanimatedModuleContext.Provider, { value: reanimated, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Portal.Host, { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native3.View, { style: [styles.flex, { backgroundColor: theme.colors.background }, style], children }) }) }) }) }) })
710
710
  ] });
711
711
  }
712
712
  var styles = import_react_native3.StyleSheet.create({
@@ -718,6 +718,11 @@ var import_react6 = require("react");
718
718
  var import_react_native4 = require("react-native");
719
719
  var import_react_native_paper5 = require("react-native-paper");
720
720
  var import_jsx_runtime5 = require("react/jsx-runtime");
721
+ var blursOnAndroid = ({ blurMethod, blurTarget, experimentalBlurMethod }) => {
722
+ const method = blurMethod ?? experimentalBlurMethod ?? "none";
723
+ if (!blurTarget || method === "none") return false;
724
+ return method === "dimezisBlurView" || Number(import_react_native4.Platform.Version) >= 31;
725
+ };
721
726
  var ELEVATION_OPACITY = { 0: 0, 1: 0.05, 2: 0.08, 3: 0.11, 4: 0.12, 5: 0.14 };
722
727
  var VARIANT_TINT_OPACITY = { light: 0.12, dark: 0.18 };
723
728
  var BlurView = ({ blur = true, children, elevation, style, tintColor, tintOpacity, variant = "surface", ...props }) => {
@@ -729,7 +734,7 @@ var BlurView = ({ blur = true, children, elevation, style, tintColor, tintOpacit
729
734
  const resolvedTintColor = tintColor ?? variantColor ?? (elevation !== void 0 ? colors.primary : void 0);
730
735
  const resolvedTintOpacity = tintOpacity ?? (tintColor ? dark ? 0.24 : 0.16 : variantColor ? dark ? VARIANT_TINT_OPACITY.dark : VARIANT_TINT_OPACITY.light : elevation !== void 0 ? ELEVATION_OPACITY[elevation] : 0);
731
736
  const tint = resolvedTintColor && resolvedTintOpacity > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native4.View, { style: [import_react_native4.StyleSheet.absoluteFill, { backgroundColor: resolvedTintColor, opacity: resolvedTintOpacity }] }) : null;
732
- if (blur && expoBlur)
737
+ if (blur && expoBlur && (import_react_native4.Platform.OS !== "android" || blursOnAndroid(props)))
733
738
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(expoBlur.BlurView, { ...props, tint: dark ? "dark" : "light", style, children: [
734
739
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native4.View, { style: [import_react_native4.StyleSheet.absoluteFill, { backgroundColor: surfaceColor, opacity: settings.blurTint }] }),
735
740
  tint,
@@ -745,6 +750,8 @@ var BlurView = ({ blur = true, children, elevation, style, tintColor, tintOpacit
745
750
  var import_jsx_runtime6 = require("react/jsx-runtime");
746
751
  var ANIMATION_DURATION = 220;
747
752
  var DIALOG_MARGIN = 32;
753
+ var AnimatedBlurView = import_react_native5.Animated.createAnimatedComponent(BlurView);
754
+ var AnimatedPressable = import_react_native5.Animated.createAnimatedComponent(import_react_native5.Pressable);
748
755
  function DialogComponent({ animatedStyle, blur: blurProp, dismissable = true, dismissableBackButton = dismissable, visible, onDismiss, children, style, testID = "dialog" }) {
749
756
  const blur = useBlur(blurProp);
750
757
  const reanimated = useReanimatedModule();
@@ -788,9 +795,16 @@ function DialogComponent({ animatedStyle, blur: blurProp, dismissable = true, di
788
795
  return child;
789
796
  });
790
797
  const borderRadius = (isV3 ? 7 : 1) * roundness;
791
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper6.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native5.Animated.View, { accessibilityLiveRegion: "polite", accessibilityViewIsModal: true, pointerEvents: visible ? "box-none" : "none", style: [import_react_native5.StyleSheet.absoluteFill, { opacity }], testID, children: [
792
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.Pressable, { accessibilityLabel: "Close modal", accessibilityRole: "button", disabled: !dismissable, onPress: dismissable ? onDismiss : void 0, style: [import_react_native5.StyleSheet.absoluteFill, { backgroundColor: colors.backdrop }], testID: `${testID}-backdrop` }),
793
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.View, { pointerEvents: "box-none", style: [styles2.wrapper, { marginBottom: bottom, marginTop: top, paddingHorizontal: Math.max(left, right, DIALOG_MARGIN) }], testID: `${testID}-wrapper`, children: reanimated && animatedStyle ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(reanimated.View, { style: animatedStyle, testID: `${testID}-animated-wrapper`, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BlurView, { blur, style: [styles2.card, { borderRadius }, style], testID: `${testID}-surface`, children: content }) }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BlurView, { blur, style: [styles2.card, { borderRadius }, style], testID: `${testID}-surface`, children: content }) })
798
+ const web = import_react_native5.Platform.OS === "web";
799
+ const fade = { opacity };
800
+ const styleOpacity = import_react_native5.StyleSheet.flatten(style)?.opacity;
801
+ const cardFade = typeof styleOpacity === "number" ? { opacity: import_react_native5.Animated.multiply(opacity, styleOpacity) } : fade;
802
+ const Backdrop = web ? AnimatedPressable : import_react_native5.Pressable;
803
+ const Card = web ? AnimatedBlurView : BlurView;
804
+ const card = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Card, { blur, style: [styles2.card, { borderRadius }, style, web && cardFade], testID: `${testID}-surface`, children: content });
805
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_paper6.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native5.Animated.View, { accessibilityLiveRegion: "polite", accessibilityViewIsModal: true, pointerEvents: visible ? "box-none" : "none", style: [import_react_native5.StyleSheet.absoluteFill, !web && fade], testID, children: [
806
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Backdrop, { accessibilityLabel: "Close modal", accessibilityRole: "button", disabled: !dismissable, onPress: dismissable ? onDismiss : void 0, style: [import_react_native5.StyleSheet.absoluteFill, { backgroundColor: colors.backdrop }, web && fade], testID: `${testID}-backdrop` }),
807
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.View, { pointerEvents: "box-none", style: [styles2.wrapper, { marginBottom: bottom, marginTop: top, paddingHorizontal: Math.max(left, right, DIALOG_MARGIN) }], testID: `${testID}-wrapper`, children: reanimated && animatedStyle ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(reanimated.View, { style: animatedStyle, testID: `${testID}-animated-wrapper`, children: card }) : card })
794
808
  ] }) });
795
809
  }
796
810
  DialogComponent.Title = import_react_native_paper6.Dialog.Title;
@@ -1031,25 +1045,45 @@ function IconButton({ variant, iconColor, containerColor, ...props }) {
1031
1045
  }
1032
1046
 
1033
1047
  // src/components/Menu.tsx
1048
+ var import_react11 = require("react");
1034
1049
  var import_react_native9 = require("react-native");
1035
1050
  var import_react_native_paper15 = require("react-native-paper");
1036
1051
  var import_jsx_runtime16 = require("react/jsx-runtime");
1052
+ var ANIMATION_DURATION2 = 250;
1053
+ var EASING = import_react_native9.Easing.bezier(0.4, 0, 0.2, 1);
1054
+ var DEFAULT_ELEVATION = 2;
1055
+ var SHADOW_HEIGHT = [0, 1, 2, 4, 6, 8];
1056
+ var SHADOW_RADIUS = [0, 3, 6, 8, 10, 12];
1057
+ var AnimatedBlurView2 = import_react_native9.Animated.createAnimatedComponent(BlurView);
1058
+ var FadingBlurView = ({ blur, children, elevation, theme: themeOverrides, visible }) => {
1059
+ const { animation, roundness } = (0, import_react_native_paper15.useTheme)(themeOverrides);
1060
+ const [opacity] = (0, import_react11.useState)(() => new import_react_native9.Animated.Value(0));
1061
+ const [closing, setClosing] = (0, import_react11.useState)(false);
1062
+ if (!visible && !closing) setClosing(true);
1063
+ (0, import_react11.useEffect)(() => {
1064
+ import_react_native9.Animated.timing(opacity, { duration: animation.scale * ANIMATION_DURATION2, easing: EASING, toValue: closing ? 0 : 1, useNativeDriver: false }).start();
1065
+ }, [animation.scale, closing, opacity]);
1066
+ const shadow = elevation > 0 ? { shadowColor: "#000000", shadowOffset: { height: SHADOW_HEIGHT[elevation], width: 0 }, shadowOpacity: 0.3, shadowRadius: SHADOW_RADIUS[elevation] } : null;
1067
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AnimatedBlurView2, { blur, style: [{ borderRadius: roundness, opacity }, shadow], children });
1068
+ };
1037
1069
  var MenuComponent = ({ blur: blurProp, children, ...props }) => {
1038
1070
  const blur = useBlur(blurProp);
1039
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native_paper15.Menu, { ...props, contentStyle: [styles5.content, props.contentStyle], children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(BlurView, { blur, children }) });
1071
+ const web = import_react_native9.Platform.OS === "web";
1072
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react_native_paper15.Menu, { ...props, contentStyle: [styles5.content, web && styles5.webSurface, props.contentStyle], children: web ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(FadingBlurView, { blur, elevation: props.mode === "flat" ? 0 : props.elevation ?? DEFAULT_ELEVATION, theme: props.theme, visible: props.visible, children }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(BlurView, { blur, children }) });
1040
1073
  };
1041
1074
  var Menu = Object.assign(MenuComponent, {
1042
1075
  Item: import_react_native_paper15.Menu.Item
1043
1076
  });
1044
1077
  var styles5 = import_react_native9.StyleSheet.create({
1045
- content: { backgroundColor: "transparent", paddingVertical: 0 }
1078
+ content: { backgroundColor: "transparent", paddingVertical: 0 },
1079
+ webSurface: { opacity: 1, shadowOpacity: 0 }
1046
1080
  });
1047
1081
 
1048
1082
  // src/components/TextInput.tsx
1049
- var import_react11 = require("react");
1083
+ var import_react12 = require("react");
1050
1084
  var import_react_native_paper16 = require("react-native-paper");
1051
1085
  var import_jsx_runtime17 = require("react/jsx-runtime");
1052
- var TextInput = (0, import_react11.forwardRef)(function TextInput2(props, ref) {
1086
+ var TextInput = (0, import_react12.forwardRef)(function TextInput2(props, ref) {
1053
1087
  const defaults = usePaperDefaults();
1054
1088
  return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react_native_paper16.TextInput, { ref, ...defaults.TextInput, ...props });
1055
1089
  });
@@ -1080,10 +1114,10 @@ var import_react_native_paper17 = require("react-native-paper");
1080
1114
  var useAutoPaperTheme = () => (0, import_react_native_paper17.useTheme)();
1081
1115
 
1082
1116
  // src/useThemeBridgeProps.ts
1083
- var import_react12 = require("react");
1117
+ var import_react13 = require("react");
1084
1118
  function useThemeBridgeProps(props) {
1085
1119
  const { initialValue, onChange, onReady } = props;
1086
- return (0, import_react12.useMemo)(() => ({ initialValue, onChange, onReady }), [initialValue, onChange, onReady]);
1120
+ return (0, import_react13.useMemo)(() => ({ initialValue, onChange, onReady }), [initialValue, onChange, onReady]);
1087
1121
  }
1088
1122
 
1089
1123
  // src/utils/getThirdColor.ts
package/dist/index.mjs CHANGED
@@ -416,14 +416,14 @@ import { Icon, TouchableRipple, useTheme as useTheme4 } from "react-native-paper
416
416
 
417
417
  // src/components/Dialog.tsx
418
418
  import React, { useEffect as useEffect3, useState as useState3 } from "react";
419
- import { Animated, BackHandler, Easing, Pressable, StyleSheet as StyleSheet3, View as View3 } from "react-native";
420
- import { Dialog as PaperDialog, Portal, useTheme as useTheme3 } from "react-native-paper";
419
+ import { Animated, BackHandler, Easing, Platform as Platform2, Pressable, StyleSheet as StyleSheet3, View as View3 } from "react-native";
420
+ import { Dialog as PaperDialog, Portal as Portal2, useTheme as useTheme3 } from "react-native-paper";
421
421
  import { useSafeAreaInsets } from "react-native-safe-area-context";
422
422
 
423
423
  // src/ThemeProvider.tsx
424
424
  import { createContext as createContext2, useContext as useContext3, useEffect as useEffect2, useMemo, useRef } from "react";
425
425
  import { StatusBar as StatusBar2, StyleSheet, View } from "react-native";
426
- import { configureFonts, Provider as PaperProvider } from "react-native-paper";
426
+ import { configureFonts, Portal, Provider as PaperProvider } from "react-native-paper";
427
427
 
428
428
  // src/useComputedTheme.ts
429
429
  import { useCallback, useEffect, useState as useState2 } from "react";
@@ -623,7 +623,7 @@ function ThemeProviderBody({ children, defaults, expoBlur, fontFamily, navigatio
623
623
  if (!theme) return null;
624
624
  return /* @__PURE__ */ jsxs2(PaperProvider, { theme, children: [
625
625
  /* @__PURE__ */ jsx4(StatusBar2, { backgroundColor: theme.colors.background, barStyle: theme.dark ? "light-content" : "dark-content", ...statusBarProps }),
626
- /* @__PURE__ */ jsx4(PaperDefaultsContext.Provider, { value: defaults ?? {}, children: /* @__PURE__ */ jsx4(NavBarContext.Provider, { value: { navigationBar, onNavBarChange }, children: /* @__PURE__ */ jsx4(BlurModuleContext.Provider, { value: expoBlur, children: /* @__PURE__ */ jsx4(ReanimatedModuleContext.Provider, { value: reanimated, children: /* @__PURE__ */ jsx4(View, { style: [styles.flex, { backgroundColor: theme.colors.background }, style], children }) }) }) }) })
626
+ /* @__PURE__ */ jsx4(PaperDefaultsContext.Provider, { value: defaults ?? {}, children: /* @__PURE__ */ jsx4(NavBarContext.Provider, { value: { navigationBar, onNavBarChange }, children: /* @__PURE__ */ jsx4(BlurModuleContext.Provider, { value: expoBlur, children: /* @__PURE__ */ jsx4(ReanimatedModuleContext.Provider, { value: reanimated, children: /* @__PURE__ */ jsx4(Portal.Host, { children: /* @__PURE__ */ jsx4(View, { style: [styles.flex, { backgroundColor: theme.colors.background }, style], children }) }) }) }) }) })
627
627
  ] });
628
628
  }
629
629
  var styles = StyleSheet.create({
@@ -632,9 +632,14 @@ var styles = StyleSheet.create({
632
632
 
633
633
  // src/components/BlurView.tsx
634
634
  import { useContext as useContext4 } from "react";
635
- import { StyleSheet as StyleSheet2, View as View2 } from "react-native";
635
+ import { Platform, StyleSheet as StyleSheet2, View as View2 } from "react-native";
636
636
  import { useTheme as useTheme2 } from "react-native-paper";
637
637
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
638
+ var blursOnAndroid = ({ blurMethod, blurTarget, experimentalBlurMethod }) => {
639
+ const method = blurMethod ?? experimentalBlurMethod ?? "none";
640
+ if (!blurTarget || method === "none") return false;
641
+ return method === "dimezisBlurView" || Number(Platform.Version) >= 31;
642
+ };
638
643
  var ELEVATION_OPACITY = { 0: 0, 1: 0.05, 2: 0.08, 3: 0.11, 4: 0.12, 5: 0.14 };
639
644
  var VARIANT_TINT_OPACITY = { light: 0.12, dark: 0.18 };
640
645
  var BlurView = ({ blur = true, children, elevation, style, tintColor, tintOpacity, variant = "surface", ...props }) => {
@@ -646,7 +651,7 @@ var BlurView = ({ blur = true, children, elevation, style, tintColor, tintOpacit
646
651
  const resolvedTintColor = tintColor ?? variantColor ?? (elevation !== void 0 ? colors.primary : void 0);
647
652
  const resolvedTintOpacity = tintOpacity ?? (tintColor ? dark ? 0.24 : 0.16 : variantColor ? dark ? VARIANT_TINT_OPACITY.dark : VARIANT_TINT_OPACITY.light : elevation !== void 0 ? ELEVATION_OPACITY[elevation] : 0);
648
653
  const tint = resolvedTintColor && resolvedTintOpacity > 0 ? /* @__PURE__ */ jsx5(View2, { style: [StyleSheet2.absoluteFill, { backgroundColor: resolvedTintColor, opacity: resolvedTintOpacity }] }) : null;
649
- if (blur && expoBlur)
654
+ if (blur && expoBlur && (Platform.OS !== "android" || blursOnAndroid(props)))
650
655
  return /* @__PURE__ */ jsxs3(expoBlur.BlurView, { ...props, tint: dark ? "dark" : "light", style, children: [
651
656
  /* @__PURE__ */ jsx5(View2, { style: [StyleSheet2.absoluteFill, { backgroundColor: surfaceColor, opacity: settings.blurTint }] }),
652
657
  tint,
@@ -662,6 +667,8 @@ var BlurView = ({ blur = true, children, elevation, style, tintColor, tintOpacit
662
667
  import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
663
668
  var ANIMATION_DURATION = 220;
664
669
  var DIALOG_MARGIN = 32;
670
+ var AnimatedBlurView = Animated.createAnimatedComponent(BlurView);
671
+ var AnimatedPressable = Animated.createAnimatedComponent(Pressable);
665
672
  function DialogComponent({ animatedStyle, blur: blurProp, dismissable = true, dismissableBackButton = dismissable, visible, onDismiss, children, style, testID = "dialog" }) {
666
673
  const blur = useBlur(blurProp);
667
674
  const reanimated = useReanimatedModule();
@@ -705,9 +712,16 @@ function DialogComponent({ animatedStyle, blur: blurProp, dismissable = true, di
705
712
  return child;
706
713
  });
707
714
  const borderRadius = (isV3 ? 7 : 1) * roundness;
708
- return /* @__PURE__ */ jsx6(Portal, { children: /* @__PURE__ */ jsxs4(Animated.View, { accessibilityLiveRegion: "polite", accessibilityViewIsModal: true, pointerEvents: visible ? "box-none" : "none", style: [StyleSheet3.absoluteFill, { opacity }], testID, children: [
709
- /* @__PURE__ */ jsx6(Pressable, { accessibilityLabel: "Close modal", accessibilityRole: "button", disabled: !dismissable, onPress: dismissable ? onDismiss : void 0, style: [StyleSheet3.absoluteFill, { backgroundColor: colors.backdrop }], testID: `${testID}-backdrop` }),
710
- /* @__PURE__ */ jsx6(View3, { pointerEvents: "box-none", style: [styles2.wrapper, { marginBottom: bottom, marginTop: top, paddingHorizontal: Math.max(left, right, DIALOG_MARGIN) }], testID: `${testID}-wrapper`, children: reanimated && animatedStyle ? /* @__PURE__ */ jsx6(reanimated.View, { style: animatedStyle, testID: `${testID}-animated-wrapper`, children: /* @__PURE__ */ jsx6(BlurView, { blur, style: [styles2.card, { borderRadius }, style], testID: `${testID}-surface`, children: content }) }) : /* @__PURE__ */ jsx6(BlurView, { blur, style: [styles2.card, { borderRadius }, style], testID: `${testID}-surface`, children: content }) })
715
+ const web = Platform2.OS === "web";
716
+ const fade = { opacity };
717
+ const styleOpacity = StyleSheet3.flatten(style)?.opacity;
718
+ const cardFade = typeof styleOpacity === "number" ? { opacity: Animated.multiply(opacity, styleOpacity) } : fade;
719
+ const Backdrop = web ? AnimatedPressable : Pressable;
720
+ const Card = web ? AnimatedBlurView : BlurView;
721
+ const card = /* @__PURE__ */ jsx6(Card, { blur, style: [styles2.card, { borderRadius }, style, web && cardFade], testID: `${testID}-surface`, children: content });
722
+ return /* @__PURE__ */ jsx6(Portal2, { children: /* @__PURE__ */ jsxs4(Animated.View, { accessibilityLiveRegion: "polite", accessibilityViewIsModal: true, pointerEvents: visible ? "box-none" : "none", style: [StyleSheet3.absoluteFill, !web && fade], testID, children: [
723
+ /* @__PURE__ */ jsx6(Backdrop, { accessibilityLabel: "Close modal", accessibilityRole: "button", disabled: !dismissable, onPress: dismissable ? onDismiss : void 0, style: [StyleSheet3.absoluteFill, { backgroundColor: colors.backdrop }, web && fade], testID: `${testID}-backdrop` }),
724
+ /* @__PURE__ */ jsx6(View3, { pointerEvents: "box-none", style: [styles2.wrapper, { marginBottom: bottom, marginTop: top, paddingHorizontal: Math.max(left, right, DIALOG_MARGIN) }], testID: `${testID}-wrapper`, children: reanimated && animatedStyle ? /* @__PURE__ */ jsx6(reanimated.View, { style: animatedStyle, testID: `${testID}-animated-wrapper`, children: card }) : card })
711
725
  ] }) });
712
726
  }
713
727
  DialogComponent.Title = PaperDialog.Title;
@@ -876,7 +890,7 @@ var AutoPalettePicker = (props) => {
876
890
 
877
891
  // src/components/BottomNavigation.tsx
878
892
  import { useEffect as useEffect4 } from "react";
879
- import { Platform } from "react-native";
893
+ import { Platform as Platform3 } from "react-native";
880
894
  import { BottomNavigation as PaperBottomNavigation, useTheme as useTheme6 } from "react-native-paper";
881
895
 
882
896
  // src/navigation-bar.ts
@@ -891,7 +905,7 @@ function useNavigationBarSync() {
891
905
  const theme = useTheme6();
892
906
  const { navigationBar, onNavBarChange } = useNavBarContext();
893
907
  useEffect4(() => {
894
- if (Platform.OS !== "android") return;
908
+ if (Platform3.OS !== "android") return;
895
909
  if (onNavBarChange) onNavBarChange(theme.colors.surface, theme.dark);
896
910
  else if (navigationBar) setNavigationBarStyle(navigationBar, theme.dark);
897
911
  }, [theme, onNavBarChange, navigationBar]);
@@ -948,18 +962,38 @@ function IconButton({ variant, iconColor, containerColor, ...props }) {
948
962
  }
949
963
 
950
964
  // src/components/Menu.tsx
951
- import { StyleSheet as StyleSheet6 } from "react-native";
952
- import { Menu as PaperMenu } from "react-native-paper";
965
+ import { useEffect as useEffect5, useState as useState6 } from "react";
966
+ import { Animated as Animated2, Easing as Easing2, Platform as Platform4, StyleSheet as StyleSheet6 } from "react-native";
967
+ import { Menu as PaperMenu, useTheme as useTheme9 } from "react-native-paper";
953
968
  import { jsx as jsx16 } from "react/jsx-runtime";
969
+ var ANIMATION_DURATION2 = 250;
970
+ var EASING = Easing2.bezier(0.4, 0, 0.2, 1);
971
+ var DEFAULT_ELEVATION = 2;
972
+ var SHADOW_HEIGHT = [0, 1, 2, 4, 6, 8];
973
+ var SHADOW_RADIUS = [0, 3, 6, 8, 10, 12];
974
+ var AnimatedBlurView2 = Animated2.createAnimatedComponent(BlurView);
975
+ var FadingBlurView = ({ blur, children, elevation, theme: themeOverrides, visible }) => {
976
+ const { animation, roundness } = useTheme9(themeOverrides);
977
+ const [opacity] = useState6(() => new Animated2.Value(0));
978
+ const [closing, setClosing] = useState6(false);
979
+ if (!visible && !closing) setClosing(true);
980
+ useEffect5(() => {
981
+ Animated2.timing(opacity, { duration: animation.scale * ANIMATION_DURATION2, easing: EASING, toValue: closing ? 0 : 1, useNativeDriver: false }).start();
982
+ }, [animation.scale, closing, opacity]);
983
+ const shadow = elevation > 0 ? { shadowColor: "#000000", shadowOffset: { height: SHADOW_HEIGHT[elevation], width: 0 }, shadowOpacity: 0.3, shadowRadius: SHADOW_RADIUS[elevation] } : null;
984
+ return /* @__PURE__ */ jsx16(AnimatedBlurView2, { blur, style: [{ borderRadius: roundness, opacity }, shadow], children });
985
+ };
954
986
  var MenuComponent = ({ blur: blurProp, children, ...props }) => {
955
987
  const blur = useBlur(blurProp);
956
- return /* @__PURE__ */ jsx16(PaperMenu, { ...props, contentStyle: [styles5.content, props.contentStyle], children: /* @__PURE__ */ jsx16(BlurView, { blur, children }) });
988
+ const web = Platform4.OS === "web";
989
+ return /* @__PURE__ */ jsx16(PaperMenu, { ...props, contentStyle: [styles5.content, web && styles5.webSurface, props.contentStyle], children: web ? /* @__PURE__ */ jsx16(FadingBlurView, { blur, elevation: props.mode === "flat" ? 0 : props.elevation ?? DEFAULT_ELEVATION, theme: props.theme, visible: props.visible, children }) : /* @__PURE__ */ jsx16(BlurView, { blur, children }) });
957
990
  };
958
991
  var Menu = Object.assign(MenuComponent, {
959
992
  Item: PaperMenu.Item
960
993
  });
961
994
  var styles5 = StyleSheet6.create({
962
- content: { backgroundColor: "transparent", paddingVertical: 0 }
995
+ content: { backgroundColor: "transparent", paddingVertical: 0 },
996
+ webSurface: { opacity: 1, shadowOpacity: 0 }
963
997
  });
964
998
 
965
999
  // src/components/TextInput.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rific/auto-paper",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "description": "Adaptive react-native-paper theming: triadic palette from a seed color + system/light/dark appearance",
5
5
  "keywords": [
6
6
  "react-native",
@@ -1,6 +1,6 @@
1
1
  import { createContext, type ReactNode, useContext, useEffect, useMemo, useRef } from 'react'
2
2
  import { StatusBar, type StatusBarProps, type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native'
3
- import { configureFonts, Provider as PaperProvider } from 'react-native-paper'
3
+ import { configureFonts, Portal, Provider as PaperProvider } from 'react-native-paper'
4
4
 
5
5
  import type { ExpoBlurModule } from './components/BlurView'
6
6
  import type { ReanimatedModule } from './components/Dialog'
@@ -99,7 +99,17 @@ function ThemeProviderBody({ children, defaults, expoBlur, fontFamily, navigatio
99
99
  <NavBarContext.Provider value={{ navigationBar, onNavBarChange }}>
100
100
  <BlurModuleContext.Provider value={expoBlur}>
101
101
  <ReanimatedModuleContext.Provider value={reanimated}>
102
- <View style={[styles.flex, { backgroundColor: theme.colors.background }, style]}>{children}</View>
102
+ {/* PaperProvider's own PortalHost sits above every context provided here, and Paper's
103
+ Portal only carries its theme and settings across, so anything rendered through a
104
+ Portal (this package's Dialog, Paper's Menu and Tooltip, or anything an app wraps
105
+ in <Portal> itself) would otherwise lose expoBlur, reanimated, defaults and the nav
106
+ bar hooks: Dialog/Menu's blur silently fell back to solid. This nested host is the
107
+ nearest one for every Portal under Provider, so their content mounts inside all of
108
+ these contexts instead. It wraps the styled View, not the other way round, so
109
+ portals still cover the full area regardless of `style`. */}
110
+ <Portal.Host>
111
+ <View style={[styles.flex, { backgroundColor: theme.colors.background }, style]}>{children}</View>
112
+ </Portal.Host>
103
113
  </ReanimatedModuleContext.Provider>
104
114
  </BlurModuleContext.Provider>
105
115
  </NavBarContext.Provider>
@@ -1,5 +1,5 @@
1
1
  import { type ComponentType, type ReactNode, type RefObject, useContext } from 'react'
2
- import { type StyleProp, StyleSheet, View, type ViewProps, type ViewStyle } from 'react-native'
2
+ import { Platform, type StyleProp, StyleSheet, View, type ViewProps, type ViewStyle } from 'react-native'
3
3
  import { useTheme } from 'react-native-paper'
4
4
 
5
5
  import { useBlurModule } from '../ThemeProvider'
@@ -33,6 +33,18 @@ type ExpoBlurViewProps = {
33
33
  tint?: ExpoBlurTint
34
34
  } & ViewProps
35
35
 
36
+ // On Android, expo-blur only really blurs when it has both a blurTarget and a blur method
37
+ // ('dimezisBlurView', or 'dimezisBlurViewSdk31Plus' on API 31+); blurMethod defaults to 'none'.
38
+ // Otherwise it paints its tint as a flat translucent fill (roughly 0.35-0.39 alpha at the default
39
+ // intensity) with the screen showing through unblurred, so BlurView renders its solid look instead.
40
+ // Checks the blurTarget ref object, not its .current, same as expo-blur's own JS: the target view
41
+ // usually commits after this render, so .current is still null here even when wired correctly.
42
+ const blursOnAndroid = ({ blurMethod, blurTarget, experimentalBlurMethod }: ExpoBlurViewProps) => {
43
+ const method = blurMethod ?? experimentalBlurMethod ?? 'none'
44
+ if (!blurTarget || method === 'none') return false
45
+ return method === 'dimezisBlurView' || Number(Platform.Version) >= 31
46
+ }
47
+
36
48
  const ELEVATION_OPACITY: Record<number, number> = { 0: 0, 1: 0.05, 2: 0.08, 3: 0.11, 4: 0.12, 5: 0.14 }
37
49
  const VARIANT_TINT_OPACITY = { light: 0.12, dark: 0.18 }
38
50
 
@@ -55,7 +67,7 @@ export const BlurView = ({ blur = true, children, elevation, style, tintColor, t
55
67
  const resolvedTintColor = tintColor ?? variantColor ?? (elevation !== undefined ? colors.primary : undefined)
56
68
  const resolvedTintOpacity = tintOpacity ?? (tintColor ? (dark ? 0.24 : 0.16) : variantColor ? (dark ? VARIANT_TINT_OPACITY.dark : VARIANT_TINT_OPACITY.light) : elevation !== undefined ? ELEVATION_OPACITY[elevation] : 0)
57
69
  const tint = resolvedTintColor && resolvedTintOpacity > 0 ? <View style={[StyleSheet.absoluteFill, { backgroundColor: resolvedTintColor, opacity: resolvedTintOpacity }]} /> : null
58
- if (blur && expoBlur)
70
+ if (blur && expoBlur && (Platform.OS !== 'android' || blursOnAndroid(props)))
59
71
  return (
60
72
  <expoBlur.BlurView {...props} tint={dark ? 'dark' : 'light'} style={style}>
61
73
  <View style={[StyleSheet.absoluteFill, { backgroundColor: surfaceColor, opacity: settings.blurTint }]} />
@@ -1,5 +1,5 @@
1
1
  import React, { type ComponentType, type ReactNode, useEffect, useState } from 'react'
2
- import { Animated, BackHandler, Easing, Pressable, type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native'
2
+ import { Animated, BackHandler, Easing, Platform, Pressable, type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native'
3
3
  import { Dialog as PaperDialog, type DialogActionsProps, type DialogContentProps, type DialogProps as PaperDialogProps, type DialogScrollAreaProps, type DialogTitleProps, Portal, useTheme } from 'react-native-paper'
4
4
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
5
5
 
@@ -39,6 +39,9 @@ const ANIMATION_DURATION = 220
39
39
  // Minimum breathing room between the card and the screen edge, clamped up from safe-area insets.
40
40
  const DIALOG_MARGIN = 32
41
41
 
42
+ const AnimatedBlurView = Animated.createAnimatedComponent(BlurView)
43
+ const AnimatedPressable = Animated.createAnimatedComponent(Pressable)
44
+
42
45
  function DialogComponent({ animatedStyle, blur: blurProp, dismissable = true, dismissableBackButton = dismissable, visible, onDismiss, children, style, testID = 'dialog' }: DialogProps) {
43
46
  const blur = useBlur(blurProp)
44
47
  const reanimated = useReanimatedModule()
@@ -93,21 +96,34 @@ function DialogComponent({ animatedStyle, blur: blurProp, dismissable = true, di
93
96
 
94
97
  const borderRadius = (isV3 ? 7 : 1) * roundness
95
98
 
99
+ // On web, an ancestor below opacity 1 cuts the card's CSS backdrop-filter off from the page behind
100
+ // it, and Chromium doesn't restore the blur once that ancestor fades back up to 1, so fading the
101
+ // whole layer would leave the open card see-through. There the fade runs on the backdrop and on the
102
+ // card (the backdrop-filter element) itself instead, off the same value. A numeric opacity in the
103
+ // card's own style still applies there, multiplied into the fade.
104
+ const web = Platform.OS === 'web'
105
+ const fade = { opacity }
106
+ const styleOpacity = StyleSheet.flatten(style)?.opacity
107
+ const cardFade = typeof styleOpacity === 'number' ? { opacity: Animated.multiply(opacity, styleOpacity) } : fade
108
+ const Backdrop = web ? AnimatedPressable : Pressable
109
+ const Card = web ? AnimatedBlurView : BlurView
110
+ const card = (
111
+ <Card blur={blur} style={[styles.card, { borderRadius }, style, web && cardFade]} testID={`${testID}-surface`}>
112
+ {content}
113
+ </Card>
114
+ )
115
+
96
116
  return (
97
117
  <Portal>
98
- <Animated.View accessibilityLiveRegion='polite' accessibilityViewIsModal pointerEvents={visible ? 'box-none' : 'none'} style={[StyleSheet.absoluteFill, { opacity }]} testID={testID}>
99
- <Pressable accessibilityLabel='Close modal' accessibilityRole='button' disabled={!dismissable} onPress={dismissable ? onDismiss : undefined} style={[StyleSheet.absoluteFill, { backgroundColor: colors.backdrop }]} testID={`${testID}-backdrop`} />
118
+ <Animated.View accessibilityLiveRegion='polite' accessibilityViewIsModal pointerEvents={visible ? 'box-none' : 'none'} style={[StyleSheet.absoluteFill, !web && fade]} testID={testID}>
119
+ <Backdrop accessibilityLabel='Close modal' accessibilityRole='button' disabled={!dismissable} onPress={dismissable ? onDismiss : undefined} style={[StyleSheet.absoluteFill, { backgroundColor: colors.backdrop }, web && fade]} testID={`${testID}-backdrop`} />
100
120
  <View pointerEvents='box-none' style={[styles.wrapper, { marginBottom: bottom, marginTop: top, paddingHorizontal: Math.max(left, right, DIALOG_MARGIN) }]} testID={`${testID}-wrapper`}>
101
121
  {reanimated && animatedStyle ? (
102
122
  <reanimated.View style={animatedStyle} testID={`${testID}-animated-wrapper`}>
103
- <BlurView blur={blur} style={[styles.card, { borderRadius }, style]} testID={`${testID}-surface`}>
104
- {content}
105
- </BlurView>
123
+ {card}
106
124
  </reanimated.View>
107
125
  ) : (
108
- <BlurView blur={blur} style={[styles.card, { borderRadius }, style]} testID={`${testID}-surface`}>
109
- {content}
110
- </BlurView>
126
+ card
111
127
  )}
112
128
  </View>
113
129
  </Animated.View>
@@ -1,6 +1,6 @@
1
- import React from 'react'
2
- import { StyleSheet } from 'react-native'
3
- import { Menu as PaperMenu, type MenuItemProps, type MenuProps as PaperMenuProps } from 'react-native-paper'
1
+ import React, { type ReactNode, useEffect, useState } from 'react'
2
+ import { Animated, Easing, Platform, StyleSheet } from 'react-native'
3
+ import { type MD2Theme, type MD3Theme, Menu as PaperMenu, type MenuItemProps, type MenuProps as PaperMenuProps, useTheme } from 'react-native-paper'
4
4
 
5
5
  import { useBlur } from '../BlurContext'
6
6
  import { BlurView } from './BlurView'
@@ -9,11 +9,61 @@ export type MenuProps = PaperMenuProps & {
9
9
  blur?: boolean
10
10
  }
11
11
 
12
+ // Mirror react-native-paper's Menu: its fade (ANIMATION_DURATION * theme.animation.scale, eased with
13
+ // EASING), its default elevation, and the MD3 shadow its Surface draws at each elevation level.
14
+ const ANIMATION_DURATION = 250
15
+ const EASING = Easing.bezier(0.4, 0, 0.2, 1)
16
+ const DEFAULT_ELEVATION = 2
17
+ const SHADOW_HEIGHT = [0, 1, 2, 4, 6, 8]
18
+ const SHADOW_RADIUS = [0, 3, 6, 8, 10, 12]
19
+
20
+ const AnimatedBlurView = Animated.createAnimatedComponent(BlurView)
21
+
22
+ type FadingBlurViewProps = {
23
+ blur: boolean
24
+ children: ReactNode
25
+ elevation: number
26
+ theme: PaperMenuProps['theme']
27
+ visible: boolean
28
+ }
29
+
30
+ // Paper fades the menu's Surface in and out. On web, an ancestor below opacity 1 cuts BlurView's CSS
31
+ // backdrop-filter off from the page behind it, and Chromium doesn't restore the blur once the Surface
32
+ // reaches 1, so the open menu stays see-through until something else repaints it. So on web the Surface
33
+ // is pinned at opacity 1 with its shadow hidden, and BlurView itself (the backdrop-filter element) runs
34
+ // Paper's fades and draws that shadow, so the two still fade together.
35
+ const FadingBlurView = ({ blur, children, elevation, theme: themeOverrides, visible }: FadingBlurViewProps) => {
36
+ const { animation, roundness } = useTheme<MD2Theme | MD3Theme>(themeOverrides)
37
+ const [opacity] = useState(() => new Animated.Value(0))
38
+ // Once Paper starts hiding the menu it unmounts this content when that fade-out ends, even if visible
39
+ // turns back on first, so a close is final here too rather than fading back in only to vanish.
40
+ const [closing, setClosing] = useState(false)
41
+ if (!visible && !closing) setClosing(true)
42
+
43
+ useEffect(() => {
44
+ Animated.timing(opacity, { duration: animation.scale * ANIMATION_DURATION, easing: EASING, toValue: closing ? 0 : 1, useNativeDriver: false }).start()
45
+ }, [animation.scale, closing, opacity])
46
+
47
+ const shadow = elevation > 0 ? { shadowColor: '#000000', shadowOffset: { height: SHADOW_HEIGHT[elevation], width: 0 }, shadowOpacity: 0.3, shadowRadius: SHADOW_RADIUS[elevation] } : null
48
+ return (
49
+ <AnimatedBlurView blur={blur} style={[{ borderRadius: roundness, opacity }, shadow]}>
50
+ {children}
51
+ </AnimatedBlurView>
52
+ )
53
+ }
54
+
12
55
  const MenuComponent = ({ blur: blurProp, children, ...props }: MenuProps) => {
13
56
  const blur = useBlur(blurProp)
57
+ const web = Platform.OS === 'web'
14
58
  return (
15
- <PaperMenu {...props} contentStyle={[styles.content, props.contentStyle]}>
16
- <BlurView blur={blur}>{children}</BlurView>
59
+ <PaperMenu {...props} contentStyle={[styles.content, web && styles.webSurface, props.contentStyle]}>
60
+ {web ? (
61
+ <FadingBlurView blur={blur} elevation={props.mode === 'flat' ? 0 : (props.elevation ?? DEFAULT_ELEVATION)} theme={props.theme} visible={props.visible}>
62
+ {children}
63
+ </FadingBlurView>
64
+ ) : (
65
+ <BlurView blur={blur}>{children}</BlurView>
66
+ )}
17
67
  </PaperMenu>
18
68
  )
19
69
  }
@@ -23,5 +73,6 @@ export const Menu = Object.assign(MenuComponent, {
23
73
  })
24
74
 
25
75
  const styles = StyleSheet.create({
26
- content: { backgroundColor: 'transparent', paddingVertical: 0 }
76
+ content: { backgroundColor: 'transparent', paddingVertical: 0 },
77
+ webSurface: { opacity: 1, shadowOpacity: 0 }
27
78
  })