Skip to content

Commit a8b5ff8

Browse files
rubennortefacebook-github-bot
authored andcommitted
Implement basic version of DOMRectList
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
1 parent c4b84ba commit a8b5ff8

File tree

3 files changed

+188
-0
lines changed

3 files changed

+188
-0
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @flow strict
9+
*/
10+
11+
// flowlint unsafe-getters-setters:off
12+
13+
import type DOMRectReadOnly from '../Geometry/DOMRectReadOnly';
14+
import type {ArrayLike} from './ArrayLikeUtils';
15+
16+
import {createValueIterator} from './ArrayLikeUtils';
17+
18+
// IMPORTANT: The Flow type definition for this module is defined in `DOMRectList.js.flow`
19+
// because Flow only supports indexers in classes in declaration files.
20+
21+
// $FlowIssue[prop-missing] Flow doesn't understand [Symbol.iterator]() {} and thinks this class doesn't implement the Iterable interface.
22+
export default class DOMRectList implements Iterable<DOMRectReadOnly> {
23+
_length: number;
24+
25+
/**
26+
* Use `createDOMRectList` to create instances of this class.
27+
*
28+
* @private This is not defined in the declaration file, so users will not see
29+
* the signature of the constructor.
30+
*/
31+
constructor(elements: $ReadOnlyArray<DOMRectReadOnly>) {
32+
for (let i = 0; i < elements.length; i++) {
33+
Object.defineProperty(this, i, {
34+
value: elements[i],
35+
enumerable: true,
36+
configurable: false,
37+
writable: false,
38+
});
39+
}
40+
41+
this._length = elements.length;
42+
}
43+
44+
get length(): number {
45+
return this._length;
46+
}
47+
48+
item(index: number): DOMRectReadOnly | null {
49+
if (index < 0 || index >= this._length) {
50+
return null;
51+
}
52+
53+
// assigning to the interface allows us to access the indexer property in a
54+
// type-safe way.
55+
// eslint-disable-next-line consistent-this
56+
const arrayLike: ArrayLike<DOMRectReadOnly> = this;
57+
return arrayLike[index];
58+
}
59+
60+
// $FlowIssue[unsupported-syntax] Flow does not support computed properties in classes.
61+
[Symbol.iterator](): Iterator<DOMRectReadOnly> {
62+
return createValueIterator(this);
63+
}
64+
}
65+
66+
/**
67+
* This is an internal method to create instances of `DOMRectList`,
68+
* which avoids leaking its constructor to end users.
69+
* We can do that because the external definition of `DOMRectList` lives in
70+
* `DOMRectList.js.flow`, not here.
71+
*/
72+
export function createDOMRectList(
73+
elements: $ReadOnlyArray<DOMRectReadOnly>,
74+
): DOMRectList {
75+
return new DOMRectList(elements);
76+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @flow strict
9+
*/
10+
11+
import type {ArrayLike} from './ArrayLikeUtils';
12+
import type DOMRectReadOnly from '../Geometry/DOMRectReadOnly';
13+
14+
declare export default class DOMRectList
15+
implements Iterable<DOMRectReadOnly>, ArrayLike<DOMRectReadOnly>
16+
{
17+
// This property should've been read-only as well, but Flow doesn't handle
18+
// read-only indexers correctly (thinks reads are writes and fails).
19+
[index: number]: DOMRectReadOnly;
20+
+length: number;
21+
item(index: number): DOMRectReadOnly | null;
22+
@@iterator(): Iterator<DOMRectReadOnly>;
23+
}
24+
25+
declare export function createDOMRectList(
26+
domRects: $ReadOnlyArray<DOMRectReadOnly>,
27+
): DOMRectList;
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
* @oncall react_native
10+
*/
11+
12+
import DOMRectReadOnly from '../../Geometry/DOMRectReadOnly';
13+
import {createDOMRectList} from '../DOMRectList';
14+
15+
const domRectA = new DOMRectReadOnly();
16+
const domRectB = new DOMRectReadOnly();
17+
const domRectC = new DOMRectReadOnly();
18+
19+
describe('DOMRectList', () => {
20+
it('provides an array-like interface', () => {
21+
const collection = createDOMRectList([domRectA, domRectB, domRectC]);
22+
23+
expect(collection[0]).toBe(domRectA);
24+
expect(collection[1]).toBe(domRectB);
25+
expect(collection[2]).toBe(domRectC);
26+
expect(collection[3]).toBe(undefined);
27+
expect(collection.length).toBe(3);
28+
});
29+
30+
it('is immutable (loose mode)', () => {
31+
const collection = createDOMRectList([domRectA, domRectB, domRectC]);
32+
33+
collection[0] = new DOMRectReadOnly();
34+
expect(collection[0]).toBe(domRectA);
35+
36+
// $FlowExpectedError[cannot-write]
37+
collection.length = 100;
38+
expect(collection.length).toBe(3);
39+
});
40+
41+
it('is immutable (strict mode)', () => {
42+
'use strict';
43+
44+
const collection = createDOMRectList([domRectA, domRectB, domRectC]);
45+
46+
expect(() => {
47+
collection[0] = new DOMRectReadOnly();
48+
}).toThrow(TypeError);
49+
expect(collection[0]).toBe(domRectA);
50+
51+
expect(() => {
52+
// $FlowExpectedError[cannot-write]
53+
collection.length = 100;
54+
}).toThrow(TypeError);
55+
expect(collection.length).toBe(3);
56+
});
57+
58+
it('can be converted to an array through common methods', () => {
59+
const collection = createDOMRectList([domRectA, domRectB, domRectC]);
60+
61+
expect(Array.from(collection)).toEqual([domRectA, domRectB, domRectC]);
62+
expect([...collection]).toEqual([domRectA, domRectB, domRectC]);
63+
});
64+
65+
it('can be traversed with for-of', () => {
66+
const collection = createDOMRectList([domRectA, domRectB, domRectC]);
67+
68+
let i = 0;
69+
for (const value of collection) {
70+
expect(value).toBe(collection[i]);
71+
i++;
72+
}
73+
});
74+
75+
describe('item()', () => {
76+
it('returns elements at the specified position, or null', () => {
77+
const collection = createDOMRectList([domRectA, domRectB, domRectC]);
78+
79+
expect(collection.item(0)).toBe(domRectA);
80+
expect(collection.item(1)).toBe(domRectB);
81+
expect(collection.item(2)).toBe(domRectC);
82+
expect(collection.item(3)).toBe(null);
83+
});
84+
});
85+
});

0 commit comments

Comments
 (0)