This commit is contained in:
Nicolas Gallagher
2021-09-28 11:01:40 -07:00
parent e92a1ce768
commit 5ec261f31e
5 changed files with 695 additions and 629 deletions

View File

@@ -1,309 +1,302 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/Image prop "accessibilityLabel" 1`] = `
<div
<img
aria-label="accessibilityLabel"
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://google.com/favicon.ico);"
/>
<img
alt="accessibilityLabel"
class="css-accessibilityImage-9pa8cd"
draggable="false"
src="https://google.com/favicon.ico"
/>
</div>
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
src="https://google.com/favicon.ico"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "alternativeText" set to empty string 1`] = `
<img
alt=""
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "alternativeText" set to value 1`] = `
<img
alt="alternative text"
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "blurRadius" 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://google.com/favicon.ico); filter: blur(5px);"
/>
<img
alt=""
class="css-accessibilityImage-9pa8cd"
draggable="false"
src="https://google.com/favicon.ico"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
src="https://google.com/favicon.ico"
style="filter: blur(5px); object-fit: cover;"
/>
`;
exports[`components/Image prop "crossOrigin" sets value 1`] = `
<img
class="css-image-tcr4mf"
crossorigin="use-credentials"
decoding="async"
draggable="false"
loading="lazy"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "decoding" sets value 1`] = `
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "defaultSource" does not override "height" and "width" styles 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
style="height: 20px; width: 40px;"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://google.com/favicon.ico);"
/>
<img
alt=""
class="css-accessibilityImage-9pa8cd"
draggable="false"
src="https://google.com/favicon.ico"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
src="https://google.com/favicon.ico"
style="height: 20px; object-fit: cover; width: 40px;"
/>
`;
exports[`components/Image prop "defaultSource" sets "height" and "width" styles if missing 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
style="height: 10px; width: 20px;"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://google.com/favicon.ico);"
/>
<img
alt=""
class="css-accessibilityImage-9pa8cd"
draggable="false"
src="https://google.com/favicon.ico"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
src="https://google.com/favicon.ico"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "defaultSource" sets background image when value is a string 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://google.com/favicon.ico);"
/>
<img
alt=""
class="css-accessibilityImage-9pa8cd"
draggable="false"
src="https://google.com/favicon.ico"
/>
</div>
exports[`components/Image prop "defaultSource" sets image when value is a string 1`] = `
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
src="https://google.com/favicon.ico"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "defaultSource" sets background image when value is an object 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://google.com/favicon.ico);"
/>
<img
alt=""
class="css-accessibilityImage-9pa8cd"
draggable="false"
src="https://google.com/favicon.ico"
/>
</div>
exports[`components/Image prop "defaultSource" sets image when value is an object 1`] = `
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
src="https://google.com/favicon.ico"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "draggable" 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://google.com/favicon.ico);"
/>
<img
alt=""
class="css-accessibilityImage-9pa8cd"
draggable="true"
src="https://google.com/favicon.ico"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="true"
loading="lazy"
src="https://google.com/favicon.ico"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "focusable" 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
style="object-fit: cover;"
tabindex="0"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
/>
</div>
/>
`;
exports[`components/Image prop "loading" sets value 1`] = `
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "nativeID" 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
id="nativeID"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
/>
</div>
loading="lazy"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "onError" is called when image fails to load and replaces src with placeholder 1`] = `
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "onLoad" is called once when image loads 1`] = `
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
src="https://google.com/favicon.ico"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "resizeMode" value "contain" 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-ehq7j7 r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
style="object-fit: contain;"
/>
`;
exports[`components/Image prop "resizeMode" value "cover" 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "resizeMode" value "none" 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-p9m3gb r-backgroundRepeat-u6sd8q r-backgroundSize-1sxrcry r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
style="object-fit: none;"
/>
`;
exports[`components/Image prop "resizeMode" value "repeat" 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-p9m3gb r-backgroundRepeat-17leim2 r-backgroundSize-1sxrcry r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
/>
`;
exports[`components/Image prop "resizeMode" value "stretch" 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-x3cy2q r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
style="object-fit: fill;"
/>
`;
exports[`components/Image prop "resizeMode" value "undefined" 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "source" is correctly updated only when loaded if defaultSource provided 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://testing.com/preview.jpg);"
/>
<img
alt=""
class="css-accessibilityImage-9pa8cd"
draggable="false"
src="https://testing.com/preview.jpg"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
src="https://testing.com/fullSize.jpg"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "source" is correctly updated only when loaded if defaultSource provided 2`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://testing.com/fullSize.jpg);"
/>
<img
alt=""
class="css-accessibilityImage-9pa8cd"
draggable="false"
src="https://testing.com/fullSize.jpg"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
src="https://testing.com/fullSize.jpg"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "source" is correctly updated when missing in initial render 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://testing.com/img.jpg);"
/>
<img
alt=""
class="css-accessibilityImage-9pa8cd"
draggable="false"
src="https://testing.com/img.jpg"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "source" is not set immediately if the image has not already been loaded 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://google.com/favicon.ico);"
/>
<img
alt=""
class="css-accessibilityImage-9pa8cd"
draggable="false"
src="https://google.com/favicon.ico"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
src="https://google.com/favicon.ico"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "source" is set immediately if the image has already been loaded 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://google.com/favicon.ico);"
/>
<img
alt=""
class="css-accessibilityImage-9pa8cd"
draggable="false"
src="https://google.com/favicon.ico"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
src="https://google.com/favicon.ico"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "source" is set immediately if the image has already been loaded 2`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://twitter.com/favicon.ico);"
/>
<img
alt=""
class="css-accessibilityImage-9pa8cd"
draggable="false"
src="https://twitter.com/favicon.ico"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
src="https://google.com/favicon.ico"
style="object-fit: cover;"
/>
`;
exports[`components/Image prop "source" is set immediately if the image was preloaded 1`] = `
@@ -324,61 +317,53 @@ exports[`components/Image prop "source" is set immediately if the image was prel
`;
exports[`components/Image prop "style" removes other unsupported View styles 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="filter: url('data:image/svg+xml,<svg xmlns=\\"http://www.w3.org/2000/svg\\"><filter id=\\"tint\\"><feColorMatrix type=\\"matrix\\" values=\\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1.00 0\\" /></filter></svg>#tint');"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
style="filter: url('data:image/svg+xml,<svg xmlns=\\"http://www.w3.org/2000/svg\\"><filter id=\\"tint\\"><feColorMatrix type=\\"matrix\\" values=\\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1.00 0\\" /></filter></svg>#tint'); object-fit: cover;"
/>
`;
exports[`components/Image prop "style" supports "resizeMode" property 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-ehq7j7 r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
style="object-fit: contain;"
/>
`;
exports[`components/Image prop "style" supports "shadow" properties (convert to filter) 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="filter: drop-shadow(1px 1px 0px rgba(255,0,0,1.00));"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
style="filter: drop-shadow(1px 1px 0px rgba(255,0,0,1.00)); object-fit: cover;"
/>
`;
exports[`components/Image prop "style" supports "tintcolor" property (convert to filter) 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://google.com/favicon.ico); filter: url('data:image/svg+xml,<svg xmlns=\\"http://www.w3.org/2000/svg\\"><filter id=\\"tint\\"><feColorMatrix type=\\"matrix\\" values=\\"0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1.00 0\\" /></filter></svg>#tint');"
/>
<img
alt=""
class="css-accessibilityImage-9pa8cd"
draggable="false"
src="https://google.com/favicon.ico"
/>
</div>
<img
class="css-image-tcr4mf"
decoding="async"
draggable="false"
loading="lazy"
src="https://google.com/favicon.ico"
style="filter: url('data:image/svg+xml,<svg xmlns=\\"http://www.w3.org/2000/svg\\"><filter id=\\"tint\\"><feColorMatrix type=\\"matrix\\" values=\\"0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1.00 0\\" /></filter></svg>#tint'); object-fit: cover;"
/>
`;
exports[`components/Image prop "testID" 1`] = `
<div
class="css-view-1dbjc4n r-flexBasis-1mlwlqe r-overflow-1udh08x r-zIndex-417010"
<img
class="css-image-tcr4mf"
data-testid="testID"
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
/>
</div>
decoding="async"
draggable="false"
loading="lazy"
style="object-fit: cover;"
/>
`;

View File

@@ -4,10 +4,11 @@
import { act } from 'react-dom/test-utils';
import * as AssetRegistry from '../../../modules/AssetRegistry';
import Image from '../';
import ImageLoader, { ImageUriCache } from '../../../modules/ImageLoader';
import { ImageUriCache } from '../../../modules/ImageLoader';
import PixelRatio from '../../PixelRatio';
import React from 'react';
import { render } from '@testing-library/react';
import { createEventTarget } from 'dom-event-testing-library';
const originalImage = window.Image;
@@ -29,20 +30,48 @@ describe('components/Image', () => {
expect(container.firstChild).toMatchSnapshot();
});
describe('prop "alternativeText"', () => {
test('set to empty string', () => {
const { container } = render(<Image alternativeText="" />);
expect(container.firstChild).toMatchSnapshot();
});
test('set to value', () => {
const { container } = render(
<Image alternativeText="alternative text" />
);
expect(container.firstChild).toMatchSnapshot();
});
});
test('prop "blurRadius"', () => {
const defaultSource = { uri: 'https://google.com/favicon.ico' };
const { container } = render(<Image blurRadius={5} defaultSource={defaultSource} />);
expect(container.firstChild).toMatchSnapshot();
});
describe('prop "crossOrigin"', () => {
test('sets value', () => {
const { container } = render(<Image crossOrigin="use-credentials" />);
expect(container.firstChild).toMatchSnapshot();
});
});
describe('prop "decoding"', () => {
test('sets value', () => {
const { container } = render(<Image decoding="async" />);
expect(container.firstChild).toMatchSnapshot();
});
});
describe('prop "defaultSource"', () => {
test('sets background image when value is an object', () => {
test('sets image when value is an object', () => {
const defaultSource = { uri: 'https://google.com/favicon.ico' };
const { container } = render(<Image defaultSource={defaultSource} />);
expect(container.firstChild).toMatchSnapshot();
});
test('sets background image when value is a string', () => {
test('sets image when value is a string', () => {
// emulate require-ed asset
const defaultSource = 'https://google.com/favicon.ico';
const { container } = render(<Image defaultSource={defaultSource} />);
@@ -83,151 +112,173 @@ describe('components/Image', () => {
expect(container.firstChild).toMatchSnapshot();
});
describe('prop "loading"', () => {
test('sets value', () => {
const { container } = render(<Image loading="lazy" />);
expect(container.firstChild).toMatchSnapshot();
});
});
test('prop "nativeID"', () => {
const { container } = render(<Image nativeID="nativeID" />);
expect(container.firstChild).toMatchSnapshot();
});
describe('prop "onLoad"', () => {
const originalLoad = ImageLoader.load;
beforeEach(() => {
ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => {
onLoad();
});
});
afterEach(() => {
ImageLoader.load = originalLoad;
});
test('is not called again if callback changes', () => {
const onLoadStub = jest.fn();
const onLoadReplacementStub = jest.fn();
const { rerender } = render(
<Image onLoad={onLoadStub} source={'https://test.com/img.jpg'} />
);
describe('prop "onError"', () => {
test('is called when image fails to load and replaces src with placeholder', () => {
const onError = jest.fn();
const ref = React.createRef();
let container;
act(() => {
rerender(<Image onLoad={onLoadReplacementStub} source={'https://test.com/img.jpg'} />);
({ container } = render(
<Image onError={onError} ref={ref} source="" />
));
});
expect(onLoadStub.mock.calls.length).toBe(1);
expect(onLoadReplacementStub.mock.calls.length).toBe(0);
const image = createEventTarget(ref.current);
act(() => {
image.error();
});
expect(onError).toBeCalledTimes(1);
expect(container.firstChild).toMatchSnapshot();
});
});
test('is called after image is loaded from network', () => {
jest.useFakeTimers();
const onLoadStartStub = jest.fn();
const onLoadStub = jest.fn();
const onLoadEndStub = jest.fn();
render(
<Image
onLoad={onLoadStub}
onLoadEnd={onLoadEndStub}
onLoadStart={onLoadStartStub}
source="https://test.com/img.jpg"
/>
describe('prop "onLoad"', () => {
test('is called once when image loads', () => {
const onLoad = jest.fn();
const onLoadEnd = jest.fn();
const onLoadStart = jest.fn();
const ref = React.createRef();
let container;
act(() => {
({ container } = render(
<Image
onLoad={onLoad}
onLoadEnd={onLoadEnd}
onLoadStart={onLoadStart}
ref={ref}
source="https://google.com/favicon.ico"
/>
));
});
const image = createEventTarget(ref.current);
act(() => {
image.load({
target: {
width: 100,
height: 100,
naturalWidth: 200,
naturalHeight: 200,
currentSrc: 'https://google.com/favicon.ico',
src: 'https://google.com/favicon.ico'
}
});
});
expect(onLoad).toBeCalledTimes(1);
expect(onLoadEnd).toBeCalledTimes(1);
expect(onLoadStart).toBeCalledTimes(1);
expect(onLoad).toBeCalledWith(
expect.objectContaining({
nativeEvent: expect.objectContaining({
source: expect.objectContaining({
width: expect.any(Number),
height: expect.any(Number),
url: 'https://google.com/favicon.ico'
})
})
})
);
jest.runOnlyPendingTimers();
expect(onLoadStub).toBeCalled();
});
test('is called after image is loaded from cache', () => {
jest.useFakeTimers();
const onLoadStartStub = jest.fn();
const onLoadStub = jest.fn();
const onLoadEndStub = jest.fn();
const uri = 'https://test.com/img.jpg';
ImageUriCache.add(uri);
render(
<Image
onLoad={onLoadStub}
onLoadEnd={onLoadEndStub}
onLoadStart={onLoadStartStub}
source={uri}
/>
);
jest.runOnlyPendingTimers();
expect(onLoadStub).toBeCalled();
ImageUriCache.remove(uri);
expect(container.firstChild).toMatchSnapshot();
});
test('is called on update if "uri" is different', () => {
const onLoadStartStub = jest.fn();
const onLoadStub = jest.fn();
const onLoadEndStub = jest.fn();
const { rerender } = render(
<Image
onLoad={onLoadStub}
onLoadEnd={onLoadEndStub}
onLoadStart={onLoadStartStub}
source={'https://test.com/img.jpg'}
/>
);
const ref = React.createRef();
act(() => {
rerender(
<Image
onLoad={onLoadStub}
onLoadEnd={onLoadEndStub}
onLoadStart={onLoadStartStub}
source={'https://blah.com/img.png'}
/>
);
});
expect(onLoadStub.mock.calls.length).toBe(2);
expect(onLoadEndStub.mock.calls.length).toBe(2);
});
test('is not called on update if "uri" is the same', () => {
const onLoadStartStub = jest.fn();
const onLoadStub = jest.fn();
const onLoadEndStub = jest.fn();
const { rerender } = render(
<Image
onLoad={onLoadStub}
onLoadEnd={onLoadEndStub}
onLoadStart={onLoadStartStub}
source={'https://test.com/img.jpg'}
/>
);
act(() => {
rerender(
render(
<Image
onLoad={onLoadStub}
onLoadEnd={onLoadEndStub}
onLoadStart={onLoadStartStub}
ref={ref}
source={'https://test.com/img.jpg'}
/>
);
});
const image = createEventTarget(ref.current);
act(() => {
image.load({
target: {
width: 100,
height: 100,
naturalWidth: 200,
naturalHeight: 200,
currentSrc: 'https://test.com/img.jpg',
src: 'https://test.com/img.jpg'
}
});
});
expect(onLoadStub.mock.calls.length).toBe(1);
expect(onLoadEndStub.mock.calls.length).toBe(1);
});
expect(onLoadStartStub.mock.calls.length).toBe(1);
test('is not called on update if "uri" is the same and given as an object', () => {
const onLoadStartStub = jest.fn();
const onLoadStub = jest.fn();
const onLoadEndStub = jest.fn();
const { rerender } = render(
<Image
onLoad={onLoadStub}
onLoadEnd={onLoadEndStub}
onLoadStart={onLoadStartStub}
source={{ uri: 'https://test.com/img.jpg', width: 1, height: 1 }}
/>
);
act(() => {
rerender(
render(
<Image
onLoad={onLoadStub}
onLoadEnd={onLoadEndStub}
onLoadStart={onLoadStartStub}
source={{ uri: 'https://test.com/img.jpg', width: 1, height: 1 }}
ref={ref}
source={'https://blah.com/img.png'}
/>
);
});
expect(onLoadStub.mock.calls.length).toBe(1);
expect(onLoadEndStub.mock.calls.length).toBe(1);
act(() => {
image.load({
target: {
width: 100,
height: 100,
naturalWidth: 200,
naturalHeight: 200,
currentSrc: 'https://blah.com/img.png',
src: 'https://blah.com/img.png'
}
});
});
expect(onLoadStub.mock.calls.length).toBe(2);
expect(onLoadEndStub.mock.calls.length).toBe(2);
expect(onLoadStartStub.mock.calls.length).toBe(2);
});
test('is not called when placeholder src is used after error', () => {
const onError = jest.fn();
const onLoad = jest.fn();
const ref = React.createRef();
act(() => {
render(
<Image
onError={onError}
onLoad={onLoad}
ref={ref}
source="https://google.com"
/>
);
});
const image = createEventTarget(ref.current);
act(() => {
// Results in placeholder being "loaded"
image.error();
});
expect(onError).toBeCalledTimes(1);
act(() => {
// Emulate the native "load" event for the placeholder
image.load();
});
expect(onLoad).toBeCalledTimes(0);
});
});
@@ -257,9 +308,9 @@ describe('components/Image', () => {
test('is set immediately if the image was preloaded', () => {
const uri = 'https://yahoo.com/favicon.ico';
ImageLoader.load = jest.fn().mockImplementationOnce((_, onLoad, onError) => {
onLoad();
});
//ImageLoader.load = jest.fn().mockImplementationOnce((_, onLoad, onError) => {
// onLoad();
//});
return Image.prefetch(uri).then(() => {
const source = { uri };
const { container } = render(<Image source={source} />, { disableLifecycleMethods: true });
@@ -298,25 +349,21 @@ describe('components/Image', () => {
test('is correctly updated only when loaded if defaultSource provided', () => {
const defaultUri = 'https://testing.com/preview.jpg';
const uri = 'https://testing.com/fullSize.jpg';
let loadCallback;
ImageLoader.load = jest.fn().mockImplementationOnce((_, onLoad, onError) => {
loadCallback = onLoad;
});
const { container } = render(<Image defaultSource={{ uri: defaultUri }} source={{ uri }} />);
expect(container.firstChild).toMatchSnapshot();
act(() => {
loadCallback();
// loadCallback();
});
expect(container.firstChild).toMatchSnapshot();
});
test('it correctly selects the source scale', () => {
AssetRegistry.getAssetByID = jest.fn(() => ({
AssetRegistry.getAssetByID = () => ({
httpServerLocation: 'static',
name: 'img',
scales: [1, 2, 3],
type: 'png'
}));
});
PixelRatio.get = jest.fn(() => 1.0);
let { container } = render(<Image source={1} />);

View File

@@ -8,38 +8,58 @@
* @flow
*/
import type { ImageProps } from './types';
import type { ImageProps, ImageStatics } from './types';
import type { PlatformMethods } from '../../types';
import * as React from 'react';
import createElement from '../createElement';
import css from '../StyleSheet/css';
import { getAssetByID } from '../../modules/AssetRegistry';
import * as forwardedProps from '../../modules/forwardedProps';
import pick from '../../modules/pick';
import processColor from '../processColor';
import resolveShadowValue from '../StyleSheet/resolveShadowValue';
import useElementLayout from '../../modules/useElementLayout';
import useMergeRefs from '../../modules/useMergeRefs';
import usePlatformMethods from '../../modules/usePlatformMethods';
import useResponderEvents from '../../modules/useResponderEvents';
import ImageLoader from '../../modules/ImageLoader';
import PixelRatio from '../PixelRatio';
import StyleSheet from '../StyleSheet';
import TextAncestorContext from '../Text/TextAncestorContext';
import View from '../View';
import processColor from '../processColor';
export type { ImageProps };
import { getAssetByID, getAssetUriByID } from '../../modules/AssetRegistry';
const ERRORED = 'ERRORED';
const LOADED = 'LOADED';
const LOADING = 'LOADING';
const IDLE = 'IDLE';
const emptyObject = {};
const forwardPropsList = {
...forwardedProps.defaultProps,
...forwardedProps.accessibilityProps,
...forwardedProps.clickProps,
...forwardedProps.focusProps,
...forwardedProps.keyboardProps,
...forwardedProps.mouseProps,
...forwardedProps.touchProps,
...forwardedProps.styleProps,
crossOrigin: true,
decoding: true,
draggable: true,
loading: true,
referrerPolicy: true
};
const pickProps = (props) => pick(props, forwardPropsList);
const svgDataUriPattern = /^(data:image\/svg\+xml;utf8,)(.*)/;
function getFlatStyle(style, blurRadius) {
const flatStyle = { ...StyleSheet.flatten(style) };
function getFlatStyle(style, blurRadius, resizeModeProp) {
const initialStyle = StyleSheet.flatten(style) || emptyObject;
const objectFitStyle = resizeModeStyles[initialStyle.resizeMode || resizeModeProp || 'cover'];
const flatStyle = { ...initialStyle, ...objectFitStyle };
const { filter, resizeMode, shadowOffset, tintColor } = flatStyle;
// Add CSS filters
// React Native exposes these features as props and proprietary styles
const filters = [];
let _filter = null;
if (filter) {
filters.push(filter);
}
@@ -74,10 +94,6 @@ function getFlatStyle(style, blurRadius) {
}
}
if (filters.length > 0) {
_filter = filters.join(' ');
}
// These styles are converted to CSS filters applied to the
// element displaying the background image.
delete flatStyle.blurRadius;
@@ -90,40 +106,32 @@ function getFlatStyle(style, blurRadius) {
delete flatStyle.overlayColor;
delete flatStyle.resizeMode;
return [flatStyle, resizeMode, _filter];
if (filters.length > 0) {
flatStyle.filter = filters.join(' ');
}
return flatStyle;
}
function resolveAssetDimensions(source) {
if (typeof source === 'number') {
const { height, width } = getAssetByID(source);
return { height, width };
} else if (source != null && !Array.isArray(source) && typeof source === 'object') {
const { height, width } = source;
return { height, width };
function getImageData(image: HTMLImageElement) {
const { width, height, currentSrc } = image;
return {
source: { height, width, url: currentSrc },
target: image
}
}
function resolveAssetUri(source): ?string {
let uri = null;
if (typeof source === 'number') {
// get the URI from the packager
const asset = getAssetByID(source);
let scale = asset.scales[0];
if (asset.scales.length > 1) {
const preferredScale = PixelRatio.get();
// Get the scale which is closest to the preferred scale
scale = asset.scales.reduce((prev, curr) =>
Math.abs(curr - preferredScale) < Math.abs(prev - preferredScale) ? curr : prev
);
}
const scaleSuffix = scale !== 1 ? `@${scale}x` : '';
uri = asset ? `${asset.httpServerLocation}/${asset.name}${scaleSuffix}.${asset.type}` : '';
uri = getAssetUriByID(source);
} else if (typeof source === 'string') {
uri = source;
} else if (Array.isArray(source)) {
uri = source[0].uri;
} else if (source && typeof source.uri === 'string') {
uri = source.uri;
}
if (uri) {
const match = uri.match(svgDataUriPattern);
// inline SVG markup may contain characters (e.g., #, ") that need to be escaped
@@ -133,36 +141,42 @@ function resolveAssetUri(source): ?string {
return `${prefix}${encodedSvg}`;
}
}
return uri;
}
interface ImageStatics {
getSize: (
uri: string,
success: (width: number, height: number) => void,
failure: () => void
) => void;
prefetch: (uri: string) => Promise<void>;
queryCache: (uris: Array<string>) => Promise<{| [uri: string]: 'disk/memory' |}>;
}
const ERROR_PLACEHOLDER =
'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==';
const Image: React.AbstractComponent<ImageProps, React.ElementRef<typeof View>> = React.forwardRef(
(props, ref) => {
const Image: React.AbstractComponent<ImageProps, HTMLImageElement & PlatformMethods> = React.forwardRef(
(props, forwardedRef) => {
const {
accessibilityLabel,
blurRadius,
defaultSource,
draggable,
onError,
onLayout,
onLoad,
onLoadEnd,
onLoadStart,
onMoveShouldSetResponder,
onMoveShouldSetResponderCapture,
onResponderEnd,
onResponderGrant,
onResponderMove,
onResponderReject,
onResponderRelease,
onResponderStart,
onResponderTerminate,
onResponderTerminationRequest,
onScrollShouldSetResponder,
onScrollShouldSetResponderCapture,
onSelectionChangeShouldSetResponder,
onSelectionChangeShouldSetResponderCapture,
onStartShouldSetResponder,
onStartShouldSetResponderCapture,
pointerEvents,
resizeMode,
source,
style,
...rest
style
} = props;
if (process.env.NODE_ENV !== 'production') {
@@ -173,135 +187,171 @@ const Image: React.AbstractComponent<ImageProps, React.ElementRef<typeof View>>
}
}
const [state, updateState] = React.useState(() => {
const uri = resolveAssetUri(source);
if (uri != null) {
const isLoaded = ImageLoader.has(uri);
if (isLoaded) {
return LOADED;
}
}
return IDLE;
const supportedProps = pickProps(props);
const src = resolveAssetUri(source) || resolveAssetUri(defaultSource);
let srcSet;
if (Array.isArray(source)) {
srcSet = source.map(({ uri, scale }) => `${uri} ${scale || 1}x`);
}
const [managedSrc, setManagedSrc] = React.useState(src);
const flatStyle = getFlatStyle(style, blurRadius, resizeMode);
const hostRef = React.useRef(null);
const hasTextAncestor = React.useContext(TextAncestorContext);
useElementLayout(hostRef, onLayout);
useResponderEvents(hostRef, {
onMoveShouldSetResponder,
onMoveShouldSetResponderCapture,
onResponderEnd,
onResponderGrant,
onResponderMove,
onResponderReject,
onResponderRelease,
onResponderStart,
onResponderTerminate,
onResponderTerminationRequest,
onScrollShouldSetResponder,
onScrollShouldSetResponderCapture,
onSelectionChangeShouldSetResponder,
onSelectionChangeShouldSetResponderCapture,
onStartShouldSetResponder,
onStartShouldSetResponderCapture
});
const [layout, updateLayout] = React.useState({});
const hasTextAncestor = React.useContext(TextAncestorContext);
const hiddenImageRef = React.useRef(null);
const requestRef = React.useRef(null);
const shouldDisplaySource = state === LOADED || (state === LOADING && defaultSource == null);
const [flatStyle, _resizeMode, filter] = getFlatStyle(style, blurRadius);
const resizeMode = props.resizeMode || _resizeMode || 'cover';
const selectedSource = shouldDisplaySource ? source : defaultSource;
const displayImageUri = resolveAssetUri(selectedSource);
const imageSizeStyle = resolveAssetDimensions(selectedSource);
const backgroundImage = displayImageUri ? `url("${displayImageUri}")` : null;
const backgroundSize = getBackgroundSize();
// Accessibility image allows users to trigger the browser's image context menu
const hiddenImage = displayImageUri
? createElement('img', {
alt: accessibilityLabel || '',
classList: [classes.accessibilityImage],
draggable: draggable || false,
ref: hiddenImageRef,
src: displayImageUri
})
: null;
function getBackgroundSize(): ?string {
if (hiddenImageRef.current != null && (resizeMode === 'center' || resizeMode === 'repeat')) {
const { naturalHeight, naturalWidth } = hiddenImageRef.current;
const { height, width } = layout;
if (naturalHeight && naturalWidth && height && width) {
const scaleFactor = Math.min(1, width / naturalWidth, height / naturalHeight);
const x = Math.ceil(scaleFactor * naturalWidth);
const y = Math.ceil(scaleFactor * naturalHeight);
return `${x}px ${y}px`;
const internalImageRef = React.useCallback((target) => {
const errorListener = function (e) {
// If the image fails to load, browsers will display a "broken" icon.
// To avoid this we replace the image with a transparent gif.
setManagedSrc(ERROR_PLACEHOLDER);
if (onError != null) {
onError({
nativeEvent: {
error: `Failed to load resource ${e.target.src} (404)`
}
});
}
}
}
function handleLayout(e) {
if (resizeMode === 'center' || resizeMode === 'repeat' || onLayout) {
const { layout } = e.nativeEvent;
onLayout && onLayout(e);
updateLayout(layout);
}
}
// Image loading
const uri = resolveAssetUri(source);
React.useEffect(() => {
abortPendingRequest();
if (uri != null) {
updateState(LOADING);
if (onLoadStart) {
onLoadStart();
if (onLoadEnd != null) {
onLoadEnd({ nativeEvent: { target }});
}
};
requestRef.current = ImageLoader.load(
uri,
function load(e) {
updateState(LOADED);
if (onLoad) {
onLoad(e);
}
if (onLoadEnd) {
onLoadEnd();
}
},
function error() {
updateState(ERRORED);
if (onError) {
onError({
nativeEvent: {
error: `Failed to load resource ${uri} (404)`
}
});
}
if (onLoadEnd) {
onLoadEnd();
}
const loadListener = function (e) {
const { target: image } = e;
if (image.src === ERROR_PLACEHOLDER) {
// Prevent the placeholder from triggering a 'load' event that event
// listeners would otherwise receive.
e.stopImmediatePropagation();
} else {
if (onLoad != null) {
onLoad({
nativeEvent: getImageData(image)
});
}
if (onLoadEnd != null) {
onLoadEnd({ nativeEvent: { target }});
}
);
}
function abortPendingRequest() {
if (requestRef.current != null) {
ImageLoader.abort(requestRef.current);
requestRef.current = null;
}
};
if (target !== null) {
// If the image is loaded before JS loads (e.g., SSR), then we manually
// call onLoad
// console.log(target.complete)
if (onLoad != null && target.complete) {
onLoad({
nativeEvent: getImageData(target)
});
return;
}
hostRef.current = target;
if (onLoadStart != null) {
onLoadStart({ nativeEvent: { target }});
}
target.addEventListener('error', errorListener);
target.addEventListener('load', loadListener);
} else if (hostRef.current != null) {
const node = hostRef.current;
node.removeEventListener('error', errorListener);
node.removeEventListener('load', loadListener);
hostRef.current = null;
}
},
[onError, onLoad, onLoadEnd]
);
return abortPendingRequest;
}, [uri, requestRef, updateState, onError, onLoad, onLoadEnd, onLoadStart]);
const platformMethodsRef = usePlatformMethods(supportedProps);
return (
<View
{...rest}
accessibilityLabel={accessibilityLabel}
onLayout={handleLayout}
pointerEvents={pointerEvents}
ref={ref}
style={[styles.root, hasTextAncestor && styles.inline, imageSizeStyle, flatStyle]}
>
<View
style={[
styles.image,
resizeModeStyles[resizeMode],
{ backgroundImage, filter },
backgroundSize != null && { backgroundSize }
]}
/>
{hiddenImage}
</View>
const ref = useMergeRefs(
hostRef,
internalImageRef,
platformMethodsRef,
forwardedRef
);
supportedProps.alt = props.alternativeText;
supportedProps.classList = hasTextAncestor ? inlineClassList : defaultClassList;
supportedProps.decoding = props.decoding || 'async';
supportedProps.draggable = props.draggable || false;
supportedProps.loading = props.loading || 'lazy';
supportedProps.ref = ref;
supportedProps.src = managedSrc;
supportedProps.srcSet =
srcSet != null && managedSrc !== ERROR_PLACEHOLDER
? srcSet.join(',')
: null;
supportedProps.style = flatStyle;
return createElement('img', supportedProps);
}
);
Image.displayName = 'Image';
const classes = css.create({
image: {
backgroundColor: 'transparent',
border: '0 solid black',
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'column',
flexShrink: 0,
margin: 0,
minHeight: 0,
minWidth: 0,
objectFit: 'cover',
padding: 0,
position: 'relative',
zIndex: 0
},
inlineImage: {
display: 'inline-flex'
}
});
const defaultClassList = [classes.image];
const inlineClassList = [classes.image, classes.inlineImage];
const resizeModeStyles = {
center: {
objectFit: 'scale-down'
},
contain: {
objectFit: 'contain'
},
cover: {
objectFit: 'cover'
},
none: {
objectFit: 'none'
},
stretch: {
objectFit: 'fill'
}
};
// $FlowIgnore: This is the correct type, but casting makes it unhappy since the variables aren't defined yet
const ImageWithStatics = (Image: React.AbstractComponent<
ImageProps,
@@ -310,7 +360,7 @@ const ImageWithStatics = (Image: React.AbstractComponent<
ImageStatics);
ImageWithStatics.getSize = function (uri, success, failure) {
ImageLoader.getSize(uri, success, failure);
return ImageLoader.getSize(uri, success, failure);
};
ImageWithStatics.prefetch = function (uri) {
@@ -321,59 +371,6 @@ ImageWithStatics.queryCache = function (uris) {
return ImageLoader.queryCache(uris);
};
const classes = css.create({
accessibilityImage: {
...StyleSheet.absoluteFillObject,
height: '100%',
opacity: 0,
width: '100%',
zIndex: -1
}
});
const styles = StyleSheet.create({
root: {
flexBasis: 'auto',
overflow: 'hidden',
zIndex: 0
},
inline: {
display: 'inline-flex'
},
image: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'transparent',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
height: '100%',
width: '100%',
zIndex: -1
}
});
const resizeModeStyles = StyleSheet.create({
center: {
backgroundSize: 'auto'
},
contain: {
backgroundSize: 'contain'
},
cover: {
backgroundSize: 'cover'
},
none: {
backgroundPosition: '0 0',
backgroundSize: 'auto'
},
repeat: {
backgroundPosition: '0 0',
backgroundRepeat: 'repeat',
backgroundSize: 'auto'
},
stretch: {
backgroundSize: '100% 100%'
}
});
export type { ImageProps };
export default ImageWithStatics;

View File

@@ -98,16 +98,36 @@ export type ImageStyle = {
export type ImageProps = {
...ViewProps,
alternativeText?: ?string,
blurRadius?: number,
crossOrigin?: 'anonymous' | 'use-credentials',
decoding?: 'auto' | 'async' | 'sync',
defaultSource?: Source,
draggable?: boolean,
loading?: 'eager' | 'lazy',
onError?: (e: any) => void,
onLayout?: (e: any) => void,
onLoad?: (e: any) => void,
onLoadEnd?: (e: any) => void,
onLoadStart?: (e: any) => void,
onProgress?: (e: any) => void,
referrerPolicy?:
| 'no-referrer'
| 'no-referrer-when-downgrade'
| 'origin'
| 'origin-when-cross-origin'
| 'unsafe-url',
resizeMode?: ResizeMode,
source?: Source,
style?: GenericStyleProp<ImageStyle>
};
export interface ImageStatics {
getSize: (
uri: string,
success: (width: number, height: number) => void,
failure: () => void
) => void;
prefetch: (uri: string) => Promise<void>;
queryCache: (uris: Array<string>) => Promise<{| [uri: string]: 'disk/memory' |}>;
}

View File

@@ -7,6 +7,8 @@
* @flow
*/
import PixelRatio from '../../exports/PixelRatio';
export type PackagerAsset = {
__packager_asset: boolean,
fileSystemLocation: string,
@@ -30,3 +32,18 @@ export function registerAsset(asset: PackagerAsset): number {
export function getAssetByID(assetId: number): PackagerAsset {
return assets[assetId - 1];
}
export function getAssetUriByID(assetId: number): string {
// get the URI from the packager
const asset = getAssetByID(assetId);
let scale = asset.scales[0];
if (asset.scales.length > 1) {
const preferredScale = PixelRatio.get();
// Get the scale which is closest to the preferred scale
scale = asset.scales.reduce((prev, curr) =>
Math.abs(curr - preferredScale) < Math.abs(prev - preferredScale) ? curr : prev
);
}
const scaleSuffix = scale !== 1 ? `@${scale}x` : '';
return asset ? `${asset.httpServerLocation}/${asset.name}${scaleSuffix}.${asset.type}` : '';
}