import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCurrentFiber';
import {getToStringValue, toString} from './ToStringValue';
import isArray from 'shared/isArray';
import {queueChangeEvent} from '../events/ReactDOMEventReplaying';
let didWarnValueDefaultValue;
if (__DEV__) {
didWarnValueDefaultValue = false;
}
function getDeclarationErrorAddendum() {
const ownerName = getCurrentFiberOwnerNameInDevOrNull();
if (ownerName) {
return '\n\nCheck the render method of `' + ownerName + '`.';
}
return '';
}
const valuePropNames = ['value', 'defaultValue'];
function checkSelectPropTypes(props: any) {
if (__DEV__) {
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++) {
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 {
const selectedValue = toString(getToStringValue(propValue));
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;
}
}
}
export function validateSelectProps(element: Element, props: Object) {
if (__DEV__) {
checkSelectPropTypes(props);
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://react.dev/link/controlled-components',
);
didWarnValueDefaultValue = true;
}
}
}
export function initSelect(
element: Element,
value: ?string,
defaultValue: ?string,
multiple: ?boolean,
) {
const node: HTMLSelectElement = (element: any);
node.multiple = !!multiple;
if (value != null) {
updateOptions(node, !!multiple, value, false);
} else if (defaultValue != null) {
updateOptions(node, !!multiple, defaultValue, true);
}
}
export function hydrateSelect(
element: Element,
value: ?string,
defaultValue: ?string,
multiple: ?boolean,
): void {
const node: HTMLSelectElement = (element: any);
const options: HTMLOptionsCollection = node.options;
const propValue: any = value != null ? value : defaultValue;
let changed = false;
if (multiple) {
const selectedValues = (propValue: ?Array<string>);
const selectedValue: {[string]: boolean} = {};
if (selectedValues != null) {
for (let i = 0; i < selectedValues.length; i++) {
selectedValue['$' + selectedValues[i]] = true;
}
}
for (let i = 0; i < options.length; i++) {
const expectedSelected = selectedValue.hasOwnProperty(
'$' + options[i].value,
);
if (options[i].selected !== expectedSelected) {
changed = true;
break;
}
}
} else {
let selectedValue =
propValue == null ? null : toString(getToStringValue(propValue));
for (let i = 0; i < options.length; i++) {
if (selectedValue == null && !options[i].disabled) {
selectedValue = options[i].value;
}
const expectedSelected = options[i].value === selectedValue;
if (options[i].selected !== expectedSelected) {
changed = true;
break;
}
}
}
if (changed) {
queueChangeEvent(node);
}
}
export function updateSelect(
element: Element,
value: ?string,
defaultValue: ?string,
multiple: ?boolean,
wasMultiple: ?boolean,
) {
const node: HTMLSelectElement = (element: any);
if (value != null) {
updateOptions(node, !!multiple, value, false);
} else if (!!wasMultiple !== !!multiple) {
if (defaultValue != null) {
updateOptions(node, !!multiple, defaultValue, true);
} else {
updateOptions(node, !!multiple, multiple ? [] : '', false);
}
}
}
export function restoreControlledSelectState(element: Element, props: Object) {
const node: HTMLSelectElement = (element: any);
const value = props.value;
if (value != null) {
updateOptions(node, !!props.multiple, value, false);
}
}