@oxyhq/bloom 0.60.0 → 0.62.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 (51) hide show
  1. package/lib/commonjs/bottom-sheet/BottomSheetBase.js +24 -9
  2. package/lib/commonjs/bottom-sheet/BottomSheetBase.js.map +1 -1
  3. package/lib/commonjs/dialog/Dialog.web.js +14 -8
  4. package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
  5. package/lib/commonjs/image-aspect-ratio-cache/imageAspectRatioCache.js +18 -1
  6. package/lib/commonjs/image-aspect-ratio-cache/imageAspectRatioCache.js.map +1 -1
  7. package/lib/commonjs/image-aspect-ratio-cache/index.js +6 -0
  8. package/lib/commonjs/image-aspect-ratio-cache/index.js.map +1 -1
  9. package/lib/commonjs/overlay/index.js +33 -19
  10. package/lib/commonjs/overlay/index.js.map +1 -1
  11. package/lib/commonjs/zoomable-image-gallery/ZoomableImageGallery.js +17 -10
  12. package/lib/commonjs/zoomable-image-gallery/ZoomableImageGallery.js.map +1 -1
  13. package/lib/module/bottom-sheet/BottomSheetBase.js +25 -10
  14. package/lib/module/bottom-sheet/BottomSheetBase.js.map +1 -1
  15. package/lib/module/dialog/Dialog.web.js +14 -8
  16. package/lib/module/dialog/Dialog.web.js.map +1 -1
  17. package/lib/module/image-aspect-ratio-cache/imageAspectRatioCache.js +16 -0
  18. package/lib/module/image-aspect-ratio-cache/imageAspectRatioCache.js.map +1 -1
  19. package/lib/module/image-aspect-ratio-cache/index.js +1 -1
  20. package/lib/module/image-aspect-ratio-cache/index.js.map +1 -1
  21. package/lib/module/overlay/index.js +33 -21
  22. package/lib/module/overlay/index.js.map +1 -1
  23. package/lib/module/zoomable-image-gallery/ZoomableImageGallery.js +18 -11
  24. package/lib/module/zoomable-image-gallery/ZoomableImageGallery.js.map +1 -1
  25. package/lib/typescript/commonjs/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
  26. package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
  27. package/lib/typescript/commonjs/image-aspect-ratio-cache/imageAspectRatioCache.d.ts +12 -0
  28. package/lib/typescript/commonjs/image-aspect-ratio-cache/imageAspectRatioCache.d.ts.map +1 -1
  29. package/lib/typescript/commonjs/image-aspect-ratio-cache/index.d.ts +2 -1
  30. package/lib/typescript/commonjs/image-aspect-ratio-cache/index.d.ts.map +1 -1
  31. package/lib/typescript/commonjs/overlay/index.d.ts +21 -3
  32. package/lib/typescript/commonjs/overlay/index.d.ts.map +1 -1
  33. package/lib/typescript/commonjs/zoomable-image-gallery/ZoomableImageGallery.d.ts.map +1 -1
  34. package/lib/typescript/module/bottom-sheet/BottomSheetBase.d.ts.map +1 -1
  35. package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
  36. package/lib/typescript/module/image-aspect-ratio-cache/imageAspectRatioCache.d.ts +12 -0
  37. package/lib/typescript/module/image-aspect-ratio-cache/imageAspectRatioCache.d.ts.map +1 -1
  38. package/lib/typescript/module/image-aspect-ratio-cache/index.d.ts +2 -1
  39. package/lib/typescript/module/image-aspect-ratio-cache/index.d.ts.map +1 -1
  40. package/lib/typescript/module/overlay/index.d.ts +21 -3
  41. package/lib/typescript/module/overlay/index.d.ts.map +1 -1
  42. package/lib/typescript/module/zoomable-image-gallery/ZoomableImageGallery.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/src/__tests__/imageIntrinsicSize.test.ts +47 -0
  45. package/src/__tests__/overlay-pointer-events.test.tsx +17 -16
  46. package/src/bottom-sheet/BottomSheetBase.tsx +24 -9
  47. package/src/dialog/Dialog.web.tsx +16 -18
  48. package/src/image-aspect-ratio-cache/imageAspectRatioCache.ts +18 -0
  49. package/src/image-aspect-ratio-cache/index.ts +2 -0
  50. package/src/overlay/index.tsx +59 -30
  51. package/src/zoomable-image-gallery/ZoomableImageGallery.tsx +25 -10
