Merge pull request #8347 from sebmarkbage/fiberdom

[Fiber] Handle controlled components
This commit is contained in:
Sebastian Markbåge
2016-11-18 18:35:26 +00:00
committed by GitHub
10 changed files with 237 additions and 196 deletions
-83
View File
@@ -14,15 +14,6 @@ src/addons/__tests__/renderSubtreeIntoContainer-test.js
src/isomorphic/classic/__tests__/ReactContextValidator-test.js
* should pass previous context to lifecycles
src/isomorphic/classic/class/__tests__/ReactBind-test.js
* Holds reference to instance
* works with mixins
src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js
* should work with manual binding
* works with mixins that have not opted out of autobinding
* works with mixins that have opted out of autobinding
src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
* includes the owner name when passing null, undefined, boolean, or number
@@ -47,15 +38,10 @@ src/renderers/dom/shared/__tests__/ReactBrowserEventEmitter-test.js
* should bubble simply
* should continue bubbling if an error is thrown
* should set currentTarget
* should support stopPropagation()
* should stop after first dispatch if stopPropagation
* should not stopPropagation if false is returned
* should invoke handlers that were removed while bubbling
* should not invoke newly inserted handlers while bubbling
* should have mouse enter simulated by test utils
* should infer onTouchTap from a touchStart/End
* should infer onTouchTap from when dragging below threshold
* should not onTouchTap from when dragging beyond threshold
* should bubble onTouchTap
src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
@@ -63,8 +49,6 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
* should warn when mutating style
* should empty element when removing innerHTML
* should transition from innerHTML to children in nested el
* should track input values
* should track textarea values
* should warn for children on void elements
* should report component containing invalid styles
* should clean up input value tracking
@@ -81,72 +65,28 @@ src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
* can reconcile text arbitrarily split into multiple nodes on some substitutions only
src/renderers/dom/shared/__tests__/ReactEventIndependence-test.js
* does not crash with other react inside
* does not crash with other react outside
* does not when event fired on unmounted tree
src/renderers/dom/shared/__tests__/ReactEventListener-test.js
* should batch between handlers from different roots
* should not fire duplicate events for a React DOM tree
src/renderers/dom/shared/__tests__/inputValueTracking-test.js
* should return tracker from node
src/renderers/dom/shared/eventPlugins/__tests__/ChangeEventPlugin-test.js
* should fire change for checkbox input
* should catch setting the value programmatically
* should not fire change when setting the value programmatically
* should not fire change when setting checked programmatically
* should only fire change for checked radio button once
* should deduplicate input value change events
* should listen for both change and input events when supported
* should only fire events when the value changes for range inputs
src/renderers/dom/shared/eventPlugins/__tests__/SelectEventPlugin-test.js
* should skip extraction if no listeners are present
* should extract if an `onSelect` listener is present
src/renderers/dom/shared/eventPlugins/__tests__/SimpleEventPlugin-test.js
* A non-interactive tags click when disabled
* A non-interactive tags clicks bubble when disabled
* should forward clicks when it starts out not disabled
* should not forward clicks when it starts out disabled
* should forward clicks when it becomes not disabled
* should not forward clicks when it becomes disabled
* should work correctly if the listener is changed
* should forward clicks when it starts out not disabled
* should not forward clicks when it starts out disabled
* should forward clicks when it becomes not disabled
* should not forward clicks when it becomes disabled
* should work correctly if the listener is changed
* should forward clicks when it starts out not disabled
* should not forward clicks when it starts out disabled
* should forward clicks when it becomes not disabled
* should not forward clicks when it becomes disabled
* should work correctly if the listener is changed
* should forward clicks when it starts out not disabled
* should not forward clicks when it starts out disabled
* should forward clicks when it becomes not disabled
* should not forward clicks when it becomes disabled
* should work correctly if the listener is changed
src/renderers/dom/shared/wrappers/__tests__/ReactDOMIframe-test.js
* should trigger load events
src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
* should properly control a value even if no event listener exists
* should control a value in reentrant events
* should control values in reentrant events with different targets
* should update `defaultValue` for uncontrolled input
* should update `defaultValue` for uncontrolled date/time input
* should properly control a value of number `0`
* should have the correct target value
* should control radio buttons
* should control radio buttons if the tree updates during render
* should have a this value of undefined if bind is not used
* should update defaultValue to empty string
* sets type, step, min, max before value always
* resets value of date/time input to fix bugs in iOS Safari
src/renderers/dom/shared/wrappers/__tests__/ReactDOMOption-test.js
* should allow ignoring `value` on option
@@ -158,28 +98,13 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMSelect-test.js
* should allow setting `value`
* should allow setting `value` with multiple
* should not select other options automatically
* should reset child options selected when they are changed and `value` is set
* should allow setting `value` with `objectToString`
* should allow switching to multiple
* should allow switching from multiple
* should remember value when switching to uncontrolled
* should remember updated value when switching to uncontrolled
* should not control defaultValue if readding options
* should refresh state on change
* should be able to safely remove select onChange
* should select grandchild options nested inside an optgroup
src/renderers/dom/shared/wrappers/__tests__/ReactDOMTextarea-test.js
* should set defaultValue
* should update defaultValue to empty string
* should allow setting `value` to `giraffe`
* should allow setting `value` to `true`
* should allow setting `value` to `false`
* should allow setting `value` to `objToString`
* should not incur unnecessary DOM mutations
* should properly control a value of number `0`
* should keep value when switching to uncontrolled element if changed
src/renderers/dom/stack/client/__tests__/ReactDOM-test.js
* throws in render() if the mount callback is not a function
* throws in render() if the update callback is not a function
@@ -312,9 +237,6 @@ src/renderers/shared/hooks/__tests__/ReactHostOperationHistoryHook-test.js
* gets reported when a child is inserted
* gets reported when a child is removed
src/renderers/shared/shared/event/__tests__/EventPluginHub-test.js
* should not prevent null listeners, at dispatch
src/renderers/shared/stack/reconciler/__tests__/ReactComponent-test.js
* should throw on invalid render targets
* throws usefully when rendering badly-typed elements
@@ -324,7 +246,6 @@ src/renderers/shared/stack/reconciler/__tests__/ReactComponentLifeCycle-test.js
src/renderers/shared/stack/reconciler/__tests__/ReactCompositeComponent-test.js
* should not thrash a server rendered layout with client side one
* should react to state changes from callbacks
* should warn about `forceUpdate` on unmounted components
* should warn about `setState` on unmounted components
* should warn about `setState` in render
@@ -365,7 +286,3 @@ src/renderers/shared/stack/reconciler/__tests__/refs-test.js
src/test/__tests__/ReactTestUtils-test.js
* should support injected wrapper components as DOM components
* should change the value of an input field
* should change the value of an input field in a component
* should not warn when simulating events with extra properties
* should set the type of the event
@@ -27,7 +27,6 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
* should warn for unknown prop
* should group multiple unknown prop warnings together
* should warn for onDblClick prop
* should work error event on <source> element
* should emit a warning once for a named custom component using shady DOM
* should not warn when server-side rendering `onScroll`
* warns on invalid nesting
@@ -48,17 +47,8 @@ src/renderers/dom/shared/__tests__/ReactDOMInvalidARIAHook-test.js
src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
* should warn if value is null
* should warn if controlled input switches to uncontrolled (value is undefined)
* should warn if controlled input switches to uncontrolled (value is null)
* should warn if controlled input switches to uncontrolled with defaultValue
* should warn if uncontrolled input (value is null) switches to controlled
* should warn if controlled checkbox switches to uncontrolled (checked is undefined)
* should warn if controlled checkbox switches to uncontrolled (checked is null)
* should warn if controlled checkbox switches to uncontrolled with defaultChecked
* should warn if controlled radio switches to uncontrolled (checked is undefined)
* should warn if controlled radio switches to uncontrolled (checked is null)
* should warn if controlled radio switches to uncontrolled with defaultChecked
* should warn if radio checked false changes to become uncontrolled
src/renderers/dom/shared/wrappers/__tests__/ReactDOMSelect-test.js
* should warn if value is null
+85
View File
@@ -138,11 +138,16 @@ src/isomorphic/classic/__tests__/ReactContextValidator-test.js
* should pass next context to lifecycles
src/isomorphic/classic/class/__tests__/ReactBind-test.js
* Holds reference to instance
* works with mixins
* warns if you try to bind to this
* does not warn if you pass an auto-bound method to setState
src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js
* should work with manual binding
* should not hold reference to instance
* works with mixins that have not opted out of autobinding
* works with mixins that have opted out of autobinding
* does not warn if you try to bind to this
* does not warn if you pass an manually bound method to setState
@@ -522,6 +527,11 @@ src/renderers/dom/shared/__tests__/DOMPropertyOperations-test.js
* should support custom attributes
src/renderers/dom/shared/__tests__/ReactBrowserEventEmitter-test.js
* should support stopPropagation()
* should stop after first dispatch if stopPropagation
* should invoke handlers that were removed while bubbling
* should not invoke newly inserted handlers while bubbling
* should not onTouchTap from when dragging beyond threshold
* should listen to events only once
* should work with event plugins without dependencies
* should work with event plugins with dependencies
@@ -558,6 +568,7 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
* should generate the correct markup with className
* should escape style names and values
* should handle dangerouslySetInnerHTML
* should work error event on <source> element
* should not duplicate uppercased selfclosing tags
* should warn against children for void elements
* should warn against dangerouslySetInnerHTML for void elements
@@ -571,6 +582,8 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
* should warn about contentEditable and children
* should respect suppressContentEditableWarning
* should validate against invalid styles
* should track input values
* should track textarea values
* should support custom elements which extend native elements
* should warn against children for void elements
* should warn against dangerouslySetInnerHTML for void elements
@@ -602,6 +615,10 @@ src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
* can be toggled in and out of the markup
* can reconcile text from pre-rendered markup
src/renderers/dom/shared/__tests__/ReactEventIndependence-test.js
* does not crash with other react inside
* does not crash with other react outside
src/renderers/dom/shared/__tests__/ReactEventListener-test.js
* should dispatch events from outside React tree
* should propagate events one level down
@@ -626,6 +643,7 @@ src/renderers/dom/shared/__tests__/inputValueTracking-test.js
* should coerce value to a string
* should update value if it changed and return result
* should track value and return true when updating untracked instance
* should return tracker from node
* should stop tracking
src/renderers/dom/shared/__tests__/quoteAttributeValueForBrowser-test.js
@@ -644,7 +662,14 @@ src/renderers/dom/shared/eventPlugins/__tests__/BeforeInputEventPlugin-test.js
* extract onBeforeInput from fallback objects
src/renderers/dom/shared/eventPlugins/__tests__/ChangeEventPlugin-test.js
* should fire change for checkbox input
* should catch setting the value programmatically
* should not fire change when setting the value programmatically
* should unmount
* should only fire change for checked radio button once
* should deduplicate input value change events
* should listen for both change and input events when supported
* should only fire events when the value changes for range inputs
src/renderers/dom/shared/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js
* should set relatedTarget properly in iframe
@@ -658,7 +683,24 @@ src/renderers/dom/shared/eventPlugins/__tests__/FallbackCompositionState-test.js
* extracts when inserted within text
* extracts when inserted at end of text
src/renderers/dom/shared/eventPlugins/__tests__/SelectEventPlugin-test.js
* should skip extraction if no listeners are present
* should extract if an `onSelect` listener is present
src/renderers/dom/shared/eventPlugins/__tests__/SimpleEventPlugin-test.js
* A non-interactive tags click when disabled
* should forward clicks when it starts out not disabled
* should forward clicks when it becomes not disabled
* should work correctly if the listener is changed
* should forward clicks when it starts out not disabled
* should forward clicks when it becomes not disabled
* should work correctly if the listener is changed
* should forward clicks when it starts out not disabled
* should forward clicks when it becomes not disabled
* should work correctly if the listener is changed
* should forward clicks when it starts out not disabled
* should forward clicks when it becomes not disabled
* should work correctly if the listener is changed
* does not add a local click to interactive elements
* adds a local click listener to non-interactive elements
@@ -726,11 +768,18 @@ src/renderers/dom/shared/utils/__tests__/getNodeForCharacterOffset-test.js
src/renderers/dom/shared/utils/__tests__/setInnerHTML-test.js
* sets innerHTML on it
src/renderers/dom/shared/wrappers/__tests__/ReactDOMIframe-test.js
* should trigger load events
src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
* should properly control a value even if no event listener exists
* should control values in reentrant events with different targets
* should display `defaultValue` of number 0
* only assigns defaultValue if it changes
* should display "true" for `defaultValue` of `true`
* should display "false" for `defaultValue` of `false`
* should update `defaultValue` for uncontrolled input
* should update `defaultValue` for uncontrolled date/time input
* should take `defaultValue` when changing to uncontrolled input
* should render defaultValue for SSR
* should render value for SSR
@@ -744,20 +793,37 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
* should allow setting `value` to `false`
* should allow setting `value` to `objToString`
* should not incur unnecessary DOM mutations
* should properly control a value of number `0`
* should have the correct target value
* should not set a value for submit buttons unnecessarily
* should control radio buttons
* should control radio buttons if the tree updates during render
* should warn with value and no onChange handler and readOnly specified
* should have a this value of undefined if bind is not used
* should warn with checked and no onChange handler with readOnly specified
* should update defaultValue to empty string
* should warn if checked and defaultChecked props are specified
* should warn if value and defaultValue props are specified
* should warn if controlled input switches to uncontrolled (value is undefined)
* should warn if controlled input switches to uncontrolled with defaultValue
* should warn if uncontrolled input (value is undefined) switches to controlled
* should warn if controlled checkbox switches to uncontrolled (checked is undefined)
* should warn if controlled checkbox switches to uncontrolled (checked is null)
* should warn if controlled checkbox switches to uncontrolled with defaultChecked
* should warn if uncontrolled checkbox (checked is undefined) switches to controlled
* should warn if uncontrolled checkbox (checked is null) switches to controlled
* should warn if controlled radio switches to uncontrolled (checked is undefined)
* should warn if controlled radio switches to uncontrolled (checked is null)
* should warn if controlled radio switches to uncontrolled with defaultChecked
* should warn if uncontrolled radio (checked is undefined) switches to controlled
* should warn if uncontrolled radio (checked is null) switches to controlled
* should not warn if radio value changes but never becomes controlled
* should not warn if radio value changes but never becomes uncontrolled
* should warn if radio checked false changes to become uncontrolled
* sets type, step, min, max before value always
* sets value properly with type coming later in props
* does not raise a validation warning when it switches types
* resets value of date/time input to fix bugs in iOS Safari
src/renderers/dom/shared/wrappers/__tests__/ReactDOMOption-test.js
* should flatten children to a string
@@ -769,24 +835,37 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMOption-test.js
src/renderers/dom/shared/wrappers/__tests__/ReactDOMSelect-test.js
* should not throw with `defaultValue` and without children
* should not throw with `value` and without children
* should reset child options selected when they are changed and `value` is set
* should remember updated value when switching to uncontrolled
* should support server-side rendering
* should support server-side rendering with defaultValue
* should support server-side rendering with multiple
* should refresh state on change
* should warn if value and defaultValue props are specified
* should be able to safely remove select onChange
src/renderers/dom/shared/wrappers/__tests__/ReactDOMTextarea-test.js
* should allow setting `defaultValue`
* should display `defaultValue` of number 0
* should display "false" for `defaultValue` of `false`
* should display "foobar" for `defaultValue` of `objToString`
* should set defaultValue
* should not render value as an attribute
* should display `value` of number 0
* should update defaultValue to empty string
* should allow setting `value` to `giraffe`
* should render defaultValue for SSR
* should render value for SSR
* should allow setting `value` to `true`
* should allow setting `value` to `false`
* should allow setting `value` to `objToString`
* should take updates to `defaultValue` for uncontrolled textarea
* should take updates to children in lieu of `defaultValue` for uncontrolled textarea
* should not incur unnecessary DOM mutations
* should properly control a value of number `0`
* should treat children like `defaultValue`
* should keep value when switching to uncontrolled element if not changed
* should keep value when switching to uncontrolled element if changed
* should allow numbers as children
* should allow booleans as children
* should allow objects as children
@@ -1069,6 +1148,7 @@ src/renderers/shared/shared/__tests__/ReactTreeTraversal-test.js
src/renderers/shared/shared/event/__tests__/EventPluginHub-test.js
* should prevent non-function listeners, at dispatch
* should not prevent null listeners, at dispatch
src/renderers/shared/shared/event/__tests__/EventPluginRegistry-test.js
* should be able to inject ordering before plugins
@@ -1130,6 +1210,7 @@ src/renderers/shared/stack/reconciler/__tests__/ReactComponentLifeCycle-test.js
src/renderers/shared/stack/reconciler/__tests__/ReactCompositeComponent-test.js
* should support module pattern components
* should support rendering to different child types over time
* should react to state changes from callbacks
* should rewire refs when rendering to different child types
* should not cache old DOM nodes when switching constructors
* should auto bind methods and values correctly
@@ -1406,8 +1487,12 @@ src/test/__tests__/ReactTestUtils-test.js
* can scryRenderedDOMComponentsWithClass with className contains \n
* can scryRenderedDOMComponentsWithClass with multiple classes
* traverses children in the correct order
* should change the value of an input field
* should change the value of an input field in a component
* should throw when attempting to use ReactTestUtils.Simulate with shallow rendering
* should not warn when simulating events with extra properties
* can scry with stateless components involved
* should set the type of the event
src/test/__tests__/reactComponentExpect-test.js
* should match composite components
+4
View File
@@ -14,6 +14,7 @@
import type { HostChildren } from 'ReactFiberReconciler';
var ReactControlledComponent = require('ReactControlledComponent');
var ReactFiberReconciler = require('ReactFiberReconciler');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
@@ -21,6 +22,9 @@ var ReactDOMFiberComponent = require('ReactDOMFiberComponent');
var ReactDOMInjection = require('ReactDOMInjection');
ReactDOMInjection.inject();
ReactControlledComponent.injection.injectFiberControlledHostComponent(
ReactDOMFiberComponent
);
var warning = require('warning');
@@ -33,7 +33,7 @@ var invariant = require('invariant');
var isEventSupported = require('isEventSupported');
var setInnerHTML = require('setInnerHTML');
var setTextContent = require('setTextContent');
// var inputValueTracking = require('inputValueTracking');
var inputValueTracking = require('inputValueTracking');
var warning = require('warning');
var didWarnShadyDOM = false;
@@ -159,7 +159,7 @@ var mediaEvents = {
topWaiting: 'waiting',
};
function trapClickOnNonInteractiveElement(node : any) {
function trapClickOnNonInteractiveElement(node : HTMLElement) {
// Mobile Safari does not fire properly bubble click events on
// non-interactive elements, which means delegated click listeners do not
// fire. The workaround for this bug involves attaching an empty click
@@ -496,7 +496,7 @@ var ReactDOMFiberComponent = {
var div = ownerDocument.createElement('div');
div.innerHTML = '<script></script>';
// This is guaranteed to yield a script element.
var firstChild = ((div.firstChild : any) : Element);
var firstChild = ((div.firstChild : any) : HTMLScriptElement);
domElement = div.removeChild(firstChild);
} else if (props.is) {
domElement = ownerDocument.createElement(tag, props.is);
@@ -519,11 +519,11 @@ var ReactDOMFiberComponent = {
setInitialProperties(
domElement : Element,
tag : string,
props : Object,
rawProps : Object,
rootContainerElement : Element
) : void {
var isCustomComponentTag = isCustomComponent(tag, props);
var isCustomComponentTag = isCustomComponent(tag, rawProps);
if (__DEV__) {
if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
warning(
@@ -536,6 +536,7 @@ var ReactDOMFiberComponent = {
}
}
var props : Object;
switch (tag) {
case 'audio':
case 'form':
@@ -546,39 +547,42 @@ var ReactDOMFiberComponent = {
case 'source':
case 'video':
trapBubbledEventsLocal(domElement, tag);
props = rawProps;
break;
case 'input':
ReactDOMFiberInput.mountWrapper(domElement, props);
props = ReactDOMFiberInput.getHostProps(domElement, props);
ReactDOMFiberInput.mountWrapper(domElement, rawProps);
props = ReactDOMFiberInput.getHostProps(domElement, rawProps);
// TODO: Make sure we check if this is still unmounted or do any clean
// up necessary since we never stop tracking anymore.
//inputValueTracking.track(domElement); // TODO
inputValueTracking.trackNode((domElement : any));
trapBubbledEventsLocal(domElement, tag);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
break;
case 'option':
ReactDOMFiberOption.mountWrapper(domElement, props);
props = ReactDOMFiberOption.getHostProps(domElement, props);
ReactDOMFiberOption.mountWrapper(domElement, rawProps);
props = ReactDOMFiberOption.getHostProps(domElement, rawProps);
break;
case 'select':
ReactDOMFiberSelect.mountWrapper(domElement, props);
props = ReactDOMFiberSelect.getHostProps(domElement, props);
ReactDOMFiberSelect.mountWrapper(domElement, rawProps);
props = ReactDOMFiberSelect.getHostProps(domElement, rawProps);
trapBubbledEventsLocal(domElement, tag);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
break;
case 'textarea':
ReactDOMFiberTextarea.mountWrapper(domElement, props);
props = ReactDOMFiberTextarea.getHostProps(domElement, props);
//inputValueTracking.track(domElement); // TODO
ReactDOMFiberTextarea.mountWrapper(domElement, rawProps);
props = ReactDOMFiberTextarea.getHostProps(domElement, rawProps);
inputValueTracking.trackNode((domElement : any));
trapBubbledEventsLocal(domElement, tag);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
break;
default:
props = rawProps;
}
assertValidProps(tag, props);
@@ -596,13 +600,13 @@ var ReactDOMFiberComponent = {
// DOM yet. We need a special effect to handle this.
switch (tag) {
case 'input':
ReactDOMFiberInput.postMountWrapper(domElement, props);
ReactDOMFiberInput.postMountWrapper(domElement, rawProps);
if (props.autoFocus) {
focusNode(domElement);
}
break;
case 'textarea':
ReactDOMFiberTextarea.postMountWrapper(domElement, props);
ReactDOMFiberTextarea.postMountWrapper(domElement, rawProps);
if (props.autoFocus) {
focusNode(domElement);
}
@@ -618,11 +622,12 @@ var ReactDOMFiberComponent = {
}
break;
case 'option':
ReactDOMFiberOption.postMountWrapper(domElement, props);
ReactDOMFiberOption.postMountWrapper(domElement, rawProps);
break;
default:
if (typeof props.onClick === 'function') {
trapClickOnNonInteractiveElement(domElement);
// TODO: This cast may not be sound for SVG, MathML or custom elements.
trapClickOnNonInteractiveElement(((domElement : any) : HTMLElement));
}
break;
}
@@ -631,31 +636,36 @@ var ReactDOMFiberComponent = {
updateProperties(
domElement : Element,
tag : string,
lastProps : Object,
nextProps : Object,
lastRawProps : Object,
nextRawProps : Object,
rootContainerElement : Element
) : void {
var lastProps : Object;
var nextProps : Object;
switch (tag) {
case 'input':
lastProps = ReactDOMFiberInput.getHostProps(domElement, lastProps);
nextProps = ReactDOMFiberInput.getHostProps(domElement, nextProps);
lastProps = ReactDOMFiberInput.getHostProps(domElement, lastRawProps);
nextProps = ReactDOMFiberInput.getHostProps(domElement, nextRawProps);
break;
case 'option':
lastProps = ReactDOMFiberOption.getHostProps(domElement, lastProps);
nextProps = ReactDOMFiberOption.getHostProps(domElement, nextProps);
lastProps = ReactDOMFiberOption.getHostProps(domElement, lastRawProps);
nextProps = ReactDOMFiberOption.getHostProps(domElement, nextRawProps);
break;
case 'select':
lastProps = ReactDOMFiberSelect.getHostProps(domElement, lastProps);
nextProps = ReactDOMFiberSelect.getHostProps(domElement, nextProps);
lastProps = ReactDOMFiberSelect.getHostProps(domElement, lastRawProps);
nextProps = ReactDOMFiberSelect.getHostProps(domElement, nextRawProps);
break;
case 'textarea':
lastProps = ReactDOMFiberTextarea.getHostProps(domElement, lastProps);
nextProps = ReactDOMFiberTextarea.getHostProps(domElement, nextProps);
lastProps = ReactDOMFiberTextarea.getHostProps(domElement, lastRawProps);
nextProps = ReactDOMFiberTextarea.getHostProps(domElement, nextRawProps);
break;
default:
lastProps = lastRawProps;
nextProps = nextRawProps;
if (typeof lastProps.onClick !== 'function' &&
typeof nextProps.onClick === 'function') {
trapClickOnNonInteractiveElement(domElement);
// TODO: This cast may not be sound for SVG, MathML or custom elements.
trapClickOnNonInteractiveElement(((domElement : any) : HTMLElement));
}
break;
}
@@ -677,15 +687,15 @@ var ReactDOMFiberComponent = {
// Update the wrapper around inputs *after* updating props. This has to
// happen after `updateDOMProperties`. Otherwise HTML5 input validations
// raise warnings and prevent the new value from being assigned.
ReactDOMFiberInput.updateWrapper(domElement, nextProps);
ReactDOMFiberInput.updateWrapper(domElement, nextRawProps);
break;
case 'textarea':
ReactDOMFiberTextarea.updateWrapper(domElement, nextProps);
ReactDOMFiberTextarea.updateWrapper(domElement, nextRawProps);
break;
case 'select':
// <select> value update needs to occur after <option> children
// reconciliation
ReactDOMFiberSelect.postUpdateWrapper(domElement, nextProps);
ReactDOMFiberSelect.postUpdateWrapper(domElement, nextRawProps);
break;
}
},
@@ -496,9 +496,9 @@ describe('ReactBrowserEventEmitter', () => {
ReactTestUtils.nativeTouchData(0, 0)
);
expect(idCallOrder.length).toBe(3);
expect(idCallOrder[0]).toBe(CHILD);
expect(idCallOrder[1]).toBe(PARENT);
expect(idCallOrder[2]).toBe(GRANDPARENT);
expect(idCallOrder[0] === CHILD).toBe(true);
expect(idCallOrder[1] === PARENT).toBe(true);
expect(idCallOrder[2] === GRANDPARENT).toBe(true);
});
it('should not crash ensureScrollValueMonitoring when createEvent returns null', () => {
@@ -22,9 +22,6 @@ var getActiveElement = require('getActiveElement');
var isTextInputElement = require('isTextInputElement');
var shallowEqual = require('shallowEqual');
// Node type for document fragments (Node.DOCUMENT_FRAGMENT_NODE).
var DOC_FRAGMENT_TYPE = 11;
var skipSelectionChangeEvent = (
ExecutionEnvironment.canUseDOM &&
'documentMode' in document &&
@@ -159,13 +156,13 @@ var SelectEventPlugin = {
nativeEvent,
nativeEventTarget
) {
if (targetInst) {
var containerInfo = targetInst._hostContainerInfo;
var isDocumentFragment = containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE;
var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument;
if (!isListeningToAllDependencies('onSelect', doc)) {
return null;
}
var doc = nativeEventTarget.window === nativeEventTarget ?
nativeEventTarget.document :
nativeEventTarget.nodeType === 9 ?
nativeEventTarget :
nativeEventTarget.ownerDocument;
if (!doc || !isListeningToAllDependencies('onSelect', doc)) {
return null;
}
var targetNode = targetInst ?
+86 -54
View File
@@ -7,12 +7,26 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule inputValueTracking
* @flow
*/
'use strict';
import type { Fiber } from 'ReactFiber';
import type { ReactInstance } from 'ReactInstanceType';
type ValueTracker = {
getValue() : string,
setValue(value : string) : void,
stopTracking() : void
};
type WrapperState = { _wrapperState: { valueTracker: ?ValueTracker } };
type ElementWithWrapperState = Element & WrapperState;
type InstanceWithWrapperState = ReactInstance & WrapperState;
var ReactDOMComponentTree = require('ReactDOMComponentTree');
function isCheckable(elem) {
function isCheckable(elem : any) {
var type = elem.type;
var nodeName = elem.nodeName;
return (
@@ -21,15 +35,18 @@ function isCheckable(elem) {
);
}
function getTracker(inst) {
function getTracker(inst : any) {
if (typeof inst.tag === 'number') {
inst = inst.stateNode;
}
return inst._wrapperState.valueTracker;
}
function attachTracker(inst, tracker) {
function attachTracker(inst : InstanceWithWrapperState, tracker : ?ValueTracker) {
inst._wrapperState.valueTracker = tracker;
}
function detachTracker(inst) {
function detachTracker(inst : InstanceWithWrapperState) {
delete inst._wrapperState.valueTracker;
}
@@ -43,74 +60,89 @@ function getValueFromNode(node) {
return value;
}
function trackValueOnNode(node : any, inst : any) : ?ValueTracker {
var valueField = isCheckable(node) ? 'checked' : 'value';
var descriptor = Object.getOwnPropertyDescriptor(
node.constructor.prototype,
valueField
);
var currentValue = '' + node[valueField];
// if someone has already defined a value or Safari, then bail
// and don't track value will cause over reporting of changes,
// but it's better then a hard failure
// (needed for certain tests that spyOn input values and Safari)
if (
node.hasOwnProperty(valueField) ||
typeof descriptor.get !== 'function' ||
typeof descriptor.set !== 'function'
) {
return;
}
Object.defineProperty(node, valueField, {
enumerable: descriptor.enumerable,
configurable: true,
get: function() {
return descriptor.get.call(this);
},
set: function(value) {
currentValue = '' + value;
descriptor.set.call(this, value);
},
});
var tracker = {
getValue() {
return currentValue;
},
setValue(value) {
currentValue = '' + value;
},
stopTracking() {
detachTracker(inst);
delete node[valueField];
},
};
return tracker;
}
var inputValueTracking = {
// exposed for testing
_getTrackerFromNode(node) {
_getTrackerFromNode(node : ElementWithWrapperState) {
return getTracker(
ReactDOMComponentTree.getInstanceFromNode(node)
);
},
track: function(inst) {
trackNode: function(node : ElementWithWrapperState) {
if (node._wrapperState.valueTracker) {
return;
}
node._wrapperState.valueTracker = trackValueOnNode(node, node);
},
track: function(inst : InstanceWithWrapperState) {
if (getTracker(inst)) {
return;
}
var node = ReactDOMComponentTree.getNodeFromInstance(inst);
var valueField = isCheckable(node) ? 'checked' : 'value';
var descriptor = Object.getOwnPropertyDescriptor(
node.constructor.prototype,
valueField
);
var currentValue = '' + node[valueField];
// if someone has already defined a value or Safari, then bail
// and don't track value will cause over reporting of changes,
// but it's better then a hard failure
// (needed for certain tests that spyOn input values and Safari)
if (
node.hasOwnProperty(valueField) ||
typeof descriptor.get !== 'function' ||
typeof descriptor.set !== 'function'
) {
return;
}
Object.defineProperty(node, valueField, {
enumerable: descriptor.enumerable,
configurable: true,
get: function() {
return descriptor.get.call(this);
},
set: function(value) {
currentValue = '' + value;
descriptor.set.call(this, value);
},
});
attachTracker(inst, {
getValue() {
return currentValue;
},
setValue(value) {
currentValue = '' + value;
},
stopTracking() {
detachTracker(inst);
delete node[valueField];
},
});
attachTracker(inst, trackValueOnNode(node, inst));
},
updateValueIfChanged(inst) {
updateValueIfChanged(inst : InstanceWithWrapperState | Fiber) {
if (!inst) {
return false;
}
var tracker = getTracker(inst);
if (!tracker) {
inputValueTracking.track(inst);
if (typeof (inst : any).tag === 'number') {
inputValueTracking.trackNode((inst : any).stateNode);
} else {
inputValueTracking.track((inst : any));
}
return true;
}
@@ -127,7 +159,7 @@ var inputValueTracking = {
return false;
},
stopTracking(inst) {
stopTracking(inst : InstanceWithWrapperState | Fiber) {
var tracker = getTracker(inst);
if (tracker) {
tracker.stopTracking();
@@ -952,7 +952,7 @@ describe('ReactDOMInput', () => {
ReactTestUtils.renderIntoDocument(<input value="0" type="range" min="0" max="100" step="1" />);
expect(log).toEqual([
'set data-reactroot',
...(ReactDOMFeatureFlags.useFiber ? [] : ['set data-reactroot']),
'set type',
'set step',
'set min',
@@ -1019,7 +1019,7 @@ describe('ReactDOMInput', () => {
ReactTestUtils.renderIntoDocument(<input type="date" defaultValue="1980-01-01" />);
expect(log).toEqual([
'node.setAttribute("data-reactroot", "")',
...(ReactDOMFeatureFlags.useFiber ? [] : ['node.setAttribute("data-reactroot", "")']),
'node.setAttribute("type", "date")',
'node.setAttribute("value", "1980-01-01")',
'node.value = ""',
@@ -36,7 +36,13 @@ function restoreStateOfTarget(internalInstance) {
'Fiber needs to be injected to handle a fiber target for controlled ' +
'events.'
);
fiberHostComponent.restoreControlledState(internalInstance);
// TODO: Ensure that this instance is the current one. Props needs to be correct.
fiberHostComponent.restoreControlledState(
internalInstance.stateNode,
internalInstance.type,
internalInstance.memoizedProps
);
return;
}
invariant(
typeof internalInstance.restoreControlledState === 'function',