Skip to content

Commit 6dbc2cc

Browse files
rubennortefacebook-github-bot
authored andcommitted
Implement ReadOnlyText and ReadOnlyCharacterData
Summary: This adds supports for text instances in React Native using a DOM-like interface (as defined in react-native-community/discussions-and-proposals#607). This reuses the `getTextContent` method from Fabric that we added in D44464637, which is supported for this use case as well. Changelog: [internal] bypass-github-export-checks Reviewed By: rshest Differential Revision: D44632362 fbshipit-source-id: edea99ce61fb17d33853c72196ece7bb06a01e41
1 parent 26fdb44 commit 6dbc2cc

File tree

7 files changed

+579
-40
lines changed

7 files changed

+579
-40
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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-local
9+
*/
10+
11+
// flowlint unsafe-getters-setters:off
12+
13+
import type ReadOnlyElement from './ReadOnlyElement';
14+
15+
import {getFabricUIManager} from '../../ReactNative/FabricUIManager';
16+
import ReadOnlyNode, {getShadowNode} from './ReadOnlyNode';
17+
import {getElementSibling} from './Utilities/Traversal';
18+
import nullthrows from 'nullthrows';
19+
20+
export default class ReadOnlyCharacterData extends ReadOnlyNode {
21+
get nextElementSibling(): ReadOnlyElement | null {
22+
return getElementSibling(this, 'next');
23+
}
24+
25+
get previousElementSibling(): ReadOnlyElement | null {
26+
return getElementSibling(this, 'previous');
27+
}
28+
29+
get data(): string {
30+
const shadowNode = getShadowNode(this);
31+
32+
if (shadowNode != null) {
33+
return nullthrows(getFabricUIManager()).getTextContent(shadowNode);
34+
}
35+
36+
return '';
37+
}
38+
39+
get length(): number {
40+
return this.data.length;
41+
}
42+
43+
/**
44+
* @override
45+
*/
46+
get textContent(): string | null {
47+
return this.data;
48+
}
49+
50+
/**
51+
* @override
52+
*/
53+
get nodeValue(): string {
54+
return this.data;
55+
}
56+
57+
substringData(offset: number, count: number): string {
58+
const data = this.data;
59+
if (offset < 0) {
60+
throw new TypeError(
61+
`Failed to execute 'substringData' on 'CharacterData': The offset ${offset} is negative.`,
62+
);
63+
}
64+
if (offset > data.length) {
65+
throw new TypeError(
66+
`Failed to execute 'substringData' on 'CharacterData': The offset ${offset} is greater than the node's length (${data.length}).`,
67+
);
68+
}
69+
let adjustedCount = count < 0 || count > data.length ? data.length : count;
70+
return data.substr(offset, adjustedCount);
71+
}
72+
}

packages/react-native/Libraries/DOM/Nodes/ReadOnlyElement.js

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {getFabricUIManager} from '../../ReactNative/FabricUIManager';
1616
import DOMRect from '../Geometry/DOMRect';
1717
import {createHTMLCollection} from '../OldStyleCollections/HTMLCollection';
1818
import ReadOnlyNode, {getChildNodes, getShadowNode} from './ReadOnlyNode';
19+
import {getElementSibling} from './Utilities/Traversal';
1920
import nullthrows from 'nullthrows';
2021

2122
export default class ReadOnlyElement extends ReadOnlyNode {
@@ -68,14 +69,7 @@ export default class ReadOnlyElement extends ReadOnlyNode {
6869
}
6970

7071
get nextElementSibling(): ReadOnlyElement | null {
71-
const [siblings, position] = getElementSiblingsAndPosition(this);
72-
73-
if (position === siblings.length - 1) {
74-
// this node is the last child of its parent, so there is no next sibling.
75-
return null;
76-
}
77-
78-
return siblings[position + 1];
72+
return getElementSibling(this, 'next');
7973
}
8074

8175
get nodeName(): string {
@@ -93,14 +87,7 @@ export default class ReadOnlyElement extends ReadOnlyNode {
9387
set nodeValue(value: string): void {}
9488

9589
get previousElementSibling(): ReadOnlyElement | null {
96-
const [siblings, position] = getElementSiblingsAndPosition(this);
97-
98-
if (position === 0) {
99-
// this node is the last child of its parent, so there is no next sibling.
100-
return null;
101-
}
102-
103-
return siblings[position - 1];
90+
return getElementSibling(this, 'previous');
10491
}
10592

10693
get scrollHeight(): number {
@@ -161,22 +148,3 @@ function getChildElements(node: ReadOnlyNode): $ReadOnlyArray<ReadOnlyElement> {
161148
childNode => childNode instanceof ReadOnlyElement,
162149
);
163150
}
164-
165-
export function getElementSiblingsAndPosition(
166-
element: ReadOnlyElement,
167-
): [$ReadOnlyArray<ReadOnlyElement>, number] {
168-
const parent = element.parentNode;
169-
if (parent == null) {
170-
// This node is the root or it's disconnected.
171-
return [[element], 0];
172-
}
173-
174-
const siblings = getChildElements(parent);
175-
const position = siblings.indexOf(element);
176-
177-
if (position === -1) {
178-
throw new TypeError("Missing node in parent's child node list");
179-
}
180-
181-
return [siblings, position];
182-
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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-local
9+
*/
10+
11+
// flowlint unsafe-getters-setters:off
12+
13+
import ReadOnlyCharacterData from './ReadOnlyCharacterData';
14+
import ReadOnlyNode from './ReadOnlyNode';
15+
16+
export default class ReadOnlyText extends ReadOnlyCharacterData {
17+
/**
18+
* @override
19+
*/
20+
get nodeName(): string {
21+
return '#text';
22+
}
23+
24+
/**
25+
* @override
26+
*/
27+
get nodeType(): number {
28+
return ReadOnlyNode.TEXT_NODE;
29+
}
30+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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-local
9+
*/
10+
11+
import type ReadOnlyElement from '../ReadOnlyElement';
12+
import type ReadOnlyNode from '../ReadOnlyNode';
13+
14+
import {getChildNodes} from '../ReadOnlyNode';
15+
16+
// We initialize this lazily to avoid a require cycle
17+
// (`ReadOnlyElement` also depends on `Traversal`).
18+
let ReadOnlyElementClass: Class<ReadOnlyElement>;
19+
20+
export function getElementSibling(
21+
node: ReadOnlyNode,
22+
direction: 'next' | 'previous',
23+
): ReadOnlyElement | null {
24+
const parent = node.parentNode;
25+
if (parent == null) {
26+
// This node is the root or it's disconnected.
27+
return null;
28+
}
29+
30+
const childNodes = getChildNodes(parent);
31+
32+
const startPosition = childNodes.indexOf(node);
33+
if (startPosition === -1) {
34+
return null;
35+
}
36+
37+
const increment = direction === 'next' ? 1 : -1;
38+
39+
let position = startPosition + increment;
40+
41+
if (ReadOnlyElementClass == null) {
42+
// We initialize this lazily to avoid a require cycle.
43+
ReadOnlyElementClass = require('../ReadOnlyElement').default;
44+
}
45+
46+
while (
47+
childNodes[position] != null &&
48+
!(childNodes[position] instanceof ReadOnlyElementClass)
49+
) {
50+
position = position + increment;
51+
}
52+
53+
return childNodes[position] ?? null;
54+
}

0 commit comments

Comments
 (0)