mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
31
Commits
@@ -118,7 +118,6 @@ async function collectResults(discordWebHook) {
|
||||
} else {
|
||||
console.log('Discord webhook not set');
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Initialize Firebase client
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
"lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/parser": "^7.25.3",
|
||||
"glob": "^7.1.1",
|
||||
"hermes-parser": "0.30.0",
|
||||
"invariant": "^2.2.4",
|
||||
|
||||
@@ -618,6 +618,9 @@ function InternalTextInput(props: TextInputProps): React.Node {
|
||||
// so omitting onBlur and onFocus pressability handlers here.
|
||||
const {onBlur, onFocus, ...eventHandlers} = usePressability(config);
|
||||
|
||||
const _accessibilityLabel =
|
||||
props?.['aria-label'] ?? props?.accessibilityLabel;
|
||||
|
||||
let _accessibilityState;
|
||||
if (
|
||||
accessibilityState != null ||
|
||||
@@ -681,6 +684,7 @@ function InternalTextInput(props: TextInputProps): React.Node {
|
||||
{...otherProps}
|
||||
{...eventHandlers}
|
||||
acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityState={_accessibilityState}
|
||||
accessible={accessible}
|
||||
submitBehavior={submitBehavior}
|
||||
@@ -744,8 +748,9 @@ function InternalTextInput(props: TextInputProps): React.Node {
|
||||
{...otherProps}
|
||||
{...colorProps}
|
||||
{...eventHandlers}
|
||||
accessibilityState={_accessibilityState}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityLabelledBy={_accessibilityLabelledBy}
|
||||
accessibilityState={_accessibilityState}
|
||||
accessible={accessible}
|
||||
acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes}
|
||||
autoCapitalize={autoCapitalize}
|
||||
|
||||
+1
@@ -432,6 +432,7 @@ jest.unmock('../TextInput');
|
||||
|
||||
expect(instance.toJSON()).toMatchInlineSnapshot(`
|
||||
<RCTSinglelineTextInputView
|
||||
accessibilityLabel="label"
|
||||
accessibilityState={
|
||||
Object {
|
||||
"busy": true,
|
||||
|
||||
@@ -169,6 +169,460 @@ describe('<Image>', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('defaultSource', () => {
|
||||
it('can provide a default image to display', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
defaultSource={require('./img/img1.png')}
|
||||
source={LOGO_SOURCE}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(
|
||||
root.getRenderedOutput({props: ['defaultSource']}).toJSX(),
|
||||
).toEqual(
|
||||
<rn-image
|
||||
defaultSource-type="remote"
|
||||
defaultSource-uri="file://drawable-mdpi/packages_reactnative_libraries_image___tests___img_img1.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('height', () => {
|
||||
it('provides height for image', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Image height={100} source={LOGO_SOURCE} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual(
|
||||
<rn-image height="100.000000" />,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('width', () => {
|
||||
it('provides width for image', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Image width={100} source={LOGO_SOURCE} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['width']}).toJSX()).toEqual(
|
||||
<rn-image width="100.000000" />,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loading progress', () => {
|
||||
(
|
||||
[
|
||||
['onError', 'fails to load'],
|
||||
['onLoadStart', 'start loading'],
|
||||
['onProgress', 'is loading'],
|
||||
['onLoad', 'loads successfully'],
|
||||
['onLoadEnd', 'ends loading'],
|
||||
] as const
|
||||
).forEach(([onProp, event]) => {
|
||||
it(`${onProp} is called when image ${event}`, () => {
|
||||
const onPropCallback = jest.fn();
|
||||
const ref = createRef<HostInstance>();
|
||||
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
ref={ref}
|
||||
source={LOGO_SOURCE}
|
||||
onError={() => {
|
||||
onProp === 'onError' && onPropCallback();
|
||||
}}
|
||||
onLoad={() => {
|
||||
onProp === 'onLoad' && onPropCallback();
|
||||
}}
|
||||
onLoadStart={() => {
|
||||
onProp === 'onLoadStart' && onPropCallback();
|
||||
}}
|
||||
onLoadEnd={() => {
|
||||
onProp === 'onLoadEnd' && onPropCallback();
|
||||
}}
|
||||
onProgress={() => {
|
||||
onProp === 'onProgress' && onPropCallback();
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(onPropCallback).toHaveBeenCalledTimes(0);
|
||||
|
||||
const image = ensureInstance(ref.current, ReactNativeElement);
|
||||
Fantom.dispatchNativeEvent(image, onProp, {});
|
||||
|
||||
expect(onPropCallback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('referrerPolicy', () => {
|
||||
(
|
||||
[
|
||||
'no-referrer',
|
||||
'no-referrer-when-downgrade',
|
||||
'origin',
|
||||
'origin-when-cross-origin',
|
||||
'same-origin',
|
||||
'strict-origin',
|
||||
'strict-origin-when-cross-origin',
|
||||
'unsafe-url',
|
||||
] as const
|
||||
).forEach(referrerPolicy => {
|
||||
it(`${referrerPolicy} sets correct "Referrer-Policy" header`, () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image referrerPolicy={referrerPolicy} src={LOGO_SOURCE.uri} />,
|
||||
);
|
||||
});
|
||||
|
||||
expect(
|
||||
root.getRenderedOutput({props: ['source-header']}).toJSX(),
|
||||
).toEqual(
|
||||
<rn-image source-header-Referrer-Policy={referrerPolicy} />,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('resizeMode', () => {
|
||||
it('is set to "cover" by default', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Image source={LOGO_SOURCE} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['resizeMode']}).toJSX()).toEqual(
|
||||
<rn-image />,
|
||||
);
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Image resizeMode="cover" source={LOGO_SOURCE} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['resizeMode']}).toJSX()).toEqual(
|
||||
<rn-image />,
|
||||
);
|
||||
});
|
||||
|
||||
(['stretch', 'contain', 'repeat', 'center'] as const).forEach(
|
||||
resizeMode => {
|
||||
it(`can be set to "${resizeMode}"`, () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image resizeMode={resizeMode} source={LOGO_SOURCE} />,
|
||||
);
|
||||
});
|
||||
|
||||
expect(
|
||||
root.getRenderedOutput({props: ['resizeMode']}).toJSX(),
|
||||
).toEqual(<rn-image resizeMode={resizeMode} />);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('source', () => {
|
||||
it('can be set to a local image', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Image source={require('./img/img1.png')} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-scale="1"
|
||||
source-size="{1, 1}"
|
||||
source-type="local"
|
||||
source-uri="file://drawable-mdpi/packages_reactnative_libraries_image___tests___img_img1.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it('can be set to a remote image', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
source={{
|
||||
uri: 'https://reactnative.dev/img/tiny_logo.png',
|
||||
width: 100,
|
||||
height: 100,
|
||||
scale: 2,
|
||||
cache: 'only-if-cached',
|
||||
method: 'POST',
|
||||
body: 'name=React+Native',
|
||||
headers: {
|
||||
Authorization: 'Basic RandomString',
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-body="name=React+Native"
|
||||
source-cache="only-if-cached"
|
||||
source-header-Authorization="Basic RandomString"
|
||||
source-method="POST"
|
||||
source-scale="2"
|
||||
source-size="{100, 100}"
|
||||
source-type="remote"
|
||||
source-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it('can be set to a list of remote images', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
source={[
|
||||
{
|
||||
uri: 'https://reactnative.dev/img/tiny_logo.png',
|
||||
scale: 1,
|
||||
headers: {
|
||||
Authorization: 'Basic RandomString',
|
||||
},
|
||||
},
|
||||
{
|
||||
uri: 'https://reactnative.dev/img/medium_logo.png',
|
||||
scale: 2,
|
||||
cache: 'only-if-cached',
|
||||
},
|
||||
{
|
||||
uri: 'https://reactnative.dev/img/large_logo.png',
|
||||
scale: 3,
|
||||
method: 'POST',
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-1x-header-Authorization="Basic RandomString"
|
||||
source-1x-scale="1"
|
||||
source-1x-type="remote"
|
||||
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
source-2x-cache="only-if-cached"
|
||||
source-2x-scale="2"
|
||||
source-2x-type="remote"
|
||||
source-2x-uri="https://reactnative.dev/img/medium_logo.png"
|
||||
source-3x-method="POST"
|
||||
source-3x-type="remote"
|
||||
source-3x-uri="https://reactnative.dev/img/large_logo.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('src', () => {
|
||||
it('can be set to a remote image', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image src="https://reactnative.dev/img/tiny_logo.png" />,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-scale="1"
|
||||
source-type="remote"
|
||||
source-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it('takes precedence over `source` prop', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
src="https://reactnative.dev/img/tiny_logo.png"
|
||||
source={{uri: 'https://reactnative.dev/img/medium_logo.png'}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-scale="1"
|
||||
source-type="remote"
|
||||
source-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('srcSet', () => {
|
||||
it('can be set to a list of remote images', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
srcSet={
|
||||
'https://reactnative.dev/img/tiny_logo.png 1x, https://reactnative.dev/img/header_logo.svg 2x'
|
||||
}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-1x-scale="1"
|
||||
source-1x-type="remote"
|
||||
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
source-2x-scale="2"
|
||||
source-2x-type="remote"
|
||||
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it('defaults to `1x` descriptor', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
srcSet={
|
||||
'https://reactnative.dev/img/tiny_logo.png, https://reactnative.dev/img/header_logo.svg 2x'
|
||||
}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-1x-scale="1"
|
||||
source-1x-type="remote"
|
||||
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
source-2x-scale="2"
|
||||
source-2x-type="remote"
|
||||
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it('uses `src` for `1x` descriptor when provided', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
srcSet={
|
||||
'https://reactnative.dev/img/header_logo.svg 2x, https://reactnative.dev/img/large_logo.svg 3x'
|
||||
}
|
||||
src="https://reactnative.dev/img/tiny_logo.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-1x-scale="1"
|
||||
source-1x-type="remote"
|
||||
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
source-2x-scale="2"
|
||||
source-2x-type="remote"
|
||||
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
|
||||
source-3x-type="remote"
|
||||
source-3x-uri="https://reactnative.dev/img/large_logo.svg"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('style', () => {
|
||||
it('can be set', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Image
|
||||
style={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
resizeMode: 'contain',
|
||||
}}
|
||||
source={LOGO_SOURCE}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput().toJSX()).toEqual(
|
||||
<rn-image
|
||||
height="100.000000"
|
||||
overflow="hidden"
|
||||
resizeMode="contain"
|
||||
width="100.000000"
|
||||
source-scale="1"
|
||||
source-type="remote"
|
||||
source-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('testID', () => {
|
||||
it('can be set', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Image testID="test" source={LOGO_SOURCE} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['testID']}).toJSX()).toEqual(
|
||||
<rn-image testID="test" />,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tintColor', () => {
|
||||
it('can be set', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Image tintColor="red" source={LOGO_SOURCE} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['tintColor']}).toJSX()).toEqual(
|
||||
<rn-image tintColor="rgba(255, 0, 0, 1)" />,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ref', () => {
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
|
||||
|
||||
import type {HostInstance} from 'react-native';
|
||||
|
||||
import * as Fantom from '@react-native/fantom';
|
||||
import * as React from 'react';
|
||||
import {createRef} from 'react';
|
||||
import {ImageBackground} from 'react-native';
|
||||
import ensureInstance from 'react-native/src/private/__tests__/utilities/ensureInstance';
|
||||
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
|
||||
|
||||
describe('<ImageBackground>', () => {
|
||||
describe('props', () => {
|
||||
describe('ImageProps', () => {
|
||||
it('can have local source', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<ImageBackground source={require('./img/img1.png')} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-scale="1"
|
||||
source-size="{1, 1}"
|
||||
source-type="local"
|
||||
source-uri="file://drawable-mdpi/packages_reactnative_libraries_image___tests___img_img1.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it('can have remote source', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<ImageBackground
|
||||
source={{
|
||||
uri: 'https://reactnative.dev/img/tiny_logo.png',
|
||||
width: 100,
|
||||
height: 100,
|
||||
scale: 2,
|
||||
cache: 'only-if-cached',
|
||||
method: 'POST',
|
||||
body: 'name=React+Native',
|
||||
headers: {
|
||||
Authorization: 'Basic RandomString',
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-body="name=React+Native"
|
||||
source-cache="only-if-cached"
|
||||
source-header-Authorization="Basic RandomString"
|
||||
source-method="POST"
|
||||
source-scale="2"
|
||||
source-size="{100, 100}"
|
||||
source-type="remote"
|
||||
source-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
it('can have srcSet', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<ImageBackground srcSet="https://reactnative.dev/img/tiny_logo.png 1x, https://reactnative.dev/img/header_logo.svg 2x" />,
|
||||
);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
|
||||
<rn-image
|
||||
source-1x-scale="1"
|
||||
source-1x-type="remote"
|
||||
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
|
||||
source-2x-scale="2"
|
||||
source-2x-type="remote"
|
||||
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
|
||||
/>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('style', () => {
|
||||
it('can be set', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<ImageBackground style={{width: 100, height: 100}} />);
|
||||
});
|
||||
|
||||
expect(
|
||||
root.getRenderedOutput({props: ['width|height']}).toJSX(),
|
||||
).toEqual(<rn-image width="100.000000" height="100.000000" />);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ref', () => {
|
||||
it('Allows to set a reference to the inner `Image` component', () => {
|
||||
const elementRef = createRef<HostInstance>();
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<ImageBackground imageRef={elementRef} />);
|
||||
});
|
||||
|
||||
const image = ensureInstance(elementRef.current, ReactNativeElement);
|
||||
expect(image.tagName).toBe('RN:Image');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7,9 +7,7 @@
|
||||
* @noformat
|
||||
* @nolint
|
||||
* @flow
|
||||
* @generated SignedSource<<16b364e89f43b8a47832b0dfb98af11e>>
|
||||
*
|
||||
* This file was sync'd from the facebook/react repository.
|
||||
* @generated SignedSource<<cf323fc5ca893bab5669c7d321660412>>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
* @noformat
|
||||
* @nolint
|
||||
* @flow strict-local
|
||||
* @generated SignedSource<<1dd9e9c3f20e37ae14e485fc6ee3d9e9>>
|
||||
*
|
||||
* This file was sync'd from the facebook/react repository.
|
||||
* @generated SignedSource<<908f5fb85384725318e261f40e49d9a6>>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
* @noformat
|
||||
* @nolint
|
||||
* @flow
|
||||
* @generated SignedSource<<e2c46705ed927302dbe9332dafba459d>>
|
||||
*
|
||||
* This file was sync'd from the facebook/react repository.
|
||||
* @generated SignedSource<<8f46fdc9267fcc4fdc9e76842fe24066>>
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
+1
-3
@@ -7,9 +7,7 @@
|
||||
* @noformat
|
||||
* @nolint
|
||||
* @flow strict-local
|
||||
* @generated SignedSource<<e8dce0e82b831c91465d04b49fb48ab2>>
|
||||
*
|
||||
* This file was sync'd from the facebook/react repository.
|
||||
* @generated SignedSource<<83073425aa3f71ced2c8c51f25a25938>>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
+1
-3
@@ -7,9 +7,7 @@
|
||||
* @noformat
|
||||
* @nolint
|
||||
* @flow strict-local
|
||||
* @generated SignedSource<<556d1487de0b9e4a09cbc67dd130a884>>
|
||||
*
|
||||
* This file was sync'd from the facebook/react repository.
|
||||
* @generated SignedSource<<52163887de05f1cff05388145cf85b3b>>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
+517
-243
@@ -14,6 +14,7 @@ import type {GestureResponderEvent} from '../Types/CoreEventTypes';
|
||||
import type {NativeTextProps} from './TextNativeComponent';
|
||||
import type {PressRetentionOffset, TextProps} from './TextProps';
|
||||
|
||||
import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
|
||||
import * as PressabilityDebug from '../Pressability/PressabilityDebug';
|
||||
import usePressability from '../Pressability/usePressability';
|
||||
import flattenStyle from '../StyleSheet/flattenStyle';
|
||||
@@ -35,156 +36,495 @@ type TextForwardRef = React.ElementRef<
|
||||
*
|
||||
* @see https://reactnative.dev/docs/text
|
||||
*/
|
||||
const TextImpl: component(
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...props: TextProps
|
||||
) = ({
|
||||
ref: forwardedRef,
|
||||
accessible,
|
||||
accessibilityLabel,
|
||||
accessibilityState,
|
||||
allowFontScaling,
|
||||
'aria-busy': ariaBusy,
|
||||
'aria-checked': ariaChecked,
|
||||
'aria-disabled': ariaDisabled,
|
||||
'aria-expanded': ariaExpanded,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-selected': ariaSelected,
|
||||
children,
|
||||
ellipsizeMode,
|
||||
disabled,
|
||||
id,
|
||||
nativeID,
|
||||
numberOfLines,
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
selectable,
|
||||
selectionColor,
|
||||
suppressHighlighting,
|
||||
style,
|
||||
...restProps
|
||||
}: {
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...TextProps,
|
||||
}) => {
|
||||
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
|
||||
|
||||
let _accessibilityState: ?TextProps['accessibilityState'] =
|
||||
accessibilityState;
|
||||
if (
|
||||
ariaBusy != null ||
|
||||
ariaChecked != null ||
|
||||
ariaDisabled != null ||
|
||||
ariaExpanded != null ||
|
||||
ariaSelected != null
|
||||
) {
|
||||
if (_accessibilityState != null) {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy ?? _accessibilityState.busy,
|
||||
checked: ariaChecked ?? _accessibilityState.checked,
|
||||
disabled: ariaDisabled ?? _accessibilityState.disabled,
|
||||
expanded: ariaExpanded ?? _accessibilityState.expanded,
|
||||
selected: ariaSelected ?? _accessibilityState.selected,
|
||||
};
|
||||
} else {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy,
|
||||
checked: ariaChecked,
|
||||
disabled: ariaDisabled,
|
||||
expanded: ariaExpanded,
|
||||
selected: ariaSelected,
|
||||
};
|
||||
let _TextImpl;
|
||||
if (ReactNativeFeatureFlags.reduceDefaultPropsInText()) {
|
||||
const TextImplNoDefaultProps: component(
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...props: TextProps
|
||||
) = ({
|
||||
ref: forwardedRef,
|
||||
accessible,
|
||||
accessibilityLabel,
|
||||
accessibilityState,
|
||||
allowFontScaling,
|
||||
'aria-busy': ariaBusy,
|
||||
'aria-checked': ariaChecked,
|
||||
'aria-disabled': ariaDisabled,
|
||||
'aria-expanded': ariaExpanded,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-selected': ariaSelected,
|
||||
children,
|
||||
ellipsizeMode,
|
||||
disabled,
|
||||
id,
|
||||
nativeID,
|
||||
numberOfLines,
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
selectable,
|
||||
selectionColor,
|
||||
suppressHighlighting,
|
||||
style,
|
||||
...restProps
|
||||
}: {
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...TextProps,
|
||||
}) => {
|
||||
const processedProps = restProps as {
|
||||
...NativeTextProps,
|
||||
};
|
||||
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
|
||||
let _accessibilityState: ?TextProps['accessibilityState'] =
|
||||
accessibilityState;
|
||||
if (
|
||||
ariaBusy != null ||
|
||||
ariaChecked != null ||
|
||||
ariaDisabled != null ||
|
||||
ariaExpanded != null ||
|
||||
ariaSelected != null
|
||||
) {
|
||||
if (_accessibilityState != null) {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy ?? _accessibilityState.busy,
|
||||
checked: ariaChecked ?? _accessibilityState.checked,
|
||||
disabled: ariaDisabled ?? _accessibilityState.disabled,
|
||||
expanded: ariaExpanded ?? _accessibilityState.expanded,
|
||||
selected: ariaSelected ?? _accessibilityState.selected,
|
||||
};
|
||||
} else {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy,
|
||||
checked: ariaChecked,
|
||||
disabled: ariaDisabled,
|
||||
expanded: ariaExpanded,
|
||||
selected: ariaSelected,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const _accessibilityStateDisabled = _accessibilityState?.disabled;
|
||||
const _disabled = disabled ?? _accessibilityStateDisabled;
|
||||
const _accessibilityStateDisabled = _accessibilityState?.disabled;
|
||||
const _disabled = disabled ?? _accessibilityStateDisabled;
|
||||
|
||||
const isPressable =
|
||||
(onPress != null ||
|
||||
onLongPress != null ||
|
||||
onStartShouldSetResponder != null) &&
|
||||
_disabled !== true;
|
||||
|
||||
// TODO: Move this processing to the view configuration.
|
||||
const _selectionColor =
|
||||
selectionColor != null ? processColor(selectionColor) : undefined;
|
||||
|
||||
let _style = style;
|
||||
if (__DEV__) {
|
||||
if (PressabilityDebug.isEnabled() && onPress != null) {
|
||||
_style = [style, {color: 'magenta'}];
|
||||
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
|
||||
// falsy states we need to update the accessibilityState object to use the disabled prop.
|
||||
if (
|
||||
_accessibilityState != null &&
|
||||
_disabled !== _accessibilityStateDisabled &&
|
||||
((_disabled != null && _disabled !== false) ||
|
||||
(_accessibilityStateDisabled != null &&
|
||||
_accessibilityStateDisabled !== false))
|
||||
) {
|
||||
_accessibilityState.disabled = _disabled;
|
||||
}
|
||||
}
|
||||
|
||||
let _numberOfLines = numberOfLines;
|
||||
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
|
||||
const _accessible = Platform.select({
|
||||
ios: accessible !== false,
|
||||
android:
|
||||
accessible == null
|
||||
? onPress != null || onLongPress != null
|
||||
: accessible,
|
||||
default: accessible,
|
||||
});
|
||||
|
||||
const isPressable =
|
||||
(onPress != null ||
|
||||
onLongPress != null ||
|
||||
onStartShouldSetResponder != null) &&
|
||||
_disabled !== true;
|
||||
|
||||
// TODO: Move this processing to the view configuration.
|
||||
const _selectionColor =
|
||||
selectionColor != null ? processColor(selectionColor) : undefined;
|
||||
|
||||
let _style = style;
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
`'numberOfLines' in <Text> must be a non-negative number, received: ${_numberOfLines}. The value will be set to 0.`,
|
||||
if (PressabilityDebug.isEnabled() && onPress != null) {
|
||||
_style = [style, {color: 'magenta'}];
|
||||
}
|
||||
}
|
||||
|
||||
let _numberOfLines = numberOfLines;
|
||||
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
`'numberOfLines' in <Text> must be a non-negative number, received: ${_numberOfLines}. The value will be set to 0.`,
|
||||
);
|
||||
}
|
||||
_numberOfLines = 0;
|
||||
}
|
||||
|
||||
let _selectable = selectable;
|
||||
|
||||
let processedStyle = flattenStyle<TextStyleProp>(_style);
|
||||
if (processedStyle != null) {
|
||||
let overrides: ?{...TextStyleInternal} = null;
|
||||
if (typeof processedStyle.fontWeight === 'number') {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.fontWeight =
|
||||
// $FlowFixMe[incompatible-cast]
|
||||
(String(processedStyle.fontWeight): TextStyleInternal['fontWeight']);
|
||||
}
|
||||
|
||||
if (processedStyle.userSelect != null) {
|
||||
_selectable = userSelectToSelectableMap[processedStyle.userSelect];
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.userSelect = undefined;
|
||||
}
|
||||
|
||||
if (processedStyle.verticalAlign != null) {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.textAlignVertical =
|
||||
verticalAlignToTextAlignVerticalMap[processedStyle.verticalAlign];
|
||||
overrides.verticalAlign = undefined;
|
||||
}
|
||||
|
||||
if (overrides != null) {
|
||||
// $FlowFixMe[incompatible-type]
|
||||
_style = [_style, overrides];
|
||||
}
|
||||
}
|
||||
|
||||
const _nativeID = id ?? nativeID;
|
||||
|
||||
if (_accessibilityLabel !== undefined) {
|
||||
processedProps.accessibilityLabel = _accessibilityLabel;
|
||||
}
|
||||
if (_accessibilityState !== undefined) {
|
||||
processedProps.accessibilityState = _accessibilityState;
|
||||
}
|
||||
if (_nativeID !== undefined) {
|
||||
processedProps.nativeID = _nativeID;
|
||||
}
|
||||
if (_numberOfLines !== undefined) {
|
||||
processedProps.numberOfLines = _numberOfLines;
|
||||
}
|
||||
if (_selectable !== undefined) {
|
||||
processedProps.selectable = _selectable;
|
||||
}
|
||||
if (_style !== undefined) {
|
||||
processedProps.style = _style;
|
||||
}
|
||||
if (_selectionColor !== undefined) {
|
||||
processedProps.selectionColor = _selectionColor;
|
||||
}
|
||||
|
||||
let textPressabilityProps: ?TextPressabilityProps;
|
||||
if (isPressable) {
|
||||
textPressabilityProps = {
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
suppressHighlighting,
|
||||
};
|
||||
}
|
||||
|
||||
const hasTextAncestor = useContext(TextAncestorContext);
|
||||
if (hasTextAncestor) {
|
||||
processedProps.disabled = disabled;
|
||||
processedProps.children = children;
|
||||
if (isPressable) {
|
||||
return (
|
||||
<NativePressableVirtualText
|
||||
ref={forwardedRef}
|
||||
textProps={processedProps}
|
||||
textPressabilityProps={textPressabilityProps ?? {}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <NativeVirtualText {...processedProps} ref={forwardedRef} />;
|
||||
}
|
||||
|
||||
let nativeText = null;
|
||||
|
||||
processedProps.accessible = _accessible;
|
||||
processedProps.allowFontScaling = allowFontScaling !== false;
|
||||
processedProps.disabled = _disabled;
|
||||
processedProps.ellipsizeMode = ellipsizeMode ?? 'tail';
|
||||
processedProps.children = children;
|
||||
|
||||
if (isPressable) {
|
||||
nativeText = (
|
||||
<NativePressableText
|
||||
ref={forwardedRef}
|
||||
textProps={processedProps}
|
||||
textPressabilityProps={textPressabilityProps ?? {}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
nativeText = <NativeText {...processedProps} ref={forwardedRef} />;
|
||||
}
|
||||
|
||||
if (children == null) {
|
||||
return nativeText;
|
||||
}
|
||||
|
||||
// If the children do not contain a JSX element it would not be possible to have a
|
||||
// nested `Text` component so we can skip adding the `TextAncestorContext` context wrapper
|
||||
// which has a performance overhead. Since we do this for performance reasons we need
|
||||
// to keep the check simple to avoid regressing overall perf. For this reason the
|
||||
// `children.length` constant is set to `3`, this should be a reasonable tradeoff
|
||||
// to capture the majority of `Text` uses but also not make this check too expensive.
|
||||
if (Array.isArray(children) && children.length <= 3) {
|
||||
let hasNonTextChild = false;
|
||||
for (let child of children) {
|
||||
if (child != null && typeof child === 'object') {
|
||||
hasNonTextChild = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasNonTextChild) {
|
||||
return nativeText;
|
||||
}
|
||||
} else if (typeof children !== 'object') {
|
||||
return nativeText;
|
||||
}
|
||||
|
||||
return <TextAncestorContext value={true}>{nativeText}</TextAncestorContext>;
|
||||
};
|
||||
_TextImpl = TextImplNoDefaultProps;
|
||||
} else {
|
||||
const TextImplLegacy: component(
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...props: TextProps
|
||||
) = ({
|
||||
ref: forwardedRef,
|
||||
accessible,
|
||||
accessibilityLabel,
|
||||
accessibilityState,
|
||||
allowFontScaling,
|
||||
'aria-busy': ariaBusy,
|
||||
'aria-checked': ariaChecked,
|
||||
'aria-disabled': ariaDisabled,
|
||||
'aria-expanded': ariaExpanded,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-selected': ariaSelected,
|
||||
children,
|
||||
ellipsizeMode,
|
||||
disabled,
|
||||
id,
|
||||
nativeID,
|
||||
numberOfLines,
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
selectable,
|
||||
selectionColor,
|
||||
suppressHighlighting,
|
||||
style,
|
||||
...restProps
|
||||
}: {
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...TextProps,
|
||||
}) => {
|
||||
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
|
||||
|
||||
let _accessibilityState: ?TextProps['accessibilityState'] =
|
||||
accessibilityState;
|
||||
if (
|
||||
ariaBusy != null ||
|
||||
ariaChecked != null ||
|
||||
ariaDisabled != null ||
|
||||
ariaExpanded != null ||
|
||||
ariaSelected != null
|
||||
) {
|
||||
if (_accessibilityState != null) {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy ?? _accessibilityState.busy,
|
||||
checked: ariaChecked ?? _accessibilityState.checked,
|
||||
disabled: ariaDisabled ?? _accessibilityState.disabled,
|
||||
expanded: ariaExpanded ?? _accessibilityState.expanded,
|
||||
selected: ariaSelected ?? _accessibilityState.selected,
|
||||
};
|
||||
} else {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy,
|
||||
checked: ariaChecked,
|
||||
disabled: ariaDisabled,
|
||||
expanded: ariaExpanded,
|
||||
selected: ariaSelected,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const _accessibilityStateDisabled = _accessibilityState?.disabled;
|
||||
const _disabled = disabled ?? _accessibilityStateDisabled;
|
||||
|
||||
const isPressable =
|
||||
(onPress != null ||
|
||||
onLongPress != null ||
|
||||
onStartShouldSetResponder != null) &&
|
||||
_disabled !== true;
|
||||
|
||||
// TODO: Move this processing to the view configuration.
|
||||
const _selectionColor =
|
||||
selectionColor != null ? processColor(selectionColor) : undefined;
|
||||
|
||||
let _style = style;
|
||||
if (__DEV__) {
|
||||
if (PressabilityDebug.isEnabled() && onPress != null) {
|
||||
_style = [style, {color: 'magenta'}];
|
||||
}
|
||||
}
|
||||
|
||||
let _numberOfLines = numberOfLines;
|
||||
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
`'numberOfLines' in <Text> must be a non-negative number, received: ${_numberOfLines}. The value will be set to 0.`,
|
||||
);
|
||||
}
|
||||
_numberOfLines = 0;
|
||||
}
|
||||
|
||||
let _selectable = selectable;
|
||||
|
||||
let processedStyle = flattenStyle<TextStyleProp>(_style);
|
||||
if (processedStyle != null) {
|
||||
let overrides: ?{...TextStyleInternal} = null;
|
||||
if (typeof processedStyle.fontWeight === 'number') {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.fontWeight =
|
||||
// $FlowFixMe[incompatible-cast]
|
||||
(processedStyle.fontWeight.toString(): TextStyleInternal['fontWeight']);
|
||||
}
|
||||
|
||||
if (processedStyle.userSelect != null) {
|
||||
_selectable = userSelectToSelectableMap[processedStyle.userSelect];
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.userSelect = undefined;
|
||||
}
|
||||
|
||||
if (processedStyle.verticalAlign != null) {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.textAlignVertical =
|
||||
verticalAlignToTextAlignVerticalMap[processedStyle.verticalAlign];
|
||||
overrides.verticalAlign = undefined;
|
||||
}
|
||||
|
||||
if (overrides != null) {
|
||||
// $FlowFixMe[incompatible-type]
|
||||
_style = [_style, overrides];
|
||||
}
|
||||
}
|
||||
|
||||
const _nativeID = id ?? nativeID;
|
||||
|
||||
const hasTextAncestor = useContext(TextAncestorContext);
|
||||
if (hasTextAncestor) {
|
||||
if (isPressable) {
|
||||
return (
|
||||
<NativePressableVirtualText
|
||||
ref={forwardedRef}
|
||||
textProps={{
|
||||
...restProps,
|
||||
accessibilityLabel: _accessibilityLabel,
|
||||
accessibilityState: _accessibilityState,
|
||||
nativeID: _nativeID,
|
||||
numberOfLines: _numberOfLines,
|
||||
selectable: _selectable,
|
||||
selectionColor: _selectionColor,
|
||||
style: _style,
|
||||
disabled: disabled,
|
||||
children,
|
||||
}}
|
||||
textPressabilityProps={{
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
suppressHighlighting,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NativeVirtualText
|
||||
{...restProps}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityState={_accessibilityState}
|
||||
nativeID={_nativeID}
|
||||
numberOfLines={_numberOfLines}
|
||||
ref={forwardedRef}
|
||||
selectable={_selectable}
|
||||
selectionColor={_selectionColor}
|
||||
style={_style}
|
||||
disabled={disabled}>
|
||||
{children}
|
||||
</NativeVirtualText>
|
||||
);
|
||||
}
|
||||
_numberOfLines = 0;
|
||||
}
|
||||
|
||||
let _selectable = selectable;
|
||||
|
||||
let processedStyle = flattenStyle<TextStyleProp>(_style);
|
||||
if (processedStyle != null) {
|
||||
let overrides: ?{...TextStyleInternal} = null;
|
||||
if (typeof processedStyle.fontWeight === 'number') {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.fontWeight =
|
||||
// $FlowFixMe[incompatible-cast]
|
||||
(processedStyle.fontWeight.toString(): TextStyleInternal['fontWeight']);
|
||||
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
|
||||
// falsy states we need to update the accessibilityState object to use the disabled prop.
|
||||
if (
|
||||
_disabled !== _accessibilityStateDisabled &&
|
||||
((_disabled != null && _disabled !== false) ||
|
||||
(_accessibilityStateDisabled != null &&
|
||||
_accessibilityStateDisabled !== false))
|
||||
) {
|
||||
_accessibilityState = {..._accessibilityState, disabled: _disabled};
|
||||
}
|
||||
|
||||
if (processedStyle.userSelect != null) {
|
||||
_selectable = userSelectToSelectableMap[processedStyle.userSelect];
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.userSelect = undefined;
|
||||
}
|
||||
const _accessible = Platform.select({
|
||||
ios: accessible !== false,
|
||||
android:
|
||||
accessible == null
|
||||
? onPress != null || onLongPress != null
|
||||
: accessible,
|
||||
default: accessible,
|
||||
});
|
||||
|
||||
if (processedStyle.verticalAlign != null) {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.textAlignVertical =
|
||||
verticalAlignToTextAlignVerticalMap[processedStyle.verticalAlign];
|
||||
overrides.verticalAlign = undefined;
|
||||
}
|
||||
|
||||
if (overrides != null) {
|
||||
// $FlowFixMe[incompatible-type]
|
||||
_style = [_style, overrides];
|
||||
}
|
||||
}
|
||||
|
||||
const _nativeID = id ?? nativeID;
|
||||
|
||||
const hasTextAncestor = useContext(TextAncestorContext);
|
||||
if (hasTextAncestor) {
|
||||
let nativeText = null;
|
||||
if (isPressable) {
|
||||
return (
|
||||
<NativePressableVirtualText
|
||||
nativeText = (
|
||||
<NativePressableText
|
||||
ref={forwardedRef}
|
||||
textProps={{
|
||||
...restProps,
|
||||
accessibilityLabel: _accessibilityLabel,
|
||||
accessibilityState: _accessibilityState,
|
||||
accessible: _accessible,
|
||||
allowFontScaling: allowFontScaling !== false,
|
||||
disabled: _disabled,
|
||||
ellipsizeMode: ellipsizeMode ?? 'tail',
|
||||
nativeID: _nativeID,
|
||||
numberOfLines: _numberOfLines,
|
||||
selectable: _selectable,
|
||||
selectionColor: _selectionColor,
|
||||
style: _style,
|
||||
disabled: disabled,
|
||||
children,
|
||||
}}
|
||||
textPressabilityProps={{
|
||||
@@ -203,127 +543,61 @@ const TextImpl: component(
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
nativeText = (
|
||||
<NativeText
|
||||
{...restProps}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityState={_accessibilityState}
|
||||
accessible={_accessible}
|
||||
allowFontScaling={allowFontScaling !== false}
|
||||
disabled={_disabled}
|
||||
ellipsizeMode={ellipsizeMode ?? 'tail'}
|
||||
nativeID={_nativeID}
|
||||
numberOfLines={_numberOfLines}
|
||||
ref={forwardedRef}
|
||||
selectable={_selectable}
|
||||
selectionColor={_selectionColor}
|
||||
style={_style}>
|
||||
{children}
|
||||
</NativeText>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NativeVirtualText
|
||||
{...restProps}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityState={_accessibilityState}
|
||||
nativeID={_nativeID}
|
||||
numberOfLines={_numberOfLines}
|
||||
ref={forwardedRef}
|
||||
selectable={_selectable}
|
||||
selectionColor={_selectionColor}
|
||||
style={_style}
|
||||
disabled={disabled}>
|
||||
{children}
|
||||
</NativeVirtualText>
|
||||
);
|
||||
}
|
||||
|
||||
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
|
||||
// falsy states we need to update the accessibilityState object to use the disabled prop.
|
||||
if (
|
||||
_disabled !== _accessibilityStateDisabled &&
|
||||
((_disabled != null && _disabled !== false) ||
|
||||
(_accessibilityStateDisabled != null &&
|
||||
_accessibilityStateDisabled !== false))
|
||||
) {
|
||||
_accessibilityState = {..._accessibilityState, disabled: _disabled};
|
||||
}
|
||||
|
||||
const _accessible = Platform.select({
|
||||
ios: accessible !== false,
|
||||
android:
|
||||
accessible == null ? onPress != null || onLongPress != null : accessible,
|
||||
default: accessible,
|
||||
});
|
||||
|
||||
let nativeText = null;
|
||||
if (isPressable) {
|
||||
nativeText = (
|
||||
<NativePressableText
|
||||
ref={forwardedRef}
|
||||
textProps={{
|
||||
...restProps,
|
||||
accessibilityLabel: _accessibilityLabel,
|
||||
accessibilityState: _accessibilityState,
|
||||
accessible: _accessible,
|
||||
allowFontScaling: allowFontScaling !== false,
|
||||
disabled: _disabled,
|
||||
ellipsizeMode: ellipsizeMode ?? 'tail',
|
||||
nativeID: _nativeID,
|
||||
numberOfLines: _numberOfLines,
|
||||
selectable: _selectable,
|
||||
selectionColor: _selectionColor,
|
||||
style: _style,
|
||||
children,
|
||||
}}
|
||||
textPressabilityProps={{
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
suppressHighlighting,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
nativeText = (
|
||||
<NativeText
|
||||
{...restProps}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityState={_accessibilityState}
|
||||
accessible={_accessible}
|
||||
allowFontScaling={allowFontScaling !== false}
|
||||
disabled={_disabled}
|
||||
ellipsizeMode={ellipsizeMode ?? 'tail'}
|
||||
nativeID={_nativeID}
|
||||
numberOfLines={_numberOfLines}
|
||||
ref={forwardedRef}
|
||||
selectable={_selectable}
|
||||
selectionColor={_selectionColor}
|
||||
style={_style}>
|
||||
{children}
|
||||
</NativeText>
|
||||
);
|
||||
}
|
||||
|
||||
if (children == null) {
|
||||
return nativeText;
|
||||
}
|
||||
|
||||
// If the children do not contain a JSX element it would not be possible to have a
|
||||
// nested `Text` component so we can skip adding the `TextAncestorContext` context wrapper
|
||||
// which has a performance overhead. Since we do this for performance reasons we need
|
||||
// to keep the check simple to avoid regressing overall perf. For this reason the
|
||||
// `children.length` constant is set to `3`, this should be a reasonable tradeoff
|
||||
// to capture the majority of `Text` uses but also not make this check too expensive.
|
||||
if (Array.isArray(children) && children.length <= 3) {
|
||||
let hasNonTextChild = false;
|
||||
for (let child of children) {
|
||||
if (child != null && typeof child === 'object') {
|
||||
hasNonTextChild = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasNonTextChild) {
|
||||
if (children == null) {
|
||||
return nativeText;
|
||||
}
|
||||
} else if (typeof children !== 'object') {
|
||||
return nativeText;
|
||||
}
|
||||
|
||||
return <TextAncestorContext value={true}>{nativeText}</TextAncestorContext>;
|
||||
};
|
||||
// If the children do not contain a JSX element it would not be possible to have a
|
||||
// nested `Text` component so we can skip adding the `TextAncestorContext` context wrapper
|
||||
// which has a performance overhead. Since we do this for performance reasons we need
|
||||
// to keep the check simple to avoid regressing overall perf. For this reason the
|
||||
// `children.length` constant is set to `3`, this should be a reasonable tradeoff
|
||||
// to capture the majority of `Text` uses but also not make this check too expensive.
|
||||
if (Array.isArray(children) && children.length <= 3) {
|
||||
let hasNonTextChild = false;
|
||||
for (let child of children) {
|
||||
if (child != null && typeof child === 'object') {
|
||||
hasNonTextChild = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasNonTextChild) {
|
||||
return nativeText;
|
||||
}
|
||||
} else if (typeof children !== 'object') {
|
||||
return nativeText;
|
||||
}
|
||||
|
||||
return <TextAncestorContext value={true}>{nativeText}</TextAncestorContext>;
|
||||
};
|
||||
_TextImpl = TextImplLegacy;
|
||||
}
|
||||
|
||||
const TextImpl: component(
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...props: TextProps
|
||||
) = _TextImpl;
|
||||
|
||||
TextImpl.displayName = 'Text';
|
||||
|
||||
|
||||
@@ -128,7 +128,6 @@ const HMRClient: HMRClientNativeInterface = {
|
||||
JSON.stringify({
|
||||
type: 'log',
|
||||
level,
|
||||
mode: global.RN$Bridgeless === true ? 'NOBRIDGE' : 'BRIDGE',
|
||||
data: data.map(item =>
|
||||
typeof item === 'string'
|
||||
? item
|
||||
|
||||
+22
@@ -96,6 +96,8 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
|
||||
@interface RCTModalHostViewComponentView () <RCTFabricModalHostViewControllerDelegate>
|
||||
|
||||
@property (nonatomic, weak) UIView *accessibilityFocusedView;
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTModalHostViewComponentView {
|
||||
@@ -148,6 +150,7 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
{
|
||||
BOOL shouldBePresented = !_isPresented && _shouldPresent && self.window;
|
||||
if (shouldBePresented) {
|
||||
[self saveAccessibilityFocusedView];
|
||||
self.viewController.presentationController.delegate = self;
|
||||
|
||||
_isPresented = YES;
|
||||
@@ -179,6 +182,8 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
if (eventEmitter) {
|
||||
eventEmitter->onDismiss(ModalHostViewEventEmitter::OnDismiss{});
|
||||
}
|
||||
|
||||
[self restoreAccessibilityFocusedView];
|
||||
}];
|
||||
}
|
||||
}
|
||||
@@ -207,6 +212,23 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
[self ensurePresentedOnlyIfNeeded];
|
||||
}
|
||||
|
||||
- (void)saveAccessibilityFocusedView
|
||||
{
|
||||
id focusedElement = UIAccessibilityFocusedElement(nil);
|
||||
if (focusedElement && [focusedElement isKindOfClass:[UIView class]]) {
|
||||
self.accessibilityFocusedView = (UIView *)focusedElement;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)restoreAccessibilityFocusedView
|
||||
{
|
||||
id viewToFocus = self.accessibilityFocusedView;
|
||||
if (viewToFocus) {
|
||||
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, viewToFocus);
|
||||
self.accessibilityFocusedView = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - RCTFabricModalHostViewControllerDelegate
|
||||
|
||||
- (void)boundsDidChange:(CGRect)newBounds
|
||||
|
||||
@@ -3348,8 +3348,10 @@ public final class com/facebook/react/uimanager/DisplayMetricsHolder {
|
||||
public static final fun getDisplayMetricsWritableMap (D)Lcom/facebook/react/bridge/WritableMap;
|
||||
public static final fun getScreenDisplayMetrics ()Landroid/util/DisplayMetrics;
|
||||
public static final fun getWindowDisplayMetrics ()Landroid/util/DisplayMetrics;
|
||||
public static final fun initDisplayMetrics (Landroid/content/Context;)V
|
||||
public static final fun initDisplayMetricsIfNotInitialized (Landroid/content/Context;)V
|
||||
public static final fun initScreenDisplayMetrics (Landroid/content/Context;)V
|
||||
public static final fun initScreenDisplayMetricsIfNotInitialized (Landroid/content/Context;)V
|
||||
public static final fun initWindowDisplayMetrics (Landroid/content/Context;)V
|
||||
public static final fun initWindowDisplayMetricsIfNotInitialized (Landroid/content/Context;)V
|
||||
public static final fun setScreenDisplayMetrics (Landroid/util/DisplayMetrics;)V
|
||||
public static final fun setWindowDisplayMetrics (Landroid/util/DisplayMetrics;)V
|
||||
}
|
||||
|
||||
@@ -630,6 +630,7 @@ dependencies {
|
||||
api(libs.androidx.autofill)
|
||||
api(libs.androidx.swiperefreshlayout)
|
||||
api(libs.androidx.tracing)
|
||||
api(libs.androidx.window)
|
||||
|
||||
api(libs.fbjni)
|
||||
api(libs.fresco)
|
||||
|
||||
+15
-1
@@ -229,6 +229,9 @@ public class ReactInstanceManager {
|
||||
return new ReactInstanceManagerBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection deprecation
|
||||
*/
|
||||
/* package */ ReactInstanceManager(
|
||||
Context applicationContext,
|
||||
@Nullable Activity currentActivity,
|
||||
@@ -259,7 +262,11 @@ public class ReactInstanceManager {
|
||||
FLog.d(TAG, "ReactInstanceManager.ctor()");
|
||||
initializeSoLoaderIfNecessary(applicationContext);
|
||||
|
||||
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(applicationContext);
|
||||
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(applicationContext);
|
||||
|
||||
if (currentActivity != null) {
|
||||
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(currentActivity);
|
||||
}
|
||||
|
||||
// See {@code ReactInstanceManagerBuilder} for description of all flags here.
|
||||
mApplicationContext = applicationContext;
|
||||
@@ -924,6 +931,13 @@ public class ReactInstanceManager {
|
||||
|
||||
ReactContext currentReactContext = getCurrentReactContext();
|
||||
if (currentReactContext != null) {
|
||||
DisplayMetricsHolder.initScreenDisplayMetrics(currentReactContext);
|
||||
Activity currentActivity = currentReactContext.getCurrentActivity();
|
||||
|
||||
if (currentActivity != null) {
|
||||
DisplayMetricsHolder.initWindowDisplayMetrics(currentActivity);
|
||||
}
|
||||
|
||||
AppearanceModule appearanceModule =
|
||||
currentReactContext.getNativeModule(AppearanceModule.class);
|
||||
|
||||
|
||||
+2
-1
@@ -35,7 +35,8 @@ import java.util.List;
|
||||
*
|
||||
* @deprecated This class will be replaced by com.facebook.react.ReactHost in the New Architecture.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(
|
||||
since = "This class is part of Legacy Architecture and will be removed in a future release")
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
public abstract class ReactNativeHost {
|
||||
|
||||
+6
-5
@@ -136,9 +136,8 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
|
||||
setRootViewTag(ReactRootViewTagGenerator.getNextRootViewTag());
|
||||
setClipChildren(false);
|
||||
|
||||
if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) {
|
||||
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
|
||||
}
|
||||
DisplayMetricsHolder.initScreenDisplayMetrics(getContext());
|
||||
DisplayMetricsHolder.initWindowDisplayMetrics(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -883,7 +882,8 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
|
||||
private int mDeviceRotation = 0;
|
||||
|
||||
/* package */ CustomGlobalLayoutListener() {
|
||||
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext().getApplicationContext());
|
||||
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(getContext());
|
||||
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(getContext());
|
||||
mVisibleViewArea = new Rect();
|
||||
mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60);
|
||||
}
|
||||
@@ -1006,7 +1006,8 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
|
||||
return;
|
||||
}
|
||||
mDeviceRotation = rotation;
|
||||
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
|
||||
DisplayMetricsHolder.initScreenDisplayMetrics(getContext());
|
||||
DisplayMetricsHolder.initWindowDisplayMetrics(getContext());
|
||||
emitOrientationChanged(rotation);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -7,13 +7,13 @@
|
||||
|
||||
package com.facebook.react.bridge
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
import com.facebook.react.common.annotations.internal.InteropLegacyArchitecture
|
||||
|
||||
/**
|
||||
* This interface includes the methods needed to use a running JS instance, without specifying any
|
||||
* of the bridge-specific initialization or lifecycle management.
|
||||
*/
|
||||
@LegacyArchitecture
|
||||
@InteropLegacyArchitecture
|
||||
public interface JSInstance {
|
||||
public fun invokeCallback(callbackID: Int, arguments: NativeArrayInterface)
|
||||
|
||||
|
||||
+5
@@ -25,6 +25,11 @@ import com.facebook.react.packagerconnection.RequestHandler
|
||||
*/
|
||||
internal class DefaultDevSupportManagerFactory : DevSupportManagerFactory {
|
||||
|
||||
@Deprecated(
|
||||
"Use the other create() method with useDevSupport parameter for New Architecture. This method will be removed in a future release.",
|
||||
replaceWith =
|
||||
ReplaceWith(
|
||||
"create(applicationContext, reactInstanceManagerHelper, packagerPathForJSBundleName, enableOnCreate, redBoxHandler, devBundleDownloadListener, minNumShakes, customPackagerCommandHandlers, surfaceDelegateFactory, devLoadingViewManager, pausedInDebuggerOverlayManager)"))
|
||||
override fun create(
|
||||
applicationContext: Context,
|
||||
reactInstanceManagerHelper: ReactInstanceDevHelper,
|
||||
|
||||
+6
@@ -22,6 +22,12 @@ public interface DevSupportManagerFactory {
|
||||
* Factory used by the Old Architecture flow to create a [DevSupportManager] and a
|
||||
* [BridgeDevSupportManager]
|
||||
*/
|
||||
@Deprecated(
|
||||
message =
|
||||
"Use the other create() method with useDevSupport parameter for New Architecture. This method will be removed in a future release.",
|
||||
replaceWith =
|
||||
ReplaceWith(
|
||||
"create(applicationContext, reactInstanceManagerHelper, packagerPathForJSBundleName, enableOnCreate, redBoxHandler, devBundleDownloadListener, minNumShakes, customPackagerCommandHandlers, surfaceDelegateFactory, devLoadingViewManager, pausedInDebuggerOverlayManager)"))
|
||||
public fun create(
|
||||
applicationContext: Context,
|
||||
reactInstanceManagerHelper: ReactInstanceDevHelper,
|
||||
|
||||
+4
-2
@@ -15,7 +15,8 @@ import com.facebook.react.bridge.ReactSoftExceptionLogger
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.module.annotations.ReactModule
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder.getDisplayMetricsWritableMap
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetricsIfNotInitialized
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized
|
||||
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
|
||||
|
||||
/** Module that exposes Android Constants to JS. */
|
||||
@@ -26,7 +27,8 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
|
||||
private var previousDisplayMetrics: ReadableMap? = null
|
||||
|
||||
init {
|
||||
initDisplayMetricsIfNotInitialized(reactContext)
|
||||
initScreenDisplayMetricsIfNotInitialized(reactContext)
|
||||
reactContext.currentActivity?.let { initWindowDisplayMetricsIfNotInitialized(it) }
|
||||
reactContext.addLifecycleEventListener(this)
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -8,6 +8,7 @@
|
||||
package com.facebook.react.modules.network
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStripAny
|
||||
import com.facebook.soloader.SoLoader
|
||||
|
||||
/**
|
||||
* [Experimental] An interface for reporting network events to the modern debugger server and Web
|
||||
@@ -19,6 +20,10 @@ import com.facebook.proguard.annotations.DoNotStripAny
|
||||
*/
|
||||
@DoNotStripAny
|
||||
internal object InspectorNetworkReporter {
|
||||
init {
|
||||
SoLoader.loadLibrary("react_devsupportjni")
|
||||
}
|
||||
|
||||
@JvmStatic external fun isDebuggingEnabled(): Boolean
|
||||
|
||||
/**
|
||||
|
||||
+3
-3
@@ -625,9 +625,8 @@ public class ReactHostImpl(
|
||||
override fun onConfigurationChanged(context: Context) {
|
||||
val currentReactContext = this.currentReactContext
|
||||
if (currentReactContext != null) {
|
||||
if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) {
|
||||
DisplayMetricsHolder.initDisplayMetrics(currentReactContext)
|
||||
}
|
||||
DisplayMetricsHolder.initScreenDisplayMetrics(currentReactContext)
|
||||
currentReactContext.currentActivity?.let { DisplayMetricsHolder.initWindowDisplayMetrics(it) }
|
||||
|
||||
val appearanceModule = currentReactContext.getNativeModule(AppearanceModule::class.java)
|
||||
appearanceModule?.onConfigurationChanged(context)
|
||||
@@ -918,6 +917,7 @@ public class ReactHostImpl(
|
||||
val instance =
|
||||
ReactInstance(
|
||||
reactContext,
|
||||
currentActivity,
|
||||
reactHostDelegate,
|
||||
componentFactory,
|
||||
devSupportManager,
|
||||
|
||||
+4
-1
@@ -7,6 +7,7 @@
|
||||
|
||||
package com.facebook.react.runtime
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.res.AssetManager
|
||||
import android.view.View
|
||||
import com.facebook.common.logging.FLog
|
||||
@@ -88,6 +89,7 @@ import kotlin.jvm.JvmStatic
|
||||
@UnstableReactNativeAPI
|
||||
internal class ReactInstance(
|
||||
private val context: BridgelessReactContext,
|
||||
private val activity: Activity?,
|
||||
delegate: ReactHostDelegate,
|
||||
componentFactory: ComponentFactory,
|
||||
devSupportManager: DevSupportManager,
|
||||
@@ -240,7 +242,8 @@ internal class ReactInstance(
|
||||
FabricUIManager(context, ViewManagerRegistry(viewManagerResolver), eventBeatManager)
|
||||
|
||||
// Misc initialization that needs to be done before Fabric init
|
||||
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(context)
|
||||
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(context)
|
||||
activity?.let { DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(it) }
|
||||
|
||||
val binding = FabricUIManagerBinding()
|
||||
binding.register(
|
||||
|
||||
+46
-17
@@ -13,16 +13,20 @@ import android.util.DisplayMetrics
|
||||
import android.view.WindowManager
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.window.layout.WindowMetricsCalculator
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.facebook.react.bridge.WritableNativeMap
|
||||
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
|
||||
|
||||
/**
|
||||
* Holds an instance of the current DisplayMetrics so we don't have to thread it through all the
|
||||
* classes that need it.
|
||||
*/
|
||||
public object DisplayMetricsHolder {
|
||||
private const val INITIALIZATION_MISSING_MESSAGE =
|
||||
"DisplayMetricsHolder must be initialized with initDisplayMetricsIfNotInitialized or initDisplayMetrics"
|
||||
private const val SCREEN_INITIALIZATION_MISSING_MESSAGE =
|
||||
"DisplayMetricsHolder must be initialized with initScreenDisplayMetricsIfNotInitialized or initScreenDisplayMetrics"
|
||||
private const val WINDOW_INITIALIZATION_MISSING_MESSAGE =
|
||||
"DisplayMetricsHolder must be initialized with initWindowDisplayMetricsIfNotInitialized or initWindowDisplayMetrics"
|
||||
|
||||
@JvmStatic private var windowDisplayMetrics: DisplayMetrics? = null
|
||||
@JvmStatic private var screenDisplayMetrics: DisplayMetrics? = null
|
||||
@@ -30,7 +34,7 @@ public object DisplayMetricsHolder {
|
||||
/** The metrics of the window associated to the Context used to initialize ReactNative */
|
||||
@JvmStatic
|
||||
public fun getWindowDisplayMetrics(): DisplayMetrics {
|
||||
checkNotNull(windowDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
|
||||
checkNotNull(windowDisplayMetrics) { WINDOW_INITIALIZATION_MISSING_MESSAGE }
|
||||
return windowDisplayMetrics as DisplayMetrics
|
||||
}
|
||||
|
||||
@@ -42,7 +46,7 @@ public object DisplayMetricsHolder {
|
||||
/** Screen metrics returns the metrics of the default screen on the device. */
|
||||
@JvmStatic
|
||||
public fun getScreenDisplayMetrics(): DisplayMetrics {
|
||||
checkNotNull(screenDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
|
||||
checkNotNull(screenDisplayMetrics) { SCREEN_INITIALIZATION_MISSING_MESSAGE }
|
||||
return screenDisplayMetrics as DisplayMetrics
|
||||
}
|
||||
|
||||
@@ -52,33 +56,58 @@ public object DisplayMetricsHolder {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun initDisplayMetricsIfNotInitialized(context: Context) {
|
||||
if (screenDisplayMetrics != null) {
|
||||
return
|
||||
public fun initScreenDisplayMetricsIfNotInitialized(context: Context) {
|
||||
if (screenDisplayMetrics == null) {
|
||||
initScreenDisplayMetrics(context)
|
||||
}
|
||||
initDisplayMetrics(context)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun initDisplayMetrics(context: Context) {
|
||||
val displayMetrics = context.resources.displayMetrics
|
||||
windowDisplayMetrics = displayMetrics
|
||||
val screenDisplayMetrics = DisplayMetrics()
|
||||
screenDisplayMetrics.setTo(displayMetrics)
|
||||
public fun initWindowDisplayMetricsIfNotInitialized(context: Context) {
|
||||
if (windowDisplayMetrics == null) {
|
||||
initWindowDisplayMetrics(context)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun initScreenDisplayMetrics(context: Context) {
|
||||
val displayMetrics = DisplayMetrics()
|
||||
displayMetrics.setTo(context.resources.displayMetrics)
|
||||
|
||||
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
// Get the real display metrics if we are using API level 17 or higher.
|
||||
// The real metrics include system decor elements (e.g. soft menu bar).
|
||||
//
|
||||
// See:
|
||||
// http://developer.android.com/reference/android/view/Display.html#getRealMetrics(android.util.DisplayMetrics)
|
||||
@Suppress("DEPRECATION") wm.defaultDisplay.getRealMetrics(screenDisplayMetrics)
|
||||
DisplayMetricsHolder.screenDisplayMetrics = screenDisplayMetrics
|
||||
@Suppress("DEPRECATION") wm.defaultDisplay.getRealMetrics(displayMetrics)
|
||||
screenDisplayMetrics = displayMetrics
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: Unlike [initScreenDisplayMetrics], this method needs a UiContext (Activity of
|
||||
* InputMethodService) else WindowMetircsCalculator will throw an exception.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun initWindowDisplayMetrics(context: Context) {
|
||||
val displayMetrics = DisplayMetrics()
|
||||
displayMetrics.setTo(context.resources.displayMetrics)
|
||||
|
||||
if (isEdgeToEdgeFeatureFlagOn) {
|
||||
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context).let { windowMetrics
|
||||
->
|
||||
displayMetrics.widthPixels = windowMetrics.bounds.width()
|
||||
displayMetrics.heightPixels = windowMetrics.bounds.height()
|
||||
}
|
||||
}
|
||||
|
||||
windowDisplayMetrics = displayMetrics
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun getDisplayMetricsWritableMap(fontScale: Double): WritableMap {
|
||||
checkNotNull(windowDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
|
||||
checkNotNull(screenDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
|
||||
checkNotNull(windowDisplayMetrics) { WINDOW_INITIALIZATION_MISSING_MESSAGE }
|
||||
checkNotNull(screenDisplayMetrics) { SCREEN_INITIALIZATION_MISSING_MESSAGE }
|
||||
|
||||
return WritableNativeMap().apply {
|
||||
putMap(
|
||||
|
||||
+4
-4
@@ -20,7 +20,7 @@ public object PixelUtil {
|
||||
}
|
||||
|
||||
return TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, value, DisplayMetricsHolder.getWindowDisplayMetrics())
|
||||
TypedValue.COMPLEX_UNIT_DIP, value, DisplayMetricsHolder.getScreenDisplayMetrics())
|
||||
}
|
||||
|
||||
/** Convert from DIP to PX */
|
||||
@@ -37,7 +37,7 @@ public object PixelUtil {
|
||||
return Float.NaN
|
||||
}
|
||||
|
||||
val displayMetrics = DisplayMetricsHolder.getWindowDisplayMetrics()
|
||||
val displayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics()
|
||||
val scaledValue = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, value, displayMetrics)
|
||||
|
||||
if (maxFontScale >= 1) {
|
||||
@@ -60,13 +60,13 @@ public object PixelUtil {
|
||||
return Float.NaN
|
||||
}
|
||||
|
||||
return value / DisplayMetricsHolder.getWindowDisplayMetrics().density
|
||||
return value / DisplayMetricsHolder.getScreenDisplayMetrics().density
|
||||
}
|
||||
|
||||
/** @return [Float] that represents the density of the display metrics for device screen. */
|
||||
@JvmStatic
|
||||
public fun getDisplayMetricDensity(): Float =
|
||||
DisplayMetricsHolder.getWindowDisplayMetrics().density
|
||||
DisplayMetricsHolder.getScreenDisplayMetrics().density
|
||||
|
||||
/* Kotlin extensions */
|
||||
public fun Int.dpToPx(): Float = toPixelFromDIP(this.toFloat())
|
||||
|
||||
+11
-2
@@ -12,6 +12,7 @@ import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_M
|
||||
import static com.facebook.react.uimanager.common.UIManagerType.FABRIC;
|
||||
import static com.facebook.react.uimanager.common.UIManagerType.LEGACY;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentCallbacks2;
|
||||
import android.content.res.Configuration;
|
||||
import android.view.View;
|
||||
@@ -126,7 +127,11 @@ public class UIManagerModule extends ReactContextBaseJavaModule
|
||||
ViewManagerResolver viewManagerResolver,
|
||||
int minTimeLeftInFrameForNonBatchedOperationMs) {
|
||||
super(reactContext);
|
||||
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext);
|
||||
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(reactContext);
|
||||
Activity currentActivity = reactContext.getCurrentActivity();
|
||||
if (currentActivity != null) {
|
||||
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(currentActivity);
|
||||
}
|
||||
mEventDispatcher = new EventDispatcherImpl(reactContext);
|
||||
mModuleConstants = createConstants(viewManagerResolver);
|
||||
mCustomDirectEvents = UIManagerModuleConstants.directEventTypeConstants;
|
||||
@@ -146,7 +151,11 @@ public class UIManagerModule extends ReactContextBaseJavaModule
|
||||
List<ViewManager> viewManagersList,
|
||||
int minTimeLeftInFrameForNonBatchedOperationMs) {
|
||||
super(reactContext);
|
||||
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext);
|
||||
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(reactContext);
|
||||
Activity currentActivity = reactContext.getCurrentActivity();
|
||||
if (currentActivity != null) {
|
||||
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(currentActivity);
|
||||
}
|
||||
mEventDispatcher = new EventDispatcherImpl(reactContext);
|
||||
mCustomDirectEvents = MapBuilder.newHashMap();
|
||||
mModuleConstants = createConstants(viewManagersList, null, mCustomDirectEvents);
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.View
|
||||
@@ -27,6 +29,9 @@ import com.facebook.react.uimanager.IllegalViewOperationException
|
||||
* order to animate layout when a valid configuration has been supplied by the application.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal abstract class AbstractLayoutAnimation {
|
||||
var interpolator: Interpolator? = null
|
||||
var delayMs: Int = 0
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
@@ -15,6 +17,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
|
||||
* creation.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal enum class AnimatedPropertyType {
|
||||
OPACITY,
|
||||
SCALE_X,
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.View
|
||||
@@ -17,6 +19,9 @@ import com.facebook.react.uimanager.IllegalViewOperationException
|
||||
|
||||
/** Class responsible for default layout animation, i.e animation of view creation and deletion. */
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal abstract class BaseLayoutAnimation : AbstractLayoutAnimation() {
|
||||
abstract fun isReverse(): Boolean
|
||||
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
@@ -14,6 +16,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
|
||||
* Enum representing the different interpolators that can be used in layout animation configuration.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal enum class InterpolatorType {
|
||||
LINEAR,
|
||||
EASE_IN,
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.util.SparseArray
|
||||
@@ -29,6 +31,9 @@ import javax.annotation.concurrent.NotThreadSafe
|
||||
*/
|
||||
@NotThreadSafe
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
public open class LayoutAnimationController {
|
||||
private val layoutCreateAnimation: AbstractLayoutAnimation = LayoutCreateAnimation()
|
||||
private val layoutUpdateAnimation: AbstractLayoutAnimation = LayoutUpdateAnimation()
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
@@ -12,6 +14,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
|
||||
|
||||
/** Listener invoked when a layout animation has completed. */
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
public fun interface LayoutAnimationListener {
|
||||
public fun onAnimationEnd()
|
||||
}
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
@@ -15,6 +17,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
|
||||
* Enum representing the different animation type that can be specified in layout animation config.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal enum class LayoutAnimationType {
|
||||
CREATE,
|
||||
UPDATE,
|
||||
|
||||
+2
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
@@ -16,6 +18,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
|
||||
* config was supplied for the layout animation of DELETE type.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal class LayoutDeleteAnimation : BaseLayoutAnimation() {
|
||||
|
||||
override fun isReverse(): Boolean = true
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
@@ -12,6 +14,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
|
||||
|
||||
/** Interface for an animation type that takes care of updating the view layout. */
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal interface LayoutHandlingAnimation {
|
||||
/**
|
||||
* Notifies the animation of a layout update in case one occurs during the animation. This avoids
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.View
|
||||
@@ -19,6 +21,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
|
||||
* was supplied for the layout animation of UPDATE type.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal class LayoutUpdateAnimation : AbstractLayoutAnimation() {
|
||||
|
||||
override fun isValid(): Boolean = durationMs > 0
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.View
|
||||
@@ -21,6 +23,9 @@ import java.lang.ref.WeakReference
|
||||
* optimize rendering performances.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal class OpacityAnimation(view: View, private val startOpacity: Float, endOpacity: Float) :
|
||||
Animation() {
|
||||
private val viewRef = WeakReference(view)
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.View
|
||||
@@ -22,6 +24,9 @@ import java.lang.ref.WeakReference
|
||||
* ScaleAnimation and TranslateAnimation.
|
||||
*/
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal class PositionAndSizeAnimation(view: View, x: Int, y: Int, width: Int, height: Int) :
|
||||
Animation(), LayoutHandlingAnimation {
|
||||
private val viewRef = WeakReference(view)
|
||||
|
||||
+5
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.animation.Interpolator
|
||||
@@ -19,6 +21,9 @@ import kotlin.math.sin
|
||||
/** Simple spring interpolator */
|
||||
// TODO(7613736): Improve spring interpolator with friction and damping variable support
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
@Deprecated(
|
||||
message = "This class is part of Legacy Architecture and will be removed in a future release",
|
||||
level = DeprecationLevel.WARNING)
|
||||
internal class SimpleSpringInterpolator @JvmOverloads constructor(springDamping: Float = FACTOR) :
|
||||
Interpolator {
|
||||
private val _springDamping: Float = springDamping
|
||||
|
||||
+11
-7
@@ -50,28 +50,32 @@ private fun rectsOverlap(rect1: Rect, rect2: Rect): Boolean {
|
||||
internal class VirtualViewContainerState {
|
||||
|
||||
private val prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
|
||||
private val detectWindowFocus = ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()
|
||||
|
||||
private val virtualViews: MutableSet<VirtualView> = mutableSetOf()
|
||||
private val emptyRect: Rect = Rect()
|
||||
private val visibleRect: Rect = Rect()
|
||||
private val prerenderRect: Rect = Rect()
|
||||
private val onWindowFocusChangeListener =
|
||||
ViewTreeObserver.OnWindowFocusChangeListener {
|
||||
debugLog("onWindowFocusChanged")
|
||||
updateModes()
|
||||
if (ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()) {
|
||||
ViewTreeObserver.OnWindowFocusChangeListener {
|
||||
debugLog("onWindowFocusChanged")
|
||||
updateModes()
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
private val scrollView: ViewGroup
|
||||
|
||||
constructor(scrollView: ViewGroup) {
|
||||
this.scrollView = scrollView
|
||||
if (detectWindowFocus) {
|
||||
if (onWindowFocusChangeListener != null) {
|
||||
scrollView.viewTreeObserver.addOnWindowFocusChangeListener(onWindowFocusChangeListener)
|
||||
}
|
||||
}
|
||||
|
||||
public fun cleanup() {
|
||||
if (detectWindowFocus) {
|
||||
if (onWindowFocusChangeListener != null) {
|
||||
scrollView.viewTreeObserver.removeOnWindowFocusChangeListener(onWindowFocusChangeListener)
|
||||
}
|
||||
}
|
||||
@@ -115,7 +119,7 @@ internal class VirtualViewContainerState {
|
||||
rect.isEmpty -> {}
|
||||
rectsOverlap(rect, visibleRect) -> {
|
||||
thresholdRect = visibleRect
|
||||
if (detectWindowFocus) {
|
||||
if (onWindowFocusChangeListener != null) {
|
||||
if (scrollView.hasWindowFocus()) {
|
||||
mode = VirtualViewMode.Visible
|
||||
} else {
|
||||
|
||||
+9
-6
@@ -40,11 +40,14 @@ public class ReactVirtualView(context: Context) :
|
||||
internal var modeChangeEmitter: VirtualViewModeChangeEmitter? = null
|
||||
internal var prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
|
||||
internal val debugLogEnabled: Boolean = ReactNativeFeatureFlags.enableVirtualViewDebugFeatures()
|
||||
internal val detectWindowFocus = ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()
|
||||
|
||||
private val onWindowFocusChangeListener =
|
||||
ViewTreeObserver.OnWindowFocusChangeListener {
|
||||
dispatchOnModeChangeIfNeeded(checkRectChange = false)
|
||||
if (ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()) {
|
||||
ViewTreeObserver.OnWindowFocusChangeListener {
|
||||
dispatchOnModeChangeIfNeeded(checkRectChange = false)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
private var parentScrollView: View? = null
|
||||
@@ -90,7 +93,7 @@ public class ReactVirtualView(context: Context) :
|
||||
ReactScrollViewHelper.addLayoutChangeListener(this)
|
||||
}
|
||||
debugLog("onAttachedToWindow")
|
||||
if (detectWindowFocus) {
|
||||
if (onWindowFocusChangeListener != null) {
|
||||
viewTreeObserver.addOnWindowFocusChangeListener(onWindowFocusChangeListener)
|
||||
}
|
||||
dispatchOnModeChangeIfNeeded(checkRectChange = false)
|
||||
@@ -100,7 +103,7 @@ public class ReactVirtualView(context: Context) :
|
||||
super.onDetachedFromWindow()
|
||||
ReactScrollViewHelper.removeScrollListener(this)
|
||||
ReactScrollViewHelper.removeLayoutChangeListener(this)
|
||||
if (detectWindowFocus) {
|
||||
if (onWindowFocusChangeListener != null) {
|
||||
viewTreeObserver.removeOnWindowFocusChangeListener(onWindowFocusChangeListener)
|
||||
}
|
||||
cleanupLayoutListeners()
|
||||
@@ -202,7 +205,7 @@ public class ReactVirtualView(context: Context) :
|
||||
|
||||
val newMode: VirtualViewMode
|
||||
if (rectsOverlap(targetRect, thresholdRect)) {
|
||||
if (detectWindowFocus) {
|
||||
if (onWindowFocusChangeListener != null) {
|
||||
if (hasWindowFocus()) {
|
||||
newMode = VirtualViewMode.Visible
|
||||
} else {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.yoga;
|
||||
|
||||
public class YogaConstants {
|
||||
|
||||
public static final float UNDEFINED = Float.NaN;
|
||||
|
||||
public static boolean isUndefined(float value) {
|
||||
return Float.compare(value, UNDEFINED) == 0;
|
||||
}
|
||||
|
||||
public static boolean isUndefined(YogaValue value) {
|
||||
return value.unit == YogaUnit.UNDEFINED;
|
||||
}
|
||||
|
||||
public static float getUndefined() {
|
||||
return UNDEFINED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.yoga
|
||||
|
||||
public object YogaConstants {
|
||||
@JvmField public val UNDEFINED: Float = Float.NaN
|
||||
|
||||
@JvmStatic public fun isUndefined(value: Float): Boolean = value.compareTo(UNDEFINED) == 0
|
||||
|
||||
@JvmStatic public fun isUndefined(value: YogaValue): Boolean = value.unit == YogaUnit.UNDEFINED
|
||||
|
||||
@JvmStatic public fun getUndefined(): Float = UNDEFINED
|
||||
}
|
||||
@@ -29,9 +29,6 @@ if(CCACHE_FOUND)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
|
||||
endif(CCACHE_FOUND)
|
||||
|
||||
# Make sure every shared lib includes a .note.gnu.build-id header
|
||||
add_link_options(-Wl,--build-id)
|
||||
|
||||
function(add_react_android_subdir relative_path)
|
||||
add_subdirectory(${REACT_ANDROID_DIR}/${relative_path} ReactAndroid/${relative_path})
|
||||
endfunction()
|
||||
|
||||
@@ -19,6 +19,7 @@ target_include_directories(react_devsupportjni PUBLIC .)
|
||||
|
||||
target_link_libraries(react_devsupportjni
|
||||
fbjni
|
||||
jsinspector)
|
||||
jsinspector
|
||||
jsinspector_network)
|
||||
|
||||
target_compile_reactnative_options(react_devsupportjni PRIVATE)
|
||||
|
||||
+24
-23
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "InspectorNetworkReporter.h"
|
||||
#include "JInspectorNetworkReporter.h"
|
||||
|
||||
#include <jsinspector-modern/network/NetworkReporter.h>
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
#endif
|
||||
|
||||
using namespace facebook::jni;
|
||||
using namespace facebook::react::jsinspector_modern;
|
||||
|
||||
namespace facebook::react {
|
||||
namespace facebook::react::jsinspector_modern {
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -59,12 +58,12 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
|
||||
#endif
|
||||
|
||||
/* static */ jboolean InspectorNetworkReporter::isDebuggingEnabled(
|
||||
/* static */ jboolean JInspectorNetworkReporter::isDebuggingEnabled(
|
||||
jni::alias_ref<jclass> /*unused*/) {
|
||||
return NetworkReporter::getInstance().isDebuggingEnabled();
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::reportRequestStart(
|
||||
/* static */ void JInspectorNetworkReporter::reportRequestStart(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jni::alias_ref<jstring> requestUrl,
|
||||
@@ -82,7 +81,7 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
std::to_string(requestId), requestInfo, encodedDataLength, std::nullopt);
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::reportConnectionTiming(
|
||||
/* static */ void JInspectorNetworkReporter::reportConnectionTiming(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jni::alias_ref<jni::JMap<jstring, jstring>> headers) {
|
||||
@@ -90,7 +89,7 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
std::to_string(requestId), convertJavaMapToHeaders(headers));
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::reportResponseStart(
|
||||
/* static */ void JInspectorNetworkReporter::reportResponseStart(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jni::alias_ref<jstring> requestUrl,
|
||||
@@ -108,7 +107,7 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
static_cast<std::int64_t>(encodedDataLength));
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::reportDataReceivedImpl(
|
||||
/* static */ void JInspectorNetworkReporter::reportDataReceivedImpl(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jint dataLength) {
|
||||
@@ -116,7 +115,7 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
std::to_string(requestId), dataLength, std::nullopt);
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::reportResponseEnd(
|
||||
/* static */ void JInspectorNetworkReporter::reportResponseEnd(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jlong encodedDataLength) {
|
||||
@@ -134,7 +133,7 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::reportRequestFailed(
|
||||
/* static */ void JInspectorNetworkReporter::reportRequestFailed(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jboolean cancelled) {
|
||||
@@ -142,7 +141,7 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
std::to_string(requestId), cancelled);
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::maybeStoreResponseBodyImpl(
|
||||
/* static */ void JInspectorNetworkReporter::maybeStoreResponseBodyImpl(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jni::alias_ref<jstring> body,
|
||||
@@ -160,7 +159,7 @@ static std::unordered_map<int, std::string> responseBuffers;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
InspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl(
|
||||
JInspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl(
|
||||
jni::alias_ref<jclass> /*unused*/,
|
||||
jint requestId,
|
||||
jni::alias_ref<jstring> data) {
|
||||
@@ -176,31 +175,33 @@ InspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl(
|
||||
#endif
|
||||
}
|
||||
|
||||
/* static */ void InspectorNetworkReporter::registerNatives() {
|
||||
/* static */ void JInspectorNetworkReporter::registerNatives() {
|
||||
javaClassLocal()->registerNatives({
|
||||
makeNativeMethod(
|
||||
"isDebuggingEnabled", InspectorNetworkReporter::isDebuggingEnabled),
|
||||
"isDebuggingEnabled", JInspectorNetworkReporter::isDebuggingEnabled),
|
||||
makeNativeMethod(
|
||||
"reportRequestStart", InspectorNetworkReporter::reportRequestStart),
|
||||
"reportRequestStart", JInspectorNetworkReporter::reportRequestStart),
|
||||
makeNativeMethod(
|
||||
"reportResponseStart", InspectorNetworkReporter::reportResponseStart),
|
||||
"reportResponseStart",
|
||||
JInspectorNetworkReporter::reportResponseStart),
|
||||
makeNativeMethod(
|
||||
"reportConnectionTiming",
|
||||
InspectorNetworkReporter::reportConnectionTiming),
|
||||
JInspectorNetworkReporter::reportConnectionTiming),
|
||||
makeNativeMethod(
|
||||
"reportDataReceivedImpl",
|
||||
InspectorNetworkReporter::reportDataReceivedImpl),
|
||||
JInspectorNetworkReporter::reportDataReceivedImpl),
|
||||
makeNativeMethod(
|
||||
"reportResponseEnd", InspectorNetworkReporter::reportResponseEnd),
|
||||
"reportResponseEnd", JInspectorNetworkReporter::reportResponseEnd),
|
||||
makeNativeMethod(
|
||||
"reportRequestFailed", InspectorNetworkReporter::reportRequestFailed),
|
||||
"reportRequestFailed",
|
||||
JInspectorNetworkReporter::reportRequestFailed),
|
||||
makeNativeMethod(
|
||||
"maybeStoreResponseBodyImpl",
|
||||
InspectorNetworkReporter::maybeStoreResponseBodyImpl),
|
||||
JInspectorNetworkReporter::maybeStoreResponseBodyImpl),
|
||||
makeNativeMethod(
|
||||
"maybeStoreResponseBodyIncrementalImpl",
|
||||
InspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl),
|
||||
JInspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl),
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
} // namespace facebook::react::jsinspector_modern
|
||||
+5
-5
@@ -9,10 +9,10 @@
|
||||
|
||||
#include <fbjni/fbjni.h>
|
||||
|
||||
namespace facebook::react {
|
||||
namespace facebook::react::jsinspector_modern {
|
||||
|
||||
class InspectorNetworkReporter
|
||||
: public jni::HybridClass<InspectorNetworkReporter> {
|
||||
class JInspectorNetworkReporter
|
||||
: public jni::HybridClass<JInspectorNetworkReporter> {
|
||||
public:
|
||||
static constexpr auto kJavaDescriptor =
|
||||
"Lcom/facebook/react/modules/network/InspectorNetworkReporter;";
|
||||
@@ -70,7 +70,7 @@ class InspectorNetworkReporter
|
||||
static void registerNatives();
|
||||
|
||||
private:
|
||||
InspectorNetworkReporter() = delete;
|
||||
JInspectorNetworkReporter() = delete;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
} // namespace facebook::react::jsinspector_modern
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "JCxxInspectorPackagerConnection.h"
|
||||
#include "JCxxInspectorPackagerConnectionWebSocketDelegate.h"
|
||||
#include "JInspectorFlags.h"
|
||||
#include "JInspectorNetworkReporter.h"
|
||||
|
||||
#include <fbjni/fbjni.h>
|
||||
|
||||
@@ -18,5 +19,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*unused*/) {
|
||||
facebook::react::jsinspector_modern::
|
||||
JCxxInspectorPackagerConnectionWebSocketDelegate::registerNatives();
|
||||
facebook::react::jsinspector_modern::JInspectorFlags::registerNatives();
|
||||
facebook::react::jsinspector_modern::JInspectorNetworkReporter::
|
||||
registerNatives();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ add_library(
|
||||
OBJECT
|
||||
CatalystInstanceImpl.cpp
|
||||
InspectorNetworkRequestListener.cpp
|
||||
InspectorNetworkReporter.cpp
|
||||
JExecutor.cpp
|
||||
JInspector.cpp
|
||||
JMessageQueueThread.cpp
|
||||
@@ -82,10 +81,8 @@ target_link_libraries(reactnativejni
|
||||
fbjni
|
||||
folly_runtime
|
||||
glog_init
|
||||
jsinspector_network
|
||||
logger
|
||||
react_cxxreact
|
||||
react_featureflags
|
||||
react_renderer_runtimescheduler
|
||||
reactnativejni_common
|
||||
runtimeexecutor
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include "CatalystInstanceImpl.h"
|
||||
#include "CxxModuleWrapperBase.h"
|
||||
#include "InspectorNetworkReporter.h"
|
||||
#include "InspectorNetworkRequestListener.h"
|
||||
#include "JInspector.h"
|
||||
#include "JavaScriptExecutorHolder.h"
|
||||
@@ -45,7 +44,6 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
JInspector::registerNatives();
|
||||
ReactInstanceManagerInspectorTarget::registerNatives();
|
||||
InspectorNetworkRequestListener::registerNatives();
|
||||
InspectorNetworkReporter::registerNatives();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -127,6 +127,9 @@ void JReactHostInspectorTarget::onSetPausedInDebuggerMessage(
|
||||
}
|
||||
}
|
||||
|
||||
void JReactHostInspectorTarget::unstable_onPerfMonitorUpdate(
|
||||
const PerfMonitorUpdateRequest& /* unused */) {}
|
||||
|
||||
void JReactHostInspectorTarget::loadNetworkResource(
|
||||
const jsinspector_modern::LoadNetworkResourceRequest& params,
|
||||
jsinspector_modern::ScopedExecutor<
|
||||
|
||||
+2
@@ -84,6 +84,8 @@ class JReactHostInspectorTarget
|
||||
void onReload(const PageReloadRequest& request) override;
|
||||
void onSetPausedInDebuggerMessage(
|
||||
const OverlaySetPausedInDebuggerMessageRequest&) override;
|
||||
void unstable_onPerfMonitorUpdate(
|
||||
const PerfMonitorUpdateRequest& /* unused */) override;
|
||||
void loadNetworkResource(
|
||||
const jsinspector_modern::LoadNetworkResourceRequest& params,
|
||||
jsinspector_modern::ScopedExecutor<
|
||||
|
||||
@@ -71,7 +71,8 @@ class RootViewTest {
|
||||
reactContext = spy(BridgeReactContext(RuntimeEnvironment.getApplication()))
|
||||
reactContext.initializeWithInstance(catalystInstanceMock)
|
||||
|
||||
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext)
|
||||
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(reactContext)
|
||||
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(reactContext)
|
||||
val uiManagerModuleMock: UIManagerModule = mock()
|
||||
whenever(catalystInstanceMock.getNativeModule(UIManagerModule::class.java))
|
||||
.thenReturn(uiManagerModuleMock)
|
||||
|
||||
+22
@@ -11,12 +11,18 @@
|
||||
|
||||
package com.facebook.react.bridge
|
||||
|
||||
import android.app.Application
|
||||
import com.facebook.react.bridge.queue.MessageQueueThreadSpec
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfiguration
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec
|
||||
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
||||
import com.facebook.react.runtime.BridgelessReactContext
|
||||
import com.facebook.react.runtime.ReactHostImpl
|
||||
import com.facebook.react.runtime.internal.bolts.Task
|
||||
import com.facebook.react.uimanager.UIManagerModule
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.spy
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
|
||||
@@ -49,4 +55,20 @@ object ReactTestHelper {
|
||||
whenever(reactInstance.isDestroyed).thenReturn(false)
|
||||
return reactInstance
|
||||
}
|
||||
|
||||
@OptIn(UnstableReactNativeAPI::class)
|
||||
fun createTestReactApplicationContext(application: Application): ReactApplicationContext {
|
||||
val reactHost =
|
||||
spy(
|
||||
ReactHostImpl(
|
||||
RuntimeEnvironment.getApplication(),
|
||||
mock(),
|
||||
mock(),
|
||||
Task.Companion.IMMEDIATE_EXECUTOR,
|
||||
Task.Companion.IMMEDIATE_EXECUTOR,
|
||||
false /* allowPackagerServerAccess */,
|
||||
false /* useDevSupport */,
|
||||
))
|
||||
return BridgelessReactContext(application, reactHost)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -467,7 +467,7 @@ class TouchEventDispatchTest {
|
||||
metrics.xdpi = 1f
|
||||
metrics.ydpi = 1f
|
||||
metrics.density = 1f
|
||||
DisplayMetricsHolder.setWindowDisplayMetrics(metrics)
|
||||
DisplayMetricsHolder.setScreenDisplayMetrics(metrics)
|
||||
|
||||
val reactContext = ReactTestHelper.createCatalystContextForTest()
|
||||
|
||||
|
||||
+16
-7
@@ -5,36 +5,45 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.modules.clipboard
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import com.facebook.react.bridge.BridgeReactContext
|
||||
import com.facebook.react.bridge.ReactTestHelper.createTestReactApplicationContext
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
|
||||
import com.facebook.testutils.shadows.ShadowSoLoader
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@SuppressLint("ClipboardManager", "DeprecatedClass")
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(shadows = [ShadowSoLoader::class])
|
||||
class ClipboardModuleTest {
|
||||
private lateinit var clipboardModule: ClipboardModule
|
||||
private lateinit var clipboardManager: ClipboardManager
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
clipboardModule = ClipboardModule(BridgeReactContext(RuntimeEnvironment.getApplication()))
|
||||
ReactNativeFeatureFlagsForTests.setUp()
|
||||
clipboardModule =
|
||||
ClipboardModule(createTestReactApplicationContext(RuntimeEnvironment.getApplication()))
|
||||
clipboardManager =
|
||||
RuntimeEnvironment.getApplication().getSystemService(Context.CLIPBOARD_SERVICE)
|
||||
as ClipboardManager
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
ReactNativeFeatureFlags.dangerouslyReset()
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Test
|
||||
fun testSetString() {
|
||||
clipboardModule.setString(TEST_CONTENT)
|
||||
|
||||
+2
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.View
|
||||
|
||||
+2
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
+2
@@ -5,6 +5,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.View
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ class ColorStopTest {
|
||||
fun setUp() {
|
||||
val metrics = DisplayMetrics()
|
||||
metrics.density = 1f
|
||||
DisplayMetricsHolder.setWindowDisplayMetrics(metrics)
|
||||
DisplayMetricsHolder.setScreenDisplayMetrics(metrics)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+2
-2
@@ -75,14 +75,14 @@ class ReactImagePropertyTest {
|
||||
context.initializeWithInstance(catalystInstanceMock)
|
||||
themeContext = ThemedReactContext(context, context, null, -1)
|
||||
Fresco.initialize(context)
|
||||
DisplayMetricsHolder.setWindowDisplayMetrics(DisplayMetrics())
|
||||
DisplayMetricsHolder.setScreenDisplayMetrics(DisplayMetrics())
|
||||
|
||||
ReactNativeFeatureFlagsForTests.setUp()
|
||||
}
|
||||
|
||||
@After
|
||||
fun teardown() {
|
||||
DisplayMetricsHolder.setWindowDisplayMetrics(null)
|
||||
DisplayMetricsHolder.setScreenDisplayMetrics(null)
|
||||
rnLog.close()
|
||||
flogMock.close()
|
||||
}
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ class ReactTextInputPropertyTest {
|
||||
context.initializeWithInstance(catalystInstanceMock)
|
||||
themedContext = ThemedReactContext(context, context.baseContext, null, ID_NULL)
|
||||
manager = ReactTextInputManager()
|
||||
DisplayMetricsHolder.setWindowDisplayMetrics(DisplayMetrics())
|
||||
DisplayMetricsHolder.setScreenDisplayMetrics(DisplayMetrics())
|
||||
view = manager.createViewInstance(themedContext)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ class ReactVirtualViewTest {
|
||||
|
||||
val displayMetricsHolder = mockStatic(DisplayMetricsHolder::class.java)
|
||||
displayMetricsHolder
|
||||
.`when`<DisplayMetrics> { DisplayMetricsHolder.getWindowDisplayMetrics() }
|
||||
.`when`<DisplayMetrics> { DisplayMetricsHolder.getScreenDisplayMetrics() }
|
||||
.thenAnswer { DisplayMetrics().apply { density = 1f } }
|
||||
}
|
||||
|
||||
|
||||
@@ -146,11 +146,53 @@ class HostCommandSender {
|
||||
std::unique_ptr<ILocalConnection> connection_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enables the caller to install and subscribe to a named CDP runtime binding
|
||||
* on the HostTarget via a callback. Note: Per CDP spec, this does not need to
|
||||
* check if the `Runtime` domain is enabled.
|
||||
*/
|
||||
class HostRuntimeBinding {
|
||||
public:
|
||||
explicit HostRuntimeBinding(
|
||||
HostTarget& target,
|
||||
std::string name,
|
||||
std::function<void(std::string)> callback)
|
||||
: connection_(target.connect(std::make_unique<CallbackRemoteConnection>(
|
||||
[callback = std::move(callback)](const std::string& message) {
|
||||
auto parsedMessage = folly::parseJson(message);
|
||||
|
||||
// Ignore initial Runtime.addBinding response
|
||||
if (parsedMessage["id"] == 0 &&
|
||||
parsedMessage["result"].isObject() &&
|
||||
parsedMessage["result"].empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Assert that we only intercept bindingCalled responses
|
||||
assert(
|
||||
parsedMessage["method"].asString() ==
|
||||
"Runtime.bindingCalled");
|
||||
callback(parsedMessage["params"]["payload"].asString());
|
||||
}))) {
|
||||
// Install runtime binding
|
||||
connection_->sendMessage(cdp::jsonRequest(
|
||||
0,
|
||||
"Runtime.addBinding",
|
||||
folly::dynamic::object("name", std::move(name))));
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<ILocalConnection> connection_;
|
||||
};
|
||||
|
||||
std::shared_ptr<HostTarget> HostTarget::create(
|
||||
HostTargetDelegate& delegate,
|
||||
VoidExecutor executor) {
|
||||
std::shared_ptr<HostTarget> hostTarget{new HostTarget(delegate)};
|
||||
hostTarget->setExecutor(std::move(executor));
|
||||
if (InspectorFlags::getInstance().getPerfMonitorV2Enabled()) {
|
||||
hostTarget->installPerfMetricsBinding();
|
||||
}
|
||||
return hostTarget;
|
||||
}
|
||||
|
||||
@@ -229,6 +271,19 @@ void HostTarget::sendCommand(HostCommand command) {
|
||||
});
|
||||
}
|
||||
|
||||
void HostTarget::installPerfMetricsBinding() {
|
||||
perfMetricsBinding_ = std::make_unique<HostRuntimeBinding>(
|
||||
*this, // Used immediately
|
||||
"__chromium_devtools_metrics_reporter",
|
||||
[this](const std::string& message) {
|
||||
auto payload = folly::parseJson(message);
|
||||
HostTargetDelegate::PerfMonitorUpdateRequest request{
|
||||
.interactionName = payload["eventName"].asString(),
|
||||
.durationMs = static_cast<uint16_t>(payload["duration"].asInt())};
|
||||
delegate_.unstable_onPerfMonitorUpdate(request);
|
||||
});
|
||||
}
|
||||
|
||||
HostTargetController::HostTargetController(HostTarget& target)
|
||||
: target_(target) {}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ class HostTargetSession;
|
||||
class HostAgent;
|
||||
class HostTracingAgent;
|
||||
class HostCommandSender;
|
||||
class HostRuntimeBinding;
|
||||
class HostTarget;
|
||||
class HostTargetTraceRecording;
|
||||
|
||||
@@ -97,6 +98,11 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
|
||||
}
|
||||
};
|
||||
|
||||
struct PerfMonitorUpdateRequest {
|
||||
std::string interactionName;
|
||||
uint16_t durationMs;
|
||||
};
|
||||
|
||||
virtual ~HostTargetDelegate() override;
|
||||
|
||||
/**
|
||||
@@ -125,6 +131,13 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
|
||||
virtual void onSetPausedInDebuggerMessage(
|
||||
const OverlaySetPausedInDebuggerMessageRequest& request) = 0;
|
||||
|
||||
/**
|
||||
* [Experimental] Called when the runtime has new data for the V2 Perf
|
||||
* Monitor overlay. This is called on the inspector thread.
|
||||
*/
|
||||
virtual void unstable_onPerfMonitorUpdate(
|
||||
const PerfMonitorUpdateRequest& /*request*/) {}
|
||||
|
||||
/**
|
||||
* Called by NetworkIOAgent on handling a `Network.loadNetworkResource` CDP
|
||||
* request. Platform implementations should override this to perform a
|
||||
@@ -296,6 +309,7 @@ class JSINSPECTOR_EXPORT HostTarget
|
||||
std::shared_ptr<ExecutionContextManager> executionContextManager_;
|
||||
std::shared_ptr<InstanceTarget> currentInstance_{nullptr};
|
||||
std::unique_ptr<HostCommandSender> commandSender_;
|
||||
std::unique_ptr<HostRuntimeBinding> perfMetricsBinding_;
|
||||
|
||||
/**
|
||||
* Current pending trace recording, which encapsulates the configuration of
|
||||
@@ -313,6 +327,13 @@ class JSINSPECTOR_EXPORT HostTarget
|
||||
return currentInstance_ != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a runtime binding subscribing to the Interaction to Next Paint
|
||||
* (INP) live metric, which we broadcast to the V2 Perf Monitor overlay
|
||||
* via \ref HostTargetDelegate::unstable_onPerfMonitorUpdate.
|
||||
*/
|
||||
void installPerfMetricsBinding();
|
||||
|
||||
// Necessary to allow HostAgent to access HostTarget's internals in a
|
||||
// controlled way (i.e. only HostTargetController gets friend access, while
|
||||
// HostAgent itself doesn't).
|
||||
|
||||
@@ -33,6 +33,10 @@ bool InspectorFlags::getNetworkInspectionEnabled() const {
|
||||
return loadFlagsAndAssertUnchanged().networkInspectionEnabled;
|
||||
}
|
||||
|
||||
bool InspectorFlags::getPerfMonitorV2Enabled() const {
|
||||
return loadFlagsAndAssertUnchanged().perfMonitorV2Enabled;
|
||||
}
|
||||
|
||||
void InspectorFlags::dangerouslyResetFlags() {
|
||||
*this = InspectorFlags{};
|
||||
}
|
||||
@@ -59,6 +63,9 @@ const InspectorFlags::Values& InspectorFlags::loadFlagsAndAssertUnchanged()
|
||||
.networkInspectionEnabled =
|
||||
ReactNativeFeatureFlags::enableBridgelessArchitecture() &&
|
||||
ReactNativeFeatureFlags::fuseboxNetworkInspectionEnabled(),
|
||||
.perfMonitorV2Enabled =
|
||||
ReactNativeFeatureFlags::enableBridgelessArchitecture() &&
|
||||
ReactNativeFeatureFlags::perfMonitorV2Enabled(),
|
||||
};
|
||||
|
||||
if (cachedValues_.has_value() && !inconsistentFlagsStateLogged_) {
|
||||
|
||||
@@ -35,6 +35,11 @@ class InspectorFlags {
|
||||
*/
|
||||
bool getNetworkInspectionEnabled() const;
|
||||
|
||||
/**
|
||||
* Flag determining if the V2 in-app Performance Monitor is enabled.
|
||||
*/
|
||||
bool getPerfMonitorV2Enabled() const;
|
||||
|
||||
/**
|
||||
* Forcibly disable the main `getFuseboxEnabled()` flag. This should ONLY be
|
||||
* used by `ReactInstanceIntegrationTest`.
|
||||
@@ -52,6 +57,7 @@ class InspectorFlags {
|
||||
bool fuseboxEnabled;
|
||||
bool isProfilingBuild;
|
||||
bool networkInspectionEnabled;
|
||||
bool perfMonitorV2Enabled;
|
||||
bool operator==(const Values&) const = default;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,14 @@ void CallbackLocalConnection::disconnect() {
|
||||
handler_ = nullptr;
|
||||
}
|
||||
|
||||
CallbackRemoteConnection::CallbackRemoteConnection(
|
||||
std::function<void(std::string)> handler)
|
||||
: handler_(std::move(handler)) {}
|
||||
|
||||
void CallbackRemoteConnection::onMessage(std::string message) {
|
||||
handler_(std::move(message));
|
||||
}
|
||||
|
||||
RAIIRemoteConnection::RAIIRemoteConnection(
|
||||
std::unique_ptr<IRemoteConnection> remote)
|
||||
: remote_(std::move(remote)) {}
|
||||
|
||||
@@ -33,6 +33,25 @@ class CallbackLocalConnection : public ILocalConnection {
|
||||
std::function<void(std::string)> handler_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps a callback function in IRemoteConnection.
|
||||
*/
|
||||
class CallbackRemoteConnection : public IRemoteConnection {
|
||||
public:
|
||||
/**
|
||||
* Creates a new Connection that uses the given callback to receive messages
|
||||
* from the backend.
|
||||
*/
|
||||
explicit CallbackRemoteConnection(std::function<void(std::string)> handler);
|
||||
|
||||
void onMessage(std::string message) override;
|
||||
|
||||
void onDisconnect() override {}
|
||||
|
||||
private:
|
||||
std::function<void(std::string)> handler_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wraps an IRemoteConnection in a simpler interface that calls `onDisconnect`
|
||||
* implicitly upon destruction.
|
||||
|
||||
@@ -292,7 +292,7 @@ SharedDebugStringConvertibleList ImageProps::getDebugProps() const {
|
||||
sourcesList = sources[0].getDebugProps("source");
|
||||
} else if (sources.size() > 1) {
|
||||
for (const auto& source : sources) {
|
||||
std::string sourceName = "source@" + react::toString(source.scale) + "x";
|
||||
std::string sourceName = "source-" + react::toString(source.scale) + "x";
|
||||
auto debugProps = source.getDebugProps(sourceName);
|
||||
sourcesList.insert(
|
||||
sourcesList.end(), debugProps.begin(), debugProps.end());
|
||||
@@ -304,8 +304,32 @@ SharedDebugStringConvertibleList ImageProps::getDebugProps() const {
|
||||
SharedDebugStringConvertibleList{
|
||||
debugStringConvertibleItem(
|
||||
"blurRadius", blurRadius, imageProps.blurRadius),
|
||||
debugStringConvertibleItem(
|
||||
"resizeMode",
|
||||
toString(resizeMode),
|
||||
toString(imageProps.resizeMode)),
|
||||
debugStringConvertibleItem(
|
||||
"tintColor", toString(tintColor), toString(imageProps.tintColor)),
|
||||
};
|
||||
}
|
||||
|
||||
inline std::string toString(ImageResizeMode resizeMode) {
|
||||
switch (resizeMode) {
|
||||
case ImageResizeMode::Cover:
|
||||
return "cover";
|
||||
case ImageResizeMode::Contain:
|
||||
return "contain";
|
||||
case ImageResizeMode::Stretch:
|
||||
return "stretch";
|
||||
case ImageResizeMode::Center:
|
||||
return "center";
|
||||
case ImageResizeMode::Repeat:
|
||||
return "repeat";
|
||||
case ImageResizeMode::None:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -34,7 +34,7 @@ class ImageProps final : public ViewProps {
|
||||
ImageSources sources{};
|
||||
ImageSource defaultSource{};
|
||||
ImageSource loadingIndicatorSource{};
|
||||
ImageResizeMode resizeMode{ImageResizeMode::Stretch};
|
||||
ImageResizeMode resizeMode{ImageResizeMode::Cover};
|
||||
Float blurRadius{};
|
||||
EdgeInsets capInsets{};
|
||||
SharedColor tintColor{};
|
||||
|
||||
+1
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
|
||||
extern const char AndroidSwitchComponentName[] = "AndroidSwitch";
|
||||
|
||||
void AndroidSwitchShadowNode::setAndroidSwitchMeasurementsManager(
|
||||
|
||||
+1
@@ -15,6 +15,7 @@
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
// NOLINTNEXTLINE(modernize-avoid-c-arrays)
|
||||
extern const char AndroidSwitchComponentName[];
|
||||
|
||||
/*
|
||||
|
||||
+1
@@ -35,6 +35,7 @@
|
||||
namespace facebook::react {
|
||||
using Content = ParagraphShadowNode::Content;
|
||||
|
||||
// NOLINTNEXTLINE(facebook-hte-CArray)
|
||||
const char ParagraphComponentName[] = "Paragraph";
|
||||
|
||||
void ParagraphShadowNode::initialize() noexcept {
|
||||
|
||||
@@ -16,6 +16,7 @@ androidx-swiperefreshlayout = "1.1.0"
|
||||
androidx-test = "1.5.0"
|
||||
androidx-test-junit = "1.2.1"
|
||||
androidx-tracing = "1.1.0"
|
||||
androidx-window = "1.4.0"
|
||||
assertj = "3.21.0"
|
||||
binary-compatibility-validator = "0.13.2"
|
||||
download = "5.4.0"
|
||||
@@ -64,6 +65,7 @@ androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-
|
||||
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test" }
|
||||
androidx-tracing = { module = "androidx.tracing:tracing", version.ref = "androidx-tracing" }
|
||||
androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" }
|
||||
androidx-window = { module = "androidx.window:window", version.ref = "androidx-window" }
|
||||
|
||||
fbjni = { module = "com.facebook.fbjni:fbjni", version.ref = "fbjni" }
|
||||
fresco = { module = "com.facebook.fresco:fresco", version.ref = "fresco" }
|
||||
|
||||
@@ -377,7 +377,7 @@ end
|
||||
|
||||
# This method can be used to set the fast_float config
|
||||
# that can be used to configure libraries.
|
||||
def set_fast_float_config(fmt_config)
|
||||
def set_fast_float_config(fast_float_config)
|
||||
Helpers::Constants.set_fast_float_config(fast_float_config)
|
||||
end
|
||||
|
||||
|
||||
+253
-2
@@ -466,7 +466,6 @@ describe('Native Animated', () => {
|
||||
{type: 'addition', input: expect.any(Array)},
|
||||
);
|
||||
const additionCalls =
|
||||
// $FlowFixMe[prop-missing]
|
||||
// $FlowFixMe[prop-missing]
|
||||
NativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
||||
call => call[1].type === 'addition',
|
||||
@@ -475,7 +474,6 @@ describe('Native Animated', () => {
|
||||
const additionCall = additionCalls[0];
|
||||
const additionNodeTag = additionCall[0];
|
||||
const additionConnectionCalls =
|
||||
// $FlowFixMe[prop-missing]
|
||||
// $FlowFixMe[prop-missing]
|
||||
NativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
|
||||
call => call[1] === additionNodeTag,
|
||||
@@ -1505,5 +1503,258 @@ describe('Native Animated', () => {
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
it('creates new props, style and transform nodes at update', async () => {
|
||||
const {Animated} = importModules();
|
||||
|
||||
const opacityA = new Animated.Value(0, {
|
||||
debugID: 'opacityA',
|
||||
useNativeDriver: true,
|
||||
});
|
||||
const opacityB = new Animated.Value(0, {
|
||||
debugID: 'opacityB',
|
||||
useNativeDriver: true,
|
||||
});
|
||||
opacityA.__makeNative();
|
||||
opacityB.__makeNative();
|
||||
const scaleA = new Animated.Value(0, {
|
||||
debugID: 'scaleA',
|
||||
useNativeDriver: true,
|
||||
});
|
||||
const scaleB = new Animated.Value(0, {
|
||||
debugID: 'scaleB',
|
||||
useNativeDriver: true,
|
||||
});
|
||||
scaleA.__makeNative();
|
||||
scaleB.__makeNative();
|
||||
|
||||
expect(NativeAnimatedModule.createAnimatedNode).not.toHaveBeenCalled();
|
||||
|
||||
// 1. First render, both opacity and transform scale are managed by Animated
|
||||
const root = await create(
|
||||
<Animated.View
|
||||
style={{opacity: opacityA, transform: [{scale: scaleA}]}}
|
||||
/>,
|
||||
);
|
||||
|
||||
let createAnimatedNodeCalledTimes = 0;
|
||||
let dropAnimatedNodeCalledTimes = 0;
|
||||
|
||||
expect(
|
||||
// $FlowFixMe[prop-missing]
|
||||
NativeAnimatedModule.createAnimatedNode.mock.calls.slice(0, 5),
|
||||
).toEqual([
|
||||
[1, {debugID: 'opacityA', offset: 0, type: 'value', value: 0}],
|
||||
[4, {debugID: 'scaleA', offset: 0, type: 'value', value: 0}],
|
||||
[
|
||||
3,
|
||||
{
|
||||
debugID: undefined,
|
||||
transforms: [{nodeTag: 4, property: 'scale', type: 'animated'}],
|
||||
type: 'transform',
|
||||
},
|
||||
],
|
||||
[
|
||||
2,
|
||||
{
|
||||
debugID: undefined,
|
||||
style: {
|
||||
opacity: 1,
|
||||
transform: 3,
|
||||
},
|
||||
type: 'style',
|
||||
},
|
||||
],
|
||||
[5, {debugID: undefined, props: {style: 2}, type: 'props'}],
|
||||
]);
|
||||
|
||||
createAnimatedNodeCalledTimes += 5;
|
||||
|
||||
expect(NativeAnimatedModule.dropAnimatedNode).not.toHaveBeenCalled();
|
||||
expect(NativeAnimatedModule.restoreDefaultValues).not.toHaveBeenCalled();
|
||||
expect(
|
||||
NativeAnimatedModule.disconnectAnimatedNodeFromView,
|
||||
).not.toHaveBeenCalled();
|
||||
expect(
|
||||
NativeAnimatedModule.connectAnimatedNodeToView,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 2. Update the Animated node to control opacity style
|
||||
await update(
|
||||
root,
|
||||
<Animated.View
|
||||
style={{opacity: opacityB, transform: [{scale: scaleA}]}}
|
||||
/>,
|
||||
);
|
||||
jest.runAllTicks();
|
||||
|
||||
expect(
|
||||
// $FlowFixMe[prop-missing]
|
||||
NativeAnimatedModule.createAnimatedNode.mock.calls.slice(5, 9),
|
||||
).toEqual([
|
||||
[6, {debugID: 'opacityB', offset: 0, type: 'value', value: 0}],
|
||||
// transform node is still recreated even though `scale` is unchanged
|
||||
[
|
||||
8,
|
||||
{
|
||||
debugID: undefined,
|
||||
transforms: [{nodeTag: 4, property: 'scale', type: 'animated'}],
|
||||
type: 'transform',
|
||||
},
|
||||
],
|
||||
[
|
||||
7,
|
||||
{
|
||||
debugID: undefined,
|
||||
style: {
|
||||
opacity: 6,
|
||||
transform: 8,
|
||||
},
|
||||
type: 'style',
|
||||
},
|
||||
],
|
||||
[9, {debugID: undefined, props: {style: 7}, type: 'props'}],
|
||||
]);
|
||||
|
||||
createAnimatedNodeCalledTimes += 4;
|
||||
|
||||
expect(
|
||||
// $FlowFixMe[prop-missing]
|
||||
NativeAnimatedModule.dropAnimatedNode.mock.calls.slice(0, 4),
|
||||
).toEqual([[1], [3], [2], [5]]);
|
||||
|
||||
dropAnimatedNodeCalledTimes += 4;
|
||||
|
||||
expect(NativeAnimatedModule.createAnimatedNode).toHaveBeenCalledTimes(
|
||||
createAnimatedNodeCalledTimes,
|
||||
);
|
||||
expect(NativeAnimatedModule.dropAnimatedNode).toHaveBeenCalledTimes(
|
||||
dropAnimatedNodeCalledTimes,
|
||||
);
|
||||
|
||||
expect(NativeAnimatedModule.restoreDefaultValues).toHaveBeenCalledTimes(
|
||||
1,
|
||||
);
|
||||
expect(
|
||||
NativeAnimatedModule.disconnectAnimatedNodeFromView,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
expect(
|
||||
NativeAnimatedModule.connectAnimatedNodeToView,
|
||||
).toHaveBeenCalledTimes(2);
|
||||
|
||||
// 3. Opacity is controlled by React while transform scale is still managed by Animated
|
||||
await update(
|
||||
root,
|
||||
<Animated.View style={{opacity: 1, transform: [{scale: scaleB}]}} />,
|
||||
);
|
||||
jest.runAllTicks();
|
||||
|
||||
expect(
|
||||
// $FlowFixMe[prop-missing]
|
||||
NativeAnimatedModule.createAnimatedNode.mock.calls.slice(9, 13),
|
||||
).toEqual([
|
||||
[10, {debugID: 'scaleB', offset: 0, type: 'value', value: 0}],
|
||||
[
|
||||
11,
|
||||
{
|
||||
debugID: undefined,
|
||||
transforms: [{nodeTag: 10, property: 'scale', type: 'animated'}],
|
||||
type: 'transform',
|
||||
},
|
||||
],
|
||||
[
|
||||
12,
|
||||
{
|
||||
debugID: undefined,
|
||||
style: {
|
||||
transform: 11,
|
||||
},
|
||||
type: 'style',
|
||||
},
|
||||
],
|
||||
[13, {debugID: undefined, props: {style: 12}, type: 'props'}],
|
||||
]);
|
||||
|
||||
createAnimatedNodeCalledTimes += 4;
|
||||
|
||||
expect(
|
||||
// $FlowFixMe[prop-missing]
|
||||
NativeAnimatedModule.dropAnimatedNode.mock.calls.slice(4, 9),
|
||||
).toEqual([[6], [4], [8], [7], [9]]);
|
||||
|
||||
dropAnimatedNodeCalledTimes += 5;
|
||||
|
||||
expect(NativeAnimatedModule.createAnimatedNode).toHaveBeenCalledTimes(
|
||||
createAnimatedNodeCalledTimes,
|
||||
);
|
||||
expect(NativeAnimatedModule.dropAnimatedNode).toHaveBeenCalledTimes(
|
||||
dropAnimatedNodeCalledTimes,
|
||||
);
|
||||
expect(NativeAnimatedModule.restoreDefaultValues).toHaveBeenCalledTimes(
|
||||
2,
|
||||
);
|
||||
expect(
|
||||
NativeAnimatedModule.disconnectAnimatedNodeFromView,
|
||||
).toHaveBeenCalledTimes(2);
|
||||
expect(
|
||||
NativeAnimatedModule.connectAnimatedNodeToView,
|
||||
).toHaveBeenCalledTimes(3);
|
||||
|
||||
// 4. Both opacity and transform scale are controlled by React instead of Animated
|
||||
await update(
|
||||
root,
|
||||
<Animated.View style={{opacity: 1, transform: [{scale: 1}]}} />,
|
||||
);
|
||||
jest.runAllTicks();
|
||||
|
||||
{
|
||||
const droppedTags = [10, 11, 12, 13];
|
||||
for (let i = 0; i < droppedTags.length; i++) {
|
||||
expect(NativeAnimatedModule.dropAnimatedNode).toHaveBeenNthCalledWith(
|
||||
i + dropAnimatedNodeCalledTimes + 1,
|
||||
droppedTags[i],
|
||||
);
|
||||
}
|
||||
|
||||
dropAnimatedNodeCalledTimes += droppedTags.length;
|
||||
}
|
||||
expect(NativeAnimatedModule.createAnimatedNode).toHaveBeenCalledTimes(
|
||||
createAnimatedNodeCalledTimes,
|
||||
);
|
||||
expect(NativeAnimatedModule.dropAnimatedNode).toHaveBeenCalledTimes(
|
||||
dropAnimatedNodeCalledTimes,
|
||||
);
|
||||
|
||||
// View is no longer connected to PropsAnimatedNode
|
||||
expect(
|
||||
NativeAnimatedModule.disconnectAnimatedNodeFromView,
|
||||
).toHaveBeenCalledTimes(3);
|
||||
expect(
|
||||
NativeAnimatedModule.connectAnimatedNodeToView,
|
||||
).toHaveBeenCalledTimes(3);
|
||||
expect(NativeAnimatedModule.restoreDefaultValues).toHaveBeenCalledTimes(
|
||||
3,
|
||||
);
|
||||
|
||||
// 5. Unmount
|
||||
await unmount(root);
|
||||
jest.runAllTicks();
|
||||
// No change for Animated nodes on unmount.
|
||||
expect(NativeAnimatedModule.createAnimatedNode).toHaveBeenCalledTimes(
|
||||
createAnimatedNodeCalledTimes,
|
||||
);
|
||||
expect(NativeAnimatedModule.dropAnimatedNode).toHaveBeenCalledTimes(
|
||||
dropAnimatedNodeCalledTimes,
|
||||
);
|
||||
expect(
|
||||
NativeAnimatedModule.disconnectAnimatedNodeFromView,
|
||||
).toHaveBeenCalledTimes(3);
|
||||
expect(
|
||||
NativeAnimatedModule.connectAnimatedNodeToView,
|
||||
).toHaveBeenCalledTimes(3);
|
||||
expect(NativeAnimatedModule.restoreDefaultValues).toHaveBeenCalledTimes(
|
||||
3,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -451,6 +451,19 @@ const examples: Array<RNTesterModuleExample> = [
|
||||
return <ToggleDefaultPaddingExample />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Accessibility',
|
||||
render: function (): React.Node {
|
||||
return (
|
||||
<View>
|
||||
<Text>accessibilityLabel prop</Text>
|
||||
<ExampleTextInput accessibilityLabel="This is Accessibility Label" />
|
||||
<Text>aria-label prop</Text>
|
||||
<ExampleTextInput aria-label="This is Aria Label" />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = ({
|
||||
|
||||
@@ -1036,6 +1036,21 @@ const textInputExamples: Array<RNTesterModuleExample> = [
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Accessibility',
|
||||
render: function (): React.Node {
|
||||
return (
|
||||
<View>
|
||||
<WithLabel label="accessibilityLabel">
|
||||
<ExampleTextInput accessibilityLabel="This is Accessibility Label" />
|
||||
</WithLabel>
|
||||
<WithLabel label="aria-label">
|
||||
<ExampleTextInput aria-label="This is Aria Label" />
|
||||
</WithLabel>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = ({
|
||||
|
||||
+19
-22
@@ -8,6 +8,8 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {markdownTable} from './utils';
|
||||
|
||||
type TestTaskTiming = {
|
||||
name: string,
|
||||
latency: {
|
||||
@@ -31,7 +33,7 @@ export const printBenchmarkResultsRanking = (
|
||||
testArtifact: mixed,
|
||||
}>,
|
||||
) => {
|
||||
const testTaskTimings: {[string]: Array<[string, number]>} = {};
|
||||
const testTaskTimings: {[string]: {[string]: number}} = {};
|
||||
let numTestVariants = 0;
|
||||
|
||||
for (const testResult of testResults) {
|
||||
@@ -49,12 +51,9 @@ export const printBenchmarkResultsRanking = (
|
||||
for (const taskTiming of testArtifact.timings) {
|
||||
const taskName = taskTiming.name;
|
||||
if (testTaskTimings[taskName] === undefined) {
|
||||
testTaskTimings[taskName] = [];
|
||||
testTaskTimings[taskName] = {};
|
||||
}
|
||||
testTaskTimings[taskName].push([
|
||||
testResult.title,
|
||||
taskTiming.latency.p50,
|
||||
]);
|
||||
testTaskTimings[taskName][testResult.title] = taskTiming.latency.p50;
|
||||
}
|
||||
}
|
||||
if (numTestVariants <= 1 || Object.keys(testTaskTimings).length === 0) {
|
||||
@@ -62,25 +61,23 @@ export const printBenchmarkResultsRanking = (
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by each task's execution times
|
||||
// Find relative execution times for tasks
|
||||
const results: {[string]: {[string]: string}} = {};
|
||||
for (const taskName in testTaskTimings) {
|
||||
testTaskTimings[taskName].sort((a, b) => a[1] - b[1]);
|
||||
const kv = Object.entries(testTaskTimings[taskName]);
|
||||
kv.sort((a, b) => a[1] - b[1]);
|
||||
const bestTiming = kv[0][1];
|
||||
results[taskName] = {};
|
||||
kv.forEach(([key, val]) => {
|
||||
results[taskName][key] =
|
||||
`${val.toFixed(3)}ms ${getTimingDelta(bestTiming, val)}`;
|
||||
});
|
||||
results[taskName][kv[0][0]] = `🏆 ${bestTiming.toFixed(3)}ms`;
|
||||
}
|
||||
|
||||
// Print the rankings
|
||||
console.log('### Benchmark Results Ranking ###');
|
||||
for (const taskName in testTaskTimings) {
|
||||
console.log(`> ${taskName}:`);
|
||||
let lastTiming;
|
||||
for (const [i, [testVariationName, latency]] of testTaskTimings[
|
||||
taskName
|
||||
].entries()) {
|
||||
console.log(
|
||||
` ${i + 1}. ${testVariationName}: ${latency.toFixed(2)}ms ${getTimingDelta(lastTiming, latency)}`,
|
||||
);
|
||||
lastTiming = latency;
|
||||
}
|
||||
}
|
||||
console.log('### Benchmark Times Comparison (p50): ###');
|
||||
console.log(markdownTable(results, 'Task name'));
|
||||
console.log('');
|
||||
};
|
||||
|
||||
function getTimingDelta(lastTiming: ?number, currentTiming: ?number): string {
|
||||
|
||||
+61
@@ -371,3 +371,64 @@ export function printConsoleLog(log: ConsoleLogMessage): void {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a markdown table corresponding to the given data, adopted from the RN console.table polyfill implementation
|
||||
export function markdownTable(
|
||||
data: {[string]: {[string]: string}},
|
||||
indexColumnName?: string = '',
|
||||
): string {
|
||||
const repeat = (element: string, n: number) =>
|
||||
Array.apply(null, Array(n)).map(() => element);
|
||||
|
||||
const rows = Object.keys(data).map((key: string) => ({
|
||||
[indexColumnName]: key,
|
||||
...data[key],
|
||||
}));
|
||||
|
||||
if (rows.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const columns = Array.from(
|
||||
rows.reduce((columnSet: Set<string>, row) => {
|
||||
Object.keys(row).forEach(key => columnSet.add(key));
|
||||
return columnSet;
|
||||
}, new Set()),
|
||||
);
|
||||
const stringRows: Array<Array<string>> = [];
|
||||
const columnWidths = [];
|
||||
|
||||
// Figure out max cell width for each column
|
||||
columns.forEach((k, i) => {
|
||||
columnWidths[i] = k.length;
|
||||
for (let j = 0; j < rows.length; j++) {
|
||||
const cellStr = rows[j][k];
|
||||
stringRows[j] = stringRows[j] || [];
|
||||
stringRows[j][i] = cellStr;
|
||||
columnWidths[i] = Math.max(columnWidths[i], cellStr.length);
|
||||
}
|
||||
});
|
||||
|
||||
// Join all elements in the row into a single string with | separators
|
||||
// (appends extra spaces to each cell to make separators | aligned)
|
||||
const joinRow = (row: Array<string>, space?: string = ' ') => {
|
||||
const cells = row.map((cell: string, i) => {
|
||||
const extraSpaces = repeat(' ', columnWidths[i] - cell.length).join('');
|
||||
return cell + extraSpaces;
|
||||
});
|
||||
return '| ' + cells.join(space + '|' + space) + ' |';
|
||||
};
|
||||
|
||||
const separators = columnWidths.map(columnWidth =>
|
||||
repeat('-', columnWidth).join(''),
|
||||
);
|
||||
const separatorRow = joinRow(separators);
|
||||
const header = joinRow(columns);
|
||||
const table = [header, separatorRow];
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
table.push(joinRow(stringRows[i]));
|
||||
}
|
||||
|
||||
return '\n' + table.join('\n');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user