@@ -1,5 +1,17 @@
1
1
  /** Fallback ratio used when a remote image's intrinsic size cannot be read. */
2
2
  export declare const DEFAULT_ASPECT_RATIO: number;
3
+ /**
4
+ * Intrinsic pixel size, recorded whenever we had to read it anyway (the
5
+ * `Image.getSize` behind `fetchAspectRatio`). A viewer needs it to avoid
6
+ * blowing a small image up past its own resolution — a ratio alone cannot say
7
+ * how big "big" is allowed to be.
8
+ */
9
+ export interface IntrinsicSize {
10
+ width: number;
11
+ height: number;
12
+ }
13
+ /** Intrinsic size for `uri`, when it has already been measured. */
14
+ export declare const getIntrinsicSize: (uri: string) => IntrinsicSize | undefined;
3
15
  export declare const getAspectRatio: (uri: string) => number | undefined;
4
16
  export declare const hasAspectRatio: (uri: string) => boolean;
5
17
  export declare const setAspectRatio: (uri: string, ratio: number) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"imageAspectRatioCache.d.ts","sourceRoot":"","sources":["../../../../src/image-aspect-ratio-cache/imageAspectRatioCache.ts"],"names":[],"mappings":"AAeA,+EAA+E;AAC/E,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAI1C,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,SAAsC,CAAC;AAE7F,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,OAAoC,CAAC;AAElF,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,EAAE,OAAO,MAAM,KAAG,IAI3D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,OAAO,CAAC,MAAM,CAwB5D,CAAC"}
1
+ {"version":3,"file":"imageAspectRatioCache.d.ts","sourceRoot":"","sources":["../../../../src/image-aspect-ratio-cache/imageAspectRatioCache.ts"],"names":[],"mappings":"AAeA,+EAA+E;AAC/E,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAI1C;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAID,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,aAAa,GAAG,SAClC,CAAC;AAE9B,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,SAAsC,CAAC;AAE7F,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,OAAoC,CAAC;AAElF,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,EAAE,OAAO,MAAM,KAAG,IAI3D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,OAAO,CAAC,MAAM,CAyB5D,CAAC"}
@@ -1,2 +1,3 @@
1
- export { getAspectRatio, hasAspectRatio, setAspectRatio, fetchAspectRatio, DEFAULT_ASPECT_RATIO, } from './imageAspectRatioCache';
1
+ export type { IntrinsicSize } from './imageAspectRatioCache';
2
+ export { getIntrinsicSize, getAspectRatio, hasAspectRatio, setAspectRatio, fetchAspectRatio, DEFAULT_ASPECT_RATIO, } from './imageAspectRatioCache';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/image-aspect-ratio-cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/image-aspect-ratio-cache/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC"}
@@ -40,7 +40,8 @@
40
40
  * dimming layer; do not re-implement either with raw `View`s.
41
41
  */
42
42
  import { type ReactNode } from 'react';
43
- import { View, type StyleProp, type ViewStyle } from 'react-native';
43
+ import { type StyleProp, type ViewStyle } from 'react-native';
44
+ import { type SharedValue } from 'react-native-reanimated';
44
45
  /**
45
46
  * One blur radius for every Bloom overlay. Surfaces differ in what they show,
46
47
  * not in how the app behind them recedes.
@@ -66,6 +67,12 @@ export declare namespace OverlayRoot {
66
67
  export interface BackdropProps {
67
68
  /** Dismiss handler. Omit (or pass `disabled`) for a backdrop that only dims. */
68
69
  onPress?: () => void;
70
+ /**
71
+ * Fade driver, 0 (hidden) → 1 (fully shown). The backdrop applies it to its
72
+ * OWN layers — see the note on `style` for why the caller must not animate
73
+ * opacity from the outside.
74
+ */
75
+ progress?: SharedValue<number>;
69
76
  /** `true` keeps the dim but makes it inert — a blocking dialog, a busy state. */
70
77
  disabled?: boolean;
71
78
  /** Blur radius behind the dim. `0` renders the dim alone. */
