Move renderer host configs into separate modules (#12791)
* Separate test renderer host config * Separate ART renderer host config * Separate ReactDOM host config * Extract RN Fabric host config * Extract RN host config
Dan Abramov committed
May 15, 2018 at 01:12 UTC
45b90d48668dae48aad611819b891d029aa2fb27
13 files changed
+1698
-1586
packages/react-art/src/ReactART.js
+5
-405
@@ -7,267 +7,18 @@
7
8
import React from 'react';
9
import ReactFiberReconciler from 'react-reconciler';
10
-import * as ReactScheduler from 'react-scheduler';
10
+import Transform from 'art/core/transform';
11
import Mode from 'art/modes/current';
12
import FastNoSideEffects from 'art/modes/fast-noSideEffects';
13
-import Transform from 'art/core/transform';
14
-import invariant from 'fbjs/lib/invariant';
15
-import emptyObject from 'fbjs/lib/emptyObject';
13
+
14
+import ReactARTHostConfig from './ReactARTHostConfig';
15
+import {TYPES, childrenAsString} from './ReactARTInternals';
16
17
Mode.setCurrent(
18
// Change to 'art/modes/dom' for easier debugging via SVG
19
FastNoSideEffects,
20
);
21
22
-const pooledTransform = new Transform();
23
-
24
-const EVENT_TYPES = {
25
- onClick: 'click',
26
- onMouseMove: 'mousemove',
27
- onMouseOver: 'mouseover',
28
- onMouseOut: 'mouseout',
29
- onMouseUp: 'mouseup',
30
- onMouseDown: 'mousedown',
31
-};
32
-
33
-const TYPES = {
34
- CLIPPING_RECTANGLE: 'ClippingRectangle',
35
- GROUP: 'Group',
36
- SHAPE: 'Shape',
37
- TEXT: 'Text',
38
-};
39
-
40
-const UPDATE_SIGNAL = {};
41
-
42
-/** Helper Methods */
43
-
44
-function addEventListeners(instance, type, listener) {
45
- // We need to explicitly unregister before unmount.
46
- // For this reason we need to track subscriptions.
47
- if (!instance._listeners) {
48
- instance._listeners = {};
49
- instance._subscriptions = {};
50
- }
51
-
52
- instance._listeners[type] = listener;
53
-
54
- if (listener) {
55
- if (!instance._subscriptions[type]) {
56
- instance._subscriptions[type] = instance.subscribe(
57
- type,
58
- createEventHandler(instance),
59
- instance,
60
- );
61
- }
62
- } else {
63
- if (instance._subscriptions[type]) {
64
- instance._subscriptions[type]();
65
- delete instance._subscriptions[type];
66
- }
67
- }
68
-}
69
-
70
-function childrenAsString(children) {
71
- if (!children) {
72
- return '';
73
- } else if (typeof children === 'string') {
74
- return children;
75
- } else if (children.length) {
76
- return children.join('');
77
- } else {
78
- return '';
79
- }
80
-}
81
-
82
-function createEventHandler(instance) {
83
- return function handleEvent(event) {
84
- const listener = instance._listeners[event.type];
85
-
86
- if (!listener) {
87
- // Noop
88
- } else if (typeof listener === 'function') {
89
- listener.call(instance, event);
90
- } else if (listener.handleEvent) {
91
- listener.handleEvent(event);
92
- }
93
- };
94
-}
95
-
96
-function destroyEventListeners(instance) {
97
- if (instance._subscriptions) {
98
- for (let type in instance._subscriptions) {
99
- instance._subscriptions[type]();
100
- }
101
- }
102
-
103
- instance._subscriptions = null;
104
- instance._listeners = null;
105
-}
106
-
107
-function getScaleX(props) {
108
- if (props.scaleX != null) {
109
- return props.scaleX;
110
- } else if (props.scale != null) {
111
- return props.scale;
112
- } else {
113
- return 1;
114
- }
115
-}
116
-
117
-function getScaleY(props) {
118
- if (props.scaleY != null) {
119
- return props.scaleY;
120
- } else if (props.scale != null) {
121
- return props.scale;
122
- } else {
123
- return 1;
124
- }
125
-}
126
-
127
-function isSameFont(oldFont, newFont) {
128
- if (oldFont === newFont) {
129
- return true;
130
- } else if (typeof newFont === 'string' || typeof oldFont === 'string') {
131
- return false;
132
- } else {
133
- return (
134
- newFont.fontSize === oldFont.fontSize &&
135
- newFont.fontStyle === oldFont.fontStyle &&
136
- newFont.fontVariant === oldFont.fontVariant &&
137
- newFont.fontWeight === oldFont.fontWeight &&
138
- newFont.fontFamily === oldFont.fontFamily
139
- );
140
- }
141
-}
142
-
143
-/** Render Methods */
144
-
145
-function applyClippingRectangleProps(instance, props, prevProps = {}) {
146
- applyNodeProps(instance, props, prevProps);
147
-
148
- instance.width = props.width;
149
- instance.height = props.height;
150
-}
151
-
152
-function applyGroupProps(instance, props, prevProps = {}) {
153
- applyNodeProps(instance, props, prevProps);
154
-
155
- instance.width = props.width;
156
- instance.height = props.height;
157
-}
158
-
159
-function applyNodeProps(instance, props, prevProps = {}) {
160
- const scaleX = getScaleX(props);
161
- const scaleY = getScaleY(props);
162
-
163
- pooledTransform
164
- .transformTo(1, 0, 0, 1, 0, 0)
165
- .move(props.x || 0, props.y || 0)
166
- .rotate(props.rotation || 0, props.originX, props.originY)
167
- .scale(scaleX, scaleY, props.originX, props.originY);
168
-
169
- if (props.transform != null) {
170
- pooledTransform.transform(props.transform);
171
- }
172
-
173
- if (
174
- instance.xx !== pooledTransform.xx ||
175
- instance.yx !== pooledTransform.yx ||
176
- instance.xy !== pooledTransform.xy ||
177
- instance.yy !== pooledTransform.yy ||
178
- instance.x !== pooledTransform.x ||
179
- instance.y !== pooledTransform.y
180
- ) {
181
- instance.transformTo(pooledTransform);
182
- }
183
-
184
- if (props.cursor !== prevProps.cursor || props.title !== prevProps.title) {
185
- instance.indicate(props.cursor, props.title);
186
- }
187
-
188
- if (instance.blend && props.opacity !== prevProps.opacity) {
189
- instance.blend(props.opacity == null ? 1 : props.opacity);
190
- }
191
-
192
- if (props.visible !== prevProps.visible) {
193
- if (props.visible == null || props.visible) {
194
- instance.show();
195
- } else {
196
- instance.hide();
197
- }
198
- }
199
-
200
- for (let type in EVENT_TYPES) {
201
- addEventListeners(instance, EVENT_TYPES[type], props[type]);
202
- }
203
-}
204
-
205
-function applyRenderableNodeProps(instance, props, prevProps = {}) {
206
- applyNodeProps(instance, props, prevProps);
207
-
208
- if (prevProps.fill !== props.fill) {
209
- if (props.fill && props.fill.applyFill) {
210
- props.fill.applyFill(instance);
211
- } else {
212
- instance.fill(props.fill);
213
- }
214
- }
215
- if (
216
- prevProps.stroke !== props.stroke ||
217
- prevProps.strokeWidth !== props.strokeWidth ||
218
- prevProps.strokeCap !== props.strokeCap ||
219
- prevProps.strokeJoin !== props.strokeJoin ||
220
- // TODO: Consider deep check of stokeDash; may benefit VML in IE.
221
- prevProps.strokeDash !== props.strokeDash
222
- ) {
223
- instance.stroke(
224
- props.stroke,
225
- props.strokeWidth,
226
- props.strokeCap,
227
- props.strokeJoin,
228
- props.strokeDash,
229
- );
230
- }
231
-}
232
-
233
-function applyShapeProps(instance, props, prevProps = {}) {
234
- applyRenderableNodeProps(instance, props, prevProps);
235
-
236
- const path = props.d || childrenAsString(props.children);
237
-
238
- const prevDelta = instance._prevDelta;
239
- const prevPath = instance._prevPath;
240
-
241
- if (
242
- path !== prevPath ||
243
- path.delta !== prevDelta ||
244
- prevProps.height !== props.height ||
245
- prevProps.width !== props.width
246
- ) {
247
- instance.draw(path, props.width, props.height);
248
-
249
- instance._prevDelta = path.delta;
250
- instance._prevPath = path;
251
- }
252
-}
253
-
254
-function applyTextProps(instance, props, prevProps = {}) {
255
- applyRenderableNodeProps(instance, props, prevProps);
256
-
257
- const string = props.children;
258
-
259
- if (
260
- instance._currentString !== string ||
261
- !isSameFont(props.font, prevProps.font) ||
262
- props.alignment !== prevProps.alignment ||
263
- props.path !== prevProps.path
264
- ) {
265
- instance.draw(string, props.font, props.alignment, props.path);
266
-
267
- instance._currentString = string;
268
- }
269
-}
270
-
22
/** Declarative fill-type objects; API design not finalized */
23
24
const slice = Array.prototype.slice;
@@ -383,158 +134,7 @@ class Text extends React.Component {
134
135
/** ART Renderer */
136
386
-const ARTRenderer = ReactFiberReconciler({
387
- appendInitialChild(parentInstance, child) {
388
- if (typeof child === 'string') {
389
- // Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
390
- invariant(false, 'Text children should already be flattened.');
391
- return;
392
- }
393
-
394
- child.inject(parentInstance);
395
- },
396
-
397
- createInstance(type, props, internalInstanceHandle) {
398
- let instance;
399
-
400
- switch (type) {
401
- case TYPES.CLIPPING_RECTANGLE:
402
- instance = Mode.ClippingRectangle();
403
- instance._applyProps = applyClippingRectangleProps;
404
- break;
405
- case TYPES.GROUP:
406
- instance = Mode.Group();
407
- instance._applyProps = applyGroupProps;
408
- break;
409
- case TYPES.SHAPE:
410
- instance = Mode.Shape();
411
- instance._applyProps = applyShapeProps;
412
- break;
413
- case TYPES.TEXT:
414
- instance = Mode.Text(
415
- props.children,
416
- props.font,
417
- props.alignment,
418
- props.path,
419
- );
420
- instance._applyProps = applyTextProps;
421
- break;
422
- }
423
-
424
- invariant(instance, 'ReactART does not support the type "%s"', type);
425
-
426
- instance._applyProps(instance, props);
427
-
428
- return instance;
429
- },
430
-
431
- createTextInstance(text, rootContainerInstance, internalInstanceHandle) {
432
- return text;
433
- },
434
-
435
- finalizeInitialChildren(domElement, type, props) {
436
- return false;
437
- },
438
-
439
- getPublicInstance(instance) {
440
- return instance;
441
- },
442
-
443
- prepareForCommit() {
444
- // Noop
445
- },
446
-
447
- prepareUpdate(domElement, type, oldProps, newProps) {
448
- return UPDATE_SIGNAL;
449
- },
450
-
451
- resetAfterCommit() {
452
- // Noop
453
- },
454
-
455
- resetTextContent(domElement) {
456
- // Noop
457
- },
458
-
459
- shouldDeprioritizeSubtree(type, props) {
460
- return false;
461
- },
462
-
463
- getRootHostContext() {
464
- return emptyObject;
465
- },
466
-
467
- getChildHostContext() {
468
- return emptyObject;
469
- },
470
-
471
- scheduleDeferredCallback: ReactScheduler.rIC,
472
-
473
- shouldSetTextContent(type, props) {
474
- return (
475
- typeof props.children === 'string' || typeof props.children === 'number'
476
- );
477
- },
478
-
479
- now: ReactScheduler.now,
480
-
481
- // The ART renderer is secondary to the React DOM renderer.
482
- isPrimaryRenderer: false,
483
-
484
- mutation: {
485
- appendChild(parentInstance, child) {
486
- if (child.parentNode === parentInstance) {
487
- child.eject();
488
- }
489
- child.inject(parentInstance);
490
- },
491
-
492
- appendChildToContainer(parentInstance, child) {
493
- if (child.parentNode === parentInstance) {
494
- child.eject();
495
- }
496
- child.inject(parentInstance);
497
- },
498
-
499
- insertBefore(parentInstance, child, beforeChild) {
500
- invariant(
501
- child !== beforeChild,
502
- 'ReactART: Can not insert node before itself',
503
- );
504
- child.injectBefore(beforeChild);
505
- },
506
-
507
- insertInContainerBefore(parentInstance, child, beforeChild) {
508
- invariant(
509
- child !== beforeChild,
510
- 'ReactART: Can not insert node before itself',
511
- );
512
- child.injectBefore(beforeChild);
513
- },
514
-
515
- removeChild(parentInstance, child) {
516
- destroyEventListeners(child);
517
- child.eject();
518
- },
519
-
520
- removeChildFromContainer(parentInstance, child) {
521
- destroyEventListeners(child);
522
- child.eject();
523
- },
524
-
525
- commitTextUpdate(textInstance, oldText, newText) {
526
- // Noop
527
- },
528
-
529
- commitMount(instance, type, newProps) {
530
- // Noop
531
- },
532
-
533
- commitUpdate(instance, updatePayload, type, oldProps, newProps) {
534
- instance._applyProps(instance, newProps, oldProps);
535
- },
536
- },
537
-});
137
+const ARTRenderer = ReactFiberReconciler(ReactARTHostConfig);
138
139
/** API */
140
packages/react-art/src/ReactARTHostConfig.js
new
+390
@@ -0,0 +1,390 @@
1
+/**
2
+ * Copyright (c) 2013-present, Facebook, Inc.
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
+
8
+import * as ReactScheduler from 'react-scheduler';
9
+import Transform from 'art/core/transform';
10
+import Mode from 'art/modes/current';
11
+import invariant from 'fbjs/lib/invariant';
12
+import emptyObject from 'fbjs/lib/emptyObject';
13
+
14
+import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
15
+
16
+const pooledTransform = new Transform();
17
+
18
+const UPDATE_SIGNAL = {};
19
+
20
+/** Helper Methods */
21
+
22
+function addEventListeners(instance, type, listener) {
23
+ // We need to explicitly unregister before unmount.
24
+ // For this reason we need to track subscriptions.
25
+ if (!instance._listeners) {
26
+ instance._listeners = {};
27
+ instance._subscriptions = {};
28
+ }
29
+
30
+ instance._listeners[type] = listener;
31
+
32
+ if (listener) {
33
+ if (!instance._subscriptions[type]) {
34
+ instance._subscriptions[type] = instance.subscribe(
35
+ type,
36
+ createEventHandler(instance),
37
+ instance,
38
+ );
39
+ }
40
+ } else {
41
+ if (instance._subscriptions[type]) {
42
+ instance._subscriptions[type]();
43
+ delete instance._subscriptions[type];
44
+ }
45
+ }
46
+}
47
+
48
+function createEventHandler(instance) {
49
+ return function handleEvent(event) {
50
+ const listener = instance._listeners[event.type];
51
+
52
+ if (!listener) {
53
+ // Noop
54
+ } else if (typeof listener === 'function') {
55
+ listener.call(instance, event);
56
+ } else if (listener.handleEvent) {
57
+ listener.handleEvent(event);
58
+ }
59
+ };
60
+}
61
+
62
+function destroyEventListeners(instance) {
63
+ if (instance._subscriptions) {
64
+ for (let type in instance._subscriptions) {
65
+ instance._subscriptions[type]();
66
+ }
67
+ }
68
+
69
+ instance._subscriptions = null;
70
+ instance._listeners = null;
71
+}
72
+
73
+function getScaleX(props) {
74
+ if (props.scaleX != null) {
75
+ return props.scaleX;
76
+ } else if (props.scale != null) {
77
+ return props.scale;
78
+ } else {
79
+ return 1;
80
+ }
81
+}
82
+
83
+function getScaleY(props) {
84
+ if (props.scaleY != null) {
85
+ return props.scaleY;
86
+ } else if (props.scale != null) {
87
+ return props.scale;
88
+ } else {
89
+ return 1;
90
+ }
91
+}
92
+
93
+function isSameFont(oldFont, newFont) {
94
+ if (oldFont === newFont) {
95
+ return true;
96
+ } else if (typeof newFont === 'string' || typeof oldFont === 'string') {
97
+ return false;
98
+ } else {
99
+ return (
100
+ newFont.fontSize === oldFont.fontSize &&
101
+ newFont.fontStyle === oldFont.fontStyle &&
102
+ newFont.fontVariant === oldFont.fontVariant &&
103
+ newFont.fontWeight === oldFont.fontWeight &&
104
+ newFont.fontFamily === oldFont.fontFamily
105
+ );
106
+ }
107
+}
108
+
109
+/** Render Methods */
110
+
111
+function applyClippingRectangleProps(instance, props, prevProps = {}) {
112
+ applyNodeProps(instance, props, prevProps);
113
+
114
+ instance.width = props.width;
115
+ instance.height = props.height;
116
+}
117
+
118
+function applyGroupProps(instance, props, prevProps = {}) {
119
+ applyNodeProps(instance, props, prevProps);
120
+
121
+ instance.width = props.width;
122
+ instance.height = props.height;
123
+}
124
+
125
+function applyNodeProps(instance, props, prevProps = {}) {
126
+ const scaleX = getScaleX(props);
127
+ const scaleY = getScaleY(props);
128
+
129
+ pooledTransform
130
+ .transformTo(1, 0, 0, 1, 0, 0)
131
+ .move(props.x || 0, props.y || 0)
132
+ .rotate(props.rotation || 0, props.originX, props.originY)
133
+ .scale(scaleX, scaleY, props.originX, props.originY);
134
+
135
+ if (props.transform != null) {
136
+ pooledTransform.transform(props.transform);
137
+ }
138
+
139
+ if (
140
+ instance.xx !== pooledTransform.xx ||
141
+ instance.yx !== pooledTransform.yx ||
142
+ instance.xy !== pooledTransform.xy ||
143
+ instance.yy !== pooledTransform.yy ||
144
+ instance.x !== pooledTransform.x ||
145
+ instance.y !== pooledTransform.y
146
+ ) {
147
+ instance.transformTo(pooledTransform);
148
+ }
149
+
150
+ if (props.cursor !== prevProps.cursor || props.title !== prevProps.title) {
151
+ instance.indicate(props.cursor, props.title);
152
+ }
153
+
154
+ if (instance.blend && props.opacity !== prevProps.opacity) {
155
+ instance.blend(props.opacity == null ? 1 : props.opacity);
156
+ }
157
+
158
+ if (props.visible !== prevProps.visible) {
159
+ if (props.visible == null || props.visible) {
160
+ instance.show();
161
+ } else {
162
+ instance.hide();
163
+ }
164
+ }
165
+
166
+ for (let type in EVENT_TYPES) {
167
+ addEventListeners(instance, EVENT_TYPES[type], props[type]);
168
+ }
169
+}
170
+
171
+function applyRenderableNodeProps(instance, props, prevProps = {}) {
172
+ applyNodeProps(instance, props, prevProps);
173
+
174
+ if (prevProps.fill !== props.fill) {
175
+ if (props.fill && props.fill.applyFill) {
176
+ props.fill.applyFill(instance);
177
+ } else {
178
+ instance.fill(props.fill);
179
+ }
180
+ }
181
+ if (
182
+ prevProps.stroke !== props.stroke ||
183
+ prevProps.strokeWidth !== props.strokeWidth ||
184
+ prevProps.strokeCap !== props.strokeCap ||
185
+ prevProps.strokeJoin !== props.strokeJoin ||
186
+ // TODO: Consider deep check of stokeDash; may benefit VML in IE.
187
+ prevProps.strokeDash !== props.strokeDash
188
+ ) {
189
+ instance.stroke(
190
+ props.stroke,
191
+ props.strokeWidth,
192
+ props.strokeCap,
193
+ props.strokeJoin,
194
+ props.strokeDash,
195
+ );
196
+ }
197
+}
198
+
199
+function applyShapeProps(instance, props, prevProps = {}) {
200
+ applyRenderableNodeProps(instance, props, prevProps);
201
+
202
+ const path = props.d || childrenAsString(props.children);
203
+
204
+ const prevDelta = instance._prevDelta;
205
+ const prevPath = instance._prevPath;
206
+
207
+ if (
208
+ path !== prevPath ||
209
+ path.delta !== prevDelta ||
210
+ prevProps.height !== props.height ||
211
+ prevProps.width !== props.width
212
+ ) {
213
+ instance.draw(path, props.width, props.height);
214
+
215
+ instance._prevDelta = path.delta;
216
+ instance._prevPath = path;
217
+ }
218
+}
219
+
220
+function applyTextProps(instance, props, prevProps = {}) {
221
+ applyRenderableNodeProps(instance, props, prevProps);
222
+
223
+ const string = props.children;
224
+
225
+ if (
226
+ instance._currentString !== string ||
227
+ !isSameFont(props.font, prevProps.font) ||
228
+ props.alignment !== prevProps.alignment ||
229
+ props.path !== prevProps.path
230
+ ) {
231
+ instance.draw(string, props.font, props.alignment, props.path);
232
+
233
+ instance._currentString = string;
234
+ }
235
+}
236
+
237
+const ReactARTHostConfig = {
238
+ appendInitialChild(parentInstance, child) {
239
+ if (typeof child === 'string') {
240
+ // Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
241
+ invariant(false, 'Text children should already be flattened.');
242
+ return;
243
+ }
244
+
245
+ child.inject(parentInstance);
246
+ },
247
+
248
+ createInstance(type, props, internalInstanceHandle) {
249
+ let instance;
250
+
251
+ switch (type) {
252
+ case TYPES.CLIPPING_RECTANGLE:
253
+ instance = Mode.ClippingRectangle();
254
+ instance._applyProps = applyClippingRectangleProps;
255
+ break;
256
+ case TYPES.GROUP:
257
+ instance = Mode.Group();
258
+ instance._applyProps = applyGroupProps;
259
+ break;
260
+ case TYPES.SHAPE:
261
+ instance = Mode.Shape();
262
+ instance._applyProps = applyShapeProps;
263
+ break;
264
+ case TYPES.TEXT:
265
+ instance = Mode.Text(
266
+ props.children,
267
+ props.font,
268
+ props.alignment,
269
+ props.path,
270
+ );
271
+ instance._applyProps = applyTextProps;
272
+ break;
273
+ }
274
+
275
+ invariant(instance, 'ReactART does not support the type "%s"', type);
276
+
277
+ instance._applyProps(instance, props);
278
+
279
+ return instance;
280
+ },
281
+
282
+ createTextInstance(text, rootContainerInstance, internalInstanceHandle) {
283
+ return text;
284
+ },
285
+
286
+ finalizeInitialChildren(domElement, type, props) {
287
+ return false;
288
+ },
289
+
290
+ getPublicInstance(instance) {
291
+ return instance;
292
+ },
293
+
294
+ prepareForCommit() {
295
+ // Noop
296
+ },
297
+
298
+ prepareUpdate(domElement, type, oldProps, newProps) {
299
+ return UPDATE_SIGNAL;
300
+ },
301
+
302
+ resetAfterCommit() {
303
+ // Noop
304
+ },
305
+
306
+ resetTextContent(domElement) {
307
+ // Noop
308
+ },
309
+
310
+ shouldDeprioritizeSubtree(type, props) {
311
+ return false;
312
+ },
313
+
314
+ getRootHostContext() {
315
+ return emptyObject;
316
+ },
317
+
318
+ getChildHostContext() {
319
+ return emptyObject;
320
+ },
321
+
322
+ scheduleDeferredCallback: ReactScheduler.rIC,
323
+
324
+ shouldSetTextContent(type, props) {
325
+ return (
326
+ typeof props.children === 'string' || typeof props.children === 'number'
327
+ );
328
+ },
329
+
330
+ now: ReactScheduler.now,
331
+
332
+ // The ART renderer is secondary to the React DOM renderer.
333
+ isPrimaryRenderer: false,
334
+
335
+ mutation: {
336
+ appendChild(parentInstance, child) {
337
+ if (child.parentNode === parentInstance) {
338
+ child.eject();
339
+ }
340
+ child.inject(parentInstance);
341
+ },
342
+
343
+ appendChildToContainer(parentInstance, child) {
344
+ if (child.parentNode === parentInstance) {
345
+ child.eject();
346
+ }
347
+ child.inject(parentInstance);
348
+ },
349
+
350
+ insertBefore(parentInstance, child, beforeChild) {
351
+ invariant(
352
+ child !== beforeChild,
353
+ 'ReactART: Can not insert node before itself',
354
+ );
355
+ child.injectBefore(beforeChild);
356
+ },
357
+
358
+ insertInContainerBefore(parentInstance, child, beforeChild) {
359
+ invariant(
360
+ child !== beforeChild,
361
+ 'ReactART: Can not insert node before itself',
362
+ );
363
+ child.injectBefore(beforeChild);
364
+ },
365
+
366
+ removeChild(parentInstance, child) {
367
+ destroyEventListeners(child);
368
+ child.eject();
369
+ },
370
+
371
+ removeChildFromContainer(parentInstance, child) {
372
+ destroyEventListeners(child);
373
+ child.eject();
374
+ },
375
+
376
+ commitTextUpdate(textInstance, oldText, newText) {
377
+ // Noop
378
+ },
379
+
380
+ commitMount(instance, type, newProps) {
381
+ // Noop
382
+ },
383
+
384
+ commitUpdate(instance, updatePayload, type, oldProps, newProps) {
385
+ instance._applyProps(instance, newProps, oldProps);
386
+ },
387
+ },
388
+};
389
+
390
+export default ReactARTHostConfig;
packages/react-art/src/ReactARTInternals.js
new
+34
@@ -0,0 +1,34 @@
1
+/**
2
+ * Copyright (c) 2013-present, Facebook, Inc.
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
+
8
+export const TYPES = {
9
+ CLIPPING_RECTANGLE: 'ClippingRectangle',
10
+ GROUP: 'Group',
11
+ SHAPE: 'Shape',
12
+ TEXT: 'Text',
13
+};
14
+
15
+export const EVENT_TYPES = {
16
+ onClick: 'click',
17
+ onMouseMove: 'mousemove',
18
+ onMouseOver: 'mouseover',
19
+ onMouseOut: 'mouseout',
20
+ onMouseUp: 'mouseup',
21
+ onMouseDown: 'mousedown',
22
+};
23
+
24
+export function childrenAsString(children) {
25
+ if (!children) {
26
+ return '';
27
+ } else if (typeof children === 'string') {
28
+ return children;
29
+ } else if (children.length) {
30
+ return children.join('');
31
+ } else {
32
+ return '';
33
+ }
34
+}
packages/react-dom/src/client/ReactDOM.js
+4
-545
@@ -14,12 +14,12 @@ import type {
14
FiberRoot,
15
Batch as FiberRootBatch,
16
} from 'react-reconciler/src/ReactFiberRoot';
17
+import type {Container} from './ReactDOMHostConfig';
18
19
import '../shared/checkReact';
20
import './ReactDOMClientInjection';
21
22
import ReactFiberReconciler from 'react-reconciler';
22
-// TODO: direct imports like some-package/src/* are bad. Fix me.
23
import * as ReactPortal from 'shared/ReactPortal';
24
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
25
import * as ReactGenericBatching from 'events/ReactGenericBatching';
@@ -29,53 +29,29 @@ import * as EventPluginRegistry from 'events/EventPluginRegistry';
29
import * as EventPropagators from 'events/EventPropagators';
30
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
31
import ReactVersion from 'shared/ReactVersion';
32
-import * as ReactScheduler from 'react-scheduler';
32
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
33
import getComponentName from 'shared/getComponentName';
34
import invariant from 'fbjs/lib/invariant';
35
import lowPriorityWarning from 'shared/lowPriorityWarning';
36
import warning from 'fbjs/lib/warning';
37
38
+import ReactDOMHostConfig from './ReactDOMHostConfig';
39
import * as ReactDOMComponentTree from './ReactDOMComponentTree';
40
import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';
41
-import * as ReactInputSelection from './ReactInputSelection';
42
-import setTextContent from './setTextContent';
43
-import validateDOMNesting from './validateDOMNesting';
44
-import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter';
41
import * as ReactDOMEventListener from '../events/ReactDOMEventListener';
46
-import {getChildNamespace} from '../shared/DOMNamespaces';
42
import {
43
ELEMENT_NODE,
49
- TEXT_NODE,
44
COMMENT_NODE,
45
DOCUMENT_NODE,
46
DOCUMENT_FRAGMENT_NODE,
47
} from '../shared/HTMLNodeType';
48
import {ROOT_ATTRIBUTE_NAME} from '../shared/DOMProperty';
55
-const {
56
- createElement,
57
- createTextNode,
58
- setInitialProperties,
59
- diffProperties,
60
- updateProperties,
61
- diffHydratedProperties,
62
- diffHydratedText,
63
- warnForUnmatchedText,
64
- warnForDeletedHydratableElement,
65
- warnForDeletedHydratableText,
66
- warnForInsertedHydratedElement,
67
- warnForInsertedHydratedText,
68
-} = ReactDOMFiberComponent;
69
-const {updatedAncestorInfo} = validateDOMNesting;
70
-const {precacheFiberNode, updateFiberProps} = ReactDOMComponentTree;
71
-
72
-let SUPPRESS_HYDRATION_WARNING;
49
+
50
let topLevelUpdateWarnings;
51
let warnOnInvalidCallback;
52
let didWarnAboutUnstableCreatePortal = false;
53
54
if (__DEV__) {
78
- SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
55
if (
56
typeof Map !== 'function' ||
57
Map.prototype == null ||
@@ -157,26 +133,6 @@ type DOMContainer =
133
_reactRootContainer: ?Root,
134
});
135
160
-type Container = Element | Document;
161
-type Props = {
162
- autoFocus?: boolean,
163
- children?: mixed,
164
- hidden?: boolean,
165
- suppressHydrationWarning?: boolean,
166
-};
167
-type Instance = Element;
168
-type TextInstance = Text;
169
-
170
-type HostContextDev = {
171
- namespace: string,
172
- ancestorInfo: mixed,
173
-};
174
-type HostContextProd = string;
175
-type HostContext = HostContextDev | HostContextProd;
176
-
177
-let eventsEnabled: ?boolean = null;
178
-let selectionInformation: ?mixed = null;
179
-
136
type Batch = FiberRootBatch & {
137
render(children: ReactNodeList): Work,
138
then(onComplete: () => mixed): void,
@@ -491,504 +447,7 @@ function shouldHydrateDueToLegacyHeuristic(container) {
447
);
448
}
449
494
-function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
495
- switch (type) {
496
- case 'button':
497
- case 'input':
498
- case 'select':
499
- case 'textarea':
500
- return !!props.autoFocus;
501
- }
502
- return false;
503
-}
504
-
505
-const DOMRenderer = ReactFiberReconciler({
506
- getRootHostContext(rootContainerInstance: Container): HostContext {
507
- let type;
508
- let namespace;
509
- const nodeType = rootContainerInstance.nodeType;
510
- switch (nodeType) {
511
- case DOCUMENT_NODE:
512
- case DOCUMENT_FRAGMENT_NODE: {
513
- type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
514
- let root = (rootContainerInstance: any).documentElement;
515
- namespace = root ? root.namespaceURI : getChildNamespace(null, '');
516
- break;
517
- }
518
- default: {
519
- const container: any =
520
- nodeType === COMMENT_NODE
521
- ? rootContainerInstance.parentNode
522
- : rootContainerInstance;
523
- const ownNamespace = container.namespaceURI || null;
524
- type = container.tagName;
525
- namespace = getChildNamespace(ownNamespace, type);
526
- break;
527
- }
528
- }
529
- if (__DEV__) {
530
- const validatedTag = type.toLowerCase();
531
- const ancestorInfo = updatedAncestorInfo(null, validatedTag, null);
532
- return {namespace, ancestorInfo};
533
- }
534
- return namespace;
535
- },
536
-
537
- getChildHostContext(
538
- parentHostContext: HostContext,
539
- type: string,
540
- ): HostContext {
541
- if (__DEV__) {
542
- const parentHostContextDev = ((parentHostContext: any): HostContextDev);
543
- const namespace = getChildNamespace(parentHostContextDev.namespace, type);
544
- const ancestorInfo = updatedAncestorInfo(
545
- parentHostContextDev.ancestorInfo,
546
- type,
547
- null,
548
- );
549
- return {namespace, ancestorInfo};
550
- }
551
- const parentNamespace = ((parentHostContext: any): HostContextProd);
552
- return getChildNamespace(parentNamespace, type);
553
- },
554
-
555
- getPublicInstance(instance) {
556
- return instance;
557
- },
558
-
559
- prepareForCommit(): void {
560
- eventsEnabled = ReactBrowserEventEmitter.isEnabled();
561
- selectionInformation = ReactInputSelection.getSelectionInformation();
562
- ReactBrowserEventEmitter.setEnabled(false);
563
- },
564
-
565
- resetAfterCommit(): void {
566
- ReactInputSelection.restoreSelection(selectionInformation);
567
- selectionInformation = null;
568
- ReactBrowserEventEmitter.setEnabled(eventsEnabled);
569
- eventsEnabled = null;
570
- },
571
-
572
- createInstance(
573
- type: string,
574
- props: Props,
575
- rootContainerInstance: Container,
576
- hostContext: HostContext,
577
- internalInstanceHandle: Object,
578
- ): Instance {
579
- let parentNamespace: string;
580
- if (__DEV__) {
581
- // TODO: take namespace into account when validating.
582
- const hostContextDev = ((hostContext: any): HostContextDev);
583
- validateDOMNesting(type, null, hostContextDev.ancestorInfo);
584
- if (
585
- typeof props.children === 'string' ||
586
- typeof props.children === 'number'
587
- ) {
588
- const string = '' + props.children;
589
- const ownAncestorInfo = updatedAncestorInfo(
590
- hostContextDev.ancestorInfo,
591
- type,
592
- null,
593
- );
594
- validateDOMNesting(null, string, ownAncestorInfo);
595
- }
596
- parentNamespace = hostContextDev.namespace;
597
- } else {
598
- parentNamespace = ((hostContext: any): HostContextProd);
599
- }
600
- const domElement: Instance = createElement(
601
- type,
602
- props,
603
- rootContainerInstance,
604
- parentNamespace,
605
- );
606
- precacheFiberNode(internalInstanceHandle, domElement);
607
- updateFiberProps(domElement, props);
608
- return domElement;
609
- },
610
-
611
- appendInitialChild(
612
- parentInstance: Instance,
613
- child: Instance | TextInstance,
614
- ): void {
615
- parentInstance.appendChild(child);
616
- },
617
-
618
- finalizeInitialChildren(
619
- domElement: Instance,
620
- type: string,
621
- props: Props,
622
- rootContainerInstance: Container,
623
- ): boolean {
624
- setInitialProperties(domElement, type, props, rootContainerInstance);
625
- return shouldAutoFocusHostComponent(type, props);
626
- },
627
-
628
- prepareUpdate(
629
- domElement: Instance,
630
- type: string,
631
- oldProps: Props,
632
- newProps: Props,
633
- rootContainerInstance: Container,
634
- hostContext: HostContext,
635
- ): null | Array<mixed> {
636
- if (__DEV__) {
637
- const hostContextDev = ((hostContext: any): HostContextDev);
638
- if (
639
- typeof newProps.children !== typeof oldProps.children &&
640
- (typeof newProps.children === 'string' ||
641
- typeof newProps.children === 'number')
642
- ) {
643
- const string = '' + newProps.children;
644
- const ownAncestorInfo = updatedAncestorInfo(
645
- hostContextDev.ancestorInfo,
646
- type,
647
- null,
648
- );
649
- validateDOMNesting(null, string, ownAncestorInfo);
650
- }
651
- }
652
- return diffProperties(
653
- domElement,
654
- type,
655
- oldProps,
656
- newProps,
657
- rootContainerInstance,
658
- );
659
- },
660
-
661
- shouldSetTextContent(type: string, props: Props): boolean {
662
- return (
663
- type === 'textarea' ||
664
- typeof props.children === 'string' ||
665
- typeof props.children === 'number' ||
666
- (typeof props.dangerouslySetInnerHTML === 'object' &&
667
- props.dangerouslySetInnerHTML !== null &&
668
- typeof props.dangerouslySetInnerHTML.__html === 'string')
669
- );
670
- },
671
-
672
- shouldDeprioritizeSubtree(type: string, props: Props): boolean {
673
- return !!props.hidden;
674
- },
675
-
676
- createTextInstance(
677
- text: string,
678
- rootContainerInstance: Container,
679
- hostContext: HostContext,
680
- internalInstanceHandle: Object,
681
- ): TextInstance {
682
- if (__DEV__) {
683
- const hostContextDev = ((hostContext: any): HostContextDev);
684
- validateDOMNesting(null, text, hostContextDev.ancestorInfo);
685
- }
686
- const textNode: TextInstance = createTextNode(text, rootContainerInstance);
687
- precacheFiberNode(internalInstanceHandle, textNode);
688
- return textNode;
689
- },
690
-
691
- now: ReactScheduler.now,
692
-
693
- isPrimaryRenderer: true,
694
-
695
- mutation: {
696
- commitMount(
697
- domElement: Instance,
698
- type: string,
699
- newProps: Props,
700
- internalInstanceHandle: Object,
701
- ): void {
702
- // Despite the naming that might imply otherwise, this method only
703
- // fires if there is an `Update` effect scheduled during mounting.
704
- // This happens if `finalizeInitialChildren` returns `true` (which it
705
- // does to implement the `autoFocus` attribute on the client). But
706
- // there are also other cases when this might happen (such as patching
707
- // up text content during hydration mismatch). So we'll check this again.
708
- if (shouldAutoFocusHostComponent(type, newProps)) {
709
- ((domElement: any):
710
- | HTMLButtonElement
711
- | HTMLInputElement
712
- | HTMLSelectElement
713
- | HTMLTextAreaElement).focus();
714
- }
715
- },
716
-
717
- commitUpdate(
718
- domElement: Instance,
719
- updatePayload: Array<mixed>,
720
- type: string,
721
- oldProps: Props,
722
- newProps: Props,
723
- internalInstanceHandle: Object,
724
- ): void {
725
- // Update the props handle so that we know which props are the ones with
726
- // with current event handlers.
727
- updateFiberProps(domElement, newProps);
728
- // Apply the diff to the DOM node.
729
- updateProperties(domElement, updatePayload, type, oldProps, newProps);
730
- },
731
-
732
- resetTextContent(domElement: Instance): void {
733
- setTextContent(domElement, '');
734
- },
735
-
736
- commitTextUpdate(
737
- textInstance: TextInstance,
738
- oldText: string,
739
- newText: string,
740
- ): void {
741
- textInstance.nodeValue = newText;
742
- },
743
-
744
- appendChild(
745
- parentInstance: Instance,
746
- child: Instance | TextInstance,
747
- ): void {
748
- parentInstance.appendChild(child);
749
- },
750
-
751
- appendChildToContainer(
752
- container: Container,
753
- child: Instance | TextInstance,
754
- ): void {
755
- if (container.nodeType === COMMENT_NODE) {
756
- (container.parentNode: any).insertBefore(child, container);
757
- } else {
758
- container.appendChild(child);
759
- }
760
- },
761
-
762
- insertBefore(
763
- parentInstance: Instance,
764
- child: Instance | TextInstance,
765
- beforeChild: Instance | TextInstance,
766
- ): void {
767
- parentInstance.insertBefore(child, beforeChild);
768
- },
769
-
770
- insertInContainerBefore(
771
- container: Container,
772
- child: Instance | TextInstance,
773
- beforeChild: Instance | TextInstance,
774
- ): void {
775
- if (container.nodeType === COMMENT_NODE) {
776
- (container.parentNode: any).insertBefore(child, beforeChild);
777
- } else {
778
- container.insertBefore(child, beforeChild);
779
- }
780
- },
781
-
782
- removeChild(
783
- parentInstance: Instance,
784
- child: Instance | TextInstance,
785
- ): void {
786
- parentInstance.removeChild(child);
787
- },
788
-
789
- removeChildFromContainer(
790
- container: Container,
791
- child: Instance | TextInstance,
792
- ): void {
793
- if (container.nodeType === COMMENT_NODE) {
794
- (container.parentNode: any).removeChild(child);
795
- } else {
796
- container.removeChild(child);
797
- }
798
- },
799
- },
800
-
801
- hydration: {
802
- canHydrateInstance(
803
- instance: Instance | TextInstance,
804
- type: string,
805
- props: Props,
806
- ): null | Instance {
807
- if (
808
- instance.nodeType !== ELEMENT_NODE ||
809
- type.toLowerCase() !== instance.nodeName.toLowerCase()
810
- ) {
811
- return null;
812
- }
813
- // This has now been refined to an element node.
814
- return ((instance: any): Instance);
815
- },
816
-
817
- canHydrateTextInstance(
818
- instance: Instance | TextInstance,
819
- text: string,
820
- ): null | TextInstance {
821
- if (text === '' || instance.nodeType !== TEXT_NODE) {
822
- // Empty strings are not parsed by HTML so there won't be a correct match here.
823
- return null;
824
- }
825
- // This has now been refined to a text node.
826
- return ((instance: any): TextInstance);
827
- },
828
-
829
- getNextHydratableSibling(
830
- instance: Instance | TextInstance,
831
- ): null | Instance | TextInstance {
832
- let node = instance.nextSibling;
833
- // Skip non-hydratable nodes.
834
- while (
835
- node &&
836
- node.nodeType !== ELEMENT_NODE &&
837
- node.nodeType !== TEXT_NODE
838
- ) {
839
- node = node.nextSibling;
840
- }
841
- return (node: any);
842
- },
843
-
844
- getFirstHydratableChild(
845
- parentInstance: Container | Instance,
846
- ): null | Instance | TextInstance {
847
- let next = parentInstance.firstChild;
848
- // Skip non-hydratable nodes.
849
- while (
850
- next &&
851
- next.nodeType !== ELEMENT_NODE &&
852
- next.nodeType !== TEXT_NODE
853
- ) {
854
- next = next.nextSibling;
855
- }
856
- return (next: any);
857
- },
858
-
859
- hydrateInstance(
860
- instance: Instance,
861
- type: string,
862
- props: Props,
863
- rootContainerInstance: Container,
864
- hostContext: HostContext,
865
- internalInstanceHandle: Object,
866
- ): null | Array<mixed> {
867
- precacheFiberNode(internalInstanceHandle, instance);
868
- // TODO: Possibly defer this until the commit phase where all the events
869
- // get attached.
870
- updateFiberProps(instance, props);
871
- let parentNamespace: string;
872
- if (__DEV__) {
873
- const hostContextDev = ((hostContext: any): HostContextDev);
874
- parentNamespace = hostContextDev.namespace;
875
- } else {
876
- parentNamespace = ((hostContext: any): HostContextProd);
877
- }
878
- return diffHydratedProperties(
879
- instance,
880
- type,
881
- props,
882
- parentNamespace,
883
- rootContainerInstance,
884
- );
885
- },
886
-
887
- hydrateTextInstance(
888
- textInstance: TextInstance,
889
- text: string,
890
- internalInstanceHandle: Object,
891
- ): boolean {
892
- precacheFiberNode(internalInstanceHandle, textInstance);
893
- return diffHydratedText(textInstance, text);
894
- },
895
-
896
- didNotMatchHydratedContainerTextInstance(
897
- parentContainer: Container,
898
- textInstance: TextInstance,
899
- text: string,
900
- ) {
901
- if (__DEV__) {
902
- warnForUnmatchedText(textInstance, text);
903
- }
904
- },
905
-
906
- didNotMatchHydratedTextInstance(
907
- parentType: string,
908
- parentProps: Props,
909
- parentInstance: Instance,
910
- textInstance: TextInstance,
911
- text: string,
912
- ) {
913
- if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
914
- warnForUnmatchedText(textInstance, text);
915
- }
916
- },
917
-
918
- didNotHydrateContainerInstance(
919
- parentContainer: Container,
920
- instance: Instance | TextInstance,
921
- ) {
922
- if (__DEV__) {
923
- if (instance.nodeType === 1) {
924
- warnForDeletedHydratableElement(parentContainer, (instance: any));
925
- } else {
926
- warnForDeletedHydratableText(parentContainer, (instance: any));
927
- }
928
- }
929
- },
930
-
931
- didNotHydrateInstance(
932
- parentType: string,
933
- parentProps: Props,
934
- parentInstance: Instance,
935
- instance: Instance | TextInstance,
936
- ) {
937
- if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
938
- if (instance.nodeType === 1) {
939
- warnForDeletedHydratableElement(parentInstance, (instance: any));
940
- } else {
941
- warnForDeletedHydratableText(parentInstance, (instance: any));
942
- }
943
- }
944
- },
945
-
946
- didNotFindHydratableContainerInstance(
947
- parentContainer: Container,
948
- type: string,
949
- props: Props,
950
- ) {
951
- if (__DEV__) {
952
- warnForInsertedHydratedElement(parentContainer, type, props);
953
- }
954
- },
955
-
956
- didNotFindHydratableContainerTextInstance(
957
- parentContainer: Container,
958
- text: string,
959
- ) {
960
- if (__DEV__) {
961
- warnForInsertedHydratedText(parentContainer, text);
962
- }
963
- },
964
-
965
- didNotFindHydratableInstance(
966
- parentType: string,
967
- parentProps: Props,
968
- parentInstance: Instance,
969
- type: string,
970
- props: Props,
971
- ) {
972
- if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
973
- warnForInsertedHydratedElement(parentInstance, type, props);
974
- }
975
- },
976
-
977
- didNotFindHydratableTextInstance(
978
- parentType: string,
979
- parentProps: Props,
980
- parentInstance: Instance,
981
- text: string,
982
- ) {
983
- if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
984
- warnForInsertedHydratedText(parentInstance, text);
985
- }
986
- },
987
- },
988
-
989
- scheduleDeferredCallback: ReactScheduler.rIC,
990
- cancelDeferredCallback: ReactScheduler.cIC,
991
-});
450
+const DOMRenderer = ReactFiberReconciler(ReactDOMHostConfig);
451
452
ReactGenericBatching.injection.injectRenderer(DOMRenderer);
453
packages/react-dom/src/client/ReactDOMHostConfig.js
new
+568
@@ -0,0 +1,568 @@
1
+/**
2
+ * Copyright (c) 2013-present, Facebook, Inc.
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
8
+ */
9
+
10
+import * as ReactScheduler from 'react-scheduler';
11
+
12
+import * as ReactDOMComponentTree from './ReactDOMComponentTree';
13
+import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';
14
+import * as ReactInputSelection from './ReactInputSelection';
15
+import setTextContent from './setTextContent';
16
+import validateDOMNesting from './validateDOMNesting';
17
+import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter';
18
+import {getChildNamespace} from '../shared/DOMNamespaces';
19
+import {
20
+ ELEMENT_NODE,
21
+ TEXT_NODE,
22
+ COMMENT_NODE,
23
+ DOCUMENT_NODE,
24
+ DOCUMENT_FRAGMENT_NODE,
25
+} from '../shared/HTMLNodeType';
26
+
27
+export type Container = Element | Document;
28
+type Props = {
29
+ autoFocus?: boolean,
30
+ children?: mixed,
31
+ hidden?: boolean,
32
+ suppressHydrationWarning?: boolean,
33
+};
34
+type Instance = Element;
35
+type TextInstance = Text;
36
+
37
+type HostContextDev = {
38
+ namespace: string,
39
+ ancestorInfo: mixed,
40
+};
41
+type HostContextProd = string;
42
+type HostContext = HostContextDev | HostContextProd;
43
+
44
+const {
45
+ createElement,
46
+ createTextNode,
47
+ setInitialProperties,
48
+ diffProperties,
49
+ updateProperties,
50
+ diffHydratedProperties,
51
+ diffHydratedText,
52
+ warnForUnmatchedText,
53
+ warnForDeletedHydratableElement,
54
+ warnForDeletedHydratableText,
55
+ warnForInsertedHydratedElement,
56
+ warnForInsertedHydratedText,
57
+} = ReactDOMFiberComponent;
58
+const {updatedAncestorInfo} = validateDOMNesting;
59
+const {precacheFiberNode, updateFiberProps} = ReactDOMComponentTree;
60
+
61
+let SUPPRESS_HYDRATION_WARNING;
62
+if (__DEV__) {
63
+ SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
64
+}
65
+
66
+let eventsEnabled: ?boolean = null;
67
+let selectionInformation: ?mixed = null;
68
+
69
+function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
70
+ switch (type) {
71
+ case 'button':
72
+ case 'input':
73
+ case 'select':
74
+ case 'textarea':
75
+ return !!props.autoFocus;
76
+ }
77
+ return false;
78
+}
79
+
80
+const ReactDOMHostConfig = {
81
+ getRootHostContext(rootContainerInstance: Container): HostContext {
82
+ let type;
83
+ let namespace;
84
+ const nodeType = rootContainerInstance.nodeType;
85
+ switch (nodeType) {
86
+ case DOCUMENT_NODE:
87
+ case DOCUMENT_FRAGMENT_NODE: {
88
+ type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
89
+ let root = (rootContainerInstance: any).documentElement;
90
+ namespace = root ? root.namespaceURI : getChildNamespace(null, '');
91
+ break;
92
+ }
93
+ default: {
94
+ const container: any =
95
+ nodeType === COMMENT_NODE
96
+ ? rootContainerInstance.parentNode
97
+ : rootContainerInstance;
98
+ const ownNamespace = container.namespaceURI || null;
99
+ type = container.tagName;
100
+ namespace = getChildNamespace(ownNamespace, type);
101
+ break;
102
+ }
103
+ }
104
+ if (__DEV__) {
105
+ const validatedTag = type.toLowerCase();
106
+ const ancestorInfo = updatedAncestorInfo(null, validatedTag, null);
107
+ return {namespace, ancestorInfo};
108
+ }
109
+ return namespace;
110
+ },
111
+
112
+ getChildHostContext(
113
+ parentHostContext: HostContext,
114
+ type: string,
115
+ ): HostContext {
116
+ if (__DEV__) {
117
+ const parentHostContextDev = ((parentHostContext: any): HostContextDev);
118
+ const namespace = getChildNamespace(parentHostContextDev.namespace, type);
119
+ const ancestorInfo = updatedAncestorInfo(
120
+ parentHostContextDev.ancestorInfo,
121
+ type,
122
+ null,
123
+ );
124
+ return {namespace, ancestorInfo};
125
+ }
126
+ const parentNamespace = ((parentHostContext: any): HostContextProd);
127
+ return getChildNamespace(parentNamespace, type);
128
+ },
129
+
130
+ getPublicInstance(instance: Instance | TextInstance): * {
131
+ return instance;
132
+ },
133
+
134
+ prepareForCommit(): void {
135
+ eventsEnabled = ReactBrowserEventEmitter.isEnabled();
136
+ selectionInformation = ReactInputSelection.getSelectionInformation();
137
+ ReactBrowserEventEmitter.setEnabled(false);
138
+ },
139
+
140
+ resetAfterCommit(): void {
141
+ ReactInputSelection.restoreSelection(selectionInformation);
142
+ selectionInformation = null;
143
+ ReactBrowserEventEmitter.setEnabled(eventsEnabled);
144
+ eventsEnabled = null;
145
+ },
146
+
147
+ createInstance(
148
+ type: string,
149
+ props: Props,
150
+ rootContainerInstance: Container,
151
+ hostContext: HostContext,
152
+ internalInstanceHandle: Object,
153
+ ): Instance {
154
+ let parentNamespace: string;
155
+ if (__DEV__) {
156
+ // TODO: take namespace into account when validating.
157
+ const hostContextDev = ((hostContext: any): HostContextDev);
158
+ validateDOMNesting(type, null, hostContextDev.ancestorInfo);
159
+ if (
160
+ typeof props.children === 'string' ||
161
+ typeof props.children === 'number'
162
+ ) {
163
+ const string = '' + props.children;
164
+ const ownAncestorInfo = updatedAncestorInfo(
165
+ hostContextDev.ancestorInfo,
166
+ type,
167
+ null,
168
+ );
169
+ validateDOMNesting(null, string, ownAncestorInfo);
170
+ }
171
+ parentNamespace = hostContextDev.namespace;
172
+ } else {
173
+ parentNamespace = ((hostContext: any): HostContextProd);
174
+ }
175
+ const domElement: Instance = createElement(
176
+ type,
177
+ props,
178
+ rootContainerInstance,
179
+ parentNamespace,
180
+ );
181
+ precacheFiberNode(internalInstanceHandle, domElement);
182
+ updateFiberProps(domElement, props);
183
+ return domElement;
184
+ },
185
+
186
+ appendInitialChild(
187
+ parentInstance: Instance,
188
+ child: Instance | TextInstance,
189
+ ): void {
190
+ parentInstance.appendChild(child);
191
+ },
192
+
193
+ finalizeInitialChildren(
194
+ domElement: Instance,
195
+ type: string,
196
+ props: Props,
197
+ rootContainerInstance: Container,
198
+ ): boolean {
199
+ setInitialProperties(domElement, type, props, rootContainerInstance);
200
+ return shouldAutoFocusHostComponent(type, props);
201
+ },
202
+
203
+ prepareUpdate(
204
+ domElement: Instance,
205
+ type: string,
206
+ oldProps: Props,
207
+ newProps: Props,
208
+ rootContainerInstance: Container,
209
+ hostContext: HostContext,
210
+ ): null | Array<mixed> {
211
+ if (__DEV__) {
212
+ const hostContextDev = ((hostContext: any): HostContextDev);
213
+ if (
214
+ typeof newProps.children !== typeof oldProps.children &&
215
+ (typeof newProps.children === 'string' ||
216
+ typeof newProps.children === 'number')
217
+ ) {
218
+ const string = '' + newProps.children;
219
+ const ownAncestorInfo = updatedAncestorInfo(
220
+ hostContextDev.ancestorInfo,
221
+ type,
222
+ null,
223
+ );
224
+ validateDOMNesting(null, string, ownAncestorInfo);
225
+ }
226
+ }
227
+ return diffProperties(
228
+ domElement,
229
+ type,
230
+ oldProps,
231
+ newProps,
232
+ rootContainerInstance,
233
+ );
234
+ },
235
+
236
+ shouldSetTextContent(type: string, props: Props): boolean {
237
+ return (
238
+ type === 'textarea' ||
239
+ typeof props.children === 'string' ||
240
+ typeof props.children === 'number' ||
241
+ (typeof props.dangerouslySetInnerHTML === 'object' &&
242
+ props.dangerouslySetInnerHTML !== null &&
243
+ typeof props.dangerouslySetInnerHTML.__html === 'string')
244
+ );
245
+ },
246
+
247
+ shouldDeprioritizeSubtree(type: string, props: Props): boolean {
248
+ return !!props.hidden;
249
+ },
250
+
251
+ createTextInstance(
252
+ text: string,
253
+ rootContainerInstance: Container,
254
+ hostContext: HostContext,
255
+ internalInstanceHandle: Object,
256
+ ): TextInstance {
257
+ if (__DEV__) {
258
+ const hostContextDev = ((hostContext: any): HostContextDev);
259
+ validateDOMNesting(null, text, hostContextDev.ancestorInfo);
260
+ }
261
+ const textNode: TextInstance = createTextNode(text, rootContainerInstance);
262
+ precacheFiberNode(internalInstanceHandle, textNode);
263
+ return textNode;
264
+ },
265
+
266
+ now: ReactScheduler.now,
267
+
268
+ isPrimaryRenderer: true,
269
+
270
+ mutation: {
271
+ commitMount(
272
+ domElement: Instance,
273
+ type: string,
274
+ newProps: Props,
275
+ internalInstanceHandle: Object,
276
+ ): void {
277
+ // Despite the naming that might imply otherwise, this method only
278
+ // fires if there is an `Update` effect scheduled during mounting.
279
+ // This happens if `finalizeInitialChildren` returns `true` (which it
280
+ // does to implement the `autoFocus` attribute on the client). But
281
+ // there are also other cases when this might happen (such as patching
282
+ // up text content during hydration mismatch). So we'll check this again.
283
+ if (shouldAutoFocusHostComponent(type, newProps)) {
284
+ ((domElement: any):
285
+ | HTMLButtonElement
286
+ | HTMLInputElement
287
+ | HTMLSelectElement
288
+ | HTMLTextAreaElement).focus();
289
+ }
290
+ },
291
+
292
+ commitUpdate(
293
+ domElement: Instance,
294
+ updatePayload: Array<mixed>,
295
+ type: string,
296
+ oldProps: Props,
297
+ newProps: Props,
298
+ internalInstanceHandle: Object,
299
+ ): void {
300
+ // Update the props handle so that we know which props are the ones with
301
+ // with current event handlers.
302
+ updateFiberProps(domElement, newProps);
303
+ // Apply the diff to the DOM node.
304
+ updateProperties(domElement, updatePayload, type, oldProps, newProps);
305
+ },
306
+
307
+ resetTextContent(domElement: Instance): void {
308
+ setTextContent(domElement, '');
309
+ },
310
+
311
+ commitTextUpdate(
312
+ textInstance: TextInstance,
313
+ oldText: string,
314
+ newText: string,
315
+ ): void {
316
+ textInstance.nodeValue = newText;
317
+ },
318
+
319
+ appendChild(
320
+ parentInstance: Instance,
321
+ child: Instance | TextInstance,
322
+ ): void {
323
+ parentInstance.appendChild(child);
324
+ },
325
+
326
+ appendChildToContainer(
327
+ container: Container,
328
+ child: Instance | TextInstance,
329
+ ): void {
330
+ if (container.nodeType === COMMENT_NODE) {
331
+ (container.parentNode: any).insertBefore(child, container);
332
+ } else {
333
+ container.appendChild(child);
334
+ }
335
+ },
336
+
337
+ insertBefore(
338
+ parentInstance: Instance,
339
+ child: Instance | TextInstance,
340
+ beforeChild: Instance | TextInstance,
341
+ ): void {
342
+ parentInstance.insertBefore(child, beforeChild);
343
+ },
344
+
345
+ insertInContainerBefore(
346
+ container: Container,
347
+ child: Instance | TextInstance,
348
+ beforeChild: Instance | TextInstance,
349
+ ): void {
350
+ if (container.nodeType === COMMENT_NODE) {
351
+ (container.parentNode: any).insertBefore(child, beforeChild);
352
+ } else {
353
+ container.insertBefore(child, beforeChild);
354
+ }
355
+ },
356
+
357
+ removeChild(
358
+ parentInstance: Instance,
359
+ child: Instance | TextInstance,
360
+ ): void {
361
+ parentInstance.removeChild(child);
362
+ },
363
+
364
+ removeChildFromContainer(
365
+ container: Container,
366
+ child: Instance | TextInstance,
367
+ ): void {
368
+ if (container.nodeType === COMMENT_NODE) {
369
+ (container.parentNode: any).removeChild(child);
370
+ } else {
371
+ container.removeChild(child);
372
+ }
373
+ },
374
+ },
375
+
376
+ hydration: {
377
+ canHydrateInstance(
378
+ instance: Instance | TextInstance,
379
+ type: string,
380
+ props: Props,
381
+ ): null | Instance {
382
+ if (
383
+ instance.nodeType !== ELEMENT_NODE ||
384
+ type.toLowerCase() !== instance.nodeName.toLowerCase()
385
+ ) {
386
+ return null;
387
+ }
388
+ // This has now been refined to an element node.
389
+ return ((instance: any): Instance);
390
+ },
391
+
392
+ canHydrateTextInstance(
393
+ instance: Instance | TextInstance,
394
+ text: string,
395
+ ): null | TextInstance {
396
+ if (text === '' || instance.nodeType !== TEXT_NODE) {
397
+ // Empty strings are not parsed by HTML so there won't be a correct match here.
398
+ return null;
399
+ }
400
+ // This has now been refined to a text node.
401
+ return ((instance: any): TextInstance);
402
+ },
403
+
404
+ getNextHydratableSibling(
405
+ instance: Instance | TextInstance,
406
+ ): null | Instance | TextInstance {
407
+ let node = instance.nextSibling;
408
+ // Skip non-hydratable nodes.
409
+ while (
410
+ node &&
411
+ node.nodeType !== ELEMENT_NODE &&
412
+ node.nodeType !== TEXT_NODE
413
+ ) {
414
+ node = node.nextSibling;
415
+ }
416
+ return (node: any);
417
+ },
418
+
419
+ getFirstHydratableChild(
420
+ parentInstance: Container | Instance,
421
+ ): null | Instance | TextInstance {
422
+ let next = parentInstance.firstChild;
423
+ // Skip non-hydratable nodes.
424
+ while (
425
+ next &&
426
+ next.nodeType !== ELEMENT_NODE &&
427
+ next.nodeType !== TEXT_NODE
428
+ ) {
429
+ next = next.nextSibling;
430
+ }
431
+ return (next: any);
432
+ },
433
+
434
+ hydrateInstance(
435
+ instance: Instance,
436
+ type: string,
437
+ props: Props,
438
+ rootContainerInstance: Container,
439
+ hostContext: HostContext,
440
+ internalInstanceHandle: Object,
441
+ ): null | Array<mixed> {
442
+ precacheFiberNode(internalInstanceHandle, instance);
443
+ // TODO: Possibly defer this until the commit phase where all the events
444
+ // get attached.
445
+ updateFiberProps(instance, props);
446
+ let parentNamespace: string;
447
+ if (__DEV__) {
448
+ const hostContextDev = ((hostContext: any): HostContextDev);
449
+ parentNamespace = hostContextDev.namespace;
450
+ } else {
451
+ parentNamespace = ((hostContext: any): HostContextProd);
452
+ }
453
+ return diffHydratedProperties(
454
+ instance,
455
+ type,
456
+ props,
457
+ parentNamespace,
458
+ rootContainerInstance,
459
+ );
460
+ },
461
+
462
+ hydrateTextInstance(
463
+ textInstance: TextInstance,
464
+ text: string,
465
+ internalInstanceHandle: Object,
466
+ ): boolean {
467
+ precacheFiberNode(internalInstanceHandle, textInstance);
468
+ return diffHydratedText(textInstance, text);
469
+ },
470
+
471
+ didNotMatchHydratedContainerTextInstance(
472
+ parentContainer: Container,
473
+ textInstance: TextInstance,
474
+ text: string,
475
+ ) {
476
+ if (__DEV__) {
477
+ warnForUnmatchedText(textInstance, text);
478
+ }
479
+ },
480
+
481
+ didNotMatchHydratedTextInstance(
482
+ parentType: string,
483
+ parentProps: Props,
484
+ parentInstance: Instance,
485
+ textInstance: TextInstance,
486
+ text: string,
487
+ ) {
488
+ if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
489
+ warnForUnmatchedText(textInstance, text);
490
+ }
491
+ },
492
+
493
+ didNotHydrateContainerInstance(
494
+ parentContainer: Container,
495
+ instance: Instance | TextInstance,
496
+ ) {
497
+ if (__DEV__) {
498
+ if (instance.nodeType === 1) {
499
+ warnForDeletedHydratableElement(parentContainer, (instance: any));
500
+ } else {
501
+ warnForDeletedHydratableText(parentContainer, (instance: any));
502
+ }
503
+ }
504
+ },
505
+
506
+ didNotHydrateInstance(
507
+ parentType: string,
508
+ parentProps: Props,
509
+ parentInstance: Instance,
510
+ instance: Instance | TextInstance,
511
+ ) {
512
+ if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
513
+ if (instance.nodeType === 1) {
514
+ warnForDeletedHydratableElement(parentInstance, (instance: any));
515
+ } else {
516
+ warnForDeletedHydratableText(parentInstance, (instance: any));
517
+ }
518
+ }
519
+ },
520
+
521
+ didNotFindHydratableContainerInstance(
522
+ parentContainer: Container,
523
+ type: string,
524
+ props: Props,
525
+ ) {
526
+ if (__DEV__) {
527
+ warnForInsertedHydratedElement(parentContainer, type, props);
528
+ }
529
+ },
530
+
531
+ didNotFindHydratableContainerTextInstance(
532
+ parentContainer: Container,
533
+ text: string,
534
+ ) {
535
+ if (__DEV__) {
536
+ warnForInsertedHydratedText(parentContainer, text);
537
+ }
538
+ },
539
+
540
+ didNotFindHydratableInstance(
541
+ parentType: string,
542
+ parentProps: Props,
543
+ parentInstance: Instance,
544
+ type: string,
545
+ props: Props,
546
+ ) {
547
+ if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
548
+ warnForInsertedHydratedElement(parentInstance, type, props);
549
+ }
550
+ },
551
+
552
+ didNotFindHydratableTextInstance(
553
+ parentType: string,
554
+ parentProps: Props,
555
+ parentInstance: Instance,
556
+ text: string,
557
+ ) {
558
+ if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
559
+ warnForInsertedHydratedText(parentInstance, text);
560
+ }
561
+ },
562
+ },
563
+
564
+ scheduleDeferredCallback: ReactScheduler.rIC,
565
+ cancelDeferredCallback: ReactScheduler.cIC,
566
+};
567
+
568
+export default ReactDOMHostConfig;
packages/react-native-renderer/src/ReactFabricHostConfig.js
new
+355
@@ -0,0 +1,355 @@
1
+/**
2
+ * Copyright (c) 2013-present, Facebook, Inc.
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
8
+ */
9
+
10
+import type {
11
+ MeasureInWindowOnSuccessCallback,
12
+ MeasureLayoutOnSuccessCallback,
13
+ MeasureOnSuccessCallback,
14
+ NativeMethodsMixinType,
15
+ ReactNativeBaseComponentViewConfig,
16
+} from './ReactNativeTypes';
17
+
18
+import {mountSafeCallback, warnForStyleProps} from './NativeMethodsMixinUtils';
19
+import * as ReactNativeAttributePayload from './ReactNativeAttributePayload';
20
+import * as ReactNativeFrameScheduling from './ReactNativeFrameScheduling';
21
+import * as ReactNativeViewConfigRegistry from 'ReactNativeViewConfigRegistry';
22
+
23
+import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
24
+import invariant from 'fbjs/lib/invariant';
25
+
26
+// Modules provided by RN:
27
+import TextInputState from 'TextInputState';
28
+import FabricUIManager from 'FabricUIManager';
29
+import UIManager from 'UIManager';
30
+
31
+// Counter for uniquely identifying views.
32
+// % 10 === 1 means it is a rootTag.
33
+// % 2 === 0 means it is a Fabric tag.
34
+// This means that they never overlap.
35
+let nextReactTag = 2;
36
+
37
+type HostContext = $ReadOnly<{|
38
+ isInAParentText: boolean,
39
+|}>;
40
+
41
+/**
42
+ * This is used for refs on host components.
43
+ */
44
+class ReactFabricHostComponent {
45
+ _nativeTag: number;
46
+ viewConfig: ReactNativeBaseComponentViewConfig;
47
+ currentProps: Props;
48
+
49
+ constructor(
50
+ tag: number,
51
+ viewConfig: ReactNativeBaseComponentViewConfig,
52
+ props: Props,
53
+ ) {
54
+ this._nativeTag = tag;
55
+ this.viewConfig = viewConfig;
56
+ this.currentProps = props;
57
+ }
58
+
59
+ blur() {
60
+ TextInputState.blurTextInput(this._nativeTag);
61
+ }
62
+
63
+ focus() {
64
+ TextInputState.focusTextInput(this._nativeTag);
65
+ }
66
+
67
+ measure(callback: MeasureOnSuccessCallback) {
68
+ UIManager.measure(this._nativeTag, mountSafeCallback(this, callback));
69
+ }
70
+
71
+ measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
72
+ UIManager.measureInWindow(
73
+ this._nativeTag,
74
+ mountSafeCallback(this, callback),
75
+ );
76
+ }
77
+
78
+ measureLayout(
79
+ relativeToNativeNode: number,
80
+ onSuccess: MeasureLayoutOnSuccessCallback,
81
+ onFail: () => void /* currently unused */,
82
+ ) {
83
+ UIManager.measureLayout(
84
+ this._nativeTag,
85
+ relativeToNativeNode,
86
+ mountSafeCallback(this, onFail),
87
+ mountSafeCallback(this, onSuccess),
88
+ );
89
+ }
90
+
91
+ setNativeProps(nativeProps: Object) {
92
+ if (__DEV__) {
93
+ warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
94
+ }
95
+
96
+ const updatePayload = ReactNativeAttributePayload.create(
97
+ nativeProps,
98
+ this.viewConfig.validAttributes,
99
+ );
100
+
101
+ // Avoid the overhead of bridge calls if there's no update.
102
+ // This is an expensive no-op for Android, and causes an unnecessary
103
+ // view invalidation for certain components (eg RCTTextInput) on iOS.
104
+ if (updatePayload != null) {
105
+ UIManager.updateView(
106
+ this._nativeTag,
107
+ this.viewConfig.uiViewClassName,
108
+ updatePayload,
109
+ );
110
+ }
111
+ }
112
+}
113
+
114
+// eslint-disable-next-line no-unused-expressions
115
+(ReactFabricHostComponent.prototype: NativeMethodsMixinType);
116
+
117
+type Node = Object;
118
+type ChildSet = Object;
119
+type Container = number;
120
+type Instance = {
121
+ node: Node,
122
+ canonical: ReactFabricHostComponent,
123
+};
124
+type Props = Object;
125
+type TextInstance = {
126
+ node: Node,
127
+};
128
+
129
+const ReacFabricHostConfig = {
130
+ appendInitialChild(
131
+ parentInstance: Instance,
132
+ child: Instance | TextInstance,
133
+ ): void {
134
+ FabricUIManager.appendChild(parentInstance.node, child.node);
135
+ },
136
+
137
+ createInstance(
138
+ type: string,
139
+ props: Props,
140
+ rootContainerInstance: Container,
141
+ hostContext: HostContext,
142
+ internalInstanceHandle: Object,
143
+ ): Instance {
144
+ const tag = nextReactTag;
145
+ nextReactTag += 2;
146
+
147
+ const viewConfig = ReactNativeViewConfigRegistry.get(type);
148
+
149
+ if (__DEV__) {
150
+ for (const key in viewConfig.validAttributes) {
151
+ if (props.hasOwnProperty(key)) {
152
+ deepFreezeAndThrowOnMutationInDev(props[key]);
153
+ }
154
+ }
155
+ }
156
+
157
+ invariant(
158
+ type !== 'RCTView' || !hostContext.isInAParentText,
159
+ 'Nesting of <View> within <Text> is not currently supported.',
160
+ );
161
+
162
+ const updatePayload = ReactNativeAttributePayload.create(
163
+ props,
164
+ viewConfig.validAttributes,
165
+ );
166
+
167
+ const node = FabricUIManager.createNode(
168
+ tag, // reactTag
169
+ viewConfig.uiViewClassName, // viewName
170
+ rootContainerInstance, // rootTag
171
+ updatePayload, // props
172
+ internalInstanceHandle, // internalInstanceHandle
173
+ );
174
+
175
+ const component = new ReactFabricHostComponent(tag, viewConfig, props);
176
+
177
+ return {
178
+ node: node,
179
+ canonical: component,
180
+ };
181
+ },
182
+
183
+ createTextInstance(
184
+ text: string,
185
+ rootContainerInstance: Container,
186
+ hostContext: HostContext,
187
+ internalInstanceHandle: Object,
188
+ ): TextInstance {
189
+ invariant(
190
+ hostContext.isInAParentText,
191
+ 'Text strings must be rendered within a <Text> component.',
192
+ );
193
+
194
+ const tag = nextReactTag;
195
+ nextReactTag += 2;
196
+
197
+ const node = FabricUIManager.createNode(
198
+ tag, // reactTag
199
+ 'RCTRawText', // viewName
200
+ rootContainerInstance, // rootTag
201
+ {text: text}, // props
202
+ internalInstanceHandle, // instance handle
203
+ );
204
+
205
+ return {
206
+ node: node,
207
+ };
208
+ },
209
+
210
+ finalizeInitialChildren(
211
+ parentInstance: Instance,
212
+ type: string,
213
+ props: Props,
214
+ rootContainerInstance: Container,
215
+ ): boolean {
216
+ return false;
217
+ },
218
+
219
+ getRootHostContext(rootContainerInstance: Container): HostContext {
220
+ return {isInAParentText: false};
221
+ },
222
+
223
+ getChildHostContext(
224
+ parentHostContext: HostContext,
225
+ type: string,
226
+ ): HostContext {
227
+ const prevIsInAParentText = parentHostContext.isInAParentText;
228
+ const isInAParentText =
229
+ type === 'AndroidTextInput' || // Android
230
+ type === 'RCTMultilineTextInputView' || // iOS
231
+ type === 'RCTSinglelineTextInputView' || // iOS
232
+ type === 'RCTText' ||
233
+ type === 'RCTVirtualText';
234
+
235
+ if (prevIsInAParentText !== isInAParentText) {
236
+ return {isInAParentText};
237
+ } else {
238
+ return parentHostContext;
239
+ }
240
+ },
241
+
242
+ getPublicInstance(instance: Instance): * {
243
+ return instance.canonical;
244
+ },
245
+
246
+ now: ReactNativeFrameScheduling.now,
247
+
248
+ // The Fabric renderer is secondary to the existing React Native renderer.
249
+ isPrimaryRenderer: false,
250
+
251
+ prepareForCommit(): void {
252
+ // Noop
253
+ },
254
+
255
+ prepareUpdate(
256
+ instance: Instance,
257
+ type: string,
258
+ oldProps: Props,
259
+ newProps: Props,
260
+ rootContainerInstance: Container,
261
+ hostContext: HostContext,
262
+ ): null | Object {
263
+ const viewConfig = instance.canonical.viewConfig;
264
+ const updatePayload = ReactNativeAttributePayload.diff(
265
+ oldProps,
266
+ newProps,
267
+ viewConfig.validAttributes,
268
+ );
269
+ // TODO: If the event handlers have changed, we need to update the current props
270
+ // in the commit phase but there is no host config hook to do it yet.
271
+ return updatePayload;
272
+ },
273
+
274
+ resetAfterCommit(): void {
275
+ // Noop
276
+ },
277
+
278
+ scheduleDeferredCallback: ReactNativeFrameScheduling.scheduleDeferredCallback,
279
+ cancelDeferredCallback: ReactNativeFrameScheduling.cancelDeferredCallback,
280
+
281
+ shouldDeprioritizeSubtree(type: string, props: Props): boolean {
282
+ return false;
283
+ },
284
+
285
+ shouldSetTextContent(type: string, props: Props): boolean {
286
+ // TODO (bvaughn) Revisit this decision.
287
+ // Always returning false simplifies the createInstance() implementation,
288
+ // But creates an additional child Fiber for raw text children.
289
+ // No additional native views are created though.
290
+ // It's not clear to me which is better so I'm deferring for now.
291
+ // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
292
+ return false;
293
+ },
294
+
295
+ persistence: {
296
+ cloneInstance(
297
+ instance: Instance,
298
+ updatePayload: null | Object,
299
+ type: string,
300
+ oldProps: Props,
301
+ newProps: Props,
302
+ internalInstanceHandle: Object,
303
+ keepChildren: boolean,
304
+ recyclableInstance: null | Instance,
305
+ ): Instance {
306
+ const node = instance.node;
307
+ let clone;
308
+ if (keepChildren) {
309
+ if (updatePayload !== null) {
310
+ clone = FabricUIManager.cloneNodeWithNewProps(node, updatePayload);
311
+ } else {
312
+ clone = FabricUIManager.cloneNode(node);
313
+ }
314
+ } else {
315
+ if (updatePayload !== null) {
316
+ clone = FabricUIManager.cloneNodeWithNewChildrenAndProps(
317
+ node,
318
+ updatePayload,
319
+ );
320
+ } else {
321
+ clone = FabricUIManager.cloneNodeWithNewChildren(node);
322
+ }
323
+ }
324
+ return {
325
+ node: clone,
326
+ canonical: instance.canonical,
327
+ };
328
+ },
329
+
330
+ createContainerChildSet(container: Container): ChildSet {
331
+ return FabricUIManager.createChildSet(container);
332
+ },
333
+
334
+ appendChildToContainerChildSet(
335
+ childSet: ChildSet,
336
+ child: Instance | TextInstance,
337
+ ): void {
338
+ FabricUIManager.appendChildToSet(childSet, child.node);
339
+ },
340
+
341
+ finalizeContainerChildren(
342
+ container: Container,
343
+ newChildren: ChildSet,
344
+ ): void {
345
+ FabricUIManager.completeRoot(container, newChildren);
346
+ },
347
+
348
+ replaceContainerChildren(
349
+ container: Container,
350
+ newChildren: ChildSet,
351
+ ): void {},
352
+ },
353
+};
354
+
355
+export default ReacFabricHostConfig;
packages/react-native-renderer/src/ReactFabricRenderer.js
+2
-343
@@ -7,350 +7,9 @@
7
* @flow
8
*/
9
10
-import type {
11
- MeasureInWindowOnSuccessCallback,
12
- MeasureLayoutOnSuccessCallback,
13
- MeasureOnSuccessCallback,
14
- NativeMethodsMixinType,
15
- ReactNativeBaseComponentViewConfig,
16
-} from './ReactNativeTypes';
17
-
18
-import {mountSafeCallback, warnForStyleProps} from './NativeMethodsMixinUtils';
19
-import * as ReactNativeAttributePayload from './ReactNativeAttributePayload';
20
-import * as ReactNativeFrameScheduling from './ReactNativeFrameScheduling';
21
-import * as ReactNativeViewConfigRegistry from 'ReactNativeViewConfigRegistry';
10
import ReactFiberReconciler from 'react-reconciler';
11
+import ReactFabricHostConfig from './ReactFabricHostConfig';
12
24
-import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
25
-import invariant from 'fbjs/lib/invariant';
26
-
27
-// Modules provided by RN:
28
-import TextInputState from 'TextInputState';
29
-import FabricUIManager from 'FabricUIManager';
30
-import UIManager from 'UIManager';
31
-
32
-// Counter for uniquely identifying views.
33
-// % 10 === 1 means it is a rootTag.
34
-// % 2 === 0 means it is a Fabric tag.
35
-// This means that they never overlap.
36
-let nextReactTag = 2;
37
-
38
-type HostContext = $ReadOnly<{|
39
- isInAParentText: boolean,
40
-|}>;
41
-
42
-/**
43
- * This is used for refs on host components.
44
- */
45
-class ReactFabricHostComponent {
46
- _nativeTag: number;
47
- viewConfig: ReactNativeBaseComponentViewConfig;
48
- currentProps: Props;
49
-
50
- constructor(
51
- tag: number,
52
- viewConfig: ReactNativeBaseComponentViewConfig,
53
- props: Props,
54
- ) {
55
- this._nativeTag = tag;
56
- this.viewConfig = viewConfig;
57
- this.currentProps = props;
58
- }
59
-
60
- blur() {
61
- TextInputState.blurTextInput(this._nativeTag);
62
- }
63
-
64
- focus() {
65
- TextInputState.focusTextInput(this._nativeTag);
66
- }
67
-
68
- measure(callback: MeasureOnSuccessCallback) {
69
- UIManager.measure(this._nativeTag, mountSafeCallback(this, callback));
70
- }
71
-
72
- measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
73
- UIManager.measureInWindow(
74
- this._nativeTag,
75
- mountSafeCallback(this, callback),
76
- );
77
- }
78
-
79
- measureLayout(
80
- relativeToNativeNode: number,
81
- onSuccess: MeasureLayoutOnSuccessCallback,
82
- onFail: () => void /* currently unused */,
83
- ) {
84
- UIManager.measureLayout(
85
- this._nativeTag,
86
- relativeToNativeNode,
87
- mountSafeCallback(this, onFail),
88
- mountSafeCallback(this, onSuccess),
89
- );
90
- }
91
-
92
- setNativeProps(nativeProps: Object) {
93
- if (__DEV__) {
94
- warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
95
- }
96
-
97
- const updatePayload = ReactNativeAttributePayload.create(
98
- nativeProps,
99
- this.viewConfig.validAttributes,
100
- );
101
-
102
- // Avoid the overhead of bridge calls if there's no update.
103
- // This is an expensive no-op for Android, and causes an unnecessary
104
- // view invalidation for certain components (eg RCTTextInput) on iOS.
105
- if (updatePayload != null) {
106
- UIManager.updateView(
107
- this._nativeTag,
108
- this.viewConfig.uiViewClassName,
109
- updatePayload,
110
- );
111
- }
112
- }
113
-}
114
-
115
-// eslint-disable-next-line no-unused-expressions
116
-(ReactFabricHostComponent.prototype: NativeMethodsMixinType);
117
-
118
-type Node = Object;
119
-type ChildSet = Object;
120
-type Container = number;
121
-type Instance = {
122
- node: Node,
123
- canonical: ReactFabricHostComponent,
124
-};
125
-type Props = Object;
126
-type TextInstance = {
127
- node: Node,
128
-};
129
-
130
-const ReactFabricRenderer = ReactFiberReconciler({
131
- appendInitialChild(
132
- parentInstance: Instance,
133
- child: Instance | TextInstance,
134
- ): void {
135
- FabricUIManager.appendChild(parentInstance.node, child.node);
136
- },
137
-
138
- createInstance(
139
- type: string,
140
- props: Props,
141
- rootContainerInstance: Container,
142
- hostContext: HostContext,
143
- internalInstanceHandle: Object,
144
- ): Instance {
145
- const tag = nextReactTag;
146
- nextReactTag += 2;
147
-
148
- const viewConfig = ReactNativeViewConfigRegistry.get(type);
149
-
150
- if (__DEV__) {
151
- for (const key in viewConfig.validAttributes) {
152
- if (props.hasOwnProperty(key)) {
153
- deepFreezeAndThrowOnMutationInDev(props[key]);
154
- }
155
- }
156
- }
157
-
158
- invariant(
159
- type !== 'RCTView' || !hostContext.isInAParentText,
160
- 'Nesting of <View> within <Text> is not currently supported.',
161
- );
162
-
163
- const updatePayload = ReactNativeAttributePayload.create(
164
- props,
165
- viewConfig.validAttributes,
166
- );
167
-
168
- const node = FabricUIManager.createNode(
169
- tag, // reactTag
170
- viewConfig.uiViewClassName, // viewName
171
- rootContainerInstance, // rootTag
172
- updatePayload, // props
173
- internalInstanceHandle, // internalInstanceHandle
174
- );
175
-
176
- const component = new ReactFabricHostComponent(tag, viewConfig, props);
177
-
178
- return {
179
- node: node,
180
- canonical: component,
181
- };
182
- },
183
-
184
- createTextInstance(
185
- text: string,
186
- rootContainerInstance: Container,
187
- hostContext: HostContext,
188
- internalInstanceHandle: Object,
189
- ): TextInstance {
190
- invariant(
191
- hostContext.isInAParentText,
192
- 'Text strings must be rendered within a <Text> component.',
193
- );
194
-
195
- const tag = nextReactTag;
196
- nextReactTag += 2;
197
-
198
- const node = FabricUIManager.createNode(
199
- tag, // reactTag
200
- 'RCTRawText', // viewName
201
- rootContainerInstance, // rootTag
202
- {text: text}, // props
203
- internalInstanceHandle, // instance handle
204
- );
205
-
206
- return {
207
- node: node,
208
- };
209
- },
210
-
211
- finalizeInitialChildren(
212
- parentInstance: Instance,
213
- type: string,
214
- props: Props,
215
- rootContainerInstance: Container,
216
- ): boolean {
217
- return false;
218
- },
219
-
220
- getRootHostContext(rootContainerInstance: Container): HostContext {
221
- return {isInAParentText: false};
222
- },
223
-
224
- getChildHostContext(
225
- parentHostContext: HostContext,
226
- type: string,
227
- ): HostContext {
228
- const prevIsInAParentText = parentHostContext.isInAParentText;
229
- const isInAParentText =
230
- type === 'AndroidTextInput' || // Android
231
- type === 'RCTMultilineTextInputView' || // iOS
232
- type === 'RCTSinglelineTextInputView' || // iOS
233
- type === 'RCTText' ||
234
- type === 'RCTVirtualText';
235
-
236
- if (prevIsInAParentText !== isInAParentText) {
237
- return {isInAParentText};
238
- } else {
239
- return parentHostContext;
240
- }
241
- },
242
-
243
- getPublicInstance(instance) {
244
- return instance.canonical;
245
- },
246
-
247
- now: ReactNativeFrameScheduling.now,
248
-
249
- // The Fabric renderer is secondary to the existing React Native renderer.
250
- isPrimaryRenderer: false,
251
-
252
- prepareForCommit(): void {
253
- // Noop
254
- },
255
-
256
- prepareUpdate(
257
- instance: Instance,
258
- type: string,
259
- oldProps: Props,
260
- newProps: Props,
261
- rootContainerInstance: Container,
262
- hostContext: HostContext,
263
- ): null | Object {
264
- const viewConfig = instance.canonical.viewConfig;
265
- const updatePayload = ReactNativeAttributePayload.diff(
266
- oldProps,
267
- newProps,
268
- viewConfig.validAttributes,
269
- );
270
- // TODO: If the event handlers have changed, we need to update the current props
271
- // in the commit phase but there is no host config hook to do it yet.
272
- return updatePayload;
273
- },
274
-
275
- resetAfterCommit(): void {
276
- // Noop
277
- },
278
-
279
- scheduleDeferredCallback: ReactNativeFrameScheduling.scheduleDeferredCallback,
280
- cancelDeferredCallback: ReactNativeFrameScheduling.cancelDeferredCallback,
281
-
282
- shouldDeprioritizeSubtree(type: string, props: Props): boolean {
283
- return false;
284
- },
285
-
286
- shouldSetTextContent(type: string, props: Props): boolean {
287
- // TODO (bvaughn) Revisit this decision.
288
- // Always returning false simplifies the createInstance() implementation,
289
- // But creates an additional child Fiber for raw text children.
290
- // No additional native views are created though.
291
- // It's not clear to me which is better so I'm deferring for now.
292
- // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
293
- return false;
294
- },
295
-
296
- persistence: {
297
- cloneInstance(
298
- instance: Instance,
299
- updatePayload: null | Object,
300
- type: string,
301
- oldProps: Props,
302
- newProps: Props,
303
- internalInstanceHandle: Object,
304
- keepChildren: boolean,
305
- recyclableInstance: null | Instance,
306
- ): Instance {
307
- const node = instance.node;
308
- let clone;
309
- if (keepChildren) {
310
- if (updatePayload !== null) {
311
- clone = FabricUIManager.cloneNodeWithNewProps(node, updatePayload);
312
- } else {
313
- clone = FabricUIManager.cloneNode(node);
314
- }
315
- } else {
316
- if (updatePayload !== null) {
317
- clone = FabricUIManager.cloneNodeWithNewChildrenAndProps(
318
- node,
319
- updatePayload,
320
- );
321
- } else {
322
- clone = FabricUIManager.cloneNodeWithNewChildren(node);
323
- }
324
- }
325
- return {
326
- node: clone,
327
- canonical: instance.canonical,
328
- };
329
- },
330
-
331
- createContainerChildSet(container: Container): ChildSet {
332
- return FabricUIManager.createChildSet(container);
333
- },
334
-
335
- appendChildToContainerChildSet(
336
- childSet: ChildSet,
337
- child: Instance | TextInstance,
338
- ): void {
339
- FabricUIManager.appendChildToSet(childSet, child.node);
340
- },
341
-
342
- finalizeContainerChildren(
343
- container: Container,
344
- newChildren: ChildSet,
345
- ): void {
346
- FabricUIManager.completeRoot(container, newChildren);
347
- },
348
-
349
- replaceContainerChildren(
350
- container: Container,
351
- newChildren: ChildSet,
352
- ): void {},
353
- },
354
-});
13
+const ReactFabricRenderer = ReactFiberReconciler(ReactFabricHostConfig);
14
15
export default ReactFabricRenderer;
packages/react-native-renderer/src/ReactNativeFiberHostComponent.js
+1
-1
@@ -14,7 +14,7 @@ import type {
14
NativeMethodsMixinType,
15
ReactNativeBaseComponentViewConfig,
16
} from './ReactNativeTypes';
17
-import type {Instance} from './ReactNativeFiberRenderer';
17
+import type {Instance} from './ReactNativeHostConfig';
18
19
// Modules provided by RN:
20
import TextInputState from 'TextInputState';
packages/react-native-renderer/src/ReactNativeHostConfig.js
renamed
+4
-5
@@ -9,7 +9,6 @@
9
10
import type {ReactNativeBaseComponentViewConfig} from './ReactNativeTypes';
11
12
-import ReactFiberReconciler from 'react-reconciler';
12
import emptyObject from 'fbjs/lib/emptyObject';
13
import invariant from 'fbjs/lib/invariant';
14
@@ -64,7 +63,7 @@ function recursivelyUncacheFiberNode(node: Instance | TextInstance) {
63
}
64
}
65
67
-const NativeRenderer = ReactFiberReconciler({
66
+const ReactNativeHostConfig = {
67
appendInitialChild(
68
parentInstance: Instance,
69
child: Instance | TextInstance,
@@ -193,7 +192,7 @@ const NativeRenderer = ReactFiberReconciler({
192
}
193
},
194
196
- getPublicInstance(instance) {
195
+ getPublicInstance(instance: Instance): * {
196
return instance;
197
},
198
@@ -427,6 +426,6 @@ const NativeRenderer = ReactFiberReconciler({
426
// Noop
427
},
428
},
430
-});
429
+};
430
432
-export default NativeRenderer;
431
+export default ReactNativeHostConfig;
packages/react-native-renderer/src/ReactNativeRenderer.js
+4
-1
@@ -12,6 +12,7 @@ import type {ReactNodeList} from 'shared/ReactTypes';
12
13
import './ReactNativeInjection';
14
15
+import ReactFiberReconciler from 'react-reconciler';
16
import * as ReactPortal from 'shared/ReactPortal';
17
import * as ReactGenericBatching from 'events/ReactGenericBatching';
18
import ReactVersion from 'shared/ReactVersion';
@@ -20,16 +21,18 @@ import UIManager from 'UIManager';
21
22
import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
23
24
+import ReactNativeHostConfig from './ReactNativeHostConfig';
25
import NativeMethodsMixin from './NativeMethodsMixin';
26
import ReactNativeComponent from './ReactNativeComponent';
27
import * as ReactNativeComponentTree from './ReactNativeComponentTree';
26
-import ReactNativeFiberRenderer from './ReactNativeFiberRenderer';
28
import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
29
30
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
31
import getComponentName from 'shared/getComponentName';
32
import warning from 'fbjs/lib/warning';
33
34
+const ReactNativeFiberRenderer = ReactFiberReconciler(ReactNativeHostConfig);
35
+
36
const findHostInstance = ReactNativeFiberRenderer.findHostInstance;
37
38
function findNodeHandle(componentOrHandle: any): ?number {
packages/react-test-renderer/src/ReactTestHostConfig.js
new
+218
@@ -0,0 +1,218 @@
1
+/**
2
+ * Copyright (c) 2013-present, Facebook, Inc.
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
8
+ */
9
+
10
+import emptyObject from 'fbjs/lib/emptyObject';
11
+
12
+import * as TestRendererScheduling from './ReactTestRendererScheduling';
13
+
14
+export type Instance = {|
15
+ type: string,
16
+ props: Object,
17
+ children: Array<Instance | TextInstance>,
18
+ rootContainerInstance: Container,
19
+ tag: 'INSTANCE',
20
+|};
21
+
22
+export type TextInstance = {|
23
+ text: string,
24
+ tag: 'TEXT',
25
+|};
26
+
27
+type Container = {|
28
+ children: Array<Instance | TextInstance>,
29
+ createNodeMock: Function,
30
+ tag: 'CONTAINER',
31
+|};
32
+
33
+type Props = Object;
34
+
35
+const UPDATE_SIGNAL = {};
36
+
37
+function getPublicInstance(inst: Instance | TextInstance): * {
38
+ switch (inst.tag) {
39
+ case 'INSTANCE':
40
+ const createNodeMock = inst.rootContainerInstance.createNodeMock;
41
+ return createNodeMock({
42
+ type: inst.type,
43
+ props: inst.props,
44
+ });
45
+ default:
46
+ return inst;
47
+ }
48
+}
49
+
50
+function appendChild(
51
+ parentInstance: Instance | Container,
52
+ child: Instance | TextInstance,
53
+): void {
54
+ const index = parentInstance.children.indexOf(child);
55
+ if (index !== -1) {
56
+ parentInstance.children.splice(index, 1);
57
+ }
58
+ parentInstance.children.push(child);
59
+}
60
+
61
+function insertBefore(
62
+ parentInstance: Instance | Container,
63
+ child: Instance | TextInstance,
64
+ beforeChild: Instance | TextInstance,
65
+): void {
66
+ const index = parentInstance.children.indexOf(child);
67
+ if (index !== -1) {
68
+ parentInstance.children.splice(index, 1);
69
+ }
70
+ const beforeIndex = parentInstance.children.indexOf(beforeChild);
71
+ parentInstance.children.splice(beforeIndex, 0, child);
72
+}
73
+
74
+function removeChild(
75
+ parentInstance: Instance | Container,
76
+ child: Instance | TextInstance,
77
+): void {
78
+ const index = parentInstance.children.indexOf(child);
79
+ parentInstance.children.splice(index, 1);
80
+}
81
+
82
+const ReactTestHostConfig = {
83
+ getRootHostContext() {
84
+ return emptyObject;
85
+ },
86
+
87
+ getChildHostContext() {
88
+ return emptyObject;
89
+ },
90
+
91
+ prepareForCommit(): void {
92
+ // noop
93
+ },
94
+
95
+ resetAfterCommit(): void {
96
+ // noop
97
+ },
98
+
99
+ createInstance(
100
+ type: string,
101
+ props: Props,
102
+ rootContainerInstance: Container,
103
+ hostContext: Object,
104
+ internalInstanceHandle: Object,
105
+ ): Instance {
106
+ return {
107
+ type,
108
+ props,
109
+ children: [],
110
+ rootContainerInstance,
111
+ tag: 'INSTANCE',
112
+ };
113
+ },
114
+
115
+ appendInitialChild(
116
+ parentInstance: Instance,
117
+ child: Instance | TextInstance,
118
+ ): void {
119
+ const index = parentInstance.children.indexOf(child);
120
+ if (index !== -1) {
121
+ parentInstance.children.splice(index, 1);
122
+ }
123
+ parentInstance.children.push(child);
124
+ },
125
+
126
+ finalizeInitialChildren(
127
+ testElement: Instance,
128
+ type: string,
129
+ props: Props,
130
+ rootContainerInstance: Container,
131
+ ): boolean {
132
+ return false;
133
+ },
134
+
135
+ prepareUpdate(
136
+ testElement: Instance,
137
+ type: string,
138
+ oldProps: Props,
139
+ newProps: Props,
140
+ rootContainerInstance: Container,
141
+ hostContext: Object,
142
+ ): null | {} {
143
+ return UPDATE_SIGNAL;
144
+ },
145
+
146
+ shouldSetTextContent(type: string, props: Props): boolean {
147
+ return false;
148
+ },
149
+
150
+ shouldDeprioritizeSubtree(type: string, props: Props): boolean {
151
+ return false;
152
+ },
153
+
154
+ createTextInstance(
155
+ text: string,
156
+ rootContainerInstance: Container,
157
+ hostContext: Object,
158
+ internalInstanceHandle: Object,
159
+ ): TextInstance {
160
+ return {
161
+ text,
162
+ tag: 'TEXT',
163
+ };
164
+ },
165
+
166
+ getPublicInstance,
167
+
168
+ scheduleDeferredCallback: TestRendererScheduling.scheduleDeferredCallback,
169
+ cancelDeferredCallback: TestRendererScheduling.cancelDeferredCallback,
170
+ // This approach enables `now` to be mocked by tests,
171
+ // Even after the reconciler has initialized and read host config values.
172
+ now: () => TestRendererScheduling.nowImplementation(),
173
+
174
+ isPrimaryRenderer: true,
175
+
176
+ mutation: {
177
+ commitUpdate(
178
+ instance: Instance,
179
+ updatePayload: {},
180
+ type: string,
181
+ oldProps: Props,
182
+ newProps: Props,
183
+ internalInstanceHandle: Object,
184
+ ): void {
185
+ instance.type = type;
186
+ instance.props = newProps;
187
+ },
188
+
189
+ commitMount(
190
+ instance: Instance,
191
+ type: string,
192
+ newProps: Props,
193
+ internalInstanceHandle: Object,
194
+ ): void {
195
+ // noop
196
+ },
197
+
198
+ commitTextUpdate(
199
+ textInstance: TextInstance,
200
+ oldText: string,
201
+ newText: string,
202
+ ): void {
203
+ textInstance.text = newText;
204
+ },
205
+ resetTextContent(testElement: Instance): void {
206
+ // noop
207
+ },
208
+
209
+ appendChild: appendChild,
210
+ appendChildToContainer: appendChild,
211
+ insertBefore: insertBefore,
212
+ insertInContainerBefore: insertBefore,
213
+ removeChild: removeChild,
214
+ removeChildFromContainer: removeChild,
215
+ },
216
+};
217
+
218
+export default ReactTestHostConfig;
packages/react-test-renderer/src/ReactTestRenderer.js
+13
-286
@@ -9,12 +9,11 @@
9
10
import type {Fiber} from 'react-reconciler/src/ReactFiber';
11
import type {FiberRoot} from 'react-reconciler/src/ReactFiberRoot';
12
-import type {Deadline} from 'react-reconciler/src/ReactFiberReconciler';
12
+import type {Instance, TextInstance} from './ReactTestHostConfig';
13
14
import ReactFiberReconciler from 'react-reconciler';
15
import {batchedUpdates} from 'events/ReactGenericBatching';
16
import {findCurrentFiberUsingSlowPath} from 'react-reconciler/reflection';
17
-import emptyObject from 'fbjs/lib/emptyObject';
17
import {
18
Fragment,
19
FunctionalComponent,
@@ -31,6 +30,9 @@ import {
30
} from 'shared/ReactTypeOfWork';
31
import invariant from 'fbjs/lib/invariant';
32
33
+import ReactTestHostConfig from './ReactTestHostConfig';
34
+import * as TestRendererScheduling from './ReactTestRendererScheduling';
35
+
36
type TestRendererOptions = {
37
createNodeMock: (element: React$Element<any>) => any,
38
unstable_isAsync: boolean,
@@ -44,26 +46,6 @@ type ReactTestRendererJSON = {|
46
|};
47
type ReactTestRendererNode = ReactTestRendererJSON | string;
48
47
-type Container = {|
48
- children: Array<Instance | TextInstance>,
49
- createNodeMock: Function,
50
- tag: 'CONTAINER',
51
-|};
52
-
53
-type Props = Object;
54
-type Instance = {|
55
- type: string,
56
- props: Object,
57
- children: Array<Instance | TextInstance>,
58
- rootContainerInstance: Container,
59
- tag: 'INSTANCE',
60
-|};
61
-
62
-type TextInstance = {|
63
- text: string,
64
- tag: 'TEXT',
65
-|};
66
-
49
type FindOptions = $Shape<{
50
// performs a "greedy" search: if a matching node is found, will continue
51
// to search within the matching node's children. (default: true)
@@ -72,203 +54,7 @@ type FindOptions = $Shape<{
54
55
export type Predicate = (node: ReactTestInstance) => ?boolean;
56
75
-const UPDATE_SIGNAL = {};
76
-
77
-function getPublicInstance(inst: Instance | TextInstance): * {
78
- switch (inst.tag) {
79
- case 'INSTANCE':
80
- const createNodeMock = inst.rootContainerInstance.createNodeMock;
81
- return createNodeMock({
82
- type: inst.type,
83
- props: inst.props,
84
- });
85
- default:
86
- return inst;
87
- }
88
-}
89
-
90
-function appendChild(
91
- parentInstance: Instance | Container,
92
- child: Instance | TextInstance,
93
-): void {
94
- const index = parentInstance.children.indexOf(child);
95
- if (index !== -1) {
96
- parentInstance.children.splice(index, 1);
97
- }
98
- parentInstance.children.push(child);
99
-}
100
-
101
-function insertBefore(
102
- parentInstance: Instance | Container,
103
- child: Instance | TextInstance,
104
- beforeChild: Instance | TextInstance,
105
-): void {
106
- const index = parentInstance.children.indexOf(child);
107
- if (index !== -1) {
108
- parentInstance.children.splice(index, 1);
109
- }
110
- const beforeIndex = parentInstance.children.indexOf(beforeChild);
111
- parentInstance.children.splice(beforeIndex, 0, child);
112
-}
113
-
114
-function removeChild(
115
- parentInstance: Instance | Container,
116
- child: Instance | TextInstance,
117
-): void {
118
- const index = parentInstance.children.indexOf(child);
119
- parentInstance.children.splice(index, 1);
120
-}
121
-
122
-// Current virtual time
123
-let nowImplementation = () => 0;
124
-let scheduledCallback: ((deadline: Deadline) => mixed) | null = null;
125
-let yieldedValues: Array<mixed> | null = null;
126
-
127
-const TestRenderer = ReactFiberReconciler({
128
- getRootHostContext() {
129
- return emptyObject;
130
- },
131
-
132
- getChildHostContext() {
133
- return emptyObject;
134
- },
135
-
136
- prepareForCommit(): void {
137
- // noop
138
- },
139
-
140
- resetAfterCommit(): void {
141
- // noop
142
- },
143
-
144
- createInstance(
145
- type: string,
146
- props: Props,
147
- rootContainerInstance: Container,
148
- hostContext: Object,
149
- internalInstanceHandle: Object,
150
- ): Instance {
151
- return {
152
- type,
153
- props,
154
- children: [],
155
- rootContainerInstance,
156
- tag: 'INSTANCE',
157
- };
158
- },
159
-
160
- appendInitialChild(
161
- parentInstance: Instance,
162
- child: Instance | TextInstance,
163
- ): void {
164
- const index = parentInstance.children.indexOf(child);
165
- if (index !== -1) {
166
- parentInstance.children.splice(index, 1);
167
- }
168
- parentInstance.children.push(child);
169
- },
170
-
171
- finalizeInitialChildren(
172
- testElement: Instance,
173
- type: string,
174
- props: Props,
175
- rootContainerInstance: Container,
176
- ): boolean {
177
- return false;
178
- },
179
-
180
- prepareUpdate(
181
- testElement: Instance,
182
- type: string,
183
- oldProps: Props,
184
- newProps: Props,
185
- rootContainerInstance: Container,
186
- hostContext: Object,
187
- ): null | {} {
188
- return UPDATE_SIGNAL;
189
- },
190
-
191
- shouldSetTextContent(type: string, props: Props): boolean {
192
- return false;
193
- },
194
-
195
- shouldDeprioritizeSubtree(type: string, props: Props): boolean {
196
- return false;
197
- },
198
-
199
- createTextInstance(
200
- text: string,
201
- rootContainerInstance: Container,
202
- hostContext: Object,
203
- internalInstanceHandle: Object,
204
- ): TextInstance {
205
- return {
206
- text,
207
- tag: 'TEXT',
208
- };
209
- },
210
-
211
- scheduleDeferredCallback(
212
- callback: (deadline: Deadline) => mixed,
213
- options?: {timeout: number},
214
- ): number {
215
- scheduledCallback = callback;
216
- return 0;
217
- },
218
-
219
- cancelDeferredCallback(timeoutID: number): void {
220
- scheduledCallback = null;
221
- },
222
-
223
- getPublicInstance,
224
-
225
- // This approach enables `now` to be mocked by tests,
226
- // Even after the reconciler has initialized and read host config values.
227
- now: () => nowImplementation(),
228
-
229
- isPrimaryRenderer: true,
230
-
231
- mutation: {
232
- commitUpdate(
233
- instance: Instance,
234
- updatePayload: {},
235
- type: string,
236
- oldProps: Props,
237
- newProps: Props,
238
- internalInstanceHandle: Object,
239
- ): void {
240
- instance.type = type;
241
- instance.props = newProps;
242
- },
243
-
244
- commitMount(
245
- instance: Instance,
246
- type: string,
247
- newProps: Props,
248
- internalInstanceHandle: Object,
249
- ): void {
250
- // noop
251
- },
252
-
253
- commitTextUpdate(
254
- textInstance: TextInstance,
255
- oldText: string,
256
- newText: string,
257
- ): void {
258
- textInstance.text = newText;
259
- },
260
- resetTextContent(testElement: Instance): void {
261
- // noop
262
- },
263
-
264
- appendChild: appendChild,
265
- appendChildToContainer: appendChild,
266
- insertBefore: insertBefore,
267
- insertInContainerBefore: insertBefore,
268
- removeChild: removeChild,
269
- removeChildFromContainer: removeChild,
270
- },
271
-});
57
+const TestRenderer = ReactFiberReconciler(ReactTestHostConfig);
58
59
const defaultTestOptions = {
60
createNodeMock: function() {
@@ -444,7 +230,7 @@ class ReactTestInstance {
230
231
get instance() {
232
if (this._fiber.tag === HostComponent) {
447
- return getPublicInstance(this._fiber.stateNode);
233
+ return ReactTestHostConfig.getPublicInstance(this._fiber.stateNode);
234
} else {
235
return this._fiber.stateNode;
236
}
@@ -676,77 +462,20 @@ const ReactTestRendererFiber = {
462
container = null;
463
root = null;
464
},
679
- unstable_flushAll(): Array<mixed> {
680
- yieldedValues = null;
681
- while (scheduledCallback !== null) {
682
- const cb = scheduledCallback;
683
- scheduledCallback = null;
684
- cb({
685
- timeRemaining() {
686
- // Keep rendering until there's no more work
687
- return 999;
688
- },
689
- // React's scheduler has its own way of keeping track of expired
690
- // work and doesn't read this, so don't bother setting it to the
691
- // correct value.
692
- didTimeout: false,
693
- });
694
- }
695
- if (yieldedValues === null) {
696
- // Always return an array.
697
- return [];
698
- }
699
- return yieldedValues;
700
- },
701
- unstable_flushThrough(expectedValues: Array<mixed>): Array<mixed> {
702
- let didStop = false;
703
- yieldedValues = null;
704
- while (scheduledCallback !== null && !didStop) {
705
- const cb = scheduledCallback;
706
- scheduledCallback = null;
707
- cb({
708
- timeRemaining() {
709
- if (
710
- yieldedValues !== null &&
711
- yieldedValues.length >= expectedValues.length
712
- ) {
713
- // We at least as many values as expected. Stop rendering.
714
- didStop = true;
715
- return 0;
716
- }
717
- // Keep rendering.
718
- return 999;
719
- },
720
- // React's scheduler has its own way of keeping track of expired
721
- // work and doesn't read this, so don't bother setting it to the
722
- // correct value.
723
- didTimeout: false,
724
- });
725
- }
726
- if (yieldedValues === null) {
727
- // Always return an array.
728
- return [];
729
- }
730
- return yieldedValues;
731
- },
732
- unstable_yield(value: mixed): void {
733
- if (yieldedValues === null) {
734
- yieldedValues = [value];
735
- } else {
736
- yieldedValues.push(value);
737
- }
738
- },
465
getInstance() {
466
if (root == null || root.current == null) {
467
return null;
468
}
469
return TestRenderer.getPublicRootInstance(root);
470
},
471
+ unstable_flushAll: TestRendererScheduling.flushAll,
472
unstable_flushSync(fn: Function) {
746
- yieldedValues = [];
747
- TestRenderer.flushSync(fn);
748
- return yieldedValues;
473
+ return TestRendererScheduling.withCleanYields(() => {
474
+ TestRenderer.flushSync(fn);
475
+ });
476
},
477
+ unstable_flushThrough: TestRendererScheduling.flushThrough,
478
+ unstable_yield: TestRendererScheduling.yieldValue,
479
};
480
481
Object.defineProperty(
@@ -771,9 +500,7 @@ const ReactTestRendererFiber = {
500
unstable_batchedUpdates: batchedUpdates,
501
/* eslint-enable camelcase */
502
774
- unstable_setNowImplementation(implementation: () => number): void {
775
- nowImplementation = implementation;
776
- },
503
+ unstable_setNowImplementation: TestRendererScheduling.setNowImplementation,
504
};
505
506
export default ReactTestRendererFiber;
packages/react-test-renderer/src/ReactTestRendererScheduling.js
new
+100
@@ -0,0 +1,100 @@
1
+/**
2
+ * Copyright (c) 2013-present, Facebook, Inc.
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
8
+ */
9
+
10
+import type {Deadline} from 'react-reconciler/src/ReactFiberReconciler';
11
+
12
+// Current virtual time
13
+export let nowImplementation = () => 0;
14
+export let scheduledCallback: ((deadline: Deadline) => mixed) | null = null;
15
+export let yieldedValues: Array<mixed> | null = null;
16
+
17
+export function scheduleDeferredCallback(
18
+ callback: (deadline: Deadline) => mixed,
19
+ options?: {timeout: number},
20
+): number {
21
+ scheduledCallback = callback;
22
+ return 0;
23
+}
24
+
25
+export function cancelDeferredCallback(timeoutID: number): void {
26
+ scheduledCallback = null;
27
+}
28
+
29
+export function setNowImplementation(implementation: () => number): void {
30
+ nowImplementation = implementation;
31
+}
32
+
33
+export function flushAll(): Array<mixed> {
34
+ yieldedValues = null;
35
+ while (scheduledCallback !== null) {
36
+ const cb = scheduledCallback;
37
+ scheduledCallback = null;
38
+ cb({
39
+ timeRemaining() {
40
+ // Keep rendering until there's no more work
41
+ return 999;
42
+ },
43
+ // React's scheduler has its own way of keeping track of expired
44
+ // work and doesn't read this, so don't bother setting it to the
45
+ // correct value.
46
+ didTimeout: false,
47
+ });
48
+ }
49
+ if (yieldedValues === null) {
50
+ // Always return an array.
51
+ return [];
52
+ }
53
+ return yieldedValues;
54
+}
55
+
56
+export function flushThrough(expectedValues: Array<mixed>): Array<mixed> {
57
+ let didStop = false;
58
+ yieldedValues = null;
59
+ while (scheduledCallback !== null && !didStop) {
60
+ const cb = scheduledCallback;
61
+ scheduledCallback = null;
62
+ cb({
63
+ timeRemaining() {
64
+ if (
65
+ yieldedValues !== null &&
66
+ yieldedValues.length >= expectedValues.length
67
+ ) {
68
+ // We at least as many values as expected. Stop rendering.
69
+ didStop = true;
70
+ return 0;
71
+ }
72
+ // Keep rendering.
73
+ return 999;
74
+ },
75
+ // React's scheduler has its own way of keeping track of expired
76
+ // work and doesn't read this, so don't bother setting it to the
77
+ // correct value.
78
+ didTimeout: false,
79
+ });
80
+ }
81
+ if (yieldedValues === null) {
82
+ // Always return an array.
83
+ return [];
84
+ }
85
+ return yieldedValues;
86
+}
87
+
88
+export function yieldValue(value: mixed): void {
89
+ if (yieldedValues === null) {
90
+ yieldedValues = [value];
91
+ } else {
92
+ yieldedValues.push(value);
93
+ }
94
+}
95
+
96
+export function withCleanYields(fn: Function) {
97
+ yieldedValues = [];
98
+ fn();
99
+ return yieldedValues;
100
+}