Skip to content

Commit

Permalink
Implement basic version of DOMRectList
Browse files Browse the repository at this point in the history
Summary:
This implements a basic version of DOMRectList that's close to the spec but diverges in some things (e.g.: methods could be called with an instance created through `Object.create`, etc.).

This will be used soon to implement `ReadOnlyelement.getClientRects()` (behind a flag).

See: react-native-community/discussions-and-proposals#607

Changelog: [internal]

Reviewed By: yungsters

Differential Revision: D44060540

fbshipit-source-id: ad29b5c41f2778864e7dd7ece9223dcf73cd5d6c
  • Loading branch information
rubennorte authored and facebook-github-bot committed Mar 20, 2023
1 parent c4b84ba commit a8b5ff8
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* 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
* @flow strict
*/

// flowlint unsafe-getters-setters:off

import type DOMRectReadOnly from '../Geometry/DOMRectReadOnly';
import type {ArrayLike} from './ArrayLikeUtils';

import {createValueIterator} from './ArrayLikeUtils';

// IMPORTANT: The Flow type definition for this module is defined in `DOMRectList.js.flow`
// because Flow only supports indexers in classes in declaration files.

// $FlowIssue[prop-missing] Flow doesn't understand [Symbol.iterator]() {} and thinks this class doesn't implement the Iterable interface.
export default class DOMRectList implements Iterable<DOMRectReadOnly> {
_length: number;

/**
* Use `createDOMRectList` to create instances of this class.
*
* @private This is not defined in the declaration file, so users will not see
* the signature of the constructor.
*/
constructor(elements: $ReadOnlyArray<DOMRectReadOnly>) {
for (let i = 0; i < elements.length; i++) {
Object.defineProperty(this, i, {
value: elements[i],
enumerable: true,
configurable: false,
writable: false,
});
}

this._length = elements.length;
}

get length(): number {
return this._length;
}

item(index: number): DOMRectReadOnly | null {
if (index < 0 || index >= this._length) {
return null;
}

// assigning to the interface allows us to access the indexer property in a
// type-safe way.
// eslint-disable-next-line consistent-this
const arrayLike: ArrayLike<DOMRectReadOnly> = this;
return arrayLike[index];
}

// $FlowIssue[unsupported-syntax] Flow does not support computed properties in classes.
[Symbol.iterator](): Iterator<DOMRectReadOnly> {
return createValueIterator(this);
}
}

/**
* This is an internal method to create instances of `DOMRectList`,
* which avoids leaking its constructor to end users.
* We can do that because the external definition of `DOMRectList` lives in
* `DOMRectList.js.flow`, not here.
*/
export function createDOMRectList(
elements: $ReadOnlyArray<DOMRectReadOnly>,
): DOMRectList {
return new DOMRectList(elements);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* 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
* @flow strict
*/

import type {ArrayLike} from './ArrayLikeUtils';
import type DOMRectReadOnly from '../Geometry/DOMRectReadOnly';

declare export default class DOMRectList
implements Iterable<DOMRectReadOnly>, ArrayLike<DOMRectReadOnly>
{
// This property should've been read-only as well, but Flow doesn't handle
// read-only indexers correctly (thinks reads are writes and fails).
[index: number]: DOMRectReadOnly;
+length: number;
item(index: number): DOMRectReadOnly | null;
@@iterator(): Iterator<DOMRectReadOnly>;
}

declare export function createDOMRectList(
domRects: $ReadOnlyArray<DOMRectReadOnly>,
): DOMRectList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/

import DOMRectReadOnly from '../../Geometry/DOMRectReadOnly';
import {createDOMRectList} from '../DOMRectList';

const domRectA = new DOMRectReadOnly();
const domRectB = new DOMRectReadOnly();
const domRectC = new DOMRectReadOnly();

describe('DOMRectList', () => {
it('provides an array-like interface', () => {
const collection = createDOMRectList([domRectA, domRectB, domRectC]);

expect(collection[0]).toBe(domRectA);
expect(collection[1]).toBe(domRectB);
expect(collection[2]).toBe(domRectC);
expect(collection[3]).toBe(undefined);
expect(collection.length).toBe(3);
});

it('is immutable (loose mode)', () => {
const collection = createDOMRectList([domRectA, domRectB, domRectC]);

collection[0] = new DOMRectReadOnly();
expect(collection[0]).toBe(domRectA);

// $FlowExpectedError[cannot-write]
collection.length = 100;
expect(collection.length).toBe(3);
});

it('is immutable (strict mode)', () => {
'use strict';

const collection = createDOMRectList([domRectA, domRectB, domRectC]);

expect(() => {
collection[0] = new DOMRectReadOnly();
}).toThrow(TypeError);
expect(collection[0]).toBe(domRectA);

expect(() => {
// $FlowExpectedError[cannot-write]
collection.length = 100;
}).toThrow(TypeError);
expect(collection.length).toBe(3);
});

it('can be converted to an array through common methods', () => {
const collection = createDOMRectList([domRectA, domRectB, domRectC]);

expect(Array.from(collection)).toEqual([domRectA, domRectB, domRectC]);
expect([...collection]).toEqual([domRectA, domRectB, domRectC]);
});

it('can be traversed with for-of', () => {
const collection = createDOMRectList([domRectA, domRectB, domRectC]);

let i = 0;
for (const value of collection) {
expect(value).toBe(collection[i]);
i++;
}
});

describe('item()', () => {
it('returns elements at the specified position, or null', () => {
const collection = createDOMRectList([domRectA, domRectB, domRectC]);

expect(collection.item(0)).toBe(domRectA);
expect(collection.item(1)).toBe(domRectB);
expect(collection.item(2)).toBe(domRectC);
expect(collection.item(3)).toBe(null);
});
});
});

0 comments on commit a8b5ff8

Please sign in to comment.