xref: /expo/packages/expo-image/src/utils.ts (revision baea6e76)
1import {
2  ImageContentFit,
3  ImageContentPosition,
4  ImageContentPositionObject,
5  ImageContentPositionString,
6  ImageProps,
7  ImageTransition,
8} from './Image.types';
9
10let loggedResizeModeDeprecationWarning = false;
11let loggedRepeatDeprecationWarning = false;
12let loggedFadeDurationDeprecationWarning = false;
13
14/**
15 * If the `contentFit` is not provided, it's resolved from the equivalent `resizeMode` prop
16 * that we support to provide compatibility with React Native Image.
17 */
18export function resolveContentFit(
19  contentFit?: ImageContentFit,
20  resizeMode?: ImageProps['resizeMode']
21): ImageContentFit {
22  if (contentFit) {
23    return contentFit;
24  }
25  if (resizeMode) {
26    if (!loggedResizeModeDeprecationWarning) {
27      console.log('[expo-image]: Prop "resizeMode" is deprecated, use "contentFit" instead');
28      loggedResizeModeDeprecationWarning = true;
29    }
30
31    switch (resizeMode) {
32      case 'contain':
33      case 'cover':
34        return resizeMode;
35      case 'stretch':
36        return 'fill';
37      case 'center':
38        return 'scale-down';
39      case 'repeat':
40        if (!loggedRepeatDeprecationWarning) {
41          console.log('[expo-image]: Resize mode "repeat" is no longer supported');
42          loggedRepeatDeprecationWarning = true;
43        }
44    }
45  }
46  return 'cover';
47}
48
49/**
50 * It resolves a stringified form of the `contentPosition` prop to an object,
51 * which is the only form supported in the native code.
52 */
53export function resolveContentPosition(
54  contentPosition?: ImageContentPosition
55): ImageContentPositionObject {
56  if (typeof contentPosition === 'string') {
57    const contentPositionStringMappings: Record<
58      ImageContentPositionString,
59      ImageContentPositionObject
60    > = {
61      center: { top: '50%', left: '50%' },
62      top: { top: 0, left: '50%' },
63      right: { top: '50%', right: 0 },
64      bottom: { bottom: 0, left: '50%' },
65      left: { top: '50%', left: 0 },
66      'top center': { top: 0, left: '50%' },
67      'top right': { top: 0, right: 0 },
68      'top left': { top: 0, left: 0 },
69      'right center': { top: '50%', right: 0 },
70      'right top': { top: 0, right: 0 },
71      'right bottom': { bottom: 0, right: 0 },
72      'bottom center': { bottom: 0, left: '50%' },
73      'bottom right': { bottom: 0, right: 0 },
74      'bottom left': { bottom: 0, left: 0 },
75      'left center': { top: '50%', left: 0 },
76      'left top': { top: 0, left: 0 },
77      'left bottom': { bottom: 0, left: 0 },
78    };
79    const contentPositionObject = contentPositionStringMappings[contentPosition];
80
81    if (!contentPositionObject) {
82      console.warn(`[expo-image]: Content position "${contentPosition}" is invalid`);
83      return contentPositionStringMappings.center;
84    }
85    return contentPositionObject;
86  }
87  return contentPosition ?? { top: '50%', left: '50%' };
88}
89
90/**
91 * If `transition` or `fadeDuration` is a number, it's resolved to a cross dissolve transition with the given duration.
92 * When `fadeDuration` is used, it logs an appropriate deprecation warning.
93 */
94export function resolveTransition(
95  transition?: ImageProps['transition'],
96  fadeDuration?: ImageProps['fadeDuration']
97): ImageTransition | null {
98  if (typeof transition === 'number') {
99    return { duration: transition };
100  }
101  if (!transition && typeof fadeDuration === 'number') {
102    if (!loggedFadeDurationDeprecationWarning) {
103      console.warn('[expo-image]: Prop "fadeDuration" is deprecated, use "transition" instead');
104      loggedFadeDurationDeprecationWarning = true;
105    }
106    return { duration: fadeDuration };
107  }
108  return transition ?? null;
109}
110