Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions packages/react-native/Libraries/Utilities/Dimensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
* @format
*/

import type {EventSubscription} from '../vendor/emitter/EventEmitter';

import Event from '../../src/private/webapis/dom/events/Event';
import EventTarget, {
type AddEventListenerOptions,
} from '../../src/private/webapis/dom/events/EventTarget';
import RCTDeviceEventEmitter from '../EventEmitter/RCTDeviceEventEmitter';
import EventEmitter, {
type EventSubscription,
} from '../vendor/emitter/EventEmitter';
import NativeDeviceInfo, {
type DimensionsPayload,
type DisplayMetrics,
Expand All @@ -24,9 +27,7 @@ export type {DimensionsPayload, DisplayMetrics, DisplayMetricsAndroid};
/** @deprecated Use DisplayMetrics */
export type ScaledSize = DisplayMetrics;

const eventEmitter = new EventEmitter<{
change: [DimensionsPayload],
}>();
const eventTarget = new EventTarget();
let dimensionsInitialized = false;
let dimensions: DimensionsPayload;

Expand Down Expand Up @@ -94,7 +95,7 @@ class Dimensions {
dimensions = {window, screen};
if (dimensionsInitialized) {
// Don't fire 'change' the first time the dimensions are set.
eventEmitter.emit('change', dimensions);
eventTarget.dispatchEvent(new Event('change'));
} else {
dimensionsInitialized = true;
}
Expand All @@ -112,13 +113,16 @@ class Dimensions {
static addEventListener(
type: 'change',
handler: LegacyFunction,
options?: Pick<AddEventListenerOptions, 'once' | 'signal'>,
): EventSubscription {
invariant(
type === 'change',
'Trying to subscribe to unknown event: "%s"',
type,
);
return eventEmitter.addListener(type, handler);
const onChange = () => handler(dimensions);
eventTarget.addEventListener(type, onChange, options);
return {remove: () => eventTarget.removeEventListener(type, onChange)};
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@ import Dimensions from '../Dimensions';
import Platform from '../Platform';

describe('Dimensions', () => {
const dimensions = {
width: 400,
height: 800,
scale: 2,
densityDpi: 2,
fontScale: 3,
};

it('should set window dimensions', () => {
const newDimensions = {...dimensions};
Dimensions.set({
windowPhysicalPixels: {
width: 400,
height: 800,
scale: 2,
densityDpi: 2,
fontScale: 3,
},
windowPhysicalPixels: newDimensions,
});

expect(Dimensions.get('window').width).toEqual(200);
Expand All @@ -33,16 +36,10 @@ describe('Dimensions', () => {
it('should set screen dimensions on Android', () => {
// $FlowFixMe[incompatible-type] - `Platform.OS` needs to be read-only.
Platform.OS = 'android';
const dimensions = {
width: 400,
height: 800,
scale: 2,
densityDpi: 2,
fontScale: 3,
};
const newDimensions = {...dimensions};
Dimensions.set({
windowPhysicalPixels: dimensions,
screenPhysicalPixels: dimensions,
windowPhysicalPixels: newDimensions,
screenPhysicalPixels: newDimensions,
});

expect(Dimensions.get('screen').width).toEqual(200);
Expand All @@ -54,17 +51,65 @@ describe('Dimensions', () => {
it('should set screen dimensions on iOS', () => {
// $FlowFixMe[incompatible-type] - `Platform.OS` needs to be read-only.
Platform.OS = 'ios';
const dimensions = {
width: 400,
height: 800,
scale: 2,
densityDpi: 2,
fontScale: 3,
};
Dimensions.set({
windowPhysicalPixels: dimensions,
});

expect(Dimensions.get('screen')).toEqual(Dimensions.get('window'));
});

it('should call a listener on each dimension change', () => {
const listener = jest.fn();
const newDimensions1 = {...dimensions, width: 10};
const newDimensions2 = {...dimensions, width: 30};
const sub = Dimensions.addEventListener('change', listener);

Dimensions.set({windowPhysicalPixels: newDimensions1});
Dimensions.set({windowPhysicalPixels: newDimensions2});

expect(listener).toBeCalledTimes(2);
expect(listener).toHaveBeenNthCalledWith(1, {
screen: {fontScale: 3, height: 400, scale: 2, width: 5},
window: {fontScale: 3, height: 400, scale: 2, width: 5},
});
expect(listener).toHaveBeenNthCalledWith(2, {
screen: {fontScale: 3, height: 400, scale: 2, width: 15},
window: {fontScale: 3, height: 400, scale: 2, width: 15},
});
sub.remove();
});

it('should call a listener once', () => {
const listener = jest.fn();
const newDimensions1 = {...dimensions, fontScale: 1};
const newDimensions2 = {...dimensions, fontScale: 2};
Dimensions.addEventListener('change', listener, {once: true});

Dimensions.set({windowPhysicalPixels: newDimensions1});
Dimensions.set({windowPhysicalPixels: newDimensions2});

expect(listener).toBeCalledTimes(1);
expect(listener).toBeCalledWith({
screen: {fontScale: 1, height: 400, scale: 2, width: 200},
window: {fontScale: 1, height: 400, scale: 2, width: 200},
});
});

it('should remove a listener on a signal abort', () => {
const listener = jest.fn();
const newDimensions1 = {...dimensions, fontScale: 1};
const newDimensions2 = {...dimensions, fontScale: 2};
const c = new AbortController();
Dimensions.addEventListener('change', listener, {signal: c.signal});

Dimensions.set({windowPhysicalPixels: newDimensions2});
c.abort(); // remove listener
Dimensions.set({windowPhysicalPixels: newDimensions1});

expect(listener).toBeCalledTimes(1);
expect(listener).toBeCalledWith({
screen: {fontScale: 2, height: 400, scale: 2, width: 200},
window: {fontScale: 2, height: 400, scale: 2, width: 200},
});
});
});
20 changes: 17 additions & 3 deletions packages/react-native/ReactNativeApi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<<b0c8c41e65758fde472ddf1c5b106a27>>
* @generated SignedSource<<3d7835eb88d765da6d5de7f92764d0af>>
*
* This file was generated by scripts/js-api/build-types/index.js.
*/
Expand Down Expand Up @@ -1113,6 +1113,13 @@ declare type add = typeof add
declare function addChangeListener(
listener: (preferences: AppearancePreferences) => void,
): EventSubscription
declare type AddEventListenerOptions = Readonly<
EventListenerOptions & {
once?: boolean
passive?: boolean
signal?: AbortSignal
}
>
declare class Alert {
static alert(
title: null | string | undefined,
Expand Down Expand Up @@ -1874,7 +1881,11 @@ declare type DevMenuStatic = {
declare type DevSettings = typeof DevSettings
declare type diffClamp = typeof diffClamp
declare class Dimensions {
static addEventListener(type: "change", handler: Function): EventSubscription
static addEventListener(
type: "change",
handler: Function,
options?: Pick<AddEventListenerOptions, "once" | "signal">,
): EventSubscription
static get(dim: string): DisplayMetrics | DisplayMetricsAndroid
static set(dims: Readonly<DimensionsPayload>): void
}
Expand Down Expand Up @@ -2116,6 +2127,9 @@ declare class EventEmitter<
declare type EventEmitter_2<T> = (
handler: ($$PARAM_0$$: T) => Promise<void> | void,
) => EventSubscription
declare type EventListenerOptions = {
readonly capture?: boolean
}
declare type EventName = (() => string) | string
declare interface EventSubscription {
remove(): void
Expand Down Expand Up @@ -5816,7 +5830,7 @@ export {
DeviceInfo, // 0f5a517b
DeviceInfoConstants, // 279e7858
DimensionValue, // b163a381
Dimensions, // 980ef68c
Dimensions, // 03d31826
DimensionsPayload, // 653bc26c
DisplayMetrics, // 1dc35cef
DisplayMetricsAndroid, // 872e62eb
Expand Down
7 changes: 7 additions & 0 deletions packages/react-native/__typetests__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ function testDimensions() {
dimensionsListener,
);
subscription.remove();

Dimensions.addEventListener('change', dimensionsListener, {
once: true,
});
Dimensions.addEventListener('change', dimensionsListener, {
signal: AbortSignal.timeout(5000),
});
}

function TextUseWindowDimensions() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export interface Dimensions {
window: ScaledSize;
screen: ScaledSize;
}) => void,
options?: {once?: true | undefined; signal?: AbortSignal | undefined},
): EmitterSubscription;
}

Expand Down
6 changes: 6 additions & 0 deletions scripts/js-api/build-types/buildApiSnapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,12 @@ async function generateConfigFiles(tempDirectory: string) {
adjustedApiExtractorConfig,
);

// copy ambient global declarations
await fs.copyFile(
path.join(__dirname, 'templates', 'globals.d.ts'),
path.join(tempDirectory, 'globals.d.ts'),
);

// generate basic package.json
const packageJSON = {name: 'react-native'};
await fs.writeFile(
Expand Down
1 change: 1 addition & 0 deletions scripts/js-api/build-types/templates/api-extractor.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"compiler": {
"overrideTsconfig": {
"$schema": "http://json.schemastore.org/tsconfig",
"files": ["${typegen_directory}/globals.d.ts"],
"compilerOptions": {
"lib": ["es2020"],
"types": [],
Expand Down
11 changes: 11 additions & 0 deletions scripts/js-api/build-types/templates/globals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* 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.
*/

// Ambient web globals referenced by the public API that are not part of the
// "es2020" lib used by api-extractor. Declared here so that api-extractor
// resolves them as globals and leaves references to them untouched.
declare class AbortSignal {}
Loading