-
Notifications
You must be signed in to change notification settings - Fork 24.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement basic version of HTMLCollection
Summary: This implements a basic version of HTMLCollection 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.children` (behind a flag). See: react-native-community/discussions-and-proposals#607 Changelog: [internal] Reviewed By: yungsters Differential Revision: D44055912 fbshipit-source-id: 37bcd7c12916b95a258e6b2e5717a642f478abdf
- Loading branch information
1 parent
4ad5fe3
commit e4d83a1
Showing
4 changed files
with
219 additions
and
0 deletions.
There are no files selected for viewing
30 changes: 30 additions & 0 deletions
30
packages/react-native/Libraries/DOM/OldStyleCollections/ArrayLikeUtils.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/** | ||
* 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 | ||
*/ | ||
|
||
/** | ||
* This definition is different from the current built-in type `$ArrayLike` | ||
* provided by Flow, in that this is an interface and that one is an object. | ||
* | ||
* The difference is important because, when using objects, Flow thinks | ||
* a `length` property would be copied over when using the spread operator, | ||
* which is incorrect. | ||
*/ | ||
export interface ArrayLike<T> extends Iterable<T> { | ||
// This property should've been read-only as well, but Flow doesn't handle | ||
// read-only indexers correctly (thinks reads are writes and fails). | ||
[indexer: number]: T; | ||
+length: number; | ||
} | ||
|
||
export function* createValueIterator<T>(arrayLike: ArrayLike<T>): Iterator<T> { | ||
for (let i = 0; i < arrayLike.length; i++) { | ||
yield arrayLike[i]; | ||
} | ||
} |
82 changes: 82 additions & 0 deletions
82
packages/react-native/Libraries/DOM/OldStyleCollections/HTMLCollection.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/** | ||
* 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 {ArrayLike} from './ArrayLikeUtils'; | ||
|
||
import {createValueIterator} from './ArrayLikeUtils'; | ||
|
||
// IMPORTANT: The type definition for this module is defined in `HTMLCollection.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<T> interface. | ||
export default class HTMLCollection<T> implements Iterable<T>, ArrayLike<T> { | ||
_length: number; | ||
|
||
/** | ||
* Use `createHTMLCollection` 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<T>) { | ||
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): T | 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<T> = this; | ||
return arrayLike[index]; | ||
} | ||
|
||
/** | ||
* @deprecated Unused in React Native. | ||
*/ | ||
namedItem(name: string): T | null { | ||
return null; | ||
} | ||
|
||
// $FlowIssue[unsupported-syntax] Flow does not support computed properties in classes. | ||
[Symbol.iterator](): Iterator<T> { | ||
return createValueIterator(this); | ||
} | ||
} | ||
|
||
/** | ||
* This is an internal method to create instances of `HTMLCollection`, | ||
* which avoids leaking its constructor to end users. | ||
* We can do that because the external definition of `HTMLCollection` lives in | ||
* `HTMLCollection.js.flow`, not here. | ||
*/ | ||
export function createHTMLCollection<T>( | ||
elements: $ReadOnlyArray<T>, | ||
): HTMLCollection<T> { | ||
return new HTMLCollection(elements); | ||
} |
27 changes: 27 additions & 0 deletions
27
packages/react-native/Libraries/DOM/OldStyleCollections/HTMLCollection.js.flow
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; | ||
|
||
declare export default class HTMLCollection<+T> | ||
implements Iterable<T>, ArrayLike<T> | ||
{ | ||
// 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]: T; | ||
+length: number; | ||
item(index: number): T | null; | ||
namedItem(name: string): T | null; | ||
@@iterator(): Iterator<T>; | ||
} | ||
|
||
declare export function createHTMLCollection<T>( | ||
elements: $ReadOnlyArray<T>, | ||
): HTMLCollection<T>; |
80 changes: 80 additions & 0 deletions
80
packages/react-native/Libraries/DOM/OldStyleCollections/__tests__/HTMLCollection-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/** | ||
* 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 {createHTMLCollection} from '../HTMLCollection'; | ||
|
||
describe('HTMLCollection', () => { | ||
it('provides an array-like interface', () => { | ||
const collection = createHTMLCollection(['a', 'b', 'c']); | ||
|
||
expect(collection[0]).toBe('a'); | ||
expect(collection[1]).toBe('b'); | ||
expect(collection[2]).toBe('c'); | ||
expect(collection[3]).toBe(undefined); | ||
expect(collection.length).toBe(3); | ||
}); | ||
|
||
it('is immutable (loose mode)', () => { | ||
const collection = createHTMLCollection(['a', 'b', 'c']); | ||
|
||
collection[0] = 'replacement'; | ||
expect(collection[0]).toBe('a'); | ||
|
||
// $FlowExpectedError[cannot-write] | ||
collection.length = 100; | ||
expect(collection.length).toBe(3); | ||
}); | ||
|
||
it('is immutable (strict mode)', () => { | ||
'use strict'; | ||
|
||
const collection = createHTMLCollection(['a', 'b', 'c']); | ||
|
||
expect(() => { | ||
collection[0] = 'replacement'; | ||
}).toThrow(TypeError); | ||
expect(collection[0]).toBe('a'); | ||
|
||
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 = createHTMLCollection(['a', 'b', 'c']); | ||
|
||
expect(Array.from(collection)).toEqual(['a', 'b', 'c']); | ||
expect([...collection]).toEqual(['a', 'b', 'c']); | ||
}); | ||
|
||
it('can be traversed with for-of', () => { | ||
const collection = createHTMLCollection(['a', 'b', 'c']); | ||
|
||
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 = createHTMLCollection(['a', 'b', 'c']); | ||
|
||
expect(collection.item(0)).toBe('a'); | ||
expect(collection.item(1)).toBe('b'); | ||
expect(collection.item(2)).toBe('c'); | ||
expect(collection.item(3)).toBe(null); | ||
}); | ||
}); | ||
}); |