mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
e2424f33b3
This enables the "exact_empty_objects" setting for Flow which makes empty objects exact instead of building up the type as properties are added in code below. This is in preparation to Flow 191 which makes this the default and removes the config. More about the change in the Flow blog [here](https://medium.com/flow-type/improved-handling-of-the-empty-object-in-flow-ead91887e40c).
205 lines
6.3 KiB
JavaScript
205 lines
6.3 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
// TODO: direct imports like some-package/src/* are bad. Fix me.
|
|
import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCurrentFiber';
|
|
|
|
import {checkControlledValueProps} from '../shared/ReactControlledValuePropTypes';
|
|
import {getToStringValue, toString} from './ToStringValue';
|
|
import assign from 'shared/assign';
|
|
import isArray from 'shared/isArray';
|
|
|
|
let didWarnValueDefaultValue;
|
|
|
|
if (__DEV__) {
|
|
didWarnValueDefaultValue = false;
|
|
}
|
|
|
|
type SelectWithWrapperState = HTMLSelectElement & {
|
|
_wrapperState: {wasMultiple: boolean},
|
|
};
|
|
|
|
function getDeclarationErrorAddendum() {
|
|
const ownerName = getCurrentFiberOwnerNameInDevOrNull();
|
|
if (ownerName) {
|
|
return '\n\nCheck the render method of `' + ownerName + '`.';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
const valuePropNames = ['value', 'defaultValue'];
|
|
|
|
/**
|
|
* Validation function for `value` and `defaultValue`.
|
|
*/
|
|
function checkSelectPropTypes(props: any) {
|
|
if (__DEV__) {
|
|
checkControlledValueProps('select', props);
|
|
|
|
for (let i = 0; i < valuePropNames.length; i++) {
|
|
const propName = valuePropNames[i];
|
|
if (props[propName] == null) {
|
|
continue;
|
|
}
|
|
const propNameIsArray = isArray(props[propName]);
|
|
if (props.multiple && !propNameIsArray) {
|
|
console.error(
|
|
'The `%s` prop supplied to <select> must be an array if ' +
|
|
'`multiple` is true.%s',
|
|
propName,
|
|
getDeclarationErrorAddendum(),
|
|
);
|
|
} else if (!props.multiple && propNameIsArray) {
|
|
console.error(
|
|
'The `%s` prop supplied to <select> must be a scalar ' +
|
|
'value if `multiple` is false.%s',
|
|
propName,
|
|
getDeclarationErrorAddendum(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateOptions(
|
|
node: HTMLSelectElement,
|
|
multiple: boolean,
|
|
propValue: any,
|
|
setDefaultSelected: boolean,
|
|
) {
|
|
const options: HTMLOptionsCollection = node.options;
|
|
|
|
if (multiple) {
|
|
const selectedValues = (propValue: Array<string>);
|
|
const selectedValue: {[string]: boolean} = {};
|
|
for (let i = 0; i < selectedValues.length; i++) {
|
|
// Prefix to avoid chaos with special keys.
|
|
selectedValue['$' + selectedValues[i]] = true;
|
|
}
|
|
for (let i = 0; i < options.length; i++) {
|
|
const selected = selectedValue.hasOwnProperty('$' + options[i].value);
|
|
if (options[i].selected !== selected) {
|
|
options[i].selected = selected;
|
|
}
|
|
if (selected && setDefaultSelected) {
|
|
options[i].defaultSelected = true;
|
|
}
|
|
}
|
|
} else {
|
|
// Do not set `select.value` as exact behavior isn't consistent across all
|
|
// browsers for all cases.
|
|
const selectedValue = toString(getToStringValue((propValue: any)));
|
|
let defaultSelected = null;
|
|
for (let i = 0; i < options.length; i++) {
|
|
if (options[i].value === selectedValue) {
|
|
options[i].selected = true;
|
|
if (setDefaultSelected) {
|
|
options[i].defaultSelected = true;
|
|
}
|
|
return;
|
|
}
|
|
if (defaultSelected === null && !options[i].disabled) {
|
|
defaultSelected = options[i];
|
|
}
|
|
}
|
|
if (defaultSelected !== null) {
|
|
defaultSelected.selected = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Implements a <select> host component that allows optionally setting the
|
|
* props `value` and `defaultValue`. If `multiple` is false, the prop must be a
|
|
* stringable. If `multiple` is true, the prop must be an array of stringables.
|
|
*
|
|
* If `value` is not supplied (or null/undefined), user actions that change the
|
|
* selected option will trigger updates to the rendered options.
|
|
*
|
|
* If it is supplied (and not null/undefined), the rendered options will not
|
|
* update in response to user actions. Instead, the `value` prop must change in
|
|
* order for the rendered options to update.
|
|
*
|
|
* If `defaultValue` is provided, any options with the supplied values will be
|
|
* selected.
|
|
*/
|
|
|
|
export function getHostProps(element: Element, props: Object): Object {
|
|
return assign({}, props, {
|
|
value: undefined,
|
|
});
|
|
}
|
|
|
|
export function initWrapperState(element: Element, props: Object) {
|
|
const node = ((element: any): SelectWithWrapperState);
|
|
if (__DEV__) {
|
|
checkSelectPropTypes(props);
|
|
}
|
|
|
|
node._wrapperState = {
|
|
wasMultiple: !!props.multiple,
|
|
};
|
|
|
|
if (__DEV__) {
|
|
if (
|
|
props.value !== undefined &&
|
|
props.defaultValue !== undefined &&
|
|
!didWarnValueDefaultValue
|
|
) {
|
|
console.error(
|
|
'Select elements must be either controlled or uncontrolled ' +
|
|
'(specify either the value prop, or the defaultValue prop, but not ' +
|
|
'both). Decide between using a controlled or uncontrolled select ' +
|
|
'element and remove one of these props. More info: ' +
|
|
'https://reactjs.org/link/controlled-components',
|
|
);
|
|
didWarnValueDefaultValue = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function postMountWrapper(element: Element, props: Object) {
|
|
const node = ((element: any): SelectWithWrapperState);
|
|
node.multiple = !!props.multiple;
|
|
const value = props.value;
|
|
if (value != null) {
|
|
updateOptions(node, !!props.multiple, value, false);
|
|
} else if (props.defaultValue != null) {
|
|
updateOptions(node, !!props.multiple, props.defaultValue, true);
|
|
}
|
|
}
|
|
|
|
export function postUpdateWrapper(element: Element, props: Object) {
|
|
const node = ((element: any): SelectWithWrapperState);
|
|
const wasMultiple = node._wrapperState.wasMultiple;
|
|
node._wrapperState.wasMultiple = !!props.multiple;
|
|
|
|
const value = props.value;
|
|
if (value != null) {
|
|
updateOptions(node, !!props.multiple, value, false);
|
|
} else if (wasMultiple !== !!props.multiple) {
|
|
// For simplicity, reapply `defaultValue` if `multiple` is toggled.
|
|
if (props.defaultValue != null) {
|
|
updateOptions(node, !!props.multiple, props.defaultValue, true);
|
|
} else {
|
|
// Revert the select back to its default unselected state.
|
|
updateOptions(node, !!props.multiple, props.multiple ? [] : '', false);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function restoreControlledState(element: Element, props: Object) {
|
|
const node = ((element: any): SelectWithWrapperState);
|
|
const value = props.value;
|
|
|
|
if (value != null) {
|
|
updateOptions(node, !!props.multiple, value, false);
|
|
}
|
|
}
|