mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
95
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0411eebe2 | ||
|
|
342e3ec530 | ||
|
|
5446d8c701 | ||
|
|
cd7cf07d32 | ||
|
|
fb7f87ecb2 | ||
|
|
73ffe1394f | ||
|
|
e6374c6e60 | ||
|
|
07b7953af1 | ||
|
|
30c3912eed | ||
|
|
36ac19442a | ||
|
|
8428a98c9a | ||
|
|
4ca9d72eab | ||
|
|
ca3cddb636 | ||
|
|
f8654f9540 | ||
|
|
fcbcf80d1c | ||
|
|
93e9d5794e | ||
|
|
08976e46da | ||
|
|
94e2b5b1b4 | ||
|
|
e0374f2199 | ||
|
|
d1ce8fafb6 | ||
|
|
d105c2c6fe | ||
|
|
7ea8e50c36 | ||
|
|
30f208eb2b | ||
|
|
a0be560dbf | ||
|
|
bb29d379f0 | ||
|
|
14185f2666 | ||
|
|
43fe69c315 | ||
|
|
3cedb09a65 | ||
|
|
33fce4488c | ||
|
|
5d82c32de7 | ||
|
|
3b64ed0097 | ||
|
|
5b2bbb84b1 | ||
|
|
304179d297 | ||
|
|
73f6277175 | ||
|
|
bc04bb4072 | ||
|
|
9f1c6bcc13 | ||
|
|
a1ac30193d | ||
|
|
0e8769e7cd | ||
|
|
abbe117a7b | ||
|
|
2d337efc23 | ||
|
|
3287014ee9 | ||
|
|
605e2e443b | ||
|
|
d8b727c6bf | ||
|
|
e70cad24f0 | ||
|
|
9946838bed | ||
|
|
08b8300548 | ||
|
|
d01d01464b | ||
|
|
ac61c14b58 | ||
|
|
81737c2b99 | ||
|
|
ff1261e7dc | ||
|
|
1e659dc44e | ||
|
|
9f65442f2d | ||
|
|
c4fd80f414 | ||
|
|
e9fc092156 | ||
|
|
e8c4faaf08 | ||
|
|
fbe38bb2a3 | ||
|
|
40093d96d1 | ||
|
|
6f9ddd8f89 | ||
|
|
3ce4b80e6f | ||
|
|
a09df751eb | ||
|
|
94d4bfd7c8 | ||
|
|
a35852f976 | ||
|
|
51b98c24bd | ||
|
|
bbe5e72768 | ||
|
|
dac6d508af | ||
|
|
0def73d1a6 | ||
|
|
4fc2c8fd1f | ||
|
|
b048659ceb | ||
|
|
201b517de0 | ||
|
|
980f4b42ca | ||
|
|
621d4ee298 | ||
|
|
e8776240b4 | ||
|
|
defc0c8c21 | ||
|
|
e56bd89eff | ||
|
|
807500a63e | ||
|
|
bea4535246 | ||
|
|
699a94d938 | ||
|
|
2e10ba945f | ||
|
|
007a8e12b8 | ||
|
|
1be8c51173 | ||
|
|
9f9e1a41ca | ||
|
|
c967deaa2d | ||
|
|
55671c00e5 | ||
|
|
ce1620616c | ||
|
|
ab0d812cc6 | ||
|
|
5e2f3e018c | ||
|
|
45ae0b44d5 | ||
|
|
02b879b1e2 | ||
|
|
788dd2e681 | ||
|
|
3f8d1fa286 | ||
|
|
066128321d | ||
|
|
60a2706e97 | ||
|
|
d91a12bc8b | ||
|
|
111d013c03 | ||
|
|
7e14ec5177 |
@@ -43,9 +43,6 @@ runs:
|
||||
shell: powershell
|
||||
run: |
|
||||
if (-not(Test-Path -Path $Env:HERMES_WS_DIR\win64-bin\hermesc.exe)) {
|
||||
choco install --no-progress cmake --version 3.14.7
|
||||
if (-not $?) { throw "Failed to install CMake" }
|
||||
|
||||
cd $Env:HERMES_WS_DIR\icu
|
||||
# If Invoke-WebRequest shows a progress bar, it will fail with
|
||||
# Win32 internal error "Access is denied" 0x5 occurred [...]
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
const {
|
||||
publishTemplate,
|
||||
verifyPublishedTemplate,
|
||||
} = require('../publishTemplate');
|
||||
|
||||
const mockRun = jest.fn();
|
||||
const mockSleep = jest.fn();
|
||||
const mockGetNpmPackageInfo = jest.fn();
|
||||
const silence = () => {};
|
||||
|
||||
jest.mock('../utils.js', () => ({
|
||||
log: silence,
|
||||
run: mockRun,
|
||||
sleep: mockSleep,
|
||||
getNpmPackageInfo: mockGetNpmPackageInfo,
|
||||
}));
|
||||
|
||||
const getMockGithub = () => ({
|
||||
rest: {
|
||||
actions: {
|
||||
createWorkflowDispatch: jest.fn(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('#publishTemplate', () => {
|
||||
beforeEach(jest.clearAllMocks);
|
||||
|
||||
it('checks commits for magic #publish-package-to-npm&latest string and sets latest', async () => {
|
||||
mockRun.mockReturnValueOnce(`
|
||||
The commit message
|
||||
|
||||
#publish-packages-to-npm&latest`);
|
||||
|
||||
const github = getMockGithub();
|
||||
await publishTemplate(github, '0.76.0', true);
|
||||
expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith({
|
||||
owner: 'react-native-community',
|
||||
repo: 'template',
|
||||
workflow_id: 'release.yaml',
|
||||
ref: '0.76-stable',
|
||||
inputs: {
|
||||
dry_run: true,
|
||||
is_latest_on_npm: true,
|
||||
version: '0.76.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('pubished as is_latest_on_npm = false if missing magic string', async () => {
|
||||
mockRun.mockReturnValueOnce(`
|
||||
The commit message without magic
|
||||
`);
|
||||
|
||||
const github = getMockGithub();
|
||||
await publishTemplate(github, '0.76.0', false);
|
||||
expect(github.rest.actions.createWorkflowDispatch).toHaveBeenCalledWith({
|
||||
owner: 'react-native-community',
|
||||
repo: 'template',
|
||||
workflow_id: 'release.yaml',
|
||||
ref: '0.76-stable',
|
||||
inputs: {
|
||||
dry_run: false,
|
||||
is_latest_on_npm: false,
|
||||
version: '0.76.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#verifyPublishedTemplate', () => {
|
||||
beforeEach(jest.clearAllMocks);
|
||||
|
||||
it("fixes versions prefixed with 'v'", async () => {
|
||||
const dirtyVersion = 'v0.76.0';
|
||||
const cleanVersion = '0.76.0';
|
||||
await verifyPublishedTemplate(dirtyVersion);
|
||||
|
||||
expect(mockGetNpmPackageInfo).toHaveBeenLastCalledWith(
|
||||
'@react-native-community/template',
|
||||
cleanVersion,
|
||||
);
|
||||
});
|
||||
|
||||
it("waits on npm updating for version and not 'latest'", async () => {
|
||||
const NOT_LATEST = false;
|
||||
mockGetNpmPackageInfo
|
||||
// template@<version>
|
||||
.mockReturnValueOnce(Promise.reject('mock http/404'))
|
||||
.mockReturnValueOnce(Promise.resolve());
|
||||
mockSleep.mockReturnValueOnce(Promise.resolve()).mockImplementation(() => {
|
||||
throw new Error('Should not be called again!');
|
||||
});
|
||||
|
||||
const version = '0.77.0';
|
||||
await verifyPublishedTemplate(version, NOT_LATEST);
|
||||
|
||||
expect(mockGetNpmPackageInfo).toHaveBeenLastCalledWith(
|
||||
'@react-native-community/template',
|
||||
version,
|
||||
);
|
||||
});
|
||||
|
||||
it('waits on npm updating version and latest tag', async () => {
|
||||
const IS_LATEST = true;
|
||||
const version = '0.77.0';
|
||||
mockGetNpmPackageInfo
|
||||
// template@latest → unknown tag
|
||||
.mockReturnValueOnce(Promise.reject('mock http/404'))
|
||||
// template@latest != version → old tag
|
||||
.mockReturnValueOnce(Promise.resolve({version: '0.76.5'}))
|
||||
// template@latest == version → correct tag
|
||||
.mockReturnValueOnce(Promise.resolve({version}));
|
||||
mockSleep
|
||||
.mockReturnValueOnce(Promise.resolve())
|
||||
.mockReturnValueOnce(Promise.resolve())
|
||||
.mockImplementation(() => {
|
||||
throw new Error('Should not be called again!');
|
||||
});
|
||||
|
||||
await verifyPublishedTemplate(version, IS_LATEST);
|
||||
|
||||
expect(mockGetNpmPackageInfo).toHaveBeenCalledWith(
|
||||
'@react-native-community/template',
|
||||
'latest',
|
||||
);
|
||||
});
|
||||
|
||||
describe('timeouts', () => {
|
||||
let mockProcess;
|
||||
beforeEach(() => {
|
||||
mockProcess = jest.spyOn(process, 'exit').mockImplementation(code => {
|
||||
throw new Error(`process.exit(${code}) called!`);
|
||||
});
|
||||
});
|
||||
afterEach(() => mockProcess.mockRestore());
|
||||
it('will timeout if npm does not update package version after a set number of retries', async () => {
|
||||
const RETRIES = 2;
|
||||
mockGetNpmPackageInfo.mockReturnValue(Promise.reject('mock http/404'));
|
||||
mockSleep.mockReturnValue(Promise.resolve());
|
||||
await expect(() =>
|
||||
verifyPublishedTemplate('0.77.0', true, RETRIES),
|
||||
).rejects.toThrowError('process.exit(1) called!');
|
||||
expect(mockGetNpmPackageInfo).toHaveBeenCalledTimes(RETRIES);
|
||||
});
|
||||
|
||||
it('will timeout if npm does not update latest tag after a set number of retries', async () => {
|
||||
const RETRIES = 7;
|
||||
const IS_LATEST = true;
|
||||
mockGetNpmPackageInfo.mockReturnValue(
|
||||
Promise.resolve({version: '0.76.5'}),
|
||||
);
|
||||
mockSleep.mockReturnValue(Promise.resolve());
|
||||
await expect(async () => {
|
||||
await verifyPublishedTemplate('0.77.0', IS_LATEST, RETRIES);
|
||||
}).rejects.toThrowError('process.exit(1) called!');
|
||||
expect(mockGetNpmPackageInfo).toHaveBeenCalledTimes(RETRIES);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
const {run, sleep, getNpmPackageInfo, log} = require('./utils.js');
|
||||
|
||||
const TAG_AS_LATEST_REGEX = /#publish-packages-to-npm&latest/;
|
||||
|
||||
/**
|
||||
* Should this commit be `latest` on npm?
|
||||
*/
|
||||
function isLatest() {
|
||||
const commitMessage = run('git log -n1 --pretty=%B');
|
||||
return TAG_AS_LATEST_REGEX.test(commitMessage);
|
||||
}
|
||||
module.exports.isLatest = isLatest;
|
||||
|
||||
/**
|
||||
* Create a Github Action to publish the community template matching the released version
|
||||
* of React Native.
|
||||
*/
|
||||
module.exports.publishTemplate = async (github, version, dryRun = true) => {
|
||||
log(`📤 Get the ${TEMPLATE_NPM_PKG} repo to publish ${version}`);
|
||||
|
||||
const is_latest_on_npm = isLatest();
|
||||
|
||||
const majorMinor = /^v?(\d+\.\d+)/.exec(version);
|
||||
|
||||
if (!majorMinor) {
|
||||
log(`🔥 can't capture MAJOR.MINOR from '${version}', giving up.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// MAJOR.MINOR-stable
|
||||
const ref = `${majorMinor[1]}-stable`;
|
||||
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
owner: 'react-native-community',
|
||||
repo: 'template',
|
||||
workflow_id: 'release.yaml',
|
||||
ref,
|
||||
inputs: {
|
||||
dry_run: dryRun,
|
||||
is_latest_on_npm,
|
||||
// 0.75.0-rc.0, note no 'v' prefix
|
||||
version: version.replace(/^v/, ''),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const SLEEP_S = 10;
|
||||
const MAX_RETRIES = 3 * 6; // 3 minutes
|
||||
const TEMPLATE_NPM_PKG = '@react-native-community/template';
|
||||
|
||||
/**
|
||||
* Will verify that @latest and the @<version> have been published.
|
||||
*
|
||||
* NOTE: This will infinitely query each step until successful, make sure the
|
||||
* calling job has a timeout.
|
||||
*/
|
||||
module.exports.verifyPublishedTemplate = async (
|
||||
version,
|
||||
latest = false,
|
||||
retries = MAX_RETRIES,
|
||||
) => {
|
||||
version = version.replace(/^v/, '');
|
||||
log(`🔍 Is ${TEMPLATE_NPM_PKG}@${version} on npm?`);
|
||||
|
||||
let count = retries;
|
||||
while (count-- > 0) {
|
||||
try {
|
||||
const json = await getNpmPackageInfo(
|
||||
TEMPLATE_NPM_PKG,
|
||||
latest ? 'latest' : version,
|
||||
);
|
||||
log(`🎉 Found ${TEMPLATE_NPM_PKG}@${version} on npm`);
|
||||
if (!latest) {
|
||||
return;
|
||||
}
|
||||
if (json.version === version) {
|
||||
log(`🎉 ${TEMPLATE_NPM_PKG}@latest → ${json.version} on npm`);
|
||||
return;
|
||||
}
|
||||
log(
|
||||
`🐌 ${TEMPLATE_NPM_PKG}@latest → ${json.version} on npm and not ${version} as expected, retrying...`,
|
||||
);
|
||||
} catch (e) {
|
||||
log(`Nope, fetch failed: ${e.message}`);
|
||||
}
|
||||
await sleep(SLEEP_S);
|
||||
}
|
||||
|
||||
let msg = `🚨 Timed out when trying to verify ${TEMPLATE_NPM_PKG}@${version} on npm`;
|
||||
if (latest) {
|
||||
msg += ' and latest tag points to this version.';
|
||||
}
|
||||
log(msg);
|
||||
process.exit(1);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
const {execSync} = require('child_process');
|
||||
|
||||
function run(cmd) {
|
||||
return execSync(cmd, 'utf8').toString().trim();
|
||||
}
|
||||
module.exports.run = run;
|
||||
|
||||
async function sleep(seconds) {
|
||||
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
|
||||
}
|
||||
module.exports.sleep = sleep;
|
||||
|
||||
async function getNpmPackageInfo(pkg, versionOrTag) {
|
||||
return fetch(`https://registry.npmjs.org/${pkg}/${versionOrTag}`).then(resp =>
|
||||
resp.json(),
|
||||
);
|
||||
}
|
||||
module.exports.getNpmPackageInfo = getNpmPackageInfo;
|
||||
|
||||
module.exports.log = (...args) => console.log(...args);
|
||||
@@ -191,46 +191,23 @@ jobs:
|
||||
gha-npm-token: ${{ env.GHA_NPM_TOKEN }}
|
||||
- name: Publish @react-native-community/template
|
||||
id: publish-template-to-npm
|
||||
shell: bash
|
||||
run: |
|
||||
COMMIT_MSG=$(git log -n1 --pretty=%B);
|
||||
if grep -q '#publish-packages-to-npm&latest' <<< "$COMMIT_MSG"; then
|
||||
echo "TAG=latest" >> $GITHUB_OUTPUT
|
||||
IS_LATEST=true
|
||||
else
|
||||
IS_LATEST=false
|
||||
fi
|
||||
# Go from v0.75.0-rc.4 -> 0.75-stable, which is the template's branching scheme
|
||||
VERSION=$(grep -oE '\d+\.\d+' <<< "${{ github.ref_name }}" | { read version; echo "$version-stable"; })
|
||||
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
curl -L https://api.github.com/repos/react-native-community/template/actions/workflows/release.yaml/dispatches
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: Bearer $REACT_NATIVE_BOT_GITHUB_TOKEN" \
|
||||
-d "{\"ref\":\"$VERSION\",\"inputs\":{\"version\":\"${{ github.ref_name }}\",\"is_latest_on_npm\":\"$IS_LATEST\"}}"
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }}
|
||||
script: |
|
||||
const {publishTemplate} = require('./.github/workflow-scripts/publishTemplate.js')
|
||||
const version = "${{ github.ref_name }}"
|
||||
const isDryRun = false
|
||||
await publishTemplate(github, version, isDryRun);
|
||||
- name: Wait for template to be published
|
||||
timeout-minutes: 3
|
||||
env:
|
||||
VERSION: ${{ steps.publish-template-to-npm.outputs.VERSION }}
|
||||
TAG: ${{ steps.publish-template-to-npm.outputs.TAG }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Waiting until @react-native-community/template is published to npm"
|
||||
while true; do
|
||||
if curl -o /dev/null -s -f "https://registry.npmjs.org/@react-native-community/template/$VERSION"; then
|
||||
echo "Confirm that @react-native-community/template@$VERSION is published on npm"
|
||||
break
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
while [ "$TAG" == "latest" ]; do
|
||||
CURRENT=$(curl -s "https://registry.npmjs.org/react-native/latest" | jq -r '.version');
|
||||
if [ "$CURRENT" == "$VERSION" ]; then
|
||||
echo "Confirm that @react-native-community/template@latest == $VERSION on npm"
|
||||
break
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }}
|
||||
script: |
|
||||
const {verifyPublishedTemplate, isLatest} = require('./.github/workflow-scripts/publishTemplate.js')
|
||||
const version = "${{ github.ref_name }}"
|
||||
await verifyPublishedTemplate(version, isLatest());
|
||||
- name: Update rn-diff-purge to generate upgrade-support diff
|
||||
run: |
|
||||
curl -X POST https://api.github.com/repos/react-native-community/rn-diff-purge/dispatches \
|
||||
|
||||
@@ -5,3 +5,4 @@ ruby ">= 2.6.10"
|
||||
|
||||
gem 'cocoapods', '~> 1.13', '!= 1.15.0', '!= 1.15.1'
|
||||
gem 'activesupport', '>= 6.1.7.5', '< 7.1.0'
|
||||
gem 'xcodeproj', '< 1.26.0'
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
+4
-4
@@ -49,8 +49,8 @@
|
||||
"@definitelytyped/dtslint": "^0.0.127",
|
||||
"@jest/create-cache-key-function": "^29.6.3",
|
||||
"@pkgjs/parseargs": "^0.11.0",
|
||||
"@react-native/metro-babel-transformer": "0.76.0-rc.4",
|
||||
"@react-native/metro-config": "0.76.0-rc.4",
|
||||
"@react-native/metro-babel-transformer": "0.76.6",
|
||||
"@react-native/metro-config": "0.76.6",
|
||||
"@tsconfig/node18": "1.0.1",
|
||||
"@types/react": "^18.2.6",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
@@ -86,8 +86,8 @@
|
||||
"jest": "^29.6.3",
|
||||
"jest-junit": "^10.0.0",
|
||||
"jscodeshift": "^0.14.0",
|
||||
"metro-babel-register": "^0.81.0-alpha.2",
|
||||
"metro-memory-fs": "^0.81.0-alpha.2",
|
||||
"metro-babel-register": "^0.81.0",
|
||||
"metro-memory-fs": "^0.81.0",
|
||||
"micromatch": "^4.0.4",
|
||||
"mkdirp": "^0.5.1",
|
||||
"node-fetch": "^2.2.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/assets-registry",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "Asset support code for React Native.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/babel-plugin-codegen",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "Babel plugin to generate native module and view manager code for React Native.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
@@ -25,7 +25,7 @@
|
||||
"index.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@react-native/codegen": "0.76.0-rc.4"
|
||||
"@react-native/codegen": "0.76.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/community-cli-plugin",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "Core CLI commands for React Native",
|
||||
"keywords": [
|
||||
"react-native",
|
||||
@@ -22,19 +22,20 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@react-native/dev-middleware": "0.76.0-rc.4",
|
||||
"@react-native/metro-babel-transformer": "0.76.0-rc.4",
|
||||
"@react-native/dev-middleware": "0.76.6",
|
||||
"@react-native/metro-babel-transformer": "0.76.6",
|
||||
"chalk": "^4.0.0",
|
||||
"execa": "^5.1.1",
|
||||
"invariant": "^2.2.4",
|
||||
"metro": "^0.81.0-alpha.2",
|
||||
"metro-config": "^0.81.0-alpha.2",
|
||||
"metro-core": "^0.81.0-alpha.2",
|
||||
"metro": "^0.81.0",
|
||||
"metro-config": "^0.81.0",
|
||||
"metro-core": "^0.81.0",
|
||||
"node-fetch": "^2.2.0",
|
||||
"readline": "^1.3.0"
|
||||
"readline": "^1.3.0",
|
||||
"semver": "^7.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"metro-resolver": "^0.81.0-alpha.2"
|
||||
"metro-resolver": "^0.81.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-native-community/cli-server-api": "*"
|
||||
|
||||
@@ -140,7 +140,7 @@ async function buildBundleWithConfig(
|
||||
args.assetCatalogDest,
|
||||
);
|
||||
} finally {
|
||||
server.end();
|
||||
await server.end();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/core-cli-utils",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "React Native CLI library for Frameworks to build on",
|
||||
"license": "MIT",
|
||||
"main": "./src/index.flow.js",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@generated SignedSource<<2ae3cc4cc779b7d9b387f7d8ba402248>>
|
||||
Git revision: ce5d32a14f18a8fc4bdaf787eb46800b7f73e524
|
||||
@generated SignedSource<<e1b6cf83a0e98051a2f929ad191b1d6c>>
|
||||
Git revision: f1f917329169ff3d2c12bcfaea7e301b71c3149e
|
||||
Built with --nohooks: false
|
||||
Is local checkout: false
|
||||
Remote URL: https://github.com/facebookexperimental/rn-chrome-devtools-frontend
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/debugger-frontend",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "Debugger frontend for React Native based on Chrome DevTools",
|
||||
"keywords": [
|
||||
"react-native",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/dev-middleware",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "Dev server middleware for React Native",
|
||||
"keywords": [
|
||||
"react-native",
|
||||
@@ -23,7 +23,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@isaacs/ttlcache": "^1.4.1",
|
||||
"@react-native/debugger-frontend": "0.76.0-rc.4",
|
||||
"@react-native/debugger-frontend": "0.76.6",
|
||||
"chrome-launcher": "^0.15.2",
|
||||
"chromium-edge-launcher": "^0.2.0",
|
||||
"connect": "^3.6.5",
|
||||
|
||||
@@ -39,7 +39,7 @@ module.exports = {
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.js'],
|
||||
parser: 'hermes-eslint',
|
||||
parser: '@babel/eslint-parser',
|
||||
plugins: ['ft-flow'],
|
||||
rules: {
|
||||
// Flow Plugin
|
||||
@@ -51,7 +51,7 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
files: ['*.jsx'],
|
||||
parser: 'hermes-eslint',
|
||||
parser: '@babel/eslint-parser',
|
||||
},
|
||||
{
|
||||
files: ['*.ts', '*.tsx'],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/eslint-config",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "ESLint config for React Native",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
@@ -22,7 +22,7 @@
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/eslint-parser": "^7.25.1",
|
||||
"@react-native/eslint-plugin": "0.76.0-rc.4",
|
||||
"@react-native/eslint-plugin": "0.76.6",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
@@ -31,8 +31,7 @@
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-native": "^4.0.0",
|
||||
"hermes-eslint": "^0.23.1"
|
||||
"eslint-plugin-react-native": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=8",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/eslint-plugin",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "ESLint rules for @react-native/eslint-config",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/eslint-plugin-specs",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "ESLint rules to validate NativeModule and Component Specs",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
@@ -27,7 +27,7 @@
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.25.2",
|
||||
"@babel/preset-flow": "^7.24.7",
|
||||
"@react-native/codegen": "0.76.0-rc.4",
|
||||
"@react-native/codegen": "0.76.6",
|
||||
"make-dir": "^2.1.0",
|
||||
"pirates": "^4.0.1",
|
||||
"source-map-support": "0.5.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/gradle-plugin",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "Gradle Plugin for React Native",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
+7
-3
@@ -12,6 +12,7 @@ import com.facebook.react.utils.detectOSAwareHermesCommand
|
||||
import com.facebook.react.utils.moveTo
|
||||
import com.facebook.react.utils.windowsAwareCommandLine
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileTree
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
@@ -19,6 +20,7 @@ import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.ListProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.*
|
||||
import org.gradle.process.ExecOperations
|
||||
|
||||
abstract class BundleHermesCTask : DefaultTask() {
|
||||
|
||||
@@ -26,6 +28,8 @@ abstract class BundleHermesCTask : DefaultTask() {
|
||||
group = "react"
|
||||
}
|
||||
|
||||
@get:Inject abstract val execOperations: ExecOperations
|
||||
|
||||
@get:Internal abstract val root: DirectoryProperty
|
||||
|
||||
@get:InputFiles
|
||||
@@ -127,9 +131,9 @@ abstract class BundleHermesCTask : DefaultTask() {
|
||||
File(jsIntermediateSourceMapsDir.get().asFile, "$bundleAssetName.compiler.map")
|
||||
|
||||
private fun runCommand(command: List<Any>) {
|
||||
project.exec {
|
||||
it.workingDir(root.get().asFile)
|
||||
it.commandLine(command)
|
||||
execOperations.exec { exec ->
|
||||
exec.workingDir(root.get().asFile)
|
||||
exec.commandLine(command)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-4
@@ -59,15 +59,15 @@ abstract class GenerateAutolinkingNewArchitecturesFileTask : DefaultTask() {
|
||||
val cxxModuleCMakeListsPath = dep.cxxModuleCMakeListsPath
|
||||
if (libraryName != null && cmakeListsPath != null) {
|
||||
// If user provided a custom cmakeListsPath, let's honor it.
|
||||
val nativeFolderPath = cmakeListsPath.replace("CMakeLists.txt", "")
|
||||
val nativeFolderPath = sanitizeCmakeListsPath(cmakeListsPath)
|
||||
addDirectoryString +=
|
||||
"add_subdirectory($nativeFolderPath ${libraryName}_autolinked_build)"
|
||||
"add_subdirectory(\"$nativeFolderPath\" ${libraryName}_autolinked_build)"
|
||||
}
|
||||
if (cxxModuleCMakeListsPath != null) {
|
||||
// If user provided a custom cxxModuleCMakeListsPath, let's honor it.
|
||||
val nativeFolderPath = cxxModuleCMakeListsPath.replace("CMakeLists.txt", "")
|
||||
val nativeFolderPath = sanitizeCmakeListsPath(cxxModuleCMakeListsPath)
|
||||
addDirectoryString +=
|
||||
"\nadd_subdirectory($nativeFolderPath ${libraryName}_cxxmodule_autolinked_build)"
|
||||
"\nadd_subdirectory(\"$nativeFolderPath\" ${libraryName}_cxxmodule_autolinked_build)"
|
||||
}
|
||||
addDirectoryString
|
||||
}
|
||||
@@ -159,6 +159,9 @@ abstract class GenerateAutolinkingNewArchitecturesFileTask : DefaultTask() {
|
||||
const val COMPONENT_DESCRIPTOR_FILENAME = "ComponentDescriptors.h"
|
||||
const val COMPONENT_INCLUDE_PATH = "react/renderer/components"
|
||||
|
||||
internal fun sanitizeCmakeListsPath(cmakeListsPath: String): String =
|
||||
cmakeListsPath.replace("CMakeLists.txt", "").replace(" ", "\\ ")
|
||||
|
||||
// language=cmake
|
||||
val CMAKE_TEMPLATE =
|
||||
"""
|
||||
|
||||
+23
-4
@@ -11,6 +11,7 @@ import com.facebook.react.model.ModelAutolinkingConfigJson
|
||||
import com.facebook.react.model.ModelAutolinkingDependenciesJson
|
||||
import com.facebook.react.model.ModelAutolinkingDependenciesPlatformAndroidJson
|
||||
import com.facebook.react.model.ModelAutolinkingDependenciesPlatformJson
|
||||
import com.facebook.react.tasks.GenerateAutolinkingNewArchitecturesFileTask.Companion.sanitizeCmakeListsPath
|
||||
import com.facebook.react.tests.createTestTask
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
@@ -145,9 +146,9 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest {
|
||||
# or link against a old prefab target (this is needed for React Native 0.76 on).
|
||||
set(REACTNATIVE_MERGED_SO true)
|
||||
|
||||
add_subdirectory(./a/directory/ aPackage_autolinked_build)
|
||||
add_subdirectory(./another/directory/ anotherPackage_autolinked_build)
|
||||
add_subdirectory(./another/directory/cxx/ anotherPackage_cxxmodule_autolinked_build)
|
||||
add_subdirectory("./a/directory/" aPackage_autolinked_build)
|
||||
add_subdirectory("./another/directory/with\ spaces/" anotherPackage_autolinked_build)
|
||||
add_subdirectory("./another/directory/cxx/" anotherPackage_cxxmodule_autolinked_build)
|
||||
|
||||
set(AUTOLINKED_LIBRARIES
|
||||
react_codegen_aPackage
|
||||
@@ -258,6 +259,24 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest {
|
||||
.trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sanitizeCmakeListsPath_withPathEndingWithFileName_removesFilename() {
|
||||
val input = "./a/directory/CMakeLists.txt"
|
||||
assertThat(sanitizeCmakeListsPath(input)).isEqualTo("./a/directory/")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sanitizeCmakeListsPath_withSpaces_removesSpaces() {
|
||||
val input = "./a/dir ectory/with spaces/"
|
||||
assertThat(sanitizeCmakeListsPath(input)).isEqualTo("./a/dir\\ ectory/with\\ spaces/")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sanitizeCmakeListsPath_withPathEndingWithFileNameAndSpaces_sanitizesIt() {
|
||||
val input = "./a/dir ectory/CMakeLists.txt"
|
||||
assertThat(sanitizeCmakeListsPath(input)).isEqualTo("./a/dir\\ ectory/")
|
||||
}
|
||||
|
||||
private val testDependencies =
|
||||
listOf(
|
||||
ModelAutolinkingDependenciesPlatformAndroidJson(
|
||||
@@ -276,7 +295,7 @@ class GenerateAutolinkingNewArchitecturesFileTaskTest {
|
||||
buildTypes = emptyList(),
|
||||
libraryName = "anotherPackage",
|
||||
componentDescriptors = listOf("AnotherPackageComponentDescriptor"),
|
||||
cmakeListsPath = "./another/directory/CMakeLists.txt",
|
||||
cmakeListsPath = "./another/directory/with spaces/CMakeLists.txt",
|
||||
cxxModuleCMakeListsPath = "./another/directory/cxx/CMakeLists.txt",
|
||||
cxxModuleHeaderName = "AnotherCxxModule",
|
||||
cxxModuleCMakeListsModuleName = "another_cxxModule",
|
||||
|
||||
@@ -4,3 +4,4 @@ ruby ">= 2.6.10"
|
||||
|
||||
gem 'cocoapods', '~> 1.13', '!= 1.15.0', '!= 1.15.1'
|
||||
gem 'activesupport', '>= 6.1.7.5', '< 7.1.0'
|
||||
gem 'xcodeproj', '< 1.26.0'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "helloworld",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"bootstrap": "node ./cli.js bootstrap",
|
||||
@@ -13,16 +13,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "18.3.1",
|
||||
"react-native": "0.76.0-rc.4"
|
||||
"react-native": "0.76.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/preset-env": "^7.25.3",
|
||||
"@babel/runtime": "^7.25.0",
|
||||
"@react-native/babel-preset": "0.76.0-rc.4",
|
||||
"@react-native/core-cli-utils": "0.76.0-rc.4",
|
||||
"@react-native/eslint-config": "0.76.0-rc.4",
|
||||
"@react-native/metro-config": "0.76.0-rc.4",
|
||||
"@react-native/babel-preset": "0.76.6",
|
||||
"@react-native/core-cli-utils": "0.76.6",
|
||||
"@react-native/eslint-config": "0.76.6",
|
||||
"@react-native/metro-config": "0.76.6",
|
||||
"babel-jest": "^29.6.3",
|
||||
"chalk": "^4.1.2",
|
||||
"commander": "^12.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/hermes-inspector-msggen",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"private": true,
|
||||
"description": "Hermes Inspector Message Generator for React Native",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/metro-config",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "Metro configuration for React Native.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
@@ -26,9 +26,9 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@react-native/js-polyfills": "0.76.0-rc.4",
|
||||
"@react-native/metro-babel-transformer": "0.76.0-rc.4",
|
||||
"metro-config": "^0.81.0-alpha.2",
|
||||
"metro-runtime": "^0.81.0-alpha.2"
|
||||
"@react-native/js-polyfills": "0.76.6",
|
||||
"@react-native/metro-babel-transformer": "0.76.6",
|
||||
"metro-config": "^0.81.0",
|
||||
"metro-runtime": "^0.81.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ export function getDefaultConfig(projectRoot: string): ConfigT {
|
||||
babelTransformerPath: require.resolve(
|
||||
'@react-native/metro-babel-transformer',
|
||||
),
|
||||
hermesParser: true,
|
||||
getTransformOptions: async () => ({
|
||||
transform: {
|
||||
experimentalImportSupport: false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/normalize-colors",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "Color normalization for React Native.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/js-polyfills",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "Polyfills for React Native.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/babel-preset",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "Babel preset for React Native applications",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
@@ -55,8 +55,8 @@
|
||||
"@babel/plugin-transform-typescript": "^7.25.2",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.24.7",
|
||||
"@babel/template": "^7.25.0",
|
||||
"@react-native/babel-plugin-codegen": "0.76.0-rc.4",
|
||||
"babel-plugin-syntax-hermes-parser": "^0.23.1",
|
||||
"@react-native/babel-plugin-codegen": "0.76.6",
|
||||
"babel-plugin-syntax-hermes-parser": "^0.25.1",
|
||||
"babel-plugin-transform-flow-enums": "^0.0.2",
|
||||
"react-refresh": "^0.14.0"
|
||||
},
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ function isTSXSource(fileName) {
|
||||
const loose = true;
|
||||
|
||||
const defaultPlugins = [
|
||||
[require('babel-plugin-syntax-hermes-parser')],
|
||||
[require('babel-plugin-syntax-hermes-parser'), {parseLangTypes: 'flow'}],
|
||||
[require('babel-plugin-transform-flow-enums')],
|
||||
[require('@babel/plugin-transform-block-scoping')],
|
||||
[require('@babel/plugin-transform-class-properties'), {loose}],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/metro-babel-transformer",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "Babel transformer for React Native applications.",
|
||||
"main": "src/index.js",
|
||||
"repository": {
|
||||
@@ -16,7 +16,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@react-native/babel-preset": "0.76.0-rc.4",
|
||||
"@react-native/babel-preset": "0.76.6",
|
||||
"hermes-parser": "0.23.1",
|
||||
"nullthrows": "^1.1.1"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-native/bots",
|
||||
"description": "React Native Bots",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/codegen-typescript-test",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"private": true,
|
||||
"description": "TypeScript related unit test for @react-native/codegen",
|
||||
"license": "MIT",
|
||||
@@ -19,7 +19,7 @@
|
||||
"prepare": "yarn run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native/codegen": "0.76.0-rc.4"
|
||||
"@react-native/codegen": "0.76.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/codegen",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "Code generation tools for React Native",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-info",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"main": "build/index.js",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
@@ -17,10 +17,10 @@
|
||||
"directory": "packages/react-native-info"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-community/cli-config": "15.0.0-alpha.2",
|
||||
"@react-native-community/cli-platform-apple": "15.0.0-alpha.2",
|
||||
"@react-native-community/cli-tools": "15.0.0-alpha.2",
|
||||
"@react-native-community/cli-types": "15.0.0-alpha.2",
|
||||
"@react-native-community/cli-config": "15.0.1",
|
||||
"@react-native-community/cli-platform-apple": "15.0.1",
|
||||
"@react-native-community/cli-tools": "15.0.1",
|
||||
"@react-native-community/cli-types": "15.0.1",
|
||||
"commander": "^12.0.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"yaml": "^2.4.1"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/popup-menu-android",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "PopupMenu for the Android platform",
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
@@ -17,7 +17,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@react-native/codegen": "0.76.0-rc.4"
|
||||
"@react-native/codegen": "0.76.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.2.6",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-native/oss-library-example",
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"private": true,
|
||||
"description": "Package that includes native module exapmle, native component example, targets both the old and the new architecture. It should serve as an example of a real-world OSS library.",
|
||||
"license": "MIT",
|
||||
@@ -26,8 +26,8 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@react-native/babel-preset": "0.76.0-rc.4",
|
||||
"react-native": "0.76.0-rc.4"
|
||||
"@react-native/babel-preset": "0.76.6",
|
||||
"react-native": "0.76.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-native/test-renderer",
|
||||
"private": true,
|
||||
"version": "0.76.0-rc.4",
|
||||
"version": "0.76.6",
|
||||
"description": "A Test rendering library for React Native",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
UIViewController *rootViewController = [self createRootViewController];
|
||||
[self setRootView:rootView toRootViewController:rootViewController];
|
||||
_window.windowScene.delegate = self;
|
||||
_window.rootViewController = rootViewController;
|
||||
[_window makeKeyAndVisible];
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ Pod::Spec.new do |s|
|
||||
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
|
||||
"DEFINES_MODULE" => "YES"
|
||||
}
|
||||
s.user_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/Headers/Private/React-Core\""}
|
||||
s.user_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/Headers/Private/React-Core\" \"$(PODS_ROOT)/Headers/Private/Yoga\""}
|
||||
|
||||
s.dependency "React-Core"
|
||||
s.dependency "RCT-Folly", folly_version
|
||||
|
||||
@@ -16,8 +16,8 @@ const version: $ReadOnly<{
|
||||
}> = {
|
||||
major: 0,
|
||||
minor: 76,
|
||||
patch: 0,
|
||||
prerelease: 'rc.4',
|
||||
patch: 6,
|
||||
prerelease: null,
|
||||
};
|
||||
|
||||
module.exports = {version};
|
||||
|
||||
@@ -21,13 +21,7 @@ ExceptionsManager.installConsoleErrorReporter();
|
||||
if (!global.__fbDisableExceptionsManager) {
|
||||
const handleError = (e: mixed, isFatal: boolean) => {
|
||||
try {
|
||||
// TODO(T196834299): We should really use a c++ turbomodule for this
|
||||
if (
|
||||
!global.RN$handleException ||
|
||||
!global.RN$handleException(e, isFatal)
|
||||
) {
|
||||
ExceptionsManager.handleException(e, isFatal);
|
||||
}
|
||||
ExceptionsManager.handleException(e, isFatal);
|
||||
} catch (ee) {
|
||||
console.log('Failed to print error: ', ee.message);
|
||||
throw e;
|
||||
|
||||
@@ -82,9 +82,9 @@ let warningFilter: WarningFilter = function (format) {
|
||||
return {
|
||||
finalFormat: format,
|
||||
forceDialogImmediately: false,
|
||||
suppressDialog_LEGACY: true,
|
||||
suppressDialog_LEGACY: false,
|
||||
suppressCompletely: false,
|
||||
monitorEvent: 'unknown',
|
||||
monitorEvent: 'warning_unhandled',
|
||||
monitorListVersion: 0,
|
||||
monitorSampleRate: 1,
|
||||
};
|
||||
|
||||
+169
-44
@@ -11,45 +11,29 @@
|
||||
import {
|
||||
DoesNotUseKey,
|
||||
FragmentWithProp,
|
||||
ManualConsoleError,
|
||||
ManualConsoleErrorWithStack,
|
||||
} from './__fixtures__/ReactWarningFixtures';
|
||||
import * as React from 'react';
|
||||
|
||||
const LogBoxData = require('../Data/LogBoxData');
|
||||
const TestRenderer = require('react-test-renderer');
|
||||
|
||||
const installLogBox = () => {
|
||||
const LogBox = require('../LogBox');
|
||||
const ExceptionsManager = require('../../Core/ExceptionsManager.js');
|
||||
|
||||
const installLogBox = () => {
|
||||
const LogBox = require('../LogBox').default;
|
||||
LogBox.install();
|
||||
};
|
||||
|
||||
const uninstallLogBox = () => {
|
||||
const LogBox = require('../LogBox');
|
||||
const LogBox = require('../LogBox').default;
|
||||
LogBox.uninstall();
|
||||
};
|
||||
|
||||
const BEFORE_SLASH_RE = /(?:\/[a-zA-Z]+\/)(.+?)(?:\/.+)\//;
|
||||
|
||||
const cleanPath = message => {
|
||||
return message.replace(BEFORE_SLASH_RE, '/path/to/');
|
||||
};
|
||||
|
||||
const cleanLog = logs => {
|
||||
return logs.map(log => {
|
||||
return {
|
||||
...log,
|
||||
componentStack: log.componentStack.map(stack => ({
|
||||
...stack,
|
||||
fileName: cleanPath(stack.fileName),
|
||||
})),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// TODO(T71117418): Re-enable skipped LogBox integration tests once React component
|
||||
// stack frames are the same internally and in open source.
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
describe.skip('LogBox', () => {
|
||||
// TODO: we can remove all the symetric matchers once OSS lands component stack frames.
|
||||
// For now, the component stack parsing differs in ways we can't easily detect in this test.
|
||||
describe('LogBox', () => {
|
||||
const {error, warn} = console;
|
||||
const mockError = jest.fn();
|
||||
const mockWarn = jest.fn();
|
||||
@@ -57,10 +41,14 @@ describe.skip('LogBox', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
jest.restoreAllMocks();
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
mockError.mockClear();
|
||||
mockWarn.mockClear();
|
||||
|
||||
// Reset ExceptionManager patching.
|
||||
if (console._errorOriginal) {
|
||||
console._errorOriginal = null;
|
||||
}
|
||||
(console: any).error = mockError;
|
||||
(console: any).warn = mockWarn;
|
||||
});
|
||||
@@ -79,7 +67,10 @@ describe.skip('LogBox', () => {
|
||||
// so we can assert on what React logs.
|
||||
jest.spyOn(console, 'error');
|
||||
|
||||
const output = TestRenderer.create(<DoesNotUseKey />);
|
||||
let output;
|
||||
TestRenderer.act(() => {
|
||||
output = TestRenderer.create(<DoesNotUseKey />);
|
||||
});
|
||||
|
||||
// The key error should always be the highest severity.
|
||||
// In LogBox, we expect these errors to:
|
||||
@@ -88,16 +79,37 @@ describe.skip('LogBox', () => {
|
||||
// - Pass to console.error, with a "Warning" prefix so it does not pop a RedBox.
|
||||
expect(output).toBeDefined();
|
||||
expect(mockWarn).not.toBeCalled();
|
||||
expect(console.error.mock.calls[0].map(cleanPath)).toMatchSnapshot(
|
||||
'Log sent from React',
|
||||
);
|
||||
expect(cleanLog(spy.mock.calls[0])).toMatchSnapshot('Log added to LogBox');
|
||||
expect(mockError.mock.calls[0].map(cleanPath)).toMatchSnapshot(
|
||||
'Log passed to console error',
|
||||
);
|
||||
expect(console.error).toBeCalledTimes(1);
|
||||
expect(console.error.mock.calls[0]).toEqual([
|
||||
'Warning: Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.%s',
|
||||
'\n\nCheck the render method of `DoesNotUseKey`.',
|
||||
'',
|
||||
expect.stringMatching('at DoesNotUseKey'),
|
||||
]);
|
||||
expect(spy).toHaveBeenCalledWith({
|
||||
level: 'error',
|
||||
category: expect.stringContaining(
|
||||
'Warning: Each child in a list should have a unique',
|
||||
),
|
||||
componentStack: expect.anything(),
|
||||
componentStackType: 'stack',
|
||||
message: {
|
||||
content:
|
||||
'Warning: Each child in a list should have a unique "key" prop.\n\nCheck the render method of `DoesNotUseKey`. See https://reactjs.org/link/warning-keys for more information.',
|
||||
substitutions: [
|
||||
{length: 45, offset: 62},
|
||||
{length: 0, offset: 107},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// The Warning: prefix is added due to a hack in LogBox to prevent double logging.
|
||||
expect(mockError.mock.calls[0][0].startsWith('Warning: ')).toBe(true);
|
||||
// We also interpolate the string before passing to the underlying console method.
|
||||
expect(mockError.mock.calls[0]).toEqual([
|
||||
expect.stringMatching(
|
||||
'Warning: Each child in a list should have a unique "key" prop.\n\nCheck the render method of `DoesNotUseKey`. See https://reactjs.org/link/warning-keys for more information.\n at ',
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
it('integrates with React and handles a fragment warning in LogBox', () => {
|
||||
@@ -108,7 +120,10 @@ describe.skip('LogBox', () => {
|
||||
// so we can assert on what React logs.
|
||||
jest.spyOn(console, 'error');
|
||||
|
||||
const output = TestRenderer.create(<FragmentWithProp />);
|
||||
let output;
|
||||
TestRenderer.act(() => {
|
||||
output = TestRenderer.create(<FragmentWithProp />);
|
||||
});
|
||||
|
||||
// The fragment warning is not as severe. For this warning we don't want to
|
||||
// pop open a dialog, so we show a collapsed error UI.
|
||||
@@ -118,15 +133,125 @@ describe.skip('LogBox', () => {
|
||||
// - Pass to console.error, with a "Warning" prefix so it does not pop a RedBox.
|
||||
expect(output).toBeDefined();
|
||||
expect(mockWarn).not.toBeCalled();
|
||||
expect(console.error.mock.calls[0].map(cleanPath)).toMatchSnapshot(
|
||||
'Log sent from React',
|
||||
);
|
||||
expect(cleanLog(spy.mock.calls[0])).toMatchSnapshot('Log added to LogBox');
|
||||
expect(mockError.mock.calls[0].map(cleanPath)).toMatchSnapshot(
|
||||
'Log passed to console error',
|
||||
);
|
||||
expect(console.error).toBeCalledTimes(1);
|
||||
expect(console.error.mock.calls[0]).toEqual([
|
||||
'Warning: Invalid prop `%s` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.%s',
|
||||
'invalid',
|
||||
expect.stringMatching('at FragmentWithProp'),
|
||||
]);
|
||||
expect(spy).toHaveBeenCalledWith({
|
||||
level: 'error',
|
||||
category: expect.stringContaining('Warning: Invalid prop'),
|
||||
componentStack: expect.anything(),
|
||||
componentStackType: expect.stringMatching(/(stack|legacy)/),
|
||||
message: {
|
||||
content:
|
||||
'Warning: Invalid prop `invalid` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.',
|
||||
substitutions: [{length: 7, offset: 23}],
|
||||
},
|
||||
});
|
||||
|
||||
// The Warning: prefix is added due to a hack in LogBox to prevent double logging.
|
||||
expect(mockError.mock.calls[0][0].startsWith('Warning: ')).toBe(true);
|
||||
// We also interpolate the string before passing to the underlying console method.
|
||||
expect(mockError.mock.calls[0]).toEqual([
|
||||
expect.stringMatching(
|
||||
'Warning: Invalid prop `invalid` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.\n at FragmentWithProp',
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles a manual console.error without a component stack in LogBox', () => {
|
||||
const LogBox = require('../LogBox').default;
|
||||
const spy = jest.spyOn(LogBox, 'addException');
|
||||
installLogBox();
|
||||
|
||||
// console.error handling depends on installing the ExceptionsManager error reporter.
|
||||
ExceptionsManager.installConsoleErrorReporter();
|
||||
|
||||
// Spy console.error after LogBox is installed
|
||||
// so we can assert on what React logs.
|
||||
jest.spyOn(console, 'error');
|
||||
|
||||
let output;
|
||||
TestRenderer.act(() => {
|
||||
output = TestRenderer.create(<ManualConsoleError />);
|
||||
});
|
||||
|
||||
// Manual console errors should show a collapsed error dialog.
|
||||
// When there is no component stack, we expect these errors to:
|
||||
// - Go to the LogBox patch and fall through to console.error.
|
||||
// - Get picked up by the ExceptionsManager console.error override.
|
||||
// - Get passed back to LogBox via addException (non-fatal).
|
||||
expect(output).toBeDefined();
|
||||
expect(mockWarn).not.toBeCalled();
|
||||
expect(spy).toBeCalledTimes(1);
|
||||
expect(console.error).toBeCalledTimes(1);
|
||||
expect(console.error.mock.calls[0]).toEqual(['Manual console error']);
|
||||
expect(spy).toHaveBeenCalledWith({
|
||||
id: 1,
|
||||
isComponentError: false,
|
||||
isFatal: false,
|
||||
name: 'console.error',
|
||||
originalMessage: 'Manual console error',
|
||||
message: 'console.error: Manual console error',
|
||||
extraData: expect.anything(),
|
||||
componentStack: null,
|
||||
stack: expect.anything(),
|
||||
});
|
||||
|
||||
// No Warning: prefix is added due since this is falling through.
|
||||
expect(mockError.mock.calls[0]).toEqual(['Manual console error']);
|
||||
});
|
||||
|
||||
it('handles a manual console.error with a component stack in LogBox', () => {
|
||||
const spy = jest.spyOn(LogBoxData, 'addLog');
|
||||
installLogBox();
|
||||
|
||||
// console.error handling depends on installing the ExceptionsManager error reporter.
|
||||
ExceptionsManager.installConsoleErrorReporter();
|
||||
|
||||
// Spy console.error after LogBox is installed
|
||||
// so we can assert on what React logs.
|
||||
jest.spyOn(console, 'error');
|
||||
|
||||
let output;
|
||||
TestRenderer.act(() => {
|
||||
output = TestRenderer.create(<ManualConsoleErrorWithStack />);
|
||||
});
|
||||
|
||||
// Manual console errors should show a collapsed error dialog.
|
||||
// When there is a component stack, we expect these errors to:
|
||||
// - Go to the LogBox patch and be detected as a React error.
|
||||
// - Check the warning filter to see if there is a fiter setting.
|
||||
// - Call console.error with the parsed error.
|
||||
// - Get picked up by ExceptionsManager console.error override.
|
||||
// - Log to console.error.
|
||||
expect(output).toBeDefined();
|
||||
expect(mockWarn).not.toBeCalled();
|
||||
expect(console.error).toBeCalledTimes(1);
|
||||
expect(spy).toBeCalledTimes(1);
|
||||
expect(console.error.mock.calls[0]).toEqual([
|
||||
expect.stringContaining(
|
||||
'Manual console error\n at ManualConsoleErrorWithStack',
|
||||
),
|
||||
]);
|
||||
expect(spy).toHaveBeenCalledWith({
|
||||
level: 'error',
|
||||
category: expect.stringContaining('Warning: Manual console error'),
|
||||
componentStack: expect.anything(),
|
||||
componentStackType: 'stack',
|
||||
message: {
|
||||
content: 'Warning: Manual console error',
|
||||
substitutions: [],
|
||||
},
|
||||
});
|
||||
|
||||
// The Warning: prefix is added due to a hack in LogBox to prevent double logging.
|
||||
// We also interpolate the string before passing to the underlying console method.
|
||||
expect(mockError.mock.calls[0]).toEqual([
|
||||
expect.stringMatching(
|
||||
'Warning: Manual console error\n at ManualConsoleErrorWithStack',
|
||||
),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
+130
-30
@@ -13,6 +13,7 @@
|
||||
|
||||
const LogBoxData = require('../Data/LogBoxData');
|
||||
const LogBox = require('../LogBox').default;
|
||||
const ExceptionsManager = require('../../Core/ExceptionsManager.js');
|
||||
|
||||
declare var console: any;
|
||||
|
||||
@@ -34,15 +35,18 @@ describe('LogBox', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
jest.restoreAllMocks();
|
||||
console.error = jest.fn();
|
||||
console.log = jest.fn();
|
||||
console.warn = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
LogBox.uninstall();
|
||||
// Reset ExceptionManager patching.
|
||||
if (console._errorOriginal) {
|
||||
console._errorOriginal = null;
|
||||
}
|
||||
console.error = error;
|
||||
console.log = log;
|
||||
console.warn = warn;
|
||||
});
|
||||
|
||||
@@ -95,7 +99,7 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('registers warnings', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
|
||||
LogBox.install();
|
||||
|
||||
@@ -105,13 +109,14 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('reports a LogBox exception if we fail to add warnings', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
const mockError = new Error('Simulated error');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
jest.spyOn(LogBoxData, 'reportLogBoxError');
|
||||
|
||||
// Picking a random implementation detail to simulate throwing.
|
||||
(LogBoxData.isMessageIgnored: any).mockImplementation(() => {
|
||||
jest.spyOn(LogBoxData, 'isMessageIgnored').mockImplementation(() => {
|
||||
throw mockError;
|
||||
});
|
||||
const mockError = new Error('Simulated error');
|
||||
|
||||
LogBox.install();
|
||||
|
||||
@@ -123,7 +128,8 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('only registers errors beginning with "Warning: "', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
|
||||
LogBox.install();
|
||||
|
||||
@@ -133,7 +139,8 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('registers react errors with the formatting from filter', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
|
||||
mockFilterResult({
|
||||
finalFormat: 'Custom format',
|
||||
@@ -157,7 +164,8 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('registers errors with component stack as errors by default', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
|
||||
mockFilterResult({});
|
||||
|
||||
@@ -174,7 +182,8 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('registers errors with component stack as errors by default if not found in warning filter', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
|
||||
mockFilterResult({
|
||||
monitorEvent: 'warning_unhandled',
|
||||
@@ -193,10 +202,12 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('registers errors with component stack with legacy suppression as warning', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
|
||||
mockFilterResult({
|
||||
suppressDialog_LEGACY: true,
|
||||
monitorEvent: 'warning',
|
||||
});
|
||||
|
||||
LogBox.install();
|
||||
@@ -211,10 +222,12 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('registers errors with component stack and a forced dialog as fatals', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
|
||||
mockFilterResult({
|
||||
forceDialogImmediately: true,
|
||||
monitorEvent: 'warning',
|
||||
});
|
||||
|
||||
LogBox.install();
|
||||
@@ -229,7 +242,8 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('registers warning module errors with the formatting from filter', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
|
||||
mockFilterResult({
|
||||
finalFormat: 'Custom format',
|
||||
@@ -248,7 +262,8 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('registers warning module errors as errors by default', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
|
||||
mockFilterResult({});
|
||||
|
||||
@@ -262,10 +277,12 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('registers warning module errors with only legacy suppression as warning', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
|
||||
mockFilterResult({
|
||||
suppressDialog_LEGACY: true,
|
||||
monitorEvent: 'warning',
|
||||
});
|
||||
|
||||
LogBox.install();
|
||||
@@ -277,10 +294,12 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('registers warning module errors with a forced dialog as fatals', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
|
||||
mockFilterResult({
|
||||
forceDialogImmediately: true,
|
||||
monitorEvent: 'warning',
|
||||
});
|
||||
|
||||
LogBox.install();
|
||||
@@ -292,10 +311,12 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('ignores warning module errors that are suppressed completely', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
|
||||
mockFilterResult({
|
||||
suppressCompletely: true,
|
||||
monitorEvent: 'warning',
|
||||
});
|
||||
|
||||
LogBox.install();
|
||||
@@ -305,10 +326,11 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('ignores warning module errors that are pattern ignored', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
jest.spyOn(LogBoxData, 'isMessageIgnored').mockReturnValue(true);
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
|
||||
mockFilterResult({});
|
||||
(LogBoxData.isMessageIgnored: any).mockReturnValue(true);
|
||||
|
||||
LogBox.install();
|
||||
|
||||
@@ -317,10 +339,11 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('ignores warning module errors that are from LogBox itself', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
jest.spyOn(LogBoxData, 'isLogBoxErrorMessage').mockReturnValue(true);
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
|
||||
mockFilterResult({});
|
||||
(LogBoxData.isLogBoxErrorMessage: any).mockReturnValue(true);
|
||||
|
||||
LogBox.install();
|
||||
|
||||
@@ -329,8 +352,9 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('ignores logs that are pattern ignored"', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
(LogBoxData.isMessageIgnored: any).mockReturnValue(true);
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
jest.spyOn(LogBoxData, 'isMessageIgnored').mockReturnValue(true);
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
|
||||
LogBox.install();
|
||||
|
||||
@@ -339,8 +363,8 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('does not add logs that are from LogBox itself"', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
(LogBoxData.isLogBoxErrorMessage: any).mockReturnValue(true);
|
||||
jest.spyOn(LogBoxData, 'isLogBoxErrorMessage').mockReturnValue(true);
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
|
||||
LogBox.install();
|
||||
|
||||
@@ -349,7 +373,7 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('ignores logs starting with "(ADVICE)"', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
|
||||
LogBox.install();
|
||||
|
||||
@@ -358,7 +382,7 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('does not ignore logs formatted to start with "(ADVICE)"', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
|
||||
LogBox.install();
|
||||
|
||||
@@ -376,7 +400,7 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('ignores console methods after uninstalling', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
|
||||
LogBox.install();
|
||||
LogBox.uninstall();
|
||||
@@ -389,7 +413,7 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('does not add logs after uninstalling', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
|
||||
LogBox.install();
|
||||
LogBox.uninstall();
|
||||
@@ -406,7 +430,7 @@ describe('LogBox', () => {
|
||||
});
|
||||
|
||||
it('does not add exceptions after uninstalling', () => {
|
||||
jest.mock('../Data/LogBoxData');
|
||||
jest.spyOn(LogBoxData, 'addException');
|
||||
|
||||
LogBox.install();
|
||||
LogBox.uninstall();
|
||||
@@ -482,4 +506,80 @@ describe('LogBox', () => {
|
||||
'Custom: after installing for the second time',
|
||||
);
|
||||
});
|
||||
|
||||
it('registers errors with component stack as errors by default, when ExceptionManager is registered first', () => {
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
|
||||
ExceptionsManager.installConsoleErrorReporter();
|
||||
LogBox.install();
|
||||
|
||||
console.error(
|
||||
'HIT\n at Text (/path/to/Component:30:175)\n at DoesNotUseKey',
|
||||
);
|
||||
|
||||
expect(LogBoxData.addLog).toBeCalledWith(
|
||||
expect.objectContaining({level: 'error'}),
|
||||
);
|
||||
expect(LogBoxData.checkWarningFilter).toBeCalledWith(
|
||||
'HIT\n at Text (/path/to/Component:30:175)\n at DoesNotUseKey',
|
||||
);
|
||||
});
|
||||
|
||||
it('registers errors with component stack as errors by default, when ExceptionManager is registered second', () => {
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
jest.spyOn(LogBoxData, 'addLog');
|
||||
|
||||
LogBox.install();
|
||||
ExceptionsManager.installConsoleErrorReporter();
|
||||
|
||||
console.error(
|
||||
'HIT\n at Text (/path/to/Component:30:175)\n at DoesNotUseKey',
|
||||
);
|
||||
|
||||
expect(LogBoxData.addLog).toBeCalledWith(
|
||||
expect.objectContaining({level: 'error'}),
|
||||
);
|
||||
expect(LogBoxData.checkWarningFilter).toBeCalledWith(
|
||||
'HIT\n at Text (/path/to/Component:30:175)\n at DoesNotUseKey',
|
||||
);
|
||||
});
|
||||
|
||||
it('registers errors without component stack as errors by default, when ExceptionManager is registered first', () => {
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
jest.spyOn(LogBoxData, 'addException');
|
||||
|
||||
ExceptionsManager.installConsoleErrorReporter();
|
||||
LogBox.install();
|
||||
|
||||
console.error('HIT');
|
||||
|
||||
// Errors without a component stack skip the warning filter and
|
||||
// fall through to the ExceptionManager, which are then reported
|
||||
// back to LogBox as non-fatal exceptions, in a convuluted dance
|
||||
// in the most legacy cruft way.
|
||||
expect(LogBoxData.addException).toBeCalledWith(
|
||||
expect.objectContaining({originalMessage: 'HIT'}),
|
||||
);
|
||||
expect(LogBoxData.checkWarningFilter).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('registers errors without component stack as errors by default, when ExceptionManager is registered second', () => {
|
||||
jest.spyOn(LogBoxData, 'checkWarningFilter');
|
||||
jest.spyOn(LogBoxData, 'addException');
|
||||
|
||||
LogBox.install();
|
||||
ExceptionsManager.installConsoleErrorReporter();
|
||||
|
||||
console.error('HIT');
|
||||
|
||||
// Errors without a component stack skip the warning filter and
|
||||
// fall through to the ExceptionManager, which are then reported
|
||||
// back to LogBox as non-fatal exceptions, in a convuluted dance
|
||||
// in the most legacy cruft way.
|
||||
expect(LogBoxData.addException).toBeCalledWith(
|
||||
expect.objectContaining({originalMessage: 'HIT'}),
|
||||
);
|
||||
expect(LogBoxData.checkWarningFilter).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
+24
@@ -30,3 +30,27 @@ export const FragmentWithProp = () => {
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export const ManualConsoleError = () => {
|
||||
console.error('Manual console error');
|
||||
return (
|
||||
<React.Fragment>
|
||||
{['foo', 'bar'].map(item => (
|
||||
<Text key={item}>{item}</Text>
|
||||
))}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export const ManualConsoleErrorWithStack = () => {
|
||||
console.error(
|
||||
'Manual console error\n at ManualConsoleErrorWithStack (/path/to/ManualConsoleErrorWithStack:30:175)\n at TestApp',
|
||||
);
|
||||
return (
|
||||
<React.Fragment>
|
||||
{['foo', 'bar'].map(item => (
|
||||
<Text key={item}>{item}</Text>
|
||||
))}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {RootTag} from '../Types/RootTagTypes';
|
||||
import type {IPerformanceLogger} from '../Utilities/createPerformanceLogger';
|
||||
import type {DisplayModeType} from './DisplayMode';
|
||||
|
||||
import BatchedBridge from '../BatchedBridge/BatchedBridge';
|
||||
import registerCallableModule from '../Core/registerCallableModule';
|
||||
import BugReporting from '../BugReporting/BugReporting';
|
||||
import createPerformanceLogger from '../Utilities/createPerformanceLogger';
|
||||
import infoLog from '../Utilities/infoLog';
|
||||
@@ -363,8 +363,8 @@ global.RN$SurfaceRegistry = {
|
||||
|
||||
if (global.RN$Bridgeless === true) {
|
||||
console.log('Bridgeless mode is enabled');
|
||||
} else {
|
||||
BatchedBridge.registerCallableModule('AppRegistry', AppRegistry);
|
||||
}
|
||||
|
||||
registerCallableModule('AppRegistry', AppRegistry);
|
||||
|
||||
module.exports = AppRegistry;
|
||||
|
||||
@@ -35,6 +35,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
@property (nonatomic, assign, readonly) CGFloat zoomScale;
|
||||
@property (nonatomic, assign, readonly) CGPoint contentOffset;
|
||||
@property (nonatomic, assign, readonly) UIEdgeInsets contentInset;
|
||||
@property (nullable, nonatomic, copy) NSDictionary<NSAttributedStringKey, id> *typingAttributes;
|
||||
|
||||
// This protocol disallows direct access to `selectedTextRange` property because
|
||||
// unwise usage of it can break the `delegate` behavior. So, we always have to
|
||||
|
||||
@@ -36,6 +36,7 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
||||
{
|
||||
if (![self isDescendantOfView:scrollView]) {
|
||||
// View is outside scroll view
|
||||
scrollView.firstResponderViewOutsideScrollView = self.backedTextInputView;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -456,7 +457,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame)
|
||||
_maxLength.integerValue - (NSInteger)backedTextInputView.attributedText.string.length + (NSInteger)range.length,
|
||||
0);
|
||||
|
||||
if (text.length > _maxLength.integerValue) {
|
||||
if (text.length > allowedLength) {
|
||||
// If we typed/pasted more than one character, limit the text inputted.
|
||||
if (text.length > 1) {
|
||||
if (allowedLength > 0) {
|
||||
|
||||
@@ -23,8 +23,8 @@ NSDictionary* RCTGetReactNativeVersion(void)
|
||||
__rnVersion = @{
|
||||
RCTVersionMajor: @(0),
|
||||
RCTVersionMinor: @(76),
|
||||
RCTVersionPatch: @(0),
|
||||
RCTVersionPrerelease: @"rc.4",
|
||||
RCTVersionPatch: @(6),
|
||||
RCTVersionPrerelease: [NSNull null],
|
||||
};
|
||||
});
|
||||
return __rnVersion;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
/**
|
||||
* UIView class for root <ModalHostView> component.
|
||||
*/
|
||||
@interface RCTModalHostViewComponentView : RCTViewComponentView
|
||||
@interface RCTModalHostViewComponentView : RCTViewComponentView <UIAdaptivePresentationControllerDelegate>
|
||||
|
||||
/**
|
||||
* Subclasses may override this method and present the modal on different view controller.
|
||||
|
||||
+8
-18
@@ -134,9 +134,7 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
completion:(void (^)(void))completion
|
||||
{
|
||||
UIViewController *controller = [self reactViewController];
|
||||
[[self _topMostViewControllerFrom:controller] presentViewController:modalViewController
|
||||
animated:animated
|
||||
completion:completion];
|
||||
[controller presentViewController:modalViewController animated:animated completion:completion];
|
||||
}
|
||||
|
||||
- (void)dismissViewController:(UIViewController *)modalViewController
|
||||
@@ -151,6 +149,8 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
{
|
||||
BOOL shouldBePresented = !_isPresented && _shouldPresent && self.window;
|
||||
if (shouldBePresented) {
|
||||
self.viewController.presentationController.delegate = self;
|
||||
|
||||
_isPresented = YES;
|
||||
[self presentViewController:self.viewController
|
||||
animated:_shouldAnimatePresentation
|
||||
@@ -276,24 +276,14 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
[childComponentView removeFromSuperview];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
#pragma mark - UIAdaptivePresentationControllerDelegate
|
||||
|
||||
- (UIViewController *)_topMostViewControllerFrom:(UIViewController *)rootViewController
|
||||
- (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)controller
|
||||
{
|
||||
UIViewController *topController = rootViewController;
|
||||
while (topController.presentedViewController) {
|
||||
topController = topController.presentedViewController;
|
||||
auto eventEmitter = [self modalEventEmitter];
|
||||
if (eventEmitter) {
|
||||
eventEmitter->onRequestClose({});
|
||||
}
|
||||
if ([topController isKindOfClass:[UINavigationController class]]) {
|
||||
UINavigationController *navigationController = (UINavigationController *)topController;
|
||||
topController = navigationController.visibleViewController;
|
||||
return [self _topMostViewControllerFrom:topController];
|
||||
} else if ([topController isKindOfClass:[UITabBarController class]]) {
|
||||
UITabBarController *tabBarController = (UITabBarController *)topController;
|
||||
topController = tabBarController.selectedViewController;
|
||||
return [self _topMostViewControllerFrom:topController];
|
||||
}
|
||||
return topController;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+3
@@ -38,6 +38,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/** Focus area of newly-activated text input relative to the window to compare against UIKeyboardFrameBegin/End */
|
||||
@property (nonatomic, assign) CGRect firstResponderFocus;
|
||||
|
||||
/** newly-activated text input outside of the scroll view */
|
||||
@property (nonatomic, weak) UIView *firstResponderViewOutsideScrollView;
|
||||
|
||||
/*
|
||||
* Returns the subview of the scroll view that the component uses to mount all subcomponents into. That's useful to
|
||||
* separate component views from auxiliary views to be able to reliably implement pull-to-refresh- and RTL-related
|
||||
|
||||
+17
-18
@@ -182,16 +182,18 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
|
||||
UIViewAnimationCurve curve =
|
||||
(UIViewAnimationCurve)[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue];
|
||||
CGRect keyboardEndFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
CGRect keyboardBeginFrame = [notification.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
|
||||
|
||||
CGPoint absoluteViewOrigin = [self convertPoint:self.bounds.origin toView:nil];
|
||||
CGFloat scrollViewLowerY = isInverted ? absoluteViewOrigin.y : absoluteViewOrigin.y + self.bounds.size.height;
|
||||
|
||||
UIEdgeInsets newEdgeInsets = _scrollView.contentInset;
|
||||
CGFloat inset = MAX(scrollViewLowerY - keyboardEndFrame.origin.y, 0);
|
||||
const auto &props = static_cast<const ScrollViewProps &>(*_props);
|
||||
if (isInverted) {
|
||||
newEdgeInsets.top = MAX(inset, _scrollView.contentInset.top);
|
||||
newEdgeInsets.top = MAX(inset, props.contentInset.top);
|
||||
} else {
|
||||
newEdgeInsets.bottom = MAX(inset, _scrollView.contentInset.bottom);
|
||||
newEdgeInsets.bottom = MAX(inset, props.contentInset.bottom);
|
||||
}
|
||||
|
||||
CGPoint newContentOffset = _scrollView.contentOffset;
|
||||
@@ -203,21 +205,18 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
|
||||
from:self
|
||||
forEvent:nil]) {
|
||||
if (CGRectEqualToRect(_firstResponderFocus, CGRectNull)) {
|
||||
// Text input view is outside of the scroll view.
|
||||
return;
|
||||
}
|
||||
|
||||
CGRect viewIntersection = CGRectIntersection(self.firstResponderFocus, keyboardEndFrame);
|
||||
|
||||
if (CGRectIsNull(viewIntersection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inner text field focused
|
||||
CGFloat focusEnd = CGRectGetMaxY(self.firstResponderFocus);
|
||||
if (focusEnd > keyboardEndFrame.origin.y) {
|
||||
// Text field active region is below visible area with keyboard - update diff to bring into view
|
||||
contentDiff = keyboardEndFrame.origin.y - focusEnd;
|
||||
UIView *inputAccessoryView = _firstResponderViewOutsideScrollView.inputAccessoryView;
|
||||
if (inputAccessoryView) {
|
||||
// Text input view is within the inputAccessoryView.
|
||||
contentDiff = keyboardEndFrame.origin.y - keyboardBeginFrame.origin.y;
|
||||
}
|
||||
} else {
|
||||
// Inner text field focused
|
||||
CGFloat focusEnd = CGRectGetMaxY(self.firstResponderFocus);
|
||||
if (focusEnd > keyboardEndFrame.origin.y) {
|
||||
// Text field active region is below visible area with keyboard - update diff to bring into view
|
||||
contentDiff = keyboardEndFrame.origin.y - focusEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +242,7 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
|
||||
animations:^{
|
||||
self->_scrollView.contentInset = newEdgeInsets;
|
||||
self->_scrollView.verticalScrollIndicatorInsets = newEdgeInsets;
|
||||
[self scrollToOffset:newContentOffset animated:NO];
|
||||
[self scrollTo:newContentOffset.x y:newContentOffset.y animated:NO];
|
||||
}
|
||||
completion:nil];
|
||||
}
|
||||
|
||||
+57
-3
@@ -61,6 +61,13 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
||||
*/
|
||||
BOOL _comingFromJS;
|
||||
BOOL _didMoveToWindow;
|
||||
|
||||
/*
|
||||
* Newly initialized default typing attributes contain a no-op NSParagraphStyle and NSShadow. These cause inequality
|
||||
* between the AttributedString backing the input and those generated from state. We store these attributes to make
|
||||
* later comparison insensitive to them.
|
||||
*/
|
||||
NSDictionary<NSAttributedStringKey, id> *_originalTypingAttributes;
|
||||
}
|
||||
|
||||
#pragma mark - UIView overrides
|
||||
@@ -76,6 +83,7 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
||||
_ignoreNextTextInputCall = NO;
|
||||
_comingFromJS = NO;
|
||||
_didMoveToWindow = NO;
|
||||
_originalTypingAttributes = [_backedTextInputView.typingAttributes copy];
|
||||
|
||||
[self addSubview:_backedTextInputView];
|
||||
[self initializeReturnKeyType];
|
||||
@@ -84,6 +92,22 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateEventEmitter:(const EventEmitter::Shared &)eventEmitter
|
||||
{
|
||||
[super updateEventEmitter:eventEmitter];
|
||||
|
||||
NSMutableDictionary<NSAttributedStringKey, id> *defaultAttributes =
|
||||
[_backedTextInputView.defaultTextAttributes mutableCopy];
|
||||
|
||||
#if !TARGET_OS_MACCATALYST
|
||||
RCTWeakEventEmitterWrapper *eventEmitterWrapper = [RCTWeakEventEmitterWrapper new];
|
||||
eventEmitterWrapper.eventEmitter = _eventEmitter;
|
||||
defaultAttributes[RCTAttributedStringEventEmitterKey] = eventEmitterWrapper;
|
||||
#endif
|
||||
|
||||
_backedTextInputView.defaultTextAttributes = defaultAttributes;
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
[super didMoveToWindow];
|
||||
@@ -104,6 +128,7 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
||||
{
|
||||
if (![self isDescendantOfView:scrollView.scrollView] || !_backedTextInputView.isFirstResponder) {
|
||||
// View is outside scroll view or it's not a first responder.
|
||||
scrollView.firstResponderViewOutsideScrollView = _backedTextInputView;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -236,8 +261,13 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
||||
}
|
||||
|
||||
if (newTextInputProps.textAttributes != oldTextInputProps.textAttributes) {
|
||||
_backedTextInputView.defaultTextAttributes =
|
||||
NSMutableDictionary<NSAttributedStringKey, id> *defaultAttributes =
|
||||
RCTNSTextAttributesFromTextAttributes(newTextInputProps.getEffectiveTextAttributes(RCTFontSizeMultiplier()));
|
||||
#if !TARGET_OS_MACCATALYST
|
||||
defaultAttributes[RCTAttributedStringEventEmitterKey] =
|
||||
_backedTextInputView.defaultTextAttributes[RCTAttributedStringEventEmitterKey];
|
||||
#endif
|
||||
_backedTextInputView.defaultTextAttributes = defaultAttributes;
|
||||
}
|
||||
|
||||
if (newTextInputProps.selectionColor != oldTextInputProps.selectionColor) {
|
||||
@@ -421,6 +451,12 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
||||
if (_comingFromJS) {
|
||||
return;
|
||||
}
|
||||
|
||||
// T207198334: Setting a new AttributedString (_comingFromJS) will trigger a selection change before the backing
|
||||
// string is updated, so indicies won't point to what we want yet. Only respond to user selection change, and let
|
||||
// `_setAttributedString` handle updating typing attributes if content changes.
|
||||
[self _updateTypingAttributes];
|
||||
|
||||
const auto &props = static_cast<const TextInputProps &>(*_props);
|
||||
if (props.traits.multiline && ![_lastStringStateWasUpdatedWith isEqual:_backedTextInputView.attributedText]) {
|
||||
[self textInputDidChange];
|
||||
@@ -674,9 +710,26 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
||||
[_backedTextInputView scrollRangeToVisible:NSMakeRange(offsetStart, 0)];
|
||||
}
|
||||
[self _restoreTextSelection];
|
||||
[self _updateTypingAttributes];
|
||||
_lastStringStateWasUpdatedWith = attributedString;
|
||||
}
|
||||
|
||||
// Ensure that newly typed text will inherit any custom attributes. We follow the logic of RN Android, where attributes
|
||||
// to the left of the cursor are copied into new text, unless we are at the start of the field, in which case we will
|
||||
// copy the attributes from text to the right. This allows consistency between backed input and new AttributedText
|
||||
// https://github.com/facebook/react-native/blob/3102a58df38d96f3dacef0530e4dbb399037fcd2/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/SetSpanOperation.kt#L30
|
||||
- (void)_updateTypingAttributes
|
||||
{
|
||||
if (_backedTextInputView.attributedText.length > 0 && _backedTextInputView.selectedTextRange != nil) {
|
||||
NSUInteger offsetStart = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
|
||||
toPosition:_backedTextInputView.selectedTextRange.start];
|
||||
|
||||
NSUInteger samplePoint = offsetStart == 0 ? 0 : offsetStart - 1;
|
||||
_backedTextInputView.typingAttributes = [_backedTextInputView.attributedText attributesAtIndex:samplePoint
|
||||
effectiveRange:NULL];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_setMultiline:(BOOL)multiline
|
||||
{
|
||||
[_backedTextInputView removeFromSuperview];
|
||||
@@ -732,9 +785,10 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
|
||||
_backedTextInputView.markedTextRange || _backedTextInputView.isSecureTextEntry || fontHasBeenUpdatedBySystem;
|
||||
|
||||
if (shouldFallbackToBareTextComparison) {
|
||||
return ([newText.string isEqualToString:oldText.string]);
|
||||
return [newText.string isEqualToString:oldText.string];
|
||||
} else {
|
||||
return ([newText isEqualToAttributedString:oldText]);
|
||||
return RCTIsAttributedStringEffectivelySame(
|
||||
newText, oldText, _originalTypingAttributes, static_cast<const TextInputProps &>(*_props).textAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-9
@@ -833,6 +833,8 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
|
||||
_backgroundColorLayer.mask = maskLayer;
|
||||
_backgroundColorLayer.cornerRadius = 0;
|
||||
}
|
||||
|
||||
[_backgroundColorLayer removeAllAnimations];
|
||||
}
|
||||
|
||||
// borders
|
||||
@@ -1001,15 +1003,10 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
|
||||
}
|
||||
|
||||
// clipping
|
||||
self.currentContainerView.layer.mask = nil;
|
||||
if (self.currentContainerView.clipsToBounds) {
|
||||
BOOL clipToPaddingBox = ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox();
|
||||
if (clipToPaddingBox) {
|
||||
CALayer *maskLayer = [self createMaskLayer:RCTCGRectFromRect(_layoutMetrics.getPaddingFrame())
|
||||
cornerInsets:RCTGetCornerInsets(
|
||||
RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii),
|
||||
RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths))];
|
||||
self.currentContainerView.layer.mask = maskLayer;
|
||||
} else {
|
||||
if (!clipToPaddingBox) {
|
||||
if (borderMetrics.borderRadii.isUniform()) {
|
||||
self.currentContainerView.layer.cornerRadius = borderMetrics.borderRadii.topLeft.horizontal;
|
||||
} else {
|
||||
@@ -1031,9 +1028,17 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
|
||||
subview.layer.mask = [self createMaskLayer:subview.bounds cornerInsets:cornerInsets];
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
!borderMetrics.borderWidths.isUniform() || borderMetrics.borderWidths.left != 0 ||
|
||||
!borderMetrics.borderRadii.isUniform()) {
|
||||
CALayer *maskLayer = [self createMaskLayer:RCTCGRectFromRect(_layoutMetrics.getPaddingFrame())
|
||||
cornerInsets:RCTGetCornerInsets(
|
||||
RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii),
|
||||
RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths))];
|
||||
self.currentContainerView.layer.mask = maskLayer;
|
||||
} else {
|
||||
self.currentContainerView.layer.cornerRadius = borderMetrics.borderRadii.topLeft.horizontal;
|
||||
}
|
||||
} else {
|
||||
self.currentContainerView.layer.mask = nil;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
typedef UIFont * (^RCTFontHandler)(CGFloat fontSize, NSString *fontWeightDescription);
|
||||
typedef CGFloat RCTFontWeight;
|
||||
|
||||
/**
|
||||
* React Native will use the System font for rendering by default. If you want to
|
||||
@@ -19,6 +20,7 @@ typedef UIFont * (^RCTFontHandler)(CGFloat fontSize, NSString *fontWeightDescrip
|
||||
*/
|
||||
RCT_EXTERN void RCTSetDefaultFontHandler(RCTFontHandler handler);
|
||||
RCT_EXTERN BOOL RCTHasFontHandlerSet(void);
|
||||
RCT_EXTERN RCTFontWeight RCTGetFontWeight(UIFont *font);
|
||||
|
||||
@interface RCTFont : NSObject
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
typedef CGFloat RCTFontWeight;
|
||||
static RCTFontWeight weightOfFont(UIFont *font)
|
||||
RCTFontWeight RCTGetFontWeight(UIFont *font)
|
||||
{
|
||||
static NSArray<NSString *> *weightSuffixes;
|
||||
static NSArray<NSNumber *> *fontWeights;
|
||||
@@ -405,7 +404,7 @@ RCT_ARRAY_CONVERTER(RCTFontVariantDescriptor)
|
||||
if (font) {
|
||||
familyName = font.familyName ?: defaultFontFamily;
|
||||
fontSize = font.pointSize ?: defaultFontSize;
|
||||
fontWeight = weightOfFont(font);
|
||||
fontWeight = RCTGetFontWeight(font);
|
||||
isItalic = isItalicFont(font);
|
||||
isCondensed = isCondensedFont(font);
|
||||
}
|
||||
@@ -453,7 +452,7 @@ RCT_ARRAY_CONVERTER(RCTFontVariantDescriptor)
|
||||
// It's actually a font name, not a font family name,
|
||||
// but we'll do what was meant, not what was said.
|
||||
familyName = font.familyName;
|
||||
fontWeight = weight ? fontWeight : weightOfFont(font);
|
||||
fontWeight = weight ? fontWeight : RCTGetFontWeight(font);
|
||||
isItalic = style ? isItalic : isItalicFont(font);
|
||||
isCondensed = isCondensedFont(font);
|
||||
} else {
|
||||
@@ -476,7 +475,7 @@ RCT_ARRAY_CONVERTER(RCTFontVariantDescriptor)
|
||||
for (NSString *name in names) {
|
||||
UIFont *match = [UIFont fontWithName:name size:fontSize];
|
||||
if (isItalic == isItalicFont(match) && isCondensed == isCondensedFont(match)) {
|
||||
CGFloat testWeight = weightOfFont(match);
|
||||
CGFloat testWeight = RCTGetFontWeight(match);
|
||||
if (ABS(testWeight - fontWeight) < ABS(closestWeight - fontWeight)) {
|
||||
font = match;
|
||||
closestWeight = testWeight;
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
@property (nonatomic, assign) BOOL inverted;
|
||||
/** Focus area of newly-activated text input relative to the window to compare against UIKeyboardFrameBegin/End */
|
||||
@property (nonatomic, assign) CGRect firstResponderFocus;
|
||||
/** newly-activated text input outside of the scroll view */
|
||||
@property (nonatomic, weak) UIView *firstResponderViewOutsideScrollView;
|
||||
|
||||
// NOTE: currently these event props are only declared so we can export the
|
||||
// event names to JS - we don't call the blocks directly because scroll events
|
||||
|
||||
@@ -338,6 +338,12 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
|
||||
if (!didFocusExternalTextField && focusEnd > endFrame.origin.y) {
|
||||
// Text field active region is below visible area with keyboard - update diff to bring into view
|
||||
contentDiff = endFrame.origin.y - focusEnd;
|
||||
} else {
|
||||
UIView *inputAccessoryView = _firstResponderViewOutsideScrollView.inputAccessoryView;
|
||||
if (inputAccessoryView) {
|
||||
// Text input view is within the inputAccessoryView.
|
||||
contentDiff = endFrame.origin.y - beginFrame.origin.y;
|
||||
}
|
||||
}
|
||||
} else if (endFrame.origin.y <= beginFrame.origin.y) {
|
||||
// Keyboard opened for other reason
|
||||
|
||||
@@ -3306,7 +3306,6 @@ public final class com/facebook/react/modules/core/TimingModule : com/facebook/f
|
||||
public fun createTimer (DDDZ)V
|
||||
public fun deleteTimer (D)V
|
||||
public fun emitTimeDriftWarning (Ljava/lang/String;)V
|
||||
public fun initialize ()V
|
||||
public fun invalidate ()V
|
||||
public fun setSendIdleEvents (Z)V
|
||||
}
|
||||
@@ -3798,13 +3797,13 @@ public abstract class com/facebook/react/packagerconnection/NotificationOnlyHand
|
||||
public final fun onRequest (Ljava/lang/Object;Lcom/facebook/react/packagerconnection/Responder;)V
|
||||
}
|
||||
|
||||
public final class com/facebook/react/packagerconnection/PackagerConnectionSettings {
|
||||
public class com/facebook/react/packagerconnection/PackagerConnectionSettings {
|
||||
public fun <init> (Landroid/content/Context;)V
|
||||
public final fun getAdditionalOptionsForPackager ()Ljava/util/Map;
|
||||
public final fun getDebugServerHost ()Ljava/lang/String;
|
||||
public fun getDebugServerHost ()Ljava/lang/String;
|
||||
public final fun getPackageName ()Ljava/lang/String;
|
||||
public final fun setAdditionalOptionForPackager (Ljava/lang/String;Ljava/lang/String;)V
|
||||
public final fun setDebugServerHost (Ljava/lang/String;)V
|
||||
public fun setDebugServerHost (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class com/facebook/react/packagerconnection/ReconnectingWebSocket : okhttp3/WebSocketListener {
|
||||
@@ -7281,6 +7280,8 @@ public final class com/facebook/react/views/scroll/ReactScrollViewHelper {
|
||||
public static final field SNAP_ALIGNMENT_END I
|
||||
public static final field SNAP_ALIGNMENT_START I
|
||||
public static final fun addScrollListener (Lcom/facebook/react/views/scroll/ReactScrollViewHelper$ScrollListener;)V
|
||||
public static final fun dispatchMomentumEndOnAnimationEnd (Landroid/view/ViewGroup;)V
|
||||
public static final fun emitLayoutChangeEvent (Landroid/view/ViewGroup;)V
|
||||
public static final fun emitLayoutEvent (Landroid/view/ViewGroup;)V
|
||||
public static final fun emitScrollBeginDragEvent (Landroid/view/ViewGroup;)V
|
||||
public static final fun emitScrollEndDragEvent (Landroid/view/ViewGroup;FF)V
|
||||
|
||||
@@ -557,7 +557,6 @@ android {
|
||||
listOf(
|
||||
"src/main/res/devsupport",
|
||||
"src/main/res/shell",
|
||||
"src/main/res/views/alert",
|
||||
"src/main/res/views/modal",
|
||||
"src/main/res/views/uimanager"))
|
||||
java.exclude("com/facebook/react/processing")
|
||||
|
||||
@@ -31,13 +31,27 @@ if(CCACHE_FOUND)
|
||||
endif(CCACHE_FOUND)
|
||||
|
||||
set(BUILD_DIR ${PROJECT_BUILD_DIR})
|
||||
if(CMAKE_HOST_WIN32)
|
||||
string(REPLACE "\\" "/" BUILD_DIR ${BUILD_DIR})
|
||||
endif()
|
||||
file(TO_CMAKE_PATH "${BUILD_DIR}" BUILD_DIR)
|
||||
file(TO_CMAKE_PATH "${REACT_ANDROID_DIR}" REACT_ANDROID_DIR)
|
||||
|
||||
file(GLOB input_SRC CONFIGURE_DEPENDS
|
||||
*.cpp
|
||||
${BUILD_DIR}/generated/autolinking/src/main/jni/*.cpp)
|
||||
if (PROJECT_ROOT_DIR)
|
||||
# This empty `if` is just to silence a CMake warning and make sure the `PROJECT_ROOT_DIR`
|
||||
# variable is defined if user need to access it.
|
||||
endif ()
|
||||
|
||||
file(GLOB override_cpp_SRC CONFIGURE_DEPENDS *.cpp)
|
||||
# We check if the user is providing a custom OnLoad.cpp file. If so, we pick that
|
||||
# for compilation. Otherwise we fallback to using the `default-app-setup/OnLoad.cpp`
|
||||
# file instead.
|
||||
if(override_cpp_SRC)
|
||||
file(GLOB input_SRC CONFIGURE_DEPENDS
|
||||
*.cpp
|
||||
${BUILD_DIR}/generated/autolinking/src/main/jni/*.cpp)
|
||||
else()
|
||||
file(GLOB input_SRC CONFIGURE_DEPENDS
|
||||
${REACT_ANDROID_DIR}/cmake-utils/default-app-setup/*.cpp
|
||||
${BUILD_DIR}/generated/autolinking/src/main/jni/*.cpp)
|
||||
endif()
|
||||
|
||||
add_library(${CMAKE_PROJECT_NAME} SHARED ${input_SRC})
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VERSION_NAME=0.76.0-rc.4
|
||||
VERSION_NAME=0.76.6
|
||||
react.internal.publishingGroup=com.facebook.react
|
||||
|
||||
android.useAndroidX=true
|
||||
|
||||
@@ -36,9 +36,13 @@ fun getSDKPath(): String {
|
||||
fun getSDKManagerPath(): String {
|
||||
val metaSdkManagerPath = File("${getSDKPath()}/cmdline-tools/latest/bin/sdkmanager")
|
||||
val ossSdkManagerPath = File("${getSDKPath()}/tools/bin/sdkmanager")
|
||||
val windowsMetaSdkManagerPath = File("${getSDKPath()}/cmdline-tools/latest/bin/sdkmanager.bat")
|
||||
val windowsOssSdkManagerPath = File("${getSDKPath()}/tools/bin/sdkmanager.bat")
|
||||
return when {
|
||||
metaSdkManagerPath.exists() -> metaSdkManagerPath.absolutePath
|
||||
windowsMetaSdkManagerPath.exists() -> windowsMetaSdkManagerPath.absolutePath
|
||||
ossSdkManagerPath.exists() -> ossSdkManagerPath.absolutePath
|
||||
windowsOssSdkManagerPath.exists() -> windowsOssSdkManagerPath.absolutePath
|
||||
else -> throw GradleException("Could not find sdkmanager executable.")
|
||||
}
|
||||
}
|
||||
|
||||
+16
-21
@@ -124,15 +124,11 @@ public abstract class HeadlessJsTaskService extends Service implements HeadlessJ
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
ReactContext reactContext = getReactContext();
|
||||
|
||||
if (getReactNativeHost().hasInstance()) {
|
||||
ReactInstanceManager reactInstanceManager = getReactNativeHost().getReactInstanceManager();
|
||||
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
|
||||
if (reactContext != null) {
|
||||
HeadlessJsTaskContext headlessJsTaskContext =
|
||||
HeadlessJsTaskContext.getInstance(reactContext);
|
||||
headlessJsTaskContext.removeTaskEventListener(this);
|
||||
}
|
||||
if (reactContext != null) {
|
||||
HeadlessJsTaskContext headlessJsTaskContext = HeadlessJsTaskContext.getInstance(reactContext);
|
||||
headlessJsTaskContext.removeTaskEventListener(this);
|
||||
}
|
||||
if (sWakeLock != null) {
|
||||
sWakeLock.release();
|
||||
@@ -183,9 +179,18 @@ public abstract class HeadlessJsTaskService extends Service implements HeadlessJ
|
||||
}
|
||||
|
||||
private void createReactContextAndScheduleTask(final HeadlessJsTaskConfig taskConfig) {
|
||||
final ReactHost reactHost = getReactHost();
|
||||
|
||||
if (reactHost == null) { // old arch
|
||||
if (ReactFeatureFlags.enableBridgelessArchitecture) {
|
||||
final ReactHost reactHost = getReactHost();
|
||||
reactHost.addReactInstanceEventListener(
|
||||
new ReactInstanceEventListener() {
|
||||
@Override
|
||||
public void onReactContextInitialized(@NonNull ReactContext reactContext) {
|
||||
invokeStartTask(reactContext, taskConfig);
|
||||
reactHost.removeReactInstanceEventListener(this);
|
||||
}
|
||||
});
|
||||
reactHost.start();
|
||||
} else {
|
||||
final ReactInstanceManager reactInstanceManager =
|
||||
getReactNativeHost().getReactInstanceManager();
|
||||
|
||||
@@ -198,16 +203,6 @@ public abstract class HeadlessJsTaskService extends Service implements HeadlessJ
|
||||
}
|
||||
});
|
||||
reactInstanceManager.createReactContextInBackground();
|
||||
} else { // new arch
|
||||
reactHost.addReactInstanceEventListener(
|
||||
new ReactInstanceEventListener() {
|
||||
@Override
|
||||
public void onReactContextInitialized(@NonNull ReactContext reactContext) {
|
||||
invokeStartTask(reactContext, taskConfig);
|
||||
reactHost.removeReactInstanceEventListener(this);
|
||||
}
|
||||
});
|
||||
reactHost.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -12,6 +12,7 @@ import androidx.annotation.AnyThread
|
||||
import androidx.annotation.UiThread
|
||||
import com.facebook.infer.annotation.ThreadConfined
|
||||
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
||||
import com.facebook.react.uimanager.events.EventDispatcher
|
||||
|
||||
@OptIn(UnstableReactNativeAPI::class)
|
||||
public interface UIManager : PerformanceCounter {
|
||||
@@ -78,7 +79,7 @@ public interface UIManager : PerformanceCounter {
|
||||
public fun dispatchCommand(reactTag: Int, commandId: String, commandArgs: ReadableArray?)
|
||||
|
||||
/** @return the [EventDispatcher] object that is used by this class. */
|
||||
public fun <T> getEventDispatcher(): T
|
||||
public val eventDispatcher: EventDispatcher
|
||||
|
||||
/**
|
||||
* Used by native animated module to bypass the process of updating the values through the shadow
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ protected constructor(
|
||||
packages,
|
||||
jsMainModuleName,
|
||||
bundleAssetName ?: "index",
|
||||
null,
|
||||
jsBundleFile,
|
||||
isHermesEnabled ?: true,
|
||||
useDeveloperSupport,
|
||||
)
|
||||
|
||||
+8
-3
@@ -449,12 +449,18 @@ public class FabricUIManager
|
||||
|
||||
@Override
|
||||
public void markActiveTouchForTag(int surfaceId, int reactTag) {
|
||||
mMountingManager.getSurfaceManager(surfaceId).markActiveTouchForTag(reactTag);
|
||||
SurfaceMountingManager surfaceMountingManager = mMountingManager.getSurfaceManager(surfaceId);
|
||||
if (surfaceMountingManager != null) {
|
||||
surfaceMountingManager.markActiveTouchForTag(reactTag);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sweepActiveTouchForTag(int surfaceId, int reactTag) {
|
||||
mMountingManager.getSurfaceManager(surfaceId).sweepActiveTouchForTag(reactTag);
|
||||
SurfaceMountingManager surfaceMountingManager = mMountingManager.getSurfaceManager(surfaceId);
|
||||
if (surfaceMountingManager != null) {
|
||||
surfaceMountingManager.sweepActiveTouchForTag(reactTag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1021,7 +1027,6 @@ public class FabricUIManager
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public EventDispatcher getEventDispatcher() {
|
||||
return mEventDispatcher;
|
||||
}
|
||||
|
||||
+1
-7
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<6cc52570dd571ddc792a0fd842c05dd9>>
|
||||
* @generated SignedSource<<89491eb63a7ca59b17419ed4432a4f88>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -34,12 +34,6 @@ public object ReactNativeFeatureFlags {
|
||||
@JvmStatic
|
||||
public fun commonTestFlag(): Boolean = accessor.commonTestFlag()
|
||||
|
||||
/**
|
||||
* Adds support for recursively processing commits that mount synchronously (Android only).
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun allowRecursiveCommitsWithSynchronousMountOnAndroid(): Boolean = accessor.allowRecursiveCommitsWithSynchronousMountOnAndroid()
|
||||
|
||||
/**
|
||||
* When enabled, the RuntimeScheduler processing the event loop will batch all rendering updates and dispatch them together at the end of each iteration of the loop.
|
||||
*/
|
||||
|
||||
+1
-11
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<eca842a1b1c823b72136c625b3bfd16e>>
|
||||
* @generated SignedSource<<9f741ec3df7cd5ecd8d5c3c099c86aba>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -21,7 +21,6 @@ package com.facebook.react.internal.featureflags
|
||||
|
||||
public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccessor {
|
||||
private var commonTestFlagCache: Boolean? = null
|
||||
private var allowRecursiveCommitsWithSynchronousMountOnAndroidCache: Boolean? = null
|
||||
private var batchRenderingUpdatesInEventLoopCache: Boolean? = null
|
||||
private var completeReactInstanceCreationOnBgThreadOnAndroidCache: Boolean? = null
|
||||
private var destroyFabricSurfacesInReactInstanceManagerCache: Boolean? = null
|
||||
@@ -78,15 +77,6 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun allowRecursiveCommitsWithSynchronousMountOnAndroid(): Boolean {
|
||||
var cached = allowRecursiveCommitsWithSynchronousMountOnAndroidCache
|
||||
if (cached == null) {
|
||||
cached = ReactNativeFeatureFlagsCxxInterop.allowRecursiveCommitsWithSynchronousMountOnAndroid()
|
||||
allowRecursiveCommitsWithSynchronousMountOnAndroidCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun batchRenderingUpdatesInEventLoop(): Boolean {
|
||||
var cached = batchRenderingUpdatesInEventLoopCache
|
||||
if (cached == null) {
|
||||
|
||||
+1
-3
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<ad54375c4ae3be2f377260887ae5aaf9>>
|
||||
* @generated SignedSource<<774337b6aee6f528b0852704271ed96f>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -30,8 +30,6 @@ public object ReactNativeFeatureFlagsCxxInterop {
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun commonTestFlag(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun allowRecursiveCommitsWithSynchronousMountOnAndroid(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun batchRenderingUpdatesInEventLoop(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun completeReactInstanceCreationOnBgThreadOnAndroid(): Boolean
|
||||
|
||||
+1
-3
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<2a0cd5a4875a54bb724e5765ffe7753e>>
|
||||
* @generated SignedSource<<43c4ba7a6c4f5a12ada181c081f91bfc>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -25,8 +25,6 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
|
||||
|
||||
override fun commonTestFlag(): Boolean = false
|
||||
|
||||
override fun allowRecursiveCommitsWithSynchronousMountOnAndroid(): Boolean = false
|
||||
|
||||
override fun batchRenderingUpdatesInEventLoop(): Boolean = false
|
||||
|
||||
override fun completeReactInstanceCreationOnBgThreadOnAndroid(): Boolean = false
|
||||
|
||||
+1
-12
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<d02af2a8ef015c57d45aba8280539606>>
|
||||
* @generated SignedSource<<0ca6ebf7ef1418d721b6f183f89b96a2>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -25,7 +25,6 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
|
||||
private val accessedFeatureFlags = mutableSetOf<String>()
|
||||
|
||||
private var commonTestFlagCache: Boolean? = null
|
||||
private var allowRecursiveCommitsWithSynchronousMountOnAndroidCache: Boolean? = null
|
||||
private var batchRenderingUpdatesInEventLoopCache: Boolean? = null
|
||||
private var completeReactInstanceCreationOnBgThreadOnAndroidCache: Boolean? = null
|
||||
private var destroyFabricSurfacesInReactInstanceManagerCache: Boolean? = null
|
||||
@@ -83,16 +82,6 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun allowRecursiveCommitsWithSynchronousMountOnAndroid(): Boolean {
|
||||
var cached = allowRecursiveCommitsWithSynchronousMountOnAndroidCache
|
||||
if (cached == null) {
|
||||
cached = currentProvider.allowRecursiveCommitsWithSynchronousMountOnAndroid()
|
||||
accessedFeatureFlags.add("allowRecursiveCommitsWithSynchronousMountOnAndroid")
|
||||
allowRecursiveCommitsWithSynchronousMountOnAndroidCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun batchRenderingUpdatesInEventLoop(): Boolean {
|
||||
var cached = batchRenderingUpdatesInEventLoopCache
|
||||
if (cached == null) {
|
||||
|
||||
+1
-3
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<92b1214e3a526d7c67dcc7b0c2a131de>>
|
||||
* @generated SignedSource<<94e1e69be22ec978859e3f242610f21b>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -25,8 +25,6 @@ import com.facebook.proguard.annotations.DoNotStrip
|
||||
public interface ReactNativeFeatureFlagsProvider {
|
||||
@DoNotStrip public fun commonTestFlag(): Boolean
|
||||
|
||||
@DoNotStrip public fun allowRecursiveCommitsWithSynchronousMountOnAndroid(): Boolean
|
||||
|
||||
@DoNotStrip public fun batchRenderingUpdatesInEventLoop(): Boolean
|
||||
|
||||
@DoNotStrip public fun completeReactInstanceCreationOnBgThreadOnAndroid(): Boolean
|
||||
|
||||
+2
@@ -65,6 +65,7 @@ public open class JavaTimerManager(
|
||||
|
||||
init {
|
||||
reactApplicationContext.addLifecycleEventListener(this)
|
||||
HeadlessJsTaskContext.getInstance(reactApplicationContext).addTaskEventListener(this)
|
||||
}
|
||||
|
||||
override fun onHostPause() {
|
||||
@@ -103,6 +104,7 @@ public open class JavaTimerManager(
|
||||
}
|
||||
|
||||
public open fun onInstanceDestroy() {
|
||||
HeadlessJsTaskContext.getInstance(reactApplicationContext).removeTaskEventListener(this)
|
||||
reactApplicationContext.removeLifecycleEventListener(this)
|
||||
clearFrameCallback()
|
||||
clearChoreographerIdleCallback()
|
||||
|
||||
-8
@@ -12,7 +12,6 @@ import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.WritableArray
|
||||
import com.facebook.react.common.annotations.VisibleForTesting
|
||||
import com.facebook.react.devsupport.interfaces.DevSupportManager
|
||||
import com.facebook.react.jstasks.HeadlessJsTaskContext
|
||||
import com.facebook.react.module.annotations.ReactModule
|
||||
|
||||
/** Native module for JS timer execution. Timers fire on frame boundaries. */
|
||||
@@ -24,11 +23,6 @@ public class TimingModule(
|
||||
private val javaTimerManager: JavaTimerManager =
|
||||
JavaTimerManager(reactContext, this, ReactChoreographer.getInstance(), devSupportManager)
|
||||
|
||||
override fun initialize() {
|
||||
HeadlessJsTaskContext.getInstance(getReactApplicationContext())
|
||||
.addTaskEventListener(javaTimerManager)
|
||||
}
|
||||
|
||||
override fun createTimer(
|
||||
callbackIDDouble: Double,
|
||||
durationDouble: Double,
|
||||
@@ -68,8 +62,6 @@ public class TimingModule(
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
val headlessJsTaskContext = HeadlessJsTaskContext.getInstance(getReactApplicationContext())
|
||||
headlessJsTaskContext.removeTaskEventListener(javaTimerManager)
|
||||
javaTimerManager.onInstanceDestroy()
|
||||
}
|
||||
|
||||
|
||||
+5
-57
@@ -12,20 +12,11 @@ import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.view.AccessibilityDelegateCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.react.R;
|
||||
|
||||
/** A fragment used to display the dialog. */
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
@@ -75,55 +66,15 @@ public class AlertFragment extends DialogFragment implements DialogInterface.OnC
|
||||
return isAppCompat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a custom dialog title View that has the role of "Heading" and focusable for
|
||||
* accessibility purposes.
|
||||
*
|
||||
* @returns accessible TextView title
|
||||
*/
|
||||
private static View getAccessibleTitle(Context activityContext, String titleText) {
|
||||
LayoutInflater inflater = LayoutInflater.from(activityContext);
|
||||
|
||||
// This layout matches the sizing and styling of AlertDialog's title_template (minus the icon)
|
||||
// since the whole thing gets tossed out when setting a custom title
|
||||
View titleContainer = inflater.inflate(R.layout.alert_title_layout, null);
|
||||
|
||||
TextView accessibleTitle =
|
||||
Assertions.assertNotNull(titleContainer.findViewById(R.id.alert_title));
|
||||
accessibleTitle.setText(titleText);
|
||||
accessibleTitle.setFocusable(true);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
accessibleTitle.setAccessibilityHeading(true);
|
||||
} else {
|
||||
ViewCompat.setAccessibilityDelegate(
|
||||
accessibleTitle,
|
||||
new AccessibilityDelegateCompat() {
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(
|
||||
View view, AccessibilityNodeInfoCompat info) {
|
||||
super.onInitializeAccessibilityNodeInfo(accessibleTitle, info);
|
||||
info.setHeading(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return titleContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a dialog compatible only with AppCompat activities. This function should be kept in
|
||||
* sync with {@link createAppDialog}.
|
||||
*/
|
||||
private static Dialog createAppCompatDialog(
|
||||
Context activityContext, Bundle arguments, DialogInterface.OnClickListener fragment) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activityContext);
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(activityContext).setTitle(arguments.getString(ARG_TITLE));
|
||||
|
||||
if (arguments.containsKey(ARG_TITLE)) {
|
||||
String title = Assertions.assertNotNull(arguments.getString(ARG_TITLE));
|
||||
View accessibleTitle = getAccessibleTitle(activityContext, title);
|
||||
builder.setCustomTitle(accessibleTitle);
|
||||
}
|
||||
if (arguments.containsKey(ARG_BUTTON_POSITIVE)) {
|
||||
builder.setPositiveButton(arguments.getString(ARG_BUTTON_POSITIVE), fragment);
|
||||
}
|
||||
@@ -154,13 +105,10 @@ public class AlertFragment extends DialogFragment implements DialogInterface.OnC
|
||||
@Deprecated(since = "0.75.0", forRemoval = true)
|
||||
private static Dialog createAppDialog(
|
||||
Context activityContext, Bundle arguments, DialogInterface.OnClickListener fragment) {
|
||||
android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(activityContext);
|
||||
android.app.AlertDialog.Builder builder =
|
||||
new android.app.AlertDialog.Builder(activityContext)
|
||||
.setTitle(arguments.getString(ARG_TITLE));
|
||||
|
||||
if (arguments.containsKey(ARG_TITLE)) {
|
||||
String title = Assertions.assertNotNull(arguments.getString(ARG_TITLE));
|
||||
View accessibleTitle = getAccessibleTitle(activityContext, title);
|
||||
builder.setCustomTitle(accessibleTitle);
|
||||
}
|
||||
if (arguments.containsKey(ARG_BUTTON_POSITIVE)) {
|
||||
builder.setPositiveButton(arguments.getString(ARG_BUTTON_POSITIVE), fragment);
|
||||
}
|
||||
|
||||
+2
-2
@@ -17,6 +17,6 @@ public class ReactNativeVersion {
|
||||
public static final Map<String, Object> VERSION = MapBuilder.<String, Object>of(
|
||||
"major", 0,
|
||||
"minor", 76,
|
||||
"patch", 0,
|
||||
"prerelease", "rc.4");
|
||||
"patch", 6,
|
||||
"prerelease", null);
|
||||
}
|
||||
|
||||
+2
-2
@@ -15,13 +15,13 @@ import android.preference.PreferenceManager
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.react.modules.systeminfo.AndroidInfoHelpers
|
||||
|
||||
public class PackagerConnectionSettings(private val appContext: Context) {
|
||||
public open class PackagerConnectionSettings(private val appContext: Context) {
|
||||
private val preferences: SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(appContext)
|
||||
public val packageName: String = appContext.packageName
|
||||
private val _additionalOptionsForPackager: MutableMap<String, String> = mutableMapOf()
|
||||
|
||||
public var debugServerHost: String
|
||||
public open var debugServerHost: String
|
||||
get() {
|
||||
// Check host setting first. If empty try to detect emulator type and use default
|
||||
// hostname for those
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ import com.facebook.yoga.YogaConstants
|
||||
public abstract class BaseViewManagerDelegate<T : View, U : BaseViewManagerInterface<T>>(
|
||||
@Suppress("NoHungarianNotation") @JvmField protected val mViewManager: U
|
||||
) : ViewManagerDelegate<T> {
|
||||
override public fun setProperty(view: T, propName: String, value: Any?) {
|
||||
override public fun setProperty(view: T, propName: String?, value: Any?) {
|
||||
when (propName) {
|
||||
ViewProps.ACCESSIBILITY_ACTIONS ->
|
||||
mViewManager.setAccessibilityActions(view, value as ReadableArray?)
|
||||
@@ -104,6 +104,6 @@ public abstract class BaseViewManagerDelegate<T : View, U : BaseViewManagerInter
|
||||
}
|
||||
}
|
||||
|
||||
override public fun receiveCommand(view: T, commandName: String, args: ReadableArray?): Unit =
|
||||
override public fun receiveCommand(view: T, commandName: String?, args: ReadableArray?): Unit =
|
||||
Unit
|
||||
}
|
||||
|
||||
+19
-2
@@ -18,7 +18,24 @@ import com.facebook.react.bridge.ReadableArray
|
||||
* @param <T> the type of the view supported by this delegate </T>
|
||||
*/
|
||||
public interface ViewManagerDelegate<T : View?> {
|
||||
public fun setProperty(view: T, propName: String, value: Any?)
|
||||
|
||||
public fun receiveCommand(view: T, commandName: String, args: ReadableArray?)
|
||||
/**
|
||||
* Sets a property on a view managed by this view manager.
|
||||
*
|
||||
* @param view the view to set the property on
|
||||
* @param propName the name of the property to set (NOTE: should be `String` but is kept as
|
||||
* `String?` to avoid breaking changes)
|
||||
* @param value the value to set the property to
|
||||
*/
|
||||
public fun setProperty(view: T, propName: String?, value: Any?)
|
||||
|
||||
/**
|
||||
* Executes a command from JS to the view
|
||||
*
|
||||
* @param view the view to execute the command on
|
||||
* @param commandName the name of the command to execute (NOTE: should be `String` but is kept as
|
||||
* `String?` to avoid breaking changes)
|
||||
* @param args the arguments to pass to the command
|
||||
*/
|
||||
public fun receiveCommand(view: T, commandName: String?, args: ReadableArray?)
|
||||
}
|
||||
|
||||
+34
-17
@@ -26,6 +26,7 @@ import android.view.WindowManager
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.UiThread
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.react.R
|
||||
import com.facebook.react.bridge.GuardedRunnable
|
||||
import com.facebook.react.bridge.LifecycleEventListener
|
||||
@@ -33,6 +34,7 @@ import com.facebook.react.bridge.ReactContext
|
||||
import com.facebook.react.bridge.UiThreadUtil
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.facebook.react.bridge.WritableNativeMap
|
||||
import com.facebook.react.common.ReactConstants
|
||||
import com.facebook.react.common.annotations.VisibleForTesting
|
||||
import com.facebook.react.config.ReactFeatureFlags
|
||||
import com.facebook.react.uimanager.JSPointerDispatcher
|
||||
@@ -294,7 +296,14 @@ public class ReactModalHostView(context: ThemedReactContext) :
|
||||
* changed. This has the pleasant side-effect of us not having to preface all Modals with "top:
|
||||
* statusBarHeight", since that margin will be included in the FrameLayout.
|
||||
*/
|
||||
get() = FrameLayout(context).apply { addView(dialogRootViewGroup) }
|
||||
get() =
|
||||
FrameLayout(context).apply {
|
||||
addView(dialogRootViewGroup)
|
||||
if (!statusBarTranslucent) {
|
||||
// this is needed to prevent content hiding behind systems bars < API 30
|
||||
this.fitsSystemWindows = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* updateProperties will update the properties that do not require us to recreate the dialog
|
||||
@@ -306,29 +315,37 @@ public class ReactModalHostView(context: ThemedReactContext) :
|
||||
val dialogWindow =
|
||||
checkNotNull(dialog.window) { "dialog must have window when we call updateProperties" }
|
||||
val currentActivity = getCurrentActivity()
|
||||
if (currentActivity == null || currentActivity.isFinishing) {
|
||||
if (currentActivity == null || currentActivity.isFinishing || currentActivity.isDestroyed) {
|
||||
// If the activity has disappeared, then we shouldn't update the window associated to the
|
||||
// Dialog.
|
||||
return
|
||||
}
|
||||
val activityWindow = currentActivity.window
|
||||
if (activityWindow != null) {
|
||||
val activityWindowFlags = activityWindow.attributes.flags
|
||||
if ((activityWindowFlags and WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0) {
|
||||
dialogWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
} else {
|
||||
dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
try {
|
||||
val activityWindow = currentActivity.window
|
||||
if (activityWindow != null) {
|
||||
val activityWindowFlags = activityWindow.attributes.flags
|
||||
if ((activityWindowFlags and WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0) {
|
||||
dialogWindow.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
} else {
|
||||
dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dialogWindow.setStatusBarTranslucency(statusBarTranslucent)
|
||||
dialogWindow.setStatusBarTranslucency(statusBarTranslucent)
|
||||
|
||||
if (transparent) {
|
||||
dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
} else {
|
||||
dialogWindow.setDimAmount(0.5f)
|
||||
dialogWindow.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_DIM_BEHIND, WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
if (transparent) {
|
||||
dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
} else {
|
||||
dialogWindow.setDimAmount(0.5f)
|
||||
dialogWindow.setFlags(
|
||||
WindowManager.LayoutParams.FLAG_DIM_BEHIND, WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// This is to prevent a crash from the following error, without a clear repro steps:
|
||||
// java.lang.IllegalArgumentException: View=DecorView@c94931b[XxxActivity] not attached to
|
||||
// window manager
|
||||
FLog.e(
|
||||
ReactConstants.TAG, "ReactModalHostView: error while setting window flags: ", e.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-3
@@ -1544,12 +1544,20 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
|
||||
DEFAULT_FLING_ANIMATOR.cancel();
|
||||
|
||||
// Update the fling animator with new values
|
||||
DEFAULT_FLING_ANIMATOR
|
||||
.setDuration(ReactScrollViewHelper.getDefaultScrollAnimationDuration(getContext()))
|
||||
.setIntValues(start, end);
|
||||
int duration = ReactScrollViewHelper.getDefaultScrollAnimationDuration(getContext());
|
||||
DEFAULT_FLING_ANIMATOR.setDuration(duration).setIntValues(start, end);
|
||||
|
||||
// Start the animator
|
||||
DEFAULT_FLING_ANIMATOR.start();
|
||||
|
||||
if (mSendMomentumEvents) {
|
||||
int xVelocity = 0;
|
||||
if (duration > 0) {
|
||||
xVelocity = (end - start) / duration;
|
||||
}
|
||||
ReactScrollViewHelper.emitScrollMomentumBeginEvent(this, xVelocity, 0);
|
||||
ReactScrollViewHelper.dispatchMomentumEndOnAnimationEnd(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+11
-3
@@ -1358,12 +1358,20 @@ public class ReactScrollView extends ScrollView
|
||||
DEFAULT_FLING_ANIMATOR.cancel();
|
||||
|
||||
// Update the fling animator with new values
|
||||
DEFAULT_FLING_ANIMATOR
|
||||
.setDuration(ReactScrollViewHelper.getDefaultScrollAnimationDuration(getContext()))
|
||||
.setIntValues(start, end);
|
||||
int duration = ReactScrollViewHelper.getDefaultScrollAnimationDuration(getContext());
|
||||
DEFAULT_FLING_ANIMATOR.setDuration(duration).setIntValues(start, end);
|
||||
|
||||
// Start the animator
|
||||
DEFAULT_FLING_ANIMATOR.start();
|
||||
|
||||
if (mSendMomentumEvents) {
|
||||
int yVelocity = 0;
|
||||
if (duration > 0) {
|
||||
yVelocity = (end - start) / duration;
|
||||
}
|
||||
ReactScrollViewHelper.emitScrollMomentumBeginEvent(this, 0, yVelocity);
|
||||
ReactScrollViewHelper.dispatchMomentumEndOnAnimationEnd(this);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
||||
+25
@@ -412,6 +412,31 @@ public object ReactScrollViewHelper {
|
||||
})
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun <T> dispatchMomentumEndOnAnimationEnd(scrollView: T) where
|
||||
T : HasFlingAnimator?,
|
||||
T : HasScrollEventThrottle?,
|
||||
T : ViewGroup {
|
||||
scrollView
|
||||
.getFlingAnimator()
|
||||
.addListener(
|
||||
object : Animator.AnimatorListener {
|
||||
override fun onAnimationStart(animator: Animator) = Unit
|
||||
|
||||
override fun onAnimationEnd(animator: Animator) {
|
||||
emitScrollMomentumEndEvent(scrollView)
|
||||
animator.removeListener(this)
|
||||
}
|
||||
|
||||
override fun onAnimationCancel(animator: Animator) {
|
||||
emitScrollMomentumEndEvent(scrollView)
|
||||
animator.removeListener(this)
|
||||
}
|
||||
|
||||
override fun onAnimationRepeat(animator: Animator) = Unit
|
||||
})
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun <T> predictFinalScrollPosition(
|
||||
scrollView: T,
|
||||
|
||||
+1
@@ -770,6 +770,7 @@ public class ReactViewGroup extends ViewGroup
|
||||
}
|
||||
}
|
||||
removeViewsInLayout(index - clippedSoFar, 1);
|
||||
invalidate();
|
||||
}
|
||||
removeFromArray(index);
|
||||
}
|
||||
|
||||
@@ -501,27 +501,25 @@ void Binding::schedulerShouldRenderTransactions(
|
||||
return;
|
||||
}
|
||||
|
||||
if (ReactNativeFeatureFlags::
|
||||
allowRecursiveCommitsWithSynchronousMountOnAndroid()) {
|
||||
std::vector<MountingTransaction> pendingTransactions;
|
||||
|
||||
{
|
||||
// Retain the lock to access the pending transactions but not to execute
|
||||
// the mount operations because that method can call into this method
|
||||
// again.
|
||||
std::unique_lock<std::mutex> lock(pendingTransactionsMutex_);
|
||||
pendingTransactions_.swap(pendingTransactions);
|
||||
}
|
||||
std::vector<MountingTransaction> pendingTransactions;
|
||||
|
||||
for (auto& transaction : pendingTransactions) {
|
||||
mountingManager->executeMount(transaction);
|
||||
}
|
||||
} else {
|
||||
{
|
||||
// Retain the lock to access the pending transactions but not to execute
|
||||
// the mount operations because that method can call into this method
|
||||
// again.
|
||||
//
|
||||
// This can be re-entrant when mounting manager triggers state updates
|
||||
// synchronously (this can happen when committing from the UI thread).
|
||||
// This is safe because we're already combining all the transactions for the
|
||||
// same surface ID in a single transaction in the pending transactions list,
|
||||
// so operations won't run out of order.
|
||||
std::unique_lock<std::mutex> lock(pendingTransactionsMutex_);
|
||||
for (auto& transaction : pendingTransactions_) {
|
||||
mountingManager->executeMount(transaction);
|
||||
}
|
||||
pendingTransactions_.clear();
|
||||
pendingTransactions_.swap(pendingTransactions);
|
||||
}
|
||||
|
||||
for (auto& transaction : pendingTransactions) {
|
||||
mountingManager->executeMount(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-15
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<c23b21fca18699470580e54b99de1126>>
|
||||
* @generated SignedSource<<68e5d4ce0ed3c237eeababaa04821101>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -45,12 +45,6 @@ class ReactNativeFeatureFlagsProviderHolder
|
||||
return method(javaProvider_);
|
||||
}
|
||||
|
||||
bool allowRecursiveCommitsWithSynchronousMountOnAndroid() override {
|
||||
static const auto method =
|
||||
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("allowRecursiveCommitsWithSynchronousMountOnAndroid");
|
||||
return method(javaProvider_);
|
||||
}
|
||||
|
||||
bool batchRenderingUpdatesInEventLoop() override {
|
||||
static const auto method =
|
||||
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("batchRenderingUpdatesInEventLoop");
|
||||
@@ -336,11 +330,6 @@ bool JReactNativeFeatureFlagsCxxInterop::commonTestFlag(
|
||||
return ReactNativeFeatureFlags::commonTestFlag();
|
||||
}
|
||||
|
||||
bool JReactNativeFeatureFlagsCxxInterop::allowRecursiveCommitsWithSynchronousMountOnAndroid(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
|
||||
return ReactNativeFeatureFlags::allowRecursiveCommitsWithSynchronousMountOnAndroid();
|
||||
}
|
||||
|
||||
bool JReactNativeFeatureFlagsCxxInterop::batchRenderingUpdatesInEventLoop(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
|
||||
return ReactNativeFeatureFlags::batchRenderingUpdatesInEventLoop();
|
||||
@@ -591,9 +580,6 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
|
||||
makeNativeMethod(
|
||||
"commonTestFlag",
|
||||
JReactNativeFeatureFlagsCxxInterop::commonTestFlag),
|
||||
makeNativeMethod(
|
||||
"allowRecursiveCommitsWithSynchronousMountOnAndroid",
|
||||
JReactNativeFeatureFlagsCxxInterop::allowRecursiveCommitsWithSynchronousMountOnAndroid),
|
||||
makeNativeMethod(
|
||||
"batchRenderingUpdatesInEventLoop",
|
||||
JReactNativeFeatureFlagsCxxInterop::batchRenderingUpdatesInEventLoop),
|
||||
|
||||
+1
-4
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<89d0da3b2bb56a4ee3c887e6c57491b2>>
|
||||
* @generated SignedSource<<bba5d2a290f39b6572db7f90b67e8469>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -33,9 +33,6 @@ class JReactNativeFeatureFlagsCxxInterop
|
||||
static bool commonTestFlag(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
static bool allowRecursiveCommitsWithSynchronousMountOnAndroid(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
static bool batchRenderingUpdatesInEventLoop(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical|start"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="?android:attr/dialogPreferredPadding"
|
||||
android:paddingTop="18dp"
|
||||
android:paddingEnd="?android:attr/dialogPreferredPadding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/alert_title"
|
||||
style="?android:attr/windowTitleStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textSize="18sp" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -92,7 +92,7 @@ class RootViewTest {
|
||||
val eventEmitterModuleMock = mock(RCTEventEmitter::class.java)
|
||||
whenever(catalystInstanceMock.getNativeModule(UIManagerModule::class.java))
|
||||
.thenReturn(uiManager)
|
||||
whenever(uiManager.getEventDispatcher()).thenReturn(eventDispatcher)
|
||||
whenever(uiManager.eventDispatcher).thenReturn(eventDispatcher)
|
||||
|
||||
// RootView IDs is React Native follow the 11, 21, 31, ... progression.
|
||||
val rootViewId = 11
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ class NativeAnimatedNodeTraversalTest {
|
||||
|
||||
uiManagerMock = mock(UIManagerModule::class.java)
|
||||
eventDispatcherMock = mock(EventDispatcher::class.java)
|
||||
whenever(uiManagerMock.getEventDispatcher()).thenAnswer { eventDispatcherMock }
|
||||
whenever(uiManagerMock.eventDispatcher).thenAnswer { eventDispatcherMock }
|
||||
whenever(uiManagerMock.constants).thenAnswer {
|
||||
mapOf("customDirectEventTypes" to emptyMap<Any, Any>())
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user