@@ -76,8 +83,19 @@ export interface BackdropProps {
76
83
  dimColor?: string;
77
84
  /** Dim opacity, 0–1. */
78
85
  dimOpacity?: number;
79
- /** Extra style on the press target — animated opacity, insets, z-index. */
86
+ /**
87
+ * Geometry for the press target: insets, z-index, layout. NOT opacity —
88
+ * `backdrop-filter` samples nothing under an ancestor with `opacity < 1`
89
+ * (the group composites in isolation), so a fade applied here silently kills
90
+ * the blur. An `opacity` found in this style is redirected onto the layers;
91
+ * animate the fade through `progress` instead.
92
+ */
80
93
  style?: StyleProp<ViewStyle>;
94
+ /**
95
+ * Extra style for the blur + dim LAYERS, for fades a shared value can't
96
+ * express — the web dialog's CSS keyframes, for instance.
97
+ */
98
+ layerStyle?: StyleProp<ViewStyle>;
81
99
  /** Rendered ON TOP of the dim, inside the press target (Dialog's panel does this). */
82
100
  children?: ReactNode;
83
101
  accessibilityLabel?: string;
@@ -88,5 +106,5 @@ export interface BackdropProps {
88
106
  * pointer events (that is its whole job), so anything that must stay pressable
89
107
  * goes INSIDE it as `children`, never as a sibling rendered over it.
90
108
  */
91
- export declare const Backdrop: import("react").NamedExoticComponent<BackdropProps & import("react").RefAttributes<View>>;
109
+ export declare const Backdrop: import("react").NamedExoticComponent<BackdropProps>;
92
110
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/overlay/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEzD,OAAO,EAIL,IAAI,EACJ,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAItB;;;GAGG;AACH,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,gBAAgB,2CAMxE;yBANe,WAAW;;;AAU3B,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IACxC,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,sFAAsF;IACtF,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,2FAuDlB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/overlay/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE7C,OAAO,EAKL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AACtB,OAAiB,EAAoB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAMvF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,gBAAgB,2CAMxE;yBANe,WAAW;;;AAU3B,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IACxC,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,sFAAsF;IACtF,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,qDAgEnB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ZoomableImageGallery.d.ts","sourceRoot":"","sources":["../../../../src/zoomable-image-gallery/ZoomableImageGallery.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAmEjF,OAAO,KAAK,EAGV,0BAA0B,EAC1B,yBAAyB,EAC1B,MAAM,SAAS,CAAC;AAo6BjB,eAAO,MAAM,oBAAoB,8GAA4B,CAAC;AAyK9D,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"ZoomableImageGallery.d.ts","sourceRoot":"","sources":["../../../../src/zoomable-image-gallery/ZoomableImageGallery.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAoEjF,OAAO,KAAK,EAGV,0BAA0B,EAC1B,yBAAyB,EAC1B,MAAM,SAAS,CAAC;AAk7BjB,eAAO,MAAM,oBAAoB,8GAA4B,CAAC;AAyK9D,eAAe,oBAAoB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"BottomSheetBase.d.ts","sourceRoot":"","sources":["../../../../src/bottom-sheet/BottomSheetBase.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAMH,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACd,KAAK,SAAS,EACjB,MAAM,cAAc,CAAC;AAItB,OAAiB,EACb,KAAK,aAAa,EAGlB,KAAK,WAAW,EAMnB,MAAM,yBAAyB,CAAC;AAyBjC,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;KAAE,KAAK,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;IAC7F,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;KAAE,KAAK,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;IACjH;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;IAC5C,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;OAcG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACxC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B;;;;OAIG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACjD;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IAClC,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAC;IACjB,gFAAgF;IAChF,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B;;;OAGG;IACH,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACpC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC1D,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;CACrD;AAID,eAAO,MAAM,eAAe,6FA4mB1B,CAAC"}
1
+ {"version":3,"file":"BottomSheetBase.d.ts","sourceRoot":"","sources":["../../../../src/bottom-sheet/BottomSheetBase.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAMH,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACd,KAAK,SAAS,EACjB,MAAM,cAAc,CAAC;AAItB,OAAiB,EACb,KAAK,aAAa,EAGlB,KAAK,WAAW,EAOnB,MAAM,yBAAyB,CAAC;AAyBjC,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACrD;AAED,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;KAAE,KAAK,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;IAC7F,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;KAAE,KAAK,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC;IACjH;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;IAC5C,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;OAcG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;;OAOG;IACH,eAAe,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IACxC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B;;;;OAIG;IACH,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACjD;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IAClC,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAC;IACjB,gFAAgF;IAChF,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B;;;OAGG;IACH,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACpC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC1D,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;CACrD;AAED,eAAO,MAAM,eAAe,6FA4nB1B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.web.d.ts","sourceRoot":"","sources":["../../../../src/dialog/Dialog.web.tsx"],"names":[],"mappings":"AAuCA,OAAO,EAOL,4BAA4B,EAM7B,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACV,YAAY,EAGZ,WAAW,EACZ,MAAM,SAAS,CAAC;AA4DjB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,MAAM,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,WAAW,2CAazD;AA+ZD,OAAO,EAAE,4BAA4B,EAAE,CAAC;AAoPxC;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,WAAW,EACX,OAAO,EACP,SAAS,GACV,EAAE;IACD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB,2CAgBA;AAiCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gBAAgB,mZAK5B,CAAC"}
1
+ {"version":3,"file":"Dialog.web.d.ts","sourceRoot":"","sources":["../../../../src/dialog/Dialog.web.tsx"],"names":[],"mappings":"AAuCA,OAAO,EAOL,4BAA4B,EAM7B,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACV,YAAY,EAGZ,WAAW,EACZ,MAAM,SAAS,CAAC;AA4DjB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,MAAM,CAAC,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,WAAW,2CAazD;AAgaD,OAAO,EAAE,4BAA4B,EAAE,CAAC;AAiPxC;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,WAAW,EACX,OAAO,EACP,SAAS,GACV,EAAE;IACD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB,2CAgBA;AAiCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gBAAgB,mZAK5B,CAAC"}
@@ -1,5 +1,17 @@
1
1
  /** Fallback ratio used when a remote image's intrinsic size cannot be read. */
2
2
  export declare const DEFAULT_ASPECT_RATIO: number;
3
+ /**
4
+ * Intrinsic pixel size, recorded whenever we had to read it anyway (the
5
+ * `Image.getSize` behind `fetchAspectRatio`). A viewer needs it to avoid
6
+ * blowing a small image up past its own resolution — a ratio alone cannot say
7
+ * how big "big" is allowed to be.
8
+ */
9
+ export interface IntrinsicSize {
10
+ width: number;
11
+ height: number;
12
+ }
13
+ /** Intrinsic size for `uri`, when it has already been measured. */
14
+ export declare const getIntrinsicSize: (uri: string) => IntrinsicSize | undefined;
3
15
  export declare const getAspectRatio: (uri: string) => number | undefined;
4
16
  export declare const hasAspectRatio: (uri: string) => boolean;
5
17
  export declare const setAspectRatio: (uri: string, ratio: number) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"imageAspectRatioCache.d.ts","sourceRoot":"","sources":["../../../../src/image-aspect-ratio-cache/imageAspectRatioCache.ts"],"names":[],"mappings":"AAeA,+EAA+E;AAC/E,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAI1C,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,SAAsC,CAAC;AAE7F,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,OAAoC,CAAC;AAElF,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,EAAE,OAAO,MAAM,KAAG,IAI3D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,OAAO,CAAC,MAAM,CAwB5D,CAAC"}
1
+ {"version":3,"file":"imageAspectRatioCache.d.ts","sourceRoot":"","sources":["../../../../src/image-aspect-ratio-cache/imageAspectRatioCache.ts"],"names":[],"mappings":"AAeA,+EAA+E;AAC/E,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAI1C;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAID,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,aAAa,GAAG,SAClC,CAAC;AAE9B,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,SAAsC,CAAC;AAE7F,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,OAAoC,CAAC;AAElF,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,EAAE,OAAO,MAAM,KAAG,IAI3D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,OAAO,CAAC,MAAM,CAyB5D,CAAC"}
@@ -1,2 +1,3 @@
1
- export { getAspectRatio, hasAspectRatio, setAspectRatio, fetchAspectRatio, DEFAULT_ASPECT_RATIO, } from './imageAspectRatioCache';
1
+ export type { IntrinsicSize } from './imageAspectRatioCache';
2
+ export { getIntrinsicSize, getAspectRatio, hasAspectRatio, setAspectRatio, fetchAspectRatio, DEFAULT_ASPECT_RATIO, } from './imageAspectRatioCache';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/image-aspect-ratio-cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/image-aspect-ratio-cache/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC"}
@@ -40,7 +40,8 @@
40
40
  * dimming layer; do not re-implement either with raw `View`s.
41
41
  */
42
42
  import { type ReactNode } from 'react';
43
- import { View, type StyleProp, type ViewStyle } from 'react-native';
43
+ import { type StyleProp, type ViewStyle } from 'react-native';
44
+ import { type SharedValue } from 'react-native-reanimated';
44
45
  /**
45
46
  * One blur radius for every Bloom overlay. Surfaces differ in what they show,
46
47
  * not in how the app behind them recedes.
@@ -66,6 +67,12 @@ export declare namespace OverlayRoot {
66
67
  export interface BackdropProps {
67
68
  /** Dismiss handler. Omit (or pass `disabled`) for a backdrop that only dims. */
68
69
  onPress?: () => void;
70
+ /**
71
+ * Fade driver, 0 (hidden) → 1 (fully shown). The backdrop applies it to its
72
+ * OWN layers — see the note on `style` for why the caller must not animate
73
+ * opacity from the outside.
74
+ */
75
+ progress?: SharedValue<number>;
69
76
  /** `true` keeps the dim but makes it inert — a blocking dialog, a busy state. */
70
77
  disabled?: boolean;
71
78
  /** Blur radius behind the dim. `0` renders the dim alone. */
@@ -76,8 +83,19 @@ export interface BackdropProps {
76
83
  dimColor?: string;
77
84
  /** Dim opacity, 0–1. */
78
85
  dimOpacity?: number;
79
- /** Extra style on the press target — animated opacity, insets, z-index. */
86
+ /**
87
+ * Geometry for the press target: insets, z-index, layout. NOT opacity —
88
+ * `backdrop-filter` samples nothing under an ancestor with `opacity < 1`
89
+ * (the group composites in isolation), so a fade applied here silently kills
90
+ * the blur. An `opacity` found in this style is redirected onto the layers;
91
+ * animate the fade through `progress` instead.
92
+ */
80
93
  style?: StyleProp<ViewStyle>;
94
+ /**
95
+ * Extra style for the blur + dim LAYERS, for fades a shared value can't
96
+ * express — the web dialog's CSS keyframes, for instance.
97
+ */
98
+ layerStyle?: StyleProp<ViewStyle>;
81
99
  /** Rendered ON TOP of the dim, inside the press target (Dialog's panel does this). */
82
100
  children?: ReactNode;
83
101
  accessibilityLabel?: string;
@@ -88,5 +106,5 @@ export interface BackdropProps {
88
106
  * pointer events (that is its whole job), so anything that must stay pressable
89
107
  * goes INSIDE it as `children`, never as a sibling rendered over it.
90
108
  */
91
- export declare const Backdrop: import("react").NamedExoticComponent<BackdropProps & import("react").RefAttributes<View>>;
109
+ export declare const Backdrop: import("react").NamedExoticComponent<BackdropProps>;
92
110
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/overlay/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEzD,OAAO,EAIL,IAAI,EACJ,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAItB;;;GAGG;AACH,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,gBAAgB,2CAMxE;yBANe,WAAW;;;AAU3B,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IACxC,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,sFAAsF;IACtF,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,2FAuDlB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/overlay/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE7C,OAAO,EAKL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AACtB,OAAiB,EAAoB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAMvF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,gBAAgB,2CAMxE;yBANe,WAAW;;;AAU3B,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IACxC,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,sFAAsF;IACtF,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,qDAgEnB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ZoomableImageGallery.d.ts","sourceRoot":"","sources":["../../../../src/zoomable-image-gallery/ZoomableImageGallery.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAmEjF,OAAO,KAAK,EAGV,0BAA0B,EAC1B,yBAAyB,EAC1B,MAAM,SAAS,CAAC;AAo6BjB,eAAO,MAAM,oBAAoB,8GAA4B,CAAC;AAyK9D,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"ZoomableImageGallery.d.ts","sourceRoot":"","sources":["../../../../src/zoomable-image-gallery/ZoomableImageGallery.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAoEjF,OAAO,KAAK,EAGV,0BAA0B,EAC1B,yBAAyB,EAC1B,MAAM,SAAS,CAAC;AAk7BjB,eAAO,MAAM,oBAAoB,8GAA4B,CAAC;AAyK9D,eAAe,oBAAoB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/bloom",
3
- "version": "0.60.0",
3
+ "version": "0.62.0",
4
4
  "description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -0,0 +1,47 @@
1
+ // The fullscreen viewer must not blow an image up past its own resolution — a
2
+ // 512px avatar or a small thumbnail filling the viewport just renders soft.
3
+ // A ratio alone cannot express that ceiling, so the shared cache records the
4
+ // intrinsic size it had to read anyway (the `Image.getSize` behind
5
+ // `fetchAspectRatio`) and the viewer clamps its fit box to it.
6
+
7
+ const getSize = jest.fn();
8
+
9
+ jest.mock('react-native', () => ({
10
+ Image: { getSize: (...args: unknown[]) => getSize(...args) },
11
+ }));
12
+
13
+ import {
14
+ DEFAULT_ASPECT_RATIO,
15
+ fetchAspectRatio,
16
+ getAspectRatio,
17
+ getIntrinsicSize,
18
+ } from '../image-aspect-ratio-cache';
19
+
20
+ describe('intrinsic size cache', () => {
21
+ beforeEach(() => {
22
+ getSize.mockReset();
23
+ });
24
+
25
+ it('records the measured pixel size alongside the ratio', async () => {
26
+ const uri = 'https://cdn.example/avatar-512.jpg';
27
+ getSize.mockImplementation((_uri: string, ok: (w: number, h: number) => void) => ok(512, 512));
28
+
29
+ const ratio = await fetchAspectRatio(uri);
30
+
31
+ expect(ratio).toBe(1);
32
+ expect(getAspectRatio(uri)).toBe(1);
33
+ expect(getIntrinsicSize(uri)).toEqual({ width: 512, height: 512 });
34
+ });
35
+
36
+ it('leaves no size behind when the image cannot be measured', async () => {
37
+ const uri = 'https://cdn.example/broken.jpg';
38
+ getSize.mockImplementation((_uri: string, _ok: unknown, fail: () => void) => fail());
39
+
40
+ const ratio = await fetchAspectRatio(uri);
41
+
42
+ expect(ratio).toBe(DEFAULT_ASPECT_RATIO);
43
+ // No measurement means no ceiling — the viewer falls back to its fit box
44
+ // rather than clamping to a guess.
45
+ expect(getIntrinsicSize(uri)).toBeUndefined();
46
+ });
47
+ });
@@ -123,25 +123,26 @@ describe('overlay pointer-events contract (web)', () => {
123
123
  noBlur.container.remove();
124
124
  });
125
125
 
126
- // Surfaces animate the backdrop via `Animated.createAnimatedComponent(Backdrop)`,
127
- // and reanimated writes those updates through the ref. Swallow it and an
128
- // opacity animated from 0 never moves: the backdrop stays fully transparent
129
- // while the surface floats over an undimmed app (shipped exactly once).
130
- it('forwards its ref to a host node so animated styles can land', () => {
131
- let node: unknown = null;
126
+ // THE fade invariant. `backdrop-filter` samples nothing under an ancestor
127
+ // that composites in isolation, so an opacity anywhere above the blur layer
128
+ // silently removes the blur which is exactly what a surface fading the
129
+ // backdrop from the outside used to do. A caller's `opacity` is therefore
130
+ // redirected onto the layers and the press target stays fully opaque.
131
+ it('keeps the press target opaque and fades the layers instead', () => {
132
132
  const { root, container } = render(
133
- createElement(Backdrop, {
134
- onPress: () => {},
135
- testID: 'backdrop',
136
- ref: (el: unknown) => {
137
- node = el;
138
- },
139
- } as never),
133
+ createElement(Backdrop, { onPress: () => {}, testID: 'backdrop', style: { opacity: 0.4 } }),
140
134
  );
141
135
 
142
- expect(node).not.toBeNull();
143
- expect(node).toBeInstanceOf(HTMLElement);
144
- expect((node as HTMLElement).getAttribute('data-testid')).toBe('backdrop');
136
+ const el = container.querySelector('[data-testid="backdrop"]') as HTMLElement;
137
+ const inline = el.style.opacity;
138
+ const opacity = Number(inline === '' ? getComputedStyle(el).opacity || '1' : inline);
139
+
140
+ // The press target itself never fades — the blur under it would go with it.
141
+ expect(opacity).toBe(1);
142
+ // …and the layers it fades instead are there to receive it. (Their opacity
143
+ // is written by reanimated, which this suite mocks, so the value itself is
144
+ // asserted in the browser rather than here.)
145
+ expect(el.children.length).toBeGreaterThanOrEqual(2);
145
146
 
146
147
  act(() => root.unmount());
147
148
  container.remove();
@@ -20,6 +20,7 @@ import Animated, {
20
20
  type SharedValue,
21
21
  useAnimatedScrollHandler,
22
22
  useAnimatedStyle,
23
+ useDerivedValue,
23
24
  useSharedValue,
24
25
  withSpring,
25
26
  withTiming,
@@ -199,8 +200,6 @@ export interface BottomSheetBaseProps extends BottomSheetProps {
199
200
  Shell: React.ComponentType<BottomSheetShellProps>;
200
201
  }
201
202
 
202
- const AnimatedBackdrop = Animated.createAnimatedComponent(Backdrop);
203
-
204
203
  export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: React.ForwardedRef<BottomSheetRef>) => {
205
204
  const {
206
205
  Shell,
@@ -642,7 +641,11 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
642
641
  // dim opacity applies smoothly across the animation. When
643
642
  // `dynamicBackdrop` is enabled, the dim also fades proportionally with
644
643
  // drag distance (iOS Photos style).
645
- const backdropStyle = useAnimatedStyle(() => {
644
+ // A VALUE, not a style: `Backdrop` applies it to its blur and dim layers
645
+ // itself. Handing it over as an animated style would put the opacity on the
646
+ // layers' shared ancestor, which composites the group in isolation and
647
+ // leaves `backdrop-filter` with nothing behind it to blur.
648
+ const backdropProgress = useDerivedValue(() => {
646
649
  const dragFactor = dynamicBackdrop
647
650
  ? interpolate(
648
651
  translateY.value,
@@ -651,11 +654,17 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
651
654
  'clamp',
652
655
  )
653
656
  : 1;
654
- return {
655
- opacity: opacity.value * backdropOpacity * dragFactor,
656
- };
657
+ return opacity.value * backdropOpacity * dragFactor;
657
658
  }, [backdropOpacity, dynamicBackdrop, opacity, translateY, screenHeightSV]);
658
659
 
660
+ // Only the consumer-supplied `backdropComponent` needs the progress as a
661
+ // style — it owns its own visuals, blur included, so the ancestor-opacity
662
+ // hazard is its call to make.
663
+ const customBackdropStyle = useAnimatedStyle(
664
+ () => ({ opacity: backdropProgress.value }),
665
+ [backdropProgress],
666
+ );
667
+
659
668
  const sheetStyle = useAnimatedStyle(() => {
660
669
  const scale = interpolate(translateY.value, [0, screenHeightSV.value], [1, 0.95]);
661
670
  return {
@@ -787,7 +796,7 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
787
796
  <Shell visible={rendered} onRequestClose={dismiss} keyboardHeight={keyboardHeight}>
788
797
  <View style={StyleSheet.absoluteFill}>
789
798
  {backdropComponent ? (
790
- <Animated.View style={[StyleSheet.absoluteFill, backdropStyle]}>
799
+ <Animated.View style={[StyleSheet.absoluteFill, customBackdropStyle]}>
791
800
  {backdropComponent({ onPress: handleBackdropPress })}
792
801
  </Animated.View>
793
802
  ) : (
@@ -795,10 +804,16 @@ export const BottomSheetBase = forwardRef((props: BottomSheetBaseProps, ref: Rea
795
804
  // `backdropStyle` drives the fade; the dim level rides on
796
805
  // `backdropOpacity`, so the shared component's own dim is
797
806
  // switched off here to keep a single source of dimming.
798
- <AnimatedBackdrop
807
+ <Backdrop
799
808
  onPress={handleBackdropPress}
809
+ // `backdropProgress` already folds in `backdropOpacity`
810
+ // and the drag factor, so the dim rides at full weight.
811
+ // It goes through `progress` rather than an animated
812
+ // style because an opacity on the root would sit above
813
+ // the blur layer and neutralise it.
800
814
  dimOpacity={1}
801
- style={[styles.backdrop, backdropStyle]}
815
+ progress={backdropProgress}
816
+ style={styles.backdrop}
802
817
  />
803
818
  )}
804
819
 
@@ -332,16 +332,17 @@ function CenterOrSideDialog({
332
332
  disabled={!dismissOnBackdrop}
333
333
  accessibilityLabel={label ? `Dismiss ${label}` : 'Dismiss dialog'}
334
334
  dimOpacity={DIALOG_BACKDROP_DIM_OPACITY}
335
- style={[
336
- {
337
- position: WEB_POSITION_FIXED,
338
- zIndex: dialogZIndex.backdrop,
339
- alignItems: 'center',
340
- justifyContent: 'center',
341
- paddingHorizontal: 20,
342
- },
343
- isClosing ? BACKDROP_FADE_OUT : BACKDROP_FADE_IN,
344
- ]}
335
+ // The fade rides on the LAYERS, never on the press target: an
336
+ // opacity animation on the blur's ancestor composites the group in
337
+ // isolation and leaves `backdrop-filter` nothing to sample.
338
+ layerStyle={isClosing ? BACKDROP_FADE_OUT : BACKDROP_FADE_IN}
339
+ style={{
340
+ position: WEB_POSITION_FIXED,
341
+ zIndex: dialogZIndex.backdrop,
342
+ alignItems: 'center',
343
+ justifyContent: 'center',
344
+ paddingHorizontal: 20,
345
+ }}
345
346
  >
346
347
  <DialogPanel
347
348
  testID={testID}
@@ -702,14 +703,11 @@ function SheetSurface({
702
703
  accessibilityLabel={label ? `Dismiss ${label}` : 'Dismiss dialog'}
703
704
  onPress={handleBackdropPress}
704
705
  disabled={!dismissOnBackdrop}
705
- // The dim rides on the element's own animated opacity here, so the
706
- // shared component's dim layer stays fully opaque under it.
707
- dimOpacity={1}
708
- style={[
709
- sheetStyles.backdrop,
710
- backdropTransition,
711
- { opacity: visible ? SHEET_BACKDROP_OPACITY : 0 },
712
- ]}
706
+ // Same reason as the centred dialog: the transition and the opacity it
707
+ // animates belong to the layers, not to the press target above them.
708
+ dimOpacity={SHEET_BACKDROP_OPACITY}
709
+ layerStyle={backdropTransition}
710
+ style={[sheetStyles.backdrop, { opacity: visible ? 1 : 0 }]}
713
711
  />
714
712
 
715
713
  <View
@@ -18,6 +18,23 @@ export const DEFAULT_ASPECT_RATIO = 4 / 3;
18
18
 
19
19
  const aspectRatioCache = new Map<string, number>();
20
20
 
21
+ /**
22
+ * Intrinsic pixel size, recorded whenever we had to read it anyway (the
23
+ * `Image.getSize` behind `fetchAspectRatio`). A viewer needs it to avoid
24
+ * blowing a small image up past its own resolution — a ratio alone cannot say
25
+ * how big "big" is allowed to be.
26
+ */
27
+ export interface IntrinsicSize {
28
+ width: number;
29
+ height: number;
30
+ }
31
+
32
+ const intrinsicSizeCache = new Map<string, IntrinsicSize>();
33
+
34
+ /** Intrinsic size for `uri`, when it has already been measured. */
35
+ export const getIntrinsicSize = (uri: string): IntrinsicSize | undefined =>
36
+ intrinsicSizeCache.get(uri);
37
+
21
38
  export const getAspectRatio = (uri: string): number | undefined => aspectRatioCache.get(uri);
22
39
 
23
40
  export const hasAspectRatio = (uri: string): boolean => aspectRatioCache.has(uri);
@@ -47,6 +64,7 @@ export const fetchAspectRatio = (uri: string): Promise<number> => {
47
64
  if (width > 0 && height > 0) {
48
65
  const ratio = width / height;
49
66
  aspectRatioCache.set(uri, ratio);
67
+ intrinsicSizeCache.set(uri, { width, height });
50
68
  resolve(ratio);
51
69
  return;
52
70
  }
@@ -1,4 +1,6 @@
1
+ export type { IntrinsicSize } from './imageAspectRatioCache';
1
2
  export {
3
+ getIntrinsicSize,
2
4
  getAspectRatio,
3
5
  hasAspectRatio,
4
6
  setAspectRatio,