Fork ReactDOMComponent and the wrappers to Fiber
From now on any change to these have to be replicated to these files. I will next start changing the implementation to work with Fiber.
Sebastian Markbage committed
Nov 15, 2016 at 15:54 UTC
9c3e9fbb057e3568f7f80bbefb79b9751e2078b2
5 files changed
+2116
src/renderers/dom/fiber/ReactDOMFiberComponent.js
new
+1303
@@ -0,0 +1,1303 @@
1
+/**
2
+ * Copyright 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactDOMFiberComponent
10
+ */
11
+
12
+/* global hasOwnProperty:true */
13
+
14
+'use strict';
15
+
16
+var AutoFocusUtils = require('AutoFocusUtils');
17
+var CSSPropertyOperations = require('CSSPropertyOperations');
18
+var DOMLazyTree = require('DOMLazyTree');
19
+var DOMNamespaces = require('DOMNamespaces');
20
+var DOMProperty = require('DOMProperty');
21
+var DOMPropertyOperations = require('DOMPropertyOperations');
22
+var EventPluginHub = require('EventPluginHub');
23
+var EventPluginRegistry = require('EventPluginRegistry');
24
+var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
25
+var ReactDOMComponentFlags = require('ReactDOMComponentFlags');
26
+var ReactDOMComponentTree = require('ReactDOMComponentTree');
27
+var ReactDOMInput = require('ReactDOMInput');
28
+var ReactDOMOption = require('ReactDOMOption');
29
+var ReactDOMSelect = require('ReactDOMSelect');
30
+var ReactDOMTextarea = require('ReactDOMTextarea');
31
+var ReactInstrumentation = require('ReactInstrumentation');
32
+var ReactMultiChild = require('ReactMultiChild');
33
+var ReactServerRenderingTransaction = require('ReactServerRenderingTransaction');
34
+
35
+var emptyFunction = require('emptyFunction');
36
+var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
37
+var invariant = require('invariant');
38
+var isEventSupported = require('isEventSupported');
39
+var shallowEqual = require('shallowEqual');
40
+var inputValueTracking = require('inputValueTracking');
41
+var validateDOMNesting = require('validateDOMNesting');
42
+var warning = require('warning');
43
+var didWarnShadyDOM = false;
44
+
45
+var Flags = ReactDOMComponentFlags;
46
+var deleteListener = EventPluginHub.deleteListener;
47
+var getNode = ReactDOMComponentTree.getNodeFromInstance;
48
+var listenTo = ReactBrowserEventEmitter.listenTo;
49
+var registrationNameModules = EventPluginRegistry.registrationNameModules;
50
+
51
+// For quickly matching children type, to test if can be treated as content.
52
+var CONTENT_TYPES = {'string': true, 'number': true};
53
+
54
+var STYLE = 'style';
55
+var HTML = '__html';
56
+var RESERVED_PROPS = {
57
+ children: null,
58
+ dangerouslySetInnerHTML: null,
59
+ suppressContentEditableWarning: null,
60
+};
61
+
62
+// Node type for document fragments (Node.DOCUMENT_FRAGMENT_NODE).
63
+var DOC_FRAGMENT_TYPE = 11;
64
+
65
+
66
+function getDeclarationErrorAddendum(internalInstance) {
67
+ if (internalInstance) {
68
+ var owner = internalInstance._currentElement._owner || null;
69
+ if (owner) {
70
+ var name = owner.getName();
71
+ if (name) {
72
+ return ' This DOM node was rendered by `' + name + '`.';
73
+ }
74
+ }
75
+ }
76
+ return '';
77
+}
78
+
79
+function friendlyStringify(obj) {
80
+ if (typeof obj === 'object') {
81
+ if (Array.isArray(obj)) {
82
+ return '[' + obj.map(friendlyStringify).join(', ') + ']';
83
+ } else {
84
+ var pairs = [];
85
+ for (var key in obj) {
86
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
87
+ var keyEscaped = /^[a-z$_][\w$_]*$/i.test(key) ?
88
+ key :
89
+ JSON.stringify(key);
90
+ pairs.push(keyEscaped + ': ' + friendlyStringify(obj[key]));
91
+ }
92
+ }
93
+ return '{' + pairs.join(', ') + '}';
94
+ }
95
+ } else if (typeof obj === 'string') {
96
+ return JSON.stringify(obj);
97
+ } else if (typeof obj === 'function') {
98
+ return '[function object]';
99
+ }
100
+ // Differs from JSON.stringify in that undefined because undefined and that
101
+ // inf and nan don't become null
102
+ return String(obj);
103
+}
104
+
105
+var styleMutationWarning = {};
106
+
107
+function checkAndWarnForMutatedStyle(style1, style2, component) {
108
+ if (style1 == null || style2 == null) {
109
+ return;
110
+ }
111
+ if (shallowEqual(style1, style2)) {
112
+ return;
113
+ }
114
+
115
+ var componentName = component._tag;
116
+ var owner = component._currentElement._owner;
117
+ var ownerName;
118
+ if (owner) {
119
+ ownerName = owner.getName();
120
+ }
121
+
122
+ var hash = ownerName + '|' + componentName;
123
+
124
+ if (styleMutationWarning.hasOwnProperty(hash)) {
125
+ return;
126
+ }
127
+
128
+ styleMutationWarning[hash] = true;
129
+
130
+ warning(
131
+ false,
132
+ '`%s` was passed a style object that has previously been mutated. ' +
133
+ 'Mutating `style` is deprecated. Consider cloning it beforehand. Check ' +
134
+ 'the `render` %s. Previous style: %s. Mutated style: %s.',
135
+ componentName,
136
+ owner ? 'of `' + ownerName + '`' : 'using <' + componentName + '>',
137
+ friendlyStringify(style1),
138
+ friendlyStringify(style2)
139
+ );
140
+}
141
+
142
+/**
143
+ * @param {object} component
144
+ * @param {?object} props
145
+ */
146
+function assertValidProps(component, props) {
147
+ if (!props) {
148
+ return;
149
+ }
150
+ // Note the use of `==` which checks for null or undefined.
151
+ if (voidElementTags[component._tag]) {
152
+ invariant(
153
+ props.children == null && props.dangerouslySetInnerHTML == null,
154
+ '%s is a void element tag and must neither have `children` nor ' +
155
+ 'use `dangerouslySetInnerHTML`.%s',
156
+ component._tag,
157
+ component._currentElement._owner ?
158
+ ' Check the render method of ' +
159
+ component._currentElement._owner.getName() + '.' :
160
+ ''
161
+ );
162
+ }
163
+ if (props.dangerouslySetInnerHTML != null) {
164
+ invariant(
165
+ props.children == null,
166
+ 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.'
167
+ );
168
+ invariant(
169
+ typeof props.dangerouslySetInnerHTML === 'object' &&
170
+ HTML in props.dangerouslySetInnerHTML,
171
+ '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +
172
+ 'Please visit https://fb.me/react-invariant-dangerously-set-inner-html ' +
173
+ 'for more information.'
174
+ );
175
+ }
176
+ if (__DEV__) {
177
+ warning(
178
+ props.innerHTML == null,
179
+ 'Directly setting property `innerHTML` is not permitted. ' +
180
+ 'For more information, lookup documentation on `dangerouslySetInnerHTML`.'
181
+ );
182
+ warning(
183
+ props.suppressContentEditableWarning ||
184
+ !props.contentEditable ||
185
+ props.children == null,
186
+ 'A component is `contentEditable` and contains `children` managed by ' +
187
+ 'React. It is now your responsibility to guarantee that none of ' +
188
+ 'those nodes are unexpectedly modified or duplicated. This is ' +
189
+ 'probably not intentional.'
190
+ );
191
+ warning(
192
+ props.onFocusIn == null &&
193
+ props.onFocusOut == null,
194
+ 'React uses onFocus and onBlur instead of onFocusIn and onFocusOut. ' +
195
+ 'All React events are normalized to bubble, so onFocusIn and onFocusOut ' +
196
+ 'are not needed/supported by React.'
197
+ );
198
+ }
199
+ invariant(
200
+ props.style == null || typeof props.style === 'object',
201
+ 'The `style` prop expects a mapping from style properties to values, ' +
202
+ 'not a string. For example, style={{marginRight: spacing + \'em\'}} when ' +
203
+ 'using JSX.%s',
204
+ getDeclarationErrorAddendum(component)
205
+ );
206
+}
207
+
208
+function enqueuePutListener(inst, registrationName, listener, transaction) {
209
+ if (transaction instanceof ReactServerRenderingTransaction) {
210
+ return;
211
+ }
212
+ if (__DEV__) {
213
+ // IE8 has no API for event capturing and the `onScroll` event doesn't
214
+ // bubble.
215
+ warning(
216
+ registrationName !== 'onScroll' || isEventSupported('scroll', true),
217
+ 'This browser doesn\'t support the `onScroll` event'
218
+ );
219
+ }
220
+ var containerInfo = inst._hostContainerInfo;
221
+ var isDocumentFragment = containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE;
222
+ var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument;
223
+ listenTo(registrationName, doc);
224
+ transaction.getReactMountReady().enqueue(putListener, {
225
+ inst: inst,
226
+ registrationName: registrationName,
227
+ listener: listener,
228
+ });
229
+}
230
+
231
+// TODO: This is coming from future #8192. Dedupe this and enqueuePutListener.
232
+function ensureListeningTo(inst, registrationName, transaction) {
233
+ if (transaction instanceof ReactServerRenderingTransaction) {
234
+ return;
235
+ }
236
+ if (__DEV__) {
237
+ // IE8 has no API for event capturing and the `onScroll` event doesn't
238
+ // bubble.
239
+ warning(
240
+ registrationName !== 'onScroll' || isEventSupported('scroll', true),
241
+ 'This browser doesn\'t support the `onScroll` event'
242
+ );
243
+ }
244
+ var containerInfo = inst._hostContainerInfo;
245
+ var isDocumentFragment = containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE;
246
+ var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument;
247
+ listenTo(registrationName, doc);
248
+}
249
+
250
+function putListener() {
251
+ var listenerToPut = this;
252
+ EventPluginHub.putListener(
253
+ listenerToPut.inst,
254
+ listenerToPut.registrationName,
255
+ listenerToPut.listener
256
+ );
257
+}
258
+
259
+function inputPostMount() {
260
+ var inst = this;
261
+ ReactDOMInput.postMountWrapper(inst);
262
+}
263
+
264
+function textareaPostMount() {
265
+ var inst = this;
266
+ ReactDOMTextarea.postMountWrapper(inst);
267
+}
268
+
269
+function optionPostMount() {
270
+ var inst = this;
271
+ ReactDOMOption.postMountWrapper(inst);
272
+}
273
+
274
+var setAndValidateContentChildDev = emptyFunction;
275
+if (__DEV__) {
276
+ setAndValidateContentChildDev = function(content) {
277
+ var hasExistingContent = this._contentDebugID != null;
278
+ var debugID = this._debugID;
279
+ // This ID represents the inlined child that has no backing instance:
280
+ var contentDebugID = -debugID;
281
+
282
+ if (content == null) {
283
+ if (hasExistingContent) {
284
+ ReactInstrumentation.debugTool.onUnmountComponent(this._contentDebugID);
285
+ }
286
+ this._contentDebugID = null;
287
+ return;
288
+ }
289
+
290
+ validateDOMNesting(null, String(content), this, this._ancestorInfo);
291
+ this._contentDebugID = contentDebugID;
292
+ if (hasExistingContent) {
293
+ ReactInstrumentation.debugTool.onBeforeUpdateComponent(contentDebugID, content);
294
+ ReactInstrumentation.debugTool.onUpdateComponent(contentDebugID);
295
+ } else {
296
+ ReactInstrumentation.debugTool.onBeforeMountComponent(contentDebugID, content, debugID);
297
+ ReactInstrumentation.debugTool.onMountComponent(contentDebugID);
298
+ ReactInstrumentation.debugTool.onSetChildren(debugID, [contentDebugID]);
299
+ }
300
+ };
301
+}
302
+
303
+// There are so many media events, it makes sense to just
304
+// maintain a list rather than create a `trapBubbledEvent` for each
305
+var mediaEvents = {
306
+ topAbort: 'abort',
307
+ topCanPlay: 'canplay',
308
+ topCanPlayThrough: 'canplaythrough',
309
+ topDurationChange: 'durationchange',
310
+ topEmptied: 'emptied',
311
+ topEncrypted: 'encrypted',
312
+ topEnded: 'ended',
313
+ topError: 'error',
314
+ topLoadedData: 'loadeddata',
315
+ topLoadedMetadata: 'loadedmetadata',
316
+ topLoadStart: 'loadstart',
317
+ topPause: 'pause',
318
+ topPlay: 'play',
319
+ topPlaying: 'playing',
320
+ topProgress: 'progress',
321
+ topRateChange: 'ratechange',
322
+ topSeeked: 'seeked',
323
+ topSeeking: 'seeking',
324
+ topStalled: 'stalled',
325
+ topSuspend: 'suspend',
326
+ topTimeUpdate: 'timeupdate',
327
+ topVolumeChange: 'volumechange',
328
+ topWaiting: 'waiting',
329
+};
330
+
331
+function trackInputValue() {
332
+ inputValueTracking.track(this);
333
+}
334
+
335
+function trapClickOnNonInteractiveElement() {
336
+ // Mobile Safari does not fire properly bubble click events on
337
+ // non-interactive elements, which means delegated click listeners do not
338
+ // fire. The workaround for this bug involves attaching an empty click
339
+ // listener on the target node.
340
+ // http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
341
+ // Just set it using the onclick property so that we don't have to manage any
342
+ // bookkeeping for it. Not sure if we need to clear it when the listener is
343
+ // removed.
344
+ // TODO: Only do this for the relevant Safaris maybe?
345
+ var node = getNode(this);
346
+ node.onclick = emptyFunction;
347
+}
348
+
349
+function trapBubbledEventsLocal() {
350
+ var inst = this;
351
+ // If a component renders to null or if another component fatals and causes
352
+ // the state of the tree to be corrupted, `node` here can be null.
353
+ invariant(inst._rootNodeID, 'Must be mounted to trap events');
354
+ var node = getNode(inst);
355
+ invariant(
356
+ node,
357
+ 'trapBubbledEvent(...): Requires node to be rendered.'
358
+ );
359
+
360
+ switch (inst._tag) {
361
+ case 'iframe':
362
+ case 'object':
363
+ inst._wrapperState.listeners = [
364
+ ReactBrowserEventEmitter.trapBubbledEvent(
365
+ 'topLoad',
366
+ 'load',
367
+ node
368
+ ),
369
+ ];
370
+ break;
371
+ case 'video':
372
+ case 'audio':
373
+
374
+ inst._wrapperState.listeners = [];
375
+ // Create listener for each media event
376
+ for (var event in mediaEvents) {
377
+ if (mediaEvents.hasOwnProperty(event)) {
378
+ inst._wrapperState.listeners.push(
379
+ ReactBrowserEventEmitter.trapBubbledEvent(
380
+ event,
381
+ mediaEvents[event],
382
+ node
383
+ )
384
+ );
385
+ }
386
+ }
387
+ break;
388
+ case 'source':
389
+ inst._wrapperState.listeners = [
390
+ ReactBrowserEventEmitter.trapBubbledEvent(
391
+ 'topError',
392
+ 'error',
393
+ node
394
+ ),
395
+ ];
396
+ break;
397
+ case 'img':
398
+ inst._wrapperState.listeners = [
399
+ ReactBrowserEventEmitter.trapBubbledEvent(
400
+ 'topError',
401
+ 'error',
402
+ node
403
+ ),
404
+ ReactBrowserEventEmitter.trapBubbledEvent(
405
+ 'topLoad',
406
+ 'load',
407
+ node
408
+ ),
409
+ ];
410
+ break;
411
+ case 'form':
412
+ inst._wrapperState.listeners = [
413
+ ReactBrowserEventEmitter.trapBubbledEvent(
414
+ 'topReset',
415
+ 'reset',
416
+ node
417
+ ),
418
+ ReactBrowserEventEmitter.trapBubbledEvent(
419
+ 'topSubmit',
420
+ 'submit',
421
+ node
422
+ ),
423
+ ];
424
+ break;
425
+ case 'input':
426
+ case 'select':
427
+ case 'textarea':
428
+ inst._wrapperState.listeners = [
429
+ ReactBrowserEventEmitter.trapBubbledEvent(
430
+ 'topInvalid',
431
+ 'invalid',
432
+ node
433
+ ),
434
+ ];
435
+ break;
436
+ }
437
+}
438
+
439
+function postUpdateSelectWrapper() {
440
+ ReactDOMSelect.postUpdateWrapper(this);
441
+}
442
+
443
+// For HTML, certain tags should omit their close tag. We keep a whitelist for
444
+// those special-case tags.
445
+
446
+var omittedCloseTags = {
447
+ 'area': true,
448
+ 'base': true,
449
+ 'br': true,
450
+ 'col': true,
451
+ 'embed': true,
452
+ 'hr': true,
453
+ 'img': true,
454
+ 'input': true,
455
+ 'keygen': true,
456
+ 'link': true,
457
+ 'meta': true,
458
+ 'param': true,
459
+ 'source': true,
460
+ 'track': true,
461
+ 'wbr': true,
462
+ // NOTE: menuitem's close tag should be omitted, but that causes problems.
463
+};
464
+
465
+var newlineEatingTags = {
466
+ 'listing': true,
467
+ 'pre': true,
468
+ 'textarea': true,
469
+};
470
+
471
+// For HTML, certain tags cannot have children. This has the same purpose as
472
+// `omittedCloseTags` except that `menuitem` should still have its closing tag.
473
+
474
+var voidElementTags = Object.assign({
475
+ 'menuitem': true,
476
+}, omittedCloseTags);
477
+
478
+// We accept any tag to be rendered but since this gets injected into arbitrary
479
+// HTML, we want to make sure that it's a safe tag.
480
+// http://www.w3.org/TR/REC-xml/#NT-Name
481
+
482
+var VALID_TAG_REGEX = /^[a-zA-Z][a-zA-Z:_\.\-\d]*$/; // Simplified subset
483
+var validatedTagCache = {};
484
+var hasOwnProperty = {}.hasOwnProperty;
485
+
486
+function validateDangerousTag(tag) {
487
+ if (!hasOwnProperty.call(validatedTagCache, tag)) {
488
+ invariant(VALID_TAG_REGEX.test(tag), 'Invalid tag: %s', tag);
489
+ validatedTagCache[tag] = true;
490
+ }
491
+}
492
+
493
+function isCustomComponent(tagName, props) {
494
+ return tagName.indexOf('-') >= 0 || props.is != null;
495
+}
496
+
497
+var globalIdCounter = 1;
498
+
499
+/**
500
+ * Creates a new React class that is idempotent and capable of containing other
501
+ * React components. It accepts event listeners and DOM properties that are
502
+ * valid according to `DOMProperty`.
503
+ *
504
+ * - Event listeners: `onClick`, `onMouseDown`, etc.
505
+ * - DOM properties: `className`, `name`, `title`, etc.
506
+ *
507
+ * The `style` property functions differently from the DOM API. It accepts an
508
+ * object mapping of style properties to values.
509
+ *
510
+ * @constructor ReactDOMComponent
511
+ * @extends ReactMultiChild
512
+ */
513
+function ReactDOMComponent(element) {
514
+ var tag = element.type;
515
+ validateDangerousTag(tag);
516
+ this._currentElement = element;
517
+ this._tag = tag.toLowerCase();
518
+ this._namespaceURI = null;
519
+ this._renderedChildren = null;
520
+ this._previousStyle = null;
521
+ this._previousStyleCopy = null;
522
+ this._hostNode = null;
523
+ this._hostParent = null;
524
+ this._rootNodeID = 0;
525
+ this._domID = 0;
526
+ this._hostContainerInfo = null;
527
+ this._wrapperState = null;
528
+ this._topLevelWrapper = null;
529
+ this._flags = 0;
530
+ if (__DEV__) {
531
+ this._ancestorInfo = null;
532
+ setAndValidateContentChildDev.call(this, null);
533
+ }
534
+}
535
+
536
+ReactDOMComponent.displayName = 'ReactDOMComponent';
537
+
538
+ReactDOMComponent.Mixin = {
539
+
540
+ /**
541
+ * Generates root tag markup then recurses. This method has side effects and
542
+ * is not idempotent.
543
+ *
544
+ * @internal
545
+ * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
546
+ * @param {?ReactDOMComponent} the parent component instance
547
+ * @param {?object} info about the host container
548
+ * @param {object} context
549
+ * @return {string} The computed markup.
550
+ */
551
+ mountComponent: function(
552
+ transaction,
553
+ hostParent,
554
+ hostContainerInfo,
555
+ context
556
+ ) {
557
+ this._rootNodeID = globalIdCounter++;
558
+ this._domID = hostContainerInfo._idCounter++;
559
+ this._hostParent = hostParent;
560
+ this._hostContainerInfo = hostContainerInfo;
561
+
562
+ var props = this._currentElement.props;
563
+
564
+ switch (this._tag) {
565
+ case 'audio':
566
+ case 'form':
567
+ case 'iframe':
568
+ case 'img':
569
+ case 'link':
570
+ case 'object':
571
+ case 'source':
572
+ case 'video':
573
+ this._wrapperState = {
574
+ listeners: null,
575
+ };
576
+ transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
577
+ break;
578
+ case 'input':
579
+ ReactDOMInput.mountWrapper(this, props, hostParent);
580
+ props = ReactDOMInput.getHostProps(this, props);
581
+ transaction.getReactMountReady().enqueue(trackInputValue, this);
582
+ transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
583
+ // For controlled components we always need to ensure we're listening
584
+ // to onChange. Even if there is no listener.
585
+ ensureListeningTo(this, 'onChange', transaction);
586
+ break;
587
+ case 'option':
588
+ ReactDOMOption.mountWrapper(this, props, hostParent);
589
+ props = ReactDOMOption.getHostProps(this, props);
590
+ break;
591
+ case 'select':
592
+ ReactDOMSelect.mountWrapper(this, props, hostParent);
593
+ props = ReactDOMSelect.getHostProps(this, props);
594
+ transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
595
+ // For controlled components we always need to ensure we're listening
596
+ // to onChange. Even if there is no listener.
597
+ ensureListeningTo(this, 'onChange', transaction);
598
+ break;
599
+ case 'textarea':
600
+ ReactDOMTextarea.mountWrapper(this, props, hostParent);
601
+ props = ReactDOMTextarea.getHostProps(this, props);
602
+ transaction.getReactMountReady().enqueue(trackInputValue, this);
603
+ transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
604
+ // For controlled components we always need to ensure we're listening
605
+ // to onChange. Even if there is no listener.
606
+ ensureListeningTo(this, 'onChange', transaction);
607
+ break;
608
+ }
609
+
610
+ assertValidProps(this, props);
611
+
612
+ // We create tags in the namespace of their parent container, except HTML
613
+ // tags get no namespace.
614
+ var namespaceURI;
615
+ var parentTag;
616
+ if (hostParent != null) {
617
+ namespaceURI = hostParent._namespaceURI;
618
+ parentTag = hostParent._tag;
619
+ } else if (hostContainerInfo._tag) {
620
+ namespaceURI = hostContainerInfo._namespaceURI;
621
+ parentTag = hostContainerInfo._tag;
622
+ }
623
+ if (namespaceURI == null ||
624
+ namespaceURI === DOMNamespaces.svg && parentTag === 'foreignobject') {
625
+ namespaceURI = DOMNamespaces.html;
626
+ }
627
+ if (namespaceURI === DOMNamespaces.html) {
628
+ if (this._tag === 'svg') {
629
+ namespaceURI = DOMNamespaces.svg;
630
+ } else if (this._tag === 'math') {
631
+ namespaceURI = DOMNamespaces.mathml;
632
+ }
633
+ }
634
+ this._namespaceURI = namespaceURI;
635
+
636
+ if (__DEV__) {
637
+ var parentInfo;
638
+ if (hostParent != null) {
639
+ parentInfo = hostParent._ancestorInfo;
640
+ } else if (hostContainerInfo._tag) {
641
+ parentInfo = hostContainerInfo._ancestorInfo;
642
+ }
643
+ if (parentInfo) {
644
+ // parentInfo should always be present except for the top-level
645
+ // component when server rendering
646
+ validateDOMNesting(this._tag, null, this, parentInfo);
647
+ }
648
+ this._ancestorInfo =
649
+ validateDOMNesting.updatedAncestorInfo(parentInfo, this._tag, this);
650
+ }
651
+
652
+ var mountImage;
653
+ var type = this._currentElement.type;
654
+ if (transaction.useCreateElement) {
655
+ var ownerDocument = hostContainerInfo._ownerDocument;
656
+ var el;
657
+ if (namespaceURI === DOMNamespaces.html) {
658
+ if (this._tag === 'script') {
659
+ // Create the script via .innerHTML so its "parser-inserted" flag is
660
+ // set to true and it does not execute
661
+ var div = ownerDocument.createElement('div');
662
+ div.innerHTML = `<${type}></${type}>`;
663
+ el = div.removeChild(div.firstChild);
664
+ } else if (props.is) {
665
+ el = ownerDocument.createElement(type, props.is);
666
+ } else {
667
+ // Separate else branch instead of using `props.is || undefined` above becuase of a Firefox bug.
668
+ // See discussion in https://github.com/facebook/react/pull/6896
669
+ // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
670
+ el = ownerDocument.createElement(type);
671
+ }
672
+ } else {
673
+ el = ownerDocument.createElementNS(
674
+ namespaceURI,
675
+ type
676
+ );
677
+ }
678
+ var isCustomComponentTag = isCustomComponent(this._tag, props);
679
+ if (__DEV__ && isCustomComponentTag && !didWarnShadyDOM && el.shadyRoot) {
680
+ var owner = this._currentElement._owner;
681
+ var name = owner && owner.getName() || 'A component';
682
+ warning(
683
+ false,
684
+ '%s is using shady DOM. Using shady DOM with React can ' +
685
+ 'cause things to break subtly.',
686
+ name
687
+ );
688
+ didWarnShadyDOM = true;
689
+ }
690
+ ReactDOMComponentTree.precacheNode(this, el);
691
+ this._flags |= Flags.hasCachedChildNodes;
692
+ if (!this._hostParent) {
693
+ DOMPropertyOperations.setAttributeForRoot(el);
694
+ }
695
+ this._updateDOMProperties(null, props, transaction, isCustomComponentTag);
696
+ var lazyTree = DOMLazyTree(el);
697
+ this._createInitialChildren(transaction, props, context, lazyTree);
698
+ mountImage = lazyTree;
699
+ } else {
700
+ var tagOpen = this._createOpenTagMarkupAndPutListeners(transaction, props);
701
+ var tagContent = this._createContentMarkup(transaction, props, context);
702
+ if (!tagContent && omittedCloseTags[this._tag]) {
703
+ mountImage = tagOpen + '/>';
704
+ } else {
705
+ mountImage = tagOpen + '>' + tagContent + '</' + type + '>';
706
+ }
707
+ }
708
+
709
+ switch (this._tag) {
710
+ case 'input':
711
+ transaction.getReactMountReady().enqueue(
712
+ inputPostMount,
713
+ this
714
+ );
715
+ if (props.autoFocus) {
716
+ transaction.getReactMountReady().enqueue(
717
+ AutoFocusUtils.focusDOMComponent,
718
+ this
719
+ );
720
+ }
721
+ break;
722
+ case 'textarea':
723
+ transaction.getReactMountReady().enqueue(
724
+ textareaPostMount,
725
+ this
726
+ );
727
+ if (props.autoFocus) {
728
+ transaction.getReactMountReady().enqueue(
729
+ AutoFocusUtils.focusDOMComponent,
730
+ this
731
+ );
732
+ }
733
+ break;
734
+ case 'select':
735
+ if (props.autoFocus) {
736
+ transaction.getReactMountReady().enqueue(
737
+ AutoFocusUtils.focusDOMComponent,
738
+ this
739
+ );
740
+ }
741
+ break;
742
+ case 'button':
743
+ if (props.autoFocus) {
744
+ transaction.getReactMountReady().enqueue(
745
+ AutoFocusUtils.focusDOMComponent,
746
+ this
747
+ );
748
+ }
749
+ break;
750
+ case 'option':
751
+ transaction.getReactMountReady().enqueue(
752
+ optionPostMount,
753
+ this
754
+ );
755
+ break;
756
+ default:
757
+ if (typeof props.onClick === 'function') {
758
+ transaction.getReactMountReady().enqueue(
759
+ trapClickOnNonInteractiveElement,
760
+ this
761
+ );
762
+ }
763
+ break;
764
+ }
765
+
766
+ return mountImage;
767
+ },
768
+
769
+ /**
770
+ * Creates markup for the open tag and all attributes.
771
+ *
772
+ * This method has side effects because events get registered.
773
+ *
774
+ * Iterating over object properties is faster than iterating over arrays.
775
+ * @see http://jsperf.com/obj-vs-arr-iteration
776
+ *
777
+ * @private
778
+ * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
779
+ * @param {object} props
780
+ * @return {string} Markup of opening tag.
781
+ */
782
+ _createOpenTagMarkupAndPutListeners: function(transaction, props) {
783
+ var ret = '<' + this._currentElement.type;
784
+
785
+ for (var propKey in props) {
786
+ if (!props.hasOwnProperty(propKey)) {
787
+ continue;
788
+ }
789
+ var propValue = props[propKey];
790
+ if (propValue == null) {
791
+ continue;
792
+ }
793
+ if (registrationNameModules.hasOwnProperty(propKey)) {
794
+ if (propValue) {
795
+ enqueuePutListener(this, propKey, propValue, transaction);
796
+ }
797
+ } else {
798
+ if (propKey === STYLE) {
799
+ if (propValue) {
800
+ if (__DEV__) {
801
+ // See `_updateDOMProperties`. style block
802
+ this._previousStyle = propValue;
803
+ }
804
+ propValue = this._previousStyleCopy = Object.assign({}, props.style);
805
+ }
806
+ propValue = CSSPropertyOperations.createMarkupForStyles(propValue, this);
807
+ }
808
+ var markup = null;
809
+ if (this._tag != null && isCustomComponent(this._tag, props)) {
810
+ if (!RESERVED_PROPS.hasOwnProperty(propKey)) {
811
+ markup = DOMPropertyOperations.createMarkupForCustomAttribute(propKey, propValue);
812
+ }
813
+ } else {
814
+ markup = DOMPropertyOperations.createMarkupForProperty(propKey, propValue);
815
+ }
816
+ if (markup) {
817
+ ret += ' ' + markup;
818
+ }
819
+ }
820
+ }
821
+
822
+ // For static pages, no need to put React ID and checksum. Saves lots of
823
+ // bytes.
824
+ if (transaction.renderToStaticMarkup) {
825
+ return ret;
826
+ }
827
+
828
+ if (!this._hostParent) {
829
+ ret += ' ' + DOMPropertyOperations.createMarkupForRoot();
830
+ }
831
+ ret += ' ' + DOMPropertyOperations.createMarkupForID(this._domID);
832
+ return ret;
833
+ },
834
+
835
+ /**
836
+ * Creates markup for the content between the tags.
837
+ *
838
+ * @private
839
+ * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
840
+ * @param {object} props
841
+ * @param {object} context
842
+ * @return {string} Content markup.
843
+ */
844
+ _createContentMarkup: function(transaction, props, context) {
845
+ var ret = '';
846
+
847
+ // Intentional use of != to avoid catching zero/false.
848
+ var innerHTML = props.dangerouslySetInnerHTML;
849
+ if (innerHTML != null) {
850
+ if (innerHTML.__html != null) {
851
+ ret = innerHTML.__html;
852
+ }
853
+ } else {
854
+ var contentToUse =
855
+ CONTENT_TYPES[typeof props.children] ? props.children : null;
856
+ var childrenToUse = contentToUse != null ? null : props.children;
857
+ if (contentToUse != null) {
858
+ // TODO: Validate that text is allowed as a child of this node
859
+ ret = escapeTextContentForBrowser(contentToUse);
860
+ if (__DEV__) {
861
+ setAndValidateContentChildDev.call(this, contentToUse);
862
+ }
863
+ } else if (childrenToUse != null) {
864
+ var mountImages = this.mountChildren(
865
+ childrenToUse,
866
+ transaction,
867
+ context
868
+ );
869
+ ret = mountImages.join('');
870
+ }
871
+ }
872
+ if (newlineEatingTags[this._tag] && ret.charAt(0) === '\n') {
873
+ // text/html ignores the first character in these tags if it's a newline
874
+ // Prefer to break application/xml over text/html (for now) by adding
875
+ // a newline specifically to get eaten by the parser. (Alternately for
876
+ // textareas, replacing "^\n" with "\r\n" doesn't get eaten, and the first
877
+ // \r is normalized out by HTMLTextAreaElement#value.)
878
+ // See: <http://www.w3.org/TR/html-polyglot/#newlines-in-textarea-and-pre>
879
+ // See: <http://www.w3.org/TR/html5/syntax.html#element-restrictions>
880
+ // See: <http://www.w3.org/TR/html5/syntax.html#newlines>
881
+ // See: Parsing of "textarea" "listing" and "pre" elements
882
+ // from <http://www.w3.org/TR/html5/syntax.html#parsing-main-inbody>
883
+ return '\n' + ret;
884
+ } else {
885
+ return ret;
886
+ }
887
+ },
888
+
889
+ _createInitialChildren: function(transaction, props, context, lazyTree) {
890
+ // Intentional use of != to avoid catching zero/false.
891
+ var innerHTML = props.dangerouslySetInnerHTML;
892
+ if (innerHTML != null) {
893
+ if (innerHTML.__html != null) {
894
+ DOMLazyTree.queueHTML(lazyTree, innerHTML.__html);
895
+ }
896
+ } else {
897
+ var contentToUse =
898
+ CONTENT_TYPES[typeof props.children] ? props.children : null;
899
+ var childrenToUse = contentToUse != null ? null : props.children;
900
+ // TODO: Validate that text is allowed as a child of this node
901
+ if (contentToUse != null) {
902
+ // Avoid setting textContent when the text is empty. In IE11 setting
903
+ // textContent on a text area will cause the placeholder to not
904
+ // show within the textarea until it has been focused and blurred again.
905
+ // https://github.com/facebook/react/issues/6731#issuecomment-254874553
906
+ if (contentToUse !== '') {
907
+ if (__DEV__) {
908
+ setAndValidateContentChildDev.call(this, contentToUse);
909
+ }
910
+ DOMLazyTree.queueText(lazyTree, contentToUse);
911
+ }
912
+ } else if (childrenToUse != null) {
913
+ var mountImages = this.mountChildren(
914
+ childrenToUse,
915
+ transaction,
916
+ context
917
+ );
918
+ for (var i = 0; i < mountImages.length; i++) {
919
+ DOMLazyTree.queueChild(lazyTree, mountImages[i]);
920
+ }
921
+ }
922
+ }
923
+ },
924
+
925
+ /**
926
+ * Receives a next element and updates the component.
927
+ *
928
+ * @internal
929
+ * @param {ReactElement} nextElement
930
+ * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
931
+ * @param {object} context
932
+ */
933
+ receiveComponent: function(nextElement, transaction, context) {
934
+ var prevElement = this._currentElement;
935
+ this._currentElement = nextElement;
936
+ this.updateComponent(transaction, prevElement, nextElement, context);
937
+ },
938
+
939
+ /**
940
+ * Updates a DOM component after it has already been allocated and
941
+ * attached to the DOM. Reconciles the root DOM node, then recurses.
942
+ *
943
+ * @param {ReactReconcileTransaction} transaction
944
+ * @param {ReactElement} prevElement
945
+ * @param {ReactElement} nextElement
946
+ * @internal
947
+ * @overridable
948
+ */
949
+ updateComponent: function(transaction, prevElement, nextElement, context) {
950
+ var lastProps = prevElement.props;
951
+ var nextProps = this._currentElement.props;
952
+
953
+ switch (this._tag) {
954
+ case 'input':
955
+ lastProps = ReactDOMInput.getHostProps(this, lastProps);
956
+ nextProps = ReactDOMInput.getHostProps(this, nextProps);
957
+ break;
958
+ case 'option':
959
+ lastProps = ReactDOMOption.getHostProps(this, lastProps);
960
+ nextProps = ReactDOMOption.getHostProps(this, nextProps);
961
+ break;
962
+ case 'select':
963
+ lastProps = ReactDOMSelect.getHostProps(this, lastProps);
964
+ nextProps = ReactDOMSelect.getHostProps(this, nextProps);
965
+ break;
966
+ case 'textarea':
967
+ lastProps = ReactDOMTextarea.getHostProps(this, lastProps);
968
+ nextProps = ReactDOMTextarea.getHostProps(this, nextProps);
969
+ break;
970
+ default:
971
+ if (typeof lastProps.onClick !== 'function' &&
972
+ typeof nextProps.onClick === 'function') {
973
+ transaction.getReactMountReady().enqueue(
974
+ trapClickOnNonInteractiveElement,
975
+ this
976
+ );
977
+ }
978
+ break;
979
+ }
980
+
981
+ assertValidProps(this, nextProps);
982
+ var isCustomComponentTag = isCustomComponent(this._tag, nextProps);
983
+ this._updateDOMProperties(lastProps, nextProps, transaction, isCustomComponentTag);
984
+ this._updateDOMChildren(
985
+ lastProps,
986
+ nextProps,
987
+ transaction,
988
+ context
989
+ );
990
+
991
+ switch (this._tag) {
992
+ case 'input':
993
+ // Update the wrapper around inputs *after* updating props. This has to
994
+ // happen after `_updateDOMProperties`. Otherwise HTML5 input validations
995
+ // raise warnings and prevent the new value from being assigned.
996
+ ReactDOMInput.updateWrapper(this);
997
+ break;
998
+ case 'textarea':
999
+ ReactDOMTextarea.updateWrapper(this);
1000
+ break;
1001
+ case 'select':
1002
+ // <select> value update needs to occur after <option> children
1003
+ // reconciliation
1004
+ transaction.getReactMountReady().enqueue(postUpdateSelectWrapper, this);
1005
+ break;
1006
+ }
1007
+ },
1008
+
1009
+ /**
1010
+ * Reconciles the properties by detecting differences in property values and
1011
+ * updating the DOM as necessary. This function is probably the single most
1012
+ * critical path for performance optimization.
1013
+ *
1014
+ * TODO: Benchmark whether checking for changed values in memory actually
1015
+ * improves performance (especially statically positioned elements).
1016
+ * TODO: Benchmark the effects of putting this at the top since 99% of props
1017
+ * do not change for a given reconciliation.
1018
+ * TODO: Benchmark areas that can be improved with caching.
1019
+ *
1020
+ * @private
1021
+ * @param {object} lastProps
1022
+ * @param {object} nextProps
1023
+ * @param {?DOMElement} node
1024
+ */
1025
+ _updateDOMProperties: function(
1026
+ lastProps,
1027
+ nextProps,
1028
+ transaction,
1029
+ isCustomComponentTag
1030
+ ) {
1031
+ var propKey;
1032
+ var styleName;
1033
+ var styleUpdates;
1034
+ for (propKey in lastProps) {
1035
+ if (nextProps.hasOwnProperty(propKey) ||
1036
+ !lastProps.hasOwnProperty(propKey) ||
1037
+ lastProps[propKey] == null) {
1038
+ continue;
1039
+ }
1040
+ if (propKey === STYLE) {
1041
+ var lastStyle = this._previousStyleCopy;
1042
+ for (styleName in lastStyle) {
1043
+ if (lastStyle.hasOwnProperty(styleName)) {
1044
+ styleUpdates = styleUpdates || {};
1045
+ styleUpdates[styleName] = '';
1046
+ }
1047
+ }
1048
+ this._previousStyleCopy = null;
1049
+ } else if (registrationNameModules.hasOwnProperty(propKey)) {
1050
+ if (lastProps[propKey]) {
1051
+ // Only call deleteListener if there was a listener previously or
1052
+ // else willDeleteListener gets called when there wasn't actually a
1053
+ // listener (e.g., onClick={null})
1054
+ deleteListener(this, propKey);
1055
+ }
1056
+ } else if (isCustomComponent(this._tag, lastProps)) {
1057
+ if (!RESERVED_PROPS.hasOwnProperty(propKey)) {
1058
+ DOMPropertyOperations.deleteValueForAttribute(
1059
+ getNode(this),
1060
+ propKey
1061
+ );
1062
+ }
1063
+ } else if (
1064
+ DOMProperty.properties[propKey] ||
1065
+ DOMProperty.isCustomAttribute(propKey)) {
1066
+ DOMPropertyOperations.deleteValueForProperty(getNode(this), propKey);
1067
+ }
1068
+ }
1069
+ for (propKey in nextProps) {
1070
+ var nextProp = nextProps[propKey];
1071
+ var lastProp =
1072
+ propKey === STYLE ? this._previousStyleCopy :
1073
+ lastProps != null ? lastProps[propKey] : undefined;
1074
+ if (!nextProps.hasOwnProperty(propKey) ||
1075
+ nextProp === lastProp ||
1076
+ nextProp == null && lastProp == null) {
1077
+ continue;
1078
+ }
1079
+ if (propKey === STYLE) {
1080
+ if (nextProp) {
1081
+ if (__DEV__) {
1082
+ checkAndWarnForMutatedStyle(
1083
+ this._previousStyleCopy,
1084
+ this._previousStyle,
1085
+ this
1086
+ );
1087
+ this._previousStyle = nextProp;
1088
+ }
1089
+ nextProp = this._previousStyleCopy = Object.assign({}, nextProp);
1090
+ } else {
1091
+ this._previousStyleCopy = null;
1092
+ }
1093
+ if (lastProp) {
1094
+ // Unset styles on `lastProp` but not on `nextProp`.
1095
+ for (styleName in lastProp) {
1096
+ if (lastProp.hasOwnProperty(styleName) &&
1097
+ (!nextProp || !nextProp.hasOwnProperty(styleName))) {
1098
+ styleUpdates = styleUpdates || {};
1099
+ styleUpdates[styleName] = '';
1100
+ }
1101
+ }
1102
+ // Update styles that changed since `lastProp`.
1103
+ for (styleName in nextProp) {
1104
+ if (nextProp.hasOwnProperty(styleName) &&
1105
+ lastProp[styleName] !== nextProp[styleName]) {
1106
+ styleUpdates = styleUpdates || {};
1107
+ styleUpdates[styleName] = nextProp[styleName];
1108
+ }
1109
+ }
1110
+ } else {
1111
+ // Relies on `updateStylesByID` not mutating `styleUpdates`.
1112
+ styleUpdates = nextProp;
1113
+ }
1114
+ } else if (registrationNameModules.hasOwnProperty(propKey)) {
1115
+ if (nextProp) {
1116
+ enqueuePutListener(this, propKey, nextProp, transaction);
1117
+ } else if (lastProp) {
1118
+ deleteListener(this, propKey);
1119
+ }
1120
+ } else if (isCustomComponentTag) {
1121
+ if (!RESERVED_PROPS.hasOwnProperty(propKey)) {
1122
+ DOMPropertyOperations.setValueForAttribute(
1123
+ getNode(this),
1124
+ propKey,
1125
+ nextProp
1126
+ );
1127
+ }
1128
+ } else if (
1129
+ DOMProperty.properties[propKey] ||
1130
+ DOMProperty.isCustomAttribute(propKey)) {
1131
+ var node = getNode(this);
1132
+ // If we're updating to null or undefined, we should remove the property
1133
+ // from the DOM node instead of inadvertently setting to a string. This
1134
+ // brings us in line with the same behavior we have on initial render.
1135
+ if (nextProp != null) {
1136
+ DOMPropertyOperations.setValueForProperty(node, propKey, nextProp);
1137
+ } else {
1138
+ DOMPropertyOperations.deleteValueForProperty(node, propKey);
1139
+ }
1140
+ }
1141
+ }
1142
+ if (styleUpdates) {
1143
+ CSSPropertyOperations.setValueForStyles(
1144
+ getNode(this),
1145
+ styleUpdates,
1146
+ this
1147
+ );
1148
+ }
1149
+ },
1150
+
1151
+ /**
1152
+ * Reconciles the children with the various properties that affect the
1153
+ * children content.
1154
+ *
1155
+ * @param {object} lastProps
1156
+ * @param {object} nextProps
1157
+ * @param {ReactReconcileTransaction} transaction
1158
+ * @param {object} context
1159
+ */
1160
+ _updateDOMChildren: function(lastProps, nextProps, transaction, context) {
1161
+ var lastContent =
1162
+ CONTENT_TYPES[typeof lastProps.children] ? lastProps.children : null;
1163
+ var nextContent =
1164
+ CONTENT_TYPES[typeof nextProps.children] ? nextProps.children : null;
1165
+
1166
+ var lastHtml =
1167
+ lastProps.dangerouslySetInnerHTML &&
1168
+ lastProps.dangerouslySetInnerHTML.__html;
1169
+ var nextHtml =
1170
+ nextProps.dangerouslySetInnerHTML &&
1171
+ nextProps.dangerouslySetInnerHTML.__html;
1172
+
1173
+ // Note the use of `!=` which checks for null or undefined.
1174
+ var lastChildren = lastContent != null ? null : lastProps.children;
1175
+ var nextChildren = nextContent != null ? null : nextProps.children;
1176
+
1177
+ // If we're switching from children to content/html or vice versa, remove
1178
+ // the old content
1179
+ var lastHasContentOrHtml = lastContent != null || lastHtml != null;
1180
+ var nextHasContentOrHtml = nextContent != null || nextHtml != null;
1181
+ if (lastChildren != null && nextChildren == null) {
1182
+ this.updateChildren(null, transaction, context);
1183
+ } else if (lastHasContentOrHtml && !nextHasContentOrHtml) {
1184
+ this.updateTextContent('');
1185
+ if (__DEV__) {
1186
+ ReactInstrumentation.debugTool.onSetChildren(this._debugID, []);
1187
+ }
1188
+ }
1189
+
1190
+ if (nextContent != null) {
1191
+ if (lastContent !== nextContent) {
1192
+ this.updateTextContent('' + nextContent);
1193
+ if (__DEV__) {
1194
+ setAndValidateContentChildDev.call(this, nextContent);
1195
+ }
1196
+ }
1197
+ } else if (nextHtml != null) {
1198
+ if (lastHtml !== nextHtml) {
1199
+ this.updateMarkup('' + nextHtml);
1200
+ }
1201
+ if (__DEV__) {
1202
+ ReactInstrumentation.debugTool.onSetChildren(this._debugID, []);
1203
+ }
1204
+ } else if (nextChildren != null) {
1205
+ if (__DEV__) {
1206
+ setAndValidateContentChildDev.call(this, null);
1207
+ }
1208
+
1209
+ this.updateChildren(nextChildren, transaction, context);
1210
+ }
1211
+ },
1212
+
1213
+ getHostNode: function() {
1214
+ return getNode(this);
1215
+ },
1216
+
1217
+ /**
1218
+ * Destroys all event registrations for this instance. Does not remove from
1219
+ * the DOM. That must be done by the parent.
1220
+ *
1221
+ * @internal
1222
+ */
1223
+ unmountComponent: function(safely, skipLifecycle) {
1224
+ switch (this._tag) {
1225
+ case 'audio':
1226
+ case 'form':
1227
+ case 'iframe':
1228
+ case 'img':
1229
+ case 'link':
1230
+ case 'object':
1231
+ case 'source':
1232
+ case 'video':
1233
+ var listeners = this._wrapperState.listeners;
1234
+ if (listeners) {
1235
+ for (var i = 0; i < listeners.length; i++) {
1236
+ listeners[i].remove();
1237
+ }
1238
+ }
1239
+ break;
1240
+ case 'input':
1241
+ case 'textarea':
1242
+ inputValueTracking.stopTracking(this);
1243
+ break;
1244
+ case 'html':
1245
+ case 'head':
1246
+ case 'body':
1247
+ /**
1248
+ * Components like <html> <head> and <body> can't be removed or added
1249
+ * easily in a cross-browser way, however it's valuable to be able to
1250
+ * take advantage of React's reconciliation for styling and <title>
1251
+ * management. So we just document it and throw in dangerous cases.
1252
+ */
1253
+ invariant(
1254
+ false,
1255
+ '<%s> tried to unmount. Because of cross-browser quirks it is ' +
1256
+ 'impossible to unmount some top-level components (eg <html>, ' +
1257
+ '<head>, and <body>) reliably and efficiently. To fix this, have a ' +
1258
+ 'single top-level component that never unmounts render these ' +
1259
+ 'elements.',
1260
+ this._tag
1261
+ );
1262
+ break;
1263
+ }
1264
+
1265
+ this.unmountChildren(safely, skipLifecycle);
1266
+ ReactDOMComponentTree.uncacheNode(this);
1267
+ EventPluginHub.deleteAllListeners(this);
1268
+ this._rootNodeID = 0;
1269
+ this._domID = 0;
1270
+ this._wrapperState = null;
1271
+
1272
+ if (__DEV__) {
1273
+ setAndValidateContentChildDev.call(this, null);
1274
+ }
1275
+ },
1276
+
1277
+ restoreControlledState: function() {
1278
+ switch (this._tag) {
1279
+ case 'input':
1280
+ ReactDOMInput.restoreControlledState(this);
1281
+ return;
1282
+ case 'textarea':
1283
+ ReactDOMTextarea.restoreControlledState(this);
1284
+ return;
1285
+ case 'select':
1286
+ ReactDOMSelect.restoreControlledState(this);
1287
+ return;
1288
+ }
1289
+ },
1290
+
1291
+ getPublicInstance: function() {
1292
+ return getNode(this);
1293
+ },
1294
+
1295
+};
1296
+
1297
+Object.assign(
1298
+ ReactDOMComponent.prototype,
1299
+ ReactDOMComponent.Mixin,
1300
+ ReactMultiChild
1301
+);
1302
+
1303
+module.exports = ReactDOMComponent;
src/renderers/dom/fiber/wrappers/ReactDOMFiberInput.js
new
+313
@@ -0,0 +1,313 @@
1
+/**
2
+ * Copyright 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactDOMFiberInput
10
+ */
11
+
12
+'use strict';
13
+
14
+var DOMPropertyOperations = require('DOMPropertyOperations');
15
+var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes');
16
+var ReactDOMComponentTree = require('ReactDOMComponentTree');
17
+
18
+var invariant = require('invariant');
19
+var warning = require('warning');
20
+
21
+var didWarnValueDefaultValue = false;
22
+var didWarnCheckedDefaultChecked = false;
23
+var didWarnControlledToUncontrolled = false;
24
+var didWarnUncontrolledToControlled = false;
25
+
26
+function isControlled(props) {
27
+ var usesChecked = props.type === 'checkbox' || props.type === 'radio';
28
+ return usesChecked ? props.checked != null : props.value != null;
29
+}
30
+
31
+/**
32
+ * Implements an <input> host component that allows setting these optional
33
+ * props: `checked`, `value`, `defaultChecked`, and `defaultValue`.
34
+ *
35
+ * If `checked` or `value` are not supplied (or null/undefined), user actions
36
+ * that affect the checked state or value will trigger updates to the element.
37
+ *
38
+ * If they are supplied (and not null/undefined), the rendered element will not
39
+ * trigger updates to the element. Instead, the props must change in order for
40
+ * the rendered element to be updated.
41
+ *
42
+ * The rendered element will be initialized as unchecked (or `defaultChecked`)
43
+ * with an empty value (or `defaultValue`).
44
+ *
45
+ * @see http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
46
+ */
47
+var ReactDOMInput = {
48
+ getHostProps: function(inst, props) {
49
+ var value = props.value;
50
+ var checked = props.checked;
51
+
52
+ var hostProps = Object.assign({
53
+ // Make sure we set .type before any other properties (setting .value
54
+ // before .type means .value is lost in IE11 and below)
55
+ type: undefined,
56
+ // Make sure we set .step before .value (setting .value before .step
57
+ // means .value is rounded on mount, based upon step precision)
58
+ step: undefined,
59
+ // Make sure we set .min & .max before .value (to ensure proper order
60
+ // in corner cases such as min or max deriving from value, e.g. Issue #7170)
61
+ min: undefined,
62
+ max: undefined,
63
+ }, props, {
64
+ defaultChecked: undefined,
65
+ defaultValue: undefined,
66
+ value: value != null ? value : inst._wrapperState.initialValue,
67
+ checked: checked != null ? checked : inst._wrapperState.initialChecked,
68
+ });
69
+
70
+ return hostProps;
71
+ },
72
+
73
+ mountWrapper: function(inst, props) {
74
+ if (__DEV__) {
75
+ ReactControlledValuePropTypes.checkPropTypes(
76
+ 'input',
77
+ props,
78
+ inst._currentElement._owner
79
+ );
80
+
81
+ var owner = inst._currentElement._owner;
82
+
83
+ if (
84
+ props.checked !== undefined &&
85
+ props.defaultChecked !== undefined &&
86
+ !didWarnCheckedDefaultChecked
87
+ ) {
88
+ warning(
89
+ false,
90
+ '%s contains an input of type %s with both checked and defaultChecked props. ' +
91
+ 'Input elements must be either controlled or uncontrolled ' +
92
+ '(specify either the checked prop, or the defaultChecked prop, but not ' +
93
+ 'both). Decide between using a controlled or uncontrolled input ' +
94
+ 'element and remove one of these props. More info: ' +
95
+ 'https://fb.me/react-controlled-components',
96
+ owner && owner.getName() || 'A component',
97
+ props.type
98
+ );
99
+ didWarnCheckedDefaultChecked = true;
100
+ }
101
+ if (
102
+ props.value !== undefined &&
103
+ props.defaultValue !== undefined &&
104
+ !didWarnValueDefaultValue
105
+ ) {
106
+ warning(
107
+ false,
108
+ '%s contains an input of type %s with both value and defaultValue props. ' +
109
+ 'Input elements must be either controlled or uncontrolled ' +
110
+ '(specify either the value prop, or the defaultValue prop, but not ' +
111
+ 'both). Decide between using a controlled or uncontrolled input ' +
112
+ 'element and remove one of these props. More info: ' +
113
+ 'https://fb.me/react-controlled-components',
114
+ owner && owner.getName() || 'A component',
115
+ props.type
116
+ );
117
+ didWarnValueDefaultValue = true;
118
+ }
119
+ }
120
+
121
+ var defaultValue = props.defaultValue;
122
+ inst._wrapperState = {
123
+ initialChecked: props.checked != null ? props.checked : props.defaultChecked,
124
+ initialValue: props.value != null ? props.value : defaultValue,
125
+ listeners: null,
126
+ };
127
+
128
+ if (__DEV__) {
129
+ inst._wrapperState.controlled = isControlled(props);
130
+ }
131
+ },
132
+
133
+ updateWrapper: function(inst) {
134
+ var props = inst._currentElement.props;
135
+
136
+ if (__DEV__) {
137
+ var controlled = isControlled(props);
138
+ var owner = inst._currentElement._owner;
139
+
140
+ if (!inst._wrapperState.controlled && controlled && !didWarnUncontrolledToControlled) {
141
+ warning(
142
+ false,
143
+ '%s is changing an uncontrolled input of type %s to be controlled. ' +
144
+ 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' +
145
+ 'Decide between using a controlled or uncontrolled input ' +
146
+ 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components',
147
+ owner && owner.getName() || 'A component',
148
+ props.type
149
+ );
150
+ didWarnUncontrolledToControlled = true;
151
+ }
152
+ if (inst._wrapperState.controlled && !controlled && !didWarnControlledToUncontrolled) {
153
+ warning(
154
+ false,
155
+ '%s is changing a controlled input of type %s to be uncontrolled. ' +
156
+ 'Input elements should not switch from controlled to uncontrolled (or vice versa). ' +
157
+ 'Decide between using a controlled or uncontrolled input ' +
158
+ 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components',
159
+ owner && owner.getName() || 'A component',
160
+ props.type
161
+ );
162
+ didWarnControlledToUncontrolled = true;
163
+ }
164
+ }
165
+
166
+ var checked = props.checked;
167
+ if (checked != null) {
168
+ DOMPropertyOperations.setValueForProperty(
169
+ ReactDOMComponentTree.getNodeFromInstance(inst),
170
+ 'checked',
171
+ checked || false
172
+ );
173
+ }
174
+
175
+ var node = ReactDOMComponentTree.getNodeFromInstance(inst);
176
+ var value = props.value;
177
+ if (value != null) {
178
+
179
+ // Cast `value` to a string to ensure the value is set correctly. While
180
+ // browsers typically do this as necessary, jsdom doesn't.
181
+ var newValue = '' + value;
182
+
183
+ // To avoid side effects (such as losing text selection), only set value if changed
184
+ if (newValue !== node.value) {
185
+ node.value = newValue;
186
+ }
187
+ } else {
188
+ if (props.value == null && props.defaultValue != null) {
189
+ // In Chrome, assigning defaultValue to certain input types triggers input validation.
190
+ // For number inputs, the display value loses trailing decimal points. For email inputs,
191
+ // Chrome raises "The specified value <x> is not a valid email address".
192
+ //
193
+ // Here we check to see if the defaultValue has actually changed, avoiding these problems
194
+ // when the user is inputting text
195
+ //
196
+ // https://github.com/facebook/react/issues/7253
197
+ if (node.defaultValue !== '' + props.defaultValue) {
198
+ node.defaultValue = '' + props.defaultValue;
199
+ }
200
+ }
201
+ if (props.checked == null && props.defaultChecked != null) {
202
+ node.defaultChecked = !!props.defaultChecked;
203
+ }
204
+ }
205
+ },
206
+
207
+ postMountWrapper: function(inst) {
208
+ var props = inst._currentElement.props;
209
+
210
+ // This is in postMount because we need access to the DOM node, which is not
211
+ // available until after the component has mounted.
212
+ var node = ReactDOMComponentTree.getNodeFromInstance(inst);
213
+
214
+ // Detach value from defaultValue. We won't do anything if we're working on
215
+ // submit or reset inputs as those values & defaultValues are linked. They
216
+ // are not resetable nodes so this operation doesn't matter and actually
217
+ // removes browser-default values (eg "Submit Query") when no value is
218
+ // provided.
219
+
220
+ switch (props.type) {
221
+ case 'submit':
222
+ case 'reset':
223
+ break;
224
+ case 'color':
225
+ case 'date':
226
+ case 'datetime':
227
+ case 'datetime-local':
228
+ case 'month':
229
+ case 'time':
230
+ case 'week':
231
+ // This fixes the no-show issue on iOS Safari and Android Chrome:
232
+ // https://github.com/facebook/react/issues/7233
233
+ node.value = '';
234
+ node.value = node.defaultValue;
235
+ break;
236
+ default:
237
+ node.value = node.value;
238
+ break;
239
+ }
240
+
241
+ // Normally, we'd just do `node.checked = node.checked` upon initial mount, less this bug
242
+ // this is needed to work around a chrome bug where setting defaultChecked
243
+ // will sometimes influence the value of checked (even after detachment).
244
+ // Reference: https://bugs.chromium.org/p/chromium/issues/detail?id=608416
245
+ // We need to temporarily unset name to avoid disrupting radio button groups.
246
+ var name = node.name;
247
+ if (name !== '') {
248
+ node.name = '';
249
+ }
250
+ node.defaultChecked = !node.defaultChecked;
251
+ node.defaultChecked = !node.defaultChecked;
252
+ if (name !== '') {
253
+ node.name = name;
254
+ }
255
+ },
256
+
257
+ restoreControlledState: function(inst) {
258
+ if (inst._rootNodeID) {
259
+ // DOM component is still mounted; update
260
+ ReactDOMInput.updateWrapper(inst);
261
+ }
262
+ var props = inst._currentElement.props;
263
+ updateNamedCousins(inst, props);
264
+ },
265
+};
266
+
267
+function updateNamedCousins(thisInstance, props) {
268
+ var name = props.name;
269
+ if (props.type === 'radio' && name != null) {
270
+ var rootNode = ReactDOMComponentTree.getNodeFromInstance(thisInstance);
271
+ var queryRoot = rootNode;
272
+
273
+ while (queryRoot.parentNode) {
274
+ queryRoot = queryRoot.parentNode;
275
+ }
276
+
277
+ // If `rootNode.form` was non-null, then we could try `form.elements`,
278
+ // but that sometimes behaves strangely in IE8. We could also try using
279
+ // `form.getElementsByName`, but that will only return direct children
280
+ // and won't include inputs that use the HTML5 `form=` attribute. Since
281
+ // the input might not even be in a form. It might not even be in the
282
+ // document. Let's just use the local `querySelectorAll` to ensure we don't
283
+ // miss anything.
284
+ var group = queryRoot.querySelectorAll(
285
+ 'input[name=' + JSON.stringify('' + name) + '][type="radio"]');
286
+
287
+ for (var i = 0; i < group.length; i++) {
288
+ var otherNode = group[i];
289
+ if (otherNode === rootNode ||
290
+ otherNode.form !== rootNode.form) {
291
+ continue;
292
+ }
293
+ // This will throw if radio buttons rendered by different copies of React
294
+ // and the same name are rendered into the same form (same as #1939).
295
+ // That's probably okay; we don't support it just as we don't support
296
+ // mixing React radio buttons with non-React ones.
297
+ var otherInstance = ReactDOMComponentTree.getInstanceFromNode(otherNode);
298
+ invariant(
299
+ otherInstance,
300
+ 'ReactDOMInput: Mixing React and non-React radio inputs with the ' +
301
+ 'same `name` is not supported.'
302
+ );
303
+ // If this is a controlled radio button group, forcing the input that
304
+ // was previously checked to update will cause it to be come re-checked
305
+ // as appropriate.
306
+ if (otherInstance._rootNodeID) {
307
+ ReactDOMInput.updateWrapper(otherInstance);
308
+ }
309
+ }
310
+ }
311
+}
312
+
313
+module.exports = ReactDOMInput;
src/renderers/dom/fiber/wrappers/ReactDOMFiberOption.js
new
+128
@@ -0,0 +1,128 @@
1
+/**
2
+ * Copyright 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactDOMFiberOption
10
+ */
11
+
12
+'use strict';
13
+
14
+var React = require('React');
15
+var ReactDOMComponentTree = require('ReactDOMComponentTree');
16
+var ReactDOMSelect = require('ReactDOMSelect');
17
+
18
+var warning = require('warning');
19
+var didWarnInvalidOptionChildren = false;
20
+
21
+function flattenChildren(children) {
22
+ var content = '';
23
+
24
+ // Flatten children and warn if they aren't strings or numbers;
25
+ // invalid types are ignored.
26
+ React.Children.forEach(children, function(child) {
27
+ if (child == null) {
28
+ return;
29
+ }
30
+ if (typeof child === 'string' || typeof child === 'number') {
31
+ content += child;
32
+ } else if (!didWarnInvalidOptionChildren) {
33
+ didWarnInvalidOptionChildren = true;
34
+ warning(
35
+ false,
36
+ 'Only strings and numbers are supported as <option> children.'
37
+ );
38
+ }
39
+ });
40
+
41
+ return content;
42
+}
43
+
44
+/**
45
+ * Implements an <option> host component that warns when `selected` is set.
46
+ */
47
+var ReactDOMOption = {
48
+ mountWrapper: function(inst, props, hostParent) {
49
+ // TODO (yungsters): Remove support for `selected` in <option>.
50
+ if (__DEV__) {
51
+ warning(
52
+ props.selected == null,
53
+ 'Use the `defaultValue` or `value` props on <select> instead of ' +
54
+ 'setting `selected` on <option>.'
55
+ );
56
+ }
57
+
58
+ // Look up whether this option is 'selected'
59
+ var selectValue = null;
60
+ if (hostParent != null) {
61
+ var selectParent = hostParent;
62
+
63
+ if (selectParent._tag === 'optgroup') {
64
+ selectParent = selectParent._hostParent;
65
+ }
66
+
67
+ if (selectParent != null && selectParent._tag === 'select') {
68
+ selectValue = ReactDOMSelect.getSelectValueContext(selectParent);
69
+ }
70
+ }
71
+
72
+ // If the value is null (e.g., no specified value or after initial mount)
73
+ // or missing (e.g., for <datalist>), we don't change props.selected
74
+ var selected = null;
75
+ if (selectValue != null) {
76
+ var value;
77
+ if (props.value != null) {
78
+ value = props.value + '';
79
+ } else {
80
+ value = flattenChildren(props.children);
81
+ }
82
+ selected = false;
83
+ if (Array.isArray(selectValue)) {
84
+ // multiple
85
+ for (var i = 0; i < selectValue.length; i++) {
86
+ if ('' + selectValue[i] === value) {
87
+ selected = true;
88
+ break;
89
+ }
90
+ }
91
+ } else {
92
+ selected = ('' + selectValue === value);
93
+ }
94
+ }
95
+
96
+ inst._wrapperState = {selected: selected};
97
+ },
98
+
99
+ postMountWrapper: function(inst) {
100
+ // value="" should make a value attribute (#6219)
101
+ var props = inst._currentElement.props;
102
+ if (props.value != null) {
103
+ var node = ReactDOMComponentTree.getNodeFromInstance(inst);
104
+ node.setAttribute('value', props.value);
105
+ }
106
+ },
107
+
108
+ getHostProps: function(inst, props) {
109
+ var hostProps = Object.assign({selected: undefined, children: undefined}, props);
110
+
111
+ // Read state only from initial mount because <select> updates value
112
+ // manually; we need the initial state only for server rendering
113
+ if (inst._wrapperState.selected != null) {
114
+ hostProps.selected = inst._wrapperState.selected;
115
+ }
116
+
117
+ var content = flattenChildren(props.children);
118
+
119
+ if (content) {
120
+ hostProps.children = content;
121
+ }
122
+
123
+ return hostProps;
124
+ },
125
+
126
+};
127
+
128
+module.exports = ReactDOMOption;
src/renderers/dom/fiber/wrappers/ReactDOMFiberSelect.js
new
+200
@@ -0,0 +1,200 @@
1
+/**
2
+ * Copyright 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactDOMFiberSelect
10
+ */
11
+
12
+'use strict';
13
+
14
+var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes');
15
+var ReactDOMComponentTree = require('ReactDOMComponentTree');
16
+
17
+var warning = require('warning');
18
+
19
+var didWarnValueDefaultValue = false;
20
+
21
+function getDeclarationErrorAddendum(owner) {
22
+ if (owner) {
23
+ var name = owner.getName();
24
+ if (name) {
25
+ return ' Check the render method of `' + name + '`.';
26
+ }
27
+ }
28
+ return '';
29
+}
30
+
31
+var valuePropNames = ['value', 'defaultValue'];
32
+
33
+/**
34
+ * Validation function for `value` and `defaultValue`.
35
+ * @private
36
+ */
37
+function checkSelectPropTypes(inst, props) {
38
+ var owner = inst._currentElement._owner;
39
+ ReactControlledValuePropTypes.checkPropTypes(
40
+ 'select',
41
+ props,
42
+ owner
43
+ );
44
+
45
+ for (var i = 0; i < valuePropNames.length; i++) {
46
+ var propName = valuePropNames[i];
47
+ if (props[propName] == null) {
48
+ continue;
49
+ }
50
+ var isArray = Array.isArray(props[propName]);
51
+ if (props.multiple && !isArray) {
52
+ warning(
53
+ false,
54
+ 'The `%s` prop supplied to <select> must be an array if ' +
55
+ '`multiple` is true.%s',
56
+ propName,
57
+ getDeclarationErrorAddendum(owner)
58
+ );
59
+ } else if (!props.multiple && isArray) {
60
+ warning(
61
+ false,
62
+ 'The `%s` prop supplied to <select> must be a scalar ' +
63
+ 'value if `multiple` is false.%s',
64
+ propName,
65
+ getDeclarationErrorAddendum(owner)
66
+ );
67
+ }
68
+ }
69
+}
70
+
71
+/**
72
+ * @param {ReactDOMComponent} inst
73
+ * @param {boolean} multiple
74
+ * @param {*} propValue A stringable (with `multiple`, a list of stringables).
75
+ * @private
76
+ */
77
+function updateOptions(inst, multiple, propValue) {
78
+ var options = ReactDOMComponentTree.getNodeFromInstance(inst).options;
79
+
80
+ if (multiple) {
81
+ let selectedValue = {};
82
+ for (let i = 0; i < propValue.length; i++) {
83
+ selectedValue['' + propValue[i]] = true;
84
+ }
85
+ for (let i = 0; i < options.length; i++) {
86
+ var selected = selectedValue.hasOwnProperty(options[i].value);
87
+ if (options[i].selected !== selected) {
88
+ options[i].selected = selected;
89
+ }
90
+ }
91
+ } else {
92
+ // Do not set `select.value` as exact behavior isn't consistent across all
93
+ // browsers for all cases.
94
+ let selectedValue = '' + propValue;
95
+ for (let i = 0; i < options.length; i++) {
96
+ if (options[i].value === selectedValue) {
97
+ options[i].selected = true;
98
+ return;
99
+ }
100
+ }
101
+ if (options.length) {
102
+ options[0].selected = true;
103
+ }
104
+ }
105
+}
106
+
107
+/**
108
+ * Implements a <select> host component that allows optionally setting the
109
+ * props `value` and `defaultValue`. If `multiple` is false, the prop must be a
110
+ * stringable. If `multiple` is true, the prop must be an array of stringables.
111
+ *
112
+ * If `value` is not supplied (or null/undefined), user actions that change the
113
+ * selected option will trigger updates to the rendered options.
114
+ *
115
+ * If it is supplied (and not null/undefined), the rendered options will not
116
+ * update in response to user actions. Instead, the `value` prop must change in
117
+ * order for the rendered options to update.
118
+ *
119
+ * If `defaultValue` is provided, any options with the supplied values will be
120
+ * selected.
121
+ */
122
+var ReactDOMSelect = {
123
+ getHostProps: function(inst, props) {
124
+ return Object.assign({}, props, {
125
+ value: undefined,
126
+ });
127
+ },
128
+
129
+ mountWrapper: function(inst, props) {
130
+ if (__DEV__) {
131
+ checkSelectPropTypes(inst, props);
132
+ }
133
+
134
+ var value = props.value;
135
+ inst._wrapperState = {
136
+ initialValue: value != null ? value : props.defaultValue,
137
+ listeners: null,
138
+ wasMultiple: Boolean(props.multiple),
139
+ };
140
+
141
+ if (
142
+ props.value !== undefined &&
143
+ props.defaultValue !== undefined &&
144
+ !didWarnValueDefaultValue
145
+ ) {
146
+ warning(
147
+ false,
148
+ 'Select elements must be either controlled or uncontrolled ' +
149
+ '(specify either the value prop, or the defaultValue prop, but not ' +
150
+ 'both). Decide between using a controlled or uncontrolled select ' +
151
+ 'element and remove one of these props. More info: ' +
152
+ 'https://fb.me/react-controlled-components'
153
+ );
154
+ didWarnValueDefaultValue = true;
155
+ }
156
+ },
157
+
158
+ getSelectValueContext: function(inst) {
159
+ // ReactDOMOption looks at this initial value so the initial generated
160
+ // markup has correct `selected` attributes
161
+ return inst._wrapperState.initialValue;
162
+ },
163
+
164
+ postUpdateWrapper: function(inst) {
165
+ var props = inst._currentElement.props;
166
+
167
+ // After the initial mount, we control selected-ness manually so don't pass
168
+ // this value down
169
+ inst._wrapperState.initialValue = undefined;
170
+
171
+ var wasMultiple = inst._wrapperState.wasMultiple;
172
+ inst._wrapperState.wasMultiple = Boolean(props.multiple);
173
+
174
+ var value = props.value;
175
+ if (value != null) {
176
+ updateOptions(inst, Boolean(props.multiple), value);
177
+ } else if (wasMultiple !== Boolean(props.multiple)) {
178
+ // For simplicity, reapply `defaultValue` if `multiple` is toggled.
179
+ if (props.defaultValue != null) {
180
+ updateOptions(inst, Boolean(props.multiple), props.defaultValue);
181
+ } else {
182
+ // Revert the select back to its default unselected state.
183
+ updateOptions(inst, Boolean(props.multiple), props.multiple ? [] : '');
184
+ }
185
+ }
186
+ },
187
+
188
+ restoreControlledState: function(inst) {
189
+ if (inst._rootNodeID) {
190
+ var props = inst._currentElement.props;
191
+ var value = props.value;
192
+
193
+ if (value != null) {
194
+ updateOptions(inst, Boolean(props.multiple), value);
195
+ }
196
+ }
197
+ },
198
+};
199
+
200
+module.exports = ReactDOMSelect;
src/renderers/dom/fiber/wrappers/ReactDOMFiberTextarea.js
new
+172
@@ -0,0 +1,172 @@
1
+/**
2
+ * Copyright 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactDOMFiberTextarea
10
+ */
11
+
12
+'use strict';
13
+
14
+var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes');
15
+var ReactDOMComponentTree = require('ReactDOMComponentTree');
16
+
17
+var invariant = require('invariant');
18
+var warning = require('warning');
19
+
20
+var didWarnValDefaultVal = false;
21
+
22
+/**
23
+ * Implements a <textarea> host component that allows setting `value`, and
24
+ * `defaultValue`. This differs from the traditional DOM API because value is
25
+ * usually set as PCDATA children.
26
+ *
27
+ * If `value` is not supplied (or null/undefined), user actions that affect the
28
+ * value will trigger updates to the element.
29
+ *
30
+ * If `value` is supplied (and not null/undefined), the rendered element will
31
+ * not trigger updates to the element. Instead, the `value` prop must change in
32
+ * order for the rendered element to be updated.
33
+ *
34
+ * The rendered element will be initialized with an empty value, the prop
35
+ * `defaultValue` if specified, or the children content (deprecated).
36
+ */
37
+var ReactDOMTextarea = {
38
+ getHostProps: function(inst, props) {
39
+ invariant(
40
+ props.dangerouslySetInnerHTML == null,
41
+ '`dangerouslySetInnerHTML` does not make sense on <textarea>.'
42
+ );
43
+
44
+ // Always set children to the same thing. In IE9, the selection range will
45
+ // get reset if `textContent` is mutated. We could add a check in setTextContent
46
+ // to only set the value if/when the value differs from the node value (which would
47
+ // completely solve this IE9 bug), but Sebastian+Ben seemed to like this solution.
48
+ // The value can be a boolean or object so that's why it's forced to be a string.
49
+ var hostProps = Object.assign({}, props, {
50
+ value: undefined,
51
+ defaultValue: undefined,
52
+ children: '' + inst._wrapperState.initialValue,
53
+ });
54
+
55
+ return hostProps;
56
+ },
57
+
58
+ mountWrapper: function(inst, props) {
59
+ if (__DEV__) {
60
+ ReactControlledValuePropTypes.checkPropTypes(
61
+ 'textarea',
62
+ props,
63
+ inst._currentElement._owner
64
+ );
65
+ if (
66
+ props.value !== undefined &&
67
+ props.defaultValue !== undefined &&
68
+ !didWarnValDefaultVal
69
+ ) {
70
+ warning(
71
+ false,
72
+ 'Textarea elements must be either controlled or uncontrolled ' +
73
+ '(specify either the value prop, or the defaultValue prop, but not ' +
74
+ 'both). Decide between using a controlled or uncontrolled textarea ' +
75
+ 'and remove one of these props. More info: ' +
76
+ 'https://fb.me/react-controlled-components'
77
+ );
78
+ didWarnValDefaultVal = true;
79
+ }
80
+ }
81
+
82
+
83
+ var value = props.value;
84
+ var initialValue = value;
85
+
86
+ // Only bother fetching default value if we're going to use it
87
+ if (value == null) {
88
+ var defaultValue = props.defaultValue;
89
+ // TODO (yungsters): Remove support for children content in <textarea>.
90
+ var children = props.children;
91
+ if (children != null) {
92
+ if (__DEV__) {
93
+ warning(
94
+ false,
95
+ 'Use the `defaultValue` or `value` props instead of setting ' +
96
+ 'children on <textarea>.'
97
+ );
98
+ }
99
+ invariant(
100
+ defaultValue == null,
101
+ 'If you supply `defaultValue` on a <textarea>, do not pass children.'
102
+ );
103
+ if (Array.isArray(children)) {
104
+ invariant(
105
+ children.length <= 1,
106
+ '<textarea> can only have at most one child.'
107
+ );
108
+ children = children[0];
109
+ }
110
+
111
+ defaultValue = '' + children;
112
+ }
113
+ if (defaultValue == null) {
114
+ defaultValue = '';
115
+ }
116
+ initialValue = defaultValue;
117
+ }
118
+
119
+ inst._wrapperState = {
120
+ initialValue: '' + initialValue,
121
+ listeners: null,
122
+ };
123
+ },
124
+
125
+ updateWrapper: function(inst) {
126
+ var props = inst._currentElement.props;
127
+
128
+ var node = ReactDOMComponentTree.getNodeFromInstance(inst);
129
+ var value = props.value;
130
+ if (value != null) {
131
+ // Cast `value` to a string to ensure the value is set correctly. While
132
+ // browsers typically do this as necessary, jsdom doesn't.
133
+ var newValue = '' + value;
134
+
135
+ // To avoid side effects (such as losing text selection), only set value if changed
136
+ if (newValue !== node.value) {
137
+ node.value = newValue;
138
+ }
139
+ if (props.defaultValue == null) {
140
+ node.defaultValue = newValue;
141
+ }
142
+ }
143
+ if (props.defaultValue != null) {
144
+ node.defaultValue = props.defaultValue;
145
+ }
146
+ },
147
+
148
+ postMountWrapper: function(inst) {
149
+ // This is in postMount because we need access to the DOM node, which is not
150
+ // available until after the component has mounted.
151
+ var node = ReactDOMComponentTree.getNodeFromInstance(inst);
152
+ var textContent = node.textContent;
153
+
154
+ // Only set node.value if textContent is equal to the expected
155
+ // initial value. In IE10/IE11 there is a bug where the placeholder attribute
156
+ // will populate textContent as well.
157
+ // https://developer.microsoft.com/microsoft-edge/platform/issues/101525/
158
+ if (textContent === inst._wrapperState.initialValue) {
159
+ node.value = textContent;
160
+ }
161
+ },
162
+
163
+ restoreControlledState: function(inst) {
164
+ if (inst._rootNodeID) {
165
+ // DOM component is still mounted; update
166
+ ReactDOMTextarea.updateWrapper(inst);
167
+ }
168
+ },
169
+
170
+};
171
+
172
+module.exports = ReactDOMTextarea;