10
'use strict';
11
12
// TODO: direct imports like some-package/src/* are bad. Fix me.
13
-var {
14
- getCurrentFiberOwnerName,
15
-} = require('react-reconciler/src/ReactDebugCurrentFiber');
16
-var EventPluginRegistry = require('events/EventPluginRegistry');
17
-var emptyFunction = require('fbjs/lib/emptyFunction');
13
+import ReactDebugCurrentFiber
14
+ from 'react-reconciler/src/ReactDebugCurrentFiber';
15
+import EventPluginRegistry from 'events/EventPluginRegistry';
16
+import emptyFunction from 'fbjs/lib/emptyFunction';
17
+import warning from 'fbjs/lib/warning';
18
19
-var DOMPropertyOperations = require('./DOMPropertyOperations');
20
-var ReactDOMFiberInput = require('./ReactDOMFiberInput');
21
-var ReactDOMFiberOption = require('./ReactDOMFiberOption');
22
-var ReactDOMFiberSelect = require('./ReactDOMFiberSelect');
23
-var ReactDOMFiberTextarea = require('./ReactDOMFiberTextarea');
24
-var inputValueTracking = require('./inputValueTracking');
25
-var setInnerHTML = require('./setInnerHTML');
26
-var setTextContent = require('./setTextContent');
27
-var ReactBrowserEventEmitter = require('../events/ReactBrowserEventEmitter');
28
-var CSSPropertyOperations = require('../shared/CSSPropertyOperations');
29
-var DOMNamespaces = require('../shared/DOMNamespaces');
30
-var DOMProperty = require('../shared/DOMProperty');
31
-var assertValidProps = require('../shared/assertValidProps');
32
-var {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE} = require('../shared/HTMLNodeType');
33
-var isCustomComponent = require('../shared/isCustomComponent');
34
-
35
-if (__DEV__) {
36
- // TODO: direct imports like some-package/src/* are bad. Fix me.
37
- var {
38
- getCurrentFiberStackAddendum,
39
- } = require('react-reconciler/src/ReactDebugCurrentFiber');
40
- var warning = require('fbjs/lib/warning');
41
-
42
- var ReactDOMInvalidARIAHook = require('../shared/ReactDOMInvalidARIAHook');
43
- var ReactDOMNullInputValuePropHook = require('../shared/ReactDOMNullInputValuePropHook');
44
- var ReactDOMUnknownPropertyHook = require('../shared/ReactDOMUnknownPropertyHook');
45
- var {validateProperties: validateARIAProperties} = ReactDOMInvalidARIAHook;
46
- var {
47
- validateProperties: validateInputProperties,
48
- } = ReactDOMNullInputValuePropHook;
49
- var {
50
- validateProperties: validateUnknownProperties,
51
- } = ReactDOMUnknownPropertyHook;
52
-}
19
+import * as DOMPropertyOperations from './DOMPropertyOperations';
20
+import * as ReactDOMFiberInput from './ReactDOMFiberInput';
21
+import * as ReactDOMFiberOption from './ReactDOMFiberOption';
22
+import * as ReactDOMFiberSelect from './ReactDOMFiberSelect';
23
+import * as ReactDOMFiberTextarea from './ReactDOMFiberTextarea';
24
+import * as inputValueTracking from './inputValueTracking';
25
+import setInnerHTML from './setInnerHTML';
26
+import setTextContent from './setTextContent';
27
+import ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter';
28
+import * as CSSPropertyOperations from '../shared/CSSPropertyOperations';
29
+import {Namespaces, getIntrinsicNamespace} from '../shared/DOMNamespaces';
30
+import DOMProperty from '../shared/DOMProperty';
31
+import assertValidProps from '../shared/assertValidProps';
32
+import {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE} from '../shared/HTMLNodeType';
33
+import isCustomComponent from '../shared/isCustomComponent';
34
+import {
35
+ validateProperties as validateARIAProperties,
36
+} from '../shared/ReactDOMInvalidARIAHook';
37
+import {
38
+ validateProperties as validateInputProperties,
39
+} from '../shared/ReactDOMNullInputValuePropHook';
40
+import {
41
+ validateProperties as validateUnknownProperties,
42
+} from '../shared/ReactDOMUnknownPropertyHook';
43
44
+var {
45
+ getCurrentFiberOwnerName,
46
+ getCurrentFiberStackAddendum,
47
+} = ReactDebugCurrentFiber;
48
var didWarnInvalidHydration = false;
49
var didWarnShadyDOM = false;
50
59
var STYLE = 'style';
60
var HTML = '__html';
61
68
-var {Namespaces: {html: HTML_NAMESPACE}, getIntrinsicNamespace} = DOMNamespaces;
62
+var {html: HTML_NAMESPACE} = Namespaces;
63
64
var getStack = emptyFunction.thatReturns('');
65
362
}
363
}
364
371
-var ReactDOMFiberComponent = {
372
- createElement(
373
- type: *,
374
- props: Object,
375
- rootContainerElement: Element | Document,
376
- parentNamespace: string,
377
- ): Element {
378
- // We create tags in the namespace of their parent container, except HTML
379
- // tags get no namespace.
380
- var ownerDocument: Document = getOwnerDocumentFromRootContainer(
381
- rootContainerElement,
382
- );
383
- var domElement: Element;
384
- var namespaceURI = parentNamespace;
385
- if (namespaceURI === HTML_NAMESPACE) {
386
- namespaceURI = getIntrinsicNamespace(type);
365
+export function createElement(
366
+ type: *,
367
+ props: Object,
368
+ rootContainerElement: Element | Document,
369
+ parentNamespace: string,
370
+): Element {
371
+ // We create tags in the namespace of their parent container, except HTML
372
+ // tags get no namespace.
373
+ var ownerDocument: Document = getOwnerDocumentFromRootContainer(
374
+ rootContainerElement,
375
+ );
376
+ var domElement: Element;
377
+ var namespaceURI = parentNamespace;
378
+ if (namespaceURI === HTML_NAMESPACE) {
379
+ namespaceURI = getIntrinsicNamespace(type);
380
+ }
381
+ if (namespaceURI === HTML_NAMESPACE) {
382
+ if (__DEV__) {
383
+ var isCustomComponentTag = isCustomComponent(type, props);
384
+ // Should this check be gated by parent namespace? Not sure we want to
385
+ // allow <SVG> or <mATH>.
386
+ warning(
387
+ isCustomComponentTag || type === type.toLowerCase(),
388
+ '<%s /> is using uppercase HTML. Always use lowercase HTML tags ' +
389
+ 'in React.',
390
+ type,
391
+ );
392
+ }
393
+
394
+ if (type === 'script') {
395
+ // Create the script via .innerHTML so its "parser-inserted" flag is
396
+ // set to true and it does not execute
397
+ var div = ownerDocument.createElement('div');
398
+ div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
399
+ // This is guaranteed to yield a script element.
400
+ var firstChild = ((div.firstChild: any): HTMLScriptElement);
401
+ domElement = div.removeChild(firstChild);
402
+ } else if (typeof props.is === 'string') {
403
+ // $FlowIssue `createElement` should be updated for Web Components
404
+ domElement = ownerDocument.createElement(type, {is: props.is});
405
+ } else {
406
+ // Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.
407
+ // See discussion in https://github.com/facebook/react/pull/6896
408
+ // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
409
+ domElement = ownerDocument.createElement(type);
410
}
411
+ } else {
412
+ domElement = ownerDocument.createElementNS(namespaceURI, type);
413
+ }
414
+
415
+ if (__DEV__) {
416
if (namespaceURI === HTML_NAMESPACE) {
389
- if (__DEV__) {
390
- var isCustomComponentTag = isCustomComponent(type, props);
391
- // Should this check be gated by parent namespace? Not sure we want to
392
- // allow <SVG> or <mATH>.
417
+ if (
418
+ !isCustomComponentTag &&
419
+ Object.prototype.toString.call(domElement) ===
420
+ '[object HTMLUnknownElement]' &&
421
+ !Object.prototype.hasOwnProperty.call(warnedUnknownTags, type)
422
+ ) {
423
+ warnedUnknownTags[type] = true;
424
warning(
394
- isCustomComponentTag || type === type.toLowerCase(),
395
- '<%s /> is using uppercase HTML. Always use lowercase HTML tags ' +
396
- 'in React.',
425
+ false,
426
+ 'The tag <%s> is unrecognized in this browser. ' +
427
+ 'If you meant to render a React component, start its name with ' +
428
+ 'an uppercase letter.',
429
type,
430
);
431
}
432
+ }
433
+ }
434
401
- if (type === 'script') {
402
- // Create the script via .innerHTML so its "parser-inserted" flag is
403
- // set to true and it does not execute
404
- var div = ownerDocument.createElement('div');
405
- div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
406
- // This is guaranteed to yield a script element.
407
- var firstChild = ((div.firstChild: any): HTMLScriptElement);
408
- domElement = div.removeChild(firstChild);
409
- } else if (typeof props.is === 'string') {
410
- // $FlowIssue `createElement` should be updated for Web Components
411
- domElement = ownerDocument.createElement(type, {is: props.is});
412
- } else {
413
- // Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.
414
- // See discussion in https://github.com/facebook/react/pull/6896
415
- // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
416
- domElement = ownerDocument.createElement(type);
417
- }
418
- } else {
419
- domElement = ownerDocument.createElementNS(namespaceURI, type);
435
+ return domElement;
436
+}
437
+
438
+export function createTextNode(
439
+ text: string,
440
+ rootContainerElement: Element | Document,
441
+): Text {
442
+ return getOwnerDocumentFromRootContainer(rootContainerElement).createTextNode(
443
+ text,
444
+ );
445
+}
446
+
447
+export function setInitialProperties(
448
+ domElement: Element,
449
+ tag: string,
450
+ rawProps: Object,
451
+ rootContainerElement: Element | Document,
452
+): void {
453
+ var isCustomComponentTag = isCustomComponent(tag, rawProps);
454
+ if (__DEV__) {
455
+ validatePropertiesInDevelopment(tag, rawProps);
456
+ if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
457
+ warning(
458
+ false,
459
+ '%s is using shady DOM. Using shady DOM with React can ' +
460
+ 'cause things to break subtly.',
461
+ getCurrentFiberOwnerName() || 'A component',
462
+ );
463
+ didWarnShadyDOM = true;
464
}
465
+ }
466
422
- if (__DEV__) {
423
- if (namespaceURI === HTML_NAMESPACE) {
424
- if (
425
- !isCustomComponentTag &&
426
- Object.prototype.toString.call(domElement) ===
427
- '[object HTMLUnknownElement]' &&
428
- !Object.prototype.hasOwnProperty.call(warnedUnknownTags, type)
429
- ) {
430
- warnedUnknownTags[type] = true;
431
- warning(
432
- false,
433
- 'The tag <%s> is unrecognized in this browser. ' +
434
- 'If you meant to render a React component, start its name with ' +
435
- 'an uppercase letter.',
436
- type,
467
+ // TODO: Make sure that we check isMounted before firing any of these events.
468
+ var props: Object;
469
+ switch (tag) {
470
+ case 'iframe':
471
+ case 'object':
472
+ ReactBrowserEventEmitter.trapBubbledEvent('topLoad', 'load', domElement);
473
+ props = rawProps;
474
+ break;
475
+ case 'video':
476
+ case 'audio':
477
+ // Create listener for each media event
478
+ for (var event in mediaEvents) {
479
+ if (mediaEvents.hasOwnProperty(event)) {
480
+ ReactBrowserEventEmitter.trapBubbledEvent(
481
+ event,
482
+ mediaEvents[event],
483
+ domElement,
484
);
485
}
486
}
440
- }
487
+ props = rawProps;
488
+ break;
489
+ case 'source':
490
+ ReactBrowserEventEmitter.trapBubbledEvent(
491
+ 'topError',
492
+ 'error',
493
+ domElement,
494
+ );
495
+ props = rawProps;
496
+ break;
497
+ case 'img':
498
+ case 'image':
499
+ ReactBrowserEventEmitter.trapBubbledEvent(
500
+ 'topError',
501
+ 'error',
502
+ domElement,
503
+ );
504
+ ReactBrowserEventEmitter.trapBubbledEvent('topLoad', 'load', domElement);
505
+ props = rawProps;
506
+ break;
507
+ case 'form':
508
+ ReactBrowserEventEmitter.trapBubbledEvent(
509
+ 'topReset',
510
+ 'reset',
511
+ domElement,
512
+ );
513
+ ReactBrowserEventEmitter.trapBubbledEvent(
514
+ 'topSubmit',
515
+ 'submit',
516
+ domElement,
517
+ );
518
+ props = rawProps;
519
+ break;
520
+ case 'details':
521
+ ReactBrowserEventEmitter.trapBubbledEvent(
522
+ 'topToggle',
523
+ 'toggle',
524
+ domElement,
525
+ );
526
+ props = rawProps;
527
+ break;
528
+ case 'input':
529
+ ReactDOMFiberInput.initWrapperState(domElement, rawProps);
530
+ props = ReactDOMFiberInput.getHostProps(domElement, rawProps);
531
+ ReactBrowserEventEmitter.trapBubbledEvent(
532
+ 'topInvalid',
533
+ 'invalid',
534
+ domElement,
535
+ );
536
+ // For controlled components we always need to ensure we're listening
537
+ // to onChange. Even if there is no listener.
538
+ ensureListeningTo(rootContainerElement, 'onChange');
539
+ break;
540
+ case 'option':
541
+ ReactDOMFiberOption.validateProps(domElement, rawProps);
542
+ props = ReactDOMFiberOption.getHostProps(domElement, rawProps);
543
+ break;
544
+ case 'select':
545
+ ReactDOMFiberSelect.initWrapperState(domElement, rawProps);
546
+ props = ReactDOMFiberSelect.getHostProps(domElement, rawProps);
547
+ ReactBrowserEventEmitter.trapBubbledEvent(
548
+ 'topInvalid',
549
+ 'invalid',
550
+ domElement,
551
+ );
552
+ // For controlled components we always need to ensure we're listening
553
+ // to onChange. Even if there is no listener.
554
+ ensureListeningTo(rootContainerElement, 'onChange');
555
+ break;
556
+ case 'textarea':
557
+ ReactDOMFiberTextarea.initWrapperState(domElement, rawProps);
558
+ props = ReactDOMFiberTextarea.getHostProps(domElement, rawProps);
559
+ ReactBrowserEventEmitter.trapBubbledEvent(
560
+ 'topInvalid',
561
+ 'invalid',
562
+ domElement,
563
+ );
564
+ // For controlled components we always need to ensure we're listening
565
+ // to onChange. Even if there is no listener.
566
+ ensureListeningTo(rootContainerElement, 'onChange');
567
+ break;
568
+ default:
569
+ props = rawProps;
570
+ }
571
442
- return domElement;
443
- },
572
+ assertValidProps(tag, props, getStack);
573
445
- createTextNode(text: string, rootContainerElement: Element | Document): Text {
446
- return getOwnerDocumentFromRootContainer(
447
- rootContainerElement,
448
- ).createTextNode(text);
449
- },
574
+ setInitialDOMProperties(
575
+ tag,
576
+ domElement,
577
+ rootContainerElement,
578
+ props,
579
+ isCustomComponentTag,
580
+ );
581
451
- setInitialProperties(
452
- domElement: Element,
453
- tag: string,
454
- rawProps: Object,
455
- rootContainerElement: Element | Document,
456
- ): void {
457
- var isCustomComponentTag = isCustomComponent(tag, rawProps);
458
- if (__DEV__) {
459
- validatePropertiesInDevelopment(tag, rawProps);
460
- if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
461
- warning(
462
- false,
463
- '%s is using shady DOM. Using shady DOM with React can ' +
464
- 'cause things to break subtly.',
465
- getCurrentFiberOwnerName() || 'A component',
466
- );
467
- didWarnShadyDOM = true;
582
+ switch (tag) {
583
+ case 'input':
584
+ // TODO: Make sure we check if this is still unmounted or do any clean
585
+ // up necessary since we never stop tracking anymore.
586
+ inputValueTracking.track((domElement: any));
587
+ ReactDOMFiberInput.postMountWrapper(domElement, rawProps);
588
+ break;
589
+ case 'textarea':
590
+ // TODO: Make sure we check if this is still unmounted or do any clean
591
+ // up necessary since we never stop tracking anymore.
592
+ inputValueTracking.track((domElement: any));
593
+ ReactDOMFiberTextarea.postMountWrapper(domElement, rawProps);
594
+ break;
595
+ case 'option':
596
+ ReactDOMFiberOption.postMountWrapper(domElement, rawProps);
597
+ break;
598
+ case 'select':
599
+ ReactDOMFiberSelect.postMountWrapper(domElement, rawProps);
600
+ break;
601
+ default:
602
+ if (typeof props.onClick === 'function') {
603
+ // TODO: This cast may not be sound for SVG, MathML or custom elements.
604
+ trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
605
}
469
- }
606
+ break;
607
+ }
608
+}
609
471
- // TODO: Make sure that we check isMounted before firing any of these events.
472
- var props: Object;
473
- switch (tag) {
474
- case 'iframe':
475
- case 'object':
476
- ReactBrowserEventEmitter.trapBubbledEvent(
477
- 'topLoad',
478
- 'load',
479
- domElement,
480
- );
481
- props = rawProps;
482
- break;
483
- case 'video':
484
- case 'audio':
485
- // Create listener for each media event
486
- for (var event in mediaEvents) {
487
- if (mediaEvents.hasOwnProperty(event)) {
488
- ReactBrowserEventEmitter.trapBubbledEvent(
489
- event,
490
- mediaEvents[event],
491
- domElement,
492
- );
493
- }
494
- }
495
- props = rawProps;
496
- break;
497
- case 'source':
498
- ReactBrowserEventEmitter.trapBubbledEvent(
499
- 'topError',
500
- 'error',
501
- domElement,
502
- );
503
- props = rawProps;
504
- break;
505
- case 'img':
506
- case 'image':
507
- ReactBrowserEventEmitter.trapBubbledEvent(
508
- 'topError',
509
- 'error',
510
- domElement,
511
- );
512
- ReactBrowserEventEmitter.trapBubbledEvent(
513
- 'topLoad',
514
- 'load',
515
- domElement,
516
- );
517
- props = rawProps;
518
- break;
519
- case 'form':
520
- ReactBrowserEventEmitter.trapBubbledEvent(
521
- 'topReset',
522
- 'reset',
523
- domElement,
524
- );
525
- ReactBrowserEventEmitter.trapBubbledEvent(
526
- 'topSubmit',
527
- 'submit',
528
- domElement,
529
- );
530
- props = rawProps;
531
- break;
532
- case 'details':
533
- ReactBrowserEventEmitter.trapBubbledEvent(
534
- 'topToggle',
535
- 'toggle',
536
- domElement,
537
- );
538
- props = rawProps;
539
- break;
540
- case 'input':
541
- ReactDOMFiberInput.initWrapperState(domElement, rawProps);
542
- props = ReactDOMFiberInput.getHostProps(domElement, rawProps);
543
- ReactBrowserEventEmitter.trapBubbledEvent(
544
- 'topInvalid',
545
- 'invalid',
546
- domElement,
547
- );
548
- // For controlled components we always need to ensure we're listening
549
- // to onChange. Even if there is no listener.
550
- ensureListeningTo(rootContainerElement, 'onChange');
551
- break;
552
- case 'option':
553
- ReactDOMFiberOption.validateProps(domElement, rawProps);
554
- props = ReactDOMFiberOption.getHostProps(domElement, rawProps);
555
- break;
556
- case 'select':
557
- ReactDOMFiberSelect.initWrapperState(domElement, rawProps);
558
- props = ReactDOMFiberSelect.getHostProps(domElement, rawProps);
559
- ReactBrowserEventEmitter.trapBubbledEvent(
560
- 'topInvalid',
561
- 'invalid',
562
- domElement,
563
- );
564
- // For controlled components we always need to ensure we're listening
565
- // to onChange. Even if there is no listener.
566
- ensureListeningTo(rootContainerElement, 'onChange');
567
- break;
568
- case 'textarea':
569
- ReactDOMFiberTextarea.initWrapperState(domElement, rawProps);
570
- props = ReactDOMFiberTextarea.getHostProps(domElement, rawProps);
571
- ReactBrowserEventEmitter.trapBubbledEvent(
572
- 'topInvalid',
573
- 'invalid',
574
- domElement,
575
- );
576
- // For controlled components we always need to ensure we're listening
577
- // to onChange. Even if there is no listener.
578
- ensureListeningTo(rootContainerElement, 'onChange');
579
- break;
580
- default:
581
- props = rawProps;
582
- }
610
+// Calculate the diff between the two objects.
611
+export function diffProperties(
612
+ domElement: Element,
613
+ tag: string,
614
+ lastRawProps: Object,
615
+ nextRawProps: Object,
616
+ rootContainerElement: Element | Document,
617
+): null | Array<mixed> {
618
+ if (__DEV__) {
619
+ validatePropertiesInDevelopment(tag, nextRawProps);
620
+ }
621
584
- assertValidProps(tag, props, getStack);
622
+ var updatePayload: null | Array<any> = null;
623
586
- setInitialDOMProperties(
587
- tag,
588
- domElement,
589
- rootContainerElement,
590
- props,
591
- isCustomComponentTag,
592
- );
624
+ var lastProps: Object;
625
+ var nextProps: Object;
626
+ switch (tag) {
627
+ case 'input':
628
+ lastProps = ReactDOMFiberInput.getHostProps(domElement, lastRawProps);
629
+ nextProps = ReactDOMFiberInput.getHostProps(domElement, nextRawProps);
630
+ updatePayload = [];
631
+ break;
632
+ case 'option':
633
+ lastProps = ReactDOMFiberOption.getHostProps(domElement, lastRawProps);
634
+ nextProps = ReactDOMFiberOption.getHostProps(domElement, nextRawProps);
635
+ updatePayload = [];
636
+ break;
637
+ case 'select':
638
+ lastProps = ReactDOMFiberSelect.getHostProps(domElement, lastRawProps);
639
+ nextProps = ReactDOMFiberSelect.getHostProps(domElement, nextRawProps);
640
+ updatePayload = [];
641
+ break;
642
+ case 'textarea':
643
+ lastProps = ReactDOMFiberTextarea.getHostProps(domElement, lastRawProps);
644
+ nextProps = ReactDOMFiberTextarea.getHostProps(domElement, nextRawProps);
645
+ updatePayload = [];
646
+ break;
647
+ default:
648
+ lastProps = lastRawProps;
649
+ nextProps = nextRawProps;
650
+ if (
651
+ typeof lastProps.onClick !== 'function' &&
652
+ typeof nextProps.onClick === 'function'
653
+ ) {
654
+ // TODO: This cast may not be sound for SVG, MathML or custom elements.
655
+ trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
656
+ }
657
+ break;
658
+ }
659
594
- switch (tag) {
595
- case 'input':
596
- // TODO: Make sure we check if this is still unmounted or do any clean
597
- // up necessary since we never stop tracking anymore.
598
- inputValueTracking.track((domElement: any));
599
- ReactDOMFiberInput.postMountWrapper(domElement, rawProps);
600
- break;
601
- case 'textarea':
602
- // TODO: Make sure we check if this is still unmounted or do any clean
603
- // up necessary since we never stop tracking anymore.
604
- inputValueTracking.track((domElement: any));
605
- ReactDOMFiberTextarea.postMountWrapper(domElement, rawProps);
606
- break;
607
- case 'option':
608
- ReactDOMFiberOption.postMountWrapper(domElement, rawProps);
609
- break;
610
- case 'select':
611
- ReactDOMFiberSelect.postMountWrapper(domElement, rawProps);
612
- break;
613
- default:
614
- if (typeof props.onClick === 'function') {
615
- // TODO: This cast may not be sound for SVG, MathML or custom elements.
616
- trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
617
- }
618
- break;
619
- }
620
- },
660
+ assertValidProps(tag, nextProps, getStack);
661
622
- // Calculate the diff between the two objects.
623
- diffProperties(
624
- domElement: Element,
625
- tag: string,
626
- lastRawProps: Object,
627
- nextRawProps: Object,
628
- rootContainerElement: Element | Document,
629
- ): null | Array<mixed> {
630
- if (__DEV__) {
631
- validatePropertiesInDevelopment(tag, nextRawProps);
662
+ var propKey;
663
+ var styleName;
664
+ var styleUpdates = null;
665
+ for (propKey in lastProps) {
666
+ if (
667
+ nextProps.hasOwnProperty(propKey) ||
668
+ !lastProps.hasOwnProperty(propKey) ||
669
+ lastProps[propKey] == null
670
+ ) {
671
+ continue;
672
}
633
-
634
- var updatePayload: null | Array<any> = null;
635
-
636
- var lastProps: Object;
637
- var nextProps: Object;
638
- switch (tag) {
639
- case 'input':
640
- lastProps = ReactDOMFiberInput.getHostProps(domElement, lastRawProps);
641
- nextProps = ReactDOMFiberInput.getHostProps(domElement, nextRawProps);
642
- updatePayload = [];
643
- break;
644
- case 'option':
645
- lastProps = ReactDOMFiberOption.getHostProps(domElement, lastRawProps);
646
- nextProps = ReactDOMFiberOption.getHostProps(domElement, nextRawProps);
647
- updatePayload = [];
648
- break;
649
- case 'select':
650
- lastProps = ReactDOMFiberSelect.getHostProps(domElement, lastRawProps);
651
- nextProps = ReactDOMFiberSelect.getHostProps(domElement, nextRawProps);
652
- updatePayload = [];
653
- break;
654
- case 'textarea':
655
- lastProps = ReactDOMFiberTextarea.getHostProps(
656
- domElement,
657
- lastRawProps,
658
- );
659
- nextProps = ReactDOMFiberTextarea.getHostProps(
660
- domElement,
661
- nextRawProps,
662
- );
663
- updatePayload = [];
664
- break;
665
- default:
666
- lastProps = lastRawProps;
667
- nextProps = nextRawProps;
668
- if (
669
- typeof lastProps.onClick !== 'function' &&
670
- typeof nextProps.onClick === 'function'
671
- ) {
672
- // TODO: This cast may not be sound for SVG, MathML or custom elements.
673
- trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
673
+ if (propKey === STYLE) {
674
+ var lastStyle = lastProps[propKey];
675
+ for (styleName in lastStyle) {
676
+ if (lastStyle.hasOwnProperty(styleName)) {
677
+ if (!styleUpdates) {
678
+ styleUpdates = {};
679
+ }
680
+ styleUpdates[styleName] = '';
681
}
675
- break;
682
+ }
683
+ } else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) {
684
+ // Noop. This is handled by the clear text mechanism.
685
+ } else if (
686
+ propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
687
+ propKey === SUPPRESS_HYDRATION_WARNING
688
+ ) {
689
+ // Noop
690
+ } else if (propKey === AUTOFOCUS) {
691
+ // Noop. It doesn't work on updates anyway.
692
+ } else if (registrationNameModules.hasOwnProperty(propKey)) {
693
+ // This is a special case. If any listener updates we need to ensure
694
+ // that the "current" fiber pointer gets updated so we need a commit
695
+ // to update this element.
696
+ if (!updatePayload) {
697
+ updatePayload = [];
698
+ }
699
+ } else {
700
+ // For all other deleted properties we add it to the queue. We use
701
+ // the whitelist in the commit phase instead.
702
+ (updatePayload = updatePayload || []).push(propKey, null);
703
}
677
-
678
- assertValidProps(tag, nextProps, getStack);
679
-
680
- var propKey;
681
- var styleName;
682
- var styleUpdates = null;
683
- for (propKey in lastProps) {
684
- if (
685
- nextProps.hasOwnProperty(propKey) ||
686
- !lastProps.hasOwnProperty(propKey) ||
687
- lastProps[propKey] == null
688
- ) {
689
- continue;
704
+ }
705
+ for (propKey in nextProps) {
706
+ var nextProp = nextProps[propKey];
707
+ var lastProp = lastProps != null ? lastProps[propKey] : undefined;
708
+ if (
709
+ !nextProps.hasOwnProperty(propKey) ||
710
+ nextProp === lastProp ||
711
+ (nextProp == null && lastProp == null)
712
+ ) {
713
+ continue;
714
+ }
715
+ if (propKey === STYLE) {
716
+ if (__DEV__) {
717
+ if (nextProp) {
718
+ // Freeze the next style object so that we can assume it won't be
719
+ // mutated. We have already warned for this in the past.
720
+ Object.freeze(nextProp);
721
+ }
722
}
691
- if (propKey === STYLE) {
692
- var lastStyle = lastProps[propKey];
693
- for (styleName in lastStyle) {
694
- if (lastStyle.hasOwnProperty(styleName)) {
723
+ if (lastProp) {
724
+ // Unset styles on `lastProp` but not on `nextProp`.
725
+ for (styleName in lastProp) {
726
+ if (
727
+ lastProp.hasOwnProperty(styleName) &&
728
+ (!nextProp || !nextProp.hasOwnProperty(styleName))
729
+ ) {
730
if (!styleUpdates) {
731
styleUpdates = {};
732
}
733
styleUpdates[styleName] = '';
734
}
735
}
701
- } else if (
702
- propKey === DANGEROUSLY_SET_INNER_HTML ||
703
- propKey === CHILDREN
704
- ) {
705
- // Noop. This is handled by the clear text mechanism.
706
- } else if (
707
- propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
708
- propKey === SUPPRESS_HYDRATION_WARNING
709
- ) {
710
- // Noop
711
- } else if (propKey === AUTOFOCUS) {
712
- // Noop. It doesn't work on updates anyway.
713
- } else if (registrationNameModules.hasOwnProperty(propKey)) {
714
- // This is a special case. If any listener updates we need to ensure
715
- // that the "current" fiber pointer gets updated so we need a commit
716
- // to update this element.
717
- if (!updatePayload) {
718
- updatePayload = [];
719
- }
720
- } else {
721
- // For all other deleted properties we add it to the queue. We use
722
- // the whitelist in the commit phase instead.
723
- (updatePayload = updatePayload || []).push(propKey, null);
724
- }
725
- }
726
- for (propKey in nextProps) {
727
- var nextProp = nextProps[propKey];
728
- var lastProp = lastProps != null ? lastProps[propKey] : undefined;
729
- if (
730
- !nextProps.hasOwnProperty(propKey) ||
731
- nextProp === lastProp ||
732
- (nextProp == null && lastProp == null)
733
- ) {
734
- continue;
735
- }
736
- if (propKey === STYLE) {
737
- if (__DEV__) {
738
- if (nextProp) {
739
- // Freeze the next style object so that we can assume it won't be
740
- // mutated. We have already warned for this in the past.
741
- Object.freeze(nextProp);
742
- }
743
- }
744
- if (lastProp) {
745
- // Unset styles on `lastProp` but not on `nextProp`.
746
- for (styleName in lastProp) {
747
- if (
748
- lastProp.hasOwnProperty(styleName) &&
749
- (!nextProp || !nextProp.hasOwnProperty(styleName))
750
- ) {
751
- if (!styleUpdates) {
752
- styleUpdates = {};
753
- }
754
- styleUpdates[styleName] = '';
755
- }
756
- }
757
- // Update styles that changed since `lastProp`.
758
- for (styleName in nextProp) {
759
- if (
760
- nextProp.hasOwnProperty(styleName) &&
761
- lastProp[styleName] !== nextProp[styleName]
762
- ) {
763
- if (!styleUpdates) {
764
- styleUpdates = {};
765
- }
766
- styleUpdates[styleName] = nextProp[styleName];
767
- }
768
- }
769
- } else {
770
- // Relies on `updateStylesByID` not mutating `styleUpdates`.
771
- if (!styleUpdates) {
772
- if (!updatePayload) {
773
- updatePayload = [];
736
+ // Update styles that changed since `lastProp`.
737
+ for (styleName in nextProp) {
738
+ if (
739
+ nextProp.hasOwnProperty(styleName) &&
740
+ lastProp[styleName] !== nextProp[styleName]
741
+ ) {
742
+ if (!styleUpdates) {
743
+ styleUpdates = {};
744
}
775
- updatePayload.push(propKey, styleUpdates);
745
+ styleUpdates[styleName] = nextProp[styleName];
746
}
777
- styleUpdates = nextProp;
747
}
779
- } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
780
- var nextHtml = nextProp ? nextProp[HTML] : undefined;
781
- var lastHtml = lastProp ? lastProp[HTML] : undefined;
782
- if (nextHtml != null) {
783
- if (lastHtml !== nextHtml) {
784
- (updatePayload = updatePayload || []).push(propKey, '' + nextHtml);
748
+ } else {
749
+ // Relies on `updateStylesByID` not mutating `styleUpdates`.
750
+ if (!styleUpdates) {
751
+ if (!updatePayload) {
752
+ updatePayload = [];
753
}
786
- } else {
787
- // TODO: It might be too late to clear this if we have children
788
- // inserted already.
754
+ updatePayload.push(propKey, styleUpdates);
755
}
790
- } else if (propKey === CHILDREN) {
791
- if (
792
- lastProp !== nextProp &&
793
- (typeof nextProp === 'string' || typeof nextProp === 'number')
794
- ) {
795
- (updatePayload = updatePayload || []).push(propKey, '' + nextProp);
756
+ styleUpdates = nextProp;
757
+ }
758
+ } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
759
+ var nextHtml = nextProp ? nextProp[HTML] : undefined;
760
+ var lastHtml = lastProp ? lastProp[HTML] : undefined;
761
+ if (nextHtml != null) {
762
+ if (lastHtml !== nextHtml) {
763
+ (updatePayload = updatePayload || []).push(propKey, '' + nextHtml);
764
}
797
- } else if (
798
- propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
799
- propKey === SUPPRESS_HYDRATION_WARNING
765
+ } else {
766
+ // TODO: It might be too late to clear this if we have children
767
+ // inserted already.
768
+ }
769
+ } else if (propKey === CHILDREN) {
770
+ if (
771
+ lastProp !== nextProp &&
772
+ (typeof nextProp === 'string' || typeof nextProp === 'number')
773
) {
801
- // Noop
802
- } else if (registrationNameModules.hasOwnProperty(propKey)) {
803
- if (nextProp != null) {
804
- // We eagerly listen to this even though we haven't committed yet.
805
- if (__DEV__ && typeof nextProp !== 'function') {
806
- warnForInvalidEventListener(propKey, nextProp);
807
- }
808
- ensureListeningTo(rootContainerElement, propKey);
809
- }
810
- if (!updatePayload && lastProp !== nextProp) {
811
- // This is a special case. If any listener updates we need to ensure
812
- // that the "current" props pointer gets updated so we need a commit
813
- // to update this element.
814
- updatePayload = [];
774
+ (updatePayload = updatePayload || []).push(propKey, '' + nextProp);
775
+ }
776
+ } else if (
777
+ propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
778
+ propKey === SUPPRESS_HYDRATION_WARNING
779
+ ) {
780
+ // Noop
781
+ } else if (registrationNameModules.hasOwnProperty(propKey)) {
782
+ if (nextProp != null) {
783
+ // We eagerly listen to this even though we haven't committed yet.
784
+ if (__DEV__ && typeof nextProp !== 'function') {
785
+ warnForInvalidEventListener(propKey, nextProp);
786
}
816
- } else {
817
- // For any other property we always add it to the queue and then we
818
- // filter it out using the whitelist during the commit.
819
- (updatePayload = updatePayload || []).push(propKey, nextProp);
787
+ ensureListeningTo(rootContainerElement, propKey);
788
}
789
+ if (!updatePayload && lastProp !== nextProp) {
790
+ // This is a special case. If any listener updates we need to ensure
791
+ // that the "current" props pointer gets updated so we need a commit
792
+ // to update this element.
793
+ updatePayload = [];
794
+ }
795
+ } else {
796
+ // For any other property we always add it to the queue and then we
797
+ // filter it out using the whitelist during the commit.
798
+ (updatePayload = updatePayload || []).push(propKey, nextProp);
799
}
822
- if (styleUpdates) {
823
- (updatePayload = updatePayload || []).push(STYLE, styleUpdates);
824
- }
825
- return updatePayload;
826
- },
800
+ }
801
+ if (styleUpdates) {
802
+ (updatePayload = updatePayload || []).push(STYLE, styleUpdates);
803
+ }
804
+ return updatePayload;
805
+}
806
807
+// Apply the diff.
808
+export function updateProperties(
809
+ domElement: Element,
810
+ updatePayload: Array<any>,
811
+ tag: string,
812
+ lastRawProps: Object,
813
+ nextRawProps: Object,
814
+): void {
815
+ var wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
816
+ var isCustomComponentTag = isCustomComponent(tag, nextRawProps);
817
// Apply the diff.
829
- updateProperties(
830
- domElement: Element,
831
- updatePayload: Array<any>,
832
- tag: string,
833
- lastRawProps: Object,
834
- nextRawProps: Object,
835
- ): void {
836
- var wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
837
- var isCustomComponentTag = isCustomComponent(tag, nextRawProps);
838
- // Apply the diff.
839
- updateDOMProperties(
840
- domElement,
841
- updatePayload,
842
- wasCustomComponentTag,
843
- isCustomComponentTag,
844
- );
818
+ updateDOMProperties(
819
+ domElement,
820
+ updatePayload,
821
+ wasCustomComponentTag,
822
+ isCustomComponentTag,
823
+ );
824
846
- // TODO: Ensure that an update gets scheduled if any of the special props
847
- // changed.
848
- switch (tag) {
849
- case 'input':
850
- // Update the wrapper around inputs *after* updating props. This has to
851
- // happen after `updateDOMProperties`. Otherwise HTML5 input validations
852
- // raise warnings and prevent the new value from being assigned.
853
- ReactDOMFiberInput.updateWrapper(domElement, nextRawProps);
825
+ // TODO: Ensure that an update gets scheduled if any of the special props
826
+ // changed.
827
+ switch (tag) {
828
+ case 'input':
829
+ // Update the wrapper around inputs *after* updating props. This has to
830
+ // happen after `updateDOMProperties`. Otherwise HTML5 input validations
831
+ // raise warnings and prevent the new value from being assigned.
832
+ ReactDOMFiberInput.updateWrapper(domElement, nextRawProps);
833
855
- // We also check that we haven't missed a value update, such as a
856
- // Radio group shifting the checked value to another named radio input.
857
- inputValueTracking.updateValueIfChanged((domElement: any));
858
- break;
859
- case 'textarea':
860
- ReactDOMFiberTextarea.updateWrapper(domElement, nextRawProps);
861
- break;
862
- case 'select':
863
- // <select> value update needs to occur after <option> children
864
- // reconciliation
865
- ReactDOMFiberSelect.postUpdateWrapper(domElement, nextRawProps);
866
- break;
867
- }
868
- },
834
+ // We also check that we haven't missed a value update, such as a
835
+ // Radio group shifting the checked value to another named radio input.
836
+ inputValueTracking.updateValueIfChanged((domElement: any));
837
+ break;
838
+ case 'textarea':
839
+ ReactDOMFiberTextarea.updateWrapper(domElement, nextRawProps);
840
+ break;
841
+ case 'select':
842
+ // <select> value update needs to occur after <option> children
843
+ // reconciliation
844
+ ReactDOMFiberSelect.postUpdateWrapper(domElement, nextRawProps);
845
+ break;
846
+ }
847
+}
848
870
- diffHydratedProperties(
871
- domElement: Element,
872
- tag: string,
873
- rawProps: Object,
874
- parentNamespace: string,
875
- rootContainerElement: Element | Document,
876
- ): null | Array<mixed> {
877
- if (__DEV__) {
878
- var suppressHydrationWarning =
879
- rawProps[SUPPRESS_HYDRATION_WARNING] === true;
880
- var isCustomComponentTag = isCustomComponent(tag, rawProps);
881
- validatePropertiesInDevelopment(tag, rawProps);
882
- if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
883
- warning(
884
- false,
885
- '%s is using shady DOM. Using shady DOM with React can ' +
886
- 'cause things to break subtly.',
887
- getCurrentFiberOwnerName() || 'A component',
888
- );
889
- didWarnShadyDOM = true;
890
- }
849
+export function diffHydratedProperties(
850
+ domElement: Element,
851
+ tag: string,
852
+ rawProps: Object,
853
+ parentNamespace: string,
854
+ rootContainerElement: Element | Document,
855
+): null | Array<mixed> {
856
+ if (__DEV__) {
857
+ var suppressHydrationWarning =
858
+ rawProps[SUPPRESS_HYDRATION_WARNING] === true;
859
+ var isCustomComponentTag = isCustomComponent(tag, rawProps);
860
+ validatePropertiesInDevelopment(tag, rawProps);
861
+ if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
862
+ warning(
863
+ false,
864
+ '%s is using shady DOM. Using shady DOM with React can ' +
865
+ 'cause things to break subtly.',
866
+ getCurrentFiberOwnerName() || 'A component',
867
+ );
868
+ didWarnShadyDOM = true;
869
}
870
+ }
871
893
- // TODO: Make sure that we check isMounted before firing any of these events.
894
- switch (tag) {
895
- case 'iframe':
896
- case 'object':
897
- ReactBrowserEventEmitter.trapBubbledEvent(
898
- 'topLoad',
899
- 'load',
900
- domElement,
901
- );
902
- break;
903
- case 'video':
904
- case 'audio':
905
- // Create listener for each media event
906
- for (var event in mediaEvents) {
907
- if (mediaEvents.hasOwnProperty(event)) {
908
- ReactBrowserEventEmitter.trapBubbledEvent(
909
- event,
910
- mediaEvents[event],
911
- domElement,
912
- );
913
- }
872
+ // TODO: Make sure that we check isMounted before firing any of these events.
873
+ switch (tag) {
874
+ case 'iframe':
875
+ case 'object':
876
+ ReactBrowserEventEmitter.trapBubbledEvent('topLoad', 'load', domElement);
877
+ break;
878
+ case 'video':
879
+ case 'audio':
880
+ // Create listener for each media event
881
+ for (var event in mediaEvents) {
882
+ if (mediaEvents.hasOwnProperty(event)) {
883
+ ReactBrowserEventEmitter.trapBubbledEvent(
884
+ event,
885
+ mediaEvents[event],
886
+ domElement,
887
+ );
888
}
915
- break;
916
- case 'source':
917
- ReactBrowserEventEmitter.trapBubbledEvent(
918
- 'topError',
919
- 'error',
920
- domElement,
921
- );
922
- break;
923
- case 'img':
924
- case 'image':
925
- ReactBrowserEventEmitter.trapBubbledEvent(
926
- 'topError',
927
- 'error',
928
- domElement,
929
- );
930
- ReactBrowserEventEmitter.trapBubbledEvent(
931
- 'topLoad',
932
- 'load',
933
- domElement,
934
- );
935
- break;
936
- case 'form':
937
- ReactBrowserEventEmitter.trapBubbledEvent(
938
- 'topReset',
939
- 'reset',
940
- domElement,
941
- );
942
- ReactBrowserEventEmitter.trapBubbledEvent(
943
- 'topSubmit',
944
- 'submit',
945
- domElement,
946
- );
947
- break;
948
- case 'details':
949
- ReactBrowserEventEmitter.trapBubbledEvent(
950
- 'topToggle',
951
- 'toggle',
952
- domElement,
953
- );
954
- break;
955
- case 'input':
956
- ReactDOMFiberInput.initWrapperState(domElement, rawProps);
957
- ReactBrowserEventEmitter.trapBubbledEvent(
958
- 'topInvalid',
959
- 'invalid',
960
- domElement,
961
- );
962
- // For controlled components we always need to ensure we're listening
963
- // to onChange. Even if there is no listener.
964
- ensureListeningTo(rootContainerElement, 'onChange');
965
- break;
966
- case 'option':
967
- ReactDOMFiberOption.validateProps(domElement, rawProps);
968
- break;
969
- case 'select':
970
- ReactDOMFiberSelect.initWrapperState(domElement, rawProps);
971
- ReactBrowserEventEmitter.trapBubbledEvent(
972
- 'topInvalid',
973
- 'invalid',
974
- domElement,
975
- );
976
- // For controlled components we always need to ensure we're listening
977
- // to onChange. Even if there is no listener.
978
- ensureListeningTo(rootContainerElement, 'onChange');
979
- break;
980
- case 'textarea':
981
- ReactDOMFiberTextarea.initWrapperState(domElement, rawProps);
982
- ReactBrowserEventEmitter.trapBubbledEvent(
983
- 'topInvalid',
984
- 'invalid',
985
- domElement,
986
- );
987
- // For controlled components we always need to ensure we're listening
988
- // to onChange. Even if there is no listener.
989
- ensureListeningTo(rootContainerElement, 'onChange');
990
- break;
991
- }
889
+ }
890
+ break;
891
+ case 'source':
892
+ ReactBrowserEventEmitter.trapBubbledEvent(
893
+ 'topError',
894
+ 'error',
895
+ domElement,
896
+ );
897
+ break;
898
+ case 'img':
899
+ case 'image':
900
+ ReactBrowserEventEmitter.trapBubbledEvent(
901
+ 'topError',
902
+ 'error',
903
+ domElement,
904
+ );
905
+ ReactBrowserEventEmitter.trapBubbledEvent('topLoad', 'load', domElement);
906
+ break;
907
+ case 'form':
908
+ ReactBrowserEventEmitter.trapBubbledEvent(
909
+ 'topReset',
910
+ 'reset',
911
+ domElement,
912
+ );
913
+ ReactBrowserEventEmitter.trapBubbledEvent(
914
+ 'topSubmit',
915
+ 'submit',
916
+ domElement,
917
+ );
918
+ break;
919
+ case 'details':
920
+ ReactBrowserEventEmitter.trapBubbledEvent(
921
+ 'topToggle',
922
+ 'toggle',
923
+ domElement,
924
+ );
925
+ break;
926
+ case 'input':
927
+ ReactDOMFiberInput.initWrapperState(domElement, rawProps);
928
+ ReactBrowserEventEmitter.trapBubbledEvent(
929
+ 'topInvalid',
930
+ 'invalid',
931
+ domElement,
932
+ );
933
+ // For controlled components we always need to ensure we're listening
934
+ // to onChange. Even if there is no listener.
935
+ ensureListeningTo(rootContainerElement, 'onChange');
936
+ break;
937
+ case 'option':
938
+ ReactDOMFiberOption.validateProps(domElement, rawProps);
939
+ break;
940
+ case 'select':
941
+ ReactDOMFiberSelect.initWrapperState(domElement, rawProps);
942
+ ReactBrowserEventEmitter.trapBubbledEvent(
943
+ 'topInvalid',
944
+ 'invalid',
945
+ domElement,
946
+ );
947
+ // For controlled components we always need to ensure we're listening
948
+ // to onChange. Even if there is no listener.
949
+ ensureListeningTo(rootContainerElement, 'onChange');
950
+ break;
951
+ case 'textarea':
952
+ ReactDOMFiberTextarea.initWrapperState(domElement, rawProps);
953
+ ReactBrowserEventEmitter.trapBubbledEvent(
954
+ 'topInvalid',
955
+ 'invalid',
956
+ domElement,
957
+ );
958
+ // For controlled components we always need to ensure we're listening
959
+ // to onChange. Even if there is no listener.
960
+ ensureListeningTo(rootContainerElement, 'onChange');
961
+ break;
962
+ }
963
993
- assertValidProps(tag, rawProps, getStack);
964
+ assertValidProps(tag, rawProps, getStack);
965
995
- if (__DEV__) {
996
- var extraAttributeNames: Set<string> = new Set();
997
- var attributes = domElement.attributes;
998
- for (var i = 0; i < attributes.length; i++) {
999
- var name = attributes[i].name.toLowerCase();
1000
- switch (name) {
1001
- // Built-in SSR attribute is whitelisted
1002
- case 'data-reactroot':
1003
- break;
1004
- // Controlled attributes are not validated
1005
- // TODO: Only ignore them on controlled tags.
1006
- case 'value':
1007
- break;
1008
- case 'checked':
1009
- break;
1010
- case 'selected':
1011
- break;
1012
- default:
1013
- // Intentionally use the original name.
1014
- // See discussion in https://github.com/facebook/react/pull/10676.
1015
- extraAttributeNames.add(attributes[i].name);
1016
- }
966
+ if (__DEV__) {
967
+ var extraAttributeNames: Set<string> = new Set();
968
+ var attributes = domElement.attributes;
969
+ for (var i = 0; i < attributes.length; i++) {
970
+ var name = attributes[i].name.toLowerCase();
971
+ switch (name) {
972
+ // Built-in SSR attribute is whitelisted
973
+ case 'data-reactroot':
974
+ break;
975
+ // Controlled attributes are not validated
976
+ // TODO: Only ignore them on controlled tags.
977
+ case 'value':
978
+ break;
979
+ case 'checked':
980
+ break;
981
+ case 'selected':
982
+ break;
983
+ default:
984
+ // Intentionally use the original name.
985
+ // See discussion in https://github.com/facebook/react/pull/10676.
986
+ extraAttributeNames.add(attributes[i].name);
987
}
988
}
989
+ }
990
1020
- var updatePayload = null;
1021
- for (var propKey in rawProps) {
1022
- if (!rawProps.hasOwnProperty(propKey)) {
1023
- continue;
1024
- }
1025
- var nextProp = rawProps[propKey];
1026
- if (propKey === CHILDREN) {
1027
- // For text content children we compare against textContent. This
1028
- // might match additional HTML that is hidden when we read it using
1029
- // textContent. E.g. "foo" will match "f<span>oo</span>" but that still
1030
- // satisfies our requirement. Our requirement is not to produce perfect
1031
- // HTML and attributes. Ideally we should preserve structure but it's
1032
- // ok not to if the visible content is still enough to indicate what
1033
- // even listeners these nodes might be wired up to.
1034
- // TODO: Warn if there is more than a single textNode as a child.
1035
- // TODO: Should we use domElement.firstChild.nodeValue to compare?
1036
- if (typeof nextProp === 'string') {
1037
- if (domElement.textContent !== nextProp) {
1038
- if (__DEV__ && !suppressHydrationWarning) {
1039
- warnForTextDifference(domElement.textContent, nextProp);
1040
- }
1041
- updatePayload = [CHILDREN, nextProp];
1042
- }
1043
- } else if (typeof nextProp === 'number') {
1044
- if (domElement.textContent !== '' + nextProp) {
1045
- if (__DEV__ && !suppressHydrationWarning) {
1046
- warnForTextDifference(domElement.textContent, nextProp);
1047
- }
1048
- updatePayload = [CHILDREN, '' + nextProp];
991
+ var updatePayload = null;
992
+ for (var propKey in rawProps) {
993
+ if (!rawProps.hasOwnProperty(propKey)) {
994
+ continue;
995
+ }
996
+ var nextProp = rawProps[propKey];
997
+ if (propKey === CHILDREN) {
998
+ // For text content children we compare against textContent. This
999
+ // might match additional HTML that is hidden when we read it using
1000
+ // textContent. E.g. "foo" will match "f<span>oo</span>" but that still
1001
+ // satisfies our requirement. Our requirement is not to produce perfect
1002
+ // HTML and attributes. Ideally we should preserve structure but it's
1003
+ // ok not to if the visible content is still enough to indicate what
1004
+ // even listeners these nodes might be wired up to.
1005
+ // TODO: Warn if there is more than a single textNode as a child.
1006
+ // TODO: Should we use domElement.firstChild.nodeValue to compare?
1007
+ if (typeof nextProp === 'string') {
1008
+ if (domElement.textContent !== nextProp) {
1009
+ if (__DEV__ && !suppressHydrationWarning) {
1010
+ warnForTextDifference(domElement.textContent, nextProp);
1011
}
1012
+ updatePayload = [CHILDREN, nextProp];
1013
}
1051
- } else if (registrationNameModules.hasOwnProperty(propKey)) {
1052
- if (nextProp != null) {
1053
- if (__DEV__ && typeof nextProp !== 'function') {
1054
- warnForInvalidEventListener(propKey, nextProp);
1014
+ } else if (typeof nextProp === 'number') {
1015
+ if (domElement.textContent !== '' + nextProp) {
1016
+ if (__DEV__ && !suppressHydrationWarning) {
1017
+ warnForTextDifference(domElement.textContent, nextProp);
1018
}
1056
- ensureListeningTo(rootContainerElement, propKey);
1019
+ updatePayload = [CHILDREN, '' + nextProp];
1020
}
1058
- } else if (__DEV__) {
1059
- // Validate that the properties correspond to their expected values.
1060
- var serverValue;
1061
- var propertyInfo;
1062
- if (suppressHydrationWarning) {
1063
- // Don't bother comparing. We're ignoring all these warnings.
1064
- } else if (
1065
- propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
1066
- propKey === SUPPRESS_HYDRATION_WARNING ||
1067
- // Controlled attributes are not validated
1068
- // TODO: Only ignore them on controlled tags.
1069
- propKey === 'value' ||
1070
- propKey === 'checked' ||
1071
- propKey === 'selected'
1072
- ) {
1073
- // Noop
1074
- } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
1075
- const rawHtml = nextProp ? nextProp[HTML] || '' : '';
1076
- const serverHTML = domElement.innerHTML;
1077
- const expectedHTML = normalizeHTML(domElement, rawHtml);
1078
- if (expectedHTML !== serverHTML) {
1079
- warnForPropDifference(propKey, serverHTML, expectedHTML);
1080
- }
1081
- } else if (propKey === STYLE) {
1082
- // $FlowFixMe - Should be inferred as not undefined.
1083
- extraAttributeNames.delete(propKey);
1084
- const expectedStyle = CSSPropertyOperations.createDangerousStringForStyles(
1085
- nextProp,
1086
- );
1087
- serverValue = domElement.getAttribute('style');
1088
- if (expectedStyle !== serverValue) {
1089
- warnForPropDifference(propKey, serverValue, expectedStyle);
1090
- }
1091
- } else if (isCustomComponentTag) {
1021
+ }
1022
+ } else if (registrationNameModules.hasOwnProperty(propKey)) {
1023
+ if (nextProp != null) {
1024
+ if (__DEV__ && typeof nextProp !== 'function') {
1025
+ warnForInvalidEventListener(propKey, nextProp);
1026
+ }
1027
+ ensureListeningTo(rootContainerElement, propKey);
1028
+ }
1029
+ } else if (__DEV__) {
1030
+ // Validate that the properties correspond to their expected values.
1031
+ var serverValue;
1032
+ var propertyInfo;
1033
+ if (suppressHydrationWarning) {
1034
+ // Don't bother comparing. We're ignoring all these warnings.
1035
+ } else if (
1036
+ propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
1037
+ propKey === SUPPRESS_HYDRATION_WARNING ||
1038
+ // Controlled attributes are not validated
1039
+ // TODO: Only ignore them on controlled tags.
1040
+ propKey === 'value' ||
1041
+ propKey === 'checked' ||
1042
+ propKey === 'selected'
1043
+ ) {
1044
+ // Noop
1045
+ } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
1046
+ const rawHtml = nextProp ? nextProp[HTML] || '' : '';
1047
+ const serverHTML = domElement.innerHTML;
1048
+ const expectedHTML = normalizeHTML(domElement, rawHtml);
1049
+ if (expectedHTML !== serverHTML) {
1050
+ warnForPropDifference(propKey, serverHTML, expectedHTML);
1051
+ }
1052
+ } else if (propKey === STYLE) {
1053
+ // $FlowFixMe - Should be inferred as not undefined.
1054
+ extraAttributeNames.delete(propKey);
1055
+ const expectedStyle = CSSPropertyOperations.createDangerousStringForStyles(
1056
+ nextProp,
1057
+ );
1058
+ serverValue = domElement.getAttribute('style');
1059
+ if (expectedStyle !== serverValue) {
1060
+ warnForPropDifference(propKey, serverValue, expectedStyle);
1061
+ }
1062
+ } else if (isCustomComponentTag) {
1063
+ // $FlowFixMe - Should be inferred as not undefined.
1064
+ extraAttributeNames.delete(propKey.toLowerCase());
1065
+ serverValue = DOMPropertyOperations.getValueForAttribute(
1066
+ domElement,
1067
+ propKey,
1068
+ nextProp,
1069
+ );
1070
+
1071
+ if (nextProp !== serverValue) {
1072
+ warnForPropDifference(propKey, serverValue, nextProp);
1073
+ }
1074
+ } else if (DOMProperty.shouldSetAttribute(propKey, nextProp)) {
1075
+ if ((propertyInfo = DOMProperty.getPropertyInfo(propKey))) {
1076
// $FlowFixMe - Should be inferred as not undefined.
1093
- extraAttributeNames.delete(propKey.toLowerCase());
1094
- serverValue = DOMPropertyOperations.getValueForAttribute(
1077
+ extraAttributeNames.delete(propertyInfo.attributeName);
1078
+ serverValue = DOMPropertyOperations.getValueForProperty(
1079
domElement,
1080
propKey,
1081
nextProp,
1082
);
1099
-
1100
- if (nextProp !== serverValue) {
1101
- warnForPropDifference(propKey, serverValue, nextProp);
1083
+ } else {
1084
+ let ownNamespace = parentNamespace;
1085
+ if (ownNamespace === HTML_NAMESPACE) {
1086
+ ownNamespace = getIntrinsicNamespace(tag);
1087
}
1103
- } else if (DOMProperty.shouldSetAttribute(propKey, nextProp)) {
1104
- if ((propertyInfo = DOMProperty.getPropertyInfo(propKey))) {
1088
+ if (ownNamespace === HTML_NAMESPACE) {
1089
// $FlowFixMe - Should be inferred as not undefined.
1106
- extraAttributeNames.delete(propertyInfo.attributeName);
1107
- serverValue = DOMPropertyOperations.getValueForProperty(
1108
- domElement,
1109
- propKey,
1110
- nextProp,
1111
- );
1090
+ extraAttributeNames.delete(propKey.toLowerCase());
1091
} else {
1113
- let ownNamespace = parentNamespace;
1114
- if (ownNamespace === HTML_NAMESPACE) {
1115
- ownNamespace = getIntrinsicNamespace(tag);
1116
- }
1117
- if (ownNamespace === HTML_NAMESPACE) {
1118
- // $FlowFixMe - Should be inferred as not undefined.
1119
- extraAttributeNames.delete(propKey.toLowerCase());
1120
- } else {
1121
- // $FlowFixMe - Should be inferred as not undefined.
1122
- extraAttributeNames.delete(propKey);
1123
- }
1124
- serverValue = DOMPropertyOperations.getValueForAttribute(
1125
- domElement,
1126
- propKey,
1127
- nextProp,
1128
- );
1092
+ // $FlowFixMe - Should be inferred as not undefined.
1093
+ extraAttributeNames.delete(propKey);
1094
}
1095
+ serverValue = DOMPropertyOperations.getValueForAttribute(
1096
+ domElement,
1097
+ propKey,
1098
+ nextProp,
1099
+ );
1100
+ }
1101
1131
- if (nextProp !== serverValue) {
1132
- warnForPropDifference(propKey, serverValue, nextProp);
1133
- }
1102
+ if (nextProp !== serverValue) {
1103
+ warnForPropDifference(propKey, serverValue, nextProp);
1104
}
1105
}
1106
}
1107
+ }
1108
1138
- if (__DEV__) {
1109
+ if (__DEV__) {
1110
+ // $FlowFixMe - Should be inferred as not undefined.
1111
+ if (extraAttributeNames.size > 0 && !suppressHydrationWarning) {
1112
// $FlowFixMe - Should be inferred as not undefined.
1140
- if (extraAttributeNames.size > 0 && !suppressHydrationWarning) {
1141
- // $FlowFixMe - Should be inferred as not undefined.
1142
- warnForExtraAttributes(extraAttributeNames);
1143
- }
1113
+ warnForExtraAttributes(extraAttributeNames);
1114
}
1115
+ }
1116
1146
- switch (tag) {
1147
- case 'input':
1148
- // TODO: Make sure we check if this is still unmounted or do any clean
1149
- // up necessary since we never stop tracking anymore.
1150
- inputValueTracking.track((domElement: any));
1151
- ReactDOMFiberInput.postMountWrapper(domElement, rawProps);
1152
- break;
1153
- case 'textarea':
1154
- // TODO: Make sure we check if this is still unmounted or do any clean
1155
- // up necessary since we never stop tracking anymore.
1156
- inputValueTracking.track((domElement: any));
1157
- ReactDOMFiberTextarea.postMountWrapper(domElement, rawProps);
1158
- break;
1159
- case 'select':
1160
- case 'option':
1161
- // For input and textarea we current always set the value property at
1162
- // post mount to force it to diverge from attributes. However, for
1163
- // option and select we don't quite do the same thing and select
1164
- // is not resilient to the DOM state changing so we don't do that here.
1165
- // TODO: Consider not doing this for input and textarea.
1166
- break;
1167
- default:
1168
- if (typeof rawProps.onClick === 'function') {
1169
- // TODO: This cast may not be sound for SVG, MathML or custom elements.
1170
- trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
1171
- }
1172
- break;
1173
- }
1117
+ switch (tag) {
1118
+ case 'input':
1119
+ // TODO: Make sure we check if this is still unmounted or do any clean
1120
+ // up necessary since we never stop tracking anymore.
1121
+ inputValueTracking.track((domElement: any));
1122
+ ReactDOMFiberInput.postMountWrapper(domElement, rawProps);
1123
+ break;
1124
+ case 'textarea':
1125
+ // TODO: Make sure we check if this is still unmounted or do any clean
1126
+ // up necessary since we never stop tracking anymore.
1127
+ inputValueTracking.track((domElement: any));
1128
+ ReactDOMFiberTextarea.postMountWrapper(domElement, rawProps);
1129
+ break;
1130
+ case 'select':
1131
+ case 'option':
1132
+ // For input and textarea we current always set the value property at
1133
+ // post mount to force it to diverge from attributes. However, for
1134
+ // option and select we don't quite do the same thing and select
1135
+ // is not resilient to the DOM state changing so we don't do that here.
1136
+ // TODO: Consider not doing this for input and textarea.
1137
+ break;
1138
+ default:
1139
+ if (typeof rawProps.onClick === 'function') {
1140
+ // TODO: This cast may not be sound for SVG, MathML or custom elements.
1141
+ trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
1142
+ }
1143
+ break;
1144
+ }
1145
1175
- return updatePayload;
1176
- },
1146
+ return updatePayload;
1147
+}
1148
1178
- diffHydratedText(textNode: Text, text: string): boolean {
1179
- const isDifferent = textNode.nodeValue !== text;
1180
- return isDifferent;
1181
- },
1149
+export function diffHydratedText(textNode: Text, text: string): boolean {
1150
+ const isDifferent = textNode.nodeValue !== text;
1151
+ return isDifferent;
1152
+}
1153
1183
- warnForUnmatchedText(textNode: Text, text: string) {
1184
- if (__DEV__) {
1185
- warnForTextDifference(textNode.nodeValue, text);
1186
- }
1187
- },
1154
+export function warnForUnmatchedText(textNode: Text, text: string) {
1155
+ if (__DEV__) {
1156
+ warnForTextDifference(textNode.nodeValue, text);
1157
+ }
1158
+}
1159
1189
- warnForDeletedHydratableElement(
1190
- parentNode: Element | Document,
1191
- child: Element,
1192
- ) {
1193
- if (__DEV__) {
1194
- if (didWarnInvalidHydration) {
1195
- return;
1196
- }
1197
- didWarnInvalidHydration = true;
1198
- warning(
1199
- false,
1200
- 'Did not expect server HTML to contain a <%s> in <%s>.',
1201
- child.nodeName.toLowerCase(),
1202
- parentNode.nodeName.toLowerCase(),
1203
- );
1160
+export function warnForDeletedHydratableElement(
1161
+ parentNode: Element | Document,
1162
+ child: Element,
1163
+) {
1164
+ if (__DEV__) {
1165
+ if (didWarnInvalidHydration) {
1166
+ return;
1167
}
1205
- },
1168
+ didWarnInvalidHydration = true;
1169
+ warning(
1170
+ false,
1171
+ 'Did not expect server HTML to contain a <%s> in <%s>.',
1172
+ child.nodeName.toLowerCase(),
1173
+ parentNode.nodeName.toLowerCase(),
1174
+ );
1175
+ }
1176
+}
1177
1207
- warnForDeletedHydratableText(parentNode: Element | Document, child: Text) {
1208
- if (__DEV__) {
1209
- if (didWarnInvalidHydration) {
1210
- return;
1211
- }
1212
- didWarnInvalidHydration = true;
1213
- warning(
1214
- false,
1215
- 'Did not expect server HTML to contain the text node "%s" in <%s>.',
1216
- child.nodeValue,
1217
- parentNode.nodeName.toLowerCase(),
1218
- );
1178
+export function warnForDeletedHydratableText(
1179
+ parentNode: Element | Document,
1180
+ child: Text,
1181
+) {
1182
+ if (__DEV__) {
1183
+ if (didWarnInvalidHydration) {
1184
+ return;
1185
}
1220
- },
1186
+ didWarnInvalidHydration = true;
1187
+ warning(
1188
+ false,
1189
+ 'Did not expect server HTML to contain the text node "%s" in <%s>.',
1190
+ child.nodeValue,
1191
+ parentNode.nodeName.toLowerCase(),
1192
+ );
1193
+ }
1194
+}
1195
1222
- warnForInsertedHydratedElement(
1223
- parentNode: Element | Document,
1224
- tag: string,
1225
- props: Object,
1226
- ) {
1227
- if (__DEV__) {
1228
- if (didWarnInvalidHydration) {
1229
- return;
1230
- }
1231
- didWarnInvalidHydration = true;
1232
- warning(
1233
- false,
1234
- 'Expected server HTML to contain a matching <%s> in <%s>.',
1235
- tag,
1236
- parentNode.nodeName.toLowerCase(),
1237
- );
1196
+export function warnForInsertedHydratedElement(
1197
+ parentNode: Element | Document,
1198
+ tag: string,
1199
+ props: Object,
1200
+) {
1201
+ if (__DEV__) {
1202
+ if (didWarnInvalidHydration) {
1203
+ return;
1204
}
1239
- },
1205
+ didWarnInvalidHydration = true;
1206
+ warning(
1207
+ false,
1208
+ 'Expected server HTML to contain a matching <%s> in <%s>.',
1209
+ tag,
1210
+ parentNode.nodeName.toLowerCase(),
1211
+ );
1212
+ }
1213
+}
1214
1241
- warnForInsertedHydratedText(parentNode: Element | Document, text: string) {
1242
- if (__DEV__) {
1243
- if (text === '') {
1244
- // We expect to insert empty text nodes since they're not represented in
1245
- // the HTML.
1246
- // TODO: Remove this special case if we can just avoid inserting empty
1247
- // text nodes.
1248
- return;
1249
- }
1250
- if (didWarnInvalidHydration) {
1251
- return;
1252
- }
1253
- didWarnInvalidHydration = true;
1254
- warning(
1255
- false,
1256
- 'Expected server HTML to contain a matching text node for "%s" in <%s>.',
1257
- text,
1258
- parentNode.nodeName.toLowerCase(),
1259
- );
1215
+export function warnForInsertedHydratedText(
1216
+ parentNode: Element | Document,
1217
+ text: string,
1218
+) {
1219
+ if (__DEV__) {
1220
+ if (text === '') {
1221
+ // We expect to insert empty text nodes since they're not represented in
1222
+ // the HTML.
1223
+ // TODO: Remove this special case if we can just avoid inserting empty
1224
+ // text nodes.
1225
+ return;
1226
}
1261
- },
1262
-
1263
- restoreControlledState(
1264
- domElement: Element,
1265
- tag: string,
1266
- props: Object,
1267
- ): void {
1268
- switch (tag) {
1269
- case 'input':
1270
- ReactDOMFiberInput.restoreControlledState(domElement, props);
1271
- return;
1272
- case 'textarea':
1273
- ReactDOMFiberTextarea.restoreControlledState(domElement, props);
1274
- return;
1275
- case 'select':
1276
- ReactDOMFiberSelect.restoreControlledState(domElement, props);
1277
- return;
1227
+ if (didWarnInvalidHydration) {
1228
+ return;
1229
}
1279
- },
1280
-};
1230
+ didWarnInvalidHydration = true;
1231
+ warning(
1232
+ false,
1233
+ 'Expected server HTML to contain a matching text node for "%s" in <%s>.',
1234
+ text,
1235
+ parentNode.nodeName.toLowerCase(),
1236
+ );
1237
+ }
1238
+}
1239
1282
-module.exports = ReactDOMFiberComponent;
1240
+export function restoreControlledState(
1241
+ domElement: Element,
1242
+ tag: string,
1243
+ props: Object,
1244
+): void {
1245
+ switch (tag) {
1246
+ case 'input':
1247
+ ReactDOMFiberInput.restoreControlledState(domElement, props);
1248
+ return;
1249
+ case 'textarea':
1250
+ ReactDOMFiberTextarea.restoreControlledState(domElement, props);
1251
+ return;
1252
+ case 'select':
1253
+ ReactDOMFiberSelect.restoreControlledState(domElement, props);
1254
+ return;
1255
+ }
1256
+}