You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
29869 lines
1.0 MiB
29869 lines
1.0 MiB
/** |
|
* @license React |
|
* react-dom.development.js |
|
* |
|
* Copyright (c) Facebook, Inc. and its affiliates. |
|
* |
|
* This source code is licensed under the MIT license found in the |
|
* LICENSE file in the root directory of this source tree. |
|
*/ |
|
(function (global, factory) { |
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react')) : |
|
typeof define === 'function' && define.amd ? define(['exports', 'react'], factory) : |
|
(global = global || self, factory(global.ReactDOM = {}, global.React)); |
|
}(this, (function (exports, React) { 'use strict'; |
|
|
|
var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; |
|
|
|
var suppressWarning = false; |
|
function setSuppressWarning(newSuppressWarning) { |
|
{ |
|
suppressWarning = newSuppressWarning; |
|
} |
|
} // In DEV, calls to console.warn and console.error get replaced |
|
// by calls to these methods by a Babel plugin. |
|
// |
|
// In PROD (or in packages without access to React internals), |
|
// they are left as they are instead. |
|
|
|
function warn(format) { |
|
{ |
|
if (!suppressWarning) { |
|
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { |
|
args[_key - 1] = arguments[_key]; |
|
} |
|
|
|
printWarning('warn', format, args); |
|
} |
|
} |
|
} |
|
function error(format) { |
|
{ |
|
if (!suppressWarning) { |
|
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { |
|
args[_key2 - 1] = arguments[_key2]; |
|
} |
|
|
|
printWarning('error', format, args); |
|
} |
|
} |
|
} |
|
|
|
function printWarning(level, format, args) { |
|
// When changing this logic, you might want to also |
|
// update consoleWithStackDev.www.js as well. |
|
{ |
|
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame; |
|
var stack = ReactDebugCurrentFrame.getStackAddendum(); |
|
|
|
if (stack !== '') { |
|
format += '%s'; |
|
args = args.concat([stack]); |
|
} // eslint-disable-next-line react-internal/safe-string-coercion |
|
|
|
|
|
var argsWithFormat = args.map(function (item) { |
|
return String(item); |
|
}); // Careful: RN currently depends on this prefix |
|
|
|
argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it |
|
// breaks IE9: https://github.com/facebook/react/issues/13610 |
|
// eslint-disable-next-line react-internal/no-production-logging |
|
|
|
Function.prototype.apply.call(console[level], console, argsWithFormat); |
|
} |
|
} |
|
|
|
var FunctionComponent = 0; |
|
var ClassComponent = 1; |
|
var IndeterminateComponent = 2; // Before we know whether it is function or class |
|
|
|
var HostRoot = 3; // Root of a host tree. Could be nested inside another node. |
|
|
|
var HostPortal = 4; // A subtree. Could be an entry point to a different renderer. |
|
|
|
var HostComponent = 5; |
|
var HostText = 6; |
|
var Fragment = 7; |
|
var Mode = 8; |
|
var ContextConsumer = 9; |
|
var ContextProvider = 10; |
|
var ForwardRef = 11; |
|
var Profiler = 12; |
|
var SuspenseComponent = 13; |
|
var MemoComponent = 14; |
|
var SimpleMemoComponent = 15; |
|
var LazyComponent = 16; |
|
var IncompleteClassComponent = 17; |
|
var DehydratedFragment = 18; |
|
var SuspenseListComponent = 19; |
|
var ScopeComponent = 21; |
|
var OffscreenComponent = 22; |
|
var LegacyHiddenComponent = 23; |
|
var CacheComponent = 24; |
|
var TracingMarkerComponent = 25; |
|
|
|
// ----------------------------------------------------------------------------- |
|
|
|
var enableClientRenderFallbackOnTextMismatch = true; // TODO: Need to review this code one more time before landing |
|
// the react-reconciler package. |
|
|
|
var enableNewReconciler = false; // Support legacy Primer support on internal FB www |
|
|
|
var enableLazyContextPropagation = false; // FB-only usage. The new API has different semantics. |
|
|
|
var enableLegacyHidden = false; // Enables unstable_avoidThisFallback feature in Fiber |
|
|
|
var enableSuspenseAvoidThisFallback = false; // Enables unstable_avoidThisFallback feature in Fizz |
|
// React DOM Chopping Block |
|
// |
|
// Similar to main Chopping Block but only flags related to React DOM. These are |
|
// grouped because we will likely batch all of them into a single major release. |
|
// ----------------------------------------------------------------------------- |
|
// Disable support for comment nodes as React DOM containers. Already disabled |
|
// in open source, but www codebase still relies on it. Need to remove. |
|
|
|
var disableCommentsAsDOMContainers = true; // Disable javascript: URL strings in href for XSS protection. |
|
// and client rendering, mostly to allow JSX attributes to apply to the custom |
|
// element's object properties instead of only HTML attributes. |
|
// https://github.com/facebook/react/issues/11347 |
|
|
|
var enableCustomElementPropertySupport = false; // Disables children for <textarea> elements |
|
var warnAboutStringRefs = false; // ----------------------------------------------------------------------------- |
|
// Debugging and DevTools |
|
// ----------------------------------------------------------------------------- |
|
// Adds user timing marks for e.g. state updates, suspense, and work loop stuff, |
|
// for an experimental timeline tool. |
|
|
|
var enableSchedulingProfiler = true; // Helps identify side effects in render-phase lifecycle hooks and setState |
|
|
|
var enableProfilerTimer = true; // Record durations for commit and passive effects phases. |
|
|
|
var enableProfilerCommitHooks = true; // Phase param passed to onRender callback differentiates between an "update" and a "cascading-update". |
|
|
|
var allNativeEvents = new Set(); |
|
/** |
|
* Mapping from registration name to event name |
|
*/ |
|
|
|
|
|
var registrationNameDependencies = {}; |
|
/** |
|
* Mapping from lowercase registration names to the properly cased version, |
|
* used to warn in the case of missing event handlers. Available |
|
* only in true. |
|
* @type {Object} |
|
*/ |
|
|
|
var possibleRegistrationNames = {} ; // Trust the developer to only use possibleRegistrationNames in true |
|
|
|
function registerTwoPhaseEvent(registrationName, dependencies) { |
|
registerDirectEvent(registrationName, dependencies); |
|
registerDirectEvent(registrationName + 'Capture', dependencies); |
|
} |
|
function registerDirectEvent(registrationName, dependencies) { |
|
{ |
|
if (registrationNameDependencies[registrationName]) { |
|
error('EventRegistry: More than one plugin attempted to publish the same ' + 'registration name, `%s`.', registrationName); |
|
} |
|
} |
|
|
|
registrationNameDependencies[registrationName] = dependencies; |
|
|
|
{ |
|
var lowerCasedName = registrationName.toLowerCase(); |
|
possibleRegistrationNames[lowerCasedName] = registrationName; |
|
|
|
if (registrationName === 'onDoubleClick') { |
|
possibleRegistrationNames.ondblclick = registrationName; |
|
} |
|
} |
|
|
|
for (var i = 0; i < dependencies.length; i++) { |
|
allNativeEvents.add(dependencies[i]); |
|
} |
|
} |
|
|
|
var canUseDOM = !!(typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined'); |
|
|
|
var hasOwnProperty = Object.prototype.hasOwnProperty; |
|
|
|
/* |
|
* The `'' + value` pattern (used in in perf-sensitive code) throws for Symbol |
|
* and Temporal.* types. See https://github.com/facebook/react/pull/22064. |
|
* |
|
* The functions in this module will throw an easier-to-understand, |
|
* easier-to-debug exception with a clear errors message message explaining the |
|
* problem. (Instead of a confusing exception thrown inside the implementation |
|
* of the `value` object). |
|
*/ |
|
// $FlowFixMe only called in DEV, so void return is not possible. |
|
function typeName(value) { |
|
{ |
|
// toStringTag is needed for namespaced types like Temporal.Instant |
|
var hasToStringTag = typeof Symbol === 'function' && Symbol.toStringTag; |
|
var type = hasToStringTag && value[Symbol.toStringTag] || value.constructor.name || 'Object'; |
|
return type; |
|
} |
|
} // $FlowFixMe only called in DEV, so void return is not possible. |
|
|
|
|
|
function willCoercionThrow(value) { |
|
{ |
|
try { |
|
testStringCoercion(value); |
|
return false; |
|
} catch (e) { |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
function testStringCoercion(value) { |
|
// If you ended up here by following an exception call stack, here's what's |
|
// happened: you supplied an object or symbol value to React (as a prop, key, |
|
// DOM attribute, CSS property, string ref, etc.) and when React tried to |
|
// coerce it to a string using `'' + value`, an exception was thrown. |
|
// |
|
// The most common types that will cause this exception are `Symbol` instances |
|
// and Temporal objects like `Temporal.Instant`. But any object that has a |
|
// `valueOf` or `[Symbol.toPrimitive]` method that throws will also cause this |
|
// exception. (Library authors do this to prevent users from using built-in |
|
// numeric operators like `+` or comparison operators like `>=` because custom |
|
// methods are needed to perform accurate arithmetic or comparison.) |
|
// |
|
// To fix the problem, coerce this object or symbol value to a string before |
|
// passing it to React. The most reliable way is usually `String(value)`. |
|
// |
|
// To find which value is throwing, check the browser or debugger console. |
|
// Before this exception was thrown, there should be `console.error` output |
|
// that shows the type (Symbol, Temporal.PlainDate, etc.) that caused the |
|
// problem and how that type was used: key, atrribute, input value prop, etc. |
|
// In most cases, this console output also shows the component and its |
|
// ancestor components where the exception happened. |
|
// |
|
// eslint-disable-next-line react-internal/safe-string-coercion |
|
return '' + value; |
|
} |
|
|
|
function checkAttributeStringCoercion(value, attributeName) { |
|
{ |
|
if (willCoercionThrow(value)) { |
|
error('The provided `%s` attribute is an unsupported type %s.' + ' This value must be coerced to a string before before using it here.', attributeName, typeName(value)); |
|
|
|
return testStringCoercion(value); // throw (to help callers find troubleshooting comments) |
|
} |
|
} |
|
} |
|
function checkKeyStringCoercion(value) { |
|
{ |
|
if (willCoercionThrow(value)) { |
|
error('The provided key is an unsupported type %s.' + ' This value must be coerced to a string before before using it here.', typeName(value)); |
|
|
|
return testStringCoercion(value); // throw (to help callers find troubleshooting comments) |
|
} |
|
} |
|
} |
|
function checkPropStringCoercion(value, propName) { |
|
{ |
|
if (willCoercionThrow(value)) { |
|
error('The provided `%s` prop is an unsupported type %s.' + ' This value must be coerced to a string before before using it here.', propName, typeName(value)); |
|
|
|
return testStringCoercion(value); // throw (to help callers find troubleshooting comments) |
|
} |
|
} |
|
} |
|
function checkCSSPropertyStringCoercion(value, propName) { |
|
{ |
|
if (willCoercionThrow(value)) { |
|
error('The provided `%s` CSS property is an unsupported type %s.' + ' This value must be coerced to a string before before using it here.', propName, typeName(value)); |
|
|
|
return testStringCoercion(value); // throw (to help callers find troubleshooting comments) |
|
} |
|
} |
|
} |
|
function checkHtmlStringCoercion(value) { |
|
{ |
|
if (willCoercionThrow(value)) { |
|
error('The provided HTML markup uses a value of unsupported type %s.' + ' This value must be coerced to a string before before using it here.', typeName(value)); |
|
|
|
return testStringCoercion(value); // throw (to help callers find troubleshooting comments) |
|
} |
|
} |
|
} |
|
function checkFormFieldValueStringCoercion(value) { |
|
{ |
|
if (willCoercionThrow(value)) { |
|
error('Form field values (value, checked, defaultValue, or defaultChecked props)' + ' must be strings, not %s.' + ' This value must be coerced to a string before before using it here.', typeName(value)); |
|
|
|
return testStringCoercion(value); // throw (to help callers find troubleshooting comments) |
|
} |
|
} |
|
} |
|
|
|
// A reserved attribute. |
|
// It is handled by React separately and shouldn't be written to the DOM. |
|
var RESERVED = 0; // A simple string attribute. |
|
// Attributes that aren't in the filter are presumed to have this type. |
|
|
|
var STRING = 1; // A string attribute that accepts booleans in React. In HTML, these are called |
|
// "enumerated" attributes with "true" and "false" as possible values. |
|
// When true, it should be set to a "true" string. |
|
// When false, it should be set to a "false" string. |
|
|
|
var BOOLEANISH_STRING = 2; // A real boolean attribute. |
|
// When true, it should be present (set either to an empty string or its name). |
|
// When false, it should be omitted. |
|
|
|
var BOOLEAN = 3; // An attribute that can be used as a flag as well as with a value. |
|
// When true, it should be present (set either to an empty string or its name). |
|
// When false, it should be omitted. |
|
// For any other value, should be present with that value. |
|
|
|
var OVERLOADED_BOOLEAN = 4; // An attribute that must be numeric or parse as a numeric. |
|
// When falsy, it should be removed. |
|
|
|
var NUMERIC = 5; // An attribute that must be positive numeric or parse as a positive numeric. |
|
// When falsy, it should be removed. |
|
|
|
var POSITIVE_NUMERIC = 6; |
|
|
|
/* eslint-disable max-len */ |
|
var ATTRIBUTE_NAME_START_CHAR = ":A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD"; |
|
/* eslint-enable max-len */ |
|
|
|
var ATTRIBUTE_NAME_CHAR = ATTRIBUTE_NAME_START_CHAR + "\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040"; |
|
var VALID_ATTRIBUTE_NAME_REGEX = new RegExp('^[' + ATTRIBUTE_NAME_START_CHAR + '][' + ATTRIBUTE_NAME_CHAR + ']*$'); |
|
var illegalAttributeNameCache = {}; |
|
var validatedAttributeNameCache = {}; |
|
function isAttributeNameSafe(attributeName) { |
|
if (hasOwnProperty.call(validatedAttributeNameCache, attributeName)) { |
|
return true; |
|
} |
|
|
|
if (hasOwnProperty.call(illegalAttributeNameCache, attributeName)) { |
|
return false; |
|
} |
|
|
|
if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) { |
|
validatedAttributeNameCache[attributeName] = true; |
|
return true; |
|
} |
|
|
|
illegalAttributeNameCache[attributeName] = true; |
|
|
|
{ |
|
error('Invalid attribute name: `%s`', attributeName); |
|
} |
|
|
|
return false; |
|
} |
|
function shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag) { |
|
if (propertyInfo !== null) { |
|
return propertyInfo.type === RESERVED; |
|
} |
|
|
|
if (isCustomComponentTag) { |
|
return false; |
|
} |
|
|
|
if (name.length > 2 && (name[0] === 'o' || name[0] === 'O') && (name[1] === 'n' || name[1] === 'N')) { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
function shouldRemoveAttributeWithWarning(name, value, propertyInfo, isCustomComponentTag) { |
|
if (propertyInfo !== null && propertyInfo.type === RESERVED) { |
|
return false; |
|
} |
|
|
|
switch (typeof value) { |
|
case 'function': // $FlowIssue symbol is perfectly valid here |
|
|
|
case 'symbol': |
|
// eslint-disable-line |
|
return true; |
|
|
|
case 'boolean': |
|
{ |
|
if (isCustomComponentTag) { |
|
return false; |
|
} |
|
|
|
if (propertyInfo !== null) { |
|
return !propertyInfo.acceptsBooleans; |
|
} else { |
|
var prefix = name.toLowerCase().slice(0, 5); |
|
return prefix !== 'data-' && prefix !== 'aria-'; |
|
} |
|
} |
|
|
|
default: |
|
return false; |
|
} |
|
} |
|
function shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag) { |
|
if (value === null || typeof value === 'undefined') { |
|
return true; |
|
} |
|
|
|
if (shouldRemoveAttributeWithWarning(name, value, propertyInfo, isCustomComponentTag)) { |
|
return true; |
|
} |
|
|
|
if (isCustomComponentTag) { |
|
|
|
return false; |
|
} |
|
|
|
if (propertyInfo !== null) { |
|
|
|
switch (propertyInfo.type) { |
|
case BOOLEAN: |
|
return !value; |
|
|
|
case OVERLOADED_BOOLEAN: |
|
return value === false; |
|
|
|
case NUMERIC: |
|
return isNaN(value); |
|
|
|
case POSITIVE_NUMERIC: |
|
return isNaN(value) || value < 1; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
function getPropertyInfo(name) { |
|
return properties.hasOwnProperty(name) ? properties[name] : null; |
|
} |
|
|
|
function PropertyInfoRecord(name, type, mustUseProperty, attributeName, attributeNamespace, sanitizeURL, removeEmptyString) { |
|
this.acceptsBooleans = type === BOOLEANISH_STRING || type === BOOLEAN || type === OVERLOADED_BOOLEAN; |
|
this.attributeName = attributeName; |
|
this.attributeNamespace = attributeNamespace; |
|
this.mustUseProperty = mustUseProperty; |
|
this.propertyName = name; |
|
this.type = type; |
|
this.sanitizeURL = sanitizeURL; |
|
this.removeEmptyString = removeEmptyString; |
|
} // When adding attributes to this list, be sure to also add them to |
|
// the `possibleStandardNames` module to ensure casing and incorrect |
|
// name warnings. |
|
|
|
|
|
var properties = {}; // These props are reserved by React. They shouldn't be written to the DOM. |
|
|
|
var reservedProps = ['children', 'dangerouslySetInnerHTML', // TODO: This prevents the assignment of defaultValue to regular |
|
// elements (not just inputs). Now that ReactDOMInput assigns to the |
|
// defaultValue property -- do we need this? |
|
'defaultValue', 'defaultChecked', 'innerHTML', 'suppressContentEditableWarning', 'suppressHydrationWarning', 'style']; |
|
|
|
reservedProps.forEach(function (name) { |
|
properties[name] = new PropertyInfoRecord(name, RESERVED, false, // mustUseProperty |
|
name, // attributeName |
|
null, // attributeNamespace |
|
false, // sanitizeURL |
|
false); |
|
}); // A few React string attributes have a different name. |
|
// This is a mapping from React prop names to the attribute names. |
|
|
|
[['acceptCharset', 'accept-charset'], ['className', 'class'], ['htmlFor', 'for'], ['httpEquiv', 'http-equiv']].forEach(function (_ref) { |
|
var name = _ref[0], |
|
attributeName = _ref[1]; |
|
properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty |
|
attributeName, // attributeName |
|
null, // attributeNamespace |
|
false, // sanitizeURL |
|
false); |
|
}); // These are "enumerated" HTML attributes that accept "true" and "false". |
|
// In React, we let users pass `true` and `false` even though technically |
|
// these aren't boolean attributes (they are coerced to strings). |
|
|
|
['contentEditable', 'draggable', 'spellCheck', 'value'].forEach(function (name) { |
|
properties[name] = new PropertyInfoRecord(name, BOOLEANISH_STRING, false, // mustUseProperty |
|
name.toLowerCase(), // attributeName |
|
null, // attributeNamespace |
|
false, // sanitizeURL |
|
false); |
|
}); // These are "enumerated" SVG attributes that accept "true" and "false". |
|
// In React, we let users pass `true` and `false` even though technically |
|
// these aren't boolean attributes (they are coerced to strings). |
|
// Since these are SVG attributes, their attribute names are case-sensitive. |
|
|
|
['autoReverse', 'externalResourcesRequired', 'focusable', 'preserveAlpha'].forEach(function (name) { |
|
properties[name] = new PropertyInfoRecord(name, BOOLEANISH_STRING, false, // mustUseProperty |
|
name, // attributeName |
|
null, // attributeNamespace |
|
false, // sanitizeURL |
|
false); |
|
}); // These are HTML boolean attributes. |
|
|
|
['allowFullScreen', 'async', // Note: there is a special case that prevents it from being written to the DOM |
|
// on the client side because the browsers are inconsistent. Instead we call focus(). |
|
'autoFocus', 'autoPlay', 'controls', 'default', 'defer', 'disabled', 'disablePictureInPicture', 'disableRemotePlayback', 'formNoValidate', 'hidden', 'loop', 'noModule', 'noValidate', 'open', 'playsInline', 'readOnly', 'required', 'reversed', 'scoped', 'seamless', // Microdata |
|
'itemScope'].forEach(function (name) { |
|
properties[name] = new PropertyInfoRecord(name, BOOLEAN, false, // mustUseProperty |
|
name.toLowerCase(), // attributeName |
|
null, // attributeNamespace |
|
false, // sanitizeURL |
|
false); |
|
}); // These are the few React props that we set as DOM properties |
|
// rather than attributes. These are all booleans. |
|
|
|
['checked', // Note: `option.selected` is not updated if `select.multiple` is |
|
// disabled with `removeAttribute`. We have special logic for handling this. |
|
'multiple', 'muted', 'selected' // NOTE: if you add a camelCased prop to this list, |
|
// you'll need to set attributeName to name.toLowerCase() |
|
// instead in the assignment below. |
|
].forEach(function (name) { |
|
properties[name] = new PropertyInfoRecord(name, BOOLEAN, true, // mustUseProperty |
|
name, // attributeName |
|
null, // attributeNamespace |
|
false, // sanitizeURL |
|
false); |
|
}); // These are HTML attributes that are "overloaded booleans": they behave like |
|
// booleans, but can also accept a string value. |
|
|
|
['capture', 'download' // NOTE: if you add a camelCased prop to this list, |
|
// you'll need to set attributeName to name.toLowerCase() |
|
// instead in the assignment below. |
|
].forEach(function (name) { |
|
properties[name] = new PropertyInfoRecord(name, OVERLOADED_BOOLEAN, false, // mustUseProperty |
|
name, // attributeName |
|
null, // attributeNamespace |
|
false, // sanitizeURL |
|
false); |
|
}); // These are HTML attributes that must be positive numbers. |
|
|
|
['cols', 'rows', 'size', 'span' // NOTE: if you add a camelCased prop to this list, |
|
// you'll need to set attributeName to name.toLowerCase() |
|
// instead in the assignment below. |
|
].forEach(function (name) { |
|
properties[name] = new PropertyInfoRecord(name, POSITIVE_NUMERIC, false, // mustUseProperty |
|
name, // attributeName |
|
null, // attributeNamespace |
|
false, // sanitizeURL |
|
false); |
|
}); // These are HTML attributes that must be numbers. |
|
|
|
['rowSpan', 'start'].forEach(function (name) { |
|
properties[name] = new PropertyInfoRecord(name, NUMERIC, false, // mustUseProperty |
|
name.toLowerCase(), // attributeName |
|
null, // attributeNamespace |
|
false, // sanitizeURL |
|
false); |
|
}); |
|
var CAMELIZE = /[\-\:]([a-z])/g; |
|
|
|
var capitalize = function (token) { |
|
return token[1].toUpperCase(); |
|
}; // This is a list of all SVG attributes that need special casing, namespacing, |
|
// or boolean value assignment. Regular attributes that just accept strings |
|
// and have the same names are omitted, just like in the HTML attribute filter. |
|
// Some of these attributes can be hard to find. This list was created by |
|
// scraping the MDN documentation. |
|
|
|
|
|
['accent-height', 'alignment-baseline', 'arabic-form', 'baseline-shift', 'cap-height', 'clip-path', 'clip-rule', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'dominant-baseline', 'enable-background', 'fill-opacity', 'fill-rule', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-name', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'horiz-adv-x', 'horiz-origin-x', 'image-rendering', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'overline-position', 'overline-thickness', 'paint-order', 'panose-1', 'pointer-events', 'rendering-intent', 'shape-rendering', 'stop-color', 'stop-opacity', 'strikethrough-position', 'strikethrough-thickness', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'underline-position', 'underline-thickness', 'unicode-bidi', 'unicode-range', 'units-per-em', 'v-alphabetic', 'v-hanging', 'v-ideographic', 'v-mathematical', 'vector-effect', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'word-spacing', 'writing-mode', 'xmlns:xlink', 'x-height' // NOTE: if you add a camelCased prop to this list, |
|
// you'll need to set attributeName to name.toLowerCase() |
|
// instead in the assignment below. |
|
].forEach(function (attributeName) { |
|
var name = attributeName.replace(CAMELIZE, capitalize); |
|
properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty |
|
attributeName, null, // attributeNamespace |
|
false, // sanitizeURL |
|
false); |
|
}); // String SVG attributes with the xlink namespace. |
|
|
|
['xlink:actuate', 'xlink:arcrole', 'xlink:role', 'xlink:show', 'xlink:title', 'xlink:type' // NOTE: if you add a camelCased prop to this list, |
|
// you'll need to set attributeName to name.toLowerCase() |
|
// instead in the assignment below. |
|
].forEach(function (attributeName) { |
|
var name = attributeName.replace(CAMELIZE, capitalize); |
|
properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty |
|
attributeName, 'http://www.w3.org/1999/xlink', false, // sanitizeURL |
|
false); |
|
}); // String SVG attributes with the xml namespace. |
|
|
|
['xml:base', 'xml:lang', 'xml:space' // NOTE: if you add a camelCased prop to this list, |
|
// you'll need to set attributeName to name.toLowerCase() |
|
// instead in the assignment below. |
|
].forEach(function (attributeName) { |
|
var name = attributeName.replace(CAMELIZE, capitalize); |
|
properties[name] = new PropertyInfoRecord(name, STRING, false, // mustUseProperty |
|
attributeName, 'http://www.w3.org/XML/1998/namespace', false, // sanitizeURL |
|
false); |
|
}); // These attribute exists both in HTML and SVG. |
|
// The attribute name is case-sensitive in SVG so we can't just use |
|
// the React name like we do for attributes that exist only in HTML. |
|
|
|
['tabIndex', 'crossOrigin'].forEach(function (attributeName) { |
|
properties[attributeName] = new PropertyInfoRecord(attributeName, STRING, false, // mustUseProperty |
|
attributeName.toLowerCase(), // attributeName |
|
null, // attributeNamespace |
|
false, // sanitizeURL |
|
false); |
|
}); // These attributes accept URLs. These must not allow javascript: URLS. |
|
// These will also need to accept Trusted Types object in the future. |
|
|
|
var xlinkHref = 'xlinkHref'; |
|
properties[xlinkHref] = new PropertyInfoRecord('xlinkHref', STRING, false, // mustUseProperty |
|
'xlink:href', 'http://www.w3.org/1999/xlink', true, // sanitizeURL |
|
false); |
|
['src', 'href', 'action', 'formAction'].forEach(function (attributeName) { |
|
properties[attributeName] = new PropertyInfoRecord(attributeName, STRING, false, // mustUseProperty |
|
attributeName.toLowerCase(), // attributeName |
|
null, // attributeNamespace |
|
true, // sanitizeURL |
|
true); |
|
}); |
|
|
|
// and any newline or tab are filtered out as if they're not part of the URL. |
|
// https://url.spec.whatwg.org/#url-parsing |
|
// Tab or newline are defined as \r\n\t: |
|
// https://infra.spec.whatwg.org/#ascii-tab-or-newline |
|
// A C0 control is a code point in the range \u0000 NULL to \u001F |
|
// INFORMATION SEPARATOR ONE, inclusive: |
|
// https://infra.spec.whatwg.org/#c0-control-or-space |
|
|
|
/* eslint-disable max-len */ |
|
|
|
var isJavaScriptProtocol = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*\:/i; |
|
var didWarn = false; |
|
|
|
function sanitizeURL(url) { |
|
{ |
|
if (!didWarn && isJavaScriptProtocol.test(url)) { |
|
didWarn = true; |
|
|
|
error('A future version of React will block javascript: URLs as a security precaution. ' + 'Use event handlers instead if you can. If you need to generate unsafe HTML try ' + 'using dangerouslySetInnerHTML instead. React was passed %s.', JSON.stringify(url)); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* Get the value for a property on a node. Only used in DEV for SSR validation. |
|
* The "expected" argument is used as a hint of what the expected value is. |
|
* Some properties have multiple equivalent values. |
|
*/ |
|
function getValueForProperty(node, name, expected, propertyInfo) { |
|
{ |
|
if (propertyInfo.mustUseProperty) { |
|
var propertyName = propertyInfo.propertyName; |
|
return node[propertyName]; |
|
} else { |
|
// This check protects multiple uses of `expected`, which is why the |
|
// react-internal/safe-string-coercion rule is disabled in several spots |
|
// below. |
|
{ |
|
checkAttributeStringCoercion(expected, name); |
|
} |
|
|
|
if ( propertyInfo.sanitizeURL) { |
|
// If we haven't fully disabled javascript: URLs, and if |
|
// the hydration is successful of a javascript: URL, we |
|
// still want to warn on the client. |
|
// eslint-disable-next-line react-internal/safe-string-coercion |
|
sanitizeURL('' + expected); |
|
} |
|
|
|
var attributeName = propertyInfo.attributeName; |
|
var stringValue = null; |
|
|
|
if (propertyInfo.type === OVERLOADED_BOOLEAN) { |
|
if (node.hasAttribute(attributeName)) { |
|
var value = node.getAttribute(attributeName); |
|
|
|
if (value === '') { |
|
return true; |
|
} |
|
|
|
if (shouldRemoveAttribute(name, expected, propertyInfo, false)) { |
|
return value; |
|
} // eslint-disable-next-line react-internal/safe-string-coercion |
|
|
|
|
|
if (value === '' + expected) { |
|
return expected; |
|
} |
|
|
|
return value; |
|
} |
|
} else if (node.hasAttribute(attributeName)) { |
|
if (shouldRemoveAttribute(name, expected, propertyInfo, false)) { |
|
// We had an attribute but shouldn't have had one, so read it |
|
// for the error message. |
|
return node.getAttribute(attributeName); |
|
} |
|
|
|
if (propertyInfo.type === BOOLEAN) { |
|
// If this was a boolean, it doesn't matter what the value is |
|
// the fact that we have it is the same as the expected. |
|
return expected; |
|
} // Even if this property uses a namespace we use getAttribute |
|
// because we assume its namespaced name is the same as our config. |
|
// To use getAttributeNS we need the local name which we don't have |
|
// in our config atm. |
|
|
|
|
|
stringValue = node.getAttribute(attributeName); |
|
} |
|
|
|
if (shouldRemoveAttribute(name, expected, propertyInfo, false)) { |
|
return stringValue === null ? expected : stringValue; // eslint-disable-next-line react-internal/safe-string-coercion |
|
} else if (stringValue === '' + expected) { |
|
return expected; |
|
} else { |
|
return stringValue; |
|
} |
|
} |
|
} |
|
} |
|
/** |
|
* Get the value for a attribute on a node. Only used in DEV for SSR validation. |
|
* The third argument is used as a hint of what the expected value is. Some |
|
* attributes have multiple equivalent values. |
|
*/ |
|
|
|
function getValueForAttribute(node, name, expected, isCustomComponentTag) { |
|
{ |
|
if (!isAttributeNameSafe(name)) { |
|
return; |
|
} |
|
|
|
if (!node.hasAttribute(name)) { |
|
return expected === undefined ? undefined : null; |
|
} |
|
|
|
var value = node.getAttribute(name); |
|
|
|
{ |
|
checkAttributeStringCoercion(expected, name); |
|
} |
|
|
|
if (value === '' + expected) { |
|
return expected; |
|
} |
|
|
|
return value; |
|
} |
|
} |
|
/** |
|
* Sets the value for a property on a node. |
|
* |
|
* @param {DOMElement} node |
|
* @param {string} name |
|
* @param {*} value |
|
*/ |
|
|
|
function setValueForProperty(node, name, value, isCustomComponentTag) { |
|
var propertyInfo = getPropertyInfo(name); |
|
|
|
if (shouldIgnoreAttribute(name, propertyInfo, isCustomComponentTag)) { |
|
return; |
|
} |
|
|
|
if (shouldRemoveAttribute(name, value, propertyInfo, isCustomComponentTag)) { |
|
value = null; |
|
} |
|
|
|
|
|
if (isCustomComponentTag || propertyInfo === null) { |
|
if (isAttributeNameSafe(name)) { |
|
var _attributeName = name; |
|
|
|
if (value === null) { |
|
node.removeAttribute(_attributeName); |
|
} else { |
|
{ |
|
checkAttributeStringCoercion(value, name); |
|
} |
|
|
|
node.setAttribute(_attributeName, '' + value); |
|
} |
|
} |
|
|
|
return; |
|
} |
|
|
|
var mustUseProperty = propertyInfo.mustUseProperty; |
|
|
|
if (mustUseProperty) { |
|
var propertyName = propertyInfo.propertyName; |
|
|
|
if (value === null) { |
|
var type = propertyInfo.type; |
|
node[propertyName] = type === BOOLEAN ? false : ''; |
|
} else { |
|
// Contrary to `setAttribute`, object properties are properly |
|
// `toString`ed by IE8/9. |
|
node[propertyName] = value; |
|
} |
|
|
|
return; |
|
} // The rest are treated as attributes with special cases. |
|
|
|
|
|
var attributeName = propertyInfo.attributeName, |
|
attributeNamespace = propertyInfo.attributeNamespace; |
|
|
|
if (value === null) { |
|
node.removeAttribute(attributeName); |
|
} else { |
|
var _type = propertyInfo.type; |
|
var attributeValue; |
|
|
|
if (_type === BOOLEAN || _type === OVERLOADED_BOOLEAN && value === true) { |
|
// If attribute type is boolean, we know for sure it won't be an execution sink |
|
// and we won't require Trusted Type here. |
|
attributeValue = ''; |
|
} else { |
|
// `setAttribute` with objects becomes only `[object]` in IE8/9, |
|
// ('' + value) makes it output the correct toString()-value. |
|
{ |
|
{ |
|
checkAttributeStringCoercion(value, attributeName); |
|
} |
|
|
|
attributeValue = '' + value; |
|
} |
|
|
|
if (propertyInfo.sanitizeURL) { |
|
sanitizeURL(attributeValue.toString()); |
|
} |
|
} |
|
|
|
if (attributeNamespace) { |
|
node.setAttributeNS(attributeNamespace, attributeName, attributeValue); |
|
} else { |
|
node.setAttribute(attributeName, attributeValue); |
|
} |
|
} |
|
} |
|
|
|
// ATTENTION |
|
// When adding new symbols to this file, |
|
// Please consider also adding to 'react-devtools-shared/src/backend/ReactSymbols' |
|
// The Symbol used to tag the ReactElement-like types. |
|
var REACT_ELEMENT_TYPE = Symbol.for('react.element'); |
|
var REACT_PORTAL_TYPE = Symbol.for('react.portal'); |
|
var REACT_FRAGMENT_TYPE = Symbol.for('react.fragment'); |
|
var REACT_STRICT_MODE_TYPE = Symbol.for('react.strict_mode'); |
|
var REACT_PROFILER_TYPE = Symbol.for('react.profiler'); |
|
var REACT_PROVIDER_TYPE = Symbol.for('react.provider'); |
|
var REACT_CONTEXT_TYPE = Symbol.for('react.context'); |
|
var REACT_FORWARD_REF_TYPE = Symbol.for('react.forward_ref'); |
|
var REACT_SUSPENSE_TYPE = Symbol.for('react.suspense'); |
|
var REACT_SUSPENSE_LIST_TYPE = Symbol.for('react.suspense_list'); |
|
var REACT_MEMO_TYPE = Symbol.for('react.memo'); |
|
var REACT_LAZY_TYPE = Symbol.for('react.lazy'); |
|
var REACT_SCOPE_TYPE = Symbol.for('react.scope'); |
|
var REACT_DEBUG_TRACING_MODE_TYPE = Symbol.for('react.debug_trace_mode'); |
|
var REACT_OFFSCREEN_TYPE = Symbol.for('react.offscreen'); |
|
var REACT_LEGACY_HIDDEN_TYPE = Symbol.for('react.legacy_hidden'); |
|
var REACT_CACHE_TYPE = Symbol.for('react.cache'); |
|
var REACT_TRACING_MARKER_TYPE = Symbol.for('react.tracing_marker'); |
|
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator; |
|
var FAUX_ITERATOR_SYMBOL = '@@iterator'; |
|
function getIteratorFn(maybeIterable) { |
|
if (maybeIterable === null || typeof maybeIterable !== 'object') { |
|
return null; |
|
} |
|
|
|
var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL]; |
|
|
|
if (typeof maybeIterator === 'function') { |
|
return maybeIterator; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
var assign = Object.assign; |
|
|
|
// Helpers to patch console.logs to avoid logging during side-effect free |
|
// replaying on render function. This currently only patches the object |
|
// lazily which won't cover if the log function was extracted eagerly. |
|
// We could also eagerly patch the method. |
|
var disabledDepth = 0; |
|
var prevLog; |
|
var prevInfo; |
|
var prevWarn; |
|
var prevError; |
|
var prevGroup; |
|
var prevGroupCollapsed; |
|
var prevGroupEnd; |
|
|
|
function disabledLog() {} |
|
|
|
disabledLog.__reactDisabledLog = true; |
|
function disableLogs() { |
|
{ |
|
if (disabledDepth === 0) { |
|
/* eslint-disable react-internal/no-production-logging */ |
|
prevLog = console.log; |
|
prevInfo = console.info; |
|
prevWarn = console.warn; |
|
prevError = console.error; |
|
prevGroup = console.group; |
|
prevGroupCollapsed = console.groupCollapsed; |
|
prevGroupEnd = console.groupEnd; // https://github.com/facebook/react/issues/19099 |
|
|
|
var props = { |
|
configurable: true, |
|
enumerable: true, |
|
value: disabledLog, |
|
writable: true |
|
}; // $FlowFixMe Flow thinks console is immutable. |
|
|
|
Object.defineProperties(console, { |
|
info: props, |
|
log: props, |
|
warn: props, |
|
error: props, |
|
group: props, |
|
groupCollapsed: props, |
|
groupEnd: props |
|
}); |
|
/* eslint-enable react-internal/no-production-logging */ |
|
} |
|
|
|
disabledDepth++; |
|
} |
|
} |
|
function reenableLogs() { |
|
{ |
|
disabledDepth--; |
|
|
|
if (disabledDepth === 0) { |
|
/* eslint-disable react-internal/no-production-logging */ |
|
var props = { |
|
configurable: true, |
|
enumerable: true, |
|
writable: true |
|
}; // $FlowFixMe Flow thinks console is immutable. |
|
|
|
Object.defineProperties(console, { |
|
log: assign({}, props, { |
|
value: prevLog |
|
}), |
|
info: assign({}, props, { |
|
value: prevInfo |
|
}), |
|
warn: assign({}, props, { |
|
value: prevWarn |
|
}), |
|
error: assign({}, props, { |
|
value: prevError |
|
}), |
|
group: assign({}, props, { |
|
value: prevGroup |
|
}), |
|
groupCollapsed: assign({}, props, { |
|
value: prevGroupCollapsed |
|
}), |
|
groupEnd: assign({}, props, { |
|
value: prevGroupEnd |
|
}) |
|
}); |
|
/* eslint-enable react-internal/no-production-logging */ |
|
} |
|
|
|
if (disabledDepth < 0) { |
|
error('disabledDepth fell below zero. ' + 'This is a bug in React. Please file an issue.'); |
|
} |
|
} |
|
} |
|
|
|
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher; |
|
var prefix; |
|
function describeBuiltInComponentFrame(name, source, ownerFn) { |
|
{ |
|
if (prefix === undefined) { |
|
// Extract the VM specific prefix used by each line. |
|
try { |
|
throw Error(); |
|
} catch (x) { |
|
var match = x.stack.trim().match(/\n( *(at )?)/); |
|
prefix = match && match[1] || ''; |
|
} |
|
} // We use the prefix to ensure our stacks line up with native stack frames. |
|
|
|
|
|
return '\n' + prefix + name; |
|
} |
|
} |
|
var reentry = false; |
|
var componentFrameCache; |
|
|
|
{ |
|
var PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map; |
|
componentFrameCache = new PossiblyWeakMap(); |
|
} |
|
|
|
function describeNativeComponentFrame(fn, construct) { |
|
// If something asked for a stack inside a fake render, it should get ignored. |
|
if ( !fn || reentry) { |
|
return ''; |
|
} |
|
|
|
{ |
|
var frame = componentFrameCache.get(fn); |
|
|
|
if (frame !== undefined) { |
|
return frame; |
|
} |
|
} |
|
|
|
var control; |
|
reentry = true; |
|
var previousPrepareStackTrace = Error.prepareStackTrace; // $FlowFixMe It does accept undefined. |
|
|
|
Error.prepareStackTrace = undefined; |
|
var previousDispatcher; |
|
|
|
{ |
|
previousDispatcher = ReactCurrentDispatcher.current; // Set the dispatcher in DEV because this might be call in the render function |
|
// for warnings. |
|
|
|
ReactCurrentDispatcher.current = null; |
|
disableLogs(); |
|
} |
|
|
|
try { |
|
// This should throw. |
|
if (construct) { |
|
// Something should be setting the props in the constructor. |
|
var Fake = function () { |
|
throw Error(); |
|
}; // $FlowFixMe |
|
|
|
|
|
Object.defineProperty(Fake.prototype, 'props', { |
|
set: function () { |
|
// We use a throwing setter instead of frozen or non-writable props |
|
// because that won't throw in a non-strict mode function. |
|
throw Error(); |
|
} |
|
}); |
|
|
|
if (typeof Reflect === 'object' && Reflect.construct) { |
|
// We construct a different control for this case to include any extra |
|
// frames added by the construct call. |
|
try { |
|
Reflect.construct(Fake, []); |
|
} catch (x) { |
|
control = x; |
|
} |
|
|
|
Reflect.construct(fn, [], Fake); |
|
} else { |
|
try { |
|
Fake.call(); |
|
} catch (x) { |
|
control = x; |
|
} |
|
|
|
fn.call(Fake.prototype); |
|
} |
|
} else { |
|
try { |
|
throw Error(); |
|
} catch (x) { |
|
control = x; |
|
} |
|
|
|
fn(); |
|
} |
|
} catch (sample) { |
|
// This is inlined manually because closure doesn't do it for us. |
|
if (sample && control && typeof sample.stack === 'string') { |
|
// This extracts the first frame from the sample that isn't also in the control. |
|
// Skipping one frame that we assume is the frame that calls the two. |
|
var sampleLines = sample.stack.split('\n'); |
|
var controlLines = control.stack.split('\n'); |
|
var s = sampleLines.length - 1; |
|
var c = controlLines.length - 1; |
|
|
|
while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) { |
|
// We expect at least one stack frame to be shared. |
|
// Typically this will be the root most one. However, stack frames may be |
|
// cut off due to maximum stack limits. In this case, one maybe cut off |
|
// earlier than the other. We assume that the sample is longer or the same |
|
// and there for cut off earlier. So we should find the root most frame in |
|
// the sample somewhere in the control. |
|
c--; |
|
} |
|
|
|
for (; s >= 1 && c >= 0; s--, c--) { |
|
// Next we find the first one that isn't the same which should be the |
|
// frame that called our sample function and the control. |
|
if (sampleLines[s] !== controlLines[c]) { |
|
// In V8, the first line is describing the message but other VMs don't. |
|
// If we're about to return the first line, and the control is also on the same |
|
// line, that's a pretty good indicator that our sample threw at same line as |
|
// the control. I.e. before we entered the sample frame. So we ignore this result. |
|
// This can happen if you passed a class to function component, or non-function. |
|
if (s !== 1 || c !== 1) { |
|
do { |
|
s--; |
|
c--; // We may still have similar intermediate frames from the construct call. |
|
// The next one that isn't the same should be our match though. |
|
|
|
if (c < 0 || sampleLines[s] !== controlLines[c]) { |
|
// V8 adds a "new" prefix for native classes. Let's remove it to make it prettier. |
|
var _frame = '\n' + sampleLines[s].replace(' at new ', ' at '); // If our component frame is labeled "<anonymous>" |
|
// but we have a user-provided "displayName" |
|
// splice it in to make the stack more readable. |
|
|
|
|
|
if (fn.displayName && _frame.includes('<anonymous>')) { |
|
_frame = _frame.replace('<anonymous>', fn.displayName); |
|
} |
|
|
|
{ |
|
if (typeof fn === 'function') { |
|
componentFrameCache.set(fn, _frame); |
|
} |
|
} // Return the line we found. |
|
|
|
|
|
return _frame; |
|
} |
|
} while (s >= 1 && c >= 0); |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
} finally { |
|
reentry = false; |
|
|
|
{ |
|
ReactCurrentDispatcher.current = previousDispatcher; |
|
reenableLogs(); |
|
} |
|
|
|
Error.prepareStackTrace = previousPrepareStackTrace; |
|
} // Fallback to just using the name if we couldn't make it throw. |
|
|
|
|
|
var name = fn ? fn.displayName || fn.name : ''; |
|
var syntheticFrame = name ? describeBuiltInComponentFrame(name) : ''; |
|
|
|
{ |
|
if (typeof fn === 'function') { |
|
componentFrameCache.set(fn, syntheticFrame); |
|
} |
|
} |
|
|
|
return syntheticFrame; |
|
} |
|
|
|
function describeClassComponentFrame(ctor, source, ownerFn) { |
|
{ |
|
return describeNativeComponentFrame(ctor, true); |
|
} |
|
} |
|
function describeFunctionComponentFrame(fn, source, ownerFn) { |
|
{ |
|
return describeNativeComponentFrame(fn, false); |
|
} |
|
} |
|
|
|
function shouldConstruct(Component) { |
|
var prototype = Component.prototype; |
|
return !!(prototype && prototype.isReactComponent); |
|
} |
|
|
|
function describeUnknownElementTypeFrameInDEV(type, source, ownerFn) { |
|
|
|
if (type == null) { |
|
return ''; |
|
} |
|
|
|
if (typeof type === 'function') { |
|
{ |
|
return describeNativeComponentFrame(type, shouldConstruct(type)); |
|
} |
|
} |
|
|
|
if (typeof type === 'string') { |
|
return describeBuiltInComponentFrame(type); |
|
} |
|
|
|
switch (type) { |
|
case REACT_SUSPENSE_TYPE: |
|
return describeBuiltInComponentFrame('Suspense'); |
|
|
|
case REACT_SUSPENSE_LIST_TYPE: |
|
return describeBuiltInComponentFrame('SuspenseList'); |
|
} |
|
|
|
if (typeof type === 'object') { |
|
switch (type.$$typeof) { |
|
case REACT_FORWARD_REF_TYPE: |
|
return describeFunctionComponentFrame(type.render); |
|
|
|
case REACT_MEMO_TYPE: |
|
// Memo may contain any component type so we recursively resolve it. |
|
return describeUnknownElementTypeFrameInDEV(type.type, source, ownerFn); |
|
|
|
case REACT_LAZY_TYPE: |
|
{ |
|
var lazyComponent = type; |
|
var payload = lazyComponent._payload; |
|
var init = lazyComponent._init; |
|
|
|
try { |
|
// Lazy may contain any component type so we recursively resolve it. |
|
return describeUnknownElementTypeFrameInDEV(init(payload), source, ownerFn); |
|
} catch (x) {} |
|
} |
|
} |
|
} |
|
|
|
return ''; |
|
} |
|
|
|
function describeFiber(fiber) { |
|
var owner = fiber._debugOwner ? fiber._debugOwner.type : null ; |
|
var source = fiber._debugSource ; |
|
|
|
switch (fiber.tag) { |
|
case HostComponent: |
|
return describeBuiltInComponentFrame(fiber.type); |
|
|
|
case LazyComponent: |
|
return describeBuiltInComponentFrame('Lazy'); |
|
|
|
case SuspenseComponent: |
|
return describeBuiltInComponentFrame('Suspense'); |
|
|
|
case SuspenseListComponent: |
|
return describeBuiltInComponentFrame('SuspenseList'); |
|
|
|
case FunctionComponent: |
|
case IndeterminateComponent: |
|
case SimpleMemoComponent: |
|
return describeFunctionComponentFrame(fiber.type); |
|
|
|
case ForwardRef: |
|
return describeFunctionComponentFrame(fiber.type.render); |
|
|
|
case ClassComponent: |
|
return describeClassComponentFrame(fiber.type); |
|
|
|
default: |
|
return ''; |
|
} |
|
} |
|
|
|
function getStackByFiberInDevAndProd(workInProgress) { |
|
try { |
|
var info = ''; |
|
var node = workInProgress; |
|
|
|
do { |
|
info += describeFiber(node); |
|
node = node.return; |
|
} while (node); |
|
|
|
return info; |
|
} catch (x) { |
|
return '\nError generating stack: ' + x.message + '\n' + x.stack; |
|
} |
|
} |
|
|
|
function getWrappedName(outerType, innerType, wrapperName) { |
|
var displayName = outerType.displayName; |
|
|
|
if (displayName) { |
|
return displayName; |
|
} |
|
|
|
var functionName = innerType.displayName || innerType.name || ''; |
|
return functionName !== '' ? wrapperName + "(" + functionName + ")" : wrapperName; |
|
} // Keep in sync with react-reconciler/getComponentNameFromFiber |
|
|
|
|
|
function getContextName(type) { |
|
return type.displayName || 'Context'; |
|
} // Note that the reconciler package should generally prefer to use getComponentNameFromFiber() instead. |
|
|
|
|
|
function getComponentNameFromType(type) { |
|
if (type == null) { |
|
// Host root, text node or just invalid type. |
|
return null; |
|
} |
|
|
|
{ |
|
if (typeof type.tag === 'number') { |
|
error('Received an unexpected object in getComponentNameFromType(). ' + 'This is likely a bug in React. Please file an issue.'); |
|
} |
|
} |
|
|
|
if (typeof type === 'function') { |
|
return type.displayName || type.name || null; |
|
} |
|
|
|
if (typeof type === 'string') { |
|
return type; |
|
} |
|
|
|
switch (type) { |
|
case REACT_FRAGMENT_TYPE: |
|
return 'Fragment'; |
|
|
|
case REACT_PORTAL_TYPE: |
|
return 'Portal'; |
|
|
|
case REACT_PROFILER_TYPE: |
|
return 'Profiler'; |
|
|
|
case REACT_STRICT_MODE_TYPE: |
|
return 'StrictMode'; |
|
|
|
case REACT_SUSPENSE_TYPE: |
|
return 'Suspense'; |
|
|
|
case REACT_SUSPENSE_LIST_TYPE: |
|
return 'SuspenseList'; |
|
|
|
} |
|
|
|
if (typeof type === 'object') { |
|
switch (type.$$typeof) { |
|
case REACT_CONTEXT_TYPE: |
|
var context = type; |
|
return getContextName(context) + '.Consumer'; |
|
|
|
case REACT_PROVIDER_TYPE: |
|
var provider = type; |
|
return getContextName(provider._context) + '.Provider'; |
|
|
|
case REACT_FORWARD_REF_TYPE: |
|
return getWrappedName(type, type.render, 'ForwardRef'); |
|
|
|
case REACT_MEMO_TYPE: |
|
var outerName = type.displayName || null; |
|
|
|
if (outerName !== null) { |
|
return outerName; |
|
} |
|
|
|
return getComponentNameFromType(type.type) || 'Memo'; |
|
|
|
case REACT_LAZY_TYPE: |
|
{ |
|
var lazyComponent = type; |
|
var payload = lazyComponent._payload; |
|
var init = lazyComponent._init; |
|
|
|
try { |
|
return getComponentNameFromType(init(payload)); |
|
} catch (x) { |
|
return null; |
|
} |
|
} |
|
|
|
// eslint-disable-next-line no-fallthrough |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function getWrappedName$1(outerType, innerType, wrapperName) { |
|
var functionName = innerType.displayName || innerType.name || ''; |
|
return outerType.displayName || (functionName !== '' ? wrapperName + "(" + functionName + ")" : wrapperName); |
|
} // Keep in sync with shared/getComponentNameFromType |
|
|
|
|
|
function getContextName$1(type) { |
|
return type.displayName || 'Context'; |
|
} |
|
|
|
function getComponentNameFromFiber(fiber) { |
|
var tag = fiber.tag, |
|
type = fiber.type; |
|
|
|
switch (tag) { |
|
case CacheComponent: |
|
return 'Cache'; |
|
|
|
case ContextConsumer: |
|
var context = type; |
|
return getContextName$1(context) + '.Consumer'; |
|
|
|
case ContextProvider: |
|
var provider = type; |
|
return getContextName$1(provider._context) + '.Provider'; |
|
|
|
case DehydratedFragment: |
|
return 'DehydratedFragment'; |
|
|
|
case ForwardRef: |
|
return getWrappedName$1(type, type.render, 'ForwardRef'); |
|
|
|
case Fragment: |
|
return 'Fragment'; |
|
|
|
case HostComponent: |
|
// Host component type is the display name (e.g. "div", "View") |
|
return type; |
|
|
|
case HostPortal: |
|
return 'Portal'; |
|
|
|
case HostRoot: |
|
return 'Root'; |
|
|
|
case HostText: |
|
return 'Text'; |
|
|
|
case LazyComponent: |
|
// Name comes from the type in this case; we don't have a tag. |
|
return getComponentNameFromType(type); |
|
|
|
case Mode: |
|
if (type === REACT_STRICT_MODE_TYPE) { |
|
// Don't be less specific than shared/getComponentNameFromType |
|
return 'StrictMode'; |
|
} |
|
|
|
return 'Mode'; |
|
|
|
case OffscreenComponent: |
|
return 'Offscreen'; |
|
|
|
case Profiler: |
|
return 'Profiler'; |
|
|
|
case ScopeComponent: |
|
return 'Scope'; |
|
|
|
case SuspenseComponent: |
|
return 'Suspense'; |
|
|
|
case SuspenseListComponent: |
|
return 'SuspenseList'; |
|
|
|
case TracingMarkerComponent: |
|
return 'TracingMarker'; |
|
// The display name for this tags come from the user-provided type: |
|
|
|
case ClassComponent: |
|
case FunctionComponent: |
|
case IncompleteClassComponent: |
|
case IndeterminateComponent: |
|
case MemoComponent: |
|
case SimpleMemoComponent: |
|
if (typeof type === 'function') { |
|
return type.displayName || type.name || null; |
|
} |
|
|
|
if (typeof type === 'string') { |
|
return type; |
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
return null; |
|
} |
|
|
|
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame; |
|
var current = null; |
|
var isRendering = false; |
|
function getCurrentFiberOwnerNameInDevOrNull() { |
|
{ |
|
if (current === null) { |
|
return null; |
|
} |
|
|
|
var owner = current._debugOwner; |
|
|
|
if (owner !== null && typeof owner !== 'undefined') { |
|
return getComponentNameFromFiber(owner); |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function getCurrentFiberStackInDev() { |
|
{ |
|
if (current === null) { |
|
return ''; |
|
} // Safe because if current fiber exists, we are reconciling, |
|
// and it is guaranteed to be the work-in-progress version. |
|
|
|
|
|
return getStackByFiberInDevAndProd(current); |
|
} |
|
} |
|
|
|
function resetCurrentFiber() { |
|
{ |
|
ReactDebugCurrentFrame.getCurrentStack = null; |
|
current = null; |
|
isRendering = false; |
|
} |
|
} |
|
function setCurrentFiber(fiber) { |
|
{ |
|
ReactDebugCurrentFrame.getCurrentStack = fiber === null ? null : getCurrentFiberStackInDev; |
|
current = fiber; |
|
isRendering = false; |
|
} |
|
} |
|
function getCurrentFiber() { |
|
{ |
|
return current; |
|
} |
|
} |
|
function setIsRendering(rendering) { |
|
{ |
|
isRendering = rendering; |
|
} |
|
} |
|
|
|
// Flow does not allow string concatenation of most non-string types. To work |
|
// around this limitation, we use an opaque type that can only be obtained by |
|
// passing the value through getToStringValue first. |
|
function toString(value) { |
|
// The coercion safety check is performed in getToStringValue(). |
|
// eslint-disable-next-line react-internal/safe-string-coercion |
|
return '' + value; |
|
} |
|
function getToStringValue(value) { |
|
switch (typeof value) { |
|
case 'boolean': |
|
case 'number': |
|
case 'string': |
|
case 'undefined': |
|
return value; |
|
|
|
case 'object': |
|
{ |
|
checkFormFieldValueStringCoercion(value); |
|
} |
|
|
|
return value; |
|
|
|
default: |
|
// function, symbol are assigned as empty strings |
|
return ''; |
|
} |
|
} |
|
|
|
var hasReadOnlyValue = { |
|
button: true, |
|
checkbox: true, |
|
image: true, |
|
hidden: true, |
|
radio: true, |
|
reset: true, |
|
submit: true |
|
}; |
|
function checkControlledValueProps(tagName, props) { |
|
{ |
|
if (!(hasReadOnlyValue[props.type] || props.onChange || props.onInput || props.readOnly || props.disabled || props.value == null)) { |
|
error('You provided a `value` prop to a form field without an ' + '`onChange` handler. This will render a read-only field. If ' + 'the field should be mutable use `defaultValue`. Otherwise, ' + 'set either `onChange` or `readOnly`.'); |
|
} |
|
|
|
if (!(props.onChange || props.readOnly || props.disabled || props.checked == null)) { |
|
error('You provided a `checked` prop to a form field without an ' + '`onChange` handler. This will render a read-only field. If ' + 'the field should be mutable use `defaultChecked`. Otherwise, ' + 'set either `onChange` or `readOnly`.'); |
|
} |
|
} |
|
} |
|
|
|
function isCheckable(elem) { |
|
var type = elem.type; |
|
var nodeName = elem.nodeName; |
|
return nodeName && nodeName.toLowerCase() === 'input' && (type === 'checkbox' || type === 'radio'); |
|
} |
|
|
|
function getTracker(node) { |
|
return node._valueTracker; |
|
} |
|
|
|
function detachTracker(node) { |
|
node._valueTracker = null; |
|
} |
|
|
|
function getValueFromNode(node) { |
|
var value = ''; |
|
|
|
if (!node) { |
|
return value; |
|
} |
|
|
|
if (isCheckable(node)) { |
|
value = node.checked ? 'true' : 'false'; |
|
} else { |
|
value = node.value; |
|
} |
|
|
|
return value; |
|
} |
|
|
|
function trackValueOnNode(node) { |
|
var valueField = isCheckable(node) ? 'checked' : 'value'; |
|
var descriptor = Object.getOwnPropertyDescriptor(node.constructor.prototype, valueField); |
|
|
|
{ |
|
checkFormFieldValueStringCoercion(node[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 === 'undefined' || typeof descriptor.get !== 'function' || typeof descriptor.set !== 'function') { |
|
return; |
|
} |
|
|
|
var get = descriptor.get, |
|
set = descriptor.set; |
|
Object.defineProperty(node, valueField, { |
|
configurable: true, |
|
get: function () { |
|
return get.call(this); |
|
}, |
|
set: function (value) { |
|
{ |
|
checkFormFieldValueStringCoercion(value); |
|
} |
|
|
|
currentValue = '' + value; |
|
set.call(this, value); |
|
} |
|
}); // We could've passed this the first time |
|
// but it triggers a bug in IE11 and Edge 14/15. |
|
// Calling defineProperty() again should be equivalent. |
|
// https://github.com/facebook/react/issues/11768 |
|
|
|
Object.defineProperty(node, valueField, { |
|
enumerable: descriptor.enumerable |
|
}); |
|
var tracker = { |
|
getValue: function () { |
|
return currentValue; |
|
}, |
|
setValue: function (value) { |
|
{ |
|
checkFormFieldValueStringCoercion(value); |
|
} |
|
|
|
currentValue = '' + value; |
|
}, |
|
stopTracking: function () { |
|
detachTracker(node); |
|
delete node[valueField]; |
|
} |
|
}; |
|
return tracker; |
|
} |
|
|
|
function track(node) { |
|
if (getTracker(node)) { |
|
return; |
|
} // TODO: Once it's just Fiber we can move this to node._wrapperState |
|
|
|
|
|
node._valueTracker = trackValueOnNode(node); |
|
} |
|
function updateValueIfChanged(node) { |
|
if (!node) { |
|
return false; |
|
} |
|
|
|
var tracker = getTracker(node); // if there is no tracker at this point it's unlikely |
|
// that trying again will succeed |
|
|
|
if (!tracker) { |
|
return true; |
|
} |
|
|
|
var lastValue = tracker.getValue(); |
|
var nextValue = getValueFromNode(node); |
|
|
|
if (nextValue !== lastValue) { |
|
tracker.setValue(nextValue); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
function getActiveElement(doc) { |
|
doc = doc || (typeof document !== 'undefined' ? document : undefined); |
|
|
|
if (typeof doc === 'undefined') { |
|
return null; |
|
} |
|
|
|
try { |
|
return doc.activeElement || doc.body; |
|
} catch (e) { |
|
return doc.body; |
|
} |
|
} |
|
|
|
var didWarnValueDefaultValue = false; |
|
var didWarnCheckedDefaultChecked = false; |
|
var didWarnControlledToUncontrolled = false; |
|
var didWarnUncontrolledToControlled = false; |
|
|
|
function isControlled(props) { |
|
var usesChecked = props.type === 'checkbox' || props.type === 'radio'; |
|
return usesChecked ? props.checked != null : props.value != null; |
|
} |
|
/** |
|
* Implements an <input> host component that allows setting these optional |
|
* props: `checked`, `value`, `defaultChecked`, and `defaultValue`. |
|
* |
|
* If `checked` or `value` are not supplied (or null/undefined), user actions |
|
* that affect the checked state or value will trigger updates to the element. |
|
* |
|
* If they are supplied (and not null/undefined), the rendered element will not |
|
* trigger updates to the element. Instead, the props must change in order for |
|
* the rendered element to be updated. |
|
* |
|
* The rendered element will be initialized as unchecked (or `defaultChecked`) |
|
* with an empty value (or `defaultValue`). |
|
* |
|
* See http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html |
|
*/ |
|
|
|
|
|
function getHostProps(element, props) { |
|
var node = element; |
|
var checked = props.checked; |
|
var hostProps = assign({}, props, { |
|
defaultChecked: undefined, |
|
defaultValue: undefined, |
|
value: undefined, |
|
checked: checked != null ? checked : node._wrapperState.initialChecked |
|
}); |
|
return hostProps; |
|
} |
|
function initWrapperState(element, props) { |
|
{ |
|
checkControlledValueProps('input', props); |
|
|
|
if (props.checked !== undefined && props.defaultChecked !== undefined && !didWarnCheckedDefaultChecked) { |
|
error('%s contains an input of type %s with both checked and defaultChecked props. ' + 'Input elements must be either controlled or uncontrolled ' + '(specify either the checked prop, or the defaultChecked prop, but not ' + 'both). Decide between using a controlled or uncontrolled input ' + 'element and remove one of these props. More info: ' + 'https://reactjs.org/link/controlled-components', getCurrentFiberOwnerNameInDevOrNull() || 'A component', props.type); |
|
|
|
didWarnCheckedDefaultChecked = true; |
|
} |
|
|
|
if (props.value !== undefined && props.defaultValue !== undefined && !didWarnValueDefaultValue) { |
|
error('%s contains an input of type %s with both value and defaultValue props. ' + 'Input 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 input ' + 'element and remove one of these props. More info: ' + 'https://reactjs.org/link/controlled-components', getCurrentFiberOwnerNameInDevOrNull() || 'A component', props.type); |
|
|
|
didWarnValueDefaultValue = true; |
|
} |
|
} |
|
|
|
var node = element; |
|
var defaultValue = props.defaultValue == null ? '' : props.defaultValue; |
|
node._wrapperState = { |
|
initialChecked: props.checked != null ? props.checked : props.defaultChecked, |
|
initialValue: getToStringValue(props.value != null ? props.value : defaultValue), |
|
controlled: isControlled(props) |
|
}; |
|
} |
|
function updateChecked(element, props) { |
|
var node = element; |
|
var checked = props.checked; |
|
|
|
if (checked != null) { |
|
setValueForProperty(node, 'checked', checked, false); |
|
} |
|
} |
|
function updateWrapper(element, props) { |
|
var node = element; |
|
|
|
{ |
|
var controlled = isControlled(props); |
|
|
|
if (!node._wrapperState.controlled && controlled && !didWarnUncontrolledToControlled) { |
|
error('A component is changing an uncontrolled input to be controlled. ' + 'This is likely caused by the value changing from undefined to ' + 'a defined value, which should not happen. ' + 'Decide between using a controlled or uncontrolled input ' + 'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components'); |
|
|
|
didWarnUncontrolledToControlled = true; |
|
} |
|
|
|
if (node._wrapperState.controlled && !controlled && !didWarnControlledToUncontrolled) { |
|
error('A component is changing a controlled input to be uncontrolled. ' + 'This is likely caused by the value changing from a defined to ' + 'undefined, which should not happen. ' + 'Decide between using a controlled or uncontrolled input ' + 'element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components'); |
|
|
|
didWarnControlledToUncontrolled = true; |
|
} |
|
} |
|
|
|
updateChecked(element, props); |
|
var value = getToStringValue(props.value); |
|
var type = props.type; |
|
|
|
if (value != null) { |
|
if (type === 'number') { |
|
if (value === 0 && node.value === '' || // We explicitly want to coerce to number here if possible. |
|
// eslint-disable-next-line |
|
node.value != value) { |
|
node.value = toString(value); |
|
} |
|
} else if (node.value !== toString(value)) { |
|
node.value = toString(value); |
|
} |
|
} else if (type === 'submit' || type === 'reset') { |
|
// Submit/reset inputs need the attribute removed completely to avoid |
|
// blank-text buttons. |
|
node.removeAttribute('value'); |
|
return; |
|
} |
|
|
|
{ |
|
// When syncing the value attribute, the value comes from a cascade of |
|
// properties: |
|
// 1. The value React property |
|
// 2. The defaultValue React property |
|
// 3. Otherwise there should be no change |
|
if (props.hasOwnProperty('value')) { |
|
setDefaultValue(node, props.type, value); |
|
} else if (props.hasOwnProperty('defaultValue')) { |
|
setDefaultValue(node, props.type, getToStringValue(props.defaultValue)); |
|
} |
|
} |
|
|
|
{ |
|
// When syncing the checked attribute, it only changes when it needs |
|
// to be removed, such as transitioning from a checkbox into a text input |
|
if (props.checked == null && props.defaultChecked != null) { |
|
node.defaultChecked = !!props.defaultChecked; |
|
} |
|
} |
|
} |
|
function postMountWrapper(element, props, isHydrating) { |
|
var node = element; // Do not assign value if it is already set. This prevents user text input |
|
// from being lost during SSR hydration. |
|
|
|
if (props.hasOwnProperty('value') || props.hasOwnProperty('defaultValue')) { |
|
var type = props.type; |
|
var isButton = type === 'submit' || type === 'reset'; // Avoid setting value attribute on submit/reset inputs as it overrides the |
|
// default value provided by the browser. See: #12872 |
|
|
|
if (isButton && (props.value === undefined || props.value === null)) { |
|
return; |
|
} |
|
|
|
var initialValue = toString(node._wrapperState.initialValue); // Do not assign value if it is already set. This prevents user text input |
|
// from being lost during SSR hydration. |
|
|
|
if (!isHydrating) { |
|
{ |
|
// When syncing the value attribute, the value property should use |
|
// the wrapperState._initialValue property. This uses: |
|
// |
|
// 1. The value React property when present |
|
// 2. The defaultValue React property when present |
|
// 3. An empty string |
|
if (initialValue !== node.value) { |
|
node.value = initialValue; |
|
} |
|
} |
|
} |
|
|
|
{ |
|
// Otherwise, the value attribute is synchronized to the property, |
|
// so we assign defaultValue to the same thing as the value property |
|
// assignment step above. |
|
node.defaultValue = initialValue; |
|
} |
|
} // Normally, we'd just do `node.checked = node.checked` upon initial mount, less this bug |
|
// this is needed to work around a chrome bug where setting defaultChecked |
|
// will sometimes influence the value of checked (even after detachment). |
|
// Reference: https://bugs.chromium.org/p/chromium/issues/detail?id=608416 |
|
// We need to temporarily unset name to avoid disrupting radio button groups. |
|
|
|
|
|
var name = node.name; |
|
|
|
if (name !== '') { |
|
node.name = ''; |
|
} |
|
|
|
{ |
|
// When syncing the checked attribute, both the checked property and |
|
// attribute are assigned at the same time using defaultChecked. This uses: |
|
// |
|
// 1. The checked React property when present |
|
// 2. The defaultChecked React property when present |
|
// 3. Otherwise, false |
|
node.defaultChecked = !node.defaultChecked; |
|
node.defaultChecked = !!node._wrapperState.initialChecked; |
|
} |
|
|
|
if (name !== '') { |
|
node.name = name; |
|
} |
|
} |
|
function restoreControlledState(element, props) { |
|
var node = element; |
|
updateWrapper(node, props); |
|
updateNamedCousins(node, props); |
|
} |
|
|
|
function updateNamedCousins(rootNode, props) { |
|
var name = props.name; |
|
|
|
if (props.type === 'radio' && name != null) { |
|
var queryRoot = rootNode; |
|
|
|
while (queryRoot.parentNode) { |
|
queryRoot = queryRoot.parentNode; |
|
} // If `rootNode.form` was non-null, then we could try `form.elements`, |
|
// but that sometimes behaves strangely in IE8. We could also try using |
|
// `form.getElementsByName`, but that will only return direct children |
|
// and won't include inputs that use the HTML5 `form=` attribute. Since |
|
// the input might not even be in a form. It might not even be in the |
|
// document. Let's just use the local `querySelectorAll` to ensure we don't |
|
// miss anything. |
|
|
|
|
|
{ |
|
checkAttributeStringCoercion(name, 'name'); |
|
} |
|
|
|
var group = queryRoot.querySelectorAll('input[name=' + JSON.stringify('' + name) + '][type="radio"]'); |
|
|
|
for (var i = 0; i < group.length; i++) { |
|
var otherNode = group[i]; |
|
|
|
if (otherNode === rootNode || otherNode.form !== rootNode.form) { |
|
continue; |
|
} // This will throw if radio buttons rendered by different copies of React |
|
// and the same name are rendered into the same form (same as #1939). |
|
// That's probably okay; we don't support it just as we don't support |
|
// mixing React radio buttons with non-React ones. |
|
|
|
|
|
var otherProps = getFiberCurrentPropsFromNode(otherNode); |
|
|
|
if (!otherProps) { |
|
throw new Error('ReactDOMInput: Mixing React and non-React radio inputs with the ' + 'same `name` is not supported.'); |
|
} // We need update the tracked value on the named cousin since the value |
|
// was changed but the input saw no event or value set |
|
|
|
|
|
updateValueIfChanged(otherNode); // If this is a controlled radio button group, forcing the input that |
|
// was previously checked to update will cause it to be come re-checked |
|
// as appropriate. |
|
|
|
updateWrapper(otherNode, otherProps); |
|
} |
|
} |
|
} // In Chrome, assigning defaultValue to certain input types triggers input validation. |
|
// For number inputs, the display value loses trailing decimal points. For email inputs, |
|
// Chrome raises "The specified value <x> is not a valid email address". |
|
// |
|
// Here we check to see if the defaultValue has actually changed, avoiding these problems |
|
// when the user is inputting text |
|
// |
|
// https://github.com/facebook/react/issues/7253 |
|
|
|
|
|
function setDefaultValue(node, type, value) { |
|
if ( // Focused number inputs synchronize on blur. See ChangeEventPlugin.js |
|
type !== 'number' || getActiveElement(node.ownerDocument) !== node) { |
|
if (value == null) { |
|
node.defaultValue = toString(node._wrapperState.initialValue); |
|
} else if (node.defaultValue !== toString(value)) { |
|
node.defaultValue = toString(value); |
|
} |
|
} |
|
} |
|
|
|
var didWarnSelectedSetOnOption = false; |
|
var didWarnInvalidChild = false; |
|
var didWarnInvalidInnerHTML = false; |
|
/** |
|
* Implements an <option> host component that warns when `selected` is set. |
|
*/ |
|
|
|
function validateProps(element, props) { |
|
{ |
|
// If a value is not provided, then the children must be simple. |
|
if (props.value == null) { |
|
if (typeof props.children === 'object' && props.children !== null) { |
|
React.Children.forEach(props.children, function (child) { |
|
if (child == null) { |
|
return; |
|
} |
|
|
|
if (typeof child === 'string' || typeof child === 'number') { |
|
return; |
|
} |
|
|
|
if (!didWarnInvalidChild) { |
|
didWarnInvalidChild = true; |
|
|
|
error('Cannot infer the option value of complex children. ' + 'Pass a `value` prop or use a plain string as children to <option>.'); |
|
} |
|
}); |
|
} else if (props.dangerouslySetInnerHTML != null) { |
|
if (!didWarnInvalidInnerHTML) { |
|
didWarnInvalidInnerHTML = true; |
|
|
|
error('Pass a `value` prop if you set dangerouslyInnerHTML so React knows ' + 'which value should be selected.'); |
|
} |
|
} |
|
} // TODO: Remove support for `selected` in <option>. |
|
|
|
|
|
if (props.selected != null && !didWarnSelectedSetOnOption) { |
|
error('Use the `defaultValue` or `value` props on <select> instead of ' + 'setting `selected` on <option>.'); |
|
|
|
didWarnSelectedSetOnOption = true; |
|
} |
|
} |
|
} |
|
function postMountWrapper$1(element, props) { |
|
// value="" should make a value attribute (#6219) |
|
if (props.value != null) { |
|
element.setAttribute('value', toString(getToStringValue(props.value))); |
|
} |
|
} |
|
|
|
var isArrayImpl = Array.isArray; // eslint-disable-next-line no-redeclare |
|
|
|
function isArray(a) { |
|
return isArrayImpl(a); |
|
} |
|
|
|
var didWarnValueDefaultValue$1; |
|
|
|
{ |
|
didWarnValueDefaultValue$1 = false; |
|
} |
|
|
|
function getDeclarationErrorAddendum() { |
|
var ownerName = getCurrentFiberOwnerNameInDevOrNull(); |
|
|
|
if (ownerName) { |
|
return '\n\nCheck the render method of `' + ownerName + '`.'; |
|
} |
|
|
|
return ''; |
|
} |
|
|
|
var valuePropNames = ['value', 'defaultValue']; |
|
/** |
|
* Validation function for `value` and `defaultValue`. |
|
*/ |
|
|
|
function checkSelectPropTypes(props) { |
|
{ |
|
checkControlledValueProps('select', props); |
|
|
|
for (var i = 0; i < valuePropNames.length; i++) { |
|
var propName = valuePropNames[i]; |
|
|
|
if (props[propName] == null) { |
|
continue; |
|
} |
|
|
|
var propNameIsArray = isArray(props[propName]); |
|
|
|
if (props.multiple && !propNameIsArray) { |
|
error('The `%s` prop supplied to <select> must be an array if ' + '`multiple` is true.%s', propName, getDeclarationErrorAddendum()); |
|
} else if (!props.multiple && propNameIsArray) { |
|
error('The `%s` prop supplied to <select> must be a scalar ' + 'value if `multiple` is false.%s', propName, getDeclarationErrorAddendum()); |
|
} |
|
} |
|
} |
|
} |
|
|
|
function updateOptions(node, multiple, propValue, setDefaultSelected) { |
|
var options = node.options; |
|
|
|
if (multiple) { |
|
var selectedValues = propValue; |
|
var selectedValue = {}; |
|
|
|
for (var i = 0; i < selectedValues.length; i++) { |
|
// Prefix to avoid chaos with special keys. |
|
selectedValue['$' + selectedValues[i]] = true; |
|
} |
|
|
|
for (var _i = 0; _i < options.length; _i++) { |
|
var 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. |
|
var _selectedValue = toString(getToStringValue(propValue)); |
|
|
|
var defaultSelected = null; |
|
|
|
for (var _i2 = 0; _i2 < options.length; _i2++) { |
|
if (options[_i2].value === _selectedValue) { |
|
options[_i2].selected = true; |
|
|
|
if (setDefaultSelected) { |
|
options[_i2].defaultSelected = true; |
|
} |
|
|
|
return; |
|
} |
|
|
|
if (defaultSelected === null && !options[_i2].disabled) { |
|
defaultSelected = options[_i2]; |
|
} |
|
} |
|
|
|
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. |
|
*/ |
|
|
|
|
|
function getHostProps$1(element, props) { |
|
return assign({}, props, { |
|
value: undefined |
|
}); |
|
} |
|
function initWrapperState$1(element, props) { |
|
var node = element; |
|
|
|
{ |
|
checkSelectPropTypes(props); |
|
} |
|
|
|
node._wrapperState = { |
|
wasMultiple: !!props.multiple |
|
}; |
|
|
|
{ |
|
if (props.value !== undefined && props.defaultValue !== undefined && !didWarnValueDefaultValue$1) { |
|
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$1 = true; |
|
} |
|
} |
|
} |
|
function postMountWrapper$2(element, props) { |
|
var node = element; |
|
node.multiple = !!props.multiple; |
|
var value = props.value; |
|
|
|
if (value != null) { |
|
updateOptions(node, !!props.multiple, value, false); |
|
} else if (props.defaultValue != null) { |
|
updateOptions(node, !!props.multiple, props.defaultValue, true); |
|
} |
|
} |
|
function postUpdateWrapper(element, props) { |
|
var node = element; |
|
var wasMultiple = node._wrapperState.wasMultiple; |
|
node._wrapperState.wasMultiple = !!props.multiple; |
|
var 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); |
|
} |
|
} |
|
} |
|
function restoreControlledState$1(element, props) { |
|
var node = element; |
|
var value = props.value; |
|
|
|
if (value != null) { |
|
updateOptions(node, !!props.multiple, value, false); |
|
} |
|
} |
|
|
|
var didWarnValDefaultVal = false; |
|
|
|
/** |
|
* Implements a <textarea> host component that allows setting `value`, and |
|
* `defaultValue`. This differs from the traditional DOM API because value is |
|
* usually set as PCDATA children. |
|
* |
|
* If `value` is not supplied (or null/undefined), user actions that affect the |
|
* value will trigger updates to the element. |
|
* |
|
* If `value` is supplied (and not null/undefined), the rendered element will |
|
* not trigger updates to the element. Instead, the `value` prop must change in |
|
* order for the rendered element to be updated. |
|
* |
|
* The rendered element will be initialized with an empty value, the prop |
|
* `defaultValue` if specified, or the children content (deprecated). |
|
*/ |
|
function getHostProps$2(element, props) { |
|
var node = element; |
|
|
|
if (props.dangerouslySetInnerHTML != null) { |
|
throw new Error('`dangerouslySetInnerHTML` does not make sense on <textarea>.'); |
|
} // Always set children to the same thing. In IE9, the selection range will |
|
// get reset if `textContent` is mutated. We could add a check in setTextContent |
|
// to only set the value if/when the value differs from the node value (which would |
|
// completely solve this IE9 bug), but Sebastian+Sophie seemed to like this |
|
// solution. The value can be a boolean or object so that's why it's forced |
|
// to be a string. |
|
|
|
|
|
var hostProps = assign({}, props, { |
|
value: undefined, |
|
defaultValue: undefined, |
|
children: toString(node._wrapperState.initialValue) |
|
}); |
|
|
|
return hostProps; |
|
} |
|
function initWrapperState$2(element, props) { |
|
var node = element; |
|
|
|
{ |
|
checkControlledValueProps('textarea', props); |
|
|
|
if (props.value !== undefined && props.defaultValue !== undefined && !didWarnValDefaultVal) { |
|
error('%s contains a textarea with both value and defaultValue props. ' + 'Textarea 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 textarea ' + 'and remove one of these props. More info: ' + 'https://reactjs.org/link/controlled-components', getCurrentFiberOwnerNameInDevOrNull() || 'A component'); |
|
|
|
didWarnValDefaultVal = true; |
|
} |
|
} |
|
|
|
var initialValue = props.value; // Only bother fetching default value if we're going to use it |
|
|
|
if (initialValue == null) { |
|
var children = props.children, |
|
defaultValue = props.defaultValue; |
|
|
|
if (children != null) { |
|
{ |
|
error('Use the `defaultValue` or `value` props instead of setting ' + 'children on <textarea>.'); |
|
} |
|
|
|
{ |
|
if (defaultValue != null) { |
|
throw new Error('If you supply `defaultValue` on a <textarea>, do not pass children.'); |
|
} |
|
|
|
if (isArray(children)) { |
|
if (children.length > 1) { |
|
throw new Error('<textarea> can only have at most one child.'); |
|
} |
|
|
|
children = children[0]; |
|
} |
|
|
|
defaultValue = children; |
|
} |
|
} |
|
|
|
if (defaultValue == null) { |
|
defaultValue = ''; |
|
} |
|
|
|
initialValue = defaultValue; |
|
} |
|
|
|
node._wrapperState = { |
|
initialValue: getToStringValue(initialValue) |
|
}; |
|
} |
|
function updateWrapper$1(element, props) { |
|
var node = element; |
|
var value = getToStringValue(props.value); |
|
var defaultValue = getToStringValue(props.defaultValue); |
|
|
|
if (value != null) { |
|
// Cast `value` to a string to ensure the value is set correctly. While |
|
// browsers typically do this as necessary, jsdom doesn't. |
|
var newValue = toString(value); // To avoid side effects (such as losing text selection), only set value if changed |
|
|
|
if (newValue !== node.value) { |
|
node.value = newValue; |
|
} |
|
|
|
if (props.defaultValue == null && node.defaultValue !== newValue) { |
|
node.defaultValue = newValue; |
|
} |
|
} |
|
|
|
if (defaultValue != null) { |
|
node.defaultValue = toString(defaultValue); |
|
} |
|
} |
|
function postMountWrapper$3(element, props) { |
|
var node = element; // This is in postMount because we need access to the DOM node, which is not |
|
// available until after the component has mounted. |
|
|
|
var textContent = node.textContent; // Only set node.value if textContent is equal to the expected |
|
// initial value. In IE10/IE11 there is a bug where the placeholder attribute |
|
// will populate textContent as well. |
|
// https://developer.microsoft.com/microsoft-edge/platform/issues/101525/ |
|
|
|
if (textContent === node._wrapperState.initialValue) { |
|
if (textContent !== '' && textContent !== null) { |
|
node.value = textContent; |
|
} |
|
} |
|
} |
|
function restoreControlledState$2(element, props) { |
|
// DOM component is still mounted; update |
|
updateWrapper$1(element, props); |
|
} |
|
|
|
var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; |
|
var MATH_NAMESPACE = 'http://www.w3.org/1998/Math/MathML'; |
|
var SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; // Assumes there is no parent namespace. |
|
|
|
function getIntrinsicNamespace(type) { |
|
switch (type) { |
|
case 'svg': |
|
return SVG_NAMESPACE; |
|
|
|
case 'math': |
|
return MATH_NAMESPACE; |
|
|
|
default: |
|
return HTML_NAMESPACE; |
|
} |
|
} |
|
function getChildNamespace(parentNamespace, type) { |
|
if (parentNamespace == null || parentNamespace === HTML_NAMESPACE) { |
|
// No (or default) parent namespace: potential entry point. |
|
return getIntrinsicNamespace(type); |
|
} |
|
|
|
if (parentNamespace === SVG_NAMESPACE && type === 'foreignObject') { |
|
// We're leaving SVG. |
|
return HTML_NAMESPACE; |
|
} // By default, pass namespace below. |
|
|
|
|
|
return parentNamespace; |
|
} |
|
|
|
/* globals MSApp */ |
|
|
|
/** |
|
* Create a function which has 'unsafe' privileges (required by windows8 apps) |
|
*/ |
|
var createMicrosoftUnsafeLocalFunction = function (func) { |
|
if (typeof MSApp !== 'undefined' && MSApp.execUnsafeLocalFunction) { |
|
return function (arg0, arg1, arg2, arg3) { |
|
MSApp.execUnsafeLocalFunction(function () { |
|
return func(arg0, arg1, arg2, arg3); |
|
}); |
|
}; |
|
} else { |
|
return func; |
|
} |
|
}; |
|
|
|
var reusableSVGContainer; |
|
/** |
|
* Set the innerHTML property of a node |
|
* |
|
* @param {DOMElement} node |
|
* @param {string} html |
|
* @internal |
|
*/ |
|
|
|
var setInnerHTML = createMicrosoftUnsafeLocalFunction(function (node, html) { |
|
if (node.namespaceURI === SVG_NAMESPACE) { |
|
|
|
if (!('innerHTML' in node)) { |
|
// IE does not have innerHTML for SVG nodes, so instead we inject the |
|
// new markup in a temp node and then move the child nodes across into |
|
// the target node |
|
reusableSVGContainer = reusableSVGContainer || document.createElement('div'); |
|
reusableSVGContainer.innerHTML = '<svg>' + html.valueOf().toString() + '</svg>'; |
|
var svgNode = reusableSVGContainer.firstChild; |
|
|
|
while (node.firstChild) { |
|
node.removeChild(node.firstChild); |
|
} |
|
|
|
while (svgNode.firstChild) { |
|
node.appendChild(svgNode.firstChild); |
|
} |
|
|
|
return; |
|
} |
|
} |
|
|
|
node.innerHTML = html; |
|
}); |
|
|
|
/** |
|
* HTML nodeType values that represent the type of the node |
|
*/ |
|
var ELEMENT_NODE = 1; |
|
var TEXT_NODE = 3; |
|
var COMMENT_NODE = 8; |
|
var DOCUMENT_NODE = 9; |
|
var DOCUMENT_FRAGMENT_NODE = 11; |
|
|
|
/** |
|
* Set the textContent property of a node. For text updates, it's faster |
|
* to set the `nodeValue` of the Text node directly instead of using |
|
* `.textContent` which will remove the existing node and create a new one. |
|
* |
|
* @param {DOMElement} node |
|
* @param {string} text |
|
* @internal |
|
*/ |
|
|
|
var setTextContent = function (node, text) { |
|
if (text) { |
|
var firstChild = node.firstChild; |
|
|
|
if (firstChild && firstChild === node.lastChild && firstChild.nodeType === TEXT_NODE) { |
|
firstChild.nodeValue = text; |
|
return; |
|
} |
|
} |
|
|
|
node.textContent = text; |
|
}; |
|
|
|
// List derived from Gecko source code: |
|
// https://github.com/mozilla/gecko-dev/blob/4e638efc71/layout/style/test/property_database.js |
|
var shorthandToLonghand = { |
|
animation: ['animationDelay', 'animationDirection', 'animationDuration', 'animationFillMode', 'animationIterationCount', 'animationName', 'animationPlayState', 'animationTimingFunction'], |
|
background: ['backgroundAttachment', 'backgroundClip', 'backgroundColor', 'backgroundImage', 'backgroundOrigin', 'backgroundPositionX', 'backgroundPositionY', 'backgroundRepeat', 'backgroundSize'], |
|
backgroundPosition: ['backgroundPositionX', 'backgroundPositionY'], |
|
border: ['borderBottomColor', 'borderBottomStyle', 'borderBottomWidth', 'borderImageOutset', 'borderImageRepeat', 'borderImageSlice', 'borderImageSource', 'borderImageWidth', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth', 'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderTopColor', 'borderTopStyle', 'borderTopWidth'], |
|
borderBlockEnd: ['borderBlockEndColor', 'borderBlockEndStyle', 'borderBlockEndWidth'], |
|
borderBlockStart: ['borderBlockStartColor', 'borderBlockStartStyle', 'borderBlockStartWidth'], |
|
borderBottom: ['borderBottomColor', 'borderBottomStyle', 'borderBottomWidth'], |
|
borderColor: ['borderBottomColor', 'borderLeftColor', 'borderRightColor', 'borderTopColor'], |
|
borderImage: ['borderImageOutset', 'borderImageRepeat', 'borderImageSlice', 'borderImageSource', 'borderImageWidth'], |
|
borderInlineEnd: ['borderInlineEndColor', 'borderInlineEndStyle', 'borderInlineEndWidth'], |
|
borderInlineStart: ['borderInlineStartColor', 'borderInlineStartStyle', 'borderInlineStartWidth'], |
|
borderLeft: ['borderLeftColor', 'borderLeftStyle', 'borderLeftWidth'], |
|
borderRadius: ['borderBottomLeftRadius', 'borderBottomRightRadius', 'borderTopLeftRadius', 'borderTopRightRadius'], |
|
borderRight: ['borderRightColor', 'borderRightStyle', 'borderRightWidth'], |
|
borderStyle: ['borderBottomStyle', 'borderLeftStyle', 'borderRightStyle', 'borderTopStyle'], |
|
borderTop: ['borderTopColor', 'borderTopStyle', 'borderTopWidth'], |
|
borderWidth: ['borderBottomWidth', 'borderLeftWidth', 'borderRightWidth', 'borderTopWidth'], |
|
columnRule: ['columnRuleColor', 'columnRuleStyle', 'columnRuleWidth'], |
|
columns: ['columnCount', 'columnWidth'], |
|
flex: ['flexBasis', 'flexGrow', 'flexShrink'], |
|
flexFlow: ['flexDirection', 'flexWrap'], |
|
font: ['fontFamily', 'fontFeatureSettings', 'fontKerning', 'fontLanguageOverride', 'fontSize', 'fontSizeAdjust', 'fontStretch', 'fontStyle', 'fontVariant', 'fontVariantAlternates', 'fontVariantCaps', 'fontVariantEastAsian', 'fontVariantLigatures', 'fontVariantNumeric', 'fontVariantPosition', 'fontWeight', 'lineHeight'], |
|
fontVariant: ['fontVariantAlternates', 'fontVariantCaps', 'fontVariantEastAsian', 'fontVariantLigatures', 'fontVariantNumeric', 'fontVariantPosition'], |
|
gap: ['columnGap', 'rowGap'], |
|
grid: ['gridAutoColumns', 'gridAutoFlow', 'gridAutoRows', 'gridTemplateAreas', 'gridTemplateColumns', 'gridTemplateRows'], |
|
gridArea: ['gridColumnEnd', 'gridColumnStart', 'gridRowEnd', 'gridRowStart'], |
|
gridColumn: ['gridColumnEnd', 'gridColumnStart'], |
|
gridColumnGap: ['columnGap'], |
|
gridGap: ['columnGap', 'rowGap'], |
|
gridRow: ['gridRowEnd', 'gridRowStart'], |
|
gridRowGap: ['rowGap'], |
|
gridTemplate: ['gridTemplateAreas', 'gridTemplateColumns', 'gridTemplateRows'], |
|
listStyle: ['listStyleImage', 'listStylePosition', 'listStyleType'], |
|
margin: ['marginBottom', 'marginLeft', 'marginRight', 'marginTop'], |
|
marker: ['markerEnd', 'markerMid', 'markerStart'], |
|
mask: ['maskClip', 'maskComposite', 'maskImage', 'maskMode', 'maskOrigin', 'maskPositionX', 'maskPositionY', 'maskRepeat', 'maskSize'], |
|
maskPosition: ['maskPositionX', 'maskPositionY'], |
|
outline: ['outlineColor', 'outlineStyle', 'outlineWidth'], |
|
overflow: ['overflowX', 'overflowY'], |
|
padding: ['paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop'], |
|
placeContent: ['alignContent', 'justifyContent'], |
|
placeItems: ['alignItems', 'justifyItems'], |
|
placeSelf: ['alignSelf', 'justifySelf'], |
|
textDecoration: ['textDecorationColor', 'textDecorationLine', 'textDecorationStyle'], |
|
textEmphasis: ['textEmphasisColor', 'textEmphasisStyle'], |
|
transition: ['transitionDelay', 'transitionDuration', 'transitionProperty', 'transitionTimingFunction'], |
|
wordWrap: ['overflowWrap'] |
|
}; |
|
|
|
/** |
|
* CSS properties which accept numbers but are not in units of "px". |
|
*/ |
|
var isUnitlessNumber = { |
|
animationIterationCount: true, |
|
aspectRatio: true, |
|
borderImageOutset: true, |
|
borderImageSlice: true, |
|
borderImageWidth: true, |
|
boxFlex: true, |
|
boxFlexGroup: true, |
|
boxOrdinalGroup: true, |
|
columnCount: true, |
|
columns: true, |
|
flex: true, |
|
flexGrow: true, |
|
flexPositive: true, |
|
flexShrink: true, |
|
flexNegative: true, |
|
flexOrder: true, |
|
gridArea: true, |
|
gridRow: true, |
|
gridRowEnd: true, |
|
gridRowSpan: true, |
|
gridRowStart: true, |
|
gridColumn: true, |
|
gridColumnEnd: true, |
|
gridColumnSpan: true, |
|
gridColumnStart: true, |
|
fontWeight: true, |
|
lineClamp: true, |
|
lineHeight: true, |
|
opacity: true, |
|
order: true, |
|
orphans: true, |
|
tabSize: true, |
|
widows: true, |
|
zIndex: true, |
|
zoom: true, |
|
// SVG-related properties |
|
fillOpacity: true, |
|
floodOpacity: true, |
|
stopOpacity: true, |
|
strokeDasharray: true, |
|
strokeDashoffset: true, |
|
strokeMiterlimit: true, |
|
strokeOpacity: true, |
|
strokeWidth: true |
|
}; |
|
/** |
|
* @param {string} prefix vendor-specific prefix, eg: Webkit |
|
* @param {string} key style name, eg: transitionDuration |
|
* @return {string} style name prefixed with `prefix`, properly camelCased, eg: |
|
* WebkitTransitionDuration |
|
*/ |
|
|
|
function prefixKey(prefix, key) { |
|
return prefix + key.charAt(0).toUpperCase() + key.substring(1); |
|
} |
|
/** |
|
* Support style names that may come passed in prefixed by adding permutations |
|
* of vendor prefixes. |
|
*/ |
|
|
|
|
|
var prefixes = ['Webkit', 'ms', 'Moz', 'O']; // Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an |
|
// infinite loop, because it iterates over the newly added props too. |
|
|
|
Object.keys(isUnitlessNumber).forEach(function (prop) { |
|
prefixes.forEach(function (prefix) { |
|
isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop]; |
|
}); |
|
}); |
|
|
|
/** |
|
* Convert a value into the proper css writable value. The style name `name` |
|
* should be logical (no hyphens), as specified |
|
* in `CSSProperty.isUnitlessNumber`. |
|
* |
|
* @param {string} name CSS property name such as `topMargin`. |
|
* @param {*} value CSS property value such as `10px`. |
|
* @return {string} Normalized style value with dimensions applied. |
|
*/ |
|
|
|
function dangerousStyleValue(name, value, isCustomProperty) { |
|
// Note that we've removed escapeTextForBrowser() calls here since the |
|
// whole string will be escaped when the attribute is injected into |
|
// the markup. If you provide unsafe user data here they can inject |
|
// arbitrary CSS which may be problematic (I couldn't repro this): |
|
// https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet |
|
// http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/ |
|
// This is not an XSS hole but instead a potential CSS injection issue |
|
// which has lead to a greater discussion about how we're going to |
|
// trust URLs moving forward. See #2115901 |
|
var isEmpty = value == null || typeof value === 'boolean' || value === ''; |
|
|
|
if (isEmpty) { |
|
return ''; |
|
} |
|
|
|
if (!isCustomProperty && typeof value === 'number' && value !== 0 && !(isUnitlessNumber.hasOwnProperty(name) && isUnitlessNumber[name])) { |
|
return value + 'px'; // Presumes implicit 'px' suffix for unitless numbers |
|
} |
|
|
|
{ |
|
checkCSSPropertyStringCoercion(value, name); |
|
} |
|
|
|
return ('' + value).trim(); |
|
} |
|
|
|
var uppercasePattern = /([A-Z])/g; |
|
var msPattern = /^ms-/; |
|
/** |
|
* Hyphenates a camelcased CSS property name, for example: |
|
* |
|
* > hyphenateStyleName('backgroundColor') |
|
* < "background-color" |
|
* > hyphenateStyleName('MozTransition') |
|
* < "-moz-transition" |
|
* > hyphenateStyleName('msTransition') |
|
* < "-ms-transition" |
|
* |
|
* As Modernizr suggests (http://modernizr.com/docs/#prefixed), an `ms` prefix |
|
* is converted to `-ms-`. |
|
*/ |
|
|
|
function hyphenateStyleName(name) { |
|
return name.replace(uppercasePattern, '-$1').toLowerCase().replace(msPattern, '-ms-'); |
|
} |
|
|
|
var warnValidStyle = function () {}; |
|
|
|
{ |
|
// 'msTransform' is correct, but the other prefixes should be capitalized |
|
var badVendoredStyleNamePattern = /^(?:webkit|moz|o)[A-Z]/; |
|
var msPattern$1 = /^-ms-/; |
|
var hyphenPattern = /-(.)/g; // style values shouldn't contain a semicolon |
|
|
|
var badStyleValueWithSemicolonPattern = /;\s*$/; |
|
var warnedStyleNames = {}; |
|
var warnedStyleValues = {}; |
|
var warnedForNaNValue = false; |
|
var warnedForInfinityValue = false; |
|
|
|
var camelize = function (string) { |
|
return string.replace(hyphenPattern, function (_, character) { |
|
return character.toUpperCase(); |
|
}); |
|
}; |
|
|
|
var warnHyphenatedStyleName = function (name) { |
|
if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) { |
|
return; |
|
} |
|
|
|
warnedStyleNames[name] = true; |
|
|
|
error('Unsupported style property %s. Did you mean %s?', name, // As Andi Smith suggests |
|
// (http://www.andismith.com/blog/2012/02/modernizr-prefixed/), an `-ms` prefix |
|
// is converted to lowercase `ms`. |
|
camelize(name.replace(msPattern$1, 'ms-'))); |
|
}; |
|
|
|
var warnBadVendoredStyleName = function (name) { |
|
if (warnedStyleNames.hasOwnProperty(name) && warnedStyleNames[name]) { |
|
return; |
|
} |
|
|
|
warnedStyleNames[name] = true; |
|
|
|
error('Unsupported vendor-prefixed style property %s. Did you mean %s?', name, name.charAt(0).toUpperCase() + name.slice(1)); |
|
}; |
|
|
|
var warnStyleValueWithSemicolon = function (name, value) { |
|
if (warnedStyleValues.hasOwnProperty(value) && warnedStyleValues[value]) { |
|
return; |
|
} |
|
|
|
warnedStyleValues[value] = true; |
|
|
|
error("Style property values shouldn't contain a semicolon. " + 'Try "%s: %s" instead.', name, value.replace(badStyleValueWithSemicolonPattern, '')); |
|
}; |
|
|
|
var warnStyleValueIsNaN = function (name, value) { |
|
if (warnedForNaNValue) { |
|
return; |
|
} |
|
|
|
warnedForNaNValue = true; |
|
|
|
error('`NaN` is an invalid value for the `%s` css style property.', name); |
|
}; |
|
|
|
var warnStyleValueIsInfinity = function (name, value) { |
|
if (warnedForInfinityValue) { |
|
return; |
|
} |
|
|
|
warnedForInfinityValue = true; |
|
|
|
error('`Infinity` is an invalid value for the `%s` css style property.', name); |
|
}; |
|
|
|
warnValidStyle = function (name, value) { |
|
if (name.indexOf('-') > -1) { |
|
warnHyphenatedStyleName(name); |
|
} else if (badVendoredStyleNamePattern.test(name)) { |
|
warnBadVendoredStyleName(name); |
|
} else if (badStyleValueWithSemicolonPattern.test(value)) { |
|
warnStyleValueWithSemicolon(name, value); |
|
} |
|
|
|
if (typeof value === 'number') { |
|
if (isNaN(value)) { |
|
warnStyleValueIsNaN(name, value); |
|
} else if (!isFinite(value)) { |
|
warnStyleValueIsInfinity(name, value); |
|
} |
|
} |
|
}; |
|
} |
|
|
|
var warnValidStyle$1 = warnValidStyle; |
|
|
|
/** |
|
* Operations for dealing with CSS properties. |
|
*/ |
|
|
|
/** |
|
* This creates a string that is expected to be equivalent to the style |
|
* attribute generated by server-side rendering. It by-passes warnings and |
|
* security checks so it's not safe to use this value for anything other than |
|
* comparison. It is only used in DEV for SSR validation. |
|
*/ |
|
|
|
function createDangerousStringForStyles(styles) { |
|
{ |
|
var serialized = ''; |
|
var delimiter = ''; |
|
|
|
for (var styleName in styles) { |
|
if (!styles.hasOwnProperty(styleName)) { |
|
continue; |
|
} |
|
|
|
var styleValue = styles[styleName]; |
|
|
|
if (styleValue != null) { |
|
var isCustomProperty = styleName.indexOf('--') === 0; |
|
serialized += delimiter + (isCustomProperty ? styleName : hyphenateStyleName(styleName)) + ':'; |
|
serialized += dangerousStyleValue(styleName, styleValue, isCustomProperty); |
|
delimiter = ';'; |
|
} |
|
} |
|
|
|
return serialized || null; |
|
} |
|
} |
|
/** |
|
* Sets the value for multiple styles on a node. If a value is specified as |
|
* '' (empty string), the corresponding style property will be unset. |
|
* |
|
* @param {DOMElement} node |
|
* @param {object} styles |
|
*/ |
|
|
|
function setValueForStyles(node, styles) { |
|
var style = node.style; |
|
|
|
for (var styleName in styles) { |
|
if (!styles.hasOwnProperty(styleName)) { |
|
continue; |
|
} |
|
|
|
var isCustomProperty = styleName.indexOf('--') === 0; |
|
|
|
{ |
|
if (!isCustomProperty) { |
|
warnValidStyle$1(styleName, styles[styleName]); |
|
} |
|
} |
|
|
|
var styleValue = dangerousStyleValue(styleName, styles[styleName], isCustomProperty); |
|
|
|
if (styleName === 'float') { |
|
styleName = 'cssFloat'; |
|
} |
|
|
|
if (isCustomProperty) { |
|
style.setProperty(styleName, styleValue); |
|
} else { |
|
style[styleName] = styleValue; |
|
} |
|
} |
|
} |
|
|
|
function isValueEmpty(value) { |
|
return value == null || typeof value === 'boolean' || value === ''; |
|
} |
|
/** |
|
* Given {color: 'red', overflow: 'hidden'} returns { |
|
* color: 'color', |
|
* overflowX: 'overflow', |
|
* overflowY: 'overflow', |
|
* }. This can be read as "the overflowY property was set by the overflow |
|
* shorthand". That is, the values are the property that each was derived from. |
|
*/ |
|
|
|
|
|
function expandShorthandMap(styles) { |
|
var expanded = {}; |
|
|
|
for (var key in styles) { |
|
var longhands = shorthandToLonghand[key] || [key]; |
|
|
|
for (var i = 0; i < longhands.length; i++) { |
|
expanded[longhands[i]] = key; |
|
} |
|
} |
|
|
|
return expanded; |
|
} |
|
/** |
|
* When mixing shorthand and longhand property names, we warn during updates if |
|
* we expect an incorrect result to occur. In particular, we warn for: |
|
* |
|
* Updating a shorthand property (longhand gets overwritten): |
|
* {font: 'foo', fontVariant: 'bar'} -> {font: 'baz', fontVariant: 'bar'} |
|
* becomes .style.font = 'baz' |
|
* Removing a shorthand property (longhand gets lost too): |
|
* {font: 'foo', fontVariant: 'bar'} -> {fontVariant: 'bar'} |
|
* becomes .style.font = '' |
|
* Removing a longhand property (should revert to shorthand; doesn't): |
|
* {font: 'foo', fontVariant: 'bar'} -> {font: 'foo'} |
|
* becomes .style.fontVariant = '' |
|
*/ |
|
|
|
|
|
function validateShorthandPropertyCollisionInDev(styleUpdates, nextStyles) { |
|
{ |
|
if (!nextStyles) { |
|
return; |
|
} |
|
|
|
var expandedUpdates = expandShorthandMap(styleUpdates); |
|
var expandedStyles = expandShorthandMap(nextStyles); |
|
var warnedAbout = {}; |
|
|
|
for (var key in expandedUpdates) { |
|
var originalKey = expandedUpdates[key]; |
|
var correctOriginalKey = expandedStyles[key]; |
|
|
|
if (correctOriginalKey && originalKey !== correctOriginalKey) { |
|
var warningKey = originalKey + ',' + correctOriginalKey; |
|
|
|
if (warnedAbout[warningKey]) { |
|
continue; |
|
} |
|
|
|
warnedAbout[warningKey] = true; |
|
|
|
error('%s a style property during rerender (%s) when a ' + 'conflicting property is set (%s) can lead to styling bugs. To ' + "avoid this, don't mix shorthand and non-shorthand properties " + 'for the same value; instead, replace the shorthand with ' + 'separate values.', isValueEmpty(styleUpdates[originalKey]) ? 'Removing' : 'Updating', originalKey, correctOriginalKey); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// For HTML, certain tags should omit their close tag. We keep a list for |
|
// those special-case tags. |
|
var omittedCloseTags = { |
|
area: true, |
|
base: true, |
|
br: true, |
|
col: true, |
|
embed: true, |
|
hr: true, |
|
img: true, |
|
input: true, |
|
keygen: true, |
|
link: true, |
|
meta: true, |
|
param: true, |
|
source: true, |
|
track: true, |
|
wbr: true // NOTE: menuitem's close tag should be omitted, but that causes problems. |
|
|
|
}; |
|
|
|
// `omittedCloseTags` except that `menuitem` should still have its closing tag. |
|
|
|
var voidElementTags = assign({ |
|
menuitem: true |
|
}, omittedCloseTags); |
|
|
|
var HTML = '__html'; |
|
|
|
function assertValidProps(tag, props) { |
|
if (!props) { |
|
return; |
|
} // Note the use of `==` which checks for null or undefined. |
|
|
|
|
|
if (voidElementTags[tag]) { |
|
if (props.children != null || props.dangerouslySetInnerHTML != null) { |
|
throw new Error(tag + " is a void element tag and must neither have `children` nor " + 'use `dangerouslySetInnerHTML`.'); |
|
} |
|
} |
|
|
|
if (props.dangerouslySetInnerHTML != null) { |
|
if (props.children != null) { |
|
throw new Error('Can only set one of `children` or `props.dangerouslySetInnerHTML`.'); |
|
} |
|
|
|
if (typeof props.dangerouslySetInnerHTML !== 'object' || !(HTML in props.dangerouslySetInnerHTML)) { |
|
throw new Error('`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' + 'Please visit https://reactjs.org/link/dangerously-set-inner-html ' + 'for more information.'); |
|
} |
|
} |
|
|
|
{ |
|
if (!props.suppressContentEditableWarning && props.contentEditable && props.children != null) { |
|
error('A component is `contentEditable` and contains `children` managed by ' + 'React. It is now your responsibility to guarantee that none of ' + 'those nodes are unexpectedly modified or duplicated. This is ' + 'probably not intentional.'); |
|
} |
|
} |
|
|
|
if (props.style != null && typeof props.style !== 'object') { |
|
throw new Error('The `style` prop expects a mapping from style properties to values, ' + "not a string. For example, style={{marginRight: spacing + 'em'}} when " + 'using JSX.'); |
|
} |
|
} |
|
|
|
function isCustomComponent(tagName, props) { |
|
if (tagName.indexOf('-') === -1) { |
|
return typeof props.is === 'string'; |
|
} |
|
|
|
switch (tagName) { |
|
// These are reserved SVG and MathML elements. |
|
// We don't mind this list too much because we expect it to never grow. |
|
// The alternative is to track the namespace in a few places which is convoluted. |
|
// https://w3c.github.io/webcomponents/spec/custom/#custom-elements-core-concepts |
|
case 'annotation-xml': |
|
case 'color-profile': |
|
case 'font-face': |
|
case 'font-face-src': |
|
case 'font-face-uri': |
|
case 'font-face-format': |
|
case 'font-face-name': |
|
case 'missing-glyph': |
|
return false; |
|
|
|
default: |
|
return true; |
|
} |
|
} |
|
|
|
// When adding attributes to the HTML or SVG allowed attribute list, be sure to |
|
// also add them to this module to ensure casing and incorrect name |
|
// warnings. |
|
var possibleStandardNames = { |
|
// HTML |
|
accept: 'accept', |
|
acceptcharset: 'acceptCharset', |
|
'accept-charset': 'acceptCharset', |
|
accesskey: 'accessKey', |
|
action: 'action', |
|
allowfullscreen: 'allowFullScreen', |
|
alt: 'alt', |
|
as: 'as', |
|
async: 'async', |
|
autocapitalize: 'autoCapitalize', |
|
autocomplete: 'autoComplete', |
|
autocorrect: 'autoCorrect', |
|
autofocus: 'autoFocus', |
|
autoplay: 'autoPlay', |
|
autosave: 'autoSave', |
|
capture: 'capture', |
|
cellpadding: 'cellPadding', |
|
cellspacing: 'cellSpacing', |
|
challenge: 'challenge', |
|
charset: 'charSet', |
|
checked: 'checked', |
|
children: 'children', |
|
cite: 'cite', |
|
class: 'className', |
|
classid: 'classID', |
|
classname: 'className', |
|
cols: 'cols', |
|
colspan: 'colSpan', |
|
content: 'content', |
|
contenteditable: 'contentEditable', |
|
contextmenu: 'contextMenu', |
|
controls: 'controls', |
|
controlslist: 'controlsList', |
|
coords: 'coords', |
|
crossorigin: 'crossOrigin', |
|
dangerouslysetinnerhtml: 'dangerouslySetInnerHTML', |
|
data: 'data', |
|
datetime: 'dateTime', |
|
default: 'default', |
|
defaultchecked: 'defaultChecked', |
|
defaultvalue: 'defaultValue', |
|
defer: 'defer', |
|
dir: 'dir', |
|
disabled: 'disabled', |
|
disablepictureinpicture: 'disablePictureInPicture', |
|
disableremoteplayback: 'disableRemotePlayback', |
|
download: 'download', |
|
draggable: 'draggable', |
|
enctype: 'encType', |
|
enterkeyhint: 'enterKeyHint', |
|
for: 'htmlFor', |
|
form: 'form', |
|
formmethod: 'formMethod', |
|
formaction: 'formAction', |
|
formenctype: 'formEncType', |
|
formnovalidate: 'formNoValidate', |
|
formtarget: 'formTarget', |
|
frameborder: 'frameBorder', |
|
headers: 'headers', |
|
height: 'height', |
|
hidden: 'hidden', |
|
high: 'high', |
|
href: 'href', |
|
hreflang: 'hrefLang', |
|
htmlfor: 'htmlFor', |
|
httpequiv: 'httpEquiv', |
|
'http-equiv': 'httpEquiv', |
|
icon: 'icon', |
|
id: 'id', |
|
imagesizes: 'imageSizes', |
|
imagesrcset: 'imageSrcSet', |
|
innerhtml: 'innerHTML', |
|
inputmode: 'inputMode', |
|
integrity: 'integrity', |
|
is: 'is', |
|
itemid: 'itemID', |
|
itemprop: 'itemProp', |
|
itemref: 'itemRef', |
|
itemscope: 'itemScope', |
|
itemtype: 'itemType', |
|
keyparams: 'keyParams', |
|
keytype: 'keyType', |
|
kind: 'kind', |
|
label: 'label', |
|
lang: 'lang', |
|
list: 'list', |
|
loop: 'loop', |
|
low: 'low', |
|
manifest: 'manifest', |
|
marginwidth: 'marginWidth', |
|
marginheight: 'marginHeight', |
|
max: 'max', |
|
maxlength: 'maxLength', |
|
media: 'media', |
|
mediagroup: 'mediaGroup', |
|
method: 'method', |
|
min: 'min', |
|
minlength: 'minLength', |
|
multiple: 'multiple', |
|
muted: 'muted', |
|
name: 'name', |
|
nomodule: 'noModule', |
|
nonce: 'nonce', |
|
novalidate: 'noValidate', |
|
open: 'open', |
|
optimum: 'optimum', |
|
pattern: 'pattern', |
|
placeholder: 'placeholder', |
|
playsinline: 'playsInline', |
|
poster: 'poster', |
|
preload: 'preload', |
|
profile: 'profile', |
|
radiogroup: 'radioGroup', |
|
readonly: 'readOnly', |
|
referrerpolicy: 'referrerPolicy', |
|
rel: 'rel', |
|
required: 'required', |
|
reversed: 'reversed', |
|
role: 'role', |
|
rows: 'rows', |
|
rowspan: 'rowSpan', |
|
sandbox: 'sandbox', |
|
scope: 'scope', |
|
scoped: 'scoped', |
|
scrolling: 'scrolling', |
|
seamless: 'seamless', |
|
selected: 'selected', |
|
shape: 'shape', |
|
size: 'size', |
|
sizes: 'sizes', |
|
span: 'span', |
|
spellcheck: 'spellCheck', |
|
src: 'src', |
|
srcdoc: 'srcDoc', |
|
srclang: 'srcLang', |
|
srcset: 'srcSet', |
|
start: 'start', |
|
step: 'step', |
|
style: 'style', |
|
summary: 'summary', |
|
tabindex: 'tabIndex', |
|
target: 'target', |
|
title: 'title', |
|
type: 'type', |
|
usemap: 'useMap', |
|
value: 'value', |
|
width: 'width', |
|
wmode: 'wmode', |
|
wrap: 'wrap', |
|
// SVG |
|
about: 'about', |
|
accentheight: 'accentHeight', |
|
'accent-height': 'accentHeight', |
|
accumulate: 'accumulate', |
|
additive: 'additive', |
|
alignmentbaseline: 'alignmentBaseline', |
|
'alignment-baseline': 'alignmentBaseline', |
|
allowreorder: 'allowReorder', |
|
alphabetic: 'alphabetic', |
|
amplitude: 'amplitude', |
|
arabicform: 'arabicForm', |
|
'arabic-form': 'arabicForm', |
|
ascent: 'ascent', |
|
attributename: 'attributeName', |
|
attributetype: 'attributeType', |
|
autoreverse: 'autoReverse', |
|
azimuth: 'azimuth', |
|
basefrequency: 'baseFrequency', |
|
baselineshift: 'baselineShift', |
|
'baseline-shift': 'baselineShift', |
|
baseprofile: 'baseProfile', |
|
bbox: 'bbox', |
|
begin: 'begin', |
|
bias: 'bias', |
|
by: 'by', |
|
calcmode: 'calcMode', |
|
capheight: 'capHeight', |
|
'cap-height': 'capHeight', |
|
clip: 'clip', |
|
clippath: 'clipPath', |
|
'clip-path': 'clipPath', |
|
clippathunits: 'clipPathUnits', |
|
cliprule: 'clipRule', |
|
'clip-rule': 'clipRule', |
|
color: 'color', |
|
colorinterpolation: 'colorInterpolation', |
|
'color-interpolation': 'colorInterpolation', |
|
colorinterpolationfilters: 'colorInterpolationFilters', |
|
'color-interpolation-filters': 'colorInterpolationFilters', |
|
colorprofile: 'colorProfile', |
|
'color-profile': 'colorProfile', |
|
colorrendering: 'colorRendering', |
|
'color-rendering': 'colorRendering', |
|
contentscripttype: 'contentScriptType', |
|
contentstyletype: 'contentStyleType', |
|
cursor: 'cursor', |
|
cx: 'cx', |
|
cy: 'cy', |
|
d: 'd', |
|
datatype: 'datatype', |
|
decelerate: 'decelerate', |
|
descent: 'descent', |
|
diffuseconstant: 'diffuseConstant', |
|
direction: 'direction', |
|
display: 'display', |
|
divisor: 'divisor', |
|
dominantbaseline: 'dominantBaseline', |
|
'dominant-baseline': 'dominantBaseline', |
|
dur: 'dur', |
|
dx: 'dx', |
|
dy: 'dy', |
|
edgemode: 'edgeMode', |
|
elevation: 'elevation', |
|
enablebackground: 'enableBackground', |
|
'enable-background': 'enableBackground', |
|
end: 'end', |
|
exponent: 'exponent', |
|
externalresourcesrequired: 'externalResourcesRequired', |
|
fill: 'fill', |
|
fillopacity: 'fillOpacity', |
|
'fill-opacity': 'fillOpacity', |
|
fillrule: 'fillRule', |
|
'fill-rule': 'fillRule', |
|
filter: 'filter', |
|
filterres: 'filterRes', |
|
filterunits: 'filterUnits', |
|
floodopacity: 'floodOpacity', |
|
'flood-opacity': 'floodOpacity', |
|
floodcolor: 'floodColor', |
|
'flood-color': 'floodColor', |
|
focusable: 'focusable', |
|
fontfamily: 'fontFamily', |
|
'font-family': 'fontFamily', |
|
fontsize: 'fontSize', |
|
'font-size': 'fontSize', |
|
fontsizeadjust: 'fontSizeAdjust', |
|
'font-size-adjust': 'fontSizeAdjust', |
|
fontstretch: 'fontStretch', |
|
'font-stretch': 'fontStretch', |
|
fontstyle: 'fontStyle', |
|
'font-style': 'fontStyle', |
|
fontvariant: 'fontVariant', |
|
'font-variant': 'fontVariant', |
|
fontweight: 'fontWeight', |
|
'font-weight': 'fontWeight', |
|
format: 'format', |
|
from: 'from', |
|
fx: 'fx', |
|
fy: 'fy', |
|
g1: 'g1', |
|
g2: 'g2', |
|
glyphname: 'glyphName', |
|
'glyph-name': 'glyphName', |
|
glyphorientationhorizontal: 'glyphOrientationHorizontal', |
|
'glyph-orientation-horizontal': 'glyphOrientationHorizontal', |
|
glyphorientationvertical: 'glyphOrientationVertical', |
|
'glyph-orientation-vertical': 'glyphOrientationVertical', |
|
glyphref: 'glyphRef', |
|
gradienttransform: 'gradientTransform', |
|
gradientunits: 'gradientUnits', |
|
hanging: 'hanging', |
|
horizadvx: 'horizAdvX', |
|
'horiz-adv-x': 'horizAdvX', |
|
horizoriginx: 'horizOriginX', |
|
'horiz-origin-x': 'horizOriginX', |
|
ideographic: 'ideographic', |
|
imagerendering: 'imageRendering', |
|
'image-rendering': 'imageRendering', |
|
in2: 'in2', |
|
in: 'in', |
|
inlist: 'inlist', |
|
intercept: 'intercept', |
|
k1: 'k1', |
|
k2: 'k2', |
|
k3: 'k3', |
|
k4: 'k4', |
|
k: 'k', |
|
kernelmatrix: 'kernelMatrix', |
|
kernelunitlength: 'kernelUnitLength', |
|
kerning: 'kerning', |
|
keypoints: 'keyPoints', |
|
keysplines: 'keySplines', |
|
keytimes: 'keyTimes', |
|
lengthadjust: 'lengthAdjust', |
|
letterspacing: 'letterSpacing', |
|
'letter-spacing': 'letterSpacing', |
|
lightingcolor: 'lightingColor', |
|
'lighting-color': 'lightingColor', |
|
limitingconeangle: 'limitingConeAngle', |
|
local: 'local', |
|
markerend: 'markerEnd', |
|
'marker-end': 'markerEnd', |
|
markerheight: 'markerHeight', |
|
markermid: 'markerMid', |
|
'marker-mid': 'markerMid', |
|
markerstart: 'markerStart', |
|
'marker-start': 'markerStart', |
|
markerunits: 'markerUnits', |
|
markerwidth: 'markerWidth', |
|
mask: 'mask', |
|
maskcontentunits: 'maskContentUnits', |
|
maskunits: 'maskUnits', |
|
mathematical: 'mathematical', |
|
mode: 'mode', |
|
numoctaves: 'numOctaves', |
|
offset: 'offset', |
|
opacity: 'opacity', |
|
operator: 'operator', |
|
order: 'order', |
|
orient: 'orient', |
|
orientation: 'orientation', |
|
origin: 'origin', |
|
overflow: 'overflow', |
|
overlineposition: 'overlinePosition', |
|
'overline-position': 'overlinePosition', |
|
overlinethickness: 'overlineThickness', |
|
'overline-thickness': 'overlineThickness', |
|
paintorder: 'paintOrder', |
|
'paint-order': 'paintOrder', |
|
panose1: 'panose1', |
|
'panose-1': 'panose1', |
|
pathlength: 'pathLength', |
|
patterncontentunits: 'patternContentUnits', |
|
patterntransform: 'patternTransform', |
|
patternunits: 'patternUnits', |
|
pointerevents: 'pointerEvents', |
|
'pointer-events': 'pointerEvents', |
|
points: 'points', |
|
pointsatx: 'pointsAtX', |
|
pointsaty: 'pointsAtY', |
|
pointsatz: 'pointsAtZ', |
|
prefix: 'prefix', |
|
preservealpha: 'preserveAlpha', |
|
preserveaspectratio: 'preserveAspectRatio', |
|
primitiveunits: 'primitiveUnits', |
|
property: 'property', |
|
r: 'r', |
|
radius: 'radius', |
|
refx: 'refX', |
|
refy: 'refY', |
|
renderingintent: 'renderingIntent', |
|
'rendering-intent': 'renderingIntent', |
|
repeatcount: 'repeatCount', |
|
repeatdur: 'repeatDur', |
|
requiredextensions: 'requiredExtensions', |
|
requiredfeatures: 'requiredFeatures', |
|
resource: 'resource', |
|
restart: 'restart', |
|
result: 'result', |
|
results: 'results', |
|
rotate: 'rotate', |
|
rx: 'rx', |
|
ry: 'ry', |
|
scale: 'scale', |
|
security: 'security', |
|
seed: 'seed', |
|
shaperendering: 'shapeRendering', |
|
'shape-rendering': 'shapeRendering', |
|
slope: 'slope', |
|
spacing: 'spacing', |
|
specularconstant: 'specularConstant', |
|
specularexponent: 'specularExponent', |
|
speed: 'speed', |
|
spreadmethod: 'spreadMethod', |
|
startoffset: 'startOffset', |
|
stddeviation: 'stdDeviation', |
|
stemh: 'stemh', |
|
stemv: 'stemv', |
|
stitchtiles: 'stitchTiles', |
|
stopcolor: 'stopColor', |
|
'stop-color': 'stopColor', |
|
stopopacity: 'stopOpacity', |
|
'stop-opacity': 'stopOpacity', |
|
strikethroughposition: 'strikethroughPosition', |
|
'strikethrough-position': 'strikethroughPosition', |
|
strikethroughthickness: 'strikethroughThickness', |
|
'strikethrough-thickness': 'strikethroughThickness', |
|
string: 'string', |
|
stroke: 'stroke', |
|
strokedasharray: 'strokeDasharray', |
|
'stroke-dasharray': 'strokeDasharray', |
|
strokedashoffset: 'strokeDashoffset', |
|
'stroke-dashoffset': 'strokeDashoffset', |
|
strokelinecap: 'strokeLinecap', |
|
'stroke-linecap': 'strokeLinecap', |
|
strokelinejoin: 'strokeLinejoin', |
|
'stroke-linejoin': 'strokeLinejoin', |
|
strokemiterlimit: 'strokeMiterlimit', |
|
'stroke-miterlimit': 'strokeMiterlimit', |
|
strokewidth: 'strokeWidth', |
|
'stroke-width': 'strokeWidth', |
|
strokeopacity: 'strokeOpacity', |
|
'stroke-opacity': 'strokeOpacity', |
|
suppresscontenteditablewarning: 'suppressContentEditableWarning', |
|
suppresshydrationwarning: 'suppressHydrationWarning', |
|
surfacescale: 'surfaceScale', |
|
systemlanguage: 'systemLanguage', |
|
tablevalues: 'tableValues', |
|
targetx: 'targetX', |
|
targety: 'targetY', |
|
textanchor: 'textAnchor', |
|
'text-anchor': 'textAnchor', |
|
textdecoration: 'textDecoration', |
|
'text-decoration': 'textDecoration', |
|
textlength: 'textLength', |
|
textrendering: 'textRendering', |
|
'text-rendering': 'textRendering', |
|
to: 'to', |
|
transform: 'transform', |
|
typeof: 'typeof', |
|
u1: 'u1', |
|
u2: 'u2', |
|
underlineposition: 'underlinePosition', |
|
'underline-position': 'underlinePosition', |
|
underlinethickness: 'underlineThickness', |
|
'underline-thickness': 'underlineThickness', |
|
unicode: 'unicode', |
|
unicodebidi: 'unicodeBidi', |
|
'unicode-bidi': 'unicodeBidi', |
|
unicoderange: 'unicodeRange', |
|
'unicode-range': 'unicodeRange', |
|
unitsperem: 'unitsPerEm', |
|
'units-per-em': 'unitsPerEm', |
|
unselectable: 'unselectable', |
|
valphabetic: 'vAlphabetic', |
|
'v-alphabetic': 'vAlphabetic', |
|
values: 'values', |
|
vectoreffect: 'vectorEffect', |
|
'vector-effect': 'vectorEffect', |
|
version: 'version', |
|
vertadvy: 'vertAdvY', |
|
'vert-adv-y': 'vertAdvY', |
|
vertoriginx: 'vertOriginX', |
|
'vert-origin-x': 'vertOriginX', |
|
vertoriginy: 'vertOriginY', |
|
'vert-origin-y': 'vertOriginY', |
|
vhanging: 'vHanging', |
|
'v-hanging': 'vHanging', |
|
videographic: 'vIdeographic', |
|
'v-ideographic': 'vIdeographic', |
|
viewbox: 'viewBox', |
|
viewtarget: 'viewTarget', |
|
visibility: 'visibility', |
|
vmathematical: 'vMathematical', |
|
'v-mathematical': 'vMathematical', |
|
vocab: 'vocab', |
|
widths: 'widths', |
|
wordspacing: 'wordSpacing', |
|
'word-spacing': 'wordSpacing', |
|
writingmode: 'writingMode', |
|
'writing-mode': 'writingMode', |
|
x1: 'x1', |
|
x2: 'x2', |
|
x: 'x', |
|
xchannelselector: 'xChannelSelector', |
|
xheight: 'xHeight', |
|
'x-height': 'xHeight', |
|
xlinkactuate: 'xlinkActuate', |
|
'xlink:actuate': 'xlinkActuate', |
|
xlinkarcrole: 'xlinkArcrole', |
|
'xlink:arcrole': 'xlinkArcrole', |
|
xlinkhref: 'xlinkHref', |
|
'xlink:href': 'xlinkHref', |
|
xlinkrole: 'xlinkRole', |
|
'xlink:role': 'xlinkRole', |
|
xlinkshow: 'xlinkShow', |
|
'xlink:show': 'xlinkShow', |
|
xlinktitle: 'xlinkTitle', |
|
'xlink:title': 'xlinkTitle', |
|
xlinktype: 'xlinkType', |
|
'xlink:type': 'xlinkType', |
|
xmlbase: 'xmlBase', |
|
'xml:base': 'xmlBase', |
|
xmllang: 'xmlLang', |
|
'xml:lang': 'xmlLang', |
|
xmlns: 'xmlns', |
|
'xml:space': 'xmlSpace', |
|
xmlnsxlink: 'xmlnsXlink', |
|
'xmlns:xlink': 'xmlnsXlink', |
|
xmlspace: 'xmlSpace', |
|
y1: 'y1', |
|
y2: 'y2', |
|
y: 'y', |
|
ychannelselector: 'yChannelSelector', |
|
z: 'z', |
|
zoomandpan: 'zoomAndPan' |
|
}; |
|
|
|
var ariaProperties = { |
|
'aria-current': 0, |
|
// state |
|
'aria-description': 0, |
|
'aria-details': 0, |
|
'aria-disabled': 0, |
|
// state |
|
'aria-hidden': 0, |
|
// state |
|
'aria-invalid': 0, |
|
// state |
|
'aria-keyshortcuts': 0, |
|
'aria-label': 0, |
|
'aria-roledescription': 0, |
|
// Widget Attributes |
|
'aria-autocomplete': 0, |
|
'aria-checked': 0, |
|
'aria-expanded': 0, |
|
'aria-haspopup': 0, |
|
'aria-level': 0, |
|
'aria-modal': 0, |
|
'aria-multiline': 0, |
|
'aria-multiselectable': 0, |
|
'aria-orientation': 0, |
|
'aria-placeholder': 0, |
|
'aria-pressed': 0, |
|
'aria-readonly': 0, |
|
'aria-required': 0, |
|
'aria-selected': 0, |
|
'aria-sort': 0, |
|
'aria-valuemax': 0, |
|
'aria-valuemin': 0, |
|
'aria-valuenow': 0, |
|
'aria-valuetext': 0, |
|
// Live Region Attributes |
|
'aria-atomic': 0, |
|
'aria-busy': 0, |
|
'aria-live': 0, |
|
'aria-relevant': 0, |
|
// Drag-and-Drop Attributes |
|
'aria-dropeffect': 0, |
|
'aria-grabbed': 0, |
|
// Relationship Attributes |
|
'aria-activedescendant': 0, |
|
'aria-colcount': 0, |
|
'aria-colindex': 0, |
|
'aria-colspan': 0, |
|
'aria-controls': 0, |
|
'aria-describedby': 0, |
|
'aria-errormessage': 0, |
|
'aria-flowto': 0, |
|
'aria-labelledby': 0, |
|
'aria-owns': 0, |
|
'aria-posinset': 0, |
|
'aria-rowcount': 0, |
|
'aria-rowindex': 0, |
|
'aria-rowspan': 0, |
|
'aria-setsize': 0 |
|
}; |
|
|
|
var warnedProperties = {}; |
|
var rARIA = new RegExp('^(aria)-[' + ATTRIBUTE_NAME_CHAR + ']*$'); |
|
var rARIACamel = new RegExp('^(aria)[A-Z][' + ATTRIBUTE_NAME_CHAR + ']*$'); |
|
|
|
function validateProperty(tagName, name) { |
|
{ |
|
if (hasOwnProperty.call(warnedProperties, name) && warnedProperties[name]) { |
|
return true; |
|
} |
|
|
|
if (rARIACamel.test(name)) { |
|
var ariaName = 'aria-' + name.slice(4).toLowerCase(); |
|
var correctName = ariaProperties.hasOwnProperty(ariaName) ? ariaName : null; // If this is an aria-* attribute, but is not listed in the known DOM |
|
// DOM properties, then it is an invalid aria-* attribute. |
|
|
|
if (correctName == null) { |
|
error('Invalid ARIA attribute `%s`. ARIA attributes follow the pattern aria-* and must be lowercase.', name); |
|
|
|
warnedProperties[name] = true; |
|
return true; |
|
} // aria-* attributes should be lowercase; suggest the lowercase version. |
|
|
|
|
|
if (name !== correctName) { |
|
error('Invalid ARIA attribute `%s`. Did you mean `%s`?', name, correctName); |
|
|
|
warnedProperties[name] = true; |
|
return true; |
|
} |
|
} |
|
|
|
if (rARIA.test(name)) { |
|
var lowerCasedName = name.toLowerCase(); |
|
var standardName = ariaProperties.hasOwnProperty(lowerCasedName) ? lowerCasedName : null; // If this is an aria-* attribute, but is not listed in the known DOM |
|
// DOM properties, then it is an invalid aria-* attribute. |
|
|
|
if (standardName == null) { |
|
warnedProperties[name] = true; |
|
return false; |
|
} // aria-* attributes should be lowercase; suggest the lowercase version. |
|
|
|
|
|
if (name !== standardName) { |
|
error('Unknown ARIA attribute `%s`. Did you mean `%s`?', name, standardName); |
|
|
|
warnedProperties[name] = true; |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function warnInvalidARIAProps(type, props) { |
|
{ |
|
var invalidProps = []; |
|
|
|
for (var key in props) { |
|
var isValid = validateProperty(type, key); |
|
|
|
if (!isValid) { |
|
invalidProps.push(key); |
|
} |
|
} |
|
|
|
var unknownPropString = invalidProps.map(function (prop) { |
|
return '`' + prop + '`'; |
|
}).join(', '); |
|
|
|
if (invalidProps.length === 1) { |
|
error('Invalid aria prop %s on <%s> tag. ' + 'For details, see https://reactjs.org/link/invalid-aria-props', unknownPropString, type); |
|
} else if (invalidProps.length > 1) { |
|
error('Invalid aria props %s on <%s> tag. ' + 'For details, see https://reactjs.org/link/invalid-aria-props', unknownPropString, type); |
|
} |
|
} |
|
} |
|
|
|
function validateProperties(type, props) { |
|
if (isCustomComponent(type, props)) { |
|
return; |
|
} |
|
|
|
warnInvalidARIAProps(type, props); |
|
} |
|
|
|
var didWarnValueNull = false; |
|
function validateProperties$1(type, props) { |
|
{ |
|
if (type !== 'input' && type !== 'textarea' && type !== 'select') { |
|
return; |
|
} |
|
|
|
if (props != null && props.value === null && !didWarnValueNull) { |
|
didWarnValueNull = true; |
|
|
|
if (type === 'select' && props.multiple) { |
|
error('`value` prop on `%s` should not be null. ' + 'Consider using an empty array when `multiple` is set to `true` ' + 'to clear the component or `undefined` for uncontrolled components.', type); |
|
} else { |
|
error('`value` prop on `%s` should not be null. ' + 'Consider using an empty string to clear the component or `undefined` ' + 'for uncontrolled components.', type); |
|
} |
|
} |
|
} |
|
} |
|
|
|
var validateProperty$1 = function () {}; |
|
|
|
{ |
|
var warnedProperties$1 = {}; |
|
var EVENT_NAME_REGEX = /^on./; |
|
var INVALID_EVENT_NAME_REGEX = /^on[^A-Z]/; |
|
var rARIA$1 = new RegExp('^(aria)-[' + ATTRIBUTE_NAME_CHAR + ']*$'); |
|
var rARIACamel$1 = new RegExp('^(aria)[A-Z][' + ATTRIBUTE_NAME_CHAR + ']*$'); |
|
|
|
validateProperty$1 = function (tagName, name, value, eventRegistry) { |
|
if (hasOwnProperty.call(warnedProperties$1, name) && warnedProperties$1[name]) { |
|
return true; |
|
} |
|
|
|
var lowerCasedName = name.toLowerCase(); |
|
|
|
if (lowerCasedName === 'onfocusin' || lowerCasedName === 'onfocusout') { |
|
error('React uses onFocus and onBlur instead of onFocusIn and onFocusOut. ' + 'All React events are normalized to bubble, so onFocusIn and onFocusOut ' + 'are not needed/supported by React.'); |
|
|
|
warnedProperties$1[name] = true; |
|
return true; |
|
} // We can't rely on the event system being injected on the server. |
|
|
|
|
|
if (eventRegistry != null) { |
|
var registrationNameDependencies = eventRegistry.registrationNameDependencies, |
|
possibleRegistrationNames = eventRegistry.possibleRegistrationNames; |
|
|
|
if (registrationNameDependencies.hasOwnProperty(name)) { |
|
return true; |
|
} |
|
|
|
var registrationName = possibleRegistrationNames.hasOwnProperty(lowerCasedName) ? possibleRegistrationNames[lowerCasedName] : null; |
|
|
|
if (registrationName != null) { |
|
error('Invalid event handler property `%s`. Did you mean `%s`?', name, registrationName); |
|
|
|
warnedProperties$1[name] = true; |
|
return true; |
|
} |
|
|
|
if (EVENT_NAME_REGEX.test(name)) { |
|
error('Unknown event handler property `%s`. It will be ignored.', name); |
|
|
|
warnedProperties$1[name] = true; |
|
return true; |
|
} |
|
} else if (EVENT_NAME_REGEX.test(name)) { |
|
// If no event plugins have been injected, we are in a server environment. |
|
// So we can't tell if the event name is correct for sure, but we can filter |
|
// out known bad ones like `onclick`. We can't suggest a specific replacement though. |
|
if (INVALID_EVENT_NAME_REGEX.test(name)) { |
|
error('Invalid event handler property `%s`. ' + 'React events use the camelCase naming convention, for example `onClick`.', name); |
|
} |
|
|
|
warnedProperties$1[name] = true; |
|
return true; |
|
} // Let the ARIA attribute hook validate ARIA attributes |
|
|
|
|
|
if (rARIA$1.test(name) || rARIACamel$1.test(name)) { |
|
return true; |
|
} |
|
|
|
if (lowerCasedName === 'innerhtml') { |
|
error('Directly setting property `innerHTML` is not permitted. ' + 'For more information, lookup documentation on `dangerouslySetInnerHTML`.'); |
|
|
|
warnedProperties$1[name] = true; |
|
return true; |
|
} |
|
|
|
if (lowerCasedName === 'aria') { |
|
error('The `aria` attribute is reserved for future use in React. ' + 'Pass individual `aria-` attributes instead.'); |
|
|
|
warnedProperties$1[name] = true; |
|
return true; |
|
} |
|
|
|
if (lowerCasedName === 'is' && value !== null && value !== undefined && typeof value !== 'string') { |
|
error('Received a `%s` for a string attribute `is`. If this is expected, cast ' + 'the value to a string.', typeof value); |
|
|
|
warnedProperties$1[name] = true; |
|
return true; |
|
} |
|
|
|
if (typeof value === 'number' && isNaN(value)) { |
|
error('Received NaN for the `%s` attribute. If this is expected, cast ' + 'the value to a string.', name); |
|
|
|
warnedProperties$1[name] = true; |
|
return true; |
|
} |
|
|
|
var propertyInfo = getPropertyInfo(name); |
|
var isReserved = propertyInfo !== null && propertyInfo.type === RESERVED; // Known attributes should match the casing specified in the property config. |
|
|
|
if (possibleStandardNames.hasOwnProperty(lowerCasedName)) { |
|
var standardName = possibleStandardNames[lowerCasedName]; |
|
|
|
if (standardName !== name) { |
|
error('Invalid DOM property `%s`. Did you mean `%s`?', name, standardName); |
|
|
|
warnedProperties$1[name] = true; |
|
return true; |
|
} |
|
} else if (!isReserved && name !== lowerCasedName) { |
|
// Unknown attributes should have lowercase casing since that's how they |
|
// will be cased anyway with server rendering. |
|
error('React does not recognize the `%s` prop on a DOM element. If you ' + 'intentionally want it to appear in the DOM as a custom ' + 'attribute, spell it as lowercase `%s` instead. ' + 'If you accidentally passed it from a parent component, remove ' + 'it from the DOM element.', name, lowerCasedName); |
|
|
|
warnedProperties$1[name] = true; |
|
return true; |
|
} |
|
|
|
if (typeof value === 'boolean' && shouldRemoveAttributeWithWarning(name, value, propertyInfo, false)) { |
|
if (value) { |
|
error('Received `%s` for a non-boolean attribute `%s`.\n\n' + 'If you want to write it to the DOM, pass a string instead: ' + '%s="%s" or %s={value.toString()}.', value, name, name, value, name); |
|
} else { |
|
error('Received `%s` for a non-boolean attribute `%s`.\n\n' + 'If you want to write it to the DOM, pass a string instead: ' + '%s="%s" or %s={value.toString()}.\n\n' + 'If you used to conditionally omit it with %s={condition && value}, ' + 'pass %s={condition ? value : undefined} instead.', value, name, name, value, name, name, name); |
|
} |
|
|
|
warnedProperties$1[name] = true; |
|
return true; |
|
} // Now that we've validated casing, do not validate |
|
// data types for reserved props |
|
|
|
|
|
if (isReserved) { |
|
return true; |
|
} // Warn when a known attribute is a bad type |
|
|
|
|
|
if (shouldRemoveAttributeWithWarning(name, value, propertyInfo, false)) { |
|
warnedProperties$1[name] = true; |
|
return false; |
|
} // Warn when passing the strings 'false' or 'true' into a boolean prop |
|
|
|
|
|
if ((value === 'false' || value === 'true') && propertyInfo !== null && propertyInfo.type === BOOLEAN) { |
|
error('Received the string `%s` for the boolean attribute `%s`. ' + '%s ' + 'Did you mean %s={%s}?', value, name, value === 'false' ? 'The browser will interpret it as a truthy value.' : 'Although this works, it will not work as expected if you pass the string "false".', name, value); |
|
|
|
warnedProperties$1[name] = true; |
|
return true; |
|
} |
|
|
|
return true; |
|
}; |
|
} |
|
|
|
var warnUnknownProperties = function (type, props, eventRegistry) { |
|
{ |
|
var unknownProps = []; |
|
|
|
for (var key in props) { |
|
var isValid = validateProperty$1(type, key, props[key], eventRegistry); |
|
|
|
if (!isValid) { |
|
unknownProps.push(key); |
|
} |
|
} |
|
|
|
var unknownPropString = unknownProps.map(function (prop) { |
|
return '`' + prop + '`'; |
|
}).join(', '); |
|
|
|
if (unknownProps.length === 1) { |
|
error('Invalid value for prop %s on <%s> tag. Either remove it from the element, ' + 'or pass a string or number value to keep it in the DOM. ' + 'For details, see https://reactjs.org/link/attribute-behavior ', unknownPropString, type); |
|
} else if (unknownProps.length > 1) { |
|
error('Invalid values for props %s on <%s> tag. Either remove them from the element, ' + 'or pass a string or number value to keep them in the DOM. ' + 'For details, see https://reactjs.org/link/attribute-behavior ', unknownPropString, type); |
|
} |
|
} |
|
}; |
|
|
|
function validateProperties$2(type, props, eventRegistry) { |
|
if (isCustomComponent(type, props)) { |
|
return; |
|
} |
|
|
|
warnUnknownProperties(type, props, eventRegistry); |
|
} |
|
|
|
var IS_EVENT_HANDLE_NON_MANAGED_NODE = 1; |
|
var IS_NON_DELEGATED = 1 << 1; |
|
var IS_CAPTURE_PHASE = 1 << 2; |
|
// set to LEGACY_FB_SUPPORT. LEGACY_FB_SUPPORT only gets set when |
|
// we call willDeferLaterForLegacyFBSupport, thus not bailing out |
|
// will result in endless cycles like an infinite loop. |
|
// We also don't want to defer during event replaying. |
|
|
|
var SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS = IS_EVENT_HANDLE_NON_MANAGED_NODE | IS_NON_DELEGATED | IS_CAPTURE_PHASE; |
|
|
|
// This exists to avoid circular dependency between ReactDOMEventReplaying |
|
// and DOMPluginEventSystem. |
|
var currentReplayingEvent = null; |
|
function setReplayingEvent(event) { |
|
{ |
|
if (currentReplayingEvent !== null) { |
|
error('Expected currently replaying event to be null. This error ' + 'is likely caused by a bug in React. Please file an issue.'); |
|
} |
|
} |
|
|
|
currentReplayingEvent = event; |
|
} |
|
function resetReplayingEvent() { |
|
{ |
|
if (currentReplayingEvent === null) { |
|
error('Expected currently replaying event to not be null. This error ' + 'is likely caused by a bug in React. Please file an issue.'); |
|
} |
|
} |
|
|
|
currentReplayingEvent = null; |
|
} |
|
function isReplayingEvent(event) { |
|
return event === currentReplayingEvent; |
|
} |
|
|
|
/** |
|
* Gets the target node from a native browser event by accounting for |
|
* inconsistencies in browser DOM APIs. |
|
* |
|
* @param {object} nativeEvent Native browser event. |
|
* @return {DOMEventTarget} Target node. |
|
*/ |
|
|
|
function getEventTarget(nativeEvent) { |
|
// Fallback to nativeEvent.srcElement for IE9 |
|
// https://github.com/facebook/react/issues/12506 |
|
var target = nativeEvent.target || nativeEvent.srcElement || window; // Normalize SVG <use> element events #4963 |
|
|
|
if (target.correspondingUseElement) { |
|
target = target.correspondingUseElement; |
|
} // Safari may fire events on text nodes (Node.TEXT_NODE is 3). |
|
// @see http://www.quirksmode.org/js/events_properties.html |
|
|
|
|
|
return target.nodeType === TEXT_NODE ? target.parentNode : target; |
|
} |
|
|
|
var restoreImpl = null; |
|
var restoreTarget = null; |
|
var restoreQueue = null; |
|
|
|
function restoreStateOfTarget(target) { |
|
// We perform this translation at the end of the event loop so that we |
|
// always receive the correct fiber here |
|
var internalInstance = getInstanceFromNode(target); |
|
|
|
if (!internalInstance) { |
|
// Unmounted |
|
return; |
|
} |
|
|
|
if (typeof restoreImpl !== 'function') { |
|
throw new Error('setRestoreImplementation() needs to be called to handle a target for controlled ' + 'events. This error is likely caused by a bug in React. Please file an issue.'); |
|
} |
|
|
|
var stateNode = internalInstance.stateNode; // Guard against Fiber being unmounted. |
|
|
|
if (stateNode) { |
|
var _props = getFiberCurrentPropsFromNode(stateNode); |
|
|
|
restoreImpl(internalInstance.stateNode, internalInstance.type, _props); |
|
} |
|
} |
|
|
|
function setRestoreImplementation(impl) { |
|
restoreImpl = impl; |
|
} |
|
function enqueueStateRestore(target) { |
|
if (restoreTarget) { |
|
if (restoreQueue) { |
|
restoreQueue.push(target); |
|
} else { |
|
restoreQueue = [target]; |
|
} |
|
} else { |
|
restoreTarget = target; |
|
} |
|
} |
|
function needsStateRestore() { |
|
return restoreTarget !== null || restoreQueue !== null; |
|
} |
|
function restoreStateIfNeeded() { |
|
if (!restoreTarget) { |
|
return; |
|
} |
|
|
|
var target = restoreTarget; |
|
var queuedTargets = restoreQueue; |
|
restoreTarget = null; |
|
restoreQueue = null; |
|
restoreStateOfTarget(target); |
|
|
|
if (queuedTargets) { |
|
for (var i = 0; i < queuedTargets.length; i++) { |
|
restoreStateOfTarget(queuedTargets[i]); |
|
} |
|
} |
|
} |
|
|
|
// the renderer. Such as when we're dispatching events or if third party |
|
// libraries need to call batchedUpdates. Eventually, this API will go away when |
|
// everything is batched by default. We'll then have a similar API to opt-out of |
|
// scheduled work and instead do synchronous work. |
|
// Defaults |
|
|
|
var batchedUpdatesImpl = function (fn, bookkeeping) { |
|
return fn(bookkeeping); |
|
}; |
|
|
|
var flushSyncImpl = function () {}; |
|
|
|
var isInsideEventHandler = false; |
|
|
|
function finishEventHandler() { |
|
// Here we wait until all updates have propagated, which is important |
|
// when using controlled components within layers: |
|
// https://github.com/facebook/react/issues/1698 |
|
// Then we restore state of any controlled component. |
|
var controlledComponentsHavePendingUpdates = needsStateRestore(); |
|
|
|
if (controlledComponentsHavePendingUpdates) { |
|
// If a controlled event was fired, we may need to restore the state of |
|
// the DOM node back to the controlled value. This is necessary when React |
|
// bails out of the update without touching the DOM. |
|
// TODO: Restore state in the microtask, after the discrete updates flush, |
|
// instead of early flushing them here. |
|
flushSyncImpl(); |
|
restoreStateIfNeeded(); |
|
} |
|
} |
|
|
|
function batchedUpdates(fn, a, b) { |
|
if (isInsideEventHandler) { |
|
// If we are currently inside another batch, we need to wait until it |
|
// fully completes before restoring state. |
|
return fn(a, b); |
|
} |
|
|
|
isInsideEventHandler = true; |
|
|
|
try { |
|
return batchedUpdatesImpl(fn, a, b); |
|
} finally { |
|
isInsideEventHandler = false; |
|
finishEventHandler(); |
|
} |
|
} // TODO: Replace with flushSync |
|
function setBatchingImplementation(_batchedUpdatesImpl, _discreteUpdatesImpl, _flushSyncImpl) { |
|
batchedUpdatesImpl = _batchedUpdatesImpl; |
|
flushSyncImpl = _flushSyncImpl; |
|
} |
|
|
|
function isInteractive(tag) { |
|
return tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea'; |
|
} |
|
|
|
function shouldPreventMouseEvent(name, type, props) { |
|
switch (name) { |
|
case 'onClick': |
|
case 'onClickCapture': |
|
case 'onDoubleClick': |
|
case 'onDoubleClickCapture': |
|
case 'onMouseDown': |
|
case 'onMouseDownCapture': |
|
case 'onMouseMove': |
|
case 'onMouseMoveCapture': |
|
case 'onMouseUp': |
|
case 'onMouseUpCapture': |
|
case 'onMouseEnter': |
|
return !!(props.disabled && isInteractive(type)); |
|
|
|
default: |
|
return false; |
|
} |
|
} |
|
/** |
|
* @param {object} inst The instance, which is the source of events. |
|
* @param {string} registrationName Name of listener (e.g. `onClick`). |
|
* @return {?function} The stored callback. |
|
*/ |
|
|
|
|
|
function getListener(inst, registrationName) { |
|
var stateNode = inst.stateNode; |
|
|
|
if (stateNode === null) { |
|
// Work in progress (ex: onload events in incremental mode). |
|
return null; |
|
} |
|
|
|
var props = getFiberCurrentPropsFromNode(stateNode); |
|
|
|
if (props === null) { |
|
// Work in progress. |
|
return null; |
|
} |
|
|
|
var listener = props[registrationName]; |
|
|
|
if (shouldPreventMouseEvent(registrationName, inst.type, props)) { |
|
return null; |
|
} |
|
|
|
if (listener && typeof listener !== 'function') { |
|
throw new Error("Expected `" + registrationName + "` listener to be a function, instead got a value of `" + typeof listener + "` type."); |
|
} |
|
|
|
return listener; |
|
} |
|
|
|
var passiveBrowserEventsSupported = false; // Check if browser support events with passive listeners |
|
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support |
|
|
|
if (canUseDOM) { |
|
try { |
|
var options = {}; // $FlowFixMe: Ignore Flow complaining about needing a value |
|
|
|
Object.defineProperty(options, 'passive', { |
|
get: function () { |
|
passiveBrowserEventsSupported = true; |
|
} |
|
}); |
|
window.addEventListener('test', options, options); |
|
window.removeEventListener('test', options, options); |
|
} catch (e) { |
|
passiveBrowserEventsSupported = false; |
|
} |
|
} |
|
|
|
function invokeGuardedCallbackProd(name, func, context, a, b, c, d, e, f) { |
|
var funcArgs = Array.prototype.slice.call(arguments, 3); |
|
|
|
try { |
|
func.apply(context, funcArgs); |
|
} catch (error) { |
|
this.onError(error); |
|
} |
|
} |
|
|
|
var invokeGuardedCallbackImpl = invokeGuardedCallbackProd; |
|
|
|
{ |
|
// In DEV mode, we swap out invokeGuardedCallback for a special version |
|
// that plays more nicely with the browser's DevTools. The idea is to preserve |
|
// "Pause on exceptions" behavior. Because React wraps all user-provided |
|
// functions in invokeGuardedCallback, and the production version of |
|
// invokeGuardedCallback uses a try-catch, all user exceptions are treated |
|
// like caught exceptions, and the DevTools won't pause unless the developer |
|
// takes the extra step of enabling pause on caught exceptions. This is |
|
// unintuitive, though, because even though React has caught the error, from |
|
// the developer's perspective, the error is uncaught. |
|
// |
|
// To preserve the expected "Pause on exceptions" behavior, we don't use a |
|
// try-catch in DEV. Instead, we synchronously dispatch a fake event to a fake |
|
// DOM node, and call the user-provided callback from inside an event handler |
|
// for that fake event. If the callback throws, the error is "captured" using |
|
// a global event handler. But because the error happens in a different |
|
// event loop context, it does not interrupt the normal program flow. |
|
// Effectively, this gives us try-catch behavior without actually using |
|
// try-catch. Neat! |
|
// Check that the browser supports the APIs we need to implement our special |
|
// DEV version of invokeGuardedCallback |
|
if (typeof window !== 'undefined' && typeof window.dispatchEvent === 'function' && typeof document !== 'undefined' && typeof document.createEvent === 'function') { |
|
var fakeNode = document.createElement('react'); |
|
|
|
invokeGuardedCallbackImpl = function invokeGuardedCallbackDev(name, func, context, a, b, c, d, e, f) { |
|
// If document doesn't exist we know for sure we will crash in this method |
|
// when we call document.createEvent(). However this can cause confusing |
|
// errors: https://github.com/facebook/create-react-app/issues/3482 |
|
// So we preemptively throw with a better message instead. |
|
if (typeof document === 'undefined' || document === null) { |
|
throw new Error('The `document` global was defined when React was initialized, but is not ' + 'defined anymore. This can happen in a test environment if a component ' + 'schedules an update from an asynchronous callback, but the test has already ' + 'finished running. To solve this, you can either unmount the component at ' + 'the end of your test (and ensure that any asynchronous operations get ' + 'canceled in `componentWillUnmount`), or you can change the test itself ' + 'to be asynchronous.'); |
|
} |
|
|
|
var evt = document.createEvent('Event'); |
|
var didCall = false; // Keeps track of whether the user-provided callback threw an error. We |
|
// set this to true at the beginning, then set it to false right after |
|
// calling the function. If the function errors, `didError` will never be |
|
// set to false. This strategy works even if the browser is flaky and |
|
// fails to call our global error handler, because it doesn't rely on |
|
// the error event at all. |
|
|
|
var didError = true; // Keeps track of the value of window.event so that we can reset it |
|
// during the callback to let user code access window.event in the |
|
// browsers that support it. |
|
|
|
var windowEvent = window.event; // Keeps track of the descriptor of window.event to restore it after event |
|
// dispatching: https://github.com/facebook/react/issues/13688 |
|
|
|
var windowEventDescriptor = Object.getOwnPropertyDescriptor(window, 'event'); |
|
|
|
function restoreAfterDispatch() { |
|
// We immediately remove the callback from event listeners so that |
|
// nested `invokeGuardedCallback` calls do not clash. Otherwise, a |
|
// nested call would trigger the fake event handlers of any call higher |
|
// in the stack. |
|
fakeNode.removeEventListener(evtType, callCallback, false); // We check for window.hasOwnProperty('event') to prevent the |
|
// window.event assignment in both IE <= 10 as they throw an error |
|
// "Member not found" in strict mode, and in Firefox which does not |
|
// support window.event. |
|
|
|
if (typeof window.event !== 'undefined' && window.hasOwnProperty('event')) { |
|
window.event = windowEvent; |
|
} |
|
} // Create an event handler for our fake event. We will synchronously |
|
// dispatch our fake event using `dispatchEvent`. Inside the handler, we |
|
// call the user-provided callback. |
|
|
|
|
|
var funcArgs = Array.prototype.slice.call(arguments, 3); |
|
|
|
function callCallback() { |
|
didCall = true; |
|
restoreAfterDispatch(); |
|
func.apply(context, funcArgs); |
|
didError = false; |
|
} // Create a global error event handler. We use this to capture the value |
|
// that was thrown. It's possible that this error handler will fire more |
|
// than once; for example, if non-React code also calls `dispatchEvent` |
|
// and a handler for that event throws. We should be resilient to most of |
|
// those cases. Even if our error event handler fires more than once, the |
|
// last error event is always used. If the callback actually does error, |
|
// we know that the last error event is the correct one, because it's not |
|
// possible for anything else to have happened in between our callback |
|
// erroring and the code that follows the `dispatchEvent` call below. If |
|
// the callback doesn't error, but the error event was fired, we know to |
|
// ignore it because `didError` will be false, as described above. |
|
|
|
|
|
var error; // Use this to track whether the error event is ever called. |
|
|
|
var didSetError = false; |
|
var isCrossOriginError = false; |
|
|
|
function handleWindowError(event) { |
|
error = event.error; |
|
didSetError = true; |
|
|
|
if (error === null && event.colno === 0 && event.lineno === 0) { |
|
isCrossOriginError = true; |
|
} |
|
|
|
if (event.defaultPrevented) { |
|
// Some other error handler has prevented default. |
|
// Browsers silence the error report if this happens. |
|
// We'll remember this to later decide whether to log it or not. |
|
if (error != null && typeof error === 'object') { |
|
try { |
|
error._suppressLogging = true; |
|
} catch (inner) {// Ignore. |
|
} |
|
} |
|
} |
|
} // Create a fake event type. |
|
|
|
|
|
var evtType = "react-" + (name ? name : 'invokeguardedcallback'); // Attach our event handlers |
|
|
|
window.addEventListener('error', handleWindowError); |
|
fakeNode.addEventListener(evtType, callCallback, false); // Synchronously dispatch our fake event. If the user-provided function |
|
// errors, it will trigger our global error handler. |
|
|
|
evt.initEvent(evtType, false, false); |
|
fakeNode.dispatchEvent(evt); |
|
|
|
if (windowEventDescriptor) { |
|
Object.defineProperty(window, 'event', windowEventDescriptor); |
|
} |
|
|
|
if (didCall && didError) { |
|
if (!didSetError) { |
|
// The callback errored, but the error event never fired. |
|
// eslint-disable-next-line react-internal/prod-error-codes |
|
error = new Error('An error was thrown inside one of your components, but React ' + "doesn't know what it was. This is likely due to browser " + 'flakiness. React does its best to preserve the "Pause on ' + 'exceptions" behavior of the DevTools, which requires some ' + "DEV-mode only tricks. It's possible that these don't work in " + 'your browser. Try triggering the error in production mode, ' + 'or switching to a modern browser. If you suspect that this is ' + 'actually an issue with React, please file an issue.'); |
|
} else if (isCrossOriginError) { |
|
// eslint-disable-next-line react-internal/prod-error-codes |
|
error = new Error("A cross-origin error was thrown. React doesn't have access to " + 'the actual error object in development. ' + 'See https://reactjs.org/link/crossorigin-error for more information.'); |
|
} |
|
|
|
this.onError(error); |
|
} // Remove our event listeners |
|
|
|
|
|
window.removeEventListener('error', handleWindowError); |
|
|
|
if (!didCall) { |
|
// Something went really wrong, and our event was not dispatched. |
|
// https://github.com/facebook/react/issues/16734 |
|
// https://github.com/facebook/react/issues/16585 |
|
// Fall back to the production implementation. |
|
restoreAfterDispatch(); |
|
return invokeGuardedCallbackProd.apply(this, arguments); |
|
} |
|
}; |
|
} |
|
} |
|
|
|
var invokeGuardedCallbackImpl$1 = invokeGuardedCallbackImpl; |
|
|
|
var hasError = false; |
|
var caughtError = null; // Used by event system to capture/rethrow the first error. |
|
|
|
var hasRethrowError = false; |
|
var rethrowError = null; |
|
var reporter = { |
|
onError: function (error) { |
|
hasError = true; |
|
caughtError = error; |
|
} |
|
}; |
|
/** |
|
* Call a function while guarding against errors that happens within it. |
|
* Returns an error if it throws, otherwise null. |
|
* |
|
* In production, this is implemented using a try-catch. The reason we don't |
|
* use a try-catch directly is so that we can swap out a different |
|
* implementation in DEV mode. |
|
* |
|
* @param {String} name of the guard to use for logging or debugging |
|
* @param {Function} func The function to invoke |
|
* @param {*} context The context to use when calling the function |
|
* @param {...*} args Arguments for function |
|
*/ |
|
|
|
function invokeGuardedCallback(name, func, context, a, b, c, d, e, f) { |
|
hasError = false; |
|
caughtError = null; |
|
invokeGuardedCallbackImpl$1.apply(reporter, arguments); |
|
} |
|
/** |
|
* Same as invokeGuardedCallback, but instead of returning an error, it stores |
|
* it in a global so it can be rethrown by `rethrowCaughtError` later. |
|
* TODO: See if caughtError and rethrowError can be unified. |
|
* |
|
* @param {String} name of the guard to use for logging or debugging |
|
* @param {Function} func The function to invoke |
|
* @param {*} context The context to use when calling the function |
|
* @param {...*} args Arguments for function |
|
*/ |
|
|
|
function invokeGuardedCallbackAndCatchFirstError(name, func, context, a, b, c, d, e, f) { |
|
invokeGuardedCallback.apply(this, arguments); |
|
|
|
if (hasError) { |
|
var error = clearCaughtError(); |
|
|
|
if (!hasRethrowError) { |
|
hasRethrowError = true; |
|
rethrowError = error; |
|
} |
|
} |
|
} |
|
/** |
|
* During execution of guarded functions we will capture the first error which |
|
* we will rethrow to be handled by the top level error handler. |
|
*/ |
|
|
|
function rethrowCaughtError() { |
|
if (hasRethrowError) { |
|
var error = rethrowError; |
|
hasRethrowError = false; |
|
rethrowError = null; |
|
throw error; |
|
} |
|
} |
|
function hasCaughtError() { |
|
return hasError; |
|
} |
|
function clearCaughtError() { |
|
if (hasError) { |
|
var error = caughtError; |
|
hasError = false; |
|
caughtError = null; |
|
return error; |
|
} else { |
|
throw new Error('clearCaughtError was called but no error was captured. This error ' + 'is likely caused by a bug in React. Please file an issue.'); |
|
} |
|
} |
|
|
|
var ReactInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; |
|
var _ReactInternals$Sched = ReactInternals.Scheduler, |
|
unstable_cancelCallback = _ReactInternals$Sched.unstable_cancelCallback, |
|
unstable_now = _ReactInternals$Sched.unstable_now, |
|
unstable_scheduleCallback = _ReactInternals$Sched.unstable_scheduleCallback, |
|
unstable_shouldYield = _ReactInternals$Sched.unstable_shouldYield, |
|
unstable_requestPaint = _ReactInternals$Sched.unstable_requestPaint, |
|
unstable_getFirstCallbackNode = _ReactInternals$Sched.unstable_getFirstCallbackNode, |
|
unstable_runWithPriority = _ReactInternals$Sched.unstable_runWithPriority, |
|
unstable_next = _ReactInternals$Sched.unstable_next, |
|
unstable_continueExecution = _ReactInternals$Sched.unstable_continueExecution, |
|
unstable_pauseExecution = _ReactInternals$Sched.unstable_pauseExecution, |
|
unstable_getCurrentPriorityLevel = _ReactInternals$Sched.unstable_getCurrentPriorityLevel, |
|
unstable_ImmediatePriority = _ReactInternals$Sched.unstable_ImmediatePriority, |
|
unstable_UserBlockingPriority = _ReactInternals$Sched.unstable_UserBlockingPriority, |
|
unstable_NormalPriority = _ReactInternals$Sched.unstable_NormalPriority, |
|
unstable_LowPriority = _ReactInternals$Sched.unstable_LowPriority, |
|
unstable_IdlePriority = _ReactInternals$Sched.unstable_IdlePriority, |
|
unstable_forceFrameRate = _ReactInternals$Sched.unstable_forceFrameRate, |
|
unstable_flushAllWithoutAsserting = _ReactInternals$Sched.unstable_flushAllWithoutAsserting, |
|
unstable_yieldValue = _ReactInternals$Sched.unstable_yieldValue, |
|
unstable_setDisableYieldValue = _ReactInternals$Sched.unstable_setDisableYieldValue; |
|
|
|
/** |
|
* `ReactInstanceMap` maintains a mapping from a public facing stateful |
|
* instance (key) and the internal representation (value). This allows public |
|
* methods to accept the user facing instance as an argument and map them back |
|
* to internal methods. |
|
* |
|
* Note that this module is currently shared and assumed to be stateless. |
|
* If this becomes an actual Map, that will break. |
|
*/ |
|
function get(key) { |
|
return key._reactInternals; |
|
} |
|
function has(key) { |
|
return key._reactInternals !== undefined; |
|
} |
|
function set(key, value) { |
|
key._reactInternals = value; |
|
} |
|
|
|
// Don't change these two values. They're used by React Dev Tools. |
|
var NoFlags = |
|
/* */ |
|
0; |
|
var PerformedWork = |
|
/* */ |
|
1; // You can change the rest (and add more). |
|
|
|
var Placement = |
|
/* */ |
|
2; |
|
var Update = |
|
/* */ |
|
4; |
|
var ChildDeletion = |
|
/* */ |
|
16; |
|
var ContentReset = |
|
/* */ |
|
32; |
|
var Callback = |
|
/* */ |
|
64; |
|
var DidCapture = |
|
/* */ |
|
128; |
|
var ForceClientRender = |
|
/* */ |
|
256; |
|
var Ref = |
|
/* */ |
|
512; |
|
var Snapshot = |
|
/* */ |
|
1024; |
|
var Passive = |
|
/* */ |
|
2048; |
|
var Hydrating = |
|
/* */ |
|
4096; |
|
var Visibility = |
|
/* */ |
|
8192; |
|
var StoreConsistency = |
|
/* */ |
|
16384; |
|
var LifecycleEffectMask = Passive | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit) |
|
|
|
var HostEffectMask = |
|
/* */ |
|
32767; // These are not really side effects, but we still reuse this field. |
|
|
|
var Incomplete = |
|
/* */ |
|
32768; |
|
var ShouldCapture = |
|
/* */ |
|
65536; |
|
var ForceUpdateForLegacySuspense = |
|
/* */ |
|
131072; |
|
var Forked = |
|
/* */ |
|
1048576; // Static tags describe aspects of a fiber that are not specific to a render, |
|
// e.g. a fiber uses a passive effect (even if there are no updates on this particular render). |
|
// This enables us to defer more work in the unmount case, |
|
// since we can defer traversing the tree during layout to look for Passive effects, |
|
// and instead rely on the static flag as a signal that there may be cleanup work. |
|
|
|
var RefStatic = |
|
/* */ |
|
2097152; |
|
var LayoutStatic = |
|
/* */ |
|
4194304; |
|
var PassiveStatic = |
|
/* */ |
|
8388608; // These flags allow us to traverse to fibers that have effects on mount |
|
// without traversing the entire tree after every commit for |
|
// double invoking |
|
|
|
var MountLayoutDev = |
|
/* */ |
|
16777216; |
|
var MountPassiveDev = |
|
/* */ |
|
33554432; // Groups of flags that are used in the commit phase to skip over trees that |
|
// don't contain effects, by checking subtreeFlags. |
|
|
|
var BeforeMutationMask = // TODO: Remove Update flag from before mutation phase by re-landing Visibility |
|
// flag logic (see #20043) |
|
Update | Snapshot | ( 0); |
|
var MutationMask = Placement | Update | ChildDeletion | ContentReset | Ref | Hydrating | Visibility; |
|
var LayoutMask = Update | Callback | Ref | Visibility; // TODO: Split into PassiveMountMask and PassiveUnmountMask |
|
|
|
var PassiveMask = Passive | ChildDeletion; // Union of tags that don't get reset on clones. |
|
// This allows certain concepts to persist without recalculating them, |
|
// e.g. whether a subtree contains passive effects or portals. |
|
|
|
var StaticMask = LayoutStatic | PassiveStatic | RefStatic; |
|
|
|
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; |
|
function getNearestMountedFiber(fiber) { |
|
var node = fiber; |
|
var nearestMounted = fiber; |
|
|
|
if (!fiber.alternate) { |
|
// If there is no alternate, this might be a new tree that isn't inserted |
|
// yet. If it is, then it will have a pending insertion effect on it. |
|
var nextNode = node; |
|
|
|
do { |
|
node = nextNode; |
|
|
|
if ((node.flags & (Placement | Hydrating)) !== NoFlags) { |
|
// This is an insertion or in-progress hydration. The nearest possible |
|
// mounted fiber is the parent but we need to continue to figure out |
|
// if that one is still mounted. |
|
nearestMounted = node.return; |
|
} |
|
|
|
nextNode = node.return; |
|
} while (nextNode); |
|
} else { |
|
while (node.return) { |
|
node = node.return; |
|
} |
|
} |
|
|
|
if (node.tag === HostRoot) { |
|
// TODO: Check if this was a nested HostRoot when used with |
|
// renderContainerIntoSubtree. |
|
return nearestMounted; |
|
} // If we didn't hit the root, that means that we're in an disconnected tree |
|
// that has been unmounted. |
|
|
|
|
|
return null; |
|
} |
|
function getSuspenseInstanceFromFiber(fiber) { |
|
if (fiber.tag === SuspenseComponent) { |
|
var suspenseState = fiber.memoizedState; |
|
|
|
if (suspenseState === null) { |
|
var current = fiber.alternate; |
|
|
|
if (current !== null) { |
|
suspenseState = current.memoizedState; |
|
} |
|
} |
|
|
|
if (suspenseState !== null) { |
|
return suspenseState.dehydrated; |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
function getContainerFromFiber(fiber) { |
|
return fiber.tag === HostRoot ? fiber.stateNode.containerInfo : null; |
|
} |
|
function isFiberMounted(fiber) { |
|
return getNearestMountedFiber(fiber) === fiber; |
|
} |
|
function isMounted(component) { |
|
{ |
|
var owner = ReactCurrentOwner.current; |
|
|
|
if (owner !== null && owner.tag === ClassComponent) { |
|
var ownerFiber = owner; |
|
var instance = ownerFiber.stateNode; |
|
|
|
if (!instance._warnedAboutRefsInRender) { |
|
error('%s is accessing isMounted inside its render() function. ' + 'render() should be a pure function of props and state. It should ' + 'never access something that requires stale data from the previous ' + 'render, such as refs. Move this logic to componentDidMount and ' + 'componentDidUpdate instead.', getComponentNameFromFiber(ownerFiber) || 'A component'); |
|
} |
|
|
|
instance._warnedAboutRefsInRender = true; |
|
} |
|
} |
|
|
|
var fiber = get(component); |
|
|
|
if (!fiber) { |
|
return false; |
|
} |
|
|
|
return getNearestMountedFiber(fiber) === fiber; |
|
} |
|
|
|
function assertIsMounted(fiber) { |
|
if (getNearestMountedFiber(fiber) !== fiber) { |
|
throw new Error('Unable to find node on an unmounted component.'); |
|
} |
|
} |
|
|
|
function findCurrentFiberUsingSlowPath(fiber) { |
|
var alternate = fiber.alternate; |
|
|
|
if (!alternate) { |
|
// If there is no alternate, then we only need to check if it is mounted. |
|
var nearestMounted = getNearestMountedFiber(fiber); |
|
|
|
if (nearestMounted === null) { |
|
throw new Error('Unable to find node on an unmounted component.'); |
|
} |
|
|
|
if (nearestMounted !== fiber) { |
|
return null; |
|
} |
|
|
|
return fiber; |
|
} // If we have two possible branches, we'll walk backwards up to the root |
|
// to see what path the root points to. On the way we may hit one of the |
|
// special cases and we'll deal with them. |
|
|
|
|
|
var a = fiber; |
|
var b = alternate; |
|
|
|
while (true) { |
|
var parentA = a.return; |
|
|
|
if (parentA === null) { |
|
// We're at the root. |
|
break; |
|
} |
|
|
|
var parentB = parentA.alternate; |
|
|
|
if (parentB === null) { |
|
// There is no alternate. This is an unusual case. Currently, it only |
|
// happens when a Suspense component is hidden. An extra fragment fiber |
|
// is inserted in between the Suspense fiber and its children. Skip |
|
// over this extra fragment fiber and proceed to the next parent. |
|
var nextParent = parentA.return; |
|
|
|
if (nextParent !== null) { |
|
a = b = nextParent; |
|
continue; |
|
} // If there's no parent, we're at the root. |
|
|
|
|
|
break; |
|
} // If both copies of the parent fiber point to the same child, we can |
|
// assume that the child is current. This happens when we bailout on low |
|
// priority: the bailed out fiber's child reuses the current child. |
|
|
|
|
|
if (parentA.child === parentB.child) { |
|
var child = parentA.child; |
|
|
|
while (child) { |
|
if (child === a) { |
|
// We've determined that A is the current branch. |
|
assertIsMounted(parentA); |
|
return fiber; |
|
} |
|
|
|
if (child === b) { |
|
// We've determined that B is the current branch. |
|
assertIsMounted(parentA); |
|
return alternate; |
|
} |
|
|
|
child = child.sibling; |
|
} // We should never have an alternate for any mounting node. So the only |
|
// way this could possibly happen is if this was unmounted, if at all. |
|
|
|
|
|
throw new Error('Unable to find node on an unmounted component.'); |
|
} |
|
|
|
if (a.return !== b.return) { |
|
// The return pointer of A and the return pointer of B point to different |
|
// fibers. We assume that return pointers never criss-cross, so A must |
|
// belong to the child set of A.return, and B must belong to the child |
|
// set of B.return. |
|
a = parentA; |
|
b = parentB; |
|
} else { |
|
// The return pointers point to the same fiber. We'll have to use the |
|
// default, slow path: scan the child sets of each parent alternate to see |
|
// which child belongs to which set. |
|
// |
|
// Search parent A's child set |
|
var didFindChild = false; |
|
var _child = parentA.child; |
|
|
|
while (_child) { |
|
if (_child === a) { |
|
didFindChild = true; |
|
a = parentA; |
|
b = parentB; |
|
break; |
|
} |
|
|
|
if (_child === b) { |
|
didFindChild = true; |
|
b = parentA; |
|
a = parentB; |
|
break; |
|
} |
|
|
|
_child = _child.sibling; |
|
} |
|
|
|
if (!didFindChild) { |
|
// Search parent B's child set |
|
_child = parentB.child; |
|
|
|
while (_child) { |
|
if (_child === a) { |
|
didFindChild = true; |
|
a = parentB; |
|
b = parentA; |
|
break; |
|
} |
|
|
|
if (_child === b) { |
|
didFindChild = true; |
|
b = parentB; |
|
a = parentA; |
|
break; |
|
} |
|
|
|
_child = _child.sibling; |
|
} |
|
|
|
if (!didFindChild) { |
|
throw new Error('Child was not found in either parent set. This indicates a bug ' + 'in React related to the return pointer. Please file an issue.'); |
|
} |
|
} |
|
} |
|
|
|
if (a.alternate !== b) { |
|
throw new Error("Return fibers should always be each others' alternates. " + 'This error is likely caused by a bug in React. Please file an issue.'); |
|
} |
|
} // If the root is not a host container, we're in a disconnected tree. I.e. |
|
// unmounted. |
|
|
|
|
|
if (a.tag !== HostRoot) { |
|
throw new Error('Unable to find node on an unmounted component.'); |
|
} |
|
|
|
if (a.stateNode.current === a) { |
|
// We've determined that A is the current branch. |
|
return fiber; |
|
} // Otherwise B has to be current branch. |
|
|
|
|
|
return alternate; |
|
} |
|
function findCurrentHostFiber(parent) { |
|
var currentParent = findCurrentFiberUsingSlowPath(parent); |
|
return currentParent !== null ? findCurrentHostFiberImpl(currentParent) : null; |
|
} |
|
|
|
function findCurrentHostFiberImpl(node) { |
|
// Next we'll drill down this component to find the first HostComponent/Text. |
|
if (node.tag === HostComponent || node.tag === HostText) { |
|
return node; |
|
} |
|
|
|
var child = node.child; |
|
|
|
while (child !== null) { |
|
var match = findCurrentHostFiberImpl(child); |
|
|
|
if (match !== null) { |
|
return match; |
|
} |
|
|
|
child = child.sibling; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function findCurrentHostFiberWithNoPortals(parent) { |
|
var currentParent = findCurrentFiberUsingSlowPath(parent); |
|
return currentParent !== null ? findCurrentHostFiberWithNoPortalsImpl(currentParent) : null; |
|
} |
|
|
|
function findCurrentHostFiberWithNoPortalsImpl(node) { |
|
// Next we'll drill down this component to find the first HostComponent/Text. |
|
if (node.tag === HostComponent || node.tag === HostText) { |
|
return node; |
|
} |
|
|
|
var child = node.child; |
|
|
|
while (child !== null) { |
|
if (child.tag !== HostPortal) { |
|
var match = findCurrentHostFiberWithNoPortalsImpl(child); |
|
|
|
if (match !== null) { |
|
return match; |
|
} |
|
} |
|
|
|
child = child.sibling; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
// This module only exists as an ESM wrapper around the external CommonJS |
|
var scheduleCallback = unstable_scheduleCallback; |
|
var cancelCallback = unstable_cancelCallback; |
|
var shouldYield = unstable_shouldYield; |
|
var requestPaint = unstable_requestPaint; |
|
var now = unstable_now; |
|
var getCurrentPriorityLevel = unstable_getCurrentPriorityLevel; |
|
var ImmediatePriority = unstable_ImmediatePriority; |
|
var UserBlockingPriority = unstable_UserBlockingPriority; |
|
var NormalPriority = unstable_NormalPriority; |
|
var LowPriority = unstable_LowPriority; |
|
var IdlePriority = unstable_IdlePriority; |
|
// this doesn't actually exist on the scheduler, but it *does* |
|
// on scheduler/unstable_mock, which we'll need for internal testing |
|
var unstable_yieldValue$1 = unstable_yieldValue; |
|
var unstable_setDisableYieldValue$1 = unstable_setDisableYieldValue; |
|
|
|
var rendererID = null; |
|
var injectedHook = null; |
|
var injectedProfilingHooks = null; |
|
var hasLoggedError = false; |
|
var isDevToolsPresent = typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined'; |
|
function injectInternals(internals) { |
|
if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') { |
|
// No DevTools |
|
return false; |
|
} |
|
|
|
var hook = __REACT_DEVTOOLS_GLOBAL_HOOK__; |
|
|
|
if (hook.isDisabled) { |
|
// This isn't a real property on the hook, but it can be set to opt out |
|
// of DevTools integration and associated warnings and logs. |
|
// https://github.com/facebook/react/issues/3877 |
|
return true; |
|
} |
|
|
|
if (!hook.supportsFiber) { |
|
{ |
|
error('The installed version of React DevTools is too old and will not work ' + 'with the current version of React. Please update React DevTools. ' + 'https://reactjs.org/link/react-devtools'); |
|
} // DevTools exists, even though it doesn't support Fiber. |
|
|
|
|
|
return true; |
|
} |
|
|
|
try { |
|
if (enableSchedulingProfiler) { |
|
// Conditionally inject these hooks only if Timeline profiler is supported by this build. |
|
// This gives DevTools a way to feature detect that isn't tied to version number |
|
// (since profiling and timeline are controlled by different feature flags). |
|
internals = assign({}, internals, { |
|
getLaneLabelMap: getLaneLabelMap, |
|
injectProfilingHooks: injectProfilingHooks |
|
}); |
|
} |
|
|
|
rendererID = hook.inject(internals); // We have successfully injected, so now it is safe to set up hooks. |
|
|
|
injectedHook = hook; |
|
} catch (err) { |
|
// Catch all errors because it is unsafe to throw during initialization. |
|
{ |
|
error('React instrumentation encountered an error: %s.', err); |
|
} |
|
} |
|
|
|
if (hook.checkDCE) { |
|
// This is the real DevTools. |
|
return true; |
|
} else { |
|
// This is likely a hook installed by Fast Refresh runtime. |
|
return false; |
|
} |
|
} |
|
function onScheduleRoot(root, children) { |
|
{ |
|
if (injectedHook && typeof injectedHook.onScheduleFiberRoot === 'function') { |
|
try { |
|
injectedHook.onScheduleFiberRoot(rendererID, root, children); |
|
} catch (err) { |
|
if ( !hasLoggedError) { |
|
hasLoggedError = true; |
|
|
|
error('React instrumentation encountered an error: %s', err); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
function onCommitRoot(root, eventPriority) { |
|
if (injectedHook && typeof injectedHook.onCommitFiberRoot === 'function') { |
|
try { |
|
var didError = (root.current.flags & DidCapture) === DidCapture; |
|
|
|
if (enableProfilerTimer) { |
|
var schedulerPriority; |
|
|
|
switch (eventPriority) { |
|
case DiscreteEventPriority: |
|
schedulerPriority = ImmediatePriority; |
|
break; |
|
|
|
case ContinuousEventPriority: |
|
schedulerPriority = UserBlockingPriority; |
|
break; |
|
|
|
case DefaultEventPriority: |
|
schedulerPriority = NormalPriority; |
|
break; |
|
|
|
case IdleEventPriority: |
|
schedulerPriority = IdlePriority; |
|
break; |
|
|
|
default: |
|
schedulerPriority = NormalPriority; |
|
break; |
|
} |
|
|
|
injectedHook.onCommitFiberRoot(rendererID, root, schedulerPriority, didError); |
|
} else { |
|
injectedHook.onCommitFiberRoot(rendererID, root, undefined, didError); |
|
} |
|
} catch (err) { |
|
{ |
|
if (!hasLoggedError) { |
|
hasLoggedError = true; |
|
|
|
error('React instrumentation encountered an error: %s', err); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
function onPostCommitRoot(root) { |
|
if (injectedHook && typeof injectedHook.onPostCommitFiberRoot === 'function') { |
|
try { |
|
injectedHook.onPostCommitFiberRoot(rendererID, root); |
|
} catch (err) { |
|
{ |
|
if (!hasLoggedError) { |
|
hasLoggedError = true; |
|
|
|
error('React instrumentation encountered an error: %s', err); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
function onCommitUnmount(fiber) { |
|
if (injectedHook && typeof injectedHook.onCommitFiberUnmount === 'function') { |
|
try { |
|
injectedHook.onCommitFiberUnmount(rendererID, fiber); |
|
} catch (err) { |
|
{ |
|
if (!hasLoggedError) { |
|
hasLoggedError = true; |
|
|
|
error('React instrumentation encountered an error: %s', err); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
function setIsStrictModeForDevtools(newIsStrictMode) { |
|
{ |
|
if (typeof unstable_yieldValue$1 === 'function') { |
|
// We're in a test because Scheduler.unstable_yieldValue only exists |
|
// in SchedulerMock. To reduce the noise in strict mode tests, |
|
// suppress warnings and disable scheduler yielding during the double render |
|
unstable_setDisableYieldValue$1(newIsStrictMode); |
|
setSuppressWarning(newIsStrictMode); |
|
} |
|
|
|
if (injectedHook && typeof injectedHook.setStrictMode === 'function') { |
|
try { |
|
injectedHook.setStrictMode(rendererID, newIsStrictMode); |
|
} catch (err) { |
|
{ |
|
if (!hasLoggedError) { |
|
hasLoggedError = true; |
|
|
|
error('React instrumentation encountered an error: %s', err); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} // Profiler API hooks |
|
|
|
function injectProfilingHooks(profilingHooks) { |
|
injectedProfilingHooks = profilingHooks; |
|
} |
|
|
|
function getLaneLabelMap() { |
|
{ |
|
var map = new Map(); |
|
var lane = 1; |
|
|
|
for (var index = 0; index < TotalLanes; index++) { |
|
var label = getLabelForLane(lane); |
|
map.set(lane, label); |
|
lane *= 2; |
|
} |
|
|
|
return map; |
|
} |
|
} |
|
|
|
function markCommitStarted(lanes) { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markCommitStarted === 'function') { |
|
injectedProfilingHooks.markCommitStarted(lanes); |
|
} |
|
} |
|
} |
|
function markCommitStopped() { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markCommitStopped === 'function') { |
|
injectedProfilingHooks.markCommitStopped(); |
|
} |
|
} |
|
} |
|
function markComponentRenderStarted(fiber) { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markComponentRenderStarted === 'function') { |
|
injectedProfilingHooks.markComponentRenderStarted(fiber); |
|
} |
|
} |
|
} |
|
function markComponentRenderStopped() { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markComponentRenderStopped === 'function') { |
|
injectedProfilingHooks.markComponentRenderStopped(); |
|
} |
|
} |
|
} |
|
function markComponentPassiveEffectMountStarted(fiber) { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markComponentPassiveEffectMountStarted === 'function') { |
|
injectedProfilingHooks.markComponentPassiveEffectMountStarted(fiber); |
|
} |
|
} |
|
} |
|
function markComponentPassiveEffectMountStopped() { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markComponentPassiveEffectMountStopped === 'function') { |
|
injectedProfilingHooks.markComponentPassiveEffectMountStopped(); |
|
} |
|
} |
|
} |
|
function markComponentPassiveEffectUnmountStarted(fiber) { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStarted === 'function') { |
|
injectedProfilingHooks.markComponentPassiveEffectUnmountStarted(fiber); |
|
} |
|
} |
|
} |
|
function markComponentPassiveEffectUnmountStopped() { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStopped === 'function') { |
|
injectedProfilingHooks.markComponentPassiveEffectUnmountStopped(); |
|
} |
|
} |
|
} |
|
function markComponentLayoutEffectMountStarted(fiber) { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markComponentLayoutEffectMountStarted === 'function') { |
|
injectedProfilingHooks.markComponentLayoutEffectMountStarted(fiber); |
|
} |
|
} |
|
} |
|
function markComponentLayoutEffectMountStopped() { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markComponentLayoutEffectMountStopped === 'function') { |
|
injectedProfilingHooks.markComponentLayoutEffectMountStopped(); |
|
} |
|
} |
|
} |
|
function markComponentLayoutEffectUnmountStarted(fiber) { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStarted === 'function') { |
|
injectedProfilingHooks.markComponentLayoutEffectUnmountStarted(fiber); |
|
} |
|
} |
|
} |
|
function markComponentLayoutEffectUnmountStopped() { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStopped === 'function') { |
|
injectedProfilingHooks.markComponentLayoutEffectUnmountStopped(); |
|
} |
|
} |
|
} |
|
function markComponentErrored(fiber, thrownValue, lanes) { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markComponentErrored === 'function') { |
|
injectedProfilingHooks.markComponentErrored(fiber, thrownValue, lanes); |
|
} |
|
} |
|
} |
|
function markComponentSuspended(fiber, wakeable, lanes) { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markComponentSuspended === 'function') { |
|
injectedProfilingHooks.markComponentSuspended(fiber, wakeable, lanes); |
|
} |
|
} |
|
} |
|
function markLayoutEffectsStarted(lanes) { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markLayoutEffectsStarted === 'function') { |
|
injectedProfilingHooks.markLayoutEffectsStarted(lanes); |
|
} |
|
} |
|
} |
|
function markLayoutEffectsStopped() { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markLayoutEffectsStopped === 'function') { |
|
injectedProfilingHooks.markLayoutEffectsStopped(); |
|
} |
|
} |
|
} |
|
function markPassiveEffectsStarted(lanes) { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markPassiveEffectsStarted === 'function') { |
|
injectedProfilingHooks.markPassiveEffectsStarted(lanes); |
|
} |
|
} |
|
} |
|
function markPassiveEffectsStopped() { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markPassiveEffectsStopped === 'function') { |
|
injectedProfilingHooks.markPassiveEffectsStopped(); |
|
} |
|
} |
|
} |
|
function markRenderStarted(lanes) { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markRenderStarted === 'function') { |
|
injectedProfilingHooks.markRenderStarted(lanes); |
|
} |
|
} |
|
} |
|
function markRenderYielded() { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markRenderYielded === 'function') { |
|
injectedProfilingHooks.markRenderYielded(); |
|
} |
|
} |
|
} |
|
function markRenderStopped() { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markRenderStopped === 'function') { |
|
injectedProfilingHooks.markRenderStopped(); |
|
} |
|
} |
|
} |
|
function markRenderScheduled(lane) { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markRenderScheduled === 'function') { |
|
injectedProfilingHooks.markRenderScheduled(lane); |
|
} |
|
} |
|
} |
|
function markForceUpdateScheduled(fiber, lane) { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markForceUpdateScheduled === 'function') { |
|
injectedProfilingHooks.markForceUpdateScheduled(fiber, lane); |
|
} |
|
} |
|
} |
|
function markStateUpdateScheduled(fiber, lane) { |
|
{ |
|
if (injectedProfilingHooks !== null && typeof injectedProfilingHooks.markStateUpdateScheduled === 'function') { |
|
injectedProfilingHooks.markStateUpdateScheduled(fiber, lane); |
|
} |
|
} |
|
} |
|
|
|
var NoMode = |
|
/* */ |
|
0; // TODO: Remove ConcurrentMode by reading from the root tag instead |
|
|
|
var ConcurrentMode = |
|
/* */ |
|
1; |
|
var ProfileMode = |
|
/* */ |
|
2; |
|
var StrictLegacyMode = |
|
/* */ |
|
8; |
|
var StrictEffectsMode = |
|
/* */ |
|
16; |
|
|
|
// TODO: This is pretty well supported by browsers. Maybe we can drop it. |
|
var clz32 = Math.clz32 ? Math.clz32 : clz32Fallback; // Count leading zeros. |
|
// Based on: |
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32 |
|
|
|
var log = Math.log; |
|
var LN2 = Math.LN2; |
|
|
|
function clz32Fallback(x) { |
|
var asUint = x >>> 0; |
|
|
|
if (asUint === 0) { |
|
return 32; |
|
} |
|
|
|
return 31 - (log(asUint) / LN2 | 0) | 0; |
|
} |
|
|
|
// If those values are changed that package should be rebuilt and redeployed. |
|
|
|
var TotalLanes = 31; |
|
var NoLanes = |
|
/* */ |
|
0; |
|
var NoLane = |
|
/* */ |
|
0; |
|
var SyncLane = |
|
/* */ |
|
1; |
|
var InputContinuousHydrationLane = |
|
/* */ |
|
2; |
|
var InputContinuousLane = |
|
/* */ |
|
4; |
|
var DefaultHydrationLane = |
|
/* */ |
|
8; |
|
var DefaultLane = |
|
/* */ |
|
16; |
|
var TransitionHydrationLane = |
|
/* */ |
|
32; |
|
var TransitionLanes = |
|
/* */ |
|
4194240; |
|
var TransitionLane1 = |
|
/* */ |
|
64; |
|
var TransitionLane2 = |
|
/* */ |
|
128; |
|
var TransitionLane3 = |
|
/* */ |
|
256; |
|
var TransitionLane4 = |
|
/* */ |
|
512; |
|
var TransitionLane5 = |
|
/* */ |
|
1024; |
|
var TransitionLane6 = |
|
/* */ |
|
2048; |
|
var TransitionLane7 = |
|
/* */ |
|
4096; |
|
var TransitionLane8 = |
|
/* */ |
|
8192; |
|
var TransitionLane9 = |
|
/* */ |
|
16384; |
|
var TransitionLane10 = |
|
/* */ |
|
32768; |
|
var TransitionLane11 = |
|
/* */ |
|
65536; |
|
var TransitionLane12 = |
|
/* */ |
|
131072; |
|
var TransitionLane13 = |
|
/* */ |
|
262144; |
|
var TransitionLane14 = |
|
/* */ |
|
524288; |
|
var TransitionLane15 = |
|
/* */ |
|
1048576; |
|
var TransitionLane16 = |
|
/* */ |
|
2097152; |
|
var RetryLanes = |
|
/* */ |
|
130023424; |
|
var RetryLane1 = |
|
/* */ |
|
4194304; |
|
var RetryLane2 = |
|
/* */ |
|
8388608; |
|
var RetryLane3 = |
|
/* */ |
|
16777216; |
|
var RetryLane4 = |
|
/* */ |
|
33554432; |
|
var RetryLane5 = |
|
/* */ |
|
67108864; |
|
var SomeRetryLane = RetryLane1; |
|
var SelectiveHydrationLane = |
|
/* */ |
|
134217728; |
|
var NonIdleLanes = |
|
/* */ |
|
268435455; |
|
var IdleHydrationLane = |
|
/* */ |
|
268435456; |
|
var IdleLane = |
|
/* */ |
|
536870912; |
|
var OffscreenLane = |
|
/* */ |
|
1073741824; // This function is used for the experimental timeline (react-devtools-timeline) |
|
// It should be kept in sync with the Lanes values above. |
|
|
|
function getLabelForLane(lane) { |
|
{ |
|
if (lane & SyncLane) { |
|
return 'Sync'; |
|
} |
|
|
|
if (lane & InputContinuousHydrationLane) { |
|
return 'InputContinuousHydration'; |
|
} |
|
|
|
if (lane & InputContinuousLane) { |
|
return 'InputContinuous'; |
|
} |
|
|
|
if (lane & DefaultHydrationLane) { |
|
return 'DefaultHydration'; |
|
} |
|
|
|
if (lane & DefaultLane) { |
|
return 'Default'; |
|
} |
|
|
|
if (lane & TransitionHydrationLane) { |
|
return 'TransitionHydration'; |
|
} |
|
|
|
if (lane & TransitionLanes) { |
|
return 'Transition'; |
|
} |
|
|
|
if (lane & RetryLanes) { |
|
return 'Retry'; |
|
} |
|
|
|
if (lane & SelectiveHydrationLane) { |
|
return 'SelectiveHydration'; |
|
} |
|
|
|
if (lane & IdleHydrationLane) { |
|
return 'IdleHydration'; |
|
} |
|
|
|
if (lane & IdleLane) { |
|
return 'Idle'; |
|
} |
|
|
|
if (lane & OffscreenLane) { |
|
return 'Offscreen'; |
|
} |
|
} |
|
} |
|
var NoTimestamp = -1; |
|
var nextTransitionLane = TransitionLane1; |
|
var nextRetryLane = RetryLane1; |
|
|
|
function getHighestPriorityLanes(lanes) { |
|
switch (getHighestPriorityLane(lanes)) { |
|
case SyncLane: |
|
return SyncLane; |
|
|
|
case InputContinuousHydrationLane: |
|
return InputContinuousHydrationLane; |
|
|
|
case InputContinuousLane: |
|
return InputContinuousLane; |
|
|
|
case DefaultHydrationLane: |
|
return DefaultHydrationLane; |
|
|
|
case DefaultLane: |
|
return DefaultLane; |
|
|
|
case TransitionHydrationLane: |
|
return TransitionHydrationLane; |
|
|
|
case TransitionLane1: |
|
case TransitionLane2: |
|
case TransitionLane3: |
|
case TransitionLane4: |
|
case TransitionLane5: |
|
case TransitionLane6: |
|
case TransitionLane7: |
|
case TransitionLane8: |
|
case TransitionLane9: |
|
case TransitionLane10: |
|
case TransitionLane11: |
|
case TransitionLane12: |
|
case TransitionLane13: |
|
case TransitionLane14: |
|
case TransitionLane15: |
|
case TransitionLane16: |
|
return lanes & TransitionLanes; |
|
|
|
case RetryLane1: |
|
case RetryLane2: |
|
case RetryLane3: |
|
case RetryLane4: |
|
case RetryLane5: |
|
return lanes & RetryLanes; |
|
|
|
case SelectiveHydrationLane: |
|
return SelectiveHydrationLane; |
|
|
|
case IdleHydrationLane: |
|
return IdleHydrationLane; |
|
|
|
case IdleLane: |
|
return IdleLane; |
|
|
|
case OffscreenLane: |
|
return OffscreenLane; |
|
|
|
default: |
|
{ |
|
error('Should have found matching lanes. This is a bug in React.'); |
|
} // This shouldn't be reachable, but as a fallback, return the entire bitmask. |
|
|
|
|
|
return lanes; |
|
} |
|
} |
|
|
|
function getNextLanes(root, wipLanes) { |
|
// Early bailout if there's no pending work left. |
|
var pendingLanes = root.pendingLanes; |
|
|
|
if (pendingLanes === NoLanes) { |
|
return NoLanes; |
|
} |
|
|
|
var nextLanes = NoLanes; |
|
var suspendedLanes = root.suspendedLanes; |
|
var pingedLanes = root.pingedLanes; // Do not work on any idle work until all the non-idle work has finished, |
|
// even if the work is suspended. |
|
|
|
var nonIdlePendingLanes = pendingLanes & NonIdleLanes; |
|
|
|
if (nonIdlePendingLanes !== NoLanes) { |
|
var nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes; |
|
|
|
if (nonIdleUnblockedLanes !== NoLanes) { |
|
nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes); |
|
} else { |
|
var nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes; |
|
|
|
if (nonIdlePingedLanes !== NoLanes) { |
|
nextLanes = getHighestPriorityLanes(nonIdlePingedLanes); |
|
} |
|
} |
|
} else { |
|
// The only remaining work is Idle. |
|
var unblockedLanes = pendingLanes & ~suspendedLanes; |
|
|
|
if (unblockedLanes !== NoLanes) { |
|
nextLanes = getHighestPriorityLanes(unblockedLanes); |
|
} else { |
|
if (pingedLanes !== NoLanes) { |
|
nextLanes = getHighestPriorityLanes(pingedLanes); |
|
} |
|
} |
|
} |
|
|
|
if (nextLanes === NoLanes) { |
|
// This should only be reachable if we're suspended |
|
// TODO: Consider warning in this path if a fallback timer is not scheduled. |
|
return NoLanes; |
|
} // If we're already in the middle of a render, switching lanes will interrupt |
|
// it and we'll lose our progress. We should only do this if the new lanes are |
|
// higher priority. |
|
|
|
|
|
if (wipLanes !== NoLanes && wipLanes !== nextLanes && // If we already suspended with a delay, then interrupting is fine. Don't |
|
// bother waiting until the root is complete. |
|
(wipLanes & suspendedLanes) === NoLanes) { |
|
var nextLane = getHighestPriorityLane(nextLanes); |
|
var wipLane = getHighestPriorityLane(wipLanes); |
|
|
|
if ( // Tests whether the next lane is equal or lower priority than the wip |
|
// one. This works because the bits decrease in priority as you go left. |
|
nextLane >= wipLane || // Default priority updates should not interrupt transition updates. The |
|
// only difference between default updates and transition updates is that |
|
// default updates do not support refresh transitions. |
|
nextLane === DefaultLane && (wipLane & TransitionLanes) !== NoLanes) { |
|
// Keep working on the existing in-progress tree. Do not interrupt. |
|
return wipLanes; |
|
} |
|
} |
|
|
|
if ((nextLanes & InputContinuousLane) !== NoLanes) { |
|
// When updates are sync by default, we entangle continuous priority updates |
|
// and default updates, so they render in the same batch. The only reason |
|
// they use separate lanes is because continuous updates should interrupt |
|
// transitions, but default updates should not. |
|
nextLanes |= pendingLanes & DefaultLane; |
|
} // Check for entangled lanes and add them to the batch. |
|
// |
|
// A lane is said to be entangled with another when it's not allowed to render |
|
// in a batch that does not also include the other lane. Typically we do this |
|
// when multiple updates have the same source, and we only want to respond to |
|
// the most recent event from that source. |
|
// |
|
// Note that we apply entanglements *after* checking for partial work above. |
|
// This means that if a lane is entangled during an interleaved event while |
|
// it's already rendering, we won't interrupt it. This is intentional, since |
|
// entanglement is usually "best effort": we'll try our best to render the |
|
// lanes in the same batch, but it's not worth throwing out partially |
|
// completed work in order to do it. |
|
// TODO: Reconsider this. The counter-argument is that the partial work |
|
// represents an intermediate state, which we don't want to show to the user. |
|
// And by spending extra time finishing it, we're increasing the amount of |
|
// time it takes to show the final state, which is what they are actually |
|
// waiting for. |
|
// |
|
// For those exceptions where entanglement is semantically important, like |
|
// useMutableSource, we should ensure that there is no partial work at the |
|
// time we apply the entanglement. |
|
|
|
|
|
var entangledLanes = root.entangledLanes; |
|
|
|
if (entangledLanes !== NoLanes) { |
|
var entanglements = root.entanglements; |
|
var lanes = nextLanes & entangledLanes; |
|
|
|
while (lanes > 0) { |
|
var index = pickArbitraryLaneIndex(lanes); |
|
var lane = 1 << index; |
|
nextLanes |= entanglements[index]; |
|
lanes &= ~lane; |
|
} |
|
} |
|
|
|
return nextLanes; |
|
} |
|
function getMostRecentEventTime(root, lanes) { |
|
var eventTimes = root.eventTimes; |
|
var mostRecentEventTime = NoTimestamp; |
|
|
|
while (lanes > 0) { |
|
var index = pickArbitraryLaneIndex(lanes); |
|
var lane = 1 << index; |
|
var eventTime = eventTimes[index]; |
|
|
|
if (eventTime > mostRecentEventTime) { |
|
mostRecentEventTime = eventTime; |
|
} |
|
|
|
lanes &= ~lane; |
|
} |
|
|
|
return mostRecentEventTime; |
|
} |
|
|
|
function computeExpirationTime(lane, currentTime) { |
|
switch (lane) { |
|
case SyncLane: |
|
case InputContinuousHydrationLane: |
|
case InputContinuousLane: |
|
// User interactions should expire slightly more quickly. |
|
// |
|
// NOTE: This is set to the corresponding constant as in Scheduler.js. |
|
// When we made it larger, a product metric in www regressed, suggesting |
|
// there's a user interaction that's being starved by a series of |
|
// synchronous updates. If that theory is correct, the proper solution is |
|
// to fix the starvation. However, this scenario supports the idea that |
|
// expiration times are an important safeguard when starvation |
|
// does happen. |
|
return currentTime + 250; |
|
|
|
case DefaultHydrationLane: |
|
case DefaultLane: |
|
case TransitionHydrationLane: |
|
case TransitionLane1: |
|
case TransitionLane2: |
|
case TransitionLane3: |
|
case TransitionLane4: |
|
case TransitionLane5: |
|
case TransitionLane6: |
|
case TransitionLane7: |
|
case TransitionLane8: |
|
case TransitionLane9: |
|
case TransitionLane10: |
|
case TransitionLane11: |
|
case TransitionLane12: |
|
case TransitionLane13: |
|
case TransitionLane14: |
|
case TransitionLane15: |
|
case TransitionLane16: |
|
return currentTime + 5000; |
|
|
|
case RetryLane1: |
|
case RetryLane2: |
|
case RetryLane3: |
|
case RetryLane4: |
|
case RetryLane5: |
|
// TODO: Retries should be allowed to expire if they are CPU bound for |
|
// too long, but when I made this change it caused a spike in browser |
|
// crashes. There must be some other underlying bug; not super urgent but |
|
// ideally should figure out why and fix it. Unfortunately we don't have |
|
// a repro for the crashes, only detected via production metrics. |
|
return NoTimestamp; |
|
|
|
case SelectiveHydrationLane: |
|
case IdleHydrationLane: |
|
case IdleLane: |
|
case OffscreenLane: |
|
// Anything idle priority or lower should never expire. |
|
return NoTimestamp; |
|
|
|
default: |
|
{ |
|
error('Should have found matching lanes. This is a bug in React.'); |
|
} |
|
|
|
return NoTimestamp; |
|
} |
|
} |
|
|
|
function markStarvedLanesAsExpired(root, currentTime) { |
|
// TODO: This gets called every time we yield. We can optimize by storing |
|
// the earliest expiration time on the root. Then use that to quickly bail out |
|
// of this function. |
|
var pendingLanes = root.pendingLanes; |
|
var suspendedLanes = root.suspendedLanes; |
|
var pingedLanes = root.pingedLanes; |
|
var expirationTimes = root.expirationTimes; // Iterate through the pending lanes and check if we've reached their |
|
// expiration time. If so, we'll assume the update is being starved and mark |
|
// it as expired to force it to finish. |
|
|
|
var lanes = pendingLanes; |
|
|
|
while (lanes > 0) { |
|
var index = pickArbitraryLaneIndex(lanes); |
|
var lane = 1 << index; |
|
var expirationTime = expirationTimes[index]; |
|
|
|
if (expirationTime === NoTimestamp) { |
|
// Found a pending lane with no expiration time. If it's not suspended, or |
|
// if it's pinged, assume it's CPU-bound. Compute a new expiration time |
|
// using the current time. |
|
if ((lane & suspendedLanes) === NoLanes || (lane & pingedLanes) !== NoLanes) { |
|
// Assumes timestamps are monotonically increasing. |
|
expirationTimes[index] = computeExpirationTime(lane, currentTime); |
|
} |
|
} else if (expirationTime <= currentTime) { |
|
// This lane expired |
|
root.expiredLanes |= lane; |
|
} |
|
|
|
lanes &= ~lane; |
|
} |
|
} // This returns the highest priority pending lanes regardless of whether they |
|
// are suspended. |
|
|
|
function getHighestPriorityPendingLanes(root) { |
|
return getHighestPriorityLanes(root.pendingLanes); |
|
} |
|
function getLanesToRetrySynchronouslyOnError(root) { |
|
var everythingButOffscreen = root.pendingLanes & ~OffscreenLane; |
|
|
|
if (everythingButOffscreen !== NoLanes) { |
|
return everythingButOffscreen; |
|
} |
|
|
|
if (everythingButOffscreen & OffscreenLane) { |
|
return OffscreenLane; |
|
} |
|
|
|
return NoLanes; |
|
} |
|
function includesSyncLane(lanes) { |
|
return (lanes & SyncLane) !== NoLanes; |
|
} |
|
function includesNonIdleWork(lanes) { |
|
return (lanes & NonIdleLanes) !== NoLanes; |
|
} |
|
function includesOnlyRetries(lanes) { |
|
return (lanes & RetryLanes) === lanes; |
|
} |
|
function includesOnlyNonUrgentLanes(lanes) { |
|
var UrgentLanes = SyncLane | InputContinuousLane | DefaultLane; |
|
return (lanes & UrgentLanes) === NoLanes; |
|
} |
|
function includesOnlyTransitions(lanes) { |
|
return (lanes & TransitionLanes) === lanes; |
|
} |
|
function includesBlockingLane(root, lanes) { |
|
|
|
var SyncDefaultLanes = InputContinuousHydrationLane | InputContinuousLane | DefaultHydrationLane | DefaultLane; |
|
return (lanes & SyncDefaultLanes) !== NoLanes; |
|
} |
|
function includesExpiredLane(root, lanes) { |
|
// This is a separate check from includesBlockingLane because a lane can |
|
// expire after a render has already started. |
|
return (lanes & root.expiredLanes) !== NoLanes; |
|
} |
|
function isTransitionLane(lane) { |
|
return (lane & TransitionLanes) !== NoLanes; |
|
} |
|
function claimNextTransitionLane() { |
|
// Cycle through the lanes, assigning each new transition to the next lane. |
|
// In most cases, this means every transition gets its own lane, until we |
|
// run out of lanes and cycle back to the beginning. |
|
var lane = nextTransitionLane; |
|
nextTransitionLane <<= 1; |
|
|
|
if ((nextTransitionLane & TransitionLanes) === NoLanes) { |
|
nextTransitionLane = TransitionLane1; |
|
} |
|
|
|
return lane; |
|
} |
|
function claimNextRetryLane() { |
|
var lane = nextRetryLane; |
|
nextRetryLane <<= 1; |
|
|
|
if ((nextRetryLane & RetryLanes) === NoLanes) { |
|
nextRetryLane = RetryLane1; |
|
} |
|
|
|
return lane; |
|
} |
|
function getHighestPriorityLane(lanes) { |
|
return lanes & -lanes; |
|
} |
|
function pickArbitraryLane(lanes) { |
|
// This wrapper function gets inlined. Only exists so to communicate that it |
|
// doesn't matter which bit is selected; you can pick any bit without |
|
// affecting the algorithms where its used. Here I'm using |
|
// getHighestPriorityLane because it requires the fewest operations. |
|
return getHighestPriorityLane(lanes); |
|
} |
|
|
|
function pickArbitraryLaneIndex(lanes) { |
|
return 31 - clz32(lanes); |
|
} |
|
|
|
function laneToIndex(lane) { |
|
return pickArbitraryLaneIndex(lane); |
|
} |
|
|
|
function includesSomeLane(a, b) { |
|
return (a & b) !== NoLanes; |
|
} |
|
function isSubsetOfLanes(set, subset) { |
|
return (set & subset) === subset; |
|
} |
|
function mergeLanes(a, b) { |
|
return a | b; |
|
} |
|
function removeLanes(set, subset) { |
|
return set & ~subset; |
|
} |
|
function intersectLanes(a, b) { |
|
return a & b; |
|
} // Seems redundant, but it changes the type from a single lane (used for |
|
// updates) to a group of lanes (used for flushing work). |
|
|
|
function laneToLanes(lane) { |
|
return lane; |
|
} |
|
function higherPriorityLane(a, b) { |
|
// This works because the bit ranges decrease in priority as you go left. |
|
return a !== NoLane && a < b ? a : b; |
|
} |
|
function createLaneMap(initial) { |
|
// Intentionally pushing one by one. |
|
// https://v8.dev/blog/elements-kinds#avoid-creating-holes |
|
var laneMap = []; |
|
|
|
for (var i = 0; i < TotalLanes; i++) { |
|
laneMap.push(initial); |
|
} |
|
|
|
return laneMap; |
|
} |
|
function markRootUpdated(root, updateLane, eventTime) { |
|
root.pendingLanes |= updateLane; // If there are any suspended transitions, it's possible this new update |
|
// could unblock them. Clear the suspended lanes so that we can try rendering |
|
// them again. |
|
// |
|
// TODO: We really only need to unsuspend only lanes that are in the |
|
// `subtreeLanes` of the updated fiber, or the update lanes of the return |
|
// path. This would exclude suspended updates in an unrelated sibling tree, |
|
// since there's no way for this update to unblock it. |
|
// |
|
// We don't do this if the incoming update is idle, because we never process |
|
// idle updates until after all the regular updates have finished; there's no |
|
// way it could unblock a transition. |
|
|
|
if (updateLane !== IdleLane) { |
|
root.suspendedLanes = NoLanes; |
|
root.pingedLanes = NoLanes; |
|
} |
|
|
|
var eventTimes = root.eventTimes; |
|
var index = laneToIndex(updateLane); // We can always overwrite an existing timestamp because we prefer the most |
|
// recent event, and we assume time is monotonically increasing. |
|
|
|
eventTimes[index] = eventTime; |
|
} |
|
function markRootSuspended(root, suspendedLanes) { |
|
root.suspendedLanes |= suspendedLanes; |
|
root.pingedLanes &= ~suspendedLanes; // The suspended lanes are no longer CPU-bound. Clear their expiration times. |
|
|
|
var expirationTimes = root.expirationTimes; |
|
var lanes = suspendedLanes; |
|
|
|
while (lanes > 0) { |
|
var index = pickArbitraryLaneIndex(lanes); |
|
var lane = 1 << index; |
|
expirationTimes[index] = NoTimestamp; |
|
lanes &= ~lane; |
|
} |
|
} |
|
function markRootPinged(root, pingedLanes, eventTime) { |
|
root.pingedLanes |= root.suspendedLanes & pingedLanes; |
|
} |
|
function markRootFinished(root, remainingLanes) { |
|
var noLongerPendingLanes = root.pendingLanes & ~remainingLanes; |
|
root.pendingLanes = remainingLanes; // Let's try everything again |
|
|
|
root.suspendedLanes = NoLanes; |
|
root.pingedLanes = NoLanes; |
|
root.expiredLanes &= remainingLanes; |
|
root.mutableReadLanes &= remainingLanes; |
|
root.entangledLanes &= remainingLanes; |
|
var entanglements = root.entanglements; |
|
var eventTimes = root.eventTimes; |
|
var expirationTimes = root.expirationTimes; // Clear the lanes that no longer have pending work |
|
|
|
var lanes = noLongerPendingLanes; |
|
|
|
while (lanes > 0) { |
|
var index = pickArbitraryLaneIndex(lanes); |
|
var lane = 1 << index; |
|
entanglements[index] = NoLanes; |
|
eventTimes[index] = NoTimestamp; |
|
expirationTimes[index] = NoTimestamp; |
|
lanes &= ~lane; |
|
} |
|
} |
|
function markRootEntangled(root, entangledLanes) { |
|
// In addition to entangling each of the given lanes with each other, we also |
|
// have to consider _transitive_ entanglements. For each lane that is already |
|
// entangled with *any* of the given lanes, that lane is now transitively |
|
// entangled with *all* the given lanes. |
|
// |
|
// Translated: If C is entangled with A, then entangling A with B also |
|
// entangles C with B. |
|
// |
|
// If this is hard to grasp, it might help to intentionally break this |
|
// function and look at the tests that fail in ReactTransition-test.js. Try |
|
// commenting out one of the conditions below. |
|
var rootEntangledLanes = root.entangledLanes |= entangledLanes; |
|
var entanglements = root.entanglements; |
|
var lanes = rootEntangledLanes; |
|
|
|
while (lanes) { |
|
var index = pickArbitraryLaneIndex(lanes); |
|
var lane = 1 << index; |
|
|
|
if ( // Is this one of the newly entangled lanes? |
|
lane & entangledLanes | // Is this lane transitively entangled with the newly entangled lanes? |
|
entanglements[index] & entangledLanes) { |
|
entanglements[index] |= entangledLanes; |
|
} |
|
|
|
lanes &= ~lane; |
|
} |
|
} |
|
function getBumpedLaneForHydration(root, renderLanes) { |
|
var renderLane = getHighestPriorityLane(renderLanes); |
|
var lane; |
|
|
|
switch (renderLane) { |
|
case InputContinuousLane: |
|
lane = InputContinuousHydrationLane; |
|
break; |
|
|
|
case DefaultLane: |
|
lane = DefaultHydrationLane; |
|
break; |
|
|
|
case TransitionLane1: |
|
case TransitionLane2: |
|
case TransitionLane3: |
|
case TransitionLane4: |
|
case TransitionLane5: |
|
case TransitionLane6: |
|
case TransitionLane7: |
|
case TransitionLane8: |
|
case TransitionLane9: |
|
case TransitionLane10: |
|
case TransitionLane11: |
|
case TransitionLane12: |
|
case TransitionLane13: |
|
case TransitionLane14: |
|
case TransitionLane15: |
|
case TransitionLane16: |
|
case RetryLane1: |
|
case RetryLane2: |
|
case RetryLane3: |
|
case RetryLane4: |
|
case RetryLane5: |
|
lane = TransitionHydrationLane; |
|
break; |
|
|
|
case IdleLane: |
|
lane = IdleHydrationLane; |
|
break; |
|
|
|
default: |
|
// Everything else is already either a hydration lane, or shouldn't |
|
// be retried at a hydration lane. |
|
lane = NoLane; |
|
break; |
|
} // Check if the lane we chose is suspended. If so, that indicates that we |
|
// already attempted and failed to hydrate at that level. Also check if we're |
|
// already rendering that lane, which is rare but could happen. |
|
|
|
|
|
if ((lane & (root.suspendedLanes | renderLanes)) !== NoLane) { |
|
// Give up trying to hydrate and fall back to client render. |
|
return NoLane; |
|
} |
|
|
|
return lane; |
|
} |
|
function addFiberToLanesMap(root, fiber, lanes) { |
|
|
|
if (!isDevToolsPresent) { |
|
return; |
|
} |
|
|
|
var pendingUpdatersLaneMap = root.pendingUpdatersLaneMap; |
|
|
|
while (lanes > 0) { |
|
var index = laneToIndex(lanes); |
|
var lane = 1 << index; |
|
var updaters = pendingUpdatersLaneMap[index]; |
|
updaters.add(fiber); |
|
lanes &= ~lane; |
|
} |
|
} |
|
function movePendingFibersToMemoized(root, lanes) { |
|
|
|
if (!isDevToolsPresent) { |
|
return; |
|
} |
|
|
|
var pendingUpdatersLaneMap = root.pendingUpdatersLaneMap; |
|
var memoizedUpdaters = root.memoizedUpdaters; |
|
|
|
while (lanes > 0) { |
|
var index = laneToIndex(lanes); |
|
var lane = 1 << index; |
|
var updaters = pendingUpdatersLaneMap[index]; |
|
|
|
if (updaters.size > 0) { |
|
updaters.forEach(function (fiber) { |
|
var alternate = fiber.alternate; |
|
|
|
if (alternate === null || !memoizedUpdaters.has(alternate)) { |
|
memoizedUpdaters.add(fiber); |
|
} |
|
}); |
|
updaters.clear(); |
|
} |
|
|
|
lanes &= ~lane; |
|
} |
|
} |
|
function getTransitionsForLanes(root, lanes) { |
|
{ |
|
return null; |
|
} |
|
} |
|
|
|
var DiscreteEventPriority = SyncLane; |
|
var ContinuousEventPriority = InputContinuousLane; |
|
var DefaultEventPriority = DefaultLane; |
|
var IdleEventPriority = IdleLane; |
|
var currentUpdatePriority = NoLane; |
|
function getCurrentUpdatePriority() { |
|
return currentUpdatePriority; |
|
} |
|
function setCurrentUpdatePriority(newPriority) { |
|
currentUpdatePriority = newPriority; |
|
} |
|
function runWithPriority(priority, fn) { |
|
var previousPriority = currentUpdatePriority; |
|
|
|
try { |
|
currentUpdatePriority = priority; |
|
return fn(); |
|
} finally { |
|
currentUpdatePriority = previousPriority; |
|
} |
|
} |
|
function higherEventPriority(a, b) { |
|
return a !== 0 && a < b ? a : b; |
|
} |
|
function lowerEventPriority(a, b) { |
|
return a === 0 || a > b ? a : b; |
|
} |
|
function isHigherEventPriority(a, b) { |
|
return a !== 0 && a < b; |
|
} |
|
function lanesToEventPriority(lanes) { |
|
var lane = getHighestPriorityLane(lanes); |
|
|
|
if (!isHigherEventPriority(DiscreteEventPriority, lane)) { |
|
return DiscreteEventPriority; |
|
} |
|
|
|
if (!isHigherEventPriority(ContinuousEventPriority, lane)) { |
|
return ContinuousEventPriority; |
|
} |
|
|
|
if (includesNonIdleWork(lane)) { |
|
return DefaultEventPriority; |
|
} |
|
|
|
return IdleEventPriority; |
|
} |
|
|
|
// This is imported by the event replaying implementation in React DOM. It's |
|
// in a separate file to break a circular dependency between the renderer and |
|
// the reconciler. |
|
function isRootDehydrated(root) { |
|
var currentState = root.current.memoizedState; |
|
return currentState.isDehydrated; |
|
} |
|
|
|
var _attemptSynchronousHydration; |
|
|
|
function setAttemptSynchronousHydration(fn) { |
|
_attemptSynchronousHydration = fn; |
|
} |
|
function attemptSynchronousHydration(fiber) { |
|
_attemptSynchronousHydration(fiber); |
|
} |
|
var attemptContinuousHydration; |
|
function setAttemptContinuousHydration(fn) { |
|
attemptContinuousHydration = fn; |
|
} |
|
var attemptHydrationAtCurrentPriority; |
|
function setAttemptHydrationAtCurrentPriority(fn) { |
|
attemptHydrationAtCurrentPriority = fn; |
|
} |
|
var getCurrentUpdatePriority$1; |
|
function setGetCurrentUpdatePriority(fn) { |
|
getCurrentUpdatePriority$1 = fn; |
|
} |
|
var attemptHydrationAtPriority; |
|
function setAttemptHydrationAtPriority(fn) { |
|
attemptHydrationAtPriority = fn; |
|
} // TODO: Upgrade this definition once we're on a newer version of Flow that |
|
// has this definition built-in. |
|
|
|
var hasScheduledReplayAttempt = false; // The queue of discrete events to be replayed. |
|
|
|
var queuedDiscreteEvents = []; // Indicates if any continuous event targets are non-null for early bailout. |
|
// if the last target was dehydrated. |
|
|
|
var queuedFocus = null; |
|
var queuedDrag = null; |
|
var queuedMouse = null; // For pointer events there can be one latest event per pointerId. |
|
|
|
var queuedPointers = new Map(); |
|
var queuedPointerCaptures = new Map(); // We could consider replaying selectionchange and touchmoves too. |
|
|
|
var queuedExplicitHydrationTargets = []; |
|
var discreteReplayableEvents = ['mousedown', 'mouseup', 'touchcancel', 'touchend', 'touchstart', 'auxclick', 'dblclick', 'pointercancel', 'pointerdown', 'pointerup', 'dragend', 'dragstart', 'drop', 'compositionend', 'compositionstart', 'keydown', 'keypress', 'keyup', 'input', 'textInput', // Intentionally camelCase |
|
'copy', 'cut', 'paste', 'click', 'change', 'contextmenu', 'reset', 'submit']; |
|
function isDiscreteEventThatRequiresHydration(eventType) { |
|
return discreteReplayableEvents.indexOf(eventType) > -1; |
|
} |
|
|
|
function createQueuedReplayableEvent(blockedOn, domEventName, eventSystemFlags, targetContainer, nativeEvent) { |
|
return { |
|
blockedOn: blockedOn, |
|
domEventName: domEventName, |
|
eventSystemFlags: eventSystemFlags, |
|
nativeEvent: nativeEvent, |
|
targetContainers: [targetContainer] |
|
}; |
|
} |
|
|
|
function clearIfContinuousEvent(domEventName, nativeEvent) { |
|
switch (domEventName) { |
|
case 'focusin': |
|
case 'focusout': |
|
queuedFocus = null; |
|
break; |
|
|
|
case 'dragenter': |
|
case 'dragleave': |
|
queuedDrag = null; |
|
break; |
|
|
|
case 'mouseover': |
|
case 'mouseout': |
|
queuedMouse = null; |
|
break; |
|
|
|
case 'pointerover': |
|
case 'pointerout': |
|
{ |
|
var pointerId = nativeEvent.pointerId; |
|
queuedPointers.delete(pointerId); |
|
break; |
|
} |
|
|
|
case 'gotpointercapture': |
|
case 'lostpointercapture': |
|
{ |
|
var _pointerId = nativeEvent.pointerId; |
|
queuedPointerCaptures.delete(_pointerId); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
function accumulateOrCreateContinuousQueuedReplayableEvent(existingQueuedEvent, blockedOn, domEventName, eventSystemFlags, targetContainer, nativeEvent) { |
|
if (existingQueuedEvent === null || existingQueuedEvent.nativeEvent !== nativeEvent) { |
|
var queuedEvent = createQueuedReplayableEvent(blockedOn, domEventName, eventSystemFlags, targetContainer, nativeEvent); |
|
|
|
if (blockedOn !== null) { |
|
var _fiber2 = getInstanceFromNode(blockedOn); |
|
|
|
if (_fiber2 !== null) { |
|
// Attempt to increase the priority of this target. |
|
attemptContinuousHydration(_fiber2); |
|
} |
|
} |
|
|
|
return queuedEvent; |
|
} // If we have already queued this exact event, then it's because |
|
// the different event systems have different DOM event listeners. |
|
// We can accumulate the flags, and the targetContainers, and |
|
// store a single event to be replayed. |
|
|
|
|
|
existingQueuedEvent.eventSystemFlags |= eventSystemFlags; |
|
var targetContainers = existingQueuedEvent.targetContainers; |
|
|
|
if (targetContainer !== null && targetContainers.indexOf(targetContainer) === -1) { |
|
targetContainers.push(targetContainer); |
|
} |
|
|
|
return existingQueuedEvent; |
|
} |
|
|
|
function queueIfContinuousEvent(blockedOn, domEventName, eventSystemFlags, targetContainer, nativeEvent) { |
|
// These set relatedTarget to null because the replayed event will be treated as if we |
|
// moved from outside the window (no target) onto the target once it hydrates. |
|
// Instead of mutating we could clone the event. |
|
switch (domEventName) { |
|
case 'focusin': |
|
{ |
|
var focusEvent = nativeEvent; |
|
queuedFocus = accumulateOrCreateContinuousQueuedReplayableEvent(queuedFocus, blockedOn, domEventName, eventSystemFlags, targetContainer, focusEvent); |
|
return true; |
|
} |
|
|
|
case 'dragenter': |
|
{ |
|
var dragEvent = nativeEvent; |
|
queuedDrag = accumulateOrCreateContinuousQueuedReplayableEvent(queuedDrag, blockedOn, domEventName, eventSystemFlags, targetContainer, dragEvent); |
|
return true; |
|
} |
|
|
|
case 'mouseover': |
|
{ |
|
var mouseEvent = nativeEvent; |
|
queuedMouse = accumulateOrCreateContinuousQueuedReplayableEvent(queuedMouse, blockedOn, domEventName, eventSystemFlags, targetContainer, mouseEvent); |
|
return true; |
|
} |
|
|
|
case 'pointerover': |
|
{ |
|
var pointerEvent = nativeEvent; |
|
var pointerId = pointerEvent.pointerId; |
|
queuedPointers.set(pointerId, accumulateOrCreateContinuousQueuedReplayableEvent(queuedPointers.get(pointerId) || null, blockedOn, domEventName, eventSystemFlags, targetContainer, pointerEvent)); |
|
return true; |
|
} |
|
|
|
case 'gotpointercapture': |
|
{ |
|
var _pointerEvent = nativeEvent; |
|
var _pointerId2 = _pointerEvent.pointerId; |
|
queuedPointerCaptures.set(_pointerId2, accumulateOrCreateContinuousQueuedReplayableEvent(queuedPointerCaptures.get(_pointerId2) || null, blockedOn, domEventName, eventSystemFlags, targetContainer, _pointerEvent)); |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} // Check if this target is unblocked. Returns true if it's unblocked. |
|
|
|
function attemptExplicitHydrationTarget(queuedTarget) { |
|
// TODO: This function shares a lot of logic with findInstanceBlockingEvent. |
|
// Try to unify them. It's a bit tricky since it would require two return |
|
// values. |
|
var targetInst = getClosestInstanceFromNode(queuedTarget.target); |
|
|
|
if (targetInst !== null) { |
|
var nearestMounted = getNearestMountedFiber(targetInst); |
|
|
|
if (nearestMounted !== null) { |
|
var tag = nearestMounted.tag; |
|
|
|
if (tag === SuspenseComponent) { |
|
var instance = getSuspenseInstanceFromFiber(nearestMounted); |
|
|
|
if (instance !== null) { |
|
// We're blocked on hydrating this boundary. |
|
// Increase its priority. |
|
queuedTarget.blockedOn = instance; |
|
attemptHydrationAtPriority(queuedTarget.priority, function () { |
|
attemptHydrationAtCurrentPriority(nearestMounted); |
|
}); |
|
return; |
|
} |
|
} else if (tag === HostRoot) { |
|
var root = nearestMounted.stateNode; |
|
|
|
if (isRootDehydrated(root)) { |
|
queuedTarget.blockedOn = getContainerFromFiber(nearestMounted); // We don't currently have a way to increase the priority of |
|
// a root other than sync. |
|
|
|
return; |
|
} |
|
} |
|
} |
|
} |
|
|
|
queuedTarget.blockedOn = null; |
|
} |
|
|
|
function queueExplicitHydrationTarget(target) { |
|
// TODO: This will read the priority if it's dispatched by the React |
|
// event system but not native events. Should read window.event.type, like |
|
// we do for updates (getCurrentEventPriority). |
|
var updatePriority = getCurrentUpdatePriority$1(); |
|
var queuedTarget = { |
|
blockedOn: null, |
|
target: target, |
|
priority: updatePriority |
|
}; |
|
var i = 0; |
|
|
|
for (; i < queuedExplicitHydrationTargets.length; i++) { |
|
// Stop once we hit the first target with lower priority than |
|
if (!isHigherEventPriority(updatePriority, queuedExplicitHydrationTargets[i].priority)) { |
|
break; |
|
} |
|
} |
|
|
|
queuedExplicitHydrationTargets.splice(i, 0, queuedTarget); |
|
|
|
if (i === 0) { |
|
attemptExplicitHydrationTarget(queuedTarget); |
|
} |
|
} |
|
|
|
function attemptReplayContinuousQueuedEvent(queuedEvent) { |
|
if (queuedEvent.blockedOn !== null) { |
|
return false; |
|
} |
|
|
|
var targetContainers = queuedEvent.targetContainers; |
|
|
|
while (targetContainers.length > 0) { |
|
var targetContainer = targetContainers[0]; |
|
var nextBlockedOn = findInstanceBlockingEvent(queuedEvent.domEventName, queuedEvent.eventSystemFlags, targetContainer, queuedEvent.nativeEvent); |
|
|
|
if (nextBlockedOn === null) { |
|
{ |
|
var nativeEvent = queuedEvent.nativeEvent; |
|
var nativeEventClone = new nativeEvent.constructor(nativeEvent.type, nativeEvent); |
|
setReplayingEvent(nativeEventClone); |
|
nativeEvent.target.dispatchEvent(nativeEventClone); |
|
resetReplayingEvent(); |
|
} |
|
} else { |
|
// We're still blocked. Try again later. |
|
var _fiber3 = getInstanceFromNode(nextBlockedOn); |
|
|
|
if (_fiber3 !== null) { |
|
attemptContinuousHydration(_fiber3); |
|
} |
|
|
|
queuedEvent.blockedOn = nextBlockedOn; |
|
return false; |
|
} // This target container was successfully dispatched. Try the next. |
|
|
|
|
|
targetContainers.shift(); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function attemptReplayContinuousQueuedEventInMap(queuedEvent, key, map) { |
|
if (attemptReplayContinuousQueuedEvent(queuedEvent)) { |
|
map.delete(key); |
|
} |
|
} |
|
|
|
function replayUnblockedEvents() { |
|
hasScheduledReplayAttempt = false; |
|
|
|
|
|
if (queuedFocus !== null && attemptReplayContinuousQueuedEvent(queuedFocus)) { |
|
queuedFocus = null; |
|
} |
|
|
|
if (queuedDrag !== null && attemptReplayContinuousQueuedEvent(queuedDrag)) { |
|
queuedDrag = null; |
|
} |
|
|
|
if (queuedMouse !== null && attemptReplayContinuousQueuedEvent(queuedMouse)) { |
|
queuedMouse = null; |
|
} |
|
|
|
queuedPointers.forEach(attemptReplayContinuousQueuedEventInMap); |
|
queuedPointerCaptures.forEach(attemptReplayContinuousQueuedEventInMap); |
|
} |
|
|
|
function scheduleCallbackIfUnblocked(queuedEvent, unblocked) { |
|
if (queuedEvent.blockedOn === unblocked) { |
|
queuedEvent.blockedOn = null; |
|
|
|
if (!hasScheduledReplayAttempt) { |
|
hasScheduledReplayAttempt = true; // Schedule a callback to attempt replaying as many events as are |
|
// now unblocked. This first might not actually be unblocked yet. |
|
// We could check it early to avoid scheduling an unnecessary callback. |
|
|
|
unstable_scheduleCallback(unstable_NormalPriority, replayUnblockedEvents); |
|
} |
|
} |
|
} |
|
|
|
function retryIfBlockedOn(unblocked) { |
|
// Mark anything that was blocked on this as no longer blocked |
|
// and eligible for a replay. |
|
if (queuedDiscreteEvents.length > 0) { |
|
scheduleCallbackIfUnblocked(queuedDiscreteEvents[0], unblocked); // This is a exponential search for each boundary that commits. I think it's |
|
// worth it because we expect very few discrete events to queue up and once |
|
// we are actually fully unblocked it will be fast to replay them. |
|
|
|
for (var i = 1; i < queuedDiscreteEvents.length; i++) { |
|
var queuedEvent = queuedDiscreteEvents[i]; |
|
|
|
if (queuedEvent.blockedOn === unblocked) { |
|
queuedEvent.blockedOn = null; |
|
} |
|
} |
|
} |
|
|
|
if (queuedFocus !== null) { |
|
scheduleCallbackIfUnblocked(queuedFocus, unblocked); |
|
} |
|
|
|
if (queuedDrag !== null) { |
|
scheduleCallbackIfUnblocked(queuedDrag, unblocked); |
|
} |
|
|
|
if (queuedMouse !== null) { |
|
scheduleCallbackIfUnblocked(queuedMouse, unblocked); |
|
} |
|
|
|
var unblock = function (queuedEvent) { |
|
return scheduleCallbackIfUnblocked(queuedEvent, unblocked); |
|
}; |
|
|
|
queuedPointers.forEach(unblock); |
|
queuedPointerCaptures.forEach(unblock); |
|
|
|
for (var _i = 0; _i < queuedExplicitHydrationTargets.length; _i++) { |
|
var queuedTarget = queuedExplicitHydrationTargets[_i]; |
|
|
|
if (queuedTarget.blockedOn === unblocked) { |
|
queuedTarget.blockedOn = null; |
|
} |
|
} |
|
|
|
while (queuedExplicitHydrationTargets.length > 0) { |
|
var nextExplicitTarget = queuedExplicitHydrationTargets[0]; |
|
|
|
if (nextExplicitTarget.blockedOn !== null) { |
|
// We're still blocked. |
|
break; |
|
} else { |
|
attemptExplicitHydrationTarget(nextExplicitTarget); |
|
|
|
if (nextExplicitTarget.blockedOn === null) { |
|
// We're unblocked. |
|
queuedExplicitHydrationTargets.shift(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
var ReactCurrentBatchConfig = ReactSharedInternals.ReactCurrentBatchConfig; // TODO: can we stop exporting these? |
|
|
|
var _enabled = true; // This is exported in FB builds for use by legacy FB layer infra. |
|
// We'd like to remove this but it's not clear if this is safe. |
|
|
|
function setEnabled(enabled) { |
|
_enabled = !!enabled; |
|
} |
|
function isEnabled() { |
|
return _enabled; |
|
} |
|
function createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags) { |
|
var eventPriority = getEventPriority(domEventName); |
|
var listenerWrapper; |
|
|
|
switch (eventPriority) { |
|
case DiscreteEventPriority: |
|
listenerWrapper = dispatchDiscreteEvent; |
|
break; |
|
|
|
case ContinuousEventPriority: |
|
listenerWrapper = dispatchContinuousEvent; |
|
break; |
|
|
|
case DefaultEventPriority: |
|
default: |
|
listenerWrapper = dispatchEvent; |
|
break; |
|
} |
|
|
|
return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer); |
|
} |
|
|
|
function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent) { |
|
var previousPriority = getCurrentUpdatePriority(); |
|
var prevTransition = ReactCurrentBatchConfig.transition; |
|
ReactCurrentBatchConfig.transition = null; |
|
|
|
try { |
|
setCurrentUpdatePriority(DiscreteEventPriority); |
|
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent); |
|
} finally { |
|
setCurrentUpdatePriority(previousPriority); |
|
ReactCurrentBatchConfig.transition = prevTransition; |
|
} |
|
} |
|
|
|
function dispatchContinuousEvent(domEventName, eventSystemFlags, container, nativeEvent) { |
|
var previousPriority = getCurrentUpdatePriority(); |
|
var prevTransition = ReactCurrentBatchConfig.transition; |
|
ReactCurrentBatchConfig.transition = null; |
|
|
|
try { |
|
setCurrentUpdatePriority(ContinuousEventPriority); |
|
dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent); |
|
} finally { |
|
setCurrentUpdatePriority(previousPriority); |
|
ReactCurrentBatchConfig.transition = prevTransition; |
|
} |
|
} |
|
|
|
function dispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) { |
|
if (!_enabled) { |
|
return; |
|
} |
|
|
|
{ |
|
dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay(domEventName, eventSystemFlags, targetContainer, nativeEvent); |
|
} |
|
} |
|
|
|
function dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay(domEventName, eventSystemFlags, targetContainer, nativeEvent) { |
|
var blockedOn = findInstanceBlockingEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent); |
|
|
|
if (blockedOn === null) { |
|
dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, return_targetInst, targetContainer); |
|
clearIfContinuousEvent(domEventName, nativeEvent); |
|
return; |
|
} |
|
|
|
if (queueIfContinuousEvent(blockedOn, domEventName, eventSystemFlags, targetContainer, nativeEvent)) { |
|
nativeEvent.stopPropagation(); |
|
return; |
|
} // We need to clear only if we didn't queue because |
|
// queueing is accumulative. |
|
|
|
|
|
clearIfContinuousEvent(domEventName, nativeEvent); |
|
|
|
if (eventSystemFlags & IS_CAPTURE_PHASE && isDiscreteEventThatRequiresHydration(domEventName)) { |
|
while (blockedOn !== null) { |
|
var fiber = getInstanceFromNode(blockedOn); |
|
|
|
if (fiber !== null) { |
|
attemptSynchronousHydration(fiber); |
|
} |
|
|
|
var nextBlockedOn = findInstanceBlockingEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent); |
|
|
|
if (nextBlockedOn === null) { |
|
dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, return_targetInst, targetContainer); |
|
} |
|
|
|
if (nextBlockedOn === blockedOn) { |
|
break; |
|
} |
|
|
|
blockedOn = nextBlockedOn; |
|
} |
|
|
|
if (blockedOn !== null) { |
|
nativeEvent.stopPropagation(); |
|
} |
|
|
|
return; |
|
} // This is not replayable so we'll invoke it but without a target, |
|
// in case the event system needs to trace it. |
|
|
|
|
|
dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, null, targetContainer); |
|
} |
|
|
|
var return_targetInst = null; // Returns a SuspenseInstance or Container if it's blocked. |
|
// The return_targetInst field above is conceptually part of the return value. |
|
|
|
function findInstanceBlockingEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) { |
|
// TODO: Warn if _enabled is false. |
|
return_targetInst = null; |
|
var nativeEventTarget = getEventTarget(nativeEvent); |
|
var targetInst = getClosestInstanceFromNode(nativeEventTarget); |
|
|
|
if (targetInst !== null) { |
|
var nearestMounted = getNearestMountedFiber(targetInst); |
|
|
|
if (nearestMounted === null) { |
|
// This tree has been unmounted already. Dispatch without a target. |
|
targetInst = null; |
|
} else { |
|
var tag = nearestMounted.tag; |
|
|
|
if (tag === SuspenseComponent) { |
|
var instance = getSuspenseInstanceFromFiber(nearestMounted); |
|
|
|
if (instance !== null) { |
|
// Queue the event to be replayed later. Abort dispatching since we |
|
// don't want this event dispatched twice through the event system. |
|
// TODO: If this is the first discrete event in the queue. Schedule an increased |
|
// priority for this boundary. |
|
return instance; |
|
} // This shouldn't happen, something went wrong but to avoid blocking |
|
// the whole system, dispatch the event without a target. |
|
// TODO: Warn. |
|
|
|
|
|
targetInst = null; |
|
} else if (tag === HostRoot) { |
|
var root = nearestMounted.stateNode; |
|
|
|
if (isRootDehydrated(root)) { |
|
// If this happens during a replay something went wrong and it might block |
|
// the whole system. |
|
return getContainerFromFiber(nearestMounted); |
|
} |
|
|
|
targetInst = null; |
|
} else if (nearestMounted !== targetInst) { |
|
// If we get an event (ex: img onload) before committing that |
|
// component's mount, ignore it for now (that is, treat it as if it was an |
|
// event on a non-React tree). We might also consider queueing events and |
|
// dispatching them after the mount. |
|
targetInst = null; |
|
} |
|
} |
|
} |
|
|
|
return_targetInst = targetInst; // We're not blocked on anything. |
|
|
|
return null; |
|
} |
|
function getEventPriority(domEventName) { |
|
switch (domEventName) { |
|
// Used by SimpleEventPlugin: |
|
case 'cancel': |
|
case 'click': |
|
case 'close': |
|
case 'contextmenu': |
|
case 'copy': |
|
case 'cut': |
|
case 'auxclick': |
|
case 'dblclick': |
|
case 'dragend': |
|
case 'dragstart': |
|
case 'drop': |
|
case 'focusin': |
|
case 'focusout': |
|
case 'input': |
|
case 'invalid': |
|
case 'keydown': |
|
case 'keypress': |
|
case 'keyup': |
|
case 'mousedown': |
|
case 'mouseup': |
|
case 'paste': |
|
case 'pause': |
|
case 'play': |
|
case 'pointercancel': |
|
case 'pointerdown': |
|
case 'pointerup': |
|
case 'ratechange': |
|
case 'reset': |
|
case 'resize': |
|
case 'seeked': |
|
case 'submit': |
|
case 'touchcancel': |
|
case 'touchend': |
|
case 'touchstart': |
|
case 'volumechange': // Used by polyfills: |
|
// eslint-disable-next-line no-fallthrough |
|
|
|
case 'change': |
|
case 'selectionchange': |
|
case 'textInput': |
|
case 'compositionstart': |
|
case 'compositionend': |
|
case 'compositionupdate': // Only enableCreateEventHandleAPI: |
|
// eslint-disable-next-line no-fallthrough |
|
|
|
case 'beforeblur': |
|
case 'afterblur': // Not used by React but could be by user code: |
|
// eslint-disable-next-line no-fallthrough |
|
|
|
case 'beforeinput': |
|
case 'blur': |
|
case 'fullscreenchange': |
|
case 'focus': |
|
case 'hashchange': |
|
case 'popstate': |
|
case 'select': |
|
case 'selectstart': |
|
return DiscreteEventPriority; |
|
|
|
case 'drag': |
|
case 'dragenter': |
|
case 'dragexit': |
|
case 'dragleave': |
|
case 'dragover': |
|
case 'mousemove': |
|
case 'mouseout': |
|
case 'mouseover': |
|
case 'pointermove': |
|
case 'pointerout': |
|
case 'pointerover': |
|
case 'scroll': |
|
case 'toggle': |
|
case 'touchmove': |
|
case 'wheel': // Not used by React but could be by user code: |
|
// eslint-disable-next-line no-fallthrough |
|
|
|
case 'mouseenter': |
|
case 'mouseleave': |
|
case 'pointerenter': |
|
case 'pointerleave': |
|
return ContinuousEventPriority; |
|
|
|
case 'message': |
|
{ |
|
// We might be in the Scheduler callback. |
|
// Eventually this mechanism will be replaced by a check |
|
// of the current priority on the native scheduler. |
|
var schedulerPriority = getCurrentPriorityLevel(); |
|
|
|
switch (schedulerPriority) { |
|
case ImmediatePriority: |
|
return DiscreteEventPriority; |
|
|
|
case UserBlockingPriority: |
|
return ContinuousEventPriority; |
|
|
|
case NormalPriority: |
|
case LowPriority: |
|
// TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration. |
|
return DefaultEventPriority; |
|
|
|
case IdlePriority: |
|
return IdleEventPriority; |
|
|
|
default: |
|
return DefaultEventPriority; |
|
} |
|
} |
|
|
|
default: |
|
return DefaultEventPriority; |
|
} |
|
} |
|
|
|
function addEventBubbleListener(target, eventType, listener) { |
|
target.addEventListener(eventType, listener, false); |
|
return listener; |
|
} |
|
function addEventCaptureListener(target, eventType, listener) { |
|
target.addEventListener(eventType, listener, true); |
|
return listener; |
|
} |
|
function addEventCaptureListenerWithPassiveFlag(target, eventType, listener, passive) { |
|
target.addEventListener(eventType, listener, { |
|
capture: true, |
|
passive: passive |
|
}); |
|
return listener; |
|
} |
|
function addEventBubbleListenerWithPassiveFlag(target, eventType, listener, passive) { |
|
target.addEventListener(eventType, listener, { |
|
passive: passive |
|
}); |
|
return listener; |
|
} |
|
|
|
/** |
|
* These variables store information about text content of a target node, |
|
* allowing comparison of content before and after a given event. |
|
* |
|
* Identify the node where selection currently begins, then observe |
|
* both its text content and its current position in the DOM. Since the |
|
* browser may natively replace the target node during composition, we can |
|
* use its position to find its replacement. |
|
* |
|
* |
|
*/ |
|
var root = null; |
|
var startText = null; |
|
var fallbackText = null; |
|
function initialize(nativeEventTarget) { |
|
root = nativeEventTarget; |
|
startText = getText(); |
|
return true; |
|
} |
|
function reset() { |
|
root = null; |
|
startText = null; |
|
fallbackText = null; |
|
} |
|
function getData() { |
|
if (fallbackText) { |
|
return fallbackText; |
|
} |
|
|
|
var start; |
|
var startValue = startText; |
|
var startLength = startValue.length; |
|
var end; |
|
var endValue = getText(); |
|
var endLength = endValue.length; |
|
|
|
for (start = 0; start < startLength; start++) { |
|
if (startValue[start] !== endValue[start]) { |
|
break; |
|
} |
|
} |
|
|
|
var minEnd = startLength - start; |
|
|
|
for (end = 1; end <= minEnd; end++) { |
|
if (startValue[startLength - end] !== endValue[endLength - end]) { |
|
break; |
|
} |
|
} |
|
|
|
var sliceTail = end > 1 ? 1 - end : undefined; |
|
fallbackText = endValue.slice(start, sliceTail); |
|
return fallbackText; |
|
} |
|
function getText() { |
|
if ('value' in root) { |
|
return root.value; |
|
} |
|
|
|
return root.textContent; |
|
} |
|
|
|
/** |
|
* `charCode` represents the actual "character code" and is safe to use with |
|
* `String.fromCharCode`. As such, only keys that correspond to printable |
|
* characters produce a valid `charCode`, the only exception to this is Enter. |
|
* The Tab-key is considered non-printable and does not have a `charCode`, |
|
* presumably because it does not produce a tab-character in browsers. |
|
* |
|
* @param {object} nativeEvent Native browser event. |
|
* @return {number} Normalized `charCode` property. |
|
*/ |
|
function getEventCharCode(nativeEvent) { |
|
var charCode; |
|
var keyCode = nativeEvent.keyCode; |
|
|
|
if ('charCode' in nativeEvent) { |
|
charCode = nativeEvent.charCode; // FF does not set `charCode` for the Enter-key, check against `keyCode`. |
|
|
|
if (charCode === 0 && keyCode === 13) { |
|
charCode = 13; |
|
} |
|
} else { |
|
// IE8 does not implement `charCode`, but `keyCode` has the correct value. |
|
charCode = keyCode; |
|
} // IE and Edge (on Windows) and Chrome / Safari (on Windows and Linux) |
|
// report Enter as charCode 10 when ctrl is pressed. |
|
|
|
|
|
if (charCode === 10) { |
|
charCode = 13; |
|
} // Some non-printable keys are reported in `charCode`/`keyCode`, discard them. |
|
// Must not discard the (non-)printable Enter-key. |
|
|
|
|
|
if (charCode >= 32 || charCode === 13) { |
|
return charCode; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
function functionThatReturnsTrue() { |
|
return true; |
|
} |
|
|
|
function functionThatReturnsFalse() { |
|
return false; |
|
} // This is intentionally a factory so that we have different returned constructors. |
|
// If we had a single constructor, it would be megamorphic and engines would deopt. |
|
|
|
|
|
function createSyntheticEvent(Interface) { |
|
/** |
|
* Synthetic events are dispatched by event plugins, typically in response to a |
|
* top-level event delegation handler. |
|
* |
|
* These systems should generally use pooling to reduce the frequency of garbage |
|
* collection. The system should check `isPersistent` to determine whether the |
|
* event should be released into the pool after being dispatched. Users that |
|
* need a persisted event should invoke `persist`. |
|
* |
|
* Synthetic events (and subclasses) implement the DOM Level 3 Events API by |
|
* normalizing browser quirks. Subclasses do not necessarily have to implement a |
|
* DOM interface; custom application-specific events can also subclass this. |
|
*/ |
|
function SyntheticBaseEvent(reactName, reactEventType, targetInst, nativeEvent, nativeEventTarget) { |
|
this._reactName = reactName; |
|
this._targetInst = targetInst; |
|
this.type = reactEventType; |
|
this.nativeEvent = nativeEvent; |
|
this.target = nativeEventTarget; |
|
this.currentTarget = null; |
|
|
|
for (var _propName in Interface) { |
|
if (!Interface.hasOwnProperty(_propName)) { |
|
continue; |
|
} |
|
|
|
var normalize = Interface[_propName]; |
|
|
|
if (normalize) { |
|
this[_propName] = normalize(nativeEvent); |
|
} else { |
|
this[_propName] = nativeEvent[_propName]; |
|
} |
|
} |
|
|
|
var defaultPrevented = nativeEvent.defaultPrevented != null ? nativeEvent.defaultPrevented : nativeEvent.returnValue === false; |
|
|
|
if (defaultPrevented) { |
|
this.isDefaultPrevented = functionThatReturnsTrue; |
|
} else { |
|
this.isDefaultPrevented = functionThatReturnsFalse; |
|
} |
|
|
|
this.isPropagationStopped = functionThatReturnsFalse; |
|
return this; |
|
} |
|
|
|
assign(SyntheticBaseEvent.prototype, { |
|
preventDefault: function () { |
|
this.defaultPrevented = true; |
|
var event = this.nativeEvent; |
|
|
|
if (!event) { |
|
return; |
|
} |
|
|
|
if (event.preventDefault) { |
|
event.preventDefault(); // $FlowFixMe - flow is not aware of `unknown` in IE |
|
} else if (typeof event.returnValue !== 'unknown') { |
|
event.returnValue = false; |
|
} |
|
|
|
this.isDefaultPrevented = functionThatReturnsTrue; |
|
}, |
|
stopPropagation: function () { |
|
var event = this.nativeEvent; |
|
|
|
if (!event) { |
|
return; |
|
} |
|
|
|
if (event.stopPropagation) { |
|
event.stopPropagation(); // $FlowFixMe - flow is not aware of `unknown` in IE |
|
} else if (typeof event.cancelBubble !== 'unknown') { |
|
// The ChangeEventPlugin registers a "propertychange" event for |
|
// IE. This event does not support bubbling or cancelling, and |
|
// any references to cancelBubble throw "Member not found". A |
|
// typeof check of "unknown" circumvents this issue (and is also |
|
// IE specific). |
|
event.cancelBubble = true; |
|
} |
|
|
|
this.isPropagationStopped = functionThatReturnsTrue; |
|
}, |
|
|
|
/** |
|
* We release all dispatched `SyntheticEvent`s after each event loop, adding |
|
* them back into the pool. This allows a way to hold onto a reference that |
|
* won't be added back into the pool. |
|
*/ |
|
persist: function () {// Modern event system doesn't use pooling. |
|
}, |
|
|
|
/** |
|
* Checks if this event should be released back into the pool. |
|
* |
|
* @return {boolean} True if this should not be released, false otherwise. |
|
*/ |
|
isPersistent: functionThatReturnsTrue |
|
}); |
|
return SyntheticBaseEvent; |
|
} |
|
/** |
|
* @interface Event |
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/ |
|
*/ |
|
|
|
|
|
var EventInterface = { |
|
eventPhase: 0, |
|
bubbles: 0, |
|
cancelable: 0, |
|
timeStamp: function (event) { |
|
return event.timeStamp || Date.now(); |
|
}, |
|
defaultPrevented: 0, |
|
isTrusted: 0 |
|
}; |
|
var SyntheticEvent = createSyntheticEvent(EventInterface); |
|
|
|
var UIEventInterface = assign({}, EventInterface, { |
|
view: 0, |
|
detail: 0 |
|
}); |
|
|
|
var SyntheticUIEvent = createSyntheticEvent(UIEventInterface); |
|
var lastMovementX; |
|
var lastMovementY; |
|
var lastMouseEvent; |
|
|
|
function updateMouseMovementPolyfillState(event) { |
|
if (event !== lastMouseEvent) { |
|
if (lastMouseEvent && event.type === 'mousemove') { |
|
lastMovementX = event.screenX - lastMouseEvent.screenX; |
|
lastMovementY = event.screenY - lastMouseEvent.screenY; |
|
} else { |
|
lastMovementX = 0; |
|
lastMovementY = 0; |
|
} |
|
|
|
lastMouseEvent = event; |
|
} |
|
} |
|
/** |
|
* @interface MouseEvent |
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/ |
|
*/ |
|
|
|
|
|
var MouseEventInterface = assign({}, UIEventInterface, { |
|
screenX: 0, |
|
screenY: 0, |
|
clientX: 0, |
|
clientY: 0, |
|
pageX: 0, |
|
pageY: 0, |
|
ctrlKey: 0, |
|
shiftKey: 0, |
|
altKey: 0, |
|
metaKey: 0, |
|
getModifierState: getEventModifierState, |
|
button: 0, |
|
buttons: 0, |
|
relatedTarget: function (event) { |
|
if (event.relatedTarget === undefined) return event.fromElement === event.srcElement ? event.toElement : event.fromElement; |
|
return event.relatedTarget; |
|
}, |
|
movementX: function (event) { |
|
if ('movementX' in event) { |
|
return event.movementX; |
|
} |
|
|
|
updateMouseMovementPolyfillState(event); |
|
return lastMovementX; |
|
}, |
|
movementY: function (event) { |
|
if ('movementY' in event) { |
|
return event.movementY; |
|
} // Don't need to call updateMouseMovementPolyfillState() here |
|
// because it's guaranteed to have already run when movementX |
|
// was copied. |
|
|
|
|
|
return lastMovementY; |
|
} |
|
}); |
|
|
|
var SyntheticMouseEvent = createSyntheticEvent(MouseEventInterface); |
|
/** |
|
* @interface DragEvent |
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/ |
|
*/ |
|
|
|
var DragEventInterface = assign({}, MouseEventInterface, { |
|
dataTransfer: 0 |
|
}); |
|
|
|
var SyntheticDragEvent = createSyntheticEvent(DragEventInterface); |
|
/** |
|
* @interface FocusEvent |
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/ |
|
*/ |
|
|
|
var FocusEventInterface = assign({}, UIEventInterface, { |
|
relatedTarget: 0 |
|
}); |
|
|
|
var SyntheticFocusEvent = createSyntheticEvent(FocusEventInterface); |
|
/** |
|
* @interface Event |
|
* @see http://www.w3.org/TR/css3-animations/#AnimationEvent-interface |
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/AnimationEvent |
|
*/ |
|
|
|
var AnimationEventInterface = assign({}, EventInterface, { |
|
animationName: 0, |
|
elapsedTime: 0, |
|
pseudoElement: 0 |
|
}); |
|
|
|
var SyntheticAnimationEvent = createSyntheticEvent(AnimationEventInterface); |
|
/** |
|
* @interface Event |
|
* @see http://www.w3.org/TR/clipboard-apis/ |
|
*/ |
|
|
|
var ClipboardEventInterface = assign({}, EventInterface, { |
|
clipboardData: function (event) { |
|
return 'clipboardData' in event ? event.clipboardData : window.clipboardData; |
|
} |
|
}); |
|
|
|
var SyntheticClipboardEvent = createSyntheticEvent(ClipboardEventInterface); |
|
/** |
|
* @interface Event |
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/#events-compositionevents |
|
*/ |
|
|
|
var CompositionEventInterface = assign({}, EventInterface, { |
|
data: 0 |
|
}); |
|
|
|
var SyntheticCompositionEvent = createSyntheticEvent(CompositionEventInterface); |
|
/** |
|
* @interface Event |
|
* @see http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105 |
|
* /#events-inputevents |
|
*/ |
|
// Happens to share the same list for now. |
|
|
|
var SyntheticInputEvent = SyntheticCompositionEvent; |
|
/** |
|
* Normalization of deprecated HTML5 `key` values |
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names |
|
*/ |
|
|
|
var normalizeKey = { |
|
Esc: 'Escape', |
|
Spacebar: ' ', |
|
Left: 'ArrowLeft', |
|
Up: 'ArrowUp', |
|
Right: 'ArrowRight', |
|
Down: 'ArrowDown', |
|
Del: 'Delete', |
|
Win: 'OS', |
|
Menu: 'ContextMenu', |
|
Apps: 'ContextMenu', |
|
Scroll: 'ScrollLock', |
|
MozPrintableKey: 'Unidentified' |
|
}; |
|
/** |
|
* Translation from legacy `keyCode` to HTML5 `key` |
|
* Only special keys supported, all others depend on keyboard layout or browser |
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names |
|
*/ |
|
|
|
var translateToKey = { |
|
'8': 'Backspace', |
|
'9': 'Tab', |
|
'12': 'Clear', |
|
'13': 'Enter', |
|
'16': 'Shift', |
|
'17': 'Control', |
|
'18': 'Alt', |
|
'19': 'Pause', |
|
'20': 'CapsLock', |
|
'27': 'Escape', |
|
'32': ' ', |
|
'33': 'PageUp', |
|
'34': 'PageDown', |
|
'35': 'End', |
|
'36': 'Home', |
|
'37': 'ArrowLeft', |
|
'38': 'ArrowUp', |
|
'39': 'ArrowRight', |
|
'40': 'ArrowDown', |
|
'45': 'Insert', |
|
'46': 'Delete', |
|
'112': 'F1', |
|
'113': 'F2', |
|
'114': 'F3', |
|
'115': 'F4', |
|
'116': 'F5', |
|
'117': 'F6', |
|
'118': 'F7', |
|
'119': 'F8', |
|
'120': 'F9', |
|
'121': 'F10', |
|
'122': 'F11', |
|
'123': 'F12', |
|
'144': 'NumLock', |
|
'145': 'ScrollLock', |
|
'224': 'Meta' |
|
}; |
|
/** |
|
* @param {object} nativeEvent Native browser event. |
|
* @return {string} Normalized `key` property. |
|
*/ |
|
|
|
function getEventKey(nativeEvent) { |
|
if (nativeEvent.key) { |
|
// Normalize inconsistent values reported by browsers due to |
|
// implementations of a working draft specification. |
|
// FireFox implements `key` but returns `MozPrintableKey` for all |
|
// printable characters (normalized to `Unidentified`), ignore it. |
|
var key = normalizeKey[nativeEvent.key] || nativeEvent.key; |
|
|
|
if (key !== 'Unidentified') { |
|
return key; |
|
} |
|
} // Browser does not implement `key`, polyfill as much of it as we can. |
|
|
|
|
|
if (nativeEvent.type === 'keypress') { |
|
var charCode = getEventCharCode(nativeEvent); // The enter-key is technically both printable and non-printable and can |
|
// thus be captured by `keypress`, no other non-printable key should. |
|
|
|
return charCode === 13 ? 'Enter' : String.fromCharCode(charCode); |
|
} |
|
|
|
if (nativeEvent.type === 'keydown' || nativeEvent.type === 'keyup') { |
|
// While user keyboard layout determines the actual meaning of each |
|
// `keyCode` value, almost all function keys have a universal value. |
|
return translateToKey[nativeEvent.keyCode] || 'Unidentified'; |
|
} |
|
|
|
return ''; |
|
} |
|
/** |
|
* Translation from modifier key to the associated property in the event. |
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/#keys-Modifiers |
|
*/ |
|
|
|
|
|
var modifierKeyToProp = { |
|
Alt: 'altKey', |
|
Control: 'ctrlKey', |
|
Meta: 'metaKey', |
|
Shift: 'shiftKey' |
|
}; // Older browsers (Safari <= 10, iOS Safari <= 10.2) do not support |
|
// getModifierState. If getModifierState is not supported, we map it to a set of |
|
// modifier keys exposed by the event. In this case, Lock-keys are not supported. |
|
|
|
function modifierStateGetter(keyArg) { |
|
var syntheticEvent = this; |
|
var nativeEvent = syntheticEvent.nativeEvent; |
|
|
|
if (nativeEvent.getModifierState) { |
|
return nativeEvent.getModifierState(keyArg); |
|
} |
|
|
|
var keyProp = modifierKeyToProp[keyArg]; |
|
return keyProp ? !!nativeEvent[keyProp] : false; |
|
} |
|
|
|
function getEventModifierState(nativeEvent) { |
|
return modifierStateGetter; |
|
} |
|
/** |
|
* @interface KeyboardEvent |
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/ |
|
*/ |
|
|
|
|
|
var KeyboardEventInterface = assign({}, UIEventInterface, { |
|
key: getEventKey, |
|
code: 0, |
|
location: 0, |
|
ctrlKey: 0, |
|
shiftKey: 0, |
|
altKey: 0, |
|
metaKey: 0, |
|
repeat: 0, |
|
locale: 0, |
|
getModifierState: getEventModifierState, |
|
// Legacy Interface |
|
charCode: function (event) { |
|
// `charCode` is the result of a KeyPress event and represents the value of |
|
// the actual printable character. |
|
// KeyPress is deprecated, but its replacement is not yet final and not |
|
// implemented in any major browser. Only KeyPress has charCode. |
|
if (event.type === 'keypress') { |
|
return getEventCharCode(event); |
|
} |
|
|
|
return 0; |
|
}, |
|
keyCode: function (event) { |
|
// `keyCode` is the result of a KeyDown/Up event and represents the value of |
|
// physical keyboard key. |
|
// The actual meaning of the value depends on the users' keyboard layout |
|
// which cannot be detected. Assuming that it is a US keyboard layout |
|
// provides a surprisingly accurate mapping for US and European users. |
|
// Due to this, it is left to the user to implement at this time. |
|
if (event.type === 'keydown' || event.type === 'keyup') { |
|
return event.keyCode; |
|
} |
|
|
|
return 0; |
|
}, |
|
which: function (event) { |
|
// `which` is an alias for either `keyCode` or `charCode` depending on the |
|
// type of the event. |
|
if (event.type === 'keypress') { |
|
return getEventCharCode(event); |
|
} |
|
|
|
if (event.type === 'keydown' || event.type === 'keyup') { |
|
return event.keyCode; |
|
} |
|
|
|
return 0; |
|
} |
|
}); |
|
|
|
var SyntheticKeyboardEvent = createSyntheticEvent(KeyboardEventInterface); |
|
/** |
|
* @interface PointerEvent |
|
* @see http://www.w3.org/TR/pointerevents/ |
|
*/ |
|
|
|
var PointerEventInterface = assign({}, MouseEventInterface, { |
|
pointerId: 0, |
|
width: 0, |
|
height: 0, |
|
pressure: 0, |
|
tangentialPressure: 0, |
|
tiltX: 0, |
|
tiltY: 0, |
|
twist: 0, |
|
pointerType: 0, |
|
isPrimary: 0 |
|
}); |
|
|
|
var SyntheticPointerEvent = createSyntheticEvent(PointerEventInterface); |
|
/** |
|
* @interface TouchEvent |
|
* @see http://www.w3.org/TR/touch-events/ |
|
*/ |
|
|
|
var TouchEventInterface = assign({}, UIEventInterface, { |
|
touches: 0, |
|
targetTouches: 0, |
|
changedTouches: 0, |
|
altKey: 0, |
|
metaKey: 0, |
|
ctrlKey: 0, |
|
shiftKey: 0, |
|
getModifierState: getEventModifierState |
|
}); |
|
|
|
var SyntheticTouchEvent = createSyntheticEvent(TouchEventInterface); |
|
/** |
|
* @interface Event |
|
* @see http://www.w3.org/TR/2009/WD-css3-transitions-20090320/#transition-events- |
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/TransitionEvent |
|
*/ |
|
|
|
var TransitionEventInterface = assign({}, EventInterface, { |
|
propertyName: 0, |
|
elapsedTime: 0, |
|
pseudoElement: 0 |
|
}); |
|
|
|
var SyntheticTransitionEvent = createSyntheticEvent(TransitionEventInterface); |
|
/** |
|
* @interface WheelEvent |
|
* @see http://www.w3.org/TR/DOM-Level-3-Events/ |
|
*/ |
|
|
|
var WheelEventInterface = assign({}, MouseEventInterface, { |
|
deltaX: function (event) { |
|
return 'deltaX' in event ? event.deltaX : // Fallback to `wheelDeltaX` for Webkit and normalize (right is positive). |
|
'wheelDeltaX' in event ? -event.wheelDeltaX : 0; |
|
}, |
|
deltaY: function (event) { |
|
return 'deltaY' in event ? event.deltaY : // Fallback to `wheelDeltaY` for Webkit and normalize (down is positive). |
|
'wheelDeltaY' in event ? -event.wheelDeltaY : // Fallback to `wheelDelta` for IE<9 and normalize (down is positive). |
|
'wheelDelta' in event ? -event.wheelDelta : 0; |
|
}, |
|
deltaZ: 0, |
|
// Browsers without "deltaMode" is reporting in raw wheel delta where one |
|
// notch on the scroll is always +/- 120, roughly equivalent to pixels. |
|
// A good approximation of DOM_DELTA_LINE (1) is 5% of viewport size or |
|
// ~40 pixels, for DOM_DELTA_SCREEN (2) it is 87.5% of viewport size. |
|
deltaMode: 0 |
|
}); |
|
|
|
var SyntheticWheelEvent = createSyntheticEvent(WheelEventInterface); |
|
|
|
var END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space |
|
|
|
var START_KEYCODE = 229; |
|
var canUseCompositionEvent = canUseDOM && 'CompositionEvent' in window; |
|
var documentMode = null; |
|
|
|
if (canUseDOM && 'documentMode' in document) { |
|
documentMode = document.documentMode; |
|
} // Webkit offers a very useful `textInput` event that can be used to |
|
// directly represent `beforeInput`. The IE `textinput` event is not as |
|
// useful, so we don't use it. |
|
|
|
|
|
var canUseTextInputEvent = canUseDOM && 'TextEvent' in window && !documentMode; // In IE9+, we have access to composition events, but the data supplied |
|
// by the native compositionend event may be incorrect. Japanese ideographic |
|
// spaces, for instance (\u3000) are not recorded correctly. |
|
|
|
var useFallbackCompositionData = canUseDOM && (!canUseCompositionEvent || documentMode && documentMode > 8 && documentMode <= 11); |
|
var SPACEBAR_CODE = 32; |
|
var SPACEBAR_CHAR = String.fromCharCode(SPACEBAR_CODE); |
|
|
|
function registerEvents() { |
|
registerTwoPhaseEvent('onBeforeInput', ['compositionend', 'keypress', 'textInput', 'paste']); |
|
registerTwoPhaseEvent('onCompositionEnd', ['compositionend', 'focusout', 'keydown', 'keypress', 'keyup', 'mousedown']); |
|
registerTwoPhaseEvent('onCompositionStart', ['compositionstart', 'focusout', 'keydown', 'keypress', 'keyup', 'mousedown']); |
|
registerTwoPhaseEvent('onCompositionUpdate', ['compositionupdate', 'focusout', 'keydown', 'keypress', 'keyup', 'mousedown']); |
|
} // Track whether we've ever handled a keypress on the space key. |
|
|
|
|
|
var hasSpaceKeypress = false; |
|
/** |
|
* Return whether a native keypress event is assumed to be a command. |
|
* This is required because Firefox fires `keypress` events for key commands |
|
* (cut, copy, select-all, etc.) even though no character is inserted. |
|
*/ |
|
|
|
function isKeypressCommand(nativeEvent) { |
|
return (nativeEvent.ctrlKey || nativeEvent.altKey || nativeEvent.metaKey) && // ctrlKey && altKey is equivalent to AltGr, and is not a command. |
|
!(nativeEvent.ctrlKey && nativeEvent.altKey); |
|
} |
|
/** |
|
* Translate native top level events into event types. |
|
*/ |
|
|
|
|
|
function getCompositionEventType(domEventName) { |
|
switch (domEventName) { |
|
case 'compositionstart': |
|
return 'onCompositionStart'; |
|
|
|
case 'compositionend': |
|
return 'onCompositionEnd'; |
|
|
|
case 'compositionupdate': |
|
return 'onCompositionUpdate'; |
|
} |
|
} |
|
/** |
|
* Does our fallback best-guess model think this event signifies that |
|
* composition has begun? |
|
*/ |
|
|
|
|
|
function isFallbackCompositionStart(domEventName, nativeEvent) { |
|
return domEventName === 'keydown' && nativeEvent.keyCode === START_KEYCODE; |
|
} |
|
/** |
|
* Does our fallback mode think that this event is the end of composition? |
|
*/ |
|
|
|
|
|
function isFallbackCompositionEnd(domEventName, nativeEvent) { |
|
switch (domEventName) { |
|
case 'keyup': |
|
// Command keys insert or clear IME input. |
|
return END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1; |
|
|
|
case 'keydown': |
|
// Expect IME keyCode on each keydown. If we get any other |
|
// code we must have exited earlier. |
|
return nativeEvent.keyCode !== START_KEYCODE; |
|
|
|
case 'keypress': |
|
case 'mousedown': |
|
case 'focusout': |
|
// Events are not possible without cancelling IME. |
|
return true; |
|
|
|
default: |
|
return false; |
|
} |
|
} |
|
/** |
|
* Google Input Tools provides composition data via a CustomEvent, |
|
* with the `data` property populated in the `detail` object. If this |
|
* is available on the event object, use it. If not, this is a plain |
|
* composition event and we have nothing special to extract. |
|
* |
|
* @param {object} nativeEvent |
|
* @return {?string} |
|
*/ |
|
|
|
|
|
function getDataFromCustomEvent(nativeEvent) { |
|
var detail = nativeEvent.detail; |
|
|
|
if (typeof detail === 'object' && 'data' in detail) { |
|
return detail.data; |
|
} |
|
|
|
return null; |
|
} |
|
/** |
|
* Check if a composition event was triggered by Korean IME. |
|
* Our fallback mode does not work well with IE's Korean IME, |
|
* so just use native composition events when Korean IME is used. |
|
* Although CompositionEvent.locale property is deprecated, |
|
* it is available in IE, where our fallback mode is enabled. |
|
* |
|
* @param {object} nativeEvent |
|
* @return {boolean} |
|
*/ |
|
|
|
|
|
function isUsingKoreanIME(nativeEvent) { |
|
return nativeEvent.locale === 'ko'; |
|
} // Track the current IME composition status, if any. |
|
|
|
|
|
var isComposing = false; |
|
/** |
|
* @return {?object} A SyntheticCompositionEvent. |
|
*/ |
|
|
|
function extractCompositionEvent(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget) { |
|
var eventType; |
|
var fallbackData; |
|
|
|
if (canUseCompositionEvent) { |
|
eventType = getCompositionEventType(domEventName); |
|
} else if (!isComposing) { |
|
if (isFallbackCompositionStart(domEventName, nativeEvent)) { |
|
eventType = 'onCompositionStart'; |
|
} |
|
} else if (isFallbackCompositionEnd(domEventName, nativeEvent)) { |
|
eventType = 'onCompositionEnd'; |
|
} |
|
|
|
if (!eventType) { |
|
return null; |
|
} |
|
|
|
if (useFallbackCompositionData && !isUsingKoreanIME(nativeEvent)) { |
|
// The current composition is stored statically and must not be |
|
// overwritten while composition continues. |
|
if (!isComposing && eventType === 'onCompositionStart') { |
|
isComposing = initialize(nativeEventTarget); |
|
} else if (eventType === 'onCompositionEnd') { |
|
if (isComposing) { |
|
fallbackData = getData(); |
|
} |
|
} |
|
} |
|
|
|
var listeners = accumulateTwoPhaseListeners(targetInst, eventType); |
|
|
|
if (listeners.length > 0) { |
|
var event = new SyntheticCompositionEvent(eventType, domEventName, null, nativeEvent, nativeEventTarget); |
|
dispatchQueue.push({ |
|
event: event, |
|
listeners: listeners |
|
}); |
|
|
|
if (fallbackData) { |
|
// Inject data generated from fallback path into the synthetic event. |
|
// This matches the property of native CompositionEventInterface. |
|
event.data = fallbackData; |
|
} else { |
|
var customData = getDataFromCustomEvent(nativeEvent); |
|
|
|
if (customData !== null) { |
|
event.data = customData; |
|
} |
|
} |
|
} |
|
} |
|
|
|
function getNativeBeforeInputChars(domEventName, nativeEvent) { |
|
switch (domEventName) { |
|
case 'compositionend': |
|
return getDataFromCustomEvent(nativeEvent); |
|
|
|
case 'keypress': |
|
/** |
|
* If native `textInput` events are available, our goal is to make |
|
* use of them. However, there is a special case: the spacebar key. |
|
* In Webkit, preventing default on a spacebar `textInput` event |
|
* cancels character insertion, but it *also* causes the browser |
|
* to fall back to its default spacebar behavior of scrolling the |
|
* page. |
|
* |
|
* Tracking at: |
|
* https://code.google.com/p/chromium/issues/detail?id=355103 |
|
* |
|
* To avoid this issue, use the keypress event as if no `textInput` |
|
* event is available. |
|
*/ |
|
var which = nativeEvent.which; |
|
|
|
if (which !== SPACEBAR_CODE) { |
|
return null; |
|
} |
|
|
|
hasSpaceKeypress = true; |
|
return SPACEBAR_CHAR; |
|
|
|
case 'textInput': |
|
// Record the characters to be added to the DOM. |
|
var chars = nativeEvent.data; // If it's a spacebar character, assume that we have already handled |
|
// it at the keypress level and bail immediately. Android Chrome |
|
// doesn't give us keycodes, so we need to ignore it. |
|
|
|
if (chars === SPACEBAR_CHAR && hasSpaceKeypress) { |
|
return null; |
|
} |
|
|
|
return chars; |
|
|
|
default: |
|
// For other native event types, do nothing. |
|
return null; |
|
} |
|
} |
|
/** |
|
* For browsers that do not provide the `textInput` event, extract the |
|
* appropriate string to use for SyntheticInputEvent. |
|
*/ |
|
|
|
|
|
function getFallbackBeforeInputChars(domEventName, nativeEvent) { |
|
// If we are currently composing (IME) and using a fallback to do so, |
|
// try to extract the composed characters from the fallback object. |
|
// If composition event is available, we extract a string only at |
|
// compositionevent, otherwise extract it at fallback events. |
|
if (isComposing) { |
|
if (domEventName === 'compositionend' || !canUseCompositionEvent && isFallbackCompositionEnd(domEventName, nativeEvent)) { |
|
var chars = getData(); |
|
reset(); |
|
isComposing = false; |
|
return chars; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
switch (domEventName) { |
|
case 'paste': |
|
// If a paste event occurs after a keypress, throw out the input |
|
// chars. Paste events should not lead to BeforeInput events. |
|
return null; |
|
|
|
case 'keypress': |
|
/** |
|
* As of v27, Firefox may fire keypress events even when no character |
|
* will be inserted. A few possibilities: |
|
* |
|
* - `which` is `0`. Arrow keys, Esc key, etc. |
|
* |
|
* - `which` is the pressed key code, but no char is available. |
|
* Ex: 'AltGr + d` in Polish. There is no modified character for |
|
* this key combination and no character is inserted into the |
|
* document, but FF fires the keypress for char code `100` anyway. |
|
* No `input` event will occur. |
|
* |
|
* - `which` is the pressed key code, but a command combination is |
|
* being used. Ex: `Cmd+C`. No character is inserted, and no |
|
* `input` event will occur. |
|
*/ |
|
if (!isKeypressCommand(nativeEvent)) { |
|
// IE fires the `keypress` event when a user types an emoji via |
|
// Touch keyboard of Windows. In such a case, the `char` property |
|
// holds an emoji character like `\uD83D\uDE0A`. Because its length |
|
// is 2, the property `which` does not represent an emoji correctly. |
|
// In such a case, we directly return the `char` property instead of |
|
// using `which`. |
|
if (nativeEvent.char && nativeEvent.char.length > 1) { |
|
return nativeEvent.char; |
|
} else if (nativeEvent.which) { |
|
return String.fromCharCode(nativeEvent.which); |
|
} |
|
} |
|
|
|
return null; |
|
|
|
case 'compositionend': |
|
return useFallbackCompositionData && !isUsingKoreanIME(nativeEvent) ? null : nativeEvent.data; |
|
|
|
default: |
|
return null; |
|
} |
|
} |
|
/** |
|
* Extract a SyntheticInputEvent for `beforeInput`, based on either native |
|
* `textInput` or fallback behavior. |
|
* |
|
* @return {?object} A SyntheticInputEvent. |
|
*/ |
|
|
|
|
|
function extractBeforeInputEvent(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget) { |
|
var chars; |
|
|
|
if (canUseTextInputEvent) { |
|
chars = getNativeBeforeInputChars(domEventName, nativeEvent); |
|
} else { |
|
chars = getFallbackBeforeInputChars(domEventName, nativeEvent); |
|
} // If no characters are being inserted, no BeforeInput event should |
|
// be fired. |
|
|
|
|
|
if (!chars) { |
|
return null; |
|
} |
|
|
|
var listeners = accumulateTwoPhaseListeners(targetInst, 'onBeforeInput'); |
|
|
|
if (listeners.length > 0) { |
|
var event = new SyntheticInputEvent('onBeforeInput', 'beforeinput', null, nativeEvent, nativeEventTarget); |
|
dispatchQueue.push({ |
|
event: event, |
|
listeners: listeners |
|
}); |
|
event.data = chars; |
|
} |
|
} |
|
/** |
|
* Create an `onBeforeInput` event to match |
|
* http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105/#events-inputevents. |
|
* |
|
* This event plugin is based on the native `textInput` event |
|
* available in Chrome, Safari, Opera, and IE. This event fires after |
|
* `onKeyPress` and `onCompositionEnd`, but before `onInput`. |
|
* |
|
* `beforeInput` is spec'd but not implemented in any browsers, and |
|
* the `input` event does not provide any useful information about what has |
|
* actually been added, contrary to the spec. Thus, `textInput` is the best |
|
* available event to identify the characters that have actually been inserted |
|
* into the target node. |
|
* |
|
* This plugin is also responsible for emitting `composition` events, thus |
|
* allowing us to share composition fallback code for both `beforeInput` and |
|
* `composition` event types. |
|
*/ |
|
|
|
|
|
function extractEvents(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer) { |
|
extractCompositionEvent(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget); |
|
extractBeforeInputEvent(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget); |
|
} |
|
|
|
/** |
|
* @see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary |
|
*/ |
|
var supportedInputTypes = { |
|
color: true, |
|
date: true, |
|
datetime: true, |
|
'datetime-local': true, |
|
email: true, |
|
month: true, |
|
number: true, |
|
password: true, |
|
range: true, |
|
search: true, |
|
tel: true, |
|
text: true, |
|
time: true, |
|
url: true, |
|
week: true |
|
}; |
|
|
|
function isTextInputElement(elem) { |
|
var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase(); |
|
|
|
if (nodeName === 'input') { |
|
return !!supportedInputTypes[elem.type]; |
|
} |
|
|
|
if (nodeName === 'textarea') { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/** |
|
* Checks if an event is supported in the current execution environment. |
|
* |
|
* NOTE: This will not work correctly for non-generic events such as `change`, |
|
* `reset`, `load`, `error`, and `select`. |
|
* |
|
* Borrows from Modernizr. |
|
* |
|
* @param {string} eventNameSuffix Event name, e.g. "click". |
|
* @return {boolean} True if the event is supported. |
|
* @internal |
|
* @license Modernizr 3.0.0pre (Custom Build) | MIT |
|
*/ |
|
|
|
function isEventSupported(eventNameSuffix) { |
|
if (!canUseDOM) { |
|
return false; |
|
} |
|
|
|
var eventName = 'on' + eventNameSuffix; |
|
var isSupported = (eventName in document); |
|
|
|
if (!isSupported) { |
|
var element = document.createElement('div'); |
|
element.setAttribute(eventName, 'return;'); |
|
isSupported = typeof element[eventName] === 'function'; |
|
} |
|
|
|
return isSupported; |
|
} |
|
|
|
function registerEvents$1() { |
|
registerTwoPhaseEvent('onChange', ['change', 'click', 'focusin', 'focusout', 'input', 'keydown', 'keyup', 'selectionchange']); |
|
} |
|
|
|
function createAndAccumulateChangeEvent(dispatchQueue, inst, nativeEvent, target) { |
|
// Flag this event loop as needing state restore. |
|
enqueueStateRestore(target); |
|
var listeners = accumulateTwoPhaseListeners(inst, 'onChange'); |
|
|
|
if (listeners.length > 0) { |
|
var event = new SyntheticEvent('onChange', 'change', null, nativeEvent, target); |
|
dispatchQueue.push({ |
|
event: event, |
|
listeners: listeners |
|
}); |
|
} |
|
} |
|
/** |
|
* For IE shims |
|
*/ |
|
|
|
|
|
var activeElement = null; |
|
var activeElementInst = null; |
|
/** |
|
* SECTION: handle `change` event |
|
*/ |
|
|
|
function shouldUseChangeEvent(elem) { |
|
var nodeName = elem.nodeName && elem.nodeName.toLowerCase(); |
|
return nodeName === 'select' || nodeName === 'input' && elem.type === 'file'; |
|
} |
|
|
|
function manualDispatchChangeEvent(nativeEvent) { |
|
var dispatchQueue = []; |
|
createAndAccumulateChangeEvent(dispatchQueue, activeElementInst, nativeEvent, getEventTarget(nativeEvent)); // If change and propertychange bubbled, we'd just bind to it like all the |
|
// other events and have it go through ReactBrowserEventEmitter. Since it |
|
// doesn't, we manually listen for the events and so we have to enqueue and |
|
// process the abstract event manually. |
|
// |
|
// Batching is necessary here in order to ensure that all event handlers run |
|
// before the next rerender (including event handlers attached to ancestor |
|
// elements instead of directly on the input). Without this, controlled |
|
// components don't work properly in conjunction with event bubbling because |
|
// the component is rerendered and the value reverted before all the event |
|
// handlers can run. See https://github.com/facebook/react/issues/708. |
|
|
|
batchedUpdates(runEventInBatch, dispatchQueue); |
|
} |
|
|
|
function runEventInBatch(dispatchQueue) { |
|
processDispatchQueue(dispatchQueue, 0); |
|
} |
|
|
|
function getInstIfValueChanged(targetInst) { |
|
var targetNode = getNodeFromInstance(targetInst); |
|
|
|
if (updateValueIfChanged(targetNode)) { |
|
return targetInst; |
|
} |
|
} |
|
|
|
function getTargetInstForChangeEvent(domEventName, targetInst) { |
|
if (domEventName === 'change') { |
|
return targetInst; |
|
} |
|
} |
|
/** |
|
* SECTION: handle `input` event |
|
*/ |
|
|
|
|
|
var isInputEventSupported = false; |
|
|
|
if (canUseDOM) { |
|
// IE9 claims to support the input event but fails to trigger it when |
|
// deleting text, so we ignore its input events. |
|
isInputEventSupported = isEventSupported('input') && (!document.documentMode || document.documentMode > 9); |
|
} |
|
/** |
|
* (For IE <=9) Starts tracking propertychange events on the passed-in element |
|
* and override the value property so that we can distinguish user events from |
|
* value changes in JS. |
|
*/ |
|
|
|
|
|
function startWatchingForValueChange(target, targetInst) { |
|
activeElement = target; |
|
activeElementInst = targetInst; |
|
activeElement.attachEvent('onpropertychange', handlePropertyChange); |
|
} |
|
/** |
|
* (For IE <=9) Removes the event listeners from the currently-tracked element, |
|
* if any exists. |
|
*/ |
|
|
|
|
|
function stopWatchingForValueChange() { |
|
if (!activeElement) { |
|
return; |
|
} |
|
|
|
activeElement.detachEvent('onpropertychange', handlePropertyChange); |
|
activeElement = null; |
|
activeElementInst = null; |
|
} |
|
/** |
|
* (For IE <=9) Handles a propertychange event, sending a `change` event if |
|
* the value of the active element has changed. |
|
*/ |
|
|
|
|
|
function handlePropertyChange(nativeEvent) { |
|
if (nativeEvent.propertyName !== 'value') { |
|
return; |
|
} |
|
|
|
if (getInstIfValueChanged(activeElementInst)) { |
|
manualDispatchChangeEvent(nativeEvent); |
|
} |
|
} |
|
|
|
function handleEventsForInputEventPolyfill(domEventName, target, targetInst) { |
|
if (domEventName === 'focusin') { |
|
// In IE9, propertychange fires for most input events but is buggy and |
|
// doesn't fire when text is deleted, but conveniently, selectionchange |
|
// appears to fire in all of the remaining cases so we catch those and |
|
// forward the event if the value has changed |
|
// In either case, we don't want to call the event handler if the value |
|
// is changed from JS so we redefine a setter for `.value` that updates |
|
// our activeElementValue variable, allowing us to ignore those changes |
|
// |
|
// stopWatching() should be a noop here but we call it just in case we |
|
// missed a blur event somehow. |
|
stopWatchingForValueChange(); |
|
startWatchingForValueChange(target, targetInst); |
|
} else if (domEventName === 'focusout') { |
|
stopWatchingForValueChange(); |
|
} |
|
} // For IE8 and IE9. |
|
|
|
|
|
function getTargetInstForInputEventPolyfill(domEventName, targetInst) { |
|
if (domEventName === 'selectionchange' || domEventName === 'keyup' || domEventName === 'keydown') { |
|
// On the selectionchange event, the target is just document which isn't |
|
// helpful for us so just check activeElement instead. |
|
// |
|
// 99% of the time, keydown and keyup aren't necessary. IE8 fails to fire |
|
// propertychange on the first input event after setting `value` from a |
|
// script and fires only keydown, keypress, keyup. Catching keyup usually |
|
// gets it and catching keydown lets us fire an event for the first |
|
// keystroke if user does a key repeat (it'll be a little delayed: right |
|
// before the second keystroke). Other input methods (e.g., paste) seem to |
|
// fire selectionchange normally. |
|
return getInstIfValueChanged(activeElementInst); |
|
} |
|
} |
|
/** |
|
* SECTION: handle `click` event |
|
*/ |
|
|
|
|
|
function shouldUseClickEvent(elem) { |
|
// Use the `click` event to detect changes to checkbox and radio inputs. |
|
// This approach works across all browsers, whereas `change` does not fire |
|
// until `blur` in IE8. |
|
var nodeName = elem.nodeName; |
|
return nodeName && nodeName.toLowerCase() === 'input' && (elem.type === 'checkbox' || elem.type === 'radio'); |
|
} |
|
|
|
function getTargetInstForClickEvent(domEventName, targetInst) { |
|
if (domEventName === 'click') { |
|
return getInstIfValueChanged(targetInst); |
|
} |
|
} |
|
|
|
function getTargetInstForInputOrChangeEvent(domEventName, targetInst) { |
|
if (domEventName === 'input' || domEventName === 'change') { |
|
return getInstIfValueChanged(targetInst); |
|
} |
|
} |
|
|
|
function handleControlledInputBlur(node) { |
|
var state = node._wrapperState; |
|
|
|
if (!state || !state.controlled || node.type !== 'number') { |
|
return; |
|
} |
|
|
|
{ |
|
// If controlled, assign the value attribute to the current value on blur |
|
setDefaultValue(node, 'number', node.value); |
|
} |
|
} |
|
/** |
|
* This plugin creates an `onChange` event that normalizes change events |
|
* across form elements. This event fires at a time when it's possible to |
|
* change the element's value without seeing a flicker. |
|
* |
|
* Supported elements are: |
|
* - input (see `isTextInputElement`) |
|
* - textarea |
|
* - select |
|
*/ |
|
|
|
|
|
function extractEvents$1(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer) { |
|
var targetNode = targetInst ? getNodeFromInstance(targetInst) : window; |
|
var getTargetInstFunc, handleEventFunc; |
|
|
|
if (shouldUseChangeEvent(targetNode)) { |
|
getTargetInstFunc = getTargetInstForChangeEvent; |
|
} else if (isTextInputElement(targetNode)) { |
|
if (isInputEventSupported) { |
|
getTargetInstFunc = getTargetInstForInputOrChangeEvent; |
|
} else { |
|
getTargetInstFunc = getTargetInstForInputEventPolyfill; |
|
handleEventFunc = handleEventsForInputEventPolyfill; |
|
} |
|
} else if (shouldUseClickEvent(targetNode)) { |
|
getTargetInstFunc = getTargetInstForClickEvent; |
|
} |
|
|
|
if (getTargetInstFunc) { |
|
var inst = getTargetInstFunc(domEventName, targetInst); |
|
|
|
if (inst) { |
|
createAndAccumulateChangeEvent(dispatchQueue, inst, nativeEvent, nativeEventTarget); |
|
return; |
|
} |
|
} |
|
|
|
if (handleEventFunc) { |
|
handleEventFunc(domEventName, targetNode, targetInst); |
|
} // When blurring, set the value attribute for number inputs |
|
|
|
|
|
if (domEventName === 'focusout') { |
|
handleControlledInputBlur(targetNode); |
|
} |
|
} |
|
|
|
function registerEvents$2() { |
|
registerDirectEvent('onMouseEnter', ['mouseout', 'mouseover']); |
|
registerDirectEvent('onMouseLeave', ['mouseout', 'mouseover']); |
|
registerDirectEvent('onPointerEnter', ['pointerout', 'pointerover']); |
|
registerDirectEvent('onPointerLeave', ['pointerout', 'pointerover']); |
|
} |
|
/** |
|
* For almost every interaction we care about, there will be both a top-level |
|
* `mouseover` and `mouseout` event that occurs. Only use `mouseout` so that |
|
* we do not extract duplicate events. However, moving the mouse into the |
|
* browser from outside will not fire a `mouseout` event. In this case, we use |
|
* the `mouseover` top-level event. |
|
*/ |
|
|
|
|
|
function extractEvents$2(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer) { |
|
var isOverEvent = domEventName === 'mouseover' || domEventName === 'pointerover'; |
|
var isOutEvent = domEventName === 'mouseout' || domEventName === 'pointerout'; |
|
|
|
if (isOverEvent && !isReplayingEvent(nativeEvent)) { |
|
// If this is an over event with a target, we might have already dispatched |
|
// the event in the out event of the other target. If this is replayed, |
|
// then it's because we couldn't dispatch against this target previously |
|
// so we have to do it now instead. |
|
var related = nativeEvent.relatedTarget || nativeEvent.fromElement; |
|
|
|
if (related) { |
|
// If the related node is managed by React, we can assume that we have |
|
// already dispatched the corresponding events during its mouseout. |
|
if (getClosestInstanceFromNode(related) || isContainerMarkedAsRoot(related)) { |
|
return; |
|
} |
|
} |
|
} |
|
|
|
if (!isOutEvent && !isOverEvent) { |
|
// Must not be a mouse or pointer in or out - ignoring. |
|
return; |
|
} |
|
|
|
var win; // TODO: why is this nullable in the types but we read from it? |
|
|
|
if (nativeEventTarget.window === nativeEventTarget) { |
|
// `nativeEventTarget` is probably a window object. |
|
win = nativeEventTarget; |
|
} else { |
|
// TODO: Figure out why `ownerDocument` is sometimes undefined in IE8. |
|
var doc = nativeEventTarget.ownerDocument; |
|
|
|
if (doc) { |
|
win = doc.defaultView || doc.parentWindow; |
|
} else { |
|
win = window; |
|
} |
|
} |
|
|
|
var from; |
|
var to; |
|
|
|
if (isOutEvent) { |
|
var _related = nativeEvent.relatedTarget || nativeEvent.toElement; |
|
|
|
from = targetInst; |
|
to = _related ? getClosestInstanceFromNode(_related) : null; |
|
|
|
if (to !== null) { |
|
var nearestMounted = getNearestMountedFiber(to); |
|
|
|
if (to !== nearestMounted || to.tag !== HostComponent && to.tag !== HostText) { |
|
to = null; |
|
} |
|
} |
|
} else { |
|
// Moving to a node from outside the window. |
|
from = null; |
|
to = targetInst; |
|
} |
|
|
|
if (from === to) { |
|
// Nothing pertains to our managed components. |
|
return; |
|
} |
|
|
|
var SyntheticEventCtor = SyntheticMouseEvent; |
|
var leaveEventType = 'onMouseLeave'; |
|
var enterEventType = 'onMouseEnter'; |
|
var eventTypePrefix = 'mouse'; |
|
|
|
if (domEventName === 'pointerout' || domEventName === 'pointerover') { |
|
SyntheticEventCtor = SyntheticPointerEvent; |
|
leaveEventType = 'onPointerLeave'; |
|
enterEventType = 'onPointerEnter'; |
|
eventTypePrefix = 'pointer'; |
|
} |
|
|
|
var fromNode = from == null ? win : getNodeFromInstance(from); |
|
var toNode = to == null ? win : getNodeFromInstance(to); |
|
var leave = new SyntheticEventCtor(leaveEventType, eventTypePrefix + 'leave', from, nativeEvent, nativeEventTarget); |
|
leave.target = fromNode; |
|
leave.relatedTarget = toNode; |
|
var enter = null; // We should only process this nativeEvent if we are processing |
|
// the first ancestor. Next time, we will ignore the event. |
|
|
|
var nativeTargetInst = getClosestInstanceFromNode(nativeEventTarget); |
|
|
|
if (nativeTargetInst === targetInst) { |
|
var enterEvent = new SyntheticEventCtor(enterEventType, eventTypePrefix + 'enter', to, nativeEvent, nativeEventTarget); |
|
enterEvent.target = toNode; |
|
enterEvent.relatedTarget = fromNode; |
|
enter = enterEvent; |
|
} |
|
|
|
accumulateEnterLeaveTwoPhaseListeners(dispatchQueue, leave, enter, from, to); |
|
} |
|
|
|
/** |
|
* inlined Object.is polyfill to avoid requiring consumers ship their own |
|
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is |
|
*/ |
|
function is(x, y) { |
|
return x === y && (x !== 0 || 1 / x === 1 / y) || x !== x && y !== y // eslint-disable-line no-self-compare |
|
; |
|
} |
|
|
|
var objectIs = typeof Object.is === 'function' ? Object.is : is; |
|
|
|
/** |
|
* Performs equality by iterating through keys on an object and returning false |
|
* when any key has values which are not strictly equal between the arguments. |
|
* Returns true when the values of all keys are strictly equal. |
|
*/ |
|
|
|
function shallowEqual(objA, objB) { |
|
if (objectIs(objA, objB)) { |
|
return true; |
|
} |
|
|
|
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { |
|
return false; |
|
} |
|
|
|
var keysA = Object.keys(objA); |
|
var keysB = Object.keys(objB); |
|
|
|
if (keysA.length !== keysB.length) { |
|
return false; |
|
} // Test for A's keys different from B. |
|
|
|
|
|
for (var i = 0; i < keysA.length; i++) { |
|
var currentKey = keysA[i]; |
|
|
|
if (!hasOwnProperty.call(objB, currentKey) || !objectIs(objA[currentKey], objB[currentKey])) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
/** |
|
* Given any node return the first leaf node without children. |
|
* |
|
* @param {DOMElement|DOMTextNode} node |
|
* @return {DOMElement|DOMTextNode} |
|
*/ |
|
|
|
function getLeafNode(node) { |
|
while (node && node.firstChild) { |
|
node = node.firstChild; |
|
} |
|
|
|
return node; |
|
} |
|
/** |
|
* Get the next sibling within a container. This will walk up the |
|
* DOM if a node's siblings have been exhausted. |
|
* |
|
* @param {DOMElement|DOMTextNode} node |
|
* @return {?DOMElement|DOMTextNode} |
|
*/ |
|
|
|
|
|
function getSiblingNode(node) { |
|
while (node) { |
|
if (node.nextSibling) { |
|
return node.nextSibling; |
|
} |
|
|
|
node = node.parentNode; |
|
} |
|
} |
|
/** |
|
* Get object describing the nodes which contain characters at offset. |
|
* |
|
* @param {DOMElement|DOMTextNode} root |
|
* @param {number} offset |
|
* @return {?object} |
|
*/ |
|
|
|
|
|
function getNodeForCharacterOffset(root, offset) { |
|
var node = getLeafNode(root); |
|
var nodeStart = 0; |
|
var nodeEnd = 0; |
|
|
|
while (node) { |
|
if (node.nodeType === TEXT_NODE) { |
|
nodeEnd = nodeStart + node.textContent.length; |
|
|
|
if (nodeStart <= offset && nodeEnd >= offset) { |
|
return { |
|
node: node, |
|
offset: offset - nodeStart |
|
}; |
|
} |
|
|
|
nodeStart = nodeEnd; |
|
} |
|
|
|
node = getLeafNode(getSiblingNode(node)); |
|
} |
|
} |
|
|
|
/** |
|
* @param {DOMElement} outerNode |
|
* @return {?object} |
|
*/ |
|
|
|
function getOffsets(outerNode) { |
|
var ownerDocument = outerNode.ownerDocument; |
|
var win = ownerDocument && ownerDocument.defaultView || window; |
|
var selection = win.getSelection && win.getSelection(); |
|
|
|
if (!selection || selection.rangeCount === 0) { |
|
return null; |
|
} |
|
|
|
var anchorNode = selection.anchorNode, |
|
anchorOffset = selection.anchorOffset, |
|
focusNode = selection.focusNode, |
|
focusOffset = selection.focusOffset; // In Firefox, anchorNode and focusNode can be "anonymous divs", e.g. the |
|
// up/down buttons on an <input type="number">. Anonymous divs do not seem to |
|
// expose properties, triggering a "Permission denied error" if any of its |
|
// properties are accessed. The only seemingly possible way to avoid erroring |
|
// is to access a property that typically works for non-anonymous divs and |
|
// catch any error that may otherwise arise. See |
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=208427 |
|
|
|
try { |
|
/* eslint-disable no-unused-expressions */ |
|
anchorNode.nodeType; |
|
focusNode.nodeType; |
|
/* eslint-enable no-unused-expressions */ |
|
} catch (e) { |
|
return null; |
|
} |
|
|
|
return getModernOffsetsFromPoints(outerNode, anchorNode, anchorOffset, focusNode, focusOffset); |
|
} |
|
/** |
|
* Returns {start, end} where `start` is the character/codepoint index of |
|
* (anchorNode, anchorOffset) within the textContent of `outerNode`, and |
|
* `end` is the index of (focusNode, focusOffset). |
|
* |
|
* Returns null if you pass in garbage input but we should probably just crash. |
|
* |
|
* Exported only for testing. |
|
*/ |
|
|
|
function getModernOffsetsFromPoints(outerNode, anchorNode, anchorOffset, focusNode, focusOffset) { |
|
var length = 0; |
|
var start = -1; |
|
var end = -1; |
|
var indexWithinAnchor = 0; |
|
var indexWithinFocus = 0; |
|
var node = outerNode; |
|
var parentNode = null; |
|
|
|
outer: while (true) { |
|
var next = null; |
|
|
|
while (true) { |
|
if (node === anchorNode && (anchorOffset === 0 || node.nodeType === TEXT_NODE)) { |
|
start = length + anchorOffset; |
|
} |
|
|
|
if (node === focusNode && (focusOffset === 0 || node.nodeType === TEXT_NODE)) { |
|
end = length + focusOffset; |
|
} |
|
|
|
if (node.nodeType === TEXT_NODE) { |
|
length += node.nodeValue.length; |
|
} |
|
|
|
if ((next = node.firstChild) === null) { |
|
break; |
|
} // Moving from `node` to its first child `next`. |
|
|
|
|
|
parentNode = node; |
|
node = next; |
|
} |
|
|
|
while (true) { |
|
if (node === outerNode) { |
|
// If `outerNode` has children, this is always the second time visiting |
|
// it. If it has no children, this is still the first loop, and the only |
|
// valid selection is anchorNode and focusNode both equal to this node |
|
// and both offsets 0, in which case we will have handled above. |
|
break outer; |
|
} |
|
|
|
if (parentNode === anchorNode && ++indexWithinAnchor === anchorOffset) { |
|
start = length; |
|
} |
|
|
|
if (parentNode === focusNode && ++indexWithinFocus === focusOffset) { |
|
end = length; |
|
} |
|
|
|
if ((next = node.nextSibling) !== null) { |
|
break; |
|
} |
|
|
|
node = parentNode; |
|
parentNode = node.parentNode; |
|
} // Moving from `node` to its next sibling `next`. |
|
|
|
|
|
node = next; |
|
} |
|
|
|
if (start === -1 || end === -1) { |
|
// This should never happen. (Would happen if the anchor/focus nodes aren't |
|
// actually inside the passed-in node.) |
|
return null; |
|
} |
|
|
|
return { |
|
start: start, |
|
end: end |
|
}; |
|
} |
|
/** |
|
* In modern non-IE browsers, we can support both forward and backward |
|
* selections. |
|
* |
|
* Note: IE10+ supports the Selection object, but it does not support |
|
* the `extend` method, which means that even in modern IE, it's not possible |
|
* to programmatically create a backward selection. Thus, for all IE |
|
* versions, we use the old IE API to create our selections. |
|
* |
|
* @param {DOMElement|DOMTextNode} node |
|
* @param {object} offsets |
|
*/ |
|
|
|
function setOffsets(node, offsets) { |
|
var doc = node.ownerDocument || document; |
|
var win = doc && doc.defaultView || window; // Edge fails with "Object expected" in some scenarios. |
|
// (For instance: TinyMCE editor used in a list component that supports pasting to add more, |
|
// fails when pasting 100+ items) |
|
|
|
if (!win.getSelection) { |
|
return; |
|
} |
|
|
|
var selection = win.getSelection(); |
|
var length = node.textContent.length; |
|
var start = Math.min(offsets.start, length); |
|
var end = offsets.end === undefined ? start : Math.min(offsets.end, length); // IE 11 uses modern selection, but doesn't support the extend method. |
|
// Flip backward selections, so we can set with a single range. |
|
|
|
if (!selection.extend && start > end) { |
|
var temp = end; |
|
end = start; |
|
start = temp; |
|
} |
|
|
|
var startMarker = getNodeForCharacterOffset(node, start); |
|
var endMarker = getNodeForCharacterOffset(node, end); |
|
|
|
if (startMarker && endMarker) { |
|
if (selection.rangeCount === 1 && selection.anchorNode === startMarker.node && selection.anchorOffset === startMarker.offset && selection.focusNode === endMarker.node && selection.focusOffset === endMarker.offset) { |
|
return; |
|
} |
|
|
|
var range = doc.createRange(); |
|
range.setStart(startMarker.node, startMarker.offset); |
|
selection.removeAllRanges(); |
|
|
|
if (start > end) { |
|
selection.addRange(range); |
|
selection.extend(endMarker.node, endMarker.offset); |
|
} else { |
|
range.setEnd(endMarker.node, endMarker.offset); |
|
selection.addRange(range); |
|
} |
|
} |
|
} |
|
|
|
function isTextNode(node) { |
|
return node && node.nodeType === TEXT_NODE; |
|
} |
|
|
|
function containsNode(outerNode, innerNode) { |
|
if (!outerNode || !innerNode) { |
|
return false; |
|
} else if (outerNode === innerNode) { |
|
return true; |
|
} else if (isTextNode(outerNode)) { |
|
return false; |
|
} else if (isTextNode(innerNode)) { |
|
return containsNode(outerNode, innerNode.parentNode); |
|
} else if ('contains' in outerNode) { |
|
return outerNode.contains(innerNode); |
|
} else if (outerNode.compareDocumentPosition) { |
|
return !!(outerNode.compareDocumentPosition(innerNode) & 16); |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
function isInDocument(node) { |
|
return node && node.ownerDocument && containsNode(node.ownerDocument.documentElement, node); |
|
} |
|
|
|
function isSameOriginFrame(iframe) { |
|
try { |
|
// Accessing the contentDocument of a HTMLIframeElement can cause the browser |
|
// to throw, e.g. if it has a cross-origin src attribute. |
|
// Safari will show an error in the console when the access results in "Blocked a frame with origin". e.g: |
|
// iframe.contentDocument.defaultView; |
|
// A safety way is to access one of the cross origin properties: Window or Location |
|
// Which might result in "SecurityError" DOM Exception and it is compatible to Safari. |
|
// https://html.spec.whatwg.org/multipage/browsers.html#integration-with-idl |
|
return typeof iframe.contentWindow.location.href === 'string'; |
|
} catch (err) { |
|
return false; |
|
} |
|
} |
|
|
|
function getActiveElementDeep() { |
|
var win = window; |
|
var element = getActiveElement(); |
|
|
|
while (element instanceof win.HTMLIFrameElement) { |
|
if (isSameOriginFrame(element)) { |
|
win = element.contentWindow; |
|
} else { |
|
return element; |
|
} |
|
|
|
element = getActiveElement(win.document); |
|
} |
|
|
|
return element; |
|
} |
|
/** |
|
* @ReactInputSelection: React input selection module. Based on Selection.js, |
|
* but modified to be suitable for react and has a couple of bug fixes (doesn't |
|
* assume buttons have range selections allowed). |
|
* Input selection module for React. |
|
*/ |
|
|
|
/** |
|
* @hasSelectionCapabilities: we get the element types that support selection |
|
* from https://html.spec.whatwg.org/#do-not-apply, looking at `selectionStart` |
|
* and `selectionEnd` rows. |
|
*/ |
|
|
|
|
|
function hasSelectionCapabilities(elem) { |
|
var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase(); |
|
return nodeName && (nodeName === 'input' && (elem.type === 'text' || elem.type === 'search' || elem.type === 'tel' || elem.type === 'url' || elem.type === 'password') || nodeName === 'textarea' || elem.contentEditable === 'true'); |
|
} |
|
function getSelectionInformation() { |
|
var focusedElem = getActiveElementDeep(); |
|
return { |
|
focusedElem: focusedElem, |
|
selectionRange: hasSelectionCapabilities(focusedElem) ? getSelection(focusedElem) : null |
|
}; |
|
} |
|
/** |
|
* @restoreSelection: If any selection information was potentially lost, |
|
* restore it. This is useful when performing operations that could remove dom |
|
* nodes and place them back in, resulting in focus being lost. |
|
*/ |
|
|
|
function restoreSelection(priorSelectionInformation) { |
|
var curFocusedElem = getActiveElementDeep(); |
|
var priorFocusedElem = priorSelectionInformation.focusedElem; |
|
var priorSelectionRange = priorSelectionInformation.selectionRange; |
|
|
|
if (curFocusedElem !== priorFocusedElem && isInDocument(priorFocusedElem)) { |
|
if (priorSelectionRange !== null && hasSelectionCapabilities(priorFocusedElem)) { |
|
setSelection(priorFocusedElem, priorSelectionRange); |
|
} // Focusing a node can change the scroll position, which is undesirable |
|
|
|
|
|
var ancestors = []; |
|
var ancestor = priorFocusedElem; |
|
|
|
while (ancestor = ancestor.parentNode) { |
|
if (ancestor.nodeType === ELEMENT_NODE) { |
|
ancestors.push({ |
|
element: ancestor, |
|
left: ancestor.scrollLeft, |
|
top: ancestor.scrollTop |
|
}); |
|
} |
|
} |
|
|
|
if (typeof priorFocusedElem.focus === 'function') { |
|
priorFocusedElem.focus(); |
|
} |
|
|
|
for (var i = 0; i < ancestors.length; i++) { |
|
var info = ancestors[i]; |
|
info.element.scrollLeft = info.left; |
|
info.element.scrollTop = info.top; |
|
} |
|
} |
|
} |
|
/** |
|
* @getSelection: Gets the selection bounds of a focused textarea, input or |
|
* contentEditable node. |
|
* -@input: Look up selection bounds of this input |
|
* -@return {start: selectionStart, end: selectionEnd} |
|
*/ |
|
|
|
function getSelection(input) { |
|
var selection; |
|
|
|
if ('selectionStart' in input) { |
|
// Modern browser with input or textarea. |
|
selection = { |
|
start: input.selectionStart, |
|
end: input.selectionEnd |
|
}; |
|
} else { |
|
// Content editable or old IE textarea. |
|
selection = getOffsets(input); |
|
} |
|
|
|
return selection || { |
|
start: 0, |
|
end: 0 |
|
}; |
|
} |
|
/** |
|
* @setSelection: Sets the selection bounds of a textarea or input and focuses |
|
* the input. |
|
* -@input Set selection bounds of this input or textarea |
|
* -@offsets Object of same form that is returned from get* |
|
*/ |
|
|
|
function setSelection(input, offsets) { |
|
var start = offsets.start; |
|
var end = offsets.end; |
|
|
|
if (end === undefined) { |
|
end = start; |
|
} |
|
|
|
if ('selectionStart' in input) { |
|
input.selectionStart = start; |
|
input.selectionEnd = Math.min(end, input.value.length); |
|
} else { |
|
setOffsets(input, offsets); |
|
} |
|
} |
|
|
|
var skipSelectionChangeEvent = canUseDOM && 'documentMode' in document && document.documentMode <= 11; |
|
|
|
function registerEvents$3() { |
|
registerTwoPhaseEvent('onSelect', ['focusout', 'contextmenu', 'dragend', 'focusin', 'keydown', 'keyup', 'mousedown', 'mouseup', 'selectionchange']); |
|
} |
|
|
|
var activeElement$1 = null; |
|
var activeElementInst$1 = null; |
|
var lastSelection = null; |
|
var mouseDown = false; |
|
/** |
|
* Get an object which is a unique representation of the current selection. |
|
* |
|
* The return value will not be consistent across nodes or browsers, but |
|
* two identical selections on the same node will return identical objects. |
|
*/ |
|
|
|
function getSelection$1(node) { |
|
if ('selectionStart' in node && hasSelectionCapabilities(node)) { |
|
return { |
|
start: node.selectionStart, |
|
end: node.selectionEnd |
|
}; |
|
} else { |
|
var win = node.ownerDocument && node.ownerDocument.defaultView || window; |
|
var selection = win.getSelection(); |
|
return { |
|
anchorNode: selection.anchorNode, |
|
anchorOffset: selection.anchorOffset, |
|
focusNode: selection.focusNode, |
|
focusOffset: selection.focusOffset |
|
}; |
|
} |
|
} |
|
/** |
|
* Get document associated with the event target. |
|
*/ |
|
|
|
|
|
function getEventTargetDocument(eventTarget) { |
|
return eventTarget.window === eventTarget ? eventTarget.document : eventTarget.nodeType === DOCUMENT_NODE ? eventTarget : eventTarget.ownerDocument; |
|
} |
|
/** |
|
* Poll selection to see whether it's changed. |
|
* |
|
* @param {object} nativeEvent |
|
* @param {object} nativeEventTarget |
|
* @return {?SyntheticEvent} |
|
*/ |
|
|
|
|
|
function constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget) { |
|
// Ensure we have the right element, and that the user is not dragging a |
|
// selection (this matches native `select` event behavior). In HTML5, select |
|
// fires only on input and textarea thus if there's no focused element we |
|
// won't dispatch. |
|
var doc = getEventTargetDocument(nativeEventTarget); |
|
|
|
if (mouseDown || activeElement$1 == null || activeElement$1 !== getActiveElement(doc)) { |
|
return; |
|
} // Only fire when selection has actually changed. |
|
|
|
|
|
var currentSelection = getSelection$1(activeElement$1); |
|
|
|
if (!lastSelection || !shallowEqual(lastSelection, currentSelection)) { |
|
lastSelection = currentSelection; |
|
var listeners = accumulateTwoPhaseListeners(activeElementInst$1, 'onSelect'); |
|
|
|
if (listeners.length > 0) { |
|
var event = new SyntheticEvent('onSelect', 'select', null, nativeEvent, nativeEventTarget); |
|
dispatchQueue.push({ |
|
event: event, |
|
listeners: listeners |
|
}); |
|
event.target = activeElement$1; |
|
} |
|
} |
|
} |
|
/** |
|
* This plugin creates an `onSelect` event that normalizes select events |
|
* across form elements. |
|
* |
|
* Supported elements are: |
|
* - input (see `isTextInputElement`) |
|
* - textarea |
|
* - contentEditable |
|
* |
|
* This differs from native browser implementations in the following ways: |
|
* - Fires on contentEditable fields as well as inputs. |
|
* - Fires for collapsed selection. |
|
* - Fires after user input. |
|
*/ |
|
|
|
|
|
function extractEvents$3(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer) { |
|
var targetNode = targetInst ? getNodeFromInstance(targetInst) : window; |
|
|
|
switch (domEventName) { |
|
// Track the input node that has focus. |
|
case 'focusin': |
|
if (isTextInputElement(targetNode) || targetNode.contentEditable === 'true') { |
|
activeElement$1 = targetNode; |
|
activeElementInst$1 = targetInst; |
|
lastSelection = null; |
|
} |
|
|
|
break; |
|
|
|
case 'focusout': |
|
activeElement$1 = null; |
|
activeElementInst$1 = null; |
|
lastSelection = null; |
|
break; |
|
// Don't fire the event while the user is dragging. This matches the |
|
// semantics of the native select event. |
|
|
|
case 'mousedown': |
|
mouseDown = true; |
|
break; |
|
|
|
case 'contextmenu': |
|
case 'mouseup': |
|
case 'dragend': |
|
mouseDown = false; |
|
constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget); |
|
break; |
|
// Chrome and IE fire non-standard event when selection is changed (and |
|
// sometimes when it hasn't). IE's event fires out of order with respect |
|
// to key and input events on deletion, so we discard it. |
|
// |
|
// Firefox doesn't support selectionchange, so check selection status |
|
// after each key entry. The selection changes after keydown and before |
|
// keyup, but we check on keydown as well in the case of holding down a |
|
// key, when multiple keydown events are fired but only one keyup is. |
|
// This is also our approach for IE handling, for the reason above. |
|
|
|
case 'selectionchange': |
|
if (skipSelectionChangeEvent) { |
|
break; |
|
} |
|
|
|
// falls through |
|
|
|
case 'keydown': |
|
case 'keyup': |
|
constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget); |
|
} |
|
} |
|
|
|
/** |
|
* Generate a mapping of standard vendor prefixes using the defined style property and event name. |
|
* |
|
* @param {string} styleProp |
|
* @param {string} eventName |
|
* @returns {object} |
|
*/ |
|
|
|
function makePrefixMap(styleProp, eventName) { |
|
var prefixes = {}; |
|
prefixes[styleProp.toLowerCase()] = eventName.toLowerCase(); |
|
prefixes['Webkit' + styleProp] = 'webkit' + eventName; |
|
prefixes['Moz' + styleProp] = 'moz' + eventName; |
|
return prefixes; |
|
} |
|
/** |
|
* A list of event names to a configurable list of vendor prefixes. |
|
*/ |
|
|
|
|
|
var vendorPrefixes = { |
|
animationend: makePrefixMap('Animation', 'AnimationEnd'), |
|
animationiteration: makePrefixMap('Animation', 'AnimationIteration'), |
|
animationstart: makePrefixMap('Animation', 'AnimationStart'), |
|
transitionend: makePrefixMap('Transition', 'TransitionEnd') |
|
}; |
|
/** |
|
* Event names that have already been detected and prefixed (if applicable). |
|
*/ |
|
|
|
var prefixedEventNames = {}; |
|
/** |
|
* Element to check for prefixes on. |
|
*/ |
|
|
|
var style = {}; |
|
/** |
|
* Bootstrap if a DOM exists. |
|
*/ |
|
|
|
if (canUseDOM) { |
|
style = document.createElement('div').style; // On some platforms, in particular some releases of Android 4.x, |
|
// the un-prefixed "animation" and "transition" properties are defined on the |
|
// style object but the events that fire will still be prefixed, so we need |
|
// to check if the un-prefixed events are usable, and if not remove them from the map. |
|
|
|
if (!('AnimationEvent' in window)) { |
|
delete vendorPrefixes.animationend.animation; |
|
delete vendorPrefixes.animationiteration.animation; |
|
delete vendorPrefixes.animationstart.animation; |
|
} // Same as above |
|
|
|
|
|
if (!('TransitionEvent' in window)) { |
|
delete vendorPrefixes.transitionend.transition; |
|
} |
|
} |
|
/** |
|
* Attempts to determine the correct vendor prefixed event name. |
|
* |
|
* @param {string} eventName |
|
* @returns {string} |
|
*/ |
|
|
|
|
|
function getVendorPrefixedEventName(eventName) { |
|
if (prefixedEventNames[eventName]) { |
|
return prefixedEventNames[eventName]; |
|
} else if (!vendorPrefixes[eventName]) { |
|
return eventName; |
|
} |
|
|
|
var prefixMap = vendorPrefixes[eventName]; |
|
|
|
for (var styleProp in prefixMap) { |
|
if (prefixMap.hasOwnProperty(styleProp) && styleProp in style) { |
|
return prefixedEventNames[eventName] = prefixMap[styleProp]; |
|
} |
|
} |
|
|
|
return eventName; |
|
} |
|
|
|
var ANIMATION_END = getVendorPrefixedEventName('animationend'); |
|
var ANIMATION_ITERATION = getVendorPrefixedEventName('animationiteration'); |
|
var ANIMATION_START = getVendorPrefixedEventName('animationstart'); |
|
var TRANSITION_END = getVendorPrefixedEventName('transitionend'); |
|
|
|
var topLevelEventsToReactNames = new Map(); // NOTE: Capitalization is important in this list! |
|
// |
|
// E.g. it needs "pointerDown", not "pointerdown". |
|
// This is because we derive both React name ("onPointerDown") |
|
// and DOM name ("pointerdown") from the same list. |
|
// |
|
// Exceptions that don't match this convention are listed separately. |
|
// |
|
// prettier-ignore |
|
|
|
var simpleEventPluginEvents = ['abort', 'auxClick', 'cancel', 'canPlay', 'canPlayThrough', 'click', 'close', 'contextMenu', 'copy', 'cut', 'drag', 'dragEnd', 'dragEnter', 'dragExit', 'dragLeave', 'dragOver', 'dragStart', 'drop', 'durationChange', 'emptied', 'encrypted', 'ended', 'error', 'gotPointerCapture', 'input', 'invalid', 'keyDown', 'keyPress', 'keyUp', 'load', 'loadedData', 'loadedMetadata', 'loadStart', 'lostPointerCapture', 'mouseDown', 'mouseMove', 'mouseOut', 'mouseOver', 'mouseUp', 'paste', 'pause', 'play', 'playing', 'pointerCancel', 'pointerDown', 'pointerMove', 'pointerOut', 'pointerOver', 'pointerUp', 'progress', 'rateChange', 'reset', 'resize', 'seeked', 'seeking', 'stalled', 'submit', 'suspend', 'timeUpdate', 'touchCancel', 'touchEnd', 'touchStart', 'volumeChange', 'scroll', 'toggle', 'touchMove', 'waiting', 'wheel']; |
|
|
|
function registerSimpleEvent(domEventName, reactName) { |
|
topLevelEventsToReactNames.set(domEventName, reactName); |
|
registerTwoPhaseEvent(reactName, [domEventName]); |
|
} |
|
|
|
function registerSimpleEvents() { |
|
for (var i = 0; i < simpleEventPluginEvents.length; i++) { |
|
var eventName = simpleEventPluginEvents[i]; |
|
var domEventName = eventName.toLowerCase(); |
|
var capitalizedEvent = eventName[0].toUpperCase() + eventName.slice(1); |
|
registerSimpleEvent(domEventName, 'on' + capitalizedEvent); |
|
} // Special cases where event names don't match. |
|
|
|
|
|
registerSimpleEvent(ANIMATION_END, 'onAnimationEnd'); |
|
registerSimpleEvent(ANIMATION_ITERATION, 'onAnimationIteration'); |
|
registerSimpleEvent(ANIMATION_START, 'onAnimationStart'); |
|
registerSimpleEvent('dblclick', 'onDoubleClick'); |
|
registerSimpleEvent('focusin', 'onFocus'); |
|
registerSimpleEvent('focusout', 'onBlur'); |
|
registerSimpleEvent(TRANSITION_END, 'onTransitionEnd'); |
|
} |
|
|
|
function extractEvents$4(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer) { |
|
var reactName = topLevelEventsToReactNames.get(domEventName); |
|
|
|
if (reactName === undefined) { |
|
return; |
|
} |
|
|
|
var SyntheticEventCtor = SyntheticEvent; |
|
var reactEventType = domEventName; |
|
|
|
switch (domEventName) { |
|
case 'keypress': |
|
// Firefox creates a keypress event for function keys too. This removes |
|
// the unwanted keypress events. Enter is however both printable and |
|
// non-printable. One would expect Tab to be as well (but it isn't). |
|
if (getEventCharCode(nativeEvent) === 0) { |
|
return; |
|
} |
|
|
|
/* falls through */ |
|
|
|
case 'keydown': |
|
case 'keyup': |
|
SyntheticEventCtor = SyntheticKeyboardEvent; |
|
break; |
|
|
|
case 'focusin': |
|
reactEventType = 'focus'; |
|
SyntheticEventCtor = SyntheticFocusEvent; |
|
break; |
|
|
|
case 'focusout': |
|
reactEventType = 'blur'; |
|
SyntheticEventCtor = SyntheticFocusEvent; |
|
break; |
|
|
|
case 'beforeblur': |
|
case 'afterblur': |
|
SyntheticEventCtor = SyntheticFocusEvent; |
|
break; |
|
|
|
case 'click': |
|
// Firefox creates a click event on right mouse clicks. This removes the |
|
// unwanted click events. |
|
if (nativeEvent.button === 2) { |
|
return; |
|
} |
|
|
|
/* falls through */ |
|
|
|
case 'auxclick': |
|
case 'dblclick': |
|
case 'mousedown': |
|
case 'mousemove': |
|
case 'mouseup': // TODO: Disabled elements should not respond to mouse events |
|
|
|
/* falls through */ |
|
|
|
case 'mouseout': |
|
case 'mouseover': |
|
case 'contextmenu': |
|
SyntheticEventCtor = SyntheticMouseEvent; |
|
break; |
|
|
|
case 'drag': |
|
case 'dragend': |
|
case 'dragenter': |
|
case 'dragexit': |
|
case 'dragleave': |
|
case 'dragover': |
|
case 'dragstart': |
|
case 'drop': |
|
SyntheticEventCtor = SyntheticDragEvent; |
|
break; |
|
|
|
case 'touchcancel': |
|
case 'touchend': |
|
case 'touchmove': |
|
case 'touchstart': |
|
SyntheticEventCtor = SyntheticTouchEvent; |
|
break; |
|
|
|
case ANIMATION_END: |
|
case ANIMATION_ITERATION: |
|
case ANIMATION_START: |
|
SyntheticEventCtor = SyntheticAnimationEvent; |
|
break; |
|
|
|
case TRANSITION_END: |
|
SyntheticEventCtor = SyntheticTransitionEvent; |
|
break; |
|
|
|
case 'scroll': |
|
SyntheticEventCtor = SyntheticUIEvent; |
|
break; |
|
|
|
case 'wheel': |
|
SyntheticEventCtor = SyntheticWheelEvent; |
|
break; |
|
|
|
case 'copy': |
|
case 'cut': |
|
case 'paste': |
|
SyntheticEventCtor = SyntheticClipboardEvent; |
|
break; |
|
|
|
case 'gotpointercapture': |
|
case 'lostpointercapture': |
|
case 'pointercancel': |
|
case 'pointerdown': |
|
case 'pointermove': |
|
case 'pointerout': |
|
case 'pointerover': |
|
case 'pointerup': |
|
SyntheticEventCtor = SyntheticPointerEvent; |
|
break; |
|
} |
|
|
|
var inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0; |
|
|
|
{ |
|
// Some events don't bubble in the browser. |
|
// In the past, React has always bubbled them, but this can be surprising. |
|
// We're going to try aligning closer to the browser behavior by not bubbling |
|
// them in React either. We'll start by not bubbling onScroll, and then expand. |
|
var accumulateTargetOnly = !inCapturePhase && // TODO: ideally, we'd eventually add all events from |
|
// nonDelegatedEvents list in DOMPluginEventSystem. |
|
// Then we can remove this special list. |
|
// This is a breaking change that can wait until React 18. |
|
domEventName === 'scroll'; |
|
|
|
var _listeners = accumulateSinglePhaseListeners(targetInst, reactName, nativeEvent.type, inCapturePhase, accumulateTargetOnly); |
|
|
|
if (_listeners.length > 0) { |
|
// Intentionally create event lazily. |
|
var _event = new SyntheticEventCtor(reactName, reactEventType, null, nativeEvent, nativeEventTarget); |
|
|
|
dispatchQueue.push({ |
|
event: _event, |
|
listeners: _listeners |
|
}); |
|
} |
|
} |
|
} |
|
|
|
// TODO: remove top-level side effect. |
|
registerSimpleEvents(); |
|
registerEvents$2(); |
|
registerEvents$1(); |
|
registerEvents$3(); |
|
registerEvents(); |
|
|
|
function extractEvents$5(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer) { |
|
// TODO: we should remove the concept of a "SimpleEventPlugin". |
|
// This is the basic functionality of the event system. All |
|
// the other plugins are essentially polyfills. So the plugin |
|
// should probably be inlined somewhere and have its logic |
|
// be core the to event system. This would potentially allow |
|
// us to ship builds of React without the polyfilled plugins below. |
|
extractEvents$4(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags); |
|
var shouldProcessPolyfillPlugins = (eventSystemFlags & SHOULD_NOT_PROCESS_POLYFILL_EVENT_PLUGINS) === 0; // We don't process these events unless we are in the |
|
// event's native "bubble" phase, which means that we're |
|
// not in the capture phase. That's because we emulate |
|
// the capture phase here still. This is a trade-off, |
|
// because in an ideal world we would not emulate and use |
|
// the phases properly, like we do with the SimpleEvent |
|
// plugin. However, the plugins below either expect |
|
// emulation (EnterLeave) or use state localized to that |
|
// plugin (BeforeInput, Change, Select). The state in |
|
// these modules complicates things, as you'll essentially |
|
// get the case where the capture phase event might change |
|
// state, only for the following bubble event to come in |
|
// later and not trigger anything as the state now |
|
// invalidates the heuristics of the event plugin. We |
|
// could alter all these plugins to work in such ways, but |
|
// that might cause other unknown side-effects that we |
|
// can't foresee right now. |
|
|
|
if (shouldProcessPolyfillPlugins) { |
|
extractEvents$2(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget); |
|
extractEvents$1(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget); |
|
extractEvents$3(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget); |
|
extractEvents(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget); |
|
} |
|
} // List of events that need to be individually attached to media elements. |
|
|
|
|
|
var mediaEventTypes = ['abort', 'canplay', 'canplaythrough', 'durationchange', 'emptied', 'encrypted', 'ended', 'error', 'loadeddata', 'loadedmetadata', 'loadstart', 'pause', 'play', 'playing', 'progress', 'ratechange', 'resize', 'seeked', 'seeking', 'stalled', 'suspend', 'timeupdate', 'volumechange', 'waiting']; // We should not delegate these events to the container, but rather |
|
// set them on the actual target element itself. This is primarily |
|
// because these events do not consistently bubble in the DOM. |
|
|
|
var nonDelegatedEvents = new Set(['cancel', 'close', 'invalid', 'load', 'scroll', 'toggle'].concat(mediaEventTypes)); |
|
|
|
function executeDispatch(event, listener, currentTarget) { |
|
var type = event.type || 'unknown-event'; |
|
event.currentTarget = currentTarget; |
|
invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event); |
|
event.currentTarget = null; |
|
} |
|
|
|
function processDispatchQueueItemsInOrder(event, dispatchListeners, inCapturePhase) { |
|
var previousInstance; |
|
|
|
if (inCapturePhase) { |
|
for (var i = dispatchListeners.length - 1; i >= 0; i--) { |
|
var _dispatchListeners$i = dispatchListeners[i], |
|
instance = _dispatchListeners$i.instance, |
|
currentTarget = _dispatchListeners$i.currentTarget, |
|
listener = _dispatchListeners$i.listener; |
|
|
|
if (instance !== previousInstance && event.isPropagationStopped()) { |
|
return; |
|
} |
|
|
|
executeDispatch(event, listener, currentTarget); |
|
previousInstance = instance; |
|
} |
|
} else { |
|
for (var _i = 0; _i < dispatchListeners.length; _i++) { |
|
var _dispatchListeners$_i = dispatchListeners[_i], |
|
_instance = _dispatchListeners$_i.instance, |
|
_currentTarget = _dispatchListeners$_i.currentTarget, |
|
_listener = _dispatchListeners$_i.listener; |
|
|
|
if (_instance !== previousInstance && event.isPropagationStopped()) { |
|
return; |
|
} |
|
|
|
executeDispatch(event, _listener, _currentTarget); |
|
previousInstance = _instance; |
|
} |
|
} |
|
} |
|
|
|
function processDispatchQueue(dispatchQueue, eventSystemFlags) { |
|
var inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0; |
|
|
|
for (var i = 0; i < dispatchQueue.length; i++) { |
|
var _dispatchQueue$i = dispatchQueue[i], |
|
event = _dispatchQueue$i.event, |
|
listeners = _dispatchQueue$i.listeners; |
|
processDispatchQueueItemsInOrder(event, listeners, inCapturePhase); // event system doesn't use pooling. |
|
} // This would be a good time to rethrow if any of the event handlers threw. |
|
|
|
|
|
rethrowCaughtError(); |
|
} |
|
|
|
function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) { |
|
var nativeEventTarget = getEventTarget(nativeEvent); |
|
var dispatchQueue = []; |
|
extractEvents$5(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags); |
|
processDispatchQueue(dispatchQueue, eventSystemFlags); |
|
} |
|
|
|
function listenToNonDelegatedEvent(domEventName, targetElement) { |
|
{ |
|
if (!nonDelegatedEvents.has(domEventName)) { |
|
error('Did not expect a listenToNonDelegatedEvent() call for "%s". ' + 'This is a bug in React. Please file an issue.', domEventName); |
|
} |
|
} |
|
|
|
var isCapturePhaseListener = false; |
|
var listenerSet = getEventListenerSet(targetElement); |
|
var listenerSetKey = getListenerSetKey(domEventName, isCapturePhaseListener); |
|
|
|
if (!listenerSet.has(listenerSetKey)) { |
|
addTrappedEventListener(targetElement, domEventName, IS_NON_DELEGATED, isCapturePhaseListener); |
|
listenerSet.add(listenerSetKey); |
|
} |
|
} |
|
function listenToNativeEvent(domEventName, isCapturePhaseListener, target) { |
|
{ |
|
if (nonDelegatedEvents.has(domEventName) && !isCapturePhaseListener) { |
|
error('Did not expect a listenToNativeEvent() call for "%s" in the bubble phase. ' + 'This is a bug in React. Please file an issue.', domEventName); |
|
} |
|
} |
|
|
|
var eventSystemFlags = 0; |
|
|
|
if (isCapturePhaseListener) { |
|
eventSystemFlags |= IS_CAPTURE_PHASE; |
|
} |
|
|
|
addTrappedEventListener(target, domEventName, eventSystemFlags, isCapturePhaseListener); |
|
} // This is only used by createEventHandle when the |
|
var listeningMarker = '_reactListening' + Math.random().toString(36).slice(2); |
|
function listenToAllSupportedEvents(rootContainerElement) { |
|
if (!rootContainerElement[listeningMarker]) { |
|
rootContainerElement[listeningMarker] = true; |
|
allNativeEvents.forEach(function (domEventName) { |
|
// We handle selectionchange separately because it |
|
// doesn't bubble and needs to be on the document. |
|
if (domEventName !== 'selectionchange') { |
|
if (!nonDelegatedEvents.has(domEventName)) { |
|
listenToNativeEvent(domEventName, false, rootContainerElement); |
|
} |
|
|
|
listenToNativeEvent(domEventName, true, rootContainerElement); |
|
} |
|
}); |
|
var ownerDocument = rootContainerElement.nodeType === DOCUMENT_NODE ? rootContainerElement : rootContainerElement.ownerDocument; |
|
|
|
if (ownerDocument !== null) { |
|
// The selectionchange event also needs deduplication |
|
// but it is attached to the document. |
|
if (!ownerDocument[listeningMarker]) { |
|
ownerDocument[listeningMarker] = true; |
|
listenToNativeEvent('selectionchange', false, ownerDocument); |
|
} |
|
} |
|
} |
|
} |
|
|
|
function addTrappedEventListener(targetContainer, domEventName, eventSystemFlags, isCapturePhaseListener, isDeferredListenerForLegacyFBSupport) { |
|
var listener = createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags); // If passive option is not supported, then the event will be |
|
// active and not passive. |
|
|
|
var isPassiveListener = undefined; |
|
|
|
if (passiveBrowserEventsSupported) { |
|
// Browsers introduced an intervention, making these events |
|
// passive by default on document. React doesn't bind them |
|
// to document anymore, but changing this now would undo |
|
// the performance wins from the change. So we emulate |
|
// the existing behavior manually on the roots now. |
|
// https://github.com/facebook/react/issues/19651 |
|
if (domEventName === 'touchstart' || domEventName === 'touchmove' || domEventName === 'wheel') { |
|
isPassiveListener = true; |
|
} |
|
} |
|
|
|
targetContainer = targetContainer; |
|
var unsubscribeListener; // When legacyFBSupport is enabled, it's for when we |
|
|
|
|
|
if (isCapturePhaseListener) { |
|
if (isPassiveListener !== undefined) { |
|
unsubscribeListener = addEventCaptureListenerWithPassiveFlag(targetContainer, domEventName, listener, isPassiveListener); |
|
} else { |
|
unsubscribeListener = addEventCaptureListener(targetContainer, domEventName, listener); |
|
} |
|
} else { |
|
if (isPassiveListener !== undefined) { |
|
unsubscribeListener = addEventBubbleListenerWithPassiveFlag(targetContainer, domEventName, listener, isPassiveListener); |
|
} else { |
|
unsubscribeListener = addEventBubbleListener(targetContainer, domEventName, listener); |
|
} |
|
} |
|
} |
|
|
|
function isMatchingRootContainer(grandContainer, targetContainer) { |
|
return grandContainer === targetContainer || grandContainer.nodeType === COMMENT_NODE && grandContainer.parentNode === targetContainer; |
|
} |
|
|
|
function dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) { |
|
var ancestorInst = targetInst; |
|
|
|
if ((eventSystemFlags & IS_EVENT_HANDLE_NON_MANAGED_NODE) === 0 && (eventSystemFlags & IS_NON_DELEGATED) === 0) { |
|
var targetContainerNode = targetContainer; // If we are using the legacy FB support flag, we |
|
|
|
if (targetInst !== null) { |
|
// The below logic attempts to work out if we need to change |
|
// the target fiber to a different ancestor. We had similar logic |
|
// in the legacy event system, except the big difference between |
|
// systems is that the modern event system now has an event listener |
|
// attached to each React Root and React Portal Root. Together, |
|
// the DOM nodes representing these roots are the "rootContainer". |
|
// To figure out which ancestor instance we should use, we traverse |
|
// up the fiber tree from the target instance and attempt to find |
|
// root boundaries that match that of our current "rootContainer". |
|
// If we find that "rootContainer", we find the parent fiber |
|
// sub-tree for that root and make that our ancestor instance. |
|
var node = targetInst; |
|
|
|
mainLoop: while (true) { |
|
if (node === null) { |
|
return; |
|
} |
|
|
|
var nodeTag = node.tag; |
|
|
|
if (nodeTag === HostRoot || nodeTag === HostPortal) { |
|
var container = node.stateNode.containerInfo; |
|
|
|
if (isMatchingRootContainer(container, targetContainerNode)) { |
|
break; |
|
} |
|
|
|
if (nodeTag === HostPortal) { |
|
// The target is a portal, but it's not the rootContainer we're looking for. |
|
// Normally portals handle their own events all the way down to the root. |
|
// So we should be able to stop now. However, we don't know if this portal |
|
// was part of *our* root. |
|
var grandNode = node.return; |
|
|
|
while (grandNode !== null) { |
|
var grandTag = grandNode.tag; |
|
|
|
if (grandTag === HostRoot || grandTag === HostPortal) { |
|
var grandContainer = grandNode.stateNode.containerInfo; |
|
|
|
if (isMatchingRootContainer(grandContainer, targetContainerNode)) { |
|
// This is the rootContainer we're looking for and we found it as |
|
// a parent of the Portal. That means we can ignore it because the |
|
// Portal will bubble through to us. |
|
return; |
|
} |
|
} |
|
|
|
grandNode = grandNode.return; |
|
} |
|
} // Now we need to find it's corresponding host fiber in the other |
|
// tree. To do this we can use getClosestInstanceFromNode, but we |
|
// need to validate that the fiber is a host instance, otherwise |
|
// we need to traverse up through the DOM till we find the correct |
|
// node that is from the other tree. |
|
|
|
|
|
while (container !== null) { |
|
var parentNode = getClosestInstanceFromNode(container); |
|
|
|
if (parentNode === null) { |
|
return; |
|
} |
|
|
|
var parentTag = parentNode.tag; |
|
|
|
if (parentTag === HostComponent || parentTag === HostText) { |
|
node = ancestorInst = parentNode; |
|
continue mainLoop; |
|
} |
|
|
|
container = container.parentNode; |
|
} |
|
} |
|
|
|
node = node.return; |
|
} |
|
} |
|
} |
|
|
|
batchedUpdates(function () { |
|
return dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, ancestorInst); |
|
}); |
|
} |
|
|
|
function createDispatchListener(instance, listener, currentTarget) { |
|
return { |
|
instance: instance, |
|
listener: listener, |
|
currentTarget: currentTarget |
|
}; |
|
} |
|
|
|
function accumulateSinglePhaseListeners(targetFiber, reactName, nativeEventType, inCapturePhase, accumulateTargetOnly, nativeEvent) { |
|
var captureName = reactName !== null ? reactName + 'Capture' : null; |
|
var reactEventName = inCapturePhase ? captureName : reactName; |
|
var listeners = []; |
|
var instance = targetFiber; |
|
var lastHostComponent = null; // Accumulate all instances and listeners via the target -> root path. |
|
|
|
while (instance !== null) { |
|
var _instance2 = instance, |
|
stateNode = _instance2.stateNode, |
|
tag = _instance2.tag; // Handle listeners that are on HostComponents (i.e. <div>) |
|
|
|
if (tag === HostComponent && stateNode !== null) { |
|
lastHostComponent = stateNode; // createEventHandle listeners |
|
|
|
|
|
if (reactEventName !== null) { |
|
var listener = getListener(instance, reactEventName); |
|
|
|
if (listener != null) { |
|
listeners.push(createDispatchListener(instance, listener, lastHostComponent)); |
|
} |
|
} |
|
} // If we are only accumulating events for the target, then we don't |
|
// continue to propagate through the React fiber tree to find other |
|
// listeners. |
|
|
|
|
|
if (accumulateTargetOnly) { |
|
break; |
|
} // If we are processing the onBeforeBlur event, then we need to take |
|
|
|
instance = instance.return; |
|
} |
|
|
|
return listeners; |
|
} // We should only use this function for: |
|
// - BeforeInputEventPlugin |
|
// - ChangeEventPlugin |
|
// - SelectEventPlugin |
|
// This is because we only process these plugins |
|
// in the bubble phase, so we need to accumulate two |
|
// phase event listeners (via emulation). |
|
|
|
function accumulateTwoPhaseListeners(targetFiber, reactName) { |
|
var captureName = reactName + 'Capture'; |
|
var listeners = []; |
|
var instance = targetFiber; // Accumulate all instances and listeners via the target -> root path. |
|
|
|
while (instance !== null) { |
|
var _instance3 = instance, |
|
stateNode = _instance3.stateNode, |
|
tag = _instance3.tag; // Handle listeners that are on HostComponents (i.e. <div>) |
|
|
|
if (tag === HostComponent && stateNode !== null) { |
|
var currentTarget = stateNode; |
|
var captureListener = getListener(instance, captureName); |
|
|
|
if (captureListener != null) { |
|
listeners.unshift(createDispatchListener(instance, captureListener, currentTarget)); |
|
} |
|
|
|
var bubbleListener = getListener(instance, reactName); |
|
|
|
if (bubbleListener != null) { |
|
listeners.push(createDispatchListener(instance, bubbleListener, currentTarget)); |
|
} |
|
} |
|
|
|
instance = instance.return; |
|
} |
|
|
|
return listeners; |
|
} |
|
|
|
function getParent(inst) { |
|
if (inst === null) { |
|
return null; |
|
} |
|
|
|
do { |
|
inst = inst.return; // TODO: If this is a HostRoot we might want to bail out. |
|
// That is depending on if we want nested subtrees (layers) to bubble |
|
// events to their parent. We could also go through parentNode on the |
|
// host node but that wouldn't work for React Native and doesn't let us |
|
// do the portal feature. |
|
} while (inst && inst.tag !== HostComponent); |
|
|
|
if (inst) { |
|
return inst; |
|
} |
|
|
|
return null; |
|
} |
|
/** |
|
* Return the lowest common ancestor of A and B, or null if they are in |
|
* different trees. |
|
*/ |
|
|
|
|
|
function getLowestCommonAncestor(instA, instB) { |
|
var nodeA = instA; |
|
var nodeB = instB; |
|
var depthA = 0; |
|
|
|
for (var tempA = nodeA; tempA; tempA = getParent(tempA)) { |
|
depthA++; |
|
} |
|
|
|
var depthB = 0; |
|
|
|
for (var tempB = nodeB; tempB; tempB = getParent(tempB)) { |
|
depthB++; |
|
} // If A is deeper, crawl up. |
|
|
|
|
|
while (depthA - depthB > 0) { |
|
nodeA = getParent(nodeA); |
|
depthA--; |
|
} // If B is deeper, crawl up. |
|
|
|
|
|
while (depthB - depthA > 0) { |
|
nodeB = getParent(nodeB); |
|
depthB--; |
|
} // Walk in lockstep until we find a match. |
|
|
|
|
|
var depth = depthA; |
|
|
|
while (depth--) { |
|
if (nodeA === nodeB || nodeB !== null && nodeA === nodeB.alternate) { |
|
return nodeA; |
|
} |
|
|
|
nodeA = getParent(nodeA); |
|
nodeB = getParent(nodeB); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function accumulateEnterLeaveListenersForEvent(dispatchQueue, event, target, common, inCapturePhase) { |
|
var registrationName = event._reactName; |
|
var listeners = []; |
|
var instance = target; |
|
|
|
while (instance !== null) { |
|
if (instance === common) { |
|
break; |
|
} |
|
|
|
var _instance4 = instance, |
|
alternate = _instance4.alternate, |
|
stateNode = _instance4.stateNode, |
|
tag = _instance4.tag; |
|
|
|
if (alternate !== null && alternate === common) { |
|
break; |
|
} |
|
|
|
if (tag === HostComponent && stateNode !== null) { |
|
var currentTarget = stateNode; |
|
|
|
if (inCapturePhase) { |
|
var captureListener = getListener(instance, registrationName); |
|
|
|
if (captureListener != null) { |
|
listeners.unshift(createDispatchListener(instance, captureListener, currentTarget)); |
|
} |
|
} else if (!inCapturePhase) { |
|
var bubbleListener = getListener(instance, registrationName); |
|
|
|
if (bubbleListener != null) { |
|
listeners.push(createDispatchListener(instance, bubbleListener, currentTarget)); |
|
} |
|
} |
|
} |
|
|
|
instance = instance.return; |
|
} |
|
|
|
if (listeners.length !== 0) { |
|
dispatchQueue.push({ |
|
event: event, |
|
listeners: listeners |
|
}); |
|
} |
|
} // We should only use this function for: |
|
// - EnterLeaveEventPlugin |
|
// This is because we only process this plugin |
|
// in the bubble phase, so we need to accumulate two |
|
// phase event listeners. |
|
|
|
|
|
function accumulateEnterLeaveTwoPhaseListeners(dispatchQueue, leaveEvent, enterEvent, from, to) { |
|
var common = from && to ? getLowestCommonAncestor(from, to) : null; |
|
|
|
if (from !== null) { |
|
accumulateEnterLeaveListenersForEvent(dispatchQueue, leaveEvent, from, common, false); |
|
} |
|
|
|
if (to !== null && enterEvent !== null) { |
|
accumulateEnterLeaveListenersForEvent(dispatchQueue, enterEvent, to, common, true); |
|
} |
|
} |
|
function getListenerSetKey(domEventName, capture) { |
|
return domEventName + "__" + (capture ? 'capture' : 'bubble'); |
|
} |
|
|
|
var didWarnInvalidHydration = false; |
|
var DANGEROUSLY_SET_INNER_HTML = 'dangerouslySetInnerHTML'; |
|
var SUPPRESS_CONTENT_EDITABLE_WARNING = 'suppressContentEditableWarning'; |
|
var SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning'; |
|
var AUTOFOCUS = 'autoFocus'; |
|
var CHILDREN = 'children'; |
|
var STYLE = 'style'; |
|
var HTML$1 = '__html'; |
|
var warnedUnknownTags; |
|
var validatePropertiesInDevelopment; |
|
var warnForPropDifference; |
|
var warnForExtraAttributes; |
|
var warnForInvalidEventListener; |
|
var canDiffStyleForHydrationWarning; |
|
var normalizeHTML; |
|
|
|
{ |
|
warnedUnknownTags = { |
|
// There are working polyfills for <dialog>. Let people use it. |
|
dialog: true, |
|
// Electron ships a custom <webview> tag to display external web content in |
|
// an isolated frame and process. |
|
// This tag is not present in non Electron environments such as JSDom which |
|
// is often used for testing purposes. |
|
// @see https://electronjs.org/docs/api/webview-tag |
|
webview: true |
|
}; |
|
|
|
validatePropertiesInDevelopment = function (type, props) { |
|
validateProperties(type, props); |
|
validateProperties$1(type, props); |
|
validateProperties$2(type, props, { |
|
registrationNameDependencies: registrationNameDependencies, |
|
possibleRegistrationNames: possibleRegistrationNames |
|
}); |
|
}; // IE 11 parses & normalizes the style attribute as opposed to other |
|
// browsers. It adds spaces and sorts the properties in some |
|
// non-alphabetical order. Handling that would require sorting CSS |
|
// properties in the client & server versions or applying |
|
// `expectedStyle` to a temporary DOM node to read its `style` attribute |
|
// normalized. Since it only affects IE, we're skipping style warnings |
|
// in that browser completely in favor of doing all that work. |
|
// See https://github.com/facebook/react/issues/11807 |
|
|
|
|
|
canDiffStyleForHydrationWarning = canUseDOM && !document.documentMode; |
|
|
|
warnForPropDifference = function (propName, serverValue, clientValue) { |
|
if (didWarnInvalidHydration) { |
|
return; |
|
} |
|
|
|
var normalizedClientValue = normalizeMarkupForTextOrAttribute(clientValue); |
|
var normalizedServerValue = normalizeMarkupForTextOrAttribute(serverValue); |
|
|
|
if (normalizedServerValue === normalizedClientValue) { |
|
return; |
|
} |
|
|
|
didWarnInvalidHydration = true; |
|
|
|
error('Prop `%s` did not match. Server: %s Client: %s', propName, JSON.stringify(normalizedServerValue), JSON.stringify(normalizedClientValue)); |
|
}; |
|
|
|
warnForExtraAttributes = function (attributeNames) { |
|
if (didWarnInvalidHydration) { |
|
return; |
|
} |
|
|
|
didWarnInvalidHydration = true; |
|
var names = []; |
|
attributeNames.forEach(function (name) { |
|
names.push(name); |
|
}); |
|
|
|
error('Extra attributes from the server: %s', names); |
|
}; |
|
|
|
warnForInvalidEventListener = function (registrationName, listener) { |
|
if (listener === false) { |
|
error('Expected `%s` listener to be a function, instead got `false`.\n\n' + 'If you used to conditionally omit it with %s={condition && value}, ' + 'pass %s={condition ? value : undefined} instead.', registrationName, registrationName, registrationName); |
|
} else { |
|
error('Expected `%s` listener to be a function, instead got a value of `%s` type.', registrationName, typeof listener); |
|
} |
|
}; // Parse the HTML and read it back to normalize the HTML string so that it |
|
// can be used for comparison. |
|
|
|
|
|
normalizeHTML = function (parent, html) { |
|
// We could have created a separate document here to avoid |
|
// re-initializing custom elements if they exist. But this breaks |
|
// how <noscript> is being handled. So we use the same document. |
|
// See the discussion in https://github.com/facebook/react/pull/11157. |
|
var testElement = parent.namespaceURI === HTML_NAMESPACE ? parent.ownerDocument.createElement(parent.tagName) : parent.ownerDocument.createElementNS(parent.namespaceURI, parent.tagName); |
|
testElement.innerHTML = html; |
|
return testElement.innerHTML; |
|
}; |
|
} // HTML parsing normalizes CR and CRLF to LF. |
|
// It also can turn \u0000 into \uFFFD inside attributes. |
|
// https://www.w3.org/TR/html5/single-page.html#preprocessing-the-input-stream |
|
// If we have a mismatch, it might be caused by that. |
|
// We will still patch up in this case but not fire the warning. |
|
|
|
|
|
var NORMALIZE_NEWLINES_REGEX = /\r\n?/g; |
|
var NORMALIZE_NULL_AND_REPLACEMENT_REGEX = /\u0000|\uFFFD/g; |
|
|
|
function normalizeMarkupForTextOrAttribute(markup) { |
|
{ |
|
checkHtmlStringCoercion(markup); |
|
} |
|
|
|
var markupString = typeof markup === 'string' ? markup : '' + markup; |
|
return markupString.replace(NORMALIZE_NEWLINES_REGEX, '\n').replace(NORMALIZE_NULL_AND_REPLACEMENT_REGEX, ''); |
|
} |
|
|
|
function checkForUnmatchedText(serverText, clientText, isConcurrentMode, shouldWarnDev) { |
|
var normalizedClientText = normalizeMarkupForTextOrAttribute(clientText); |
|
var normalizedServerText = normalizeMarkupForTextOrAttribute(serverText); |
|
|
|
if (normalizedServerText === normalizedClientText) { |
|
return; |
|
} |
|
|
|
if (shouldWarnDev) { |
|
{ |
|
if (!didWarnInvalidHydration) { |
|
didWarnInvalidHydration = true; |
|
|
|
error('Text content did not match. Server: "%s" Client: "%s"', normalizedServerText, normalizedClientText); |
|
} |
|
} |
|
} |
|
|
|
if (isConcurrentMode && enableClientRenderFallbackOnTextMismatch) { |
|
// In concurrent roots, we throw when there's a text mismatch and revert to |
|
// client rendering, up to the nearest Suspense boundary. |
|
throw new Error('Text content does not match server-rendered HTML.'); |
|
} |
|
} |
|
|
|
function getOwnerDocumentFromRootContainer(rootContainerElement) { |
|
return rootContainerElement.nodeType === DOCUMENT_NODE ? rootContainerElement : rootContainerElement.ownerDocument; |
|
} |
|
|
|
function noop() {} |
|
|
|
function trapClickOnNonInteractiveElement(node) { |
|
// 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 |
|
// listener on the target node. |
|
// https://www.quirksmode.org/blog/archives/2010/09/click_event_del.html |
|
// Just set it using the onclick property so that we don't have to manage any |
|
// bookkeeping for it. Not sure if we need to clear it when the listener is |
|
// removed. |
|
// TODO: Only do this for the relevant Safaris maybe? |
|
node.onclick = noop; |
|
} |
|
|
|
function setInitialDOMProperties(tag, domElement, rootContainerElement, nextProps, isCustomComponentTag) { |
|
for (var propKey in nextProps) { |
|
if (!nextProps.hasOwnProperty(propKey)) { |
|
continue; |
|
} |
|
|
|
var nextProp = nextProps[propKey]; |
|
|
|
if (propKey === STYLE) { |
|
{ |
|
if (nextProp) { |
|
// Freeze the next style object so that we can assume it won't be |
|
// mutated. We have already warned for this in the past. |
|
Object.freeze(nextProp); |
|
} |
|
} // Relies on `updateStylesByID` not mutating `styleUpdates`. |
|
|
|
|
|
setValueForStyles(domElement, nextProp); |
|
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) { |
|
var nextHtml = nextProp ? nextProp[HTML$1] : undefined; |
|
|
|
if (nextHtml != null) { |
|
setInnerHTML(domElement, nextHtml); |
|
} |
|
} else if (propKey === CHILDREN) { |
|
if (typeof nextProp === 'string') { |
|
// Avoid setting initial textContent when the text is empty. In IE11 setting |
|
// textContent on a <textarea> will cause the placeholder to not |
|
// show within the <textarea> until it has been focused and blurred again. |
|
// https://github.com/facebook/react/issues/6731#issuecomment-254874553 |
|
var canSetTextContent = tag !== 'textarea' || nextProp !== ''; |
|
|
|
if (canSetTextContent) { |
|
setTextContent(domElement, nextProp); |
|
} |
|
} else if (typeof nextProp === 'number') { |
|
setTextContent(domElement, '' + nextProp); |
|
} |
|
} else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING) ; else if (propKey === AUTOFOCUS) ; else if (registrationNameDependencies.hasOwnProperty(propKey)) { |
|
if (nextProp != null) { |
|
if ( typeof nextProp !== 'function') { |
|
warnForInvalidEventListener(propKey, nextProp); |
|
} |
|
|
|
if (propKey === 'onScroll') { |
|
listenToNonDelegatedEvent('scroll', domElement); |
|
} |
|
} |
|
} else if (nextProp != null) { |
|
setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag); |
|
} |
|
} |
|
} |
|
|
|
function updateDOMProperties(domElement, updatePayload, wasCustomComponentTag, isCustomComponentTag) { |
|
// TODO: Handle wasCustomComponentTag |
|
for (var i = 0; i < updatePayload.length; i += 2) { |
|
var propKey = updatePayload[i]; |
|
var propValue = updatePayload[i + 1]; |
|
|
|
if (propKey === STYLE) { |
|
setValueForStyles(domElement, propValue); |
|
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) { |
|
setInnerHTML(domElement, propValue); |
|
} else if (propKey === CHILDREN) { |
|
setTextContent(domElement, propValue); |
|
} else { |
|
setValueForProperty(domElement, propKey, propValue, isCustomComponentTag); |
|
} |
|
} |
|
} |
|
|
|
function createElement(type, props, rootContainerElement, parentNamespace) { |
|
var isCustomComponentTag; // We create tags in the namespace of their parent container, except HTML |
|
// tags get no namespace. |
|
|
|
var ownerDocument = getOwnerDocumentFromRootContainer(rootContainerElement); |
|
var domElement; |
|
var namespaceURI = parentNamespace; |
|
|
|
if (namespaceURI === HTML_NAMESPACE) { |
|
namespaceURI = getIntrinsicNamespace(type); |
|
} |
|
|
|
if (namespaceURI === HTML_NAMESPACE) { |
|
{ |
|
isCustomComponentTag = isCustomComponent(type, props); // Should this check be gated by parent namespace? Not sure we want to |
|
// allow <SVG> or <mATH>. |
|
|
|
if (!isCustomComponentTag && type !== type.toLowerCase()) { |
|
error('<%s /> is using incorrect casing. ' + 'Use PascalCase for React components, ' + 'or lowercase for HTML elements.', type); |
|
} |
|
} |
|
|
|
if (type === 'script') { |
|
// Create the script via .innerHTML so its "parser-inserted" flag is |
|
// set to true and it does not execute |
|
var div = ownerDocument.createElement('div'); |
|
|
|
div.innerHTML = '<script><' + '/script>'; // eslint-disable-line |
|
// This is guaranteed to yield a script element. |
|
|
|
var firstChild = div.firstChild; |
|
domElement = div.removeChild(firstChild); |
|
} else if (typeof props.is === 'string') { |
|
// $FlowIssue `createElement` should be updated for Web Components |
|
domElement = ownerDocument.createElement(type, { |
|
is: props.is |
|
}); |
|
} else { |
|
// Separate else branch instead of using `props.is || undefined` above because of a Firefox bug. |
|
// See discussion in https://github.com/facebook/react/pull/6896 |
|
// and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240 |
|
domElement = ownerDocument.createElement(type); // Normally attributes are assigned in `setInitialDOMProperties`, however the `multiple` and `size` |
|
// attributes on `select`s needs to be added before `option`s are inserted. |
|
// This prevents: |
|
// - a bug where the `select` does not scroll to the correct option because singular |
|
// `select` elements automatically pick the first item #13222 |
|
// - a bug where the `select` set the first item as selected despite the `size` attribute #14239 |
|
// See https://github.com/facebook/react/issues/13222 |
|
// and https://github.com/facebook/react/issues/14239 |
|
|
|
if (type === 'select') { |
|
var node = domElement; |
|
|
|
if (props.multiple) { |
|
node.multiple = true; |
|
} else if (props.size) { |
|
// Setting a size greater than 1 causes a select to behave like `multiple=true`, where |
|
// it is possible that no option is selected. |
|
// |
|
// This is only necessary when a select in "single selection mode". |
|
node.size = props.size; |
|
} |
|
} |
|
} |
|
} else { |
|
domElement = ownerDocument.createElementNS(namespaceURI, type); |
|
} |
|
|
|
{ |
|
if (namespaceURI === HTML_NAMESPACE) { |
|
if (!isCustomComponentTag && Object.prototype.toString.call(domElement) === '[object HTMLUnknownElement]' && !hasOwnProperty.call(warnedUnknownTags, type)) { |
|
warnedUnknownTags[type] = true; |
|
|
|
error('The tag <%s> is unrecognized in this browser. ' + 'If you meant to render a React component, start its name with ' + 'an uppercase letter.', type); |
|
} |
|
} |
|
} |
|
|
|
return domElement; |
|
} |
|
function createTextNode(text, rootContainerElement) { |
|
return getOwnerDocumentFromRootContainer(rootContainerElement).createTextNode(text); |
|
} |
|
function setInitialProperties(domElement, tag, rawProps, rootContainerElement) { |
|
var isCustomComponentTag = isCustomComponent(tag, rawProps); |
|
|
|
{ |
|
validatePropertiesInDevelopment(tag, rawProps); |
|
} // TODO: Make sure that we check isMounted before firing any of these events. |
|
|
|
|
|
var props; |
|
|
|
switch (tag) { |
|
case 'dialog': |
|
listenToNonDelegatedEvent('cancel', domElement); |
|
listenToNonDelegatedEvent('close', domElement); |
|
props = rawProps; |
|
break; |
|
|
|
case 'iframe': |
|
case 'object': |
|
case 'embed': |
|
// We listen to this event in case to ensure emulated bubble |
|
// listeners still fire for the load event. |
|
listenToNonDelegatedEvent('load', domElement); |
|
props = rawProps; |
|
break; |
|
|
|
case 'video': |
|
case 'audio': |
|
// We listen to these events in case to ensure emulated bubble |
|
// listeners still fire for all the media events. |
|
for (var i = 0; i < mediaEventTypes.length; i++) { |
|
listenToNonDelegatedEvent(mediaEventTypes[i], domElement); |
|
} |
|
|
|
props = rawProps; |
|
break; |
|
|
|
case 'source': |
|
// We listen to this event in case to ensure emulated bubble |
|
// listeners still fire for the error event. |
|
listenToNonDelegatedEvent('error', domElement); |
|
props = rawProps; |
|
break; |
|
|
|
case 'img': |
|
case 'image': |
|
case 'link': |
|
// We listen to these events in case to ensure emulated bubble |
|
// listeners still fire for error and load events. |
|
listenToNonDelegatedEvent('error', domElement); |
|
listenToNonDelegatedEvent('load', domElement); |
|
props = rawProps; |
|
break; |
|
|
|
case 'details': |
|
// We listen to this event in case to ensure emulated bubble |
|
// listeners still fire for the toggle event. |
|
listenToNonDelegatedEvent('toggle', domElement); |
|
props = rawProps; |
|
break; |
|
|
|
case 'input': |
|
initWrapperState(domElement, rawProps); |
|
props = getHostProps(domElement, rawProps); // We listen to this event in case to ensure emulated bubble |
|
// listeners still fire for the invalid event. |
|
|
|
listenToNonDelegatedEvent('invalid', domElement); |
|
break; |
|
|
|
case 'option': |
|
validateProps(domElement, rawProps); |
|
props = rawProps; |
|
break; |
|
|
|
case 'select': |
|
initWrapperState$1(domElement, rawProps); |
|
props = getHostProps$1(domElement, rawProps); // We listen to this event in case to ensure emulated bubble |
|
// listeners still fire for the invalid event. |
|
|
|
listenToNonDelegatedEvent('invalid', domElement); |
|
break; |
|
|
|
case 'textarea': |
|
initWrapperState$2(domElement, rawProps); |
|
props = getHostProps$2(domElement, rawProps); // We listen to this event in case to ensure emulated bubble |
|
// listeners still fire for the invalid event. |
|
|
|
listenToNonDelegatedEvent('invalid', domElement); |
|
break; |
|
|
|
default: |
|
props = rawProps; |
|
} |
|
|
|
assertValidProps(tag, props); |
|
setInitialDOMProperties(tag, domElement, rootContainerElement, props, isCustomComponentTag); |
|
|
|
switch (tag) { |
|
case 'input': |
|
// TODO: Make sure we check if this is still unmounted or do any clean |
|
// up necessary since we never stop tracking anymore. |
|
track(domElement); |
|
postMountWrapper(domElement, rawProps, false); |
|
break; |
|
|
|
case 'textarea': |
|
// TODO: Make sure we check if this is still unmounted or do any clean |
|
// up necessary since we never stop tracking anymore. |
|
track(domElement); |
|
postMountWrapper$3(domElement); |
|
break; |
|
|
|
case 'option': |
|
postMountWrapper$1(domElement, rawProps); |
|
break; |
|
|
|
case 'select': |
|
postMountWrapper$2(domElement, rawProps); |
|
break; |
|
|
|
default: |
|
if (typeof props.onClick === 'function') { |
|
// TODO: This cast may not be sound for SVG, MathML or custom elements. |
|
trapClickOnNonInteractiveElement(domElement); |
|
} |
|
|
|
break; |
|
} |
|
} // Calculate the diff between the two objects. |
|
|
|
function diffProperties(domElement, tag, lastRawProps, nextRawProps, rootContainerElement) { |
|
{ |
|
validatePropertiesInDevelopment(tag, nextRawProps); |
|
} |
|
|
|
var updatePayload = null; |
|
var lastProps; |
|
var nextProps; |
|
|
|
switch (tag) { |
|
case 'input': |
|
lastProps = getHostProps(domElement, lastRawProps); |
|
nextProps = getHostProps(domElement, nextRawProps); |
|
updatePayload = []; |
|
break; |
|
|
|
case 'select': |
|
lastProps = getHostProps$1(domElement, lastRawProps); |
|
nextProps = getHostProps$1(domElement, nextRawProps); |
|
updatePayload = []; |
|
break; |
|
|
|
case 'textarea': |
|
lastProps = getHostProps$2(domElement, lastRawProps); |
|
nextProps = getHostProps$2(domElement, nextRawProps); |
|
updatePayload = []; |
|
break; |
|
|
|
default: |
|
lastProps = lastRawProps; |
|
nextProps = nextRawProps; |
|
|
|
if (typeof lastProps.onClick !== 'function' && typeof nextProps.onClick === 'function') { |
|
// TODO: This cast may not be sound for SVG, MathML or custom elements. |
|
trapClickOnNonInteractiveElement(domElement); |
|
} |
|
|
|
break; |
|
} |
|
|
|
assertValidProps(tag, nextProps); |
|
var propKey; |
|
var styleName; |
|
var styleUpdates = null; |
|
|
|
for (propKey in lastProps) { |
|
if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] == null) { |
|
continue; |
|
} |
|
|
|
if (propKey === STYLE) { |
|
var lastStyle = lastProps[propKey]; |
|
|
|
for (styleName in lastStyle) { |
|
if (lastStyle.hasOwnProperty(styleName)) { |
|
if (!styleUpdates) { |
|
styleUpdates = {}; |
|
} |
|
|
|
styleUpdates[styleName] = ''; |
|
} |
|
} |
|
} else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) ; else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING) ; else if (propKey === AUTOFOCUS) ; else if (registrationNameDependencies.hasOwnProperty(propKey)) { |
|
// This is a special case. If any listener updates we need to ensure |
|
// that the "current" fiber pointer gets updated so we need a commit |
|
// to update this element. |
|
if (!updatePayload) { |
|
updatePayload = []; |
|
} |
|
} else { |
|
// For all other deleted properties we add it to the queue. We use |
|
// the allowed property list in the commit phase instead. |
|
(updatePayload = updatePayload || []).push(propKey, null); |
|
} |
|
} |
|
|
|
for (propKey in nextProps) { |
|
var nextProp = nextProps[propKey]; |
|
var lastProp = lastProps != null ? lastProps[propKey] : undefined; |
|
|
|
if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || nextProp == null && lastProp == null) { |
|
continue; |
|
} |
|
|
|
if (propKey === STYLE) { |
|
{ |
|
if (nextProp) { |
|
// Freeze the next style object so that we can assume it won't be |
|
// mutated. We have already warned for this in the past. |
|
Object.freeze(nextProp); |
|
} |
|
} |
|
|
|
if (lastProp) { |
|
// Unset styles on `lastProp` but not on `nextProp`. |
|
for (styleName in lastProp) { |
|
if (lastProp.hasOwnProperty(styleName) && (!nextProp || !nextProp.hasOwnProperty(styleName))) { |
|
if (!styleUpdates) { |
|
styleUpdates = {}; |
|
} |
|
|
|
styleUpdates[styleName] = ''; |
|
} |
|
} // Update styles that changed since `lastProp`. |
|
|
|
|
|
for (styleName in nextProp) { |
|
if (nextProp.hasOwnProperty(styleName) && lastProp[styleName] !== nextProp[styleName]) { |
|
if (!styleUpdates) { |
|
styleUpdates = {}; |
|
} |
|
|
|
styleUpdates[styleName] = nextProp[styleName]; |
|
} |
|
} |
|
} else { |
|
// Relies on `updateStylesByID` not mutating `styleUpdates`. |
|
if (!styleUpdates) { |
|
if (!updatePayload) { |
|
updatePayload = []; |
|
} |
|
|
|
updatePayload.push(propKey, styleUpdates); |
|
} |
|
|
|
styleUpdates = nextProp; |
|
} |
|
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) { |
|
var nextHtml = nextProp ? nextProp[HTML$1] : undefined; |
|
var lastHtml = lastProp ? lastProp[HTML$1] : undefined; |
|
|
|
if (nextHtml != null) { |
|
if (lastHtml !== nextHtml) { |
|
(updatePayload = updatePayload || []).push(propKey, nextHtml); |
|
} |
|
} |
|
} else if (propKey === CHILDREN) { |
|
if (typeof nextProp === 'string' || typeof nextProp === 'number') { |
|
(updatePayload = updatePayload || []).push(propKey, '' + nextProp); |
|
} |
|
} else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING) ; else if (registrationNameDependencies.hasOwnProperty(propKey)) { |
|
if (nextProp != null) { |
|
// We eagerly listen to this even though we haven't committed yet. |
|
if ( typeof nextProp !== 'function') { |
|
warnForInvalidEventListener(propKey, nextProp); |
|
} |
|
|
|
if (propKey === 'onScroll') { |
|
listenToNonDelegatedEvent('scroll', domElement); |
|
} |
|
} |
|
|
|
if (!updatePayload && lastProp !== nextProp) { |
|
// This is a special case. If any listener updates we need to ensure |
|
// that the "current" props pointer gets updated so we need a commit |
|
// to update this element. |
|
updatePayload = []; |
|
} |
|
} else { |
|
// For any other property we always add it to the queue and then we |
|
// filter it out using the allowed property list during the commit. |
|
(updatePayload = updatePayload || []).push(propKey, nextProp); |
|
} |
|
} |
|
|
|
if (styleUpdates) { |
|
{ |
|
validateShorthandPropertyCollisionInDev(styleUpdates, nextProps[STYLE]); |
|
} |
|
|
|
(updatePayload = updatePayload || []).push(STYLE, styleUpdates); |
|
} |
|
|
|
return updatePayload; |
|
} // Apply the diff. |
|
|
|
function updateProperties(domElement, updatePayload, tag, lastRawProps, nextRawProps) { |
|
// Update checked *before* name. |
|
// In the middle of an update, it is possible to have multiple checked. |
|
// When a checked radio tries to change name, browser makes another radio's checked false. |
|
if (tag === 'input' && nextRawProps.type === 'radio' && nextRawProps.name != null) { |
|
updateChecked(domElement, nextRawProps); |
|
} |
|
|
|
var wasCustomComponentTag = isCustomComponent(tag, lastRawProps); |
|
var isCustomComponentTag = isCustomComponent(tag, nextRawProps); // Apply the diff. |
|
|
|
updateDOMProperties(domElement, updatePayload, wasCustomComponentTag, isCustomComponentTag); // TODO: Ensure that an update gets scheduled if any of the special props |
|
// changed. |
|
|
|
switch (tag) { |
|
case 'input': |
|
// 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. |
|
updateWrapper(domElement, nextRawProps); |
|
break; |
|
|
|
case 'textarea': |
|
updateWrapper$1(domElement, nextRawProps); |
|
break; |
|
|
|
case 'select': |
|
// <select> value update needs to occur after <option> children |
|
// reconciliation |
|
postUpdateWrapper(domElement, nextRawProps); |
|
break; |
|
} |
|
} |
|
|
|
function getPossibleStandardName(propName) { |
|
{ |
|
var lowerCasedName = propName.toLowerCase(); |
|
|
|
if (!possibleStandardNames.hasOwnProperty(lowerCasedName)) { |
|
return null; |
|
} |
|
|
|
return possibleStandardNames[lowerCasedName] || null; |
|
} |
|
} |
|
|
|
function diffHydratedProperties(domElement, tag, rawProps, parentNamespace, rootContainerElement, isConcurrentMode, shouldWarnDev) { |
|
var isCustomComponentTag; |
|
var extraAttributeNames; |
|
|
|
{ |
|
isCustomComponentTag = isCustomComponent(tag, rawProps); |
|
validatePropertiesInDevelopment(tag, rawProps); |
|
} // TODO: Make sure that we check isMounted before firing any of these events. |
|
|
|
|
|
switch (tag) { |
|
case 'dialog': |
|
listenToNonDelegatedEvent('cancel', domElement); |
|
listenToNonDelegatedEvent('close', domElement); |
|
break; |
|
|
|
case 'iframe': |
|
case 'object': |
|
case 'embed': |
|
// We listen to this event in case to ensure emulated bubble |
|
// listeners still fire for the load event. |
|
listenToNonDelegatedEvent('load', domElement); |
|
break; |
|
|
|
case 'video': |
|
case 'audio': |
|
// We listen to these events in case to ensure emulated bubble |
|
// listeners still fire for all the media events. |
|
for (var i = 0; i < mediaEventTypes.length; i++) { |
|
listenToNonDelegatedEvent(mediaEventTypes[i], domElement); |
|
} |
|
|
|
break; |
|
|
|
case 'source': |
|
// We listen to this event in case to ensure emulated bubble |
|
// listeners still fire for the error event. |
|
listenToNonDelegatedEvent('error', domElement); |
|
break; |
|
|
|
case 'img': |
|
case 'image': |
|
case 'link': |
|
// We listen to these events in case to ensure emulated bubble |
|
// listeners still fire for error and load events. |
|
listenToNonDelegatedEvent('error', domElement); |
|
listenToNonDelegatedEvent('load', domElement); |
|
break; |
|
|
|
case 'details': |
|
// We listen to this event in case to ensure emulated bubble |
|
// listeners still fire for the toggle event. |
|
listenToNonDelegatedEvent('toggle', domElement); |
|
break; |
|
|
|
case 'input': |
|
initWrapperState(domElement, rawProps); // We listen to this event in case to ensure emulated bubble |
|
// listeners still fire for the invalid event. |
|
|
|
listenToNonDelegatedEvent('invalid', domElement); |
|
break; |
|
|
|
case 'option': |
|
validateProps(domElement, rawProps); |
|
break; |
|
|
|
case 'select': |
|
initWrapperState$1(domElement, rawProps); // We listen to this event in case to ensure emulated bubble |
|
// listeners still fire for the invalid event. |
|
|
|
listenToNonDelegatedEvent('invalid', domElement); |
|
break; |
|
|
|
case 'textarea': |
|
initWrapperState$2(domElement, rawProps); // We listen to this event in case to ensure emulated bubble |
|
// listeners still fire for the invalid event. |
|
|
|
listenToNonDelegatedEvent('invalid', domElement); |
|
break; |
|
} |
|
|
|
assertValidProps(tag, rawProps); |
|
|
|
{ |
|
extraAttributeNames = new Set(); |
|
var attributes = domElement.attributes; |
|
|
|
for (var _i = 0; _i < attributes.length; _i++) { |
|
var name = attributes[_i].name.toLowerCase(); |
|
|
|
switch (name) { |
|
// Controlled attributes are not validated |
|
// TODO: Only ignore them on controlled tags. |
|
case 'value': |
|
break; |
|
|
|
case 'checked': |
|
break; |
|
|
|
case 'selected': |
|
break; |
|
|
|
default: |
|
// Intentionally use the original name. |
|
// See discussion in https://github.com/facebook/react/pull/10676. |
|
extraAttributeNames.add(attributes[_i].name); |
|
} |
|
} |
|
} |
|
|
|
var updatePayload = null; |
|
|
|
for (var propKey in rawProps) { |
|
if (!rawProps.hasOwnProperty(propKey)) { |
|
continue; |
|
} |
|
|
|
var nextProp = rawProps[propKey]; |
|
|
|
if (propKey === CHILDREN) { |
|
// For text content children we compare against textContent. This |
|
// might match additional HTML that is hidden when we read it using |
|
// textContent. E.g. "foo" will match "f<span>oo</span>" but that still |
|
// satisfies our requirement. Our requirement is not to produce perfect |
|
// HTML and attributes. Ideally we should preserve structure but it's |
|
// ok not to if the visible content is still enough to indicate what |
|
// even listeners these nodes might be wired up to. |
|
// TODO: Warn if there is more than a single textNode as a child. |
|
// TODO: Should we use domElement.firstChild.nodeValue to compare? |
|
if (typeof nextProp === 'string') { |
|
if (domElement.textContent !== nextProp) { |
|
if (rawProps[SUPPRESS_HYDRATION_WARNING] !== true) { |
|
checkForUnmatchedText(domElement.textContent, nextProp, isConcurrentMode, shouldWarnDev); |
|
} |
|
|
|
updatePayload = [CHILDREN, nextProp]; |
|
} |
|
} else if (typeof nextProp === 'number') { |
|
if (domElement.textContent !== '' + nextProp) { |
|
if (rawProps[SUPPRESS_HYDRATION_WARNING] !== true) { |
|
checkForUnmatchedText(domElement.textContent, nextProp, isConcurrentMode, shouldWarnDev); |
|
} |
|
|
|
updatePayload = [CHILDREN, '' + nextProp]; |
|
} |
|
} |
|
} else if (registrationNameDependencies.hasOwnProperty(propKey)) { |
|
if (nextProp != null) { |
|
if ( typeof nextProp !== 'function') { |
|
warnForInvalidEventListener(propKey, nextProp); |
|
} |
|
|
|
if (propKey === 'onScroll') { |
|
listenToNonDelegatedEvent('scroll', domElement); |
|
} |
|
} |
|
} else if (shouldWarnDev && true && // Convince Flow we've calculated it (it's DEV-only in this method.) |
|
typeof isCustomComponentTag === 'boolean') { |
|
// Validate that the properties correspond to their expected values. |
|
var serverValue = void 0; |
|
var propertyInfo = isCustomComponentTag && enableCustomElementPropertySupport ? null : getPropertyInfo(propKey); |
|
|
|
if (rawProps[SUPPRESS_HYDRATION_WARNING] === true) ; else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING || // Controlled attributes are not validated |
|
// TODO: Only ignore them on controlled tags. |
|
propKey === 'value' || propKey === 'checked' || propKey === 'selected') ; else if (propKey === DANGEROUSLY_SET_INNER_HTML) { |
|
var serverHTML = domElement.innerHTML; |
|
var nextHtml = nextProp ? nextProp[HTML$1] : undefined; |
|
|
|
if (nextHtml != null) { |
|
var expectedHTML = normalizeHTML(domElement, nextHtml); |
|
|
|
if (expectedHTML !== serverHTML) { |
|
warnForPropDifference(propKey, serverHTML, expectedHTML); |
|
} |
|
} |
|
} else if (propKey === STYLE) { |
|
// $FlowFixMe - Should be inferred as not undefined. |
|
extraAttributeNames.delete(propKey); |
|
|
|
if (canDiffStyleForHydrationWarning) { |
|
var expectedStyle = createDangerousStringForStyles(nextProp); |
|
serverValue = domElement.getAttribute('style'); |
|
|
|
if (expectedStyle !== serverValue) { |
|
warnForPropDifference(propKey, serverValue, expectedStyle); |
|
} |
|
} |
|
} else if (isCustomComponentTag && !enableCustomElementPropertySupport) { |
|
// $FlowFixMe - Should be inferred as not undefined. |
|
extraAttributeNames.delete(propKey.toLowerCase()); |
|
serverValue = getValueForAttribute(domElement, propKey, nextProp); |
|
|
|
if (nextProp !== serverValue) { |
|
warnForPropDifference(propKey, serverValue, nextProp); |
|
} |
|
} else if (!shouldIgnoreAttribute(propKey, propertyInfo, isCustomComponentTag) && !shouldRemoveAttribute(propKey, nextProp, propertyInfo, isCustomComponentTag)) { |
|
var isMismatchDueToBadCasing = false; |
|
|
|
if (propertyInfo !== null) { |
|
// $FlowFixMe - Should be inferred as not undefined. |
|
extraAttributeNames.delete(propertyInfo.attributeName); |
|
serverValue = getValueForProperty(domElement, propKey, nextProp, propertyInfo); |
|
} else { |
|
var ownNamespace = parentNamespace; |
|
|
|
if (ownNamespace === HTML_NAMESPACE) { |
|
ownNamespace = getIntrinsicNamespace(tag); |
|
} |
|
|
|
if (ownNamespace === HTML_NAMESPACE) { |
|
// $FlowFixMe - Should be inferred as not undefined. |
|
extraAttributeNames.delete(propKey.toLowerCase()); |
|
} else { |
|
var standardName = getPossibleStandardName(propKey); |
|
|
|
if (standardName !== null && standardName !== propKey) { |
|
// If an SVG prop is supplied with bad casing, it will |
|
// be successfully parsed from HTML, but will produce a mismatch |
|
// (and would be incorrectly rendered on the client). |
|
// However, we already warn about bad casing elsewhere. |
|
// So we'll skip the misleading extra mismatch warning in this case. |
|
isMismatchDueToBadCasing = true; // $FlowFixMe - Should be inferred as not undefined. |
|
|
|
extraAttributeNames.delete(standardName); |
|
} // $FlowFixMe - Should be inferred as not undefined. |
|
|
|
|
|
extraAttributeNames.delete(propKey); |
|
} |
|
|
|
serverValue = getValueForAttribute(domElement, propKey, nextProp); |
|
} |
|
|
|
var dontWarnCustomElement = enableCustomElementPropertySupport ; |
|
|
|
if (!dontWarnCustomElement && nextProp !== serverValue && !isMismatchDueToBadCasing) { |
|
warnForPropDifference(propKey, serverValue, nextProp); |
|
} |
|
} |
|
} |
|
} |
|
|
|
{ |
|
if (shouldWarnDev) { |
|
if ( // $FlowFixMe - Should be inferred as not undefined. |
|
extraAttributeNames.size > 0 && rawProps[SUPPRESS_HYDRATION_WARNING] !== true) { |
|
// $FlowFixMe - Should be inferred as not undefined. |
|
warnForExtraAttributes(extraAttributeNames); |
|
} |
|
} |
|
} |
|
|
|
switch (tag) { |
|
case 'input': |
|
// TODO: Make sure we check if this is still unmounted or do any clean |
|
// up necessary since we never stop tracking anymore. |
|
track(domElement); |
|
postMountWrapper(domElement, rawProps, true); |
|
break; |
|
|
|
case 'textarea': |
|
// TODO: Make sure we check if this is still unmounted or do any clean |
|
// up necessary since we never stop tracking anymore. |
|
track(domElement); |
|
postMountWrapper$3(domElement); |
|
break; |
|
|
|
case 'select': |
|
case 'option': |
|
// For input and textarea we current always set the value property at |
|
// post mount to force it to diverge from attributes. However, for |
|
// option and select we don't quite do the same thing and select |
|
// is not resilient to the DOM state changing so we don't do that here. |
|
// TODO: Consider not doing this for input and textarea. |
|
break; |
|
|
|
default: |
|
if (typeof rawProps.onClick === 'function') { |
|
// TODO: This cast may not be sound for SVG, MathML or custom elements. |
|
trapClickOnNonInteractiveElement(domElement); |
|
} |
|
|
|
break; |
|
} |
|
|
|
return updatePayload; |
|
} |
|
function diffHydratedText(textNode, text, isConcurrentMode) { |
|
var isDifferent = textNode.nodeValue !== text; |
|
return isDifferent; |
|
} |
|
function warnForDeletedHydratableElement(parentNode, child) { |
|
{ |
|
if (didWarnInvalidHydration) { |
|
return; |
|
} |
|
|
|
didWarnInvalidHydration = true; |
|
|
|
error('Did not expect server HTML to contain a <%s> in <%s>.', child.nodeName.toLowerCase(), parentNode.nodeName.toLowerCase()); |
|
} |
|
} |
|
function warnForDeletedHydratableText(parentNode, child) { |
|
{ |
|
if (didWarnInvalidHydration) { |
|
return; |
|
} |
|
|
|
didWarnInvalidHydration = true; |
|
|
|
error('Did not expect server HTML to contain the text node "%s" in <%s>.', child.nodeValue, parentNode.nodeName.toLowerCase()); |
|
} |
|
} |
|
function warnForInsertedHydratedElement(parentNode, tag, props) { |
|
{ |
|
if (didWarnInvalidHydration) { |
|
return; |
|
} |
|
|
|
didWarnInvalidHydration = true; |
|
|
|
error('Expected server HTML to contain a matching <%s> in <%s>.', tag, parentNode.nodeName.toLowerCase()); |
|
} |
|
} |
|
function warnForInsertedHydratedText(parentNode, text) { |
|
{ |
|
if (text === '') { |
|
// We expect to insert empty text nodes since they're not represented in |
|
// the HTML. |
|
// TODO: Remove this special case if we can just avoid inserting empty |
|
// text nodes. |
|
return; |
|
} |
|
|
|
if (didWarnInvalidHydration) { |
|
return; |
|
} |
|
|
|
didWarnInvalidHydration = true; |
|
|
|
error('Expected server HTML to contain a matching text node for "%s" in <%s>.', text, parentNode.nodeName.toLowerCase()); |
|
} |
|
} |
|
function restoreControlledState$3(domElement, tag, props) { |
|
switch (tag) { |
|
case 'input': |
|
restoreControlledState(domElement, props); |
|
return; |
|
|
|
case 'textarea': |
|
restoreControlledState$2(domElement, props); |
|
return; |
|
|
|
case 'select': |
|
restoreControlledState$1(domElement, props); |
|
return; |
|
} |
|
} |
|
|
|
var validateDOMNesting = function () {}; |
|
|
|
var updatedAncestorInfo = function () {}; |
|
|
|
{ |
|
// This validation code was written based on the HTML5 parsing spec: |
|
// https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope |
|
// |
|
// Note: this does not catch all invalid nesting, nor does it try to (as it's |
|
// not clear what practical benefit doing so provides); instead, we warn only |
|
// for cases where the parser will give a parse tree differing from what React |
|
// intended. For example, <b><div></div></b> is invalid but we don't warn |
|
// because it still parses correctly; we do warn for other cases like nested |
|
// <p> tags where the beginning of the second element implicitly closes the |
|
// first, causing a confusing mess. |
|
// https://html.spec.whatwg.org/multipage/syntax.html#special |
|
var specialTags = ['address', 'applet', 'area', 'article', 'aside', 'base', 'basefont', 'bgsound', 'blockquote', 'body', 'br', 'button', 'caption', 'center', 'col', 'colgroup', 'dd', 'details', 'dir', 'div', 'dl', 'dt', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'iframe', 'img', 'input', 'isindex', 'li', 'link', 'listing', 'main', 'marquee', 'menu', 'menuitem', 'meta', 'nav', 'noembed', 'noframes', 'noscript', 'object', 'ol', 'p', 'param', 'plaintext', 'pre', 'script', 'section', 'select', 'source', 'style', 'summary', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'title', 'tr', 'track', 'ul', 'wbr', 'xmp']; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-scope |
|
|
|
var inScopeTags = ['applet', 'caption', 'html', 'table', 'td', 'th', 'marquee', 'object', 'template', // https://html.spec.whatwg.org/multipage/syntax.html#html-integration-point |
|
// TODO: Distinguish by namespace here -- for <title>, including it here |
|
// errs on the side of fewer warnings |
|
'foreignObject', 'desc', 'title']; // https://html.spec.whatwg.org/multipage/syntax.html#has-an-element-in-button-scope |
|
|
|
var buttonScopeTags = inScopeTags.concat(['button']); // https://html.spec.whatwg.org/multipage/syntax.html#generate-implied-end-tags |
|
|
|
var impliedEndTags = ['dd', 'dt', 'li', 'option', 'optgroup', 'p', 'rp', 'rt']; |
|
var emptyAncestorInfo = { |
|
current: null, |
|
formTag: null, |
|
aTagInScope: null, |
|
buttonTagInScope: null, |
|
nobrTagInScope: null, |
|
pTagInButtonScope: null, |
|
listItemTagAutoclosing: null, |
|
dlItemTagAutoclosing: null |
|
}; |
|
|
|
updatedAncestorInfo = function (oldInfo, tag) { |
|
var ancestorInfo = assign({}, oldInfo || emptyAncestorInfo); |
|
|
|
var info = { |
|
tag: tag |
|
}; |
|
|
|
if (inScopeTags.indexOf(tag) !== -1) { |
|
ancestorInfo.aTagInScope = null; |
|
ancestorInfo.buttonTagInScope = null; |
|
ancestorInfo.nobrTagInScope = null; |
|
} |
|
|
|
if (buttonScopeTags.indexOf(tag) !== -1) { |
|
ancestorInfo.pTagInButtonScope = null; |
|
} // See rules for 'li', 'dd', 'dt' start tags in |
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody |
|
|
|
|
|
if (specialTags.indexOf(tag) !== -1 && tag !== 'address' && tag !== 'div' && tag !== 'p') { |
|
ancestorInfo.listItemTagAutoclosing = null; |
|
ancestorInfo.dlItemTagAutoclosing = null; |
|
} |
|
|
|
ancestorInfo.current = info; |
|
|
|
if (tag === 'form') { |
|
ancestorInfo.formTag = info; |
|
} |
|
|
|
if (tag === 'a') { |
|
ancestorInfo.aTagInScope = info; |
|
} |
|
|
|
if (tag === 'button') { |
|
ancestorInfo.buttonTagInScope = info; |
|
} |
|
|
|
if (tag === 'nobr') { |
|
ancestorInfo.nobrTagInScope = info; |
|
} |
|
|
|
if (tag === 'p') { |
|
ancestorInfo.pTagInButtonScope = info; |
|
} |
|
|
|
if (tag === 'li') { |
|
ancestorInfo.listItemTagAutoclosing = info; |
|
} |
|
|
|
if (tag === 'dd' || tag === 'dt') { |
|
ancestorInfo.dlItemTagAutoclosing = info; |
|
} |
|
|
|
return ancestorInfo; |
|
}; |
|
/** |
|
* Returns whether |
|
*/ |
|
|
|
|
|
var isTagValidWithParent = function (tag, parentTag) { |
|
// First, let's check if we're in an unusual parsing mode... |
|
switch (parentTag) { |
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inselect |
|
case 'select': |
|
return tag === 'option' || tag === 'optgroup' || tag === '#text'; |
|
|
|
case 'optgroup': |
|
return tag === 'option' || tag === '#text'; |
|
// Strictly speaking, seeing an <option> doesn't mean we're in a <select> |
|
// but |
|
|
|
case 'option': |
|
return tag === '#text'; |
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intd |
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incaption |
|
// No special behavior since these rules fall back to "in body" mode for |
|
// all except special table nodes which cause bad parsing behavior anyway. |
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intr |
|
|
|
case 'tr': |
|
return tag === 'th' || tag === 'td' || tag === 'style' || tag === 'script' || tag === 'template'; |
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intbody |
|
|
|
case 'tbody': |
|
case 'thead': |
|
case 'tfoot': |
|
return tag === 'tr' || tag === 'style' || tag === 'script' || tag === 'template'; |
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-incolgroup |
|
|
|
case 'colgroup': |
|
return tag === 'col' || tag === 'template'; |
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-intable |
|
|
|
case 'table': |
|
return tag === 'caption' || tag === 'colgroup' || tag === 'tbody' || tag === 'tfoot' || tag === 'thead' || tag === 'style' || tag === 'script' || tag === 'template'; |
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inhead |
|
|
|
case 'head': |
|
return tag === 'base' || tag === 'basefont' || tag === 'bgsound' || tag === 'link' || tag === 'meta' || tag === 'title' || tag === 'noscript' || tag === 'noframes' || tag === 'style' || tag === 'script' || tag === 'template'; |
|
// https://html.spec.whatwg.org/multipage/semantics.html#the-html-element |
|
|
|
case 'html': |
|
return tag === 'head' || tag === 'body' || tag === 'frameset'; |
|
|
|
case 'frameset': |
|
return tag === 'frame'; |
|
|
|
case '#document': |
|
return tag === 'html'; |
|
} // Probably in the "in body" parsing mode, so we outlaw only tag combos |
|
// where the parsing rules cause implicit opens or closes to be added. |
|
// https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inbody |
|
|
|
|
|
switch (tag) { |
|
case 'h1': |
|
case 'h2': |
|
case 'h3': |
|
case 'h4': |
|
case 'h5': |
|
case 'h6': |
|
return parentTag !== 'h1' && parentTag !== 'h2' && parentTag !== 'h3' && parentTag !== 'h4' && parentTag !== 'h5' && parentTag !== 'h6'; |
|
|
|
case 'rp': |
|
case 'rt': |
|
return impliedEndTags.indexOf(parentTag) === -1; |
|
|
|
case 'body': |
|
case 'caption': |
|
case 'col': |
|
case 'colgroup': |
|
case 'frameset': |
|
case 'frame': |
|
case 'head': |
|
case 'html': |
|
case 'tbody': |
|
case 'td': |
|
case 'tfoot': |
|
case 'th': |
|
case 'thead': |
|
case 'tr': |
|
// These tags are only valid with a few parents that have special child |
|
// parsing rules -- if we're down here, then none of those matched and |
|
// so we allow it only if we don't know what the parent is, as all other |
|
// cases are invalid. |
|
return parentTag == null; |
|
} |
|
|
|
return true; |
|
}; |
|
/** |
|
* Returns whether |
|
*/ |
|
|
|
|
|
var findInvalidAncestorForTag = function (tag, ancestorInfo) { |
|
switch (tag) { |
|
case 'address': |
|
case 'article': |
|
case 'aside': |
|
case 'blockquote': |
|
case 'center': |
|
case 'details': |
|
case 'dialog': |
|
case 'dir': |
|
case 'div': |
|
case 'dl': |
|
case 'fieldset': |
|
case 'figcaption': |
|
case 'figure': |
|
case 'footer': |
|
case 'header': |
|
case 'hgroup': |
|
case 'main': |
|
case 'menu': |
|
case 'nav': |
|
case 'ol': |
|
case 'p': |
|
case 'section': |
|
case 'summary': |
|
case 'ul': |
|
case 'pre': |
|
case 'listing': |
|
case 'table': |
|
case 'hr': |
|
case 'xmp': |
|
case 'h1': |
|
case 'h2': |
|
case 'h3': |
|
case 'h4': |
|
case 'h5': |
|
case 'h6': |
|
return ancestorInfo.pTagInButtonScope; |
|
|
|
case 'form': |
|
return ancestorInfo.formTag || ancestorInfo.pTagInButtonScope; |
|
|
|
case 'li': |
|
return ancestorInfo.listItemTagAutoclosing; |
|
|
|
case 'dd': |
|
case 'dt': |
|
return ancestorInfo.dlItemTagAutoclosing; |
|
|
|
case 'button': |
|
return ancestorInfo.buttonTagInScope; |
|
|
|
case 'a': |
|
// Spec says something about storing a list of markers, but it sounds |
|
// equivalent to this check. |
|
return ancestorInfo.aTagInScope; |
|
|
|
case 'nobr': |
|
return ancestorInfo.nobrTagInScope; |
|
} |
|
|
|
return null; |
|
}; |
|
|
|
var didWarn$1 = {}; |
|
|
|
validateDOMNesting = function (childTag, childText, ancestorInfo) { |
|
ancestorInfo = ancestorInfo || emptyAncestorInfo; |
|
var parentInfo = ancestorInfo.current; |
|
var parentTag = parentInfo && parentInfo.tag; |
|
|
|
if (childText != null) { |
|
if (childTag != null) { |
|
error('validateDOMNesting: when childText is passed, childTag should be null'); |
|
} |
|
|
|
childTag = '#text'; |
|
} |
|
|
|
var invalidParent = isTagValidWithParent(childTag, parentTag) ? null : parentInfo; |
|
var invalidAncestor = invalidParent ? null : findInvalidAncestorForTag(childTag, ancestorInfo); |
|
var invalidParentOrAncestor = invalidParent || invalidAncestor; |
|
|
|
if (!invalidParentOrAncestor) { |
|
return; |
|
} |
|
|
|
var ancestorTag = invalidParentOrAncestor.tag; |
|
var warnKey = !!invalidParent + '|' + childTag + '|' + ancestorTag; |
|
|
|
if (didWarn$1[warnKey]) { |
|
return; |
|
} |
|
|
|
didWarn$1[warnKey] = true; |
|
var tagDisplayName = childTag; |
|
var whitespaceInfo = ''; |
|
|
|
if (childTag === '#text') { |
|
if (/\S/.test(childText)) { |
|
tagDisplayName = 'Text nodes'; |
|
} else { |
|
tagDisplayName = 'Whitespace text nodes'; |
|
whitespaceInfo = " Make sure you don't have any extra whitespace between tags on " + 'each line of your source code.'; |
|
} |
|
} else { |
|
tagDisplayName = '<' + childTag + '>'; |
|
} |
|
|
|
if (invalidParent) { |
|
var info = ''; |
|
|
|
if (ancestorTag === 'table' && childTag === 'tr') { |
|
info += ' Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by ' + 'the browser.'; |
|
} |
|
|
|
error('validateDOMNesting(...): %s cannot appear as a child of <%s>.%s%s', tagDisplayName, ancestorTag, whitespaceInfo, info); |
|
} else { |
|
error('validateDOMNesting(...): %s cannot appear as a descendant of ' + '<%s>.', tagDisplayName, ancestorTag); |
|
} |
|
}; |
|
} |
|
|
|
var SUPPRESS_HYDRATION_WARNING$1 = 'suppressHydrationWarning'; |
|
var SUSPENSE_START_DATA = '$'; |
|
var SUSPENSE_END_DATA = '/$'; |
|
var SUSPENSE_PENDING_START_DATA = '$?'; |
|
var SUSPENSE_FALLBACK_START_DATA = '$!'; |
|
var STYLE$1 = 'style'; |
|
var eventsEnabled = null; |
|
var selectionInformation = null; |
|
function getRootHostContext(rootContainerInstance) { |
|
var type; |
|
var namespace; |
|
var nodeType = rootContainerInstance.nodeType; |
|
|
|
switch (nodeType) { |
|
case DOCUMENT_NODE: |
|
case DOCUMENT_FRAGMENT_NODE: |
|
{ |
|
type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment'; |
|
var root = rootContainerInstance.documentElement; |
|
namespace = root ? root.namespaceURI : getChildNamespace(null, ''); |
|
break; |
|
} |
|
|
|
default: |
|
{ |
|
var container = nodeType === COMMENT_NODE ? rootContainerInstance.parentNode : rootContainerInstance; |
|
var ownNamespace = container.namespaceURI || null; |
|
type = container.tagName; |
|
namespace = getChildNamespace(ownNamespace, type); |
|
break; |
|
} |
|
} |
|
|
|
{ |
|
var validatedTag = type.toLowerCase(); |
|
var ancestorInfo = updatedAncestorInfo(null, validatedTag); |
|
return { |
|
namespace: namespace, |
|
ancestorInfo: ancestorInfo |
|
}; |
|
} |
|
} |
|
function getChildHostContext(parentHostContext, type, rootContainerInstance) { |
|
{ |
|
var parentHostContextDev = parentHostContext; |
|
var namespace = getChildNamespace(parentHostContextDev.namespace, type); |
|
var ancestorInfo = updatedAncestorInfo(parentHostContextDev.ancestorInfo, type); |
|
return { |
|
namespace: namespace, |
|
ancestorInfo: ancestorInfo |
|
}; |
|
} |
|
} |
|
function getPublicInstance(instance) { |
|
return instance; |
|
} |
|
function prepareForCommit(containerInfo) { |
|
eventsEnabled = isEnabled(); |
|
selectionInformation = getSelectionInformation(); |
|
var activeInstance = null; |
|
|
|
setEnabled(false); |
|
return activeInstance; |
|
} |
|
function resetAfterCommit(containerInfo) { |
|
restoreSelection(selectionInformation); |
|
setEnabled(eventsEnabled); |
|
eventsEnabled = null; |
|
selectionInformation = null; |
|
} |
|
function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) { |
|
var parentNamespace; |
|
|
|
{ |
|
// TODO: take namespace into account when validating. |
|
var hostContextDev = hostContext; |
|
validateDOMNesting(type, null, hostContextDev.ancestorInfo); |
|
|
|
if (typeof props.children === 'string' || typeof props.children === 'number') { |
|
var string = '' + props.children; |
|
var ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo, type); |
|
validateDOMNesting(null, string, ownAncestorInfo); |
|
} |
|
|
|
parentNamespace = hostContextDev.namespace; |
|
} |
|
|
|
var domElement = createElement(type, props, rootContainerInstance, parentNamespace); |
|
precacheFiberNode(internalInstanceHandle, domElement); |
|
updateFiberProps(domElement, props); |
|
return domElement; |
|
} |
|
function appendInitialChild(parentInstance, child) { |
|
parentInstance.appendChild(child); |
|
} |
|
function finalizeInitialChildren(domElement, type, props, rootContainerInstance, hostContext) { |
|
setInitialProperties(domElement, type, props, rootContainerInstance); |
|
|
|
switch (type) { |
|
case 'button': |
|
case 'input': |
|
case 'select': |
|
case 'textarea': |
|
return !!props.autoFocus; |
|
|
|
case 'img': |
|
return true; |
|
|
|
default: |
|
return false; |
|
} |
|
} |
|
function prepareUpdate(domElement, type, oldProps, newProps, rootContainerInstance, hostContext) { |
|
{ |
|
var hostContextDev = hostContext; |
|
|
|
if (typeof newProps.children !== typeof oldProps.children && (typeof newProps.children === 'string' || typeof newProps.children === 'number')) { |
|
var string = '' + newProps.children; |
|
var ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo, type); |
|
validateDOMNesting(null, string, ownAncestorInfo); |
|
} |
|
} |
|
|
|
return diffProperties(domElement, type, oldProps, newProps); |
|
} |
|
function shouldSetTextContent(type, props) { |
|
return type === 'textarea' || type === 'noscript' || typeof props.children === 'string' || typeof props.children === 'number' || typeof props.dangerouslySetInnerHTML === 'object' && props.dangerouslySetInnerHTML !== null && props.dangerouslySetInnerHTML.__html != null; |
|
} |
|
function createTextInstance(text, rootContainerInstance, hostContext, internalInstanceHandle) { |
|
{ |
|
var hostContextDev = hostContext; |
|
validateDOMNesting(null, text, hostContextDev.ancestorInfo); |
|
} |
|
|
|
var textNode = createTextNode(text, rootContainerInstance); |
|
precacheFiberNode(internalInstanceHandle, textNode); |
|
return textNode; |
|
} |
|
function getCurrentEventPriority() { |
|
var currentEvent = window.event; |
|
|
|
if (currentEvent === undefined) { |
|
return DefaultEventPriority; |
|
} |
|
|
|
return getEventPriority(currentEvent.type); |
|
} |
|
// if a component just imports ReactDOM (e.g. for findDOMNode). |
|
// Some environments might not have setTimeout or clearTimeout. |
|
|
|
var scheduleTimeout = typeof setTimeout === 'function' ? setTimeout : undefined; |
|
var cancelTimeout = typeof clearTimeout === 'function' ? clearTimeout : undefined; |
|
var noTimeout = -1; |
|
var localPromise = typeof Promise === 'function' ? Promise : undefined; // ------------------- |
|
var scheduleMicrotask = typeof queueMicrotask === 'function' ? queueMicrotask : typeof localPromise !== 'undefined' ? function (callback) { |
|
return localPromise.resolve(null).then(callback).catch(handleErrorInNextTick); |
|
} : scheduleTimeout; // TODO: Determine the best fallback here. |
|
|
|
function handleErrorInNextTick(error) { |
|
setTimeout(function () { |
|
throw error; |
|
}); |
|
} // ------------------- |
|
function commitMount(domElement, type, newProps, internalInstanceHandle) { |
|
// Despite the naming that might imply otherwise, this method only |
|
// fires if there is an `Update` effect scheduled during mounting. |
|
// This happens if `finalizeInitialChildren` returns `true` (which it |
|
// does to implement the `autoFocus` attribute on the client). But |
|
// there are also other cases when this might happen (such as patching |
|
// up text content during hydration mismatch). So we'll check this again. |
|
switch (type) { |
|
case 'button': |
|
case 'input': |
|
case 'select': |
|
case 'textarea': |
|
if (newProps.autoFocus) { |
|
domElement.focus(); |
|
} |
|
|
|
return; |
|
|
|
case 'img': |
|
{ |
|
if (newProps.src) { |
|
domElement.src = newProps.src; |
|
} |
|
|
|
return; |
|
} |
|
} |
|
} |
|
function commitUpdate(domElement, updatePayload, type, oldProps, newProps, internalInstanceHandle) { |
|
// Apply the diff to the DOM node. |
|
updateProperties(domElement, updatePayload, type, oldProps, newProps); // Update the props handle so that we know which props are the ones with |
|
// with current event handlers. |
|
|
|
updateFiberProps(domElement, newProps); |
|
} |
|
function resetTextContent(domElement) { |
|
setTextContent(domElement, ''); |
|
} |
|
function commitTextUpdate(textInstance, oldText, newText) { |
|
textInstance.nodeValue = newText; |
|
} |
|
function appendChild(parentInstance, child) { |
|
parentInstance.appendChild(child); |
|
} |
|
function appendChildToContainer(container, child) { |
|
var parentNode; |
|
|
|
if (container.nodeType === COMMENT_NODE) { |
|
parentNode = container.parentNode; |
|
parentNode.insertBefore(child, container); |
|
} else { |
|
parentNode = container; |
|
parentNode.appendChild(child); |
|
} // This container might be used for a portal. |
|
// If something inside a portal is clicked, that click should bubble |
|
// through the React tree. However, on Mobile Safari the click would |
|
// never bubble through the *DOM* tree unless an ancestor with onclick |
|
// event exists. So we wouldn't see it and dispatch it. |
|
// This is why we ensure that non React root containers have inline onclick |
|
// defined. |
|
// https://github.com/facebook/react/issues/11918 |
|
|
|
|
|
var reactRootContainer = container._reactRootContainer; |
|
|
|
if ((reactRootContainer === null || reactRootContainer === undefined) && parentNode.onclick === null) { |
|
// TODO: This cast may not be sound for SVG, MathML or custom elements. |
|
trapClickOnNonInteractiveElement(parentNode); |
|
} |
|
} |
|
function insertBefore(parentInstance, child, beforeChild) { |
|
parentInstance.insertBefore(child, beforeChild); |
|
} |
|
function insertInContainerBefore(container, child, beforeChild) { |
|
if (container.nodeType === COMMENT_NODE) { |
|
container.parentNode.insertBefore(child, beforeChild); |
|
} else { |
|
container.insertBefore(child, beforeChild); |
|
} |
|
} |
|
|
|
function removeChild(parentInstance, child) { |
|
parentInstance.removeChild(child); |
|
} |
|
function removeChildFromContainer(container, child) { |
|
if (container.nodeType === COMMENT_NODE) { |
|
container.parentNode.removeChild(child); |
|
} else { |
|
container.removeChild(child); |
|
} |
|
} |
|
function clearSuspenseBoundary(parentInstance, suspenseInstance) { |
|
var node = suspenseInstance; // Delete all nodes within this suspense boundary. |
|
// There might be nested nodes so we need to keep track of how |
|
// deep we are and only break out when we're back on top. |
|
|
|
var depth = 0; |
|
|
|
do { |
|
var nextNode = node.nextSibling; |
|
parentInstance.removeChild(node); |
|
|
|
if (nextNode && nextNode.nodeType === COMMENT_NODE) { |
|
var data = nextNode.data; |
|
|
|
if (data === SUSPENSE_END_DATA) { |
|
if (depth === 0) { |
|
parentInstance.removeChild(nextNode); // Retry if any event replaying was blocked on this. |
|
|
|
retryIfBlockedOn(suspenseInstance); |
|
return; |
|
} else { |
|
depth--; |
|
} |
|
} else if (data === SUSPENSE_START_DATA || data === SUSPENSE_PENDING_START_DATA || data === SUSPENSE_FALLBACK_START_DATA) { |
|
depth++; |
|
} |
|
} |
|
|
|
node = nextNode; |
|
} while (node); // TODO: Warn, we didn't find the end comment boundary. |
|
// Retry if any event replaying was blocked on this. |
|
|
|
|
|
retryIfBlockedOn(suspenseInstance); |
|
} |
|
function clearSuspenseBoundaryFromContainer(container, suspenseInstance) { |
|
if (container.nodeType === COMMENT_NODE) { |
|
clearSuspenseBoundary(container.parentNode, suspenseInstance); |
|
} else if (container.nodeType === ELEMENT_NODE) { |
|
clearSuspenseBoundary(container, suspenseInstance); |
|
} // Retry if any event replaying was blocked on this. |
|
|
|
|
|
retryIfBlockedOn(container); |
|
} |
|
function hideInstance(instance) { |
|
// TODO: Does this work for all element types? What about MathML? Should we |
|
// pass host context to this method? |
|
instance = instance; |
|
var style = instance.style; |
|
|
|
if (typeof style.setProperty === 'function') { |
|
style.setProperty('display', 'none', 'important'); |
|
} else { |
|
style.display = 'none'; |
|
} |
|
} |
|
function hideTextInstance(textInstance) { |
|
textInstance.nodeValue = ''; |
|
} |
|
function unhideInstance(instance, props) { |
|
instance = instance; |
|
var styleProp = props[STYLE$1]; |
|
var display = styleProp !== undefined && styleProp !== null && styleProp.hasOwnProperty('display') ? styleProp.display : null; |
|
instance.style.display = dangerousStyleValue('display', display); |
|
} |
|
function unhideTextInstance(textInstance, text) { |
|
textInstance.nodeValue = text; |
|
} |
|
function clearContainer(container) { |
|
if (container.nodeType === ELEMENT_NODE) { |
|
container.textContent = ''; |
|
} else if (container.nodeType === DOCUMENT_NODE) { |
|
if (container.documentElement) { |
|
container.removeChild(container.documentElement); |
|
} |
|
} |
|
} // ------------------- |
|
function canHydrateInstance(instance, type, props) { |
|
if (instance.nodeType !== ELEMENT_NODE || type.toLowerCase() !== instance.nodeName.toLowerCase()) { |
|
return null; |
|
} // This has now been refined to an element node. |
|
|
|
|
|
return instance; |
|
} |
|
function canHydrateTextInstance(instance, text) { |
|
if (text === '' || instance.nodeType !== TEXT_NODE) { |
|
// Empty strings are not parsed by HTML so there won't be a correct match here. |
|
return null; |
|
} // This has now been refined to a text node. |
|
|
|
|
|
return instance; |
|
} |
|
function canHydrateSuspenseInstance(instance) { |
|
if (instance.nodeType !== COMMENT_NODE) { |
|
// Empty strings are not parsed by HTML so there won't be a correct match here. |
|
return null; |
|
} // This has now been refined to a suspense node. |
|
|
|
|
|
return instance; |
|
} |
|
function isSuspenseInstancePending(instance) { |
|
return instance.data === SUSPENSE_PENDING_START_DATA; |
|
} |
|
function isSuspenseInstanceFallback(instance) { |
|
return instance.data === SUSPENSE_FALLBACK_START_DATA; |
|
} |
|
function getSuspenseInstanceFallbackErrorDetails(instance) { |
|
var dataset = instance.nextSibling && instance.nextSibling.dataset; |
|
var digest, message, stack; |
|
|
|
if (dataset) { |
|
digest = dataset.dgst; |
|
|
|
{ |
|
message = dataset.msg; |
|
stack = dataset.stck; |
|
} |
|
} |
|
|
|
{ |
|
return { |
|
message: message, |
|
digest: digest, |
|
stack: stack |
|
}; |
|
} // let value = {message: undefined, hash: undefined}; |
|
// const nextSibling = instance.nextSibling; |
|
// if (nextSibling) { |
|
// const dataset = ((nextSibling: any): HTMLTemplateElement).dataset; |
|
// value.message = dataset.msg; |
|
// value.hash = dataset.hash; |
|
// if (true) { |
|
// value.stack = dataset.stack; |
|
// } |
|
// } |
|
// return value; |
|
|
|
} |
|
function registerSuspenseInstanceRetry(instance, callback) { |
|
instance._reactRetry = callback; |
|
} |
|
|
|
function getNextHydratable(node) { |
|
// Skip non-hydratable nodes. |
|
for (; node != null; node = node.nextSibling) { |
|
var nodeType = node.nodeType; |
|
|
|
if (nodeType === ELEMENT_NODE || nodeType === TEXT_NODE) { |
|
break; |
|
} |
|
|
|
if (nodeType === COMMENT_NODE) { |
|
var nodeData = node.data; |
|
|
|
if (nodeData === SUSPENSE_START_DATA || nodeData === SUSPENSE_FALLBACK_START_DATA || nodeData === SUSPENSE_PENDING_START_DATA) { |
|
break; |
|
} |
|
|
|
if (nodeData === SUSPENSE_END_DATA) { |
|
return null; |
|
} |
|
} |
|
} |
|
|
|
return node; |
|
} |
|
|
|
function getNextHydratableSibling(instance) { |
|
return getNextHydratable(instance.nextSibling); |
|
} |
|
function getFirstHydratableChild(parentInstance) { |
|
return getNextHydratable(parentInstance.firstChild); |
|
} |
|
function getFirstHydratableChildWithinContainer(parentContainer) { |
|
return getNextHydratable(parentContainer.firstChild); |
|
} |
|
function getFirstHydratableChildWithinSuspenseInstance(parentInstance) { |
|
return getNextHydratable(parentInstance.nextSibling); |
|
} |
|
function hydrateInstance(instance, type, props, rootContainerInstance, hostContext, internalInstanceHandle, shouldWarnDev) { |
|
precacheFiberNode(internalInstanceHandle, instance); // TODO: Possibly defer this until the commit phase where all the events |
|
// get attached. |
|
|
|
updateFiberProps(instance, props); |
|
var parentNamespace; |
|
|
|
{ |
|
var hostContextDev = hostContext; |
|
parentNamespace = hostContextDev.namespace; |
|
} // TODO: Temporary hack to check if we're in a concurrent root. We can delete |
|
// when the legacy root API is removed. |
|
|
|
|
|
var isConcurrentMode = (internalInstanceHandle.mode & ConcurrentMode) !== NoMode; |
|
return diffHydratedProperties(instance, type, props, parentNamespace, rootContainerInstance, isConcurrentMode, shouldWarnDev); |
|
} |
|
function hydrateTextInstance(textInstance, text, internalInstanceHandle, shouldWarnDev) { |
|
precacheFiberNode(internalInstanceHandle, textInstance); // TODO: Temporary hack to check if we're in a concurrent root. We can delete |
|
// when the legacy root API is removed. |
|
|
|
var isConcurrentMode = (internalInstanceHandle.mode & ConcurrentMode) !== NoMode; |
|
return diffHydratedText(textInstance, text); |
|
} |
|
function hydrateSuspenseInstance(suspenseInstance, internalInstanceHandle) { |
|
precacheFiberNode(internalInstanceHandle, suspenseInstance); |
|
} |
|
function getNextHydratableInstanceAfterSuspenseInstance(suspenseInstance) { |
|
var node = suspenseInstance.nextSibling; // Skip past all nodes within this suspense boundary. |
|
// There might be nested nodes so we need to keep track of how |
|
// deep we are and only break out when we're back on top. |
|
|
|
var depth = 0; |
|
|
|
while (node) { |
|
if (node.nodeType === COMMENT_NODE) { |
|
var data = node.data; |
|
|
|
if (data === SUSPENSE_END_DATA) { |
|
if (depth === 0) { |
|
return getNextHydratableSibling(node); |
|
} else { |
|
depth--; |
|
} |
|
} else if (data === SUSPENSE_START_DATA || data === SUSPENSE_FALLBACK_START_DATA || data === SUSPENSE_PENDING_START_DATA) { |
|
depth++; |
|
} |
|
} |
|
|
|
node = node.nextSibling; |
|
} // TODO: Warn, we didn't find the end comment boundary. |
|
|
|
|
|
return null; |
|
} // Returns the SuspenseInstance if this node is a direct child of a |
|
// SuspenseInstance. I.e. if its previous sibling is a Comment with |
|
// SUSPENSE_x_START_DATA. Otherwise, null. |
|
|
|
function getParentSuspenseInstance(targetInstance) { |
|
var node = targetInstance.previousSibling; // Skip past all nodes within this suspense boundary. |
|
// There might be nested nodes so we need to keep track of how |
|
// deep we are and only break out when we're back on top. |
|
|
|
var depth = 0; |
|
|
|
while (node) { |
|
if (node.nodeType === COMMENT_NODE) { |
|
var data = node.data; |
|
|
|
if (data === SUSPENSE_START_DATA || data === SUSPENSE_FALLBACK_START_DATA || data === SUSPENSE_PENDING_START_DATA) { |
|
if (depth === 0) { |
|
return node; |
|
} else { |
|
depth--; |
|
} |
|
} else if (data === SUSPENSE_END_DATA) { |
|
depth++; |
|
} |
|
} |
|
|
|
node = node.previousSibling; |
|
} |
|
|
|
return null; |
|
} |
|
function commitHydratedContainer(container) { |
|
// Retry if any event replaying was blocked on this. |
|
retryIfBlockedOn(container); |
|
} |
|
function commitHydratedSuspenseInstance(suspenseInstance) { |
|
// Retry if any event replaying was blocked on this. |
|
retryIfBlockedOn(suspenseInstance); |
|
} |
|
function shouldDeleteUnhydratedTailInstances(parentType) { |
|
return parentType !== 'head' && parentType !== 'body'; |
|
} |
|
function didNotMatchHydratedContainerTextInstance(parentContainer, textInstance, text, isConcurrentMode) { |
|
var shouldWarnDev = true; |
|
checkForUnmatchedText(textInstance.nodeValue, text, isConcurrentMode, shouldWarnDev); |
|
} |
|
function didNotMatchHydratedTextInstance(parentType, parentProps, parentInstance, textInstance, text, isConcurrentMode) { |
|
if (parentProps[SUPPRESS_HYDRATION_WARNING$1] !== true) { |
|
var shouldWarnDev = true; |
|
checkForUnmatchedText(textInstance.nodeValue, text, isConcurrentMode, shouldWarnDev); |
|
} |
|
} |
|
function didNotHydrateInstanceWithinContainer(parentContainer, instance) { |
|
{ |
|
if (instance.nodeType === ELEMENT_NODE) { |
|
warnForDeletedHydratableElement(parentContainer, instance); |
|
} else if (instance.nodeType === COMMENT_NODE) ; else { |
|
warnForDeletedHydratableText(parentContainer, instance); |
|
} |
|
} |
|
} |
|
function didNotHydrateInstanceWithinSuspenseInstance(parentInstance, instance) { |
|
{ |
|
// $FlowFixMe: Only Element or Document can be parent nodes. |
|
var parentNode = parentInstance.parentNode; |
|
|
|
if (parentNode !== null) { |
|
if (instance.nodeType === ELEMENT_NODE) { |
|
warnForDeletedHydratableElement(parentNode, instance); |
|
} else if (instance.nodeType === COMMENT_NODE) ; else { |
|
warnForDeletedHydratableText(parentNode, instance); |
|
} |
|
} |
|
} |
|
} |
|
function didNotHydrateInstance(parentType, parentProps, parentInstance, instance, isConcurrentMode) { |
|
{ |
|
if (isConcurrentMode || parentProps[SUPPRESS_HYDRATION_WARNING$1] !== true) { |
|
if (instance.nodeType === ELEMENT_NODE) { |
|
warnForDeletedHydratableElement(parentInstance, instance); |
|
} else if (instance.nodeType === COMMENT_NODE) ; else { |
|
warnForDeletedHydratableText(parentInstance, instance); |
|
} |
|
} |
|
} |
|
} |
|
function didNotFindHydratableInstanceWithinContainer(parentContainer, type, props) { |
|
{ |
|
warnForInsertedHydratedElement(parentContainer, type); |
|
} |
|
} |
|
function didNotFindHydratableTextInstanceWithinContainer(parentContainer, text) { |
|
{ |
|
warnForInsertedHydratedText(parentContainer, text); |
|
} |
|
} |
|
function didNotFindHydratableInstanceWithinSuspenseInstance(parentInstance, type, props) { |
|
{ |
|
// $FlowFixMe: Only Element or Document can be parent nodes. |
|
var parentNode = parentInstance.parentNode; |
|
if (parentNode !== null) warnForInsertedHydratedElement(parentNode, type); |
|
} |
|
} |
|
function didNotFindHydratableTextInstanceWithinSuspenseInstance(parentInstance, text) { |
|
{ |
|
// $FlowFixMe: Only Element or Document can be parent nodes. |
|
var parentNode = parentInstance.parentNode; |
|
if (parentNode !== null) warnForInsertedHydratedText(parentNode, text); |
|
} |
|
} |
|
function didNotFindHydratableInstance(parentType, parentProps, parentInstance, type, props, isConcurrentMode) { |
|
{ |
|
if (isConcurrentMode || parentProps[SUPPRESS_HYDRATION_WARNING$1] !== true) { |
|
warnForInsertedHydratedElement(parentInstance, type); |
|
} |
|
} |
|
} |
|
function didNotFindHydratableTextInstance(parentType, parentProps, parentInstance, text, isConcurrentMode) { |
|
{ |
|
if (isConcurrentMode || parentProps[SUPPRESS_HYDRATION_WARNING$1] !== true) { |
|
warnForInsertedHydratedText(parentInstance, text); |
|
} |
|
} |
|
} |
|
function errorHydratingContainer(parentContainer) { |
|
{ |
|
// TODO: This gets logged by onRecoverableError, too, so we should be |
|
// able to remove it. |
|
error('An error occurred during hydration. The server HTML was replaced with client content in <%s>.', parentContainer.nodeName.toLowerCase()); |
|
} |
|
} |
|
function preparePortalMount(portalInstance) { |
|
listenToAllSupportedEvents(portalInstance); |
|
} |
|
|
|
var randomKey = Math.random().toString(36).slice(2); |
|
var internalInstanceKey = '__reactFiber$' + randomKey; |
|
var internalPropsKey = '__reactProps$' + randomKey; |
|
var internalContainerInstanceKey = '__reactContainer$' + randomKey; |
|
var internalEventHandlersKey = '__reactEvents$' + randomKey; |
|
var internalEventHandlerListenersKey = '__reactListeners$' + randomKey; |
|
var internalEventHandlesSetKey = '__reactHandles$' + randomKey; |
|
function detachDeletedInstance(node) { |
|
// TODO: This function is only called on host components. I don't think all of |
|
// these fields are relevant. |
|
delete node[internalInstanceKey]; |
|
delete node[internalPropsKey]; |
|
delete node[internalEventHandlersKey]; |
|
delete node[internalEventHandlerListenersKey]; |
|
delete node[internalEventHandlesSetKey]; |
|
} |
|
function precacheFiberNode(hostInst, node) { |
|
node[internalInstanceKey] = hostInst; |
|
} |
|
function markContainerAsRoot(hostRoot, node) { |
|
node[internalContainerInstanceKey] = hostRoot; |
|
} |
|
function unmarkContainerAsRoot(node) { |
|
node[internalContainerInstanceKey] = null; |
|
} |
|
function isContainerMarkedAsRoot(node) { |
|
return !!node[internalContainerInstanceKey]; |
|
} // Given a DOM node, return the closest HostComponent or HostText fiber ancestor. |
|
// If the target node is part of a hydrated or not yet rendered subtree, then |
|
// this may also return a SuspenseComponent or HostRoot to indicate that. |
|
// Conceptually the HostRoot fiber is a child of the Container node. So if you |
|
// pass the Container node as the targetNode, you will not actually get the |
|
// HostRoot back. To get to the HostRoot, you need to pass a child of it. |
|
// The same thing applies to Suspense boundaries. |
|
|
|
function getClosestInstanceFromNode(targetNode) { |
|
var targetInst = targetNode[internalInstanceKey]; |
|
|
|
if (targetInst) { |
|
// Don't return HostRoot or SuspenseComponent here. |
|
return targetInst; |
|
} // If the direct event target isn't a React owned DOM node, we need to look |
|
// to see if one of its parents is a React owned DOM node. |
|
|
|
|
|
var parentNode = targetNode.parentNode; |
|
|
|
while (parentNode) { |
|
// We'll check if this is a container root that could include |
|
// React nodes in the future. We need to check this first because |
|
// if we're a child of a dehydrated container, we need to first |
|
// find that inner container before moving on to finding the parent |
|
// instance. Note that we don't check this field on the targetNode |
|
// itself because the fibers are conceptually between the container |
|
// node and the first child. It isn't surrounding the container node. |
|
// If it's not a container, we check if it's an instance. |
|
targetInst = parentNode[internalContainerInstanceKey] || parentNode[internalInstanceKey]; |
|
|
|
if (targetInst) { |
|
// Since this wasn't the direct target of the event, we might have |
|
// stepped past dehydrated DOM nodes to get here. However they could |
|
// also have been non-React nodes. We need to answer which one. |
|
// If we the instance doesn't have any children, then there can't be |
|
// a nested suspense boundary within it. So we can use this as a fast |
|
// bailout. Most of the time, when people add non-React children to |
|
// the tree, it is using a ref to a child-less DOM node. |
|
// Normally we'd only need to check one of the fibers because if it |
|
// has ever gone from having children to deleting them or vice versa |
|
// it would have deleted the dehydrated boundary nested inside already. |
|
// However, since the HostRoot starts out with an alternate it might |
|
// have one on the alternate so we need to check in case this was a |
|
// root. |
|
var alternate = targetInst.alternate; |
|
|
|
if (targetInst.child !== null || alternate !== null && alternate.child !== null) { |
|
// Next we need to figure out if the node that skipped past is |
|
// nested within a dehydrated boundary and if so, which one. |
|
var suspenseInstance = getParentSuspenseInstance(targetNode); |
|
|
|
while (suspenseInstance !== null) { |
|
// We found a suspense instance. That means that we haven't |
|
// hydrated it yet. Even though we leave the comments in the |
|
// DOM after hydrating, and there are boundaries in the DOM |
|
// that could already be hydrated, we wouldn't have found them |
|
// through this pass since if the target is hydrated it would |
|
// have had an internalInstanceKey on it. |
|
// Let's get the fiber associated with the SuspenseComponent |
|
// as the deepest instance. |
|
var targetSuspenseInst = suspenseInstance[internalInstanceKey]; |
|
|
|
if (targetSuspenseInst) { |
|
return targetSuspenseInst; |
|
} // If we don't find a Fiber on the comment, it might be because |
|
// we haven't gotten to hydrate it yet. There might still be a |
|
// parent boundary that hasn't above this one so we need to find |
|
// the outer most that is known. |
|
|
|
|
|
suspenseInstance = getParentSuspenseInstance(suspenseInstance); // If we don't find one, then that should mean that the parent |
|
// host component also hasn't hydrated yet. We can return it |
|
// below since it will bail out on the isMounted check later. |
|
} |
|
} |
|
|
|
return targetInst; |
|
} |
|
|
|
targetNode = parentNode; |
|
parentNode = targetNode.parentNode; |
|
} |
|
|
|
return null; |
|
} |
|
/** |
|
* Given a DOM node, return the ReactDOMComponent or ReactDOMTextComponent |
|
* instance, or null if the node was not rendered by this React. |
|
*/ |
|
|
|
function getInstanceFromNode(node) { |
|
var inst = node[internalInstanceKey] || node[internalContainerInstanceKey]; |
|
|
|
if (inst) { |
|
if (inst.tag === HostComponent || inst.tag === HostText || inst.tag === SuspenseComponent || inst.tag === HostRoot) { |
|
return inst; |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
/** |
|
* Given a ReactDOMComponent or ReactDOMTextComponent, return the corresponding |
|
* DOM node. |
|
*/ |
|
|
|
function getNodeFromInstance(inst) { |
|
if (inst.tag === HostComponent || inst.tag === HostText) { |
|
// In Fiber this, is just the state node right now. We assume it will be |
|
// a host component or host text. |
|
return inst.stateNode; |
|
} // Without this first invariant, passing a non-DOM-component triggers the next |
|
// invariant for a missing parent, which is super confusing. |
|
|
|
|
|
throw new Error('getNodeFromInstance: Invalid argument.'); |
|
} |
|
function getFiberCurrentPropsFromNode(node) { |
|
return node[internalPropsKey] || null; |
|
} |
|
function updateFiberProps(node, props) { |
|
node[internalPropsKey] = props; |
|
} |
|
function getEventListenerSet(node) { |
|
var elementListenerSet = node[internalEventHandlersKey]; |
|
|
|
if (elementListenerSet === undefined) { |
|
elementListenerSet = node[internalEventHandlersKey] = new Set(); |
|
} |
|
|
|
return elementListenerSet; |
|
} |
|
|
|
var loggedTypeFailures = {}; |
|
var ReactDebugCurrentFrame$1 = ReactSharedInternals.ReactDebugCurrentFrame; |
|
|
|
function setCurrentlyValidatingElement(element) { |
|
{ |
|
if (element) { |
|
var owner = element._owner; |
|
var stack = describeUnknownElementTypeFrameInDEV(element.type, element._source, owner ? owner.type : null); |
|
ReactDebugCurrentFrame$1.setExtraStackFrame(stack); |
|
} else { |
|
ReactDebugCurrentFrame$1.setExtraStackFrame(null); |
|
} |
|
} |
|
} |
|
|
|
function checkPropTypes(typeSpecs, values, location, componentName, element) { |
|
{ |
|
// $FlowFixMe This is okay but Flow doesn't know it. |
|
var has = Function.call.bind(hasOwnProperty); |
|
|
|
for (var typeSpecName in typeSpecs) { |
|
if (has(typeSpecs, typeSpecName)) { |
|
var error$1 = void 0; // Prop type validation may throw. In case they do, we don't want to |
|
// fail the render phase where it didn't fail before. So we log it. |
|
// After these have been cleaned up, we'll let them throw. |
|
|
|
try { |
|
// This is intentionally an invariant that gets caught. It's the same |
|
// behavior as without this statement except with a better message. |
|
if (typeof typeSpecs[typeSpecName] !== 'function') { |
|
// eslint-disable-next-line react-internal/prod-error-codes |
|
var err = Error((componentName || 'React class') + ': ' + location + ' type `' + typeSpecName + '` is invalid; ' + 'it must be a function, usually from the `prop-types` package, but received `' + typeof typeSpecs[typeSpecName] + '`.' + 'This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.'); |
|
err.name = 'Invariant Violation'; |
|
throw err; |
|
} |
|
|
|
error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED'); |
|
} catch (ex) { |
|
error$1 = ex; |
|
} |
|
|
|
if (error$1 && !(error$1 instanceof Error)) { |
|
setCurrentlyValidatingElement(element); |
|
|
|
error('%s: type specification of %s' + ' `%s` is invalid; the type checker ' + 'function must return `null` or an `Error` but returned a %s. ' + 'You may have forgotten to pass an argument to the type checker ' + 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' + 'shape all require an argument).', componentName || 'React class', location, typeSpecName, typeof error$1); |
|
|
|
setCurrentlyValidatingElement(null); |
|
} |
|
|
|
if (error$1 instanceof Error && !(error$1.message in loggedTypeFailures)) { |
|
// Only monitor this failure once because there tends to be a lot of the |
|
// same error. |
|
loggedTypeFailures[error$1.message] = true; |
|
setCurrentlyValidatingElement(element); |
|
|
|
error('Failed %s type: %s', location, error$1.message); |
|
|
|
setCurrentlyValidatingElement(null); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
var valueStack = []; |
|
var fiberStack; |
|
|
|
{ |
|
fiberStack = []; |
|
} |
|
|
|
var index = -1; |
|
|
|
function createCursor(defaultValue) { |
|
return { |
|
current: defaultValue |
|
}; |
|
} |
|
|
|
function pop(cursor, fiber) { |
|
if (index < 0) { |
|
{ |
|
error('Unexpected pop.'); |
|
} |
|
|
|
return; |
|
} |
|
|
|
{ |
|
if (fiber !== fiberStack[index]) { |
|
error('Unexpected Fiber popped.'); |
|
} |
|
} |
|
|
|
cursor.current = valueStack[index]; |
|
valueStack[index] = null; |
|
|
|
{ |
|
fiberStack[index] = null; |
|
} |
|
|
|
index--; |
|
} |
|
|
|
function push(cursor, value, fiber) { |
|
index++; |
|
valueStack[index] = cursor.current; |
|
|
|
{ |
|
fiberStack[index] = fiber; |
|
} |
|
|
|
cursor.current = value; |
|
} |
|
|
|
var warnedAboutMissingGetChildContext; |
|
|
|
{ |
|
warnedAboutMissingGetChildContext = {}; |
|
} |
|
|
|
var emptyContextObject = {}; |
|
|
|
{ |
|
Object.freeze(emptyContextObject); |
|
} // A cursor to the current merged context object on the stack. |
|
|
|
|
|
var contextStackCursor = createCursor(emptyContextObject); // A cursor to a boolean indicating whether the context has changed. |
|
|
|
var didPerformWorkStackCursor = createCursor(false); // Keep track of the previous context object that was on the stack. |
|
// We use this to get access to the parent context after we have already |
|
// pushed the next context provider, and now need to merge their contexts. |
|
|
|
var previousContext = emptyContextObject; |
|
|
|
function getUnmaskedContext(workInProgress, Component, didPushOwnContextIfProvider) { |
|
{ |
|
if (didPushOwnContextIfProvider && isContextProvider(Component)) { |
|
// If the fiber is a context provider itself, when we read its context |
|
// we may have already pushed its own child context on the stack. A context |
|
// provider should not "see" its own child context. Therefore we read the |
|
// previous (parent) context instead for a context provider. |
|
return previousContext; |
|
} |
|
|
|
return contextStackCursor.current; |
|
} |
|
} |
|
|
|
function cacheContext(workInProgress, unmaskedContext, maskedContext) { |
|
{ |
|
var instance = workInProgress.stateNode; |
|
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext; |
|
instance.__reactInternalMemoizedMaskedChildContext = maskedContext; |
|
} |
|
} |
|
|
|
function getMaskedContext(workInProgress, unmaskedContext) { |
|
{ |
|
var type = workInProgress.type; |
|
var contextTypes = type.contextTypes; |
|
|
|
if (!contextTypes) { |
|
return emptyContextObject; |
|
} // Avoid recreating masked context unless unmasked context has changed. |
|
// Failing to do this will result in unnecessary calls to componentWillReceiveProps. |
|
// This may trigger infinite loops if componentWillReceiveProps calls setState. |
|
|
|
|
|
var instance = workInProgress.stateNode; |
|
|
|
if (instance && instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext) { |
|
return instance.__reactInternalMemoizedMaskedChildContext; |
|
} |
|
|
|
var context = {}; |
|
|
|
for (var key in contextTypes) { |
|
context[key] = unmaskedContext[key]; |
|
} |
|
|
|
{ |
|
var name = getComponentNameFromFiber(workInProgress) || 'Unknown'; |
|
checkPropTypes(contextTypes, context, 'context', name); |
|
} // Cache unmasked context so we can avoid recreating masked context unless necessary. |
|
// Context is created before the class component is instantiated so check for instance. |
|
|
|
|
|
if (instance) { |
|
cacheContext(workInProgress, unmaskedContext, context); |
|
} |
|
|
|
return context; |
|
} |
|
} |
|
|
|
function hasContextChanged() { |
|
{ |
|
return didPerformWorkStackCursor.current; |
|
} |
|
} |
|
|
|
function isContextProvider(type) { |
|
{ |
|
var childContextTypes = type.childContextTypes; |
|
return childContextTypes !== null && childContextTypes !== undefined; |
|
} |
|
} |
|
|
|
function popContext(fiber) { |
|
{ |
|
pop(didPerformWorkStackCursor, fiber); |
|
pop(contextStackCursor, fiber); |
|
} |
|
} |
|
|
|
function popTopLevelContextObject(fiber) { |
|
{ |
|
pop(didPerformWorkStackCursor, fiber); |
|
pop(contextStackCursor, fiber); |
|
} |
|
} |
|
|
|
function pushTopLevelContextObject(fiber, context, didChange) { |
|
{ |
|
if (contextStackCursor.current !== emptyContextObject) { |
|
throw new Error('Unexpected context found on stack. ' + 'This error is likely caused by a bug in React. Please file an issue.'); |
|
} |
|
|
|
push(contextStackCursor, context, fiber); |
|
push(didPerformWorkStackCursor, didChange, fiber); |
|
} |
|
} |
|
|
|
function processChildContext(fiber, type, parentContext) { |
|
{ |
|
var instance = fiber.stateNode; |
|
var childContextTypes = type.childContextTypes; // TODO (bvaughn) Replace this behavior with an invariant() in the future. |
|
// It has only been added in Fiber to match the (unintentional) behavior in Stack. |
|
|
|
if (typeof instance.getChildContext !== 'function') { |
|
{ |
|
var componentName = getComponentNameFromFiber(fiber) || 'Unknown'; |
|
|
|
if (!warnedAboutMissingGetChildContext[componentName]) { |
|
warnedAboutMissingGetChildContext[componentName] = true; |
|
|
|
error('%s.childContextTypes is specified but there is no getChildContext() method ' + 'on the instance. You can either define getChildContext() on %s or remove ' + 'childContextTypes from it.', componentName, componentName); |
|
} |
|
} |
|
|
|
return parentContext; |
|
} |
|
|
|
var childContext = instance.getChildContext(); |
|
|
|
for (var contextKey in childContext) { |
|
if (!(contextKey in childContextTypes)) { |
|
throw new Error((getComponentNameFromFiber(fiber) || 'Unknown') + ".getChildContext(): key \"" + contextKey + "\" is not defined in childContextTypes."); |
|
} |
|
} |
|
|
|
{ |
|
var name = getComponentNameFromFiber(fiber) || 'Unknown'; |
|
checkPropTypes(childContextTypes, childContext, 'child context', name); |
|
} |
|
|
|
return assign({}, parentContext, childContext); |
|
} |
|
} |
|
|
|
function pushContextProvider(workInProgress) { |
|
{ |
|
var instance = workInProgress.stateNode; // We push the context as early as possible to ensure stack integrity. |
|
// If the instance does not exist yet, we will push null at first, |
|
// and replace it on the stack later when invalidating the context. |
|
|
|
var memoizedMergedChildContext = instance && instance.__reactInternalMemoizedMergedChildContext || emptyContextObject; // Remember the parent context so we can merge with it later. |
|
// Inherit the parent's did-perform-work value to avoid inadvertently blocking updates. |
|
|
|
previousContext = contextStackCursor.current; |
|
push(contextStackCursor, memoizedMergedChildContext, workInProgress); |
|
push(didPerformWorkStackCursor, didPerformWorkStackCursor.current, workInProgress); |
|
return true; |
|
} |
|
} |
|
|
|
function invalidateContextProvider(workInProgress, type, didChange) { |
|
{ |
|
var instance = workInProgress.stateNode; |
|
|
|
if (!instance) { |
|
throw new Error('Expected to have an instance by this point. ' + 'This error is likely caused by a bug in React. Please file an issue.'); |
|
} |
|
|
|
if (didChange) { |
|
// Merge parent and own context. |
|
// Skip this if we're not updating due to sCU. |
|
// This avoids unnecessarily recomputing memoized values. |
|
var mergedContext = processChildContext(workInProgress, type, previousContext); |
|
instance.__reactInternalMemoizedMergedChildContext = mergedContext; // Replace the old (or empty) context with the new one. |
|
// It is important to unwind the context in the reverse order. |
|
|
|
pop(didPerformWorkStackCursor, workInProgress); |
|
pop(contextStackCursor, workInProgress); // Now push the new context and mark that it has changed. |
|
|
|
push(contextStackCursor, mergedContext, workInProgress); |
|
push(didPerformWorkStackCursor, didChange, workInProgress); |
|
} else { |
|
pop(didPerformWorkStackCursor, workInProgress); |
|
push(didPerformWorkStackCursor, didChange, workInProgress); |
|
} |
|
} |
|
} |
|
|
|
function findCurrentUnmaskedContext(fiber) { |
|
{ |
|
// Currently this is only used with renderSubtreeIntoContainer; not sure if it |
|
// makes sense elsewhere |
|
if (!isFiberMounted(fiber) || fiber.tag !== ClassComponent) { |
|
throw new Error('Expected subtree parent to be a mounted class component. ' + 'This error is likely caused by a bug in React. Please file an issue.'); |
|
} |
|
|
|
var node = fiber; |
|
|
|
do { |
|
switch (node.tag) { |
|
case HostRoot: |
|
return node.stateNode.context; |
|
|
|
case ClassComponent: |
|
{ |
|
var Component = node.type; |
|
|
|
if (isContextProvider(Component)) { |
|
return node.stateNode.__reactInternalMemoizedMergedChildContext; |
|
} |
|
|
|
break; |
|
} |
|
} |
|
|
|
node = node.return; |
|
} while (node !== null); |
|
|
|
throw new Error('Found unexpected detached subtree parent. ' + 'This error is likely caused by a bug in React. Please file an issue.'); |
|
} |
|
} |
|
|
|
var LegacyRoot = 0; |
|
var ConcurrentRoot = 1; |
|
|
|
var syncQueue = null; |
|
var includesLegacySyncCallbacks = false; |
|
var isFlushingSyncQueue = false; |
|
function scheduleSyncCallback(callback) { |
|
// Push this callback into an internal queue. We'll flush these either in |
|
// the next tick, or earlier if something calls `flushSyncCallbackQueue`. |
|
if (syncQueue === null) { |
|
syncQueue = [callback]; |
|
} else { |
|
// Push onto existing queue. Don't need to schedule a callback because |
|
// we already scheduled one when we created the queue. |
|
syncQueue.push(callback); |
|
} |
|
} |
|
function scheduleLegacySyncCallback(callback) { |
|
includesLegacySyncCallbacks = true; |
|
scheduleSyncCallback(callback); |
|
} |
|
function flushSyncCallbacksOnlyInLegacyMode() { |
|
// Only flushes the queue if there's a legacy sync callback scheduled. |
|
// TODO: There's only a single type of callback: performSyncOnWorkOnRoot. So |
|
// it might make more sense for the queue to be a list of roots instead of a |
|
// list of generic callbacks. Then we can have two: one for legacy roots, one |
|
// for concurrent roots. And this method would only flush the legacy ones. |
|
if (includesLegacySyncCallbacks) { |
|
flushSyncCallbacks(); |
|
} |
|
} |
|
function flushSyncCallbacks() { |
|
if (!isFlushingSyncQueue && syncQueue !== null) { |
|
// Prevent re-entrance. |
|
isFlushingSyncQueue = true; |
|
var i = 0; |
|
var previousUpdatePriority = getCurrentUpdatePriority(); |
|
|
|
try { |
|
var isSync = true; |
|
var queue = syncQueue; // TODO: Is this necessary anymore? The only user code that runs in this |
|
// queue is in the render or commit phases. |
|
|
|
setCurrentUpdatePriority(DiscreteEventPriority); |
|
|
|
for (; i < queue.length; i++) { |
|
var callback = queue[i]; |
|
|
|
do { |
|
callback = callback(isSync); |
|
} while (callback !== null); |
|
} |
|
|
|
syncQueue = null; |
|
includesLegacySyncCallbacks = false; |
|
} catch (error) { |
|
// If something throws, leave the remaining callbacks on the queue. |
|
if (syncQueue !== null) { |
|
syncQueue = syncQueue.slice(i + 1); |
|
} // Resume flushing in the next tick |
|
|
|
|
|
scheduleCallback(ImmediatePriority, flushSyncCallbacks); |
|
throw error; |
|
} finally { |
|
setCurrentUpdatePriority(previousUpdatePriority); |
|
isFlushingSyncQueue = false; |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
// TODO: Use the unified fiber stack module instead of this local one? |
|
// Intentionally not using it yet to derisk the initial implementation, because |
|
// the way we push/pop these values is a bit unusual. If there's a mistake, I'd |
|
// rather the ids be wrong than crash the whole reconciler. |
|
var forkStack = []; |
|
var forkStackIndex = 0; |
|
var treeForkProvider = null; |
|
var treeForkCount = 0; |
|
var idStack = []; |
|
var idStackIndex = 0; |
|
var treeContextProvider = null; |
|
var treeContextId = 1; |
|
var treeContextOverflow = ''; |
|
function isForkedChild(workInProgress) { |
|
warnIfNotHydrating(); |
|
return (workInProgress.flags & Forked) !== NoFlags; |
|
} |
|
function getForksAtLevel(workInProgress) { |
|
warnIfNotHydrating(); |
|
return treeForkCount; |
|
} |
|
function getTreeId() { |
|
var overflow = treeContextOverflow; |
|
var idWithLeadingBit = treeContextId; |
|
var id = idWithLeadingBit & ~getLeadingBit(idWithLeadingBit); |
|
return id.toString(32) + overflow; |
|
} |
|
function pushTreeFork(workInProgress, totalChildren) { |
|
// This is called right after we reconcile an array (or iterator) of child |
|
// fibers, because that's the only place where we know how many children in |
|
// the whole set without doing extra work later, or storing addtional |
|
// information on the fiber. |
|
// |
|
// That's why this function is separate from pushTreeId — it's called during |
|
// the render phase of the fork parent, not the child, which is where we push |
|
// the other context values. |
|
// |
|
// In the Fizz implementation this is much simpler because the child is |
|
// rendered in the same callstack as the parent. |
|
// |
|
// It might be better to just add a `forks` field to the Fiber type. It would |
|
// make this module simpler. |
|
warnIfNotHydrating(); |
|
forkStack[forkStackIndex++] = treeForkCount; |
|
forkStack[forkStackIndex++] = treeForkProvider; |
|
treeForkProvider = workInProgress; |
|
treeForkCount = totalChildren; |
|
} |
|
function pushTreeId(workInProgress, totalChildren, index) { |
|
warnIfNotHydrating(); |
|
idStack[idStackIndex++] = treeContextId; |
|
idStack[idStackIndex++] = treeContextOverflow; |
|
idStack[idStackIndex++] = treeContextProvider; |
|
treeContextProvider = workInProgress; |
|
var baseIdWithLeadingBit = treeContextId; |
|
var baseOverflow = treeContextOverflow; // The leftmost 1 marks the end of the sequence, non-inclusive. It's not part |
|
// of the id; we use it to account for leading 0s. |
|
|
|
var baseLength = getBitLength(baseIdWithLeadingBit) - 1; |
|
var baseId = baseIdWithLeadingBit & ~(1 << baseLength); |
|
var slot = index + 1; |
|
var length = getBitLength(totalChildren) + baseLength; // 30 is the max length we can store without overflowing, taking into |
|
// consideration the leading 1 we use to mark the end of the sequence. |
|
|
|
if (length > 30) { |
|
// We overflowed the bitwise-safe range. Fall back to slower algorithm. |
|
// This branch assumes the length of the base id is greater than 5; it won't |
|
// work for smaller ids, because you need 5 bits per character. |
|
// |
|
// We encode the id in multiple steps: first the base id, then the |
|
// remaining digits. |
|
// |
|
// Each 5 bit sequence corresponds to a single base 32 character. So for |
|
// example, if the current id is 23 bits long, we can convert 20 of those |
|
// bits into a string of 4 characters, with 3 bits left over. |
|
// |
|
// First calculate how many bits in the base id represent a complete |
|
// sequence of characters. |
|
var numberOfOverflowBits = baseLength - baseLength % 5; // Then create a bitmask that selects only those bits. |
|
|
|
var newOverflowBits = (1 << numberOfOverflowBits) - 1; // Select the bits, and convert them to a base 32 string. |
|
|
|
var newOverflow = (baseId & newOverflowBits).toString(32); // Now we can remove those bits from the base id. |
|
|
|
var restOfBaseId = baseId >> numberOfOverflowBits; |
|
var restOfBaseLength = baseLength - numberOfOverflowBits; // Finally, encode the rest of the bits using the normal algorithm. Because |
|
// we made more room, this time it won't overflow. |
|
|
|
var restOfLength = getBitLength(totalChildren) + restOfBaseLength; |
|
var restOfNewBits = slot << restOfBaseLength; |
|
var id = restOfNewBits | restOfBaseId; |
|
var overflow = newOverflow + baseOverflow; |
|
treeContextId = 1 << restOfLength | id; |
|
treeContextOverflow = overflow; |
|
} else { |
|
// Normal path |
|
var newBits = slot << baseLength; |
|
|
|
var _id = newBits | baseId; |
|
|
|
var _overflow = baseOverflow; |
|
treeContextId = 1 << length | _id; |
|
treeContextOverflow = _overflow; |
|
} |
|
} |
|
function pushMaterializedTreeId(workInProgress) { |
|
warnIfNotHydrating(); // This component materialized an id. This will affect any ids that appear |
|
// in its children. |
|
|
|
var returnFiber = workInProgress.return; |
|
|
|
if (returnFiber !== null) { |
|
var numberOfForks = 1; |
|
var slotIndex = 0; |
|
pushTreeFork(workInProgress, numberOfForks); |
|
pushTreeId(workInProgress, numberOfForks, slotIndex); |
|
} |
|
} |
|
|
|
function getBitLength(number) { |
|
return 32 - clz32(number); |
|
} |
|
|
|
function getLeadingBit(id) { |
|
return 1 << getBitLength(id) - 1; |
|
} |
|
|
|
function popTreeContext(workInProgress) { |
|
// Restore the previous values. |
|
// This is a bit more complicated than other context-like modules in Fiber |
|
// because the same Fiber may appear on the stack multiple times and for |
|
// different reasons. We have to keep popping until the work-in-progress is |
|
// no longer at the top of the stack. |
|
while (workInProgress === treeForkProvider) { |
|
treeForkProvider = forkStack[--forkStackIndex]; |
|
forkStack[forkStackIndex] = null; |
|
treeForkCount = forkStack[--forkStackIndex]; |
|
forkStack[forkStackIndex] = null; |
|
} |
|
|
|
while (workInProgress === treeContextProvider) { |
|
treeContextProvider = idStack[--idStackIndex]; |
|
idStack[idStackIndex] = null; |
|
treeContextOverflow = idStack[--idStackIndex]; |
|
idStack[idStackIndex] = null; |
|
treeContextId = idStack[--idStackIndex]; |
|
idStack[idStackIndex] = null; |
|
} |
|
} |
|
function getSuspendedTreeContext() { |
|
warnIfNotHydrating(); |
|
|
|
if (treeContextProvider !== null) { |
|
return { |
|
id: treeContextId, |
|
overflow: treeContextOverflow |
|
}; |
|
} else { |
|
return null; |
|
} |
|
} |
|
function restoreSuspendedTreeContext(workInProgress, suspendedContext) { |
|
warnIfNotHydrating(); |
|
idStack[idStackIndex++] = treeContextId; |
|
idStack[idStackIndex++] = treeContextOverflow; |
|
idStack[idStackIndex++] = treeContextProvider; |
|
treeContextId = suspendedContext.id; |
|
treeContextOverflow = suspendedContext.overflow; |
|
treeContextProvider = workInProgress; |
|
} |
|
|
|
function warnIfNotHydrating() { |
|
{ |
|
if (!getIsHydrating()) { |
|
error('Expected to be hydrating. This is a bug in React. Please file ' + 'an issue.'); |
|
} |
|
} |
|
} |
|
|
|
// This may have been an insertion or a hydration. |
|
|
|
var hydrationParentFiber = null; |
|
var nextHydratableInstance = null; |
|
var isHydrating = false; // This flag allows for warning supression when we expect there to be mismatches |
|
// due to earlier mismatches or a suspended fiber. |
|
|
|
var didSuspendOrErrorDEV = false; // Hydration errors that were thrown inside this boundary |
|
|
|
var hydrationErrors = null; |
|
|
|
function warnIfHydrating() { |
|
{ |
|
if (isHydrating) { |
|
error('We should not be hydrating here. This is a bug in React. Please file a bug.'); |
|
} |
|
} |
|
} |
|
|
|
function markDidThrowWhileHydratingDEV() { |
|
{ |
|
didSuspendOrErrorDEV = true; |
|
} |
|
} |
|
function didSuspendOrErrorWhileHydratingDEV() { |
|
{ |
|
return didSuspendOrErrorDEV; |
|
} |
|
} |
|
|
|
function enterHydrationState(fiber) { |
|
|
|
var parentInstance = fiber.stateNode.containerInfo; |
|
nextHydratableInstance = getFirstHydratableChildWithinContainer(parentInstance); |
|
hydrationParentFiber = fiber; |
|
isHydrating = true; |
|
hydrationErrors = null; |
|
didSuspendOrErrorDEV = false; |
|
return true; |
|
} |
|
|
|
function reenterHydrationStateFromDehydratedSuspenseInstance(fiber, suspenseInstance, treeContext) { |
|
|
|
nextHydratableInstance = getFirstHydratableChildWithinSuspenseInstance(suspenseInstance); |
|
hydrationParentFiber = fiber; |
|
isHydrating = true; |
|
hydrationErrors = null; |
|
didSuspendOrErrorDEV = false; |
|
|
|
if (treeContext !== null) { |
|
restoreSuspendedTreeContext(fiber, treeContext); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function warnUnhydratedInstance(returnFiber, instance) { |
|
{ |
|
switch (returnFiber.tag) { |
|
case HostRoot: |
|
{ |
|
didNotHydrateInstanceWithinContainer(returnFiber.stateNode.containerInfo, instance); |
|
break; |
|
} |
|
|
|
case HostComponent: |
|
{ |
|
var isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode; |
|
didNotHydrateInstance(returnFiber.type, returnFiber.memoizedProps, returnFiber.stateNode, instance, // TODO: Delete this argument when we remove the legacy root API. |
|
isConcurrentMode); |
|
break; |
|
} |
|
|
|
case SuspenseComponent: |
|
{ |
|
var suspenseState = returnFiber.memoizedState; |
|
if (suspenseState.dehydrated !== null) didNotHydrateInstanceWithinSuspenseInstance(suspenseState.dehydrated, instance); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
function deleteHydratableInstance(returnFiber, instance) { |
|
warnUnhydratedInstance(returnFiber, instance); |
|
var childToDelete = createFiberFromHostInstanceForDeletion(); |
|
childToDelete.stateNode = instance; |
|
childToDelete.return = returnFiber; |
|
var deletions = returnFiber.deletions; |
|
|
|
if (deletions === null) { |
|
returnFiber.deletions = [childToDelete]; |
|
returnFiber.flags |= ChildDeletion; |
|
} else { |
|
deletions.push(childToDelete); |
|
} |
|
} |
|
|
|
function warnNonhydratedInstance(returnFiber, fiber) { |
|
{ |
|
if (didSuspendOrErrorDEV) { |
|
// Inside a boundary that already suspended. We're currently rendering the |
|
// siblings of a suspended node. The mismatch may be due to the missing |
|
// data, so it's probably a false positive. |
|
return; |
|
} |
|
|
|
switch (returnFiber.tag) { |
|
case HostRoot: |
|
{ |
|
var parentContainer = returnFiber.stateNode.containerInfo; |
|
|
|
switch (fiber.tag) { |
|
case HostComponent: |
|
var type = fiber.type; |
|
var props = fiber.pendingProps; |
|
didNotFindHydratableInstanceWithinContainer(parentContainer, type); |
|
break; |
|
|
|
case HostText: |
|
var text = fiber.pendingProps; |
|
didNotFindHydratableTextInstanceWithinContainer(parentContainer, text); |
|
break; |
|
} |
|
|
|
break; |
|
} |
|
|
|
case HostComponent: |
|
{ |
|
var parentType = returnFiber.type; |
|
var parentProps = returnFiber.memoizedProps; |
|
var parentInstance = returnFiber.stateNode; |
|
|
|
switch (fiber.tag) { |
|
case HostComponent: |
|
{ |
|
var _type = fiber.type; |
|
var _props = fiber.pendingProps; |
|
var isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode; |
|
didNotFindHydratableInstance(parentType, parentProps, parentInstance, _type, _props, // TODO: Delete this argument when we remove the legacy root API. |
|
isConcurrentMode); |
|
break; |
|
} |
|
|
|
case HostText: |
|
{ |
|
var _text = fiber.pendingProps; |
|
|
|
var _isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode; |
|
|
|
didNotFindHydratableTextInstance(parentType, parentProps, parentInstance, _text, // TODO: Delete this argument when we remove the legacy root API. |
|
_isConcurrentMode); |
|
break; |
|
} |
|
} |
|
|
|
break; |
|
} |
|
|
|
case SuspenseComponent: |
|
{ |
|
var suspenseState = returnFiber.memoizedState; |
|
var _parentInstance = suspenseState.dehydrated; |
|
if (_parentInstance !== null) switch (fiber.tag) { |
|
case HostComponent: |
|
var _type2 = fiber.type; |
|
var _props2 = fiber.pendingProps; |
|
didNotFindHydratableInstanceWithinSuspenseInstance(_parentInstance, _type2); |
|
break; |
|
|
|
case HostText: |
|
var _text2 = fiber.pendingProps; |
|
didNotFindHydratableTextInstanceWithinSuspenseInstance(_parentInstance, _text2); |
|
break; |
|
} |
|
break; |
|
} |
|
|
|
default: |
|
return; |
|
} |
|
} |
|
} |
|
|
|
function insertNonHydratedInstance(returnFiber, fiber) { |
|
fiber.flags = fiber.flags & ~Hydrating | Placement; |
|
warnNonhydratedInstance(returnFiber, fiber); |
|
} |
|
|
|
function tryHydrate(fiber, nextInstance) { |
|
switch (fiber.tag) { |
|
case HostComponent: |
|
{ |
|
var type = fiber.type; |
|
var props = fiber.pendingProps; |
|
var instance = canHydrateInstance(nextInstance, type); |
|
|
|
if (instance !== null) { |
|
fiber.stateNode = instance; |
|
hydrationParentFiber = fiber; |
|
nextHydratableInstance = getFirstHydratableChild(instance); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
case HostText: |
|
{ |
|
var text = fiber.pendingProps; |
|
var textInstance = canHydrateTextInstance(nextInstance, text); |
|
|
|
if (textInstance !== null) { |
|
fiber.stateNode = textInstance; |
|
hydrationParentFiber = fiber; // Text Instances don't have children so there's nothing to hydrate. |
|
|
|
nextHydratableInstance = null; |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
case SuspenseComponent: |
|
{ |
|
var suspenseInstance = canHydrateSuspenseInstance(nextInstance); |
|
|
|
if (suspenseInstance !== null) { |
|
var suspenseState = { |
|
dehydrated: suspenseInstance, |
|
treeContext: getSuspendedTreeContext(), |
|
retryLane: OffscreenLane |
|
}; |
|
fiber.memoizedState = suspenseState; // Store the dehydrated fragment as a child fiber. |
|
// This simplifies the code for getHostSibling and deleting nodes, |
|
// since it doesn't have to consider all Suspense boundaries and |
|
// check if they're dehydrated ones or not. |
|
|
|
var dehydratedFragment = createFiberFromDehydratedFragment(suspenseInstance); |
|
dehydratedFragment.return = fiber; |
|
fiber.child = dehydratedFragment; |
|
hydrationParentFiber = fiber; // While a Suspense Instance does have children, we won't step into |
|
// it during the first pass. Instead, we'll reenter it later. |
|
|
|
nextHydratableInstance = null; |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
default: |
|
return false; |
|
} |
|
} |
|
|
|
function shouldClientRenderOnMismatch(fiber) { |
|
return (fiber.mode & ConcurrentMode) !== NoMode && (fiber.flags & DidCapture) === NoFlags; |
|
} |
|
|
|
function throwOnHydrationMismatch(fiber) { |
|
throw new Error('Hydration failed because the initial UI does not match what was ' + 'rendered on the server.'); |
|
} |
|
|
|
function tryToClaimNextHydratableInstance(fiber) { |
|
if (!isHydrating) { |
|
return; |
|
} |
|
|
|
var nextInstance = nextHydratableInstance; |
|
|
|
if (!nextInstance) { |
|
if (shouldClientRenderOnMismatch(fiber)) { |
|
warnNonhydratedInstance(hydrationParentFiber, fiber); |
|
throwOnHydrationMismatch(); |
|
} // Nothing to hydrate. Make it an insertion. |
|
|
|
|
|
insertNonHydratedInstance(hydrationParentFiber, fiber); |
|
isHydrating = false; |
|
hydrationParentFiber = fiber; |
|
return; |
|
} |
|
|
|
var firstAttemptedInstance = nextInstance; |
|
|
|
if (!tryHydrate(fiber, nextInstance)) { |
|
if (shouldClientRenderOnMismatch(fiber)) { |
|
warnNonhydratedInstance(hydrationParentFiber, fiber); |
|
throwOnHydrationMismatch(); |
|
} // If we can't hydrate this instance let's try the next one. |
|
// We use this as a heuristic. It's based on intuition and not data so it |
|
// might be flawed or unnecessary. |
|
|
|
|
|
nextInstance = getNextHydratableSibling(firstAttemptedInstance); |
|
var prevHydrationParentFiber = hydrationParentFiber; |
|
|
|
if (!nextInstance || !tryHydrate(fiber, nextInstance)) { |
|
// Nothing to hydrate. Make it an insertion. |
|
insertNonHydratedInstance(hydrationParentFiber, fiber); |
|
isHydrating = false; |
|
hydrationParentFiber = fiber; |
|
return; |
|
} // We matched the next one, we'll now assume that the first one was |
|
// superfluous and we'll delete it. Since we can't eagerly delete it |
|
// we'll have to schedule a deletion. To do that, this node needs a dummy |
|
// fiber associated with it. |
|
|
|
|
|
deleteHydratableInstance(prevHydrationParentFiber, firstAttemptedInstance); |
|
} |
|
} |
|
|
|
function prepareToHydrateHostInstance(fiber, rootContainerInstance, hostContext) { |
|
|
|
var instance = fiber.stateNode; |
|
var shouldWarnIfMismatchDev = !didSuspendOrErrorDEV; |
|
var updatePayload = hydrateInstance(instance, fiber.type, fiber.memoizedProps, rootContainerInstance, hostContext, fiber, shouldWarnIfMismatchDev); // TODO: Type this specific to this type of component. |
|
|
|
fiber.updateQueue = updatePayload; // If the update payload indicates that there is a change or if there |
|
// is a new ref we mark this as an update. |
|
|
|
if (updatePayload !== null) { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
function prepareToHydrateHostTextInstance(fiber) { |
|
|
|
var textInstance = fiber.stateNode; |
|
var textContent = fiber.memoizedProps; |
|
var shouldUpdate = hydrateTextInstance(textInstance, textContent, fiber); |
|
|
|
if (shouldUpdate) { |
|
// We assume that prepareToHydrateHostTextInstance is called in a context where the |
|
// hydration parent is the parent host component of this host text. |
|
var returnFiber = hydrationParentFiber; |
|
|
|
if (returnFiber !== null) { |
|
switch (returnFiber.tag) { |
|
case HostRoot: |
|
{ |
|
var parentContainer = returnFiber.stateNode.containerInfo; |
|
var isConcurrentMode = (returnFiber.mode & ConcurrentMode) !== NoMode; |
|
didNotMatchHydratedContainerTextInstance(parentContainer, textInstance, textContent, // TODO: Delete this argument when we remove the legacy root API. |
|
isConcurrentMode); |
|
break; |
|
} |
|
|
|
case HostComponent: |
|
{ |
|
var parentType = returnFiber.type; |
|
var parentProps = returnFiber.memoizedProps; |
|
var parentInstance = returnFiber.stateNode; |
|
|
|
var _isConcurrentMode2 = (returnFiber.mode & ConcurrentMode) !== NoMode; |
|
|
|
didNotMatchHydratedTextInstance(parentType, parentProps, parentInstance, textInstance, textContent, // TODO: Delete this argument when we remove the legacy root API. |
|
_isConcurrentMode2); |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return shouldUpdate; |
|
} |
|
|
|
function prepareToHydrateHostSuspenseInstance(fiber) { |
|
|
|
var suspenseState = fiber.memoizedState; |
|
var suspenseInstance = suspenseState !== null ? suspenseState.dehydrated : null; |
|
|
|
if (!suspenseInstance) { |
|
throw new Error('Expected to have a hydrated suspense instance. ' + 'This error is likely caused by a bug in React. Please file an issue.'); |
|
} |
|
|
|
hydrateSuspenseInstance(suspenseInstance, fiber); |
|
} |
|
|
|
function skipPastDehydratedSuspenseInstance(fiber) { |
|
|
|
var suspenseState = fiber.memoizedState; |
|
var suspenseInstance = suspenseState !== null ? suspenseState.dehydrated : null; |
|
|
|
if (!suspenseInstance) { |
|
throw new Error('Expected to have a hydrated suspense instance. ' + 'This error is likely caused by a bug in React. Please file an issue.'); |
|
} |
|
|
|
return getNextHydratableInstanceAfterSuspenseInstance(suspenseInstance); |
|
} |
|
|
|
function popToNextHostParent(fiber) { |
|
var parent = fiber.return; |
|
|
|
while (parent !== null && parent.tag !== HostComponent && parent.tag !== HostRoot && parent.tag !== SuspenseComponent) { |
|
parent = parent.return; |
|
} |
|
|
|
hydrationParentFiber = parent; |
|
} |
|
|
|
function popHydrationState(fiber) { |
|
|
|
if (fiber !== hydrationParentFiber) { |
|
// We're deeper than the current hydration context, inside an inserted |
|
// tree. |
|
return false; |
|
} |
|
|
|
if (!isHydrating) { |
|
// If we're not currently hydrating but we're in a hydration context, then |
|
// we were an insertion and now need to pop up reenter hydration of our |
|
// siblings. |
|
popToNextHostParent(fiber); |
|
isHydrating = true; |
|
return false; |
|
} // If we have any remaining hydratable nodes, we need to delete them now. |
|
// We only do this deeper than head and body since they tend to have random |
|
// other nodes in them. We also ignore components with pure text content in |
|
// side of them. We also don't delete anything inside the root container. |
|
|
|
|
|
if (fiber.tag !== HostRoot && (fiber.tag !== HostComponent || shouldDeleteUnhydratedTailInstances(fiber.type) && !shouldSetTextContent(fiber.type, fiber.memoizedProps))) { |
|
var nextInstance = nextHydratableInstance; |
|
|
|
if (nextInstance) { |
|
if (shouldClientRenderOnMismatch(fiber)) { |
|
warnIfUnhydratedTailNodes(fiber); |
|
throwOnHydrationMismatch(); |
|
} else { |
|
while (nextInstance) { |
|
deleteHydratableInstance(fiber, nextInstance); |
|
nextInstance = getNextHydratableSibling(nextInstance); |
|
} |
|
} |
|
} |
|
} |
|
|
|
popToNextHostParent(fiber); |
|
|
|
if (fiber.tag === SuspenseComponent) { |
|
nextHydratableInstance = skipPastDehydratedSuspenseInstance(fiber); |
|
} else { |
|
nextHydratableInstance = hydrationParentFiber ? getNextHydratableSibling(fiber.stateNode) : null; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function hasUnhydratedTailNodes() { |
|
return isHydrating && nextHydratableInstance !== null; |
|
} |
|
|
|
function warnIfUnhydratedTailNodes(fiber) { |
|
var nextInstance = nextHydratableInstance; |
|
|
|
while (nextInstance) { |
|
warnUnhydratedInstance(fiber, nextInstance); |
|
nextInstance = getNextHydratableSibling(nextInstance); |
|
} |
|
} |
|
|
|
function resetHydrationState() { |
|
|
|
hydrationParentFiber = null; |
|
nextHydratableInstance = null; |
|
isHydrating = false; |
|
didSuspendOrErrorDEV = false; |
|
} |
|
|
|
function upgradeHydrationErrorsToRecoverable() { |
|
if (hydrationErrors !== null) { |
|
// Successfully completed a forced client render. The errors that occurred |
|
// during the hydration attempt are now recovered. We will log them in |
|
// commit phase, once the entire tree has finished. |
|
queueRecoverableErrors(hydrationErrors); |
|
hydrationErrors = null; |
|
} |
|
} |
|
|
|
function getIsHydrating() { |
|
return isHydrating; |
|
} |
|
|
|
function queueHydrationError(error) { |
|
if (hydrationErrors === null) { |
|
hydrationErrors = [error]; |
|
} else { |
|
hydrationErrors.push(error); |
|
} |
|
} |
|
|
|
var ReactCurrentBatchConfig$1 = ReactSharedInternals.ReactCurrentBatchConfig; |
|
var NoTransition = null; |
|
function requestCurrentTransition() { |
|
return ReactCurrentBatchConfig$1.transition; |
|
} |
|
|
|
var ReactStrictModeWarnings = { |
|
recordUnsafeLifecycleWarnings: function (fiber, instance) {}, |
|
flushPendingUnsafeLifecycleWarnings: function () {}, |
|
recordLegacyContextWarning: function (fiber, instance) {}, |
|
flushLegacyContextWarning: function () {}, |
|
discardPendingWarnings: function () {} |
|
}; |
|
|
|
{ |
|
var findStrictRoot = function (fiber) { |
|
var maybeStrictRoot = null; |
|
var node = fiber; |
|
|
|
while (node !== null) { |
|
if (node.mode & StrictLegacyMode) { |
|
maybeStrictRoot = node; |
|
} |
|
|
|
node = node.return; |
|
} |
|
|
|
return maybeStrictRoot; |
|
}; |
|
|
|
var setToSortedString = function (set) { |
|
var array = []; |
|
set.forEach(function (value) { |
|
array.push(value); |
|
}); |
|
return array.sort().join(', '); |
|
}; |
|
|
|
var pendingComponentWillMountWarnings = []; |
|
var pendingUNSAFE_ComponentWillMountWarnings = []; |
|
var pendingComponentWillReceivePropsWarnings = []; |
|
var pendingUNSAFE_ComponentWillReceivePropsWarnings = []; |
|
var pendingComponentWillUpdateWarnings = []; |
|
var pendingUNSAFE_ComponentWillUpdateWarnings = []; // Tracks components we have already warned about. |
|
|
|
var didWarnAboutUnsafeLifecycles = new Set(); |
|
|
|
ReactStrictModeWarnings.recordUnsafeLifecycleWarnings = function (fiber, instance) { |
|
// Dedupe strategy: Warn once per component. |
|
if (didWarnAboutUnsafeLifecycles.has(fiber.type)) { |
|
return; |
|
} |
|
|
|
if (typeof instance.componentWillMount === 'function' && // Don't warn about react-lifecycles-compat polyfilled components. |
|
instance.componentWillMount.__suppressDeprecationWarning !== true) { |
|
pendingComponentWillMountWarnings.push(fiber); |
|
} |
|
|
|
if (fiber.mode & StrictLegacyMode && typeof instance.UNSAFE_componentWillMount === 'function') { |
|
pendingUNSAFE_ComponentWillMountWarnings.push(fiber); |
|
} |
|
|
|
if (typeof instance.componentWillReceiveProps === 'function' && instance.componentWillReceiveProps.__suppressDeprecationWarning !== true) { |
|
pendingComponentWillReceivePropsWarnings.push(fiber); |
|
} |
|
|
|
if (fiber.mode & StrictLegacyMode && typeof instance.UNSAFE_componentWillReceiveProps === 'function') { |
|
pendingUNSAFE_ComponentWillReceivePropsWarnings.push(fiber); |
|
} |
|
|
|
if (typeof instance.componentWillUpdate === 'function' && instance.componentWillUpdate.__suppressDeprecationWarning !== true) { |
|
pendingComponentWillUpdateWarnings.push(fiber); |
|
} |
|
|
|
if (fiber.mode & StrictLegacyMode && typeof instance.UNSAFE_componentWillUpdate === 'function') { |
|
pendingUNSAFE_ComponentWillUpdateWarnings.push(fiber); |
|
} |
|
}; |
|
|
|
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings = function () { |
|
// We do an initial pass to gather component names |
|
var componentWillMountUniqueNames = new Set(); |
|
|
|
if (pendingComponentWillMountWarnings.length > 0) { |
|
pendingComponentWillMountWarnings.forEach(function (fiber) { |
|
componentWillMountUniqueNames.add(getComponentNameFromFiber(fiber) || 'Component'); |
|
didWarnAboutUnsafeLifecycles.add(fiber.type); |
|
}); |
|
pendingComponentWillMountWarnings = []; |
|
} |
|
|
|
var UNSAFE_componentWillMountUniqueNames = new Set(); |
|
|
|
if (pendingUNSAFE_ComponentWillMountWarnings.length > 0) { |
|
pendingUNSAFE_ComponentWillMountWarnings.forEach(function (fiber) { |
|
UNSAFE_componentWillMountUniqueNames.add(getComponentNameFromFiber(fiber) || 'Component'); |
|
didWarnAboutUnsafeLifecycles.add(fiber.type); |
|
}); |
|
pendingUNSAFE_ComponentWillMountWarnings = []; |
|
} |
|
|
|
var componentWillReceivePropsUniqueNames = new Set(); |
|
|
|
if (pendingComponentWillReceivePropsWarnings.length > 0) { |
|
pendingComponentWillReceivePropsWarnings.forEach(function (fiber) { |
|
componentWillReceivePropsUniqueNames.add(getComponentNameFromFiber(fiber) || 'Component'); |
|
didWarnAboutUnsafeLifecycles.add(fiber.type); |
|
}); |
|
pendingComponentWillReceivePropsWarnings = []; |
|
} |
|
|
|
var UNSAFE_componentWillReceivePropsUniqueNames = new Set(); |
|
|
|
if (pendingUNSAFE_ComponentWillReceivePropsWarnings.length > 0) { |
|
pendingUNSAFE_ComponentWillReceivePropsWarnings.forEach(function (fiber) { |
|
UNSAFE_componentWillReceivePropsUniqueNames.add(getComponentNameFromFiber(fiber) || 'Component'); |
|
didWarnAboutUnsafeLifecycles.add(fiber.type); |
|
}); |
|
pendingUNSAFE_ComponentWillReceivePropsWarnings = []; |
|
} |
|
|
|
var componentWillUpdateUniqueNames = new Set(); |
|
|
|
if (pendingComponentWillUpdateWarnings.length > 0) { |
|
pendingComponentWillUpdateWarnings.forEach(function (fiber) { |
|
componentWillUpdateUniqueNames.add(getComponentNameFromFiber(fiber) || 'Component'); |
|
didWarnAboutUnsafeLifecycles.add(fiber.type); |
|
}); |
|
pendingComponentWillUpdateWarnings = []; |
|
} |
|
|
|
var UNSAFE_componentWillUpdateUniqueNames = new Set(); |
|
|
|
if (pendingUNSAFE_ComponentWillUpdateWarnings.length > 0) { |
|
pendingUNSAFE_ComponentWillUpdateWarnings.forEach(function (fiber) { |
|
UNSAFE_componentWillUpdateUniqueNames.add(getComponentNameFromFiber(fiber) || 'Component'); |
|
didWarnAboutUnsafeLifecycles.add(fiber.type); |
|
}); |
|
pendingUNSAFE_ComponentWillUpdateWarnings = []; |
|
} // Finally, we flush all the warnings |
|
// UNSAFE_ ones before the deprecated ones, since they'll be 'louder' |
|
|
|
|
|
if (UNSAFE_componentWillMountUniqueNames.size > 0) { |
|
var sortedNames = setToSortedString(UNSAFE_componentWillMountUniqueNames); |
|
|
|
error('Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. ' + 'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' + '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' + '\nPlease update the following components: %s', sortedNames); |
|
} |
|
|
|
if (UNSAFE_componentWillReceivePropsUniqueNames.size > 0) { |
|
var _sortedNames = setToSortedString(UNSAFE_componentWillReceivePropsUniqueNames); |
|
|
|
error('Using UNSAFE_componentWillReceiveProps in strict mode is not recommended ' + 'and may indicate bugs in your code. ' + 'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' + '* Move data fetching code or side effects to componentDidUpdate.\n' + "* If you're updating state whenever props change, " + 'refactor your code to use memoization techniques or move it to ' + 'static getDerivedStateFromProps. Learn more at: https://reactjs.org/link/derived-state\n' + '\nPlease update the following components: %s', _sortedNames); |
|
} |
|
|
|
if (UNSAFE_componentWillUpdateUniqueNames.size > 0) { |
|
var _sortedNames2 = setToSortedString(UNSAFE_componentWillUpdateUniqueNames); |
|
|
|
error('Using UNSAFE_componentWillUpdate in strict mode is not recommended ' + 'and may indicate bugs in your code. ' + 'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' + '* Move data fetching code or side effects to componentDidUpdate.\n' + '\nPlease update the following components: %s', _sortedNames2); |
|
} |
|
|
|
if (componentWillMountUniqueNames.size > 0) { |
|
var _sortedNames3 = setToSortedString(componentWillMountUniqueNames); |
|
|
|
warn('componentWillMount has been renamed, and is not recommended for use. ' + 'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' + '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' + '* Rename componentWillMount to UNSAFE_componentWillMount to suppress ' + 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' + 'To rename all deprecated lifecycles to their new names, you can run ' + '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' + '\nPlease update the following components: %s', _sortedNames3); |
|
} |
|
|
|
if (componentWillReceivePropsUniqueNames.size > 0) { |
|
var _sortedNames4 = setToSortedString(componentWillReceivePropsUniqueNames); |
|
|
|
warn('componentWillReceiveProps has been renamed, and is not recommended for use. ' + 'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' + '* Move data fetching code or side effects to componentDidUpdate.\n' + "* If you're updating state whenever props change, refactor your " + 'code to use memoization techniques or move it to ' + 'static getDerivedStateFromProps. Learn more at: https://reactjs.org/link/derived-state\n' + '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress ' + 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' + 'To rename all deprecated lifecycles to their new names, you can run ' + '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' + '\nPlease update the following components: %s', _sortedNames4); |
|
} |
|
|
|
if (componentWillUpdateUniqueNames.size > 0) { |
|
var _sortedNames5 = setToSortedString(componentWillUpdateUniqueNames); |
|
|
|
warn('componentWillUpdate has been renamed, and is not recommended for use. ' + 'See https://reactjs.org/link/unsafe-component-lifecycles for details.\n\n' + '* Move data fetching code or side effects to componentDidUpdate.\n' + '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress ' + 'this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. ' + 'To rename all deprecated lifecycles to their new names, you can run ' + '`npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' + '\nPlease update the following components: %s', _sortedNames5); |
|
} |
|
}; |
|
|
|
var pendingLegacyContextWarning = new Map(); // Tracks components we have already warned about. |
|
|
|
var didWarnAboutLegacyContext = new Set(); |
|
|
|
ReactStrictModeWarnings.recordLegacyContextWarning = function (fiber, instance) { |
|
var strictRoot = findStrictRoot(fiber); |
|
|
|
if (strictRoot === null) { |
|
error('Expected to find a StrictMode component in a strict mode tree. ' + 'This error is likely caused by a bug in React. Please file an issue.'); |
|
|
|
return; |
|
} // Dedup strategy: Warn once per component. |
|
|
|
|
|
if (didWarnAboutLegacyContext.has(fiber.type)) { |
|
return; |
|
} |
|
|
|
var warningsForRoot = pendingLegacyContextWarning.get(strictRoot); |
|
|
|
if (fiber.type.contextTypes != null || fiber.type.childContextTypes != null || instance !== null && typeof instance.getChildContext === 'function') { |
|
if (warningsForRoot === undefined) { |
|
warningsForRoot = []; |
|
pendingLegacyContextWarning.set(strictRoot, warningsForRoot); |
|
} |
|
|
|
warningsForRoot.push(fiber); |
|
} |
|
}; |
|
|
|
ReactStrictModeWarnings.flushLegacyContextWarning = function () { |
|
pendingLegacyContextWarning.forEach(function (fiberArray, strictRoot) { |
|
if (fiberArray.length === 0) { |
|
return; |
|
} |
|
|
|
var firstFiber = fiberArray[0]; |
|
var uniqueNames = new Set(); |
|
fiberArray.forEach(function (fiber) { |
|
uniqueNames.add(getComponentNameFromFiber(fiber) || 'Component'); |
|
didWarnAboutLegacyContext.add(fiber.type); |
|
}); |
|
var sortedNames = setToSortedString(uniqueNames); |
|
|
|
try { |
|
setCurrentFiber(firstFiber); |
|
|
|
error('Legacy context API has been detected within a strict-mode tree.' + '\n\nThe old API will be supported in all 16.x releases, but applications ' + 'using it should migrate to the new version.' + '\n\nPlease update the following components: %s' + '\n\nLearn more about this warning here: https://reactjs.org/link/legacy-context', sortedNames); |
|
} finally { |
|
resetCurrentFiber(); |
|
} |
|
}); |
|
}; |
|
|
|
ReactStrictModeWarnings.discardPendingWarnings = function () { |
|
pendingComponentWillMountWarnings = []; |
|
pendingUNSAFE_ComponentWillMountWarnings = []; |
|
pendingComponentWillReceivePropsWarnings = []; |
|
pendingUNSAFE_ComponentWillReceivePropsWarnings = []; |
|
pendingComponentWillUpdateWarnings = []; |
|
pendingUNSAFE_ComponentWillUpdateWarnings = []; |
|
pendingLegacyContextWarning = new Map(); |
|
}; |
|
} |
|
|
|
function resolveDefaultProps(Component, baseProps) { |
|
if (Component && Component.defaultProps) { |
|
// Resolve default props. Taken from ReactElement |
|
var props = assign({}, baseProps); |
|
var defaultProps = Component.defaultProps; |
|
|
|
for (var propName in defaultProps) { |
|
if (props[propName] === undefined) { |
|
props[propName] = defaultProps[propName]; |
|
} |
|
} |
|
|
|
return props; |
|
} |
|
|
|
return baseProps; |
|
} |
|
|
|
var valueCursor = createCursor(null); |
|
var rendererSigil; |
|
|
|
{ |
|
// Use this to detect multiple renderers using the same context |
|
rendererSigil = {}; |
|
} |
|
|
|
var currentlyRenderingFiber = null; |
|
var lastContextDependency = null; |
|
var lastFullyObservedContext = null; |
|
var isDisallowedContextReadInDEV = false; |
|
function resetContextDependencies() { |
|
// This is called right before React yields execution, to ensure `readContext` |
|
// cannot be called outside the render phase. |
|
currentlyRenderingFiber = null; |
|
lastContextDependency = null; |
|
lastFullyObservedContext = null; |
|
|
|
{ |
|
isDisallowedContextReadInDEV = false; |
|
} |
|
} |
|
function enterDisallowedContextReadInDEV() { |
|
{ |
|
isDisallowedContextReadInDEV = true; |
|
} |
|
} |
|
function exitDisallowedContextReadInDEV() { |
|
{ |
|
isDisallowedContextReadInDEV = false; |
|
} |
|
} |
|
function pushProvider(providerFiber, context, nextValue) { |
|
{ |
|
push(valueCursor, context._currentValue, providerFiber); |
|
context._currentValue = nextValue; |
|
|
|
{ |
|
if (context._currentRenderer !== undefined && context._currentRenderer !== null && context._currentRenderer !== rendererSigil) { |
|
error('Detected multiple renderers concurrently rendering the ' + 'same context provider. This is currently unsupported.'); |
|
} |
|
|
|
context._currentRenderer = rendererSigil; |
|
} |
|
} |
|
} |
|
function popProvider(context, providerFiber) { |
|
var currentValue = valueCursor.current; |
|
pop(valueCursor, providerFiber); |
|
|
|
{ |
|
{ |
|
context._currentValue = currentValue; |
|
} |
|
} |
|
} |
|
function scheduleContextWorkOnParentPath(parent, renderLanes, propagationRoot) { |
|
// Update the child lanes of all the ancestors, including the alternates. |
|
var node = parent; |
|
|
|
while (node !== null) { |
|
var alternate = node.alternate; |
|
|
|
if (!isSubsetOfLanes(node.childLanes, renderLanes)) { |
|
node.childLanes = mergeLanes(node.childLanes, renderLanes); |
|
|
|
if (alternate !== null) { |
|
alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes); |
|
} |
|
} else if (alternate !== null && !isSubsetOfLanes(alternate.childLanes, renderLanes)) { |
|
alternate.childLanes = mergeLanes(alternate.childLanes, renderLanes); |
|
} |
|
|
|
if (node === propagationRoot) { |
|
break; |
|
} |
|
|
|
node = node.return; |
|
} |
|
|
|
{ |
|
if (node !== propagationRoot) { |
|
error('Expected to find the propagation root when scheduling context work. ' + 'This error is likely caused by a bug in React. Please file an issue.'); |
|
} |
|
} |
|
} |
|
function propagateContextChange(workInProgress, context, renderLanes) { |
|
{ |
|
propagateContextChange_eager(workInProgress, context, renderLanes); |
|
} |
|
} |
|
|
|
function propagateContextChange_eager(workInProgress, context, renderLanes) { |
|
|
|
var fiber = workInProgress.child; |
|
|
|
if (fiber !== null) { |
|
// Set the return pointer of the child to the work-in-progress fiber. |
|
fiber.return = workInProgress; |
|
} |
|
|
|
while (fiber !== null) { |
|
var nextFiber = void 0; // Visit this fiber. |
|
|
|
var list = fiber.dependencies; |
|
|
|
if (list !== null) { |
|
nextFiber = fiber.child; |
|
var dependency = list.firstContext; |
|
|
|
while (dependency !== null) { |
|
// Check if the context matches. |
|
if (dependency.context === context) { |
|
// Match! Schedule an update on this fiber. |
|
if (fiber.tag === ClassComponent) { |
|
// Schedule a force update on the work-in-progress. |
|
var lane = pickArbitraryLane(renderLanes); |
|
var update = createUpdate(NoTimestamp, lane); |
|
update.tag = ForceUpdate; // TODO: Because we don't have a work-in-progress, this will add the |
|
// update to the current fiber, too, which means it will persist even if |
|
// this render is thrown away. Since it's a race condition, not sure it's |
|
// worth fixing. |
|
// Inlined `enqueueUpdate` to remove interleaved update check |
|
|
|
var updateQueue = fiber.updateQueue; |
|
|
|
if (updateQueue === null) ; else { |
|
var sharedQueue = updateQueue.shared; |
|
var pending = sharedQueue.pending; |
|
|
|
if (pending === null) { |
|
// This is the first update. Create a circular list. |
|
update.next = update; |
|
} else { |
|
update.next = pending.next; |
|
pending.next = update; |
|
} |
|
|
|
sharedQueue.pending = update; |
|
} |
|
} |
|
|
|
fiber.lanes = mergeLanes(fiber.lanes, renderLanes); |
|
var alternate = fiber.alternate; |
|
|
|
if (alternate !== null) { |
|
alternate.lanes = mergeLanes(alternate.lanes, renderLanes); |
|
} |
|
|
|
scheduleContextWorkOnParentPath(fiber.return, renderLanes, workInProgress); // Mark the updated lanes on the list, too. |
|
|
|
list.lanes = mergeLanes(list.lanes, renderLanes); // Since we already found a match, we can stop traversing the |
|
// dependency list. |
|
|
|
break; |
|
} |
|
|
|
dependency = dependency.next; |
|
} |
|
} else if (fiber.tag === ContextProvider) { |
|
// Don't scan deeper if this is a matching provider |
|
nextFiber = fiber.type === workInProgress.type ? null : fiber.child; |
|
} else if (fiber.tag === DehydratedFragment) { |
|
// If a dehydrated suspense boundary is in this subtree, we don't know |
|
// if it will have any context consumers in it. The best we can do is |
|
// mark it as having updates. |
|
var parentSuspense = fiber.return; |
|
|
|
if (parentSuspense === null) { |
|
throw new Error('We just came from a parent so we must have had a parent. This is a bug in React.'); |
|
} |
|
|
|
parentSuspense.lanes = mergeLanes(parentSuspense.lanes, renderLanes); |
|
var _alternate = parentSuspense.alternate; |
|
|
|
if (_alternate !== null) { |
|
_alternate.lanes = mergeLanes(_alternate.lanes, renderLanes); |
|
} // This is intentionally passing this fiber as the parent |
|
// because we want to schedule this fiber as having work |
|
// on its children. We'll use the childLanes on |
|
// this fiber to indicate that a context has changed. |
|
|
|
|
|
scheduleContextWorkOnParentPath(parentSuspense, renderLanes, workInProgress); |
|
nextFiber = fiber.sibling; |
|
} else { |
|
// Traverse down. |
|
nextFiber = fiber.child; |
|
} |
|
|
|
if (nextFiber !== null) { |
|
// Set the return pointer of the child to the work-in-progress fiber. |
|
nextFiber.return = fiber; |
|
} else { |
|
// No child. Traverse to next sibling. |
|
nextFiber = fiber; |
|
|
|
while (nextFiber !== null) { |
|
if (nextFiber === workInProgress) { |
|
// We're back to the root of this subtree. Exit. |
|
nextFiber = null; |
|
break; |
|
} |
|
|
|
var sibling = nextFiber.sibling; |
|
|
|
if (sibling !== null) { |
|
// Set the return pointer of the sibling to the work-in-progress fiber. |
|
sibling.return = nextFiber.return; |
|
nextFiber = sibling; |
|
break; |
|
} // No more siblings. Traverse up. |
|
|
|
|
|
nextFiber = nextFiber.return; |
|
} |
|
} |
|
|
|
fiber = nextFiber; |
|
} |
|
} |
|
function prepareToReadContext(workInProgress, renderLanes) { |
|
currentlyRenderingFiber = workInProgress; |
|
lastContextDependency = null; |
|
lastFullyObservedContext = null; |
|
var dependencies = workInProgress.dependencies; |
|
|
|
if (dependencies !== null) { |
|
{ |
|
var firstContext = dependencies.firstContext; |
|
|
|
if (firstContext !== null) { |
|
if (includesSomeLane(dependencies.lanes, renderLanes)) { |
|
// Context list has a pending update. Mark that this fiber performed work. |
|
markWorkInProgressReceivedUpdate(); |
|
} // Reset the work-in-progress list |
|
|
|
|
|
dependencies.firstContext = null; |
|
} |
|
} |
|
} |
|
} |
|
function readContext(context) { |
|
{ |
|
// This warning would fire if you read context inside a Hook like useMemo. |
|
// Unlike the class check below, it's not enforced in production for perf. |
|
if (isDisallowedContextReadInDEV) { |
|
error('Context can only be read while React is rendering. ' + 'In classes, you can read it in the render method or getDerivedStateFromProps. ' + 'In function components, you can read it directly in the function body, but not ' + 'inside Hooks like useReducer() or useMemo().'); |
|
} |
|
} |
|
|
|
var value = context._currentValue ; |
|
|
|
if (lastFullyObservedContext === context) ; else { |
|
var contextItem = { |
|
context: context, |
|
memoizedValue: value, |
|
next: null |
|
}; |
|
|
|
if (lastContextDependency === null) { |
|
if (currentlyRenderingFiber === null) { |
|
throw new Error('Context can only be read while React is rendering. ' + 'In classes, you can read it in the render method or getDerivedStateFromProps. ' + 'In function components, you can read it directly in the function body, but not ' + 'inside Hooks like useReducer() or useMemo().'); |
|
} // This is the first dependency for this component. Create a new list. |
|
|
|
|
|
lastContextDependency = contextItem; |
|
currentlyRenderingFiber.dependencies = { |
|
lanes: NoLanes, |
|
firstContext: contextItem |
|
}; |
|
} else { |
|
// Append a new context item. |
|
lastContextDependency = lastContextDependency.next = contextItem; |
|
} |
|
} |
|
|
|
return value; |
|
} |
|
|
|
// render. When this render exits, either because it finishes or because it is |
|
// interrupted, the interleaved updates will be transferred onto the main part |
|
// of the queue. |
|
|
|
var concurrentQueues = null; |
|
function pushConcurrentUpdateQueue(queue) { |
|
if (concurrentQueues === null) { |
|
concurrentQueues = [queue]; |
|
} else { |
|
concurrentQueues.push(queue); |
|
} |
|
} |
|
function finishQueueingConcurrentUpdates() { |
|
// Transfer the interleaved updates onto the main queue. Each queue has a |
|
// `pending` field and an `interleaved` field. When they are not null, they |
|
// point to the last node in a circular linked list. We need to append the |
|
// interleaved list to the end of the pending list by joining them into a |
|
// single, circular list. |
|
if (concurrentQueues !== null) { |
|
for (var i = 0; i < concurrentQueues.length; i++) { |
|
var queue = concurrentQueues[i]; |
|
var lastInterleavedUpdate = queue.interleaved; |
|
|
|
if (lastInterleavedUpdate !== null) { |
|
queue.interleaved = null; |
|
var firstInterleavedUpdate = lastInterleavedUpdate.next; |
|
var lastPendingUpdate = queue.pending; |
|
|
|
if (lastPendingUpdate !== null) { |
|
var firstPendingUpdate = lastPendingUpdate.next; |
|
lastPendingUpdate.next = firstInterleavedUpdate; |
|
lastInterleavedUpdate.next = firstPendingUpdate; |
|
} |
|
|
|
queue.pending = lastInterleavedUpdate; |
|
} |
|
} |
|
|
|
concurrentQueues = null; |
|
} |
|
} |
|
function enqueueConcurrentHookUpdate(fiber, queue, update, lane) { |
|
var interleaved = queue.interleaved; |
|
|
|
if (interleaved === null) { |
|
// This is the first update. Create a circular list. |
|
update.next = update; // At the end of the current render, this queue's interleaved updates will |
|
// be transferred to the pending queue. |
|
|
|
pushConcurrentUpdateQueue(queue); |
|
} else { |
|
update.next = interleaved.next; |
|
interleaved.next = update; |
|
} |
|
|
|
queue.interleaved = update; |
|
return markUpdateLaneFromFiberToRoot(fiber, lane); |
|
} |
|
function enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update, lane) { |
|
var interleaved = queue.interleaved; |
|
|
|
if (interleaved === null) { |
|
// This is the first update. Create a circular list. |
|
update.next = update; // At the end of the current render, this queue's interleaved updates will |
|
// be transferred to the pending queue. |
|
|
|
pushConcurrentUpdateQueue(queue); |
|
} else { |
|
update.next = interleaved.next; |
|
interleaved.next = update; |
|
} |
|
|
|
queue.interleaved = update; |
|
} |
|
function enqueueConcurrentClassUpdate(fiber, queue, update, lane) { |
|
var interleaved = queue.interleaved; |
|
|
|
if (interleaved === null) { |
|
// This is the first update. Create a circular list. |
|
update.next = update; // At the end of the current render, this queue's interleaved updates will |
|
// be transferred to the pending queue. |
|
|
|
pushConcurrentUpdateQueue(queue); |
|
} else { |
|
update.next = interleaved.next; |
|
interleaved.next = update; |
|
} |
|
|
|
queue.interleaved = update; |
|
return markUpdateLaneFromFiberToRoot(fiber, lane); |
|
} |
|
function enqueueConcurrentRenderForLane(fiber, lane) { |
|
return markUpdateLaneFromFiberToRoot(fiber, lane); |
|
} // Calling this function outside this module should only be done for backwards |
|
// compatibility and should always be accompanied by a warning. |
|
|
|
var unsafe_markUpdateLaneFromFiberToRoot = markUpdateLaneFromFiberToRoot; |
|
|
|
function markUpdateLaneFromFiberToRoot(sourceFiber, lane) { |
|
// Update the source fiber's lanes |
|
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); |
|
var alternate = sourceFiber.alternate; |
|
|
|
if (alternate !== null) { |
|
alternate.lanes = mergeLanes(alternate.lanes, lane); |
|
} |
|
|
|
{ |
|
if (alternate === null && (sourceFiber.flags & (Placement | Hydrating)) !== NoFlags) { |
|
warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); |
|
} |
|
} // Walk the parent path to the root and update the child lanes. |
|
|
|
|
|
var node = sourceFiber; |
|
var parent = sourceFiber.return; |
|
|
|
while (parent !== null) { |
|
parent.childLanes = mergeLanes(parent.childLanes, lane); |
|
alternate = parent.alternate; |
|
|
|
if (alternate !== null) { |
|
alternate.childLanes = mergeLanes(alternate.childLanes, lane); |
|
} else { |
|
{ |
|
if ((parent.flags & (Placement | Hydrating)) !== NoFlags) { |
|
warnAboutUpdateOnNotYetMountedFiberInDEV(sourceFiber); |
|
} |
|
} |
|
} |
|
|
|
node = parent; |
|
parent = parent.return; |
|
} |
|
|
|
if (node.tag === HostRoot) { |
|
var root = node.stateNode; |
|
return root; |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
var UpdateState = 0; |
|
var ReplaceState = 1; |
|
var ForceUpdate = 2; |
|
var CaptureUpdate = 3; // Global state that is reset at the beginning of calling `processUpdateQueue`. |
|
// It should only be read right after calling `processUpdateQueue`, via |
|
// `checkHasForceUpdateAfterProcessing`. |
|
|
|
var hasForceUpdate = false; |
|
var didWarnUpdateInsideUpdate; |
|
var currentlyProcessingQueue; |
|
|
|
{ |
|
didWarnUpdateInsideUpdate = false; |
|
currentlyProcessingQueue = null; |
|
} |
|
|
|
function initializeUpdateQueue(fiber) { |
|
var queue = { |
|
baseState: fiber.memoizedState, |
|
firstBaseUpdate: null, |
|
lastBaseUpdate: null, |
|
shared: { |
|
pending: null, |
|
interleaved: null, |
|
lanes: NoLanes |
|
}, |
|
effects: null |
|
}; |
|
fiber.updateQueue = queue; |
|
} |
|
function cloneUpdateQueue(current, workInProgress) { |
|
// Clone the update queue from current. Unless it's already a clone. |
|
var queue = workInProgress.updateQueue; |
|
var currentQueue = current.updateQueue; |
|
|
|
if (queue === currentQueue) { |
|
var clone = { |
|
baseState: currentQueue.baseState, |
|
firstBaseUpdate: currentQueue.firstBaseUpdate, |
|
lastBaseUpdate: currentQueue.lastBaseUpdate, |
|
shared: currentQueue.shared, |
|
effects: currentQueue.effects |
|
}; |
|
workInProgress.updateQueue = clone; |
|
} |
|
} |
|
function createUpdate(eventTime, lane) { |
|
var update = { |
|
eventTime: eventTime, |
|
lane: lane, |
|
tag: UpdateState, |
|
payload: null, |
|
callback: null, |
|
next: null |
|
}; |
|
return update; |
|
} |
|
function enqueueUpdate(fiber, update, lane) { |
|
var updateQueue = fiber.updateQueue; |
|
|
|
if (updateQueue === null) { |
|
// Only occurs if the fiber has been unmounted. |
|
return null; |
|
} |
|
|
|
var sharedQueue = updateQueue.shared; |
|
|
|
{ |
|
if (currentlyProcessingQueue === sharedQueue && !didWarnUpdateInsideUpdate) { |
|
error('An update (setState, replaceState, or forceUpdate) was scheduled ' + 'from inside an update function. Update functions should be pure, ' + 'with zero side-effects. Consider using componentDidUpdate or a ' + 'callback.'); |
|
|
|
didWarnUpdateInsideUpdate = true; |
|
} |
|
} |
|
|
|
if (isUnsafeClassRenderPhaseUpdate()) { |
|
// This is an unsafe render phase update. Add directly to the update |
|
// queue so we can process it immediately during the current render. |
|
var pending = sharedQueue.pending; |
|
|
|
if (pending === null) { |
|
// This is the first update. Create a circular list. |
|
update.next = update; |
|
} else { |
|
update.next = pending.next; |
|
pending.next = update; |
|
} |
|
|
|
sharedQueue.pending = update; // Update the childLanes even though we're most likely already rendering |
|
// this fiber. This is for backwards compatibility in the case where you |
|
// update a different component during render phase than the one that is |
|
// currently renderings (a pattern that is accompanied by a warning). |
|
|
|
return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane); |
|
} else { |
|
return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane); |
|
} |
|
} |
|
function entangleTransitions(root, fiber, lane) { |
|
var updateQueue = fiber.updateQueue; |
|
|
|
if (updateQueue === null) { |
|
// Only occurs if the fiber has been unmounted. |
|
return; |
|
} |
|
|
|
var sharedQueue = updateQueue.shared; |
|
|
|
if (isTransitionLane(lane)) { |
|
var queueLanes = sharedQueue.lanes; // If any entangled lanes are no longer pending on the root, then they must |
|
// have finished. We can remove them from the shared queue, which represents |
|
// a superset of the actually pending lanes. In some cases we may entangle |
|
// more than we need to, but that's OK. In fact it's worse if we *don't* |
|
// entangle when we should. |
|
|
|
queueLanes = intersectLanes(queueLanes, root.pendingLanes); // Entangle the new transition lane with the other transition lanes. |
|
|
|
var newQueueLanes = mergeLanes(queueLanes, lane); |
|
sharedQueue.lanes = newQueueLanes; // Even if queue.lanes already include lane, we don't know for certain if |
|
// the lane finished since the last time we entangled it. So we need to |
|
// entangle it again, just to be sure. |
|
|
|
markRootEntangled(root, newQueueLanes); |
|
} |
|
} |
|
function enqueueCapturedUpdate(workInProgress, capturedUpdate) { |
|
// Captured updates are updates that are thrown by a child during the render |
|
// phase. They should be discarded if the render is aborted. Therefore, |
|
// we should only put them on the work-in-progress queue, not the current one. |
|
var queue = workInProgress.updateQueue; // Check if the work-in-progress queue is a clone. |
|
|
|
var current = workInProgress.alternate; |
|
|
|
if (current !== null) { |
|
var currentQueue = current.updateQueue; |
|
|
|
if (queue === currentQueue) { |
|
// The work-in-progress queue is the same as current. This happens when |
|
// we bail out on a parent fiber that then captures an error thrown by |
|
// a child. Since we want to append the update only to the work-in |
|
// -progress queue, we need to clone the updates. We usually clone during |
|
// processUpdateQueue, but that didn't happen in this case because we |
|
// skipped over the parent when we bailed out. |
|
var newFirst = null; |
|
var newLast = null; |
|
var firstBaseUpdate = queue.firstBaseUpdate; |
|
|
|
if (firstBaseUpdate !== null) { |
|
// Loop through the updates and clone them. |
|
var update = firstBaseUpdate; |
|
|
|
do { |
|
var clone = { |
|
eventTime: update.eventTime, |
|
lane: update.lane, |
|
tag: update.tag, |
|
payload: update.payload, |
|
callback: update.callback, |
|
next: null |
|
}; |
|
|
|
if (newLast === null) { |
|
newFirst = newLast = clone; |
|
} else { |
|
newLast.next = clone; |
|
newLast = clone; |
|
} |
|
|
|
update = update.next; |
|
} while (update !== null); // Append the captured update the end of the cloned list. |
|
|
|
|
|
if (newLast === null) { |
|
newFirst = newLast = capturedUpdate; |
|
} else { |
|
newLast.next = capturedUpdate; |
|
newLast = capturedUpdate; |
|
} |
|
} else { |
|
// There are no base updates. |
|
newFirst = newLast = capturedUpdate; |
|
} |
|
|
|
queue = { |
|
baseState: currentQueue.baseState, |
|
firstBaseUpdate: newFirst, |
|
lastBaseUpdate: newLast, |
|
shared: currentQueue.shared, |
|
effects: currentQueue.effects |
|
}; |
|
workInProgress.updateQueue = queue; |
|
return; |
|
} |
|
} // Append the update to the end of the list. |
|
|
|
|
|
var lastBaseUpdate = queue.lastBaseUpdate; |
|
|
|
if (lastBaseUpdate === null) { |
|
queue.firstBaseUpdate = capturedUpdate; |
|
} else { |
|
lastBaseUpdate.next = capturedUpdate; |
|
} |
|
|
|
queue.lastBaseUpdate = capturedUpdate; |
|
} |
|
|
|
function getStateFromUpdate(workInProgress, queue, update, prevState, nextProps, instance) { |
|
switch (update.tag) { |
|
case ReplaceState: |
|
{ |
|
var payload = update.payload; |
|
|
|
if (typeof payload === 'function') { |
|
// Updater function |
|
{ |
|
enterDisallowedContextReadInDEV(); |
|
} |
|
|
|
var nextState = payload.call(instance, prevState, nextProps); |
|
|
|
{ |
|
if ( workInProgress.mode & StrictLegacyMode) { |
|
setIsStrictModeForDevtools(true); |
|
|
|
try { |
|
payload.call(instance, prevState, nextProps); |
|
} finally { |
|
setIsStrictModeForDevtools(false); |
|
} |
|
} |
|
|
|
exitDisallowedContextReadInDEV(); |
|
} |
|
|
|
return nextState; |
|
} // State object |
|
|
|
|
|
return payload; |
|
} |
|
|
|
case CaptureUpdate: |
|
{ |
|
workInProgress.flags = workInProgress.flags & ~ShouldCapture | DidCapture; |
|
} |
|
// Intentional fallthrough |
|
|
|
case UpdateState: |
|
{ |
|
var _payload = update.payload; |
|
var partialState; |
|
|
|
if (typeof _payload === 'function') { |
|
// Updater function |
|
{ |
|
enterDisallowedContextReadInDEV(); |
|
} |
|
|
|
partialState = _payload.call(instance, prevState, nextProps); |
|
|
|
{ |
|
if ( workInProgress.mode & StrictLegacyMode) { |
|
setIsStrictModeForDevtools(true); |
|
|
|
try { |
|
_payload.call(instance, prevState, nextProps); |
|
} finally { |
|
setIsStrictModeForDevtools(false); |
|
} |
|
} |
|
|
|
exitDisallowedContextReadInDEV(); |
|
} |
|
} else { |
|
// Partial state object |
|
partialState = _payload; |
|
} |
|
|
|
if (partialState === null || partialState === undefined) { |
|
// Null and undefined are treated as no-ops. |
|
return prevState; |
|
} // Merge the partial state and the previous state. |
|
|
|
|
|
return assign({}, prevState, partialState); |
|
} |
|
|
|
case ForceUpdate: |
|
{ |
|
hasForceUpdate = true; |
|
return prevState; |
|
} |
|
} |
|
|
|
return prevState; |
|
} |
|
|
|
function processUpdateQueue(workInProgress, props, instance, renderLanes) { |
|
// This is always non-null on a ClassComponent or HostRoot |
|
var queue = workInProgress.updateQueue; |
|
hasForceUpdate = false; |
|
|
|
{ |
|
currentlyProcessingQueue = queue.shared; |
|
} |
|
|
|
var firstBaseUpdate = queue.firstBaseUpdate; |
|
var lastBaseUpdate = queue.lastBaseUpdate; // Check if there are pending updates. If so, transfer them to the base queue. |
|
|
|
var pendingQueue = queue.shared.pending; |
|
|
|
if (pendingQueue !== null) { |
|
queue.shared.pending = null; // The pending queue is circular. Disconnect the pointer between first |
|
// and last so that it's non-circular. |
|
|
|
var lastPendingUpdate = pendingQueue; |
|
var firstPendingUpdate = lastPendingUpdate.next; |
|
lastPendingUpdate.next = null; // Append pending updates to base queue |
|
|
|
if (lastBaseUpdate === null) { |
|
firstBaseUpdate = firstPendingUpdate; |
|
} else { |
|
lastBaseUpdate.next = firstPendingUpdate; |
|
} |
|
|
|
lastBaseUpdate = lastPendingUpdate; // If there's a current queue, and it's different from the base queue, then |
|
// we need to transfer the updates to that queue, too. Because the base |
|
// queue is a singly-linked list with no cycles, we can append to both |
|
// lists and take advantage of structural sharing. |
|
// TODO: Pass `current` as argument |
|
|
|
var current = workInProgress.alternate; |
|
|
|
if (current !== null) { |
|
// This is always non-null on a ClassComponent or HostRoot |
|
var currentQueue = current.updateQueue; |
|
var currentLastBaseUpdate = currentQueue.lastBaseUpdate; |
|
|
|
if (currentLastBaseUpdate !== lastBaseUpdate) { |
|
if (currentLastBaseUpdate === null) { |
|
currentQueue.firstBaseUpdate = firstPendingUpdate; |
|
} else { |
|
currentLastBaseUpdate.next = firstPendingUpdate; |
|
} |
|
|
|
currentQueue.lastBaseUpdate = lastPendingUpdate; |
|
} |
|
} |
|
} // These values may change as we process the queue. |
|
|
|
|
|
if (firstBaseUpdate !== null) { |
|
// Iterate through the list of updates to compute the result. |
|
var newState = queue.baseState; // TODO: Don't need to accumulate this. Instead, we can remove renderLanes |
|
// from the original lanes. |
|
|
|
var newLanes = NoLanes; |
|
var newBaseState = null; |
|
var newFirstBaseUpdate = null; |
|
var newLastBaseUpdate = null; |
|
var update = firstBaseUpdate; |
|
|
|
do { |
|
var updateLane = update.lane; |
|
var updateEventTime = update.eventTime; |
|
|
|
if (!isSubsetOfLanes(renderLanes, updateLane)) { |
|
// Priority is insufficient. Skip this update. If this is the first |
|
// skipped update, the previous update/state is the new base |
|
// update/state. |
|
var clone = { |
|
eventTime: updateEventTime, |
|
lane: updateLane, |
|
tag: update.tag, |
|
payload: update.payload, |
|
callback: update.callback, |
|
next: null |
|
}; |
|
|
|
if (newLastBaseUpdate === null) { |
|
newFirstBaseUpdate = newLastBaseUpdate = clone; |
|
newBaseState = newState; |
|
} else { |
|
newLastBaseUpdate = newLastBaseUpdate.next = clone; |
|
} // Update the remaining priority in the queue. |
|
|
|
|
|
newLanes = mergeLanes(newLanes, updateLane); |
|
} else { |
|
// This update does have sufficient priority. |
|
if (newLastBaseUpdate !== null) { |
|
var _clone = { |
|
eventTime: updateEventTime, |
|
// This update is going to be committed so we never want uncommit |
|
// it. Using NoLane works because 0 is a subset of all bitmasks, so |
|
// this will never be skipped by the check above. |
|
lane: NoLane, |
|
tag: update.tag, |
|
payload: update.payload, |
|
callback: update.callback, |
|
next: null |
|
}; |
|
newLastBaseUpdate = newLastBaseUpdate.next = _clone; |
|
} // Process this update. |
|
|
|
|
|
newState = getStateFromUpdate(workInProgress, queue, update, newState, props, instance); |
|
var callback = update.callback; |
|
|
|
if (callback !== null && // If the update was already committed, we should not queue its |
|
// callback again. |
|
update.lane !== NoLane) { |
|
workInProgress.flags |= Callback; |
|
var effects = queue.effects; |
|
|
|
if (effects === null) { |
|
queue.effects = [update]; |
|
} else { |
|
effects.push(update); |
|
} |
|
} |
|
} |
|
|
|
update = update.next; |
|
|
|
if (update === null) { |
|
pendingQueue = queue.shared.pending; |
|
|
|
if (pendingQueue === null) { |
|
break; |
|
} else { |
|
// An update was scheduled from inside a reducer. Add the new |
|
// pending updates to the end of the list and keep processing. |
|
var _lastPendingUpdate = pendingQueue; // Intentionally unsound. Pending updates form a circular list, but we |
|
// unravel them when transferring them to the base queue. |
|
|
|
var _firstPendingUpdate = _lastPendingUpdate.next; |
|
_lastPendingUpdate.next = null; |
|
update = _firstPendingUpdate; |
|
queue.lastBaseUpdate = _lastPendingUpdate; |
|
queue.shared.pending = null; |
|
} |
|
} |
|
} while (true); |
|
|
|
if (newLastBaseUpdate === null) { |
|
newBaseState = newState; |
|
} |
|
|
|
queue.baseState = newBaseState; |
|
queue.firstBaseUpdate = newFirstBaseUpdate; |
|
queue.lastBaseUpdate = newLastBaseUpdate; // Interleaved updates are stored on a separate queue. We aren't going to |
|
// process them during this render, but we do need to track which lanes |
|
// are remaining. |
|
|
|
var lastInterleaved = queue.shared.interleaved; |
|
|
|
if (lastInterleaved !== null) { |
|
var interleaved = lastInterleaved; |
|
|
|
do { |
|
newLanes = mergeLanes(newLanes, interleaved.lane); |
|
interleaved = interleaved.next; |
|
} while (interleaved !== lastInterleaved); |
|
} else if (firstBaseUpdate === null) { |
|
// `queue.lanes` is used for entangling transitions. We can set it back to |
|
// zero once the queue is empty. |
|
queue.shared.lanes = NoLanes; |
|
} // Set the remaining expiration time to be whatever is remaining in the queue. |
|
// This should be fine because the only two other things that contribute to |
|
// expiration time are props and context. We're already in the middle of the |
|
// begin phase by the time we start processing the queue, so we've already |
|
// dealt with the props. Context in components that specify |
|
// shouldComponentUpdate is tricky; but we'll have to account for |
|
// that regardless. |
|
|
|
|
|
markSkippedUpdateLanes(newLanes); |
|
workInProgress.lanes = newLanes; |
|
workInProgress.memoizedState = newState; |
|
} |
|
|
|
{ |
|
currentlyProcessingQueue = null; |
|
} |
|
} |
|
|
|
function callCallback(callback, context) { |
|
if (typeof callback !== 'function') { |
|
throw new Error('Invalid argument passed as callback. Expected a function. Instead ' + ("received: " + callback)); |
|
} |
|
|
|
callback.call(context); |
|
} |
|
|
|
function resetHasForceUpdateBeforeProcessing() { |
|
hasForceUpdate = false; |
|
} |
|
function checkHasForceUpdateAfterProcessing() { |
|
return hasForceUpdate; |
|
} |
|
function commitUpdateQueue(finishedWork, finishedQueue, instance) { |
|
// Commit the effects |
|
var effects = finishedQueue.effects; |
|
finishedQueue.effects = null; |
|
|
|
if (effects !== null) { |
|
for (var i = 0; i < effects.length; i++) { |
|
var effect = effects[i]; |
|
var callback = effect.callback; |
|
|
|
if (callback !== null) { |
|
effect.callback = null; |
|
callCallback(callback, instance); |
|
} |
|
} |
|
} |
|
} |
|
|
|
var fakeInternalInstance = {}; // React.Component uses a shared frozen object by default. |
|
// We'll use it to determine whether we need to initialize legacy refs. |
|
|
|
var emptyRefsObject = new React.Component().refs; |
|
var didWarnAboutStateAssignmentForComponent; |
|
var didWarnAboutUninitializedState; |
|
var didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate; |
|
var didWarnAboutLegacyLifecyclesAndDerivedState; |
|
var didWarnAboutUndefinedDerivedState; |
|
var warnOnUndefinedDerivedState; |
|
var warnOnInvalidCallback; |
|
var didWarnAboutDirectlyAssigningPropsToState; |
|
var didWarnAboutContextTypeAndContextTypes; |
|
var didWarnAboutInvalidateContextType; |
|
|
|
{ |
|
didWarnAboutStateAssignmentForComponent = new Set(); |
|
didWarnAboutUninitializedState = new Set(); |
|
didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate = new Set(); |
|
didWarnAboutLegacyLifecyclesAndDerivedState = new Set(); |
|
didWarnAboutDirectlyAssigningPropsToState = new Set(); |
|
didWarnAboutUndefinedDerivedState = new Set(); |
|
didWarnAboutContextTypeAndContextTypes = new Set(); |
|
didWarnAboutInvalidateContextType = new Set(); |
|
var didWarnOnInvalidCallback = new Set(); |
|
|
|
warnOnInvalidCallback = function (callback, callerName) { |
|
if (callback === null || typeof callback === 'function') { |
|
return; |
|
} |
|
|
|
var key = callerName + '_' + callback; |
|
|
|
if (!didWarnOnInvalidCallback.has(key)) { |
|
didWarnOnInvalidCallback.add(key); |
|
|
|
error('%s(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callerName, callback); |
|
} |
|
}; |
|
|
|
warnOnUndefinedDerivedState = function (type, partialState) { |
|
if (partialState === undefined) { |
|
var componentName = getComponentNameFromType(type) || 'Component'; |
|
|
|
if (!didWarnAboutUndefinedDerivedState.has(componentName)) { |
|
didWarnAboutUndefinedDerivedState.add(componentName); |
|
|
|
error('%s.getDerivedStateFromProps(): A valid state object (or null) must be returned. ' + 'You have returned undefined.', componentName); |
|
} |
|
} |
|
}; // This is so gross but it's at least non-critical and can be removed if |
|
// it causes problems. This is meant to give a nicer error message for |
|
// ReactDOM15.unstable_renderSubtreeIntoContainer(reactDOM16Component, |
|
// ...)) which otherwise throws a "_processChildContext is not a function" |
|
// exception. |
|
|
|
|
|
Object.defineProperty(fakeInternalInstance, '_processChildContext', { |
|
enumerable: false, |
|
value: function () { |
|
throw new Error('_processChildContext is not available in React 16+. This likely ' + 'means you have multiple copies of React and are attempting to nest ' + 'a React 15 tree inside a React 16 tree using ' + "unstable_renderSubtreeIntoContainer, which isn't supported. Try " + 'to make sure you have only one copy of React (and ideally, switch ' + 'to ReactDOM.createPortal).'); |
|
} |
|
}); |
|
Object.freeze(fakeInternalInstance); |
|
} |
|
|
|
function applyDerivedStateFromProps(workInProgress, ctor, getDerivedStateFromProps, nextProps) { |
|
var prevState = workInProgress.memoizedState; |
|
var partialState = getDerivedStateFromProps(nextProps, prevState); |
|
|
|
{ |
|
if ( workInProgress.mode & StrictLegacyMode) { |
|
setIsStrictModeForDevtools(true); |
|
|
|
try { |
|
// Invoke the function an extra time to help detect side-effects. |
|
partialState = getDerivedStateFromProps(nextProps, prevState); |
|
} finally { |
|
setIsStrictModeForDevtools(false); |
|
} |
|
} |
|
|
|
warnOnUndefinedDerivedState(ctor, partialState); |
|
} // Merge the partial state and the previous state. |
|
|
|
|
|
var memoizedState = partialState === null || partialState === undefined ? prevState : assign({}, prevState, partialState); |
|
workInProgress.memoizedState = memoizedState; // Once the update queue is empty, persist the derived state onto the |
|
// base state. |
|
|
|
if (workInProgress.lanes === NoLanes) { |
|
// Queue is always non-null for classes |
|
var updateQueue = workInProgress.updateQueue; |
|
updateQueue.baseState = memoizedState; |
|
} |
|
} |
|
|
|
var classComponentUpdater = { |
|
isMounted: isMounted, |
|
enqueueSetState: function (inst, payload, callback) { |
|
var fiber = get(inst); |
|
var eventTime = requestEventTime(); |
|
var lane = requestUpdateLane(fiber); |
|
var update = createUpdate(eventTime, lane); |
|
update.payload = payload; |
|
|
|
if (callback !== undefined && callback !== null) { |
|
{ |
|
warnOnInvalidCallback(callback, 'setState'); |
|
} |
|
|
|
update.callback = callback; |
|
} |
|
|
|
var root = enqueueUpdate(fiber, update, lane); |
|
|
|
if (root !== null) { |
|
scheduleUpdateOnFiber(root, fiber, lane, eventTime); |
|
entangleTransitions(root, fiber, lane); |
|
} |
|
|
|
{ |
|
markStateUpdateScheduled(fiber, lane); |
|
} |
|
}, |
|
enqueueReplaceState: function (inst, payload, callback) { |
|
var fiber = get(inst); |
|
var eventTime = requestEventTime(); |
|
var lane = requestUpdateLane(fiber); |
|
var update = createUpdate(eventTime, lane); |
|
update.tag = ReplaceState; |
|
update.payload = payload; |
|
|
|
if (callback !== undefined && callback !== null) { |
|
{ |
|
warnOnInvalidCallback(callback, 'replaceState'); |
|
} |
|
|
|
update.callback = callback; |
|
} |
|
|
|
var root = enqueueUpdate(fiber, update, lane); |
|
|
|
if (root !== null) { |
|
scheduleUpdateOnFiber(root, fiber, lane, eventTime); |
|
entangleTransitions(root, fiber, lane); |
|
} |
|
|
|
{ |
|
markStateUpdateScheduled(fiber, lane); |
|
} |
|
}, |
|
enqueueForceUpdate: function (inst, callback) { |
|
var fiber = get(inst); |
|
var eventTime = requestEventTime(); |
|
var lane = requestUpdateLane(fiber); |
|
var update = createUpdate(eventTime, lane); |
|
update.tag = ForceUpdate; |
|
|
|
if (callback !== undefined && callback !== null) { |
|
{ |
|
warnOnInvalidCallback(callback, 'forceUpdate'); |
|
} |
|
|
|
update.callback = callback; |
|
} |
|
|
|
var root = enqueueUpdate(fiber, update, lane); |
|
|
|
if (root !== null) { |
|
scheduleUpdateOnFiber(root, fiber, lane, eventTime); |
|
entangleTransitions(root, fiber, lane); |
|
} |
|
|
|
{ |
|
markForceUpdateScheduled(fiber, lane); |
|
} |
|
} |
|
}; |
|
|
|
function checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext) { |
|
var instance = workInProgress.stateNode; |
|
|
|
if (typeof instance.shouldComponentUpdate === 'function') { |
|
var shouldUpdate = instance.shouldComponentUpdate(newProps, newState, nextContext); |
|
|
|
{ |
|
if ( workInProgress.mode & StrictLegacyMode) { |
|
setIsStrictModeForDevtools(true); |
|
|
|
try { |
|
// Invoke the function an extra time to help detect side-effects. |
|
shouldUpdate = instance.shouldComponentUpdate(newProps, newState, nextContext); |
|
} finally { |
|
setIsStrictModeForDevtools(false); |
|
} |
|
} |
|
|
|
if (shouldUpdate === undefined) { |
|
error('%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', getComponentNameFromType(ctor) || 'Component'); |
|
} |
|
} |
|
|
|
return shouldUpdate; |
|
} |
|
|
|
if (ctor.prototype && ctor.prototype.isPureReactComponent) { |
|
return !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function checkClassInstance(workInProgress, ctor, newProps) { |
|
var instance = workInProgress.stateNode; |
|
|
|
{ |
|
var name = getComponentNameFromType(ctor) || 'Component'; |
|
var renderPresent = instance.render; |
|
|
|
if (!renderPresent) { |
|
if (ctor.prototype && typeof ctor.prototype.render === 'function') { |
|
error('%s(...): No `render` method found on the returned component ' + 'instance: did you accidentally return an object from the constructor?', name); |
|
} else { |
|
error('%s(...): No `render` method found on the returned component ' + 'instance: you may have forgotten to define `render`.', name); |
|
} |
|
} |
|
|
|
if (instance.getInitialState && !instance.getInitialState.isReactClassApproved && !instance.state) { |
|
error('getInitialState was defined on %s, a plain JavaScript class. ' + 'This is only supported for classes created using React.createClass. ' + 'Did you mean to define a state property instead?', name); |
|
} |
|
|
|
if (instance.getDefaultProps && !instance.getDefaultProps.isReactClassApproved) { |
|
error('getDefaultProps was defined on %s, a plain JavaScript class. ' + 'This is only supported for classes created using React.createClass. ' + 'Use a static property to define defaultProps instead.', name); |
|
} |
|
|
|
if (instance.propTypes) { |
|
error('propTypes was defined as an instance property on %s. Use a static ' + 'property to define propTypes instead.', name); |
|
} |
|
|
|
if (instance.contextType) { |
|
error('contextType was defined as an instance property on %s. Use a static ' + 'property to define contextType instead.', name); |
|
} |
|
|
|
{ |
|
if (instance.contextTypes) { |
|
error('contextTypes was defined as an instance property on %s. Use a static ' + 'property to define contextTypes instead.', name); |
|
} |
|
|
|
if (ctor.contextType && ctor.contextTypes && !didWarnAboutContextTypeAndContextTypes.has(ctor)) { |
|
didWarnAboutContextTypeAndContextTypes.add(ctor); |
|
|
|
error('%s declares both contextTypes and contextType static properties. ' + 'The legacy contextTypes property will be ignored.', name); |
|
} |
|
} |
|
|
|
if (typeof instance.componentShouldUpdate === 'function') { |
|
error('%s has a method called ' + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + 'The name is phrased as a question because the function is ' + 'expected to return a value.', name); |
|
} |
|
|
|
if (ctor.prototype && ctor.prototype.isPureReactComponent && typeof instance.shouldComponentUpdate !== 'undefined') { |
|
error('%s has a method called shouldComponentUpdate(). ' + 'shouldComponentUpdate should not be used when extending React.PureComponent. ' + 'Please extend React.Component if shouldComponentUpdate is used.', getComponentNameFromType(ctor) || 'A pure component'); |
|
} |
|
|
|
if (typeof instance.componentDidUnmount === 'function') { |
|
error('%s has a method called ' + 'componentDidUnmount(). But there is no such lifecycle method. ' + 'Did you mean componentWillUnmount()?', name); |
|
} |
|
|
|
if (typeof instance.componentDidReceiveProps === 'function') { |
|
error('%s has a method called ' + 'componentDidReceiveProps(). But there is no such lifecycle method. ' + 'If you meant to update the state in response to changing props, ' + 'use componentWillReceiveProps(). If you meant to fetch data or ' + 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().', name); |
|
} |
|
|
|
if (typeof instance.componentWillRecieveProps === 'function') { |
|
error('%s has a method called ' + 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', name); |
|
} |
|
|
|
if (typeof instance.UNSAFE_componentWillRecieveProps === 'function') { |
|
error('%s has a method called ' + 'UNSAFE_componentWillRecieveProps(). Did you mean UNSAFE_componentWillReceiveProps()?', name); |
|
} |
|
|
|
var hasMutatedProps = instance.props !== newProps; |
|
|
|
if (instance.props !== undefined && hasMutatedProps) { |
|
error('%s(...): When calling super() in `%s`, make sure to pass ' + "up the same props that your component's constructor was passed.", name, name); |
|
} |
|
|
|
if (instance.defaultProps) { |
|
error('Setting defaultProps as an instance property on %s is not supported and will be ignored.' + ' Instead, define defaultProps as a static property on %s.', name, name); |
|
} |
|
|
|
if (typeof instance.getSnapshotBeforeUpdate === 'function' && typeof instance.componentDidUpdate !== 'function' && !didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.has(ctor)) { |
|
didWarnAboutGetSnapshotBeforeUpdateWithoutDidUpdate.add(ctor); |
|
|
|
error('%s: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' + 'This component defines getSnapshotBeforeUpdate() only.', getComponentNameFromType(ctor)); |
|
} |
|
|
|
if (typeof instance.getDerivedStateFromProps === 'function') { |
|
error('%s: getDerivedStateFromProps() is defined as an instance method ' + 'and will be ignored. Instead, declare it as a static method.', name); |
|
} |
|
|
|
if (typeof instance.getDerivedStateFromError === 'function') { |
|
error('%s: getDerivedStateFromError() is defined as an instance method ' + 'and will be ignored. Instead, declare it as a static method.', name); |
|
} |
|
|
|
if (typeof ctor.getSnapshotBeforeUpdate === 'function') { |
|
error('%s: getSnapshotBeforeUpdate() is defined as a static method ' + 'and will be ignored. Instead, declare it as an instance method.', name); |
|
} |
|
|
|
var _state = instance.state; |
|
|
|
if (_state && (typeof _state !== 'object' || isArray(_state))) { |
|
error('%s.state: must be set to an object or null', name); |
|
} |
|
|
|
if (typeof instance.getChildContext === 'function' && typeof ctor.childContextTypes !== 'object') { |
|
error('%s.getChildContext(): childContextTypes must be defined in order to ' + 'use getChildContext().', name); |
|
} |
|
} |
|
} |
|
|
|
function adoptClassInstance(workInProgress, instance) { |
|
instance.updater = classComponentUpdater; |
|
workInProgress.stateNode = instance; // The instance needs access to the fiber so that it can schedule updates |
|
|
|
set(instance, workInProgress); |
|
|
|
{ |
|
instance._reactInternalInstance = fakeInternalInstance; |
|
} |
|
} |
|
|
|
function constructClassInstance(workInProgress, ctor, props) { |
|
var isLegacyContextConsumer = false; |
|
var unmaskedContext = emptyContextObject; |
|
var context = emptyContextObject; |
|
var contextType = ctor.contextType; |
|
|
|
{ |
|
if ('contextType' in ctor) { |
|
var isValid = // Allow null for conditional declaration |
|
contextType === null || contextType !== undefined && contextType.$$typeof === REACT_CONTEXT_TYPE && contextType._context === undefined; // Not a <Context.Consumer> |
|
|
|
if (!isValid && !didWarnAboutInvalidateContextType.has(ctor)) { |
|
didWarnAboutInvalidateContextType.add(ctor); |
|
var addendum = ''; |
|
|
|
if (contextType === undefined) { |
|
addendum = ' However, it is set to undefined. ' + 'This can be caused by a typo or by mixing up named and default imports. ' + 'This can also happen due to a circular dependency, so ' + 'try moving the createContext() call to a separate file.'; |
|
} else if (typeof contextType !== 'object') { |
|
addendum = ' However, it is set to a ' + typeof contextType + '.'; |
|
} else if (contextType.$$typeof === REACT_PROVIDER_TYPE) { |
|
addendum = ' Did you accidentally pass the Context.Provider instead?'; |
|
} else if (contextType._context !== undefined) { |
|
// <Context.Consumer> |
|
addendum = ' Did you accidentally pass the Context.Consumer instead?'; |
|
} else { |
|
addendum = ' However, it is set to an object with keys {' + Object.keys(contextType).join(', ') + '}.'; |
|
} |
|
|
|
error('%s defines an invalid contextType. ' + 'contextType should point to the Context object returned by React.createContext().%s', getComponentNameFromType(ctor) || 'Component', addendum); |
|
} |
|
} |
|
} |
|
|
|
if (typeof contextType === 'object' && contextType !== null) { |
|
context = readContext(contextType); |
|
} else { |
|
unmaskedContext = getUnmaskedContext(workInProgress, ctor, true); |
|
var contextTypes = ctor.contextTypes; |
|
isLegacyContextConsumer = contextTypes !== null && contextTypes !== undefined; |
|
context = isLegacyContextConsumer ? getMaskedContext(workInProgress, unmaskedContext) : emptyContextObject; |
|
} |
|
|
|
var instance = new ctor(props, context); // Instantiate twice to help detect side-effects. |
|
|
|
{ |
|
if ( workInProgress.mode & StrictLegacyMode) { |
|
setIsStrictModeForDevtools(true); |
|
|
|
try { |
|
instance = new ctor(props, context); // eslint-disable-line no-new |
|
} finally { |
|
setIsStrictModeForDevtools(false); |
|
} |
|
} |
|
} |
|
|
|
var state = workInProgress.memoizedState = instance.state !== null && instance.state !== undefined ? instance.state : null; |
|
adoptClassInstance(workInProgress, instance); |
|
|
|
{ |
|
if (typeof ctor.getDerivedStateFromProps === 'function' && state === null) { |
|
var componentName = getComponentNameFromType(ctor) || 'Component'; |
|
|
|
if (!didWarnAboutUninitializedState.has(componentName)) { |
|
didWarnAboutUninitializedState.add(componentName); |
|
|
|
error('`%s` uses `getDerivedStateFromProps` but its initial state is ' + '%s. This is not recommended. Instead, define the initial state by ' + 'assigning an object to `this.state` in the constructor of `%s`. ' + 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.', componentName, instance.state === null ? 'null' : 'undefined', componentName); |
|
} |
|
} // If new component APIs are defined, "unsafe" lifecycles won't be called. |
|
// Warn about these lifecycles if they are present. |
|
// Don't warn about react-lifecycles-compat polyfilled methods though. |
|
|
|
|
|
if (typeof ctor.getDerivedStateFromProps === 'function' || typeof instance.getSnapshotBeforeUpdate === 'function') { |
|
var foundWillMountName = null; |
|
var foundWillReceivePropsName = null; |
|
var foundWillUpdateName = null; |
|
|
|
if (typeof instance.componentWillMount === 'function' && instance.componentWillMount.__suppressDeprecationWarning !== true) { |
|
foundWillMountName = 'componentWillMount'; |
|
} else if (typeof instance.UNSAFE_componentWillMount === 'function') { |
|
foundWillMountName = 'UNSAFE_componentWillMount'; |
|
} |
|
|
|
if (typeof instance.componentWillReceiveProps === 'function' && instance.componentWillReceiveProps.__suppressDeprecationWarning !== true) { |
|
foundWillReceivePropsName = 'componentWillReceiveProps'; |
|
} else if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') { |
|
foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps'; |
|
} |
|
|
|
if (typeof instance.componentWillUpdate === 'function' && instance.componentWillUpdate.__suppressDeprecationWarning !== true) { |
|
foundWillUpdateName = 'componentWillUpdate'; |
|
} else if (typeof instance.UNSAFE_componentWillUpdate === 'function') { |
|
foundWillUpdateName = 'UNSAFE_componentWillUpdate'; |
|
} |
|
|
|
if (foundWillMountName !== null || foundWillReceivePropsName !== null || foundWillUpdateName !== null) { |
|
var _componentName = getComponentNameFromType(ctor) || 'Component'; |
|
|
|
var newApiName = typeof ctor.getDerivedStateFromProps === 'function' ? 'getDerivedStateFromProps()' : 'getSnapshotBeforeUpdate()'; |
|
|
|
if (!didWarnAboutLegacyLifecyclesAndDerivedState.has(_componentName)) { |
|
didWarnAboutLegacyLifecyclesAndDerivedState.add(_componentName); |
|
|
|
error('Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + '%s uses %s but also contains the following legacy lifecycles:%s%s%s\n\n' + 'The above lifecycles should be removed. Learn more about this warning here:\n' + 'https://reactjs.org/link/unsafe-component-lifecycles', _componentName, newApiName, foundWillMountName !== null ? "\n " + foundWillMountName : '', foundWillReceivePropsName !== null ? "\n " + foundWillReceivePropsName : '', foundWillUpdateName !== null ? "\n " + foundWillUpdateName : ''); |
|
} |
|
} |
|
} |
|
} // Cache unmasked context so we can avoid recreating masked context unless necessary. |
|
// ReactFiberContext usually updates this cache but can't for newly-created instances. |
|
|
|
|
|
if (isLegacyContextConsumer) { |
|
cacheContext(workInProgress, unmaskedContext, context); |
|
} |
|
|
|
return instance; |
|
} |
|
|
|
function callComponentWillMount(workInProgress, instance) { |
|
var oldState = instance.state; |
|
|
|
if (typeof instance.componentWillMount === 'function') { |
|
instance.componentWillMount(); |
|
} |
|
|
|
if (typeof instance.UNSAFE_componentWillMount === 'function') { |
|
instance.UNSAFE_componentWillMount(); |
|
} |
|
|
|
if (oldState !== instance.state) { |
|
{ |
|
error('%s.componentWillMount(): Assigning directly to this.state is ' + "deprecated (except inside a component's " + 'constructor). Use setState instead.', getComponentNameFromFiber(workInProgress) || 'Component'); |
|
} |
|
|
|
classComponentUpdater.enqueueReplaceState(instance, instance.state, null); |
|
} |
|
} |
|
|
|
function callComponentWillReceiveProps(workInProgress, instance, newProps, nextContext) { |
|
var oldState = instance.state; |
|
|
|
if (typeof instance.componentWillReceiveProps === 'function') { |
|
instance.componentWillReceiveProps(newProps, nextContext); |
|
} |
|
|
|
if (typeof instance.UNSAFE_componentWillReceiveProps === 'function') { |
|
instance.UNSAFE_componentWillReceiveProps(newProps, nextContext); |
|
} |
|
|
|
if (instance.state !== oldState) { |
|
{ |
|
var componentName = getComponentNameFromFiber(workInProgress) || 'Component'; |
|
|
|
if (!didWarnAboutStateAssignmentForComponent.has(componentName)) { |
|
didWarnAboutStateAssignmentForComponent.add(componentName); |
|
|
|
error('%s.componentWillReceiveProps(): Assigning directly to ' + "this.state is deprecated (except inside a component's " + 'constructor). Use setState instead.', componentName); |
|
} |
|
} |
|
|
|
classComponentUpdater.enqueueReplaceState(instance, instance.state, null); |
|
} |
|
} // Invokes the mount life-cycles on a previously never rendered instance. |
|
|
|
|
|
function mountClassInstance(workInProgress, ctor, newProps, renderLanes) { |
|
{ |
|
checkClassInstance(workInProgress, ctor, newProps); |
|
} |
|
|
|
var instance = workInProgress.stateNode; |
|
instance.props = newProps; |
|
instance.state = workInProgress.memoizedState; |
|
instance.refs = emptyRefsObject; |
|
initializeUpdateQueue(workInProgress); |
|
var contextType = ctor.contextType; |
|
|
|
if (typeof contextType === 'object' && contextType !== null) { |
|
instance.context = readContext(contextType); |
|
} else { |
|
var unmaskedContext = getUnmaskedContext(workInProgress, ctor, true); |
|
instance.context = getMaskedContext(workInProgress, unmaskedContext); |
|
} |
|
|
|
{ |
|
if (instance.state === newProps) { |
|
var componentName = getComponentNameFromType(ctor) || 'Component'; |
|
|
|
if (!didWarnAboutDirectlyAssigningPropsToState.has(componentName)) { |
|
didWarnAboutDirectlyAssigningPropsToState.add(componentName); |
|
|
|
error('%s: It is not recommended to assign props directly to state ' + "because updates to props won't be reflected in state. " + 'In most cases, it is better to use props directly.', componentName); |
|
} |
|
} |
|
|
|
if (workInProgress.mode & StrictLegacyMode) { |
|
ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, instance); |
|
} |
|
|
|
{ |
|
ReactStrictModeWarnings.recordUnsafeLifecycleWarnings(workInProgress, instance); |
|
} |
|
} |
|
|
|
instance.state = workInProgress.memoizedState; |
|
var getDerivedStateFromProps = ctor.getDerivedStateFromProps; |
|
|
|
if (typeof getDerivedStateFromProps === 'function') { |
|
applyDerivedStateFromProps(workInProgress, ctor, getDerivedStateFromProps, newProps); |
|
instance.state = workInProgress.memoizedState; |
|
} // In order to support react-lifecycles-compat polyfilled components, |
|
// Unsafe lifecycles should not be invoked for components using the new APIs. |
|
|
|
|
|
if (typeof ctor.getDerivedStateFromProps !== 'function' && typeof instance.getSnapshotBeforeUpdate !== 'function' && (typeof instance.UNSAFE_componentWillMount === 'function' || typeof instance.componentWillMount === 'function')) { |
|
callComponentWillMount(workInProgress, instance); // If we had additional state updates during this life-cycle, let's |
|
// process them now. |
|
|
|
processUpdateQueue(workInProgress, newProps, instance, renderLanes); |
|
instance.state = workInProgress.memoizedState; |
|
} |
|
|
|
if (typeof instance.componentDidMount === 'function') { |
|
var fiberFlags = Update; |
|
|
|
{ |
|
fiberFlags |= LayoutStatic; |
|
} |
|
|
|
if ( (workInProgress.mode & StrictEffectsMode) !== NoMode) { |
|
fiberFlags |= MountLayoutDev; |
|
} |
|
|
|
workInProgress.flags |= fiberFlags; |
|
} |
|
} |
|
|
|
function resumeMountClassInstance(workInProgress, ctor, newProps, renderLanes) { |
|
var instance = workInProgress.stateNode; |
|
var oldProps = workInProgress.memoizedProps; |
|
instance.props = oldProps; |
|
var oldContext = instance.context; |
|
var contextType = ctor.contextType; |
|
var nextContext = emptyContextObject; |
|
|
|
if (typeof contextType === 'object' && contextType !== null) { |
|
nextContext = readContext(contextType); |
|
} else { |
|
var nextLegacyUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true); |
|
nextContext = getMaskedContext(workInProgress, nextLegacyUnmaskedContext); |
|
} |
|
|
|
var getDerivedStateFromProps = ctor.getDerivedStateFromProps; |
|
var hasNewLifecycles = typeof getDerivedStateFromProps === 'function' || typeof instance.getSnapshotBeforeUpdate === 'function'; // Note: During these life-cycles, instance.props/instance.state are what |
|
// ever the previously attempted to render - not the "current". However, |
|
// during componentDidUpdate we pass the "current" props. |
|
// In order to support react-lifecycles-compat polyfilled components, |
|
// Unsafe lifecycles should not be invoked for components using the new APIs. |
|
|
|
if (!hasNewLifecycles && (typeof instance.UNSAFE_componentWillReceiveProps === 'function' || typeof instance.componentWillReceiveProps === 'function')) { |
|
if (oldProps !== newProps || oldContext !== nextContext) { |
|
callComponentWillReceiveProps(workInProgress, instance, newProps, nextContext); |
|
} |
|
} |
|
|
|
resetHasForceUpdateBeforeProcessing(); |
|
var oldState = workInProgress.memoizedState; |
|
var newState = instance.state = oldState; |
|
processUpdateQueue(workInProgress, newProps, instance, renderLanes); |
|
newState = workInProgress.memoizedState; |
|
|
|
if (oldProps === newProps && oldState === newState && !hasContextChanged() && !checkHasForceUpdateAfterProcessing()) { |
|
// If an update was already in progress, we should schedule an Update |
|
// effect even though we're bailing out, so that cWU/cDU are called. |
|
if (typeof instance.componentDidMount === 'function') { |
|
var fiberFlags = Update; |
|
|
|
{ |
|
fiberFlags |= LayoutStatic; |
|
} |
|
|
|
if ( (workInProgress.mode & StrictEffectsMode) !== NoMode) { |
|
fiberFlags |= MountLayoutDev; |
|
} |
|
|
|
workInProgress.flags |= fiberFlags; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
if (typeof getDerivedStateFromProps === 'function') { |
|
applyDerivedStateFromProps(workInProgress, ctor, getDerivedStateFromProps, newProps); |
|
newState = workInProgress.memoizedState; |
|
} |
|
|
|
var shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext); |
|
|
|
if (shouldUpdate) { |
|
// In order to support react-lifecycles-compat polyfilled components, |
|
// Unsafe lifecycles should not be invoked for components using the new APIs. |
|
if (!hasNewLifecycles && (typeof instance.UNSAFE_componentWillMount === 'function' || typeof instance.componentWillMount === 'function')) { |
|
if (typeof instance.componentWillMount === 'function') { |
|
instance.componentWillMount(); |
|
} |
|
|
|
if (typeof instance.UNSAFE_componentWillMount === 'function') { |
|
instance.UNSAFE_componentWillMount(); |
|
} |
|
} |
|
|
|
if (typeof instance.componentDidMount === 'function') { |
|
var _fiberFlags = Update; |
|
|
|
{ |
|
_fiberFlags |= LayoutStatic; |
|
} |
|
|
|
if ( (workInProgress.mode & StrictEffectsMode) !== NoMode) { |
|
_fiberFlags |= MountLayoutDev; |
|
} |
|
|
|
workInProgress.flags |= _fiberFlags; |
|
} |
|
} else { |
|
// If an update was already in progress, we should schedule an Update |
|
// effect even though we're bailing out, so that cWU/cDU are called. |
|
if (typeof instance.componentDidMount === 'function') { |
|
var _fiberFlags2 = Update; |
|
|
|
{ |
|
_fiberFlags2 |= LayoutStatic; |
|
} |
|
|
|
if ( (workInProgress.mode & StrictEffectsMode) !== NoMode) { |
|
_fiberFlags2 |= MountLayoutDev; |
|
} |
|
|
|
workInProgress.flags |= _fiberFlags2; |
|
} // If shouldComponentUpdate returned false, we should still update the |
|
// memoized state to indicate that this work can be reused. |
|
|
|
|
|
workInProgress.memoizedProps = newProps; |
|
workInProgress.memoizedState = newState; |
|
} // Update the existing instance's state, props, and context pointers even |
|
// if shouldComponentUpdate returns false. |
|
|
|
|
|
instance.props = newProps; |
|
instance.state = newState; |
|
instance.context = nextContext; |
|
return shouldUpdate; |
|
} // Invokes the update life-cycles and returns false if it shouldn't rerender. |
|
|
|
|
|
function updateClassInstance(current, workInProgress, ctor, newProps, renderLanes) { |
|
var instance = workInProgress.stateNode; |
|
cloneUpdateQueue(current, workInProgress); |
|
var unresolvedOldProps = workInProgress.memoizedProps; |
|
var oldProps = workInProgress.type === workInProgress.elementType ? unresolvedOldProps : resolveDefaultProps(workInProgress.type, unresolvedOldProps); |
|
instance.props = oldProps; |
|
var unresolvedNewProps = workInProgress.pendingProps; |
|
var oldContext = instance.context; |
|
var contextType = ctor.contextType; |
|
var nextContext = emptyContextObject; |
|
|
|
if (typeof contextType === 'object' && contextType !== null) { |
|
nextContext = readContext(contextType); |
|
} else { |
|
var nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true); |
|
nextContext = getMaskedContext(workInProgress, nextUnmaskedContext); |
|
} |
|
|
|
var getDerivedStateFromProps = ctor.getDerivedStateFromProps; |
|
var hasNewLifecycles = typeof getDerivedStateFromProps === 'function' || typeof instance.getSnapshotBeforeUpdate === 'function'; // Note: During these life-cycles, instance.props/instance.state are what |
|
// ever the previously attempted to render - not the "current". However, |
|
// during componentDidUpdate we pass the "current" props. |
|
// In order to support react-lifecycles-compat polyfilled components, |
|
// Unsafe lifecycles should not be invoked for components using the new APIs. |
|
|
|
if (!hasNewLifecycles && (typeof instance.UNSAFE_componentWillReceiveProps === 'function' || typeof instance.componentWillReceiveProps === 'function')) { |
|
if (unresolvedOldProps !== unresolvedNewProps || oldContext !== nextContext) { |
|
callComponentWillReceiveProps(workInProgress, instance, newProps, nextContext); |
|
} |
|
} |
|
|
|
resetHasForceUpdateBeforeProcessing(); |
|
var oldState = workInProgress.memoizedState; |
|
var newState = instance.state = oldState; |
|
processUpdateQueue(workInProgress, newProps, instance, renderLanes); |
|
newState = workInProgress.memoizedState; |
|
|
|
if (unresolvedOldProps === unresolvedNewProps && oldState === newState && !hasContextChanged() && !checkHasForceUpdateAfterProcessing() && !(enableLazyContextPropagation )) { |
|
// If an update was already in progress, we should schedule an Update |
|
// effect even though we're bailing out, so that cWU/cDU are called. |
|
if (typeof instance.componentDidUpdate === 'function') { |
|
if (unresolvedOldProps !== current.memoizedProps || oldState !== current.memoizedState) { |
|
workInProgress.flags |= Update; |
|
} |
|
} |
|
|
|
if (typeof instance.getSnapshotBeforeUpdate === 'function') { |
|
if (unresolvedOldProps !== current.memoizedProps || oldState !== current.memoizedState) { |
|
workInProgress.flags |= Snapshot; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
if (typeof getDerivedStateFromProps === 'function') { |
|
applyDerivedStateFromProps(workInProgress, ctor, getDerivedStateFromProps, newProps); |
|
newState = workInProgress.memoizedState; |
|
} |
|
|
|
var shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext) || // TODO: In some cases, we'll end up checking if context has changed twice, |
|
// both before and after `shouldComponentUpdate` has been called. Not ideal, |
|
// but I'm loath to refactor this function. This only happens for memoized |
|
// components so it's not that common. |
|
enableLazyContextPropagation ; |
|
|
|
if (shouldUpdate) { |
|
// In order to support react-lifecycles-compat polyfilled components, |
|
// Unsafe lifecycles should not be invoked for components using the new APIs. |
|
if (!hasNewLifecycles && (typeof instance.UNSAFE_componentWillUpdate === 'function' || typeof instance.componentWillUpdate === 'function')) { |
|
if (typeof instance.componentWillUpdate === 'function') { |
|
instance.componentWillUpdate(newProps, newState, nextContext); |
|
} |
|
|
|
if (typeof instance.UNSAFE_componentWillUpdate === 'function') { |
|
instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext); |
|
} |
|
} |
|
|
|
if (typeof instance.componentDidUpdate === 'function') { |
|
workInProgress.flags |= Update; |
|
} |
|
|
|
if (typeof instance.getSnapshotBeforeUpdate === 'function') { |
|
workInProgress.flags |= Snapshot; |
|
} |
|
} else { |
|
// If an update was already in progress, we should schedule an Update |
|
// effect even though we're bailing out, so that cWU/cDU are called. |
|
if (typeof instance.componentDidUpdate === 'function') { |
|
if (unresolvedOldProps !== current.memoizedProps || oldState !== current.memoizedState) { |
|
workInProgress.flags |= Update; |
|
} |
|
} |
|
|
|
if (typeof instance.getSnapshotBeforeUpdate === 'function') { |
|
if (unresolvedOldProps !== current.memoizedProps || oldState !== current.memoizedState) { |
|
workInProgress.flags |= Snapshot; |
|
} |
|
} // If shouldComponentUpdate returned false, we should still update the |
|
// memoized props/state to indicate that this work can be reused. |
|
|
|
|
|
workInProgress.memoizedProps = newProps; |
|
workInProgress.memoizedState = newState; |
|
} // Update the existing instance's state, props, and context pointers even |
|
// if shouldComponentUpdate returns false. |
|
|
|
|
|
instance.props = newProps; |
|
instance.state = newState; |
|
instance.context = nextContext; |
|
return shouldUpdate; |
|
} |
|
|
|
var didWarnAboutMaps; |
|
var didWarnAboutGenerators; |
|
var didWarnAboutStringRefs; |
|
var ownerHasKeyUseWarning; |
|
var ownerHasFunctionTypeWarning; |
|
|
|
var warnForMissingKey = function (child, returnFiber) {}; |
|
|
|
{ |
|
didWarnAboutMaps = false; |
|
didWarnAboutGenerators = false; |
|
didWarnAboutStringRefs = {}; |
|
/** |
|
* Warn if there's no key explicitly set on dynamic arrays of children or |
|
* object keys are not valid. This allows us to keep track of children between |
|
* updates. |
|
*/ |
|
|
|
ownerHasKeyUseWarning = {}; |
|
ownerHasFunctionTypeWarning = {}; |
|
|
|
warnForMissingKey = function (child, returnFiber) { |
|
if (child === null || typeof child !== 'object') { |
|
return; |
|
} |
|
|
|
if (!child._store || child._store.validated || child.key != null) { |
|
return; |
|
} |
|
|
|
if (typeof child._store !== 'object') { |
|
throw new Error('React Component in warnForMissingKey should have a _store. ' + 'This error is likely caused by a bug in React. Please file an issue.'); |
|
} |
|
|
|
child._store.validated = true; |
|
var componentName = getComponentNameFromFiber(returnFiber) || 'Component'; |
|
|
|
if (ownerHasKeyUseWarning[componentName]) { |
|
return; |
|
} |
|
|
|
ownerHasKeyUseWarning[componentName] = true; |
|
|
|
error('Each child in a list should have a unique ' + '"key" prop. See https://reactjs.org/link/warning-keys for ' + 'more information.'); |
|
}; |
|
} |
|
|
|
function coerceRef(returnFiber, current, element) { |
|
var mixedRef = element.ref; |
|
|
|
if (mixedRef !== null && typeof mixedRef !== 'function' && typeof mixedRef !== 'object') { |
|
{ |
|
// TODO: Clean this up once we turn on the string ref warning for |
|
// everyone, because the strict mode case will no longer be relevant |
|
if ((returnFiber.mode & StrictLegacyMode || warnAboutStringRefs) && // We warn in ReactElement.js if owner and self are equal for string refs |
|
// because these cannot be automatically converted to an arrow function |
|
// using a codemod. Therefore, we don't have to warn about string refs again. |
|
!(element._owner && element._self && element._owner.stateNode !== element._self)) { |
|
var componentName = getComponentNameFromFiber(returnFiber) || 'Component'; |
|
|
|
if (!didWarnAboutStringRefs[componentName]) { |
|
{ |
|
error('A string ref, "%s", has been found within a strict mode tree. ' + 'String refs are a source of potential bugs and should be avoided. ' + 'We recommend using useRef() or createRef() instead. ' + 'Learn more about using refs safely here: ' + 'https://reactjs.org/link/strict-mode-string-ref', mixedRef); |
|
} |
|
|
|
didWarnAboutStringRefs[componentName] = true; |
|
} |
|
} |
|
} |
|
|
|
if (element._owner) { |
|
var owner = element._owner; |
|
var inst; |
|
|
|
if (owner) { |
|
var ownerFiber = owner; |
|
|
|
if (ownerFiber.tag !== ClassComponent) { |
|
throw new Error('Function components cannot have string refs. ' + 'We recommend using useRef() instead. ' + 'Learn more about using refs safely here: ' + 'https://reactjs.org/link/strict-mode-string-ref'); |
|
} |
|
|
|
inst = ownerFiber.stateNode; |
|
} |
|
|
|
if (!inst) { |
|
throw new Error("Missing owner for string ref " + mixedRef + ". This error is likely caused by a " + 'bug in React. Please file an issue.'); |
|
} // Assigning this to a const so Flow knows it won't change in the closure |
|
|
|
|
|
var resolvedInst = inst; |
|
|
|
{ |
|
checkPropStringCoercion(mixedRef, 'ref'); |
|
} |
|
|
|
var stringRef = '' + mixedRef; // Check if previous string ref matches new string ref |
|
|
|
if (current !== null && current.ref !== null && typeof current.ref === 'function' && current.ref._stringRef === stringRef) { |
|
return current.ref; |
|
} |
|
|
|
var ref = function (value) { |
|
var refs = resolvedInst.refs; |
|
|
|
if (refs === emptyRefsObject) { |
|
// This is a lazy pooled frozen object, so we need to initialize. |
|
refs = resolvedInst.refs = {}; |
|
} |
|
|
|
if (value === null) { |
|
delete refs[stringRef]; |
|
} else { |
|
refs[stringRef] = value; |
|
} |
|
}; |
|
|
|
ref._stringRef = stringRef; |
|
return ref; |
|
} else { |
|
if (typeof mixedRef !== 'string') { |
|
throw new Error('Expected ref to be a function, a string, an object returned by React.createRef(), or null.'); |
|
} |
|
|
|
if (!element._owner) { |
|
throw new Error("Element ref was specified as a string (" + mixedRef + ") but no owner was set. This could happen for one of" + ' the following reasons:\n' + '1. You may be adding a ref to a function component\n' + "2. You may be adding a ref to a component that was not created inside a component's render method\n" + '3. You have multiple copies of React loaded\n' + 'See https://reactjs.org/link/refs-must-have-owner for more information.'); |
|
} |
|
} |
|
} |
|
|
|
return mixedRef; |
|
} |
|
|
|
function throwOnInvalidObjectType(returnFiber, newChild) { |
|
var childString = Object.prototype.toString.call(newChild); |
|
throw new Error("Objects are not valid as a React child (found: " + (childString === '[object Object]' ? 'object with keys {' + Object.keys(newChild).join(', ') + '}' : childString) + "). " + 'If you meant to render a collection of children, use an array ' + 'instead.'); |
|
} |
|
|
|
function warnOnFunctionType(returnFiber) { |
|
{ |
|
var componentName = getComponentNameFromFiber(returnFiber) || 'Component'; |
|
|
|
if (ownerHasFunctionTypeWarning[componentName]) { |
|
return; |
|
} |
|
|
|
ownerHasFunctionTypeWarning[componentName] = true; |
|
|
|
error('Functions are not valid as a React child. This may happen if ' + 'you return a Component instead of <Component /> from render. ' + 'Or maybe you meant to call this function rather than return it.'); |
|
} |
|
} |
|
|
|
function resolveLazy(lazyType) { |
|
var payload = lazyType._payload; |
|
var init = lazyType._init; |
|
return init(payload); |
|
} // This wrapper function exists because I expect to clone the code in each path |
|
// to be able to optimize each path individually by branching early. This needs |
|
// a compiler or we can do it manually. Helpers that don't need this branching |
|
// live outside of this function. |
|
|
|
|
|
function ChildReconciler(shouldTrackSideEffects) { |
|
function deleteChild(returnFiber, childToDelete) { |
|
if (!shouldTrackSideEffects) { |
|
// Noop. |
|
return; |
|
} |
|
|
|
var deletions = returnFiber.deletions; |
|
|
|
if (deletions === null) { |
|
returnFiber.deletions = [childToDelete]; |
|
returnFiber.flags |= ChildDeletion; |
|
} else { |
|
deletions.push(childToDelete); |
|
} |
|
} |
|
|
|
function deleteRemainingChildren(returnFiber, currentFirstChild) { |
|
if (!shouldTrackSideEffects) { |
|
// Noop. |
|
return null; |
|
} // TODO: For the shouldClone case, this could be micro-optimized a bit by |
|
// assuming that after the first child we've already added everything. |
|
|
|
|
|
var childToDelete = currentFirstChild; |
|
|
|
while (childToDelete !== null) { |
|
deleteChild(returnFiber, childToDelete); |
|
childToDelete = childToDelete.sibling; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function mapRemainingChildren(returnFiber, currentFirstChild) { |
|
// Add the remaining children to a temporary map so that we can find them by |
|
// keys quickly. Implicit (null) keys get added to this set with their index |
|
// instead. |
|
var existingChildren = new Map(); |
|
var existingChild = currentFirstChild; |
|
|
|
while (existingChild !== null) { |
|
if (existingChild.key !== null) { |
|
existingChildren.set(existingChild.key, existingChild); |
|
} else { |
|
existingChildren.set(existingChild.index, existingChild); |
|
} |
|
|
|
existingChild = existingChild.sibling; |
|
} |
|
|
|
return existingChildren; |
|
} |
|
|
|
function useFiber(fiber, pendingProps) { |
|
// We currently set sibling to null and index to 0 here because it is easy |
|
// to forget to do before returning it. E.g. for the single child case. |
|
var clone = createWorkInProgress(fiber, pendingProps); |
|
clone.index = 0; |
|
clone.sibling = null; |
|
return clone; |
|
} |
|
|
|
function placeChild(newFiber, lastPlacedIndex, newIndex) { |
|
newFiber.index = newIndex; |
|
|
|
if (!shouldTrackSideEffects) { |
|
// During hydration, the useId algorithm needs to know which fibers are |
|
// part of a list of children (arrays, iterators). |
|
newFiber.flags |= Forked; |
|
return lastPlacedIndex; |
|
} |
|
|
|
var current = newFiber.alternate; |
|
|
|
if (current !== null) { |
|
var oldIndex = current.index; |
|
|
|
if (oldIndex < lastPlacedIndex) { |
|
// This is a move. |
|
newFiber.flags |= Placement; |
|
return lastPlacedIndex; |
|
} else { |
|
// This item can stay in place. |
|
return oldIndex; |
|
} |
|
} else { |
|
// This is an insertion. |
|
newFiber.flags |= Placement; |
|
return lastPlacedIndex; |
|
} |
|
} |
|
|
|
function placeSingleChild(newFiber) { |
|
// This is simpler for the single child case. We only need to do a |
|
// placement for inserting new children. |
|
if (shouldTrackSideEffects && newFiber.alternate === null) { |
|
newFiber.flags |= Placement; |
|
} |
|
|
|
return newFiber; |
|
} |
|
|
|
function updateTextNode(returnFiber, current, textContent, lanes) { |
|
if (current === null || current.tag !== HostText) { |
|
// Insert |
|
var created = createFiberFromText(textContent, returnFiber.mode, lanes); |
|
created.return = returnFiber; |
|
return created; |
|
} else { |
|
// Update |
|
var existing = useFiber(current, textContent); |
|
existing.return = returnFiber; |
|
return existing; |
|
} |
|
} |
|
|
|
function updateElement(returnFiber, current, element, lanes) { |
|
var elementType = element.type; |
|
|
|
if (elementType === REACT_FRAGMENT_TYPE) { |
|
return updateFragment(returnFiber, current, element.props.children, lanes, element.key); |
|
} |
|
|
|
if (current !== null) { |
|
if (current.elementType === elementType || ( // Keep this check inline so it only runs on the false path: |
|
isCompatibleFamilyForHotReloading(current, element) ) || // Lazy types should reconcile their resolved type. |
|
// We need to do this after the Hot Reloading check above, |
|
// because hot reloading has different semantics than prod because |
|
// it doesn't resuspend. So we can't let the call below suspend. |
|
typeof elementType === 'object' && elementType !== null && elementType.$$typeof === REACT_LAZY_TYPE && resolveLazy(elementType) === current.type) { |
|
// Move based on index |
|
var existing = useFiber(current, element.props); |
|
existing.ref = coerceRef(returnFiber, current, element); |
|
existing.return = returnFiber; |
|
|
|
{ |
|
existing._debugSource = element._source; |
|
existing._debugOwner = element._owner; |
|
} |
|
|
|
return existing; |
|
} |
|
} // Insert |
|
|
|
|
|
var created = createFiberFromElement(element, returnFiber.mode, lanes); |
|
created.ref = coerceRef(returnFiber, current, element); |
|
created.return = returnFiber; |
|
return created; |
|
} |
|
|
|
function updatePortal(returnFiber, current, portal, lanes) { |
|
if (current === null || current.tag !== HostPortal || current.stateNode.containerInfo !== portal.containerInfo || current.stateNode.implementation !== portal.implementation) { |
|
// Insert |
|
var created = createFiberFromPortal(portal, returnFiber.mode, lanes); |
|
created.return = returnFiber; |
|
return created; |
|
} else { |
|
// Update |
|
var existing = useFiber(current, portal.children || []); |
|
existing.return = returnFiber; |
|
return existing; |
|
} |
|
} |
|
|
|
function updateFragment(returnFiber, current, fragment, lanes, key) { |
|
if (current === null || current.tag !== Fragment) { |
|
// Insert |
|
var created = createFiberFromFragment(fragment, returnFiber.mode, lanes, key); |
|
created.return = returnFiber; |
|
return created; |
|
} else { |
|
// Update |
|
var existing = useFiber(current, fragment); |
|
existing.return = returnFiber; |
|
return existing; |
|
} |
|
} |
|
|
|
function createChild(returnFiber, newChild, lanes) { |
|
if (typeof newChild === 'string' && newChild !== '' || typeof newChild === 'number') { |
|
// Text nodes don't have keys. If the previous node is implicitly keyed |
|
// we can continue to replace it without aborting even if it is not a text |
|
// node. |
|
var created = createFiberFromText('' + newChild, returnFiber.mode, lanes); |
|
created.return = returnFiber; |
|
return created; |
|
} |
|
|
|
if (typeof newChild === 'object' && newChild !== null) { |
|
switch (newChild.$$typeof) { |
|
case REACT_ELEMENT_TYPE: |
|
{ |
|
var _created = createFiberFromElement(newChild, returnFiber.mode, lanes); |
|
|
|
_created.ref = coerceRef(returnFiber, null, newChild); |
|
_created.return = returnFiber; |
|
return _created; |
|
} |
|
|
|
case REACT_PORTAL_TYPE: |
|
{ |
|
var _created2 = createFiberFromPortal(newChild, returnFiber.mode, lanes); |
|
|
|
_created2.return = returnFiber; |
|
return _created2; |
|
} |
|
|
|
case REACT_LAZY_TYPE: |
|
{ |
|
var payload = newChild._payload; |
|
var init = newChild._init; |
|
return createChild(returnFiber, init(payload), lanes); |
|
} |
|
} |
|
|
|
if (isArray(newChild) || getIteratorFn(newChild)) { |
|
var _created3 = createFiberFromFragment(newChild, returnFiber.mode, lanes, null); |
|
|
|
_created3.return = returnFiber; |
|
return _created3; |
|
} |
|
|
|
throwOnInvalidObjectType(returnFiber, newChild); |
|
} |
|
|
|
{ |
|
if (typeof newChild === 'function') { |
|
warnOnFunctionType(returnFiber); |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function updateSlot(returnFiber, oldFiber, newChild, lanes) { |
|
// Update the fiber if the keys match, otherwise return null. |
|
var key = oldFiber !== null ? oldFiber.key : null; |
|
|
|
if (typeof newChild === 'string' && newChild !== '' || typeof newChild === 'number') { |
|
// Text nodes don't have keys. If the previous node is implicitly keyed |
|
// we can continue to replace it without aborting even if it is not a text |
|
// node. |
|
if (key !== null) { |
|
return null; |
|
} |
|
|
|
return updateTextNode(returnFiber, oldFiber, '' + newChild, lanes); |
|
} |
|
|
|
if (typeof newChild === 'object' && newChild !== null) { |
|
switch (newChild.$$typeof) { |
|
case REACT_ELEMENT_TYPE: |
|
{ |
|
if (newChild.key === key) { |
|
return updateElement(returnFiber, oldFiber, newChild, lanes); |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
case REACT_PORTAL_TYPE: |
|
{ |
|
if (newChild.key === key) { |
|
return updatePortal(returnFiber, oldFiber, newChild, lanes); |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
case REACT_LAZY_TYPE: |
|
{ |
|
var payload = newChild._payload; |
|
var init = newChild._init; |
|
return updateSlot(returnFiber, oldFiber, init(payload), lanes); |
|
} |
|
} |
|
|
|
if (isArray(newChild) || getIteratorFn(newChild)) { |
|
if (key !== null) { |
|
return null; |
|
} |
|
|
|
return updateFragment(returnFiber, oldFiber, newChild, lanes, null); |
|
} |
|
|
|
throwOnInvalidObjectType(returnFiber, newChild); |
|
} |
|
|
|
{ |
|
if (typeof newChild === 'function') { |
|
warnOnFunctionType(returnFiber); |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function updateFromMap(existingChildren, returnFiber, newIdx, newChild, lanes) { |
|
if (typeof newChild === 'string' && newChild !== '' || typeof newChild === 'number') { |
|
// Text nodes don't have keys, so we neither have to check the old nor |
|
// new node for the key. If both are text nodes, they match. |
|
var matchedFiber = existingChildren.get(newIdx) || null; |
|
return updateTextNode(returnFiber, matchedFiber, '' + newChild, lanes); |
|
} |
|
|
|
if (typeof newChild === 'object' && newChild !== null) { |
|
switch (newChild.$$typeof) { |
|
case REACT_ELEMENT_TYPE: |
|
{ |
|
var _matchedFiber = existingChildren.get(newChild.key === null ? newIdx : newChild.key) || null; |
|
|
|
return updateElement(returnFiber, _matchedFiber, newChild, lanes); |
|
} |
|
|
|
case REACT_PORTAL_TYPE: |
|
{ |
|
var _matchedFiber2 = existingChildren.get(newChild.key === null ? newIdx : newChild.key) || null; |
|
|
|
return updatePortal(returnFiber, _matchedFiber2, newChild, lanes); |
|
} |
|
|
|
case REACT_LAZY_TYPE: |
|
var payload = newChild._payload; |
|
var init = newChild._init; |
|
return updateFromMap(existingChildren, returnFiber, newIdx, init(payload), lanes); |
|
} |
|
|
|
if (isArray(newChild) || getIteratorFn(newChild)) { |
|
var _matchedFiber3 = existingChildren.get(newIdx) || null; |
|
|
|
return updateFragment(returnFiber, _matchedFiber3, newChild, lanes, null); |
|
} |
|
|
|
throwOnInvalidObjectType(returnFiber, newChild); |
|
} |
|
|
|
{ |
|
if (typeof newChild === 'function') { |
|
warnOnFunctionType(returnFiber); |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
/** |
|
* Warns if there is a duplicate or missing key |
|
*/ |
|
|
|
|
|
function warnOnInvalidKey(child, knownKeys, returnFiber) { |
|
{ |
|
if (typeof child !== 'object' || child === null) { |
|
return knownKeys; |
|
} |
|
|
|
switch (child.$$typeof) { |
|
case REACT_ELEMENT_TYPE: |
|
case REACT_PORTAL_TYPE: |
|
warnForMissingKey(child, returnFiber); |
|
var key = child.key; |
|
|
|
if (typeof key !== 'string') { |
|
break; |
|
} |
|
|
|
if (knownKeys === null) { |
|
knownKeys = new Set(); |
|
knownKeys.add(key); |
|
break; |
|
} |
|
|
|
if (!knownKeys.has(key)) { |
|
knownKeys.add(key); |
|
break; |
|
} |
|
|
|
error('Encountered two children with the same key, `%s`. ' + 'Keys should be unique so that components maintain their identity ' + 'across updates. Non-unique keys may cause children to be ' + 'duplicated and/or omitted — the behavior is unsupported and ' + 'could change in a future version.', key); |
|
|
|
break; |
|
|
|
case REACT_LAZY_TYPE: |
|
var payload = child._payload; |
|
var init = child._init; |
|
warnOnInvalidKey(init(payload), knownKeys, returnFiber); |
|
break; |
|
} |
|
} |
|
|
|
return knownKeys; |
|
} |
|
|
|
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren, lanes) { |
|
// This algorithm can't optimize by searching from both ends since we |
|
// don't have backpointers on fibers. I'm trying to see how far we can get |
|
// with that model. If it ends up not being worth the tradeoffs, we can |
|
// add it later. |
|
// Even with a two ended optimization, we'd want to optimize for the case |
|
// where there are few changes and brute force the comparison instead of |
|
// going for the Map. It'd like to explore hitting that path first in |
|
// forward-only mode and only go for the Map once we notice that we need |
|
// lots of look ahead. This doesn't handle reversal as well as two ended |
|
// search but that's unusual. Besides, for the two ended optimization to |
|
// work on Iterables, we'd need to copy the whole set. |
|
// In this first iteration, we'll just live with hitting the bad case |
|
// (adding everything to a Map) in for every insert/move. |
|
// If you change this code, also update reconcileChildrenIterator() which |
|
// uses the same algorithm. |
|
{ |
|
// First, validate keys. |
|
var knownKeys = null; |
|
|
|
for (var i = 0; i < newChildren.length; i++) { |
|
var child = newChildren[i]; |
|
knownKeys = warnOnInvalidKey(child, knownKeys, returnFiber); |
|
} |
|
} |
|
|
|
var resultingFirstChild = null; |
|
var previousNewFiber = null; |
|
var oldFiber = currentFirstChild; |
|
var lastPlacedIndex = 0; |
|
var newIdx = 0; |
|
var nextOldFiber = null; |
|
|
|
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) { |
|
if (oldFiber.index > newIdx) { |
|
nextOldFiber = oldFiber; |
|
oldFiber = null; |
|
} else { |
|
nextOldFiber = oldFiber.sibling; |
|
} |
|
|
|
var newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx], lanes); |
|
|
|
if (newFiber === null) { |
|
// TODO: This breaks on empty slots like null children. That's |
|
// unfortunate because it triggers the slow path all the time. We need |
|
// a better way to communicate whether this was a miss or null, |
|
// boolean, undefined, etc. |
|
if (oldFiber === null) { |
|
oldFiber = nextOldFiber; |
|
} |
|
|
|
break; |
|
} |
|
|
|
if (shouldTrackSideEffects) { |
|
if (oldFiber && newFiber.alternate === null) { |
|
// We matched the slot, but we didn't reuse the existing fiber, so we |
|
// need to delete the existing child. |
|
deleteChild(returnFiber, oldFiber); |
|
} |
|
} |
|
|
|
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); |
|
|
|
if (previousNewFiber === null) { |
|
// TODO: Move out of the loop. This only happens for the first run. |
|
resultingFirstChild = newFiber; |
|
} else { |
|
// TODO: Defer siblings if we're not at the right index for this slot. |
|
// I.e. if we had null values before, then we want to defer this |
|
// for each null value. However, we also don't want to call updateSlot |
|
// with the previous one. |
|
previousNewFiber.sibling = newFiber; |
|
} |
|
|
|
previousNewFiber = newFiber; |
|
oldFiber = nextOldFiber; |
|
} |
|
|
|
if (newIdx === newChildren.length) { |
|
// We've reached the end of the new children. We can delete the rest. |
|
deleteRemainingChildren(returnFiber, oldFiber); |
|
|
|
if (getIsHydrating()) { |
|
var numberOfForks = newIdx; |
|
pushTreeFork(returnFiber, numberOfForks); |
|
} |
|
|
|
return resultingFirstChild; |
|
} |
|
|
|
if (oldFiber === null) { |
|
// If we don't have any more existing children we can choose a fast path |
|
// since the rest will all be insertions. |
|
for (; newIdx < newChildren.length; newIdx++) { |
|
var _newFiber = createChild(returnFiber, newChildren[newIdx], lanes); |
|
|
|
if (_newFiber === null) { |
|
continue; |
|
} |
|
|
|
lastPlacedIndex = placeChild(_newFiber, lastPlacedIndex, newIdx); |
|
|
|
if (previousNewFiber === null) { |
|
// TODO: Move out of the loop. This only happens for the first run. |
|
resultingFirstChild = _newFiber; |
|
} else { |
|
previousNewFiber.sibling = _newFiber; |
|
} |
|
|
|
previousNewFiber = _newFiber; |
|
} |
|
|
|
if (getIsHydrating()) { |
|
var _numberOfForks = newIdx; |
|
pushTreeFork(returnFiber, _numberOfForks); |
|
} |
|
|
|
return resultingFirstChild; |
|
} // Add all children to a key map for quick lookups. |
|
|
|
|
|
var existingChildren = mapRemainingChildren(returnFiber, oldFiber); // Keep scanning and use the map to restore deleted items as moves. |
|
|
|
for (; newIdx < newChildren.length; newIdx++) { |
|
var _newFiber2 = updateFromMap(existingChildren, returnFiber, newIdx, newChildren[newIdx], lanes); |
|
|
|
if (_newFiber2 !== null) { |
|
if (shouldTrackSideEffects) { |
|
if (_newFiber2.alternate !== null) { |
|
// The new fiber is a work in progress, but if there exists a |
|
// current, that means that we reused the fiber. We need to delete |
|
// it from the child list so that we don't add it to the deletion |
|
// list. |
|
existingChildren.delete(_newFiber2.key === null ? newIdx : _newFiber2.key); |
|
} |
|
} |
|
|
|
lastPlacedIndex = placeChild(_newFiber2, lastPlacedIndex, newIdx); |
|
|
|
if (previousNewFiber === null) { |
|
resultingFirstChild = _newFiber2; |
|
} else { |
|
previousNewFiber.sibling = _newFiber2; |
|
} |
|
|
|
previousNewFiber = _newFiber2; |
|
} |
|
} |
|
|
|
if (shouldTrackSideEffects) { |
|
// Any existing children that weren't consumed above were deleted. We need |
|
// to add them to the deletion list. |
|
existingChildren.forEach(function (child) { |
|
return deleteChild(returnFiber, child); |
|
}); |
|
} |
|
|
|
if (getIsHydrating()) { |
|
var _numberOfForks2 = newIdx; |
|
pushTreeFork(returnFiber, _numberOfForks2); |
|
} |
|
|
|
return resultingFirstChild; |
|
} |
|
|
|
function reconcileChildrenIterator(returnFiber, currentFirstChild, newChildrenIterable, lanes) { |
|
// This is the same implementation as reconcileChildrenArray(), |
|
// but using the iterator instead. |
|
var iteratorFn = getIteratorFn(newChildrenIterable); |
|
|
|
if (typeof iteratorFn !== 'function') { |
|
throw new Error('An object is not an iterable. This error is likely caused by a bug in ' + 'React. Please file an issue.'); |
|
} |
|
|
|
{ |
|
// We don't support rendering Generators because it's a mutation. |
|
// See https://github.com/facebook/react/issues/12995 |
|
if (typeof Symbol === 'function' && // $FlowFixMe Flow doesn't know about toStringTag |
|
newChildrenIterable[Symbol.toStringTag] === 'Generator') { |
|
if (!didWarnAboutGenerators) { |
|
error('Using Generators as children is unsupported and will likely yield ' + 'unexpected results because enumerating a generator mutates it. ' + 'You may convert it to an array with `Array.from()` or the ' + '`[...spread]` operator before rendering. Keep in mind ' + 'you might need to polyfill these features for older browsers.'); |
|
} |
|
|
|
didWarnAboutGenerators = true; |
|
} // Warn about using Maps as children |
|
|
|
|
|
if (newChildrenIterable.entries === iteratorFn) { |
|
if (!didWarnAboutMaps) { |
|
error('Using Maps as children is not supported. ' + 'Use an array of keyed ReactElements instead.'); |
|
} |
|
|
|
didWarnAboutMaps = true; |
|
} // First, validate keys. |
|
// We'll get a different iterator later for the main pass. |
|
|
|
|
|
var _newChildren = iteratorFn.call(newChildrenIterable); |
|
|
|
if (_newChildren) { |
|
var knownKeys = null; |
|
|
|
var _step = _newChildren.next(); |
|
|
|
for (; !_step.done; _step = _newChildren.next()) { |
|
var child = _step.value; |
|
knownKeys = warnOnInvalidKey(child, knownKeys, returnFiber); |
|
} |
|
} |
|
} |
|
|
|
var newChildren = iteratorFn.call(newChildrenIterable); |
|
|
|
if (newChildren == null) { |
|
throw new Error('An iterable object provided no iterator.'); |
|
} |
|
|
|
var resultingFirstChild = null; |
|
var previousNewFiber = null; |
|
var oldFiber = currentFirstChild; |
|
var lastPlacedIndex = 0; |
|
var newIdx = 0; |
|
var nextOldFiber = null; |
|
var step = newChildren.next(); |
|
|
|
for (; oldFiber !== null && !step.done; newIdx++, step = newChildren.next()) { |
|
if (oldFiber.index > newIdx) { |
|
nextOldFiber = oldFiber; |
|
oldFiber = null; |
|
} else { |
|
nextOldFiber = oldFiber.sibling; |
|
} |
|
|
|
var newFiber = updateSlot(returnFiber, oldFiber, step.value, lanes); |
|
|
|
if (newFiber === null) { |
|
// TODO: This breaks on empty slots like null children. That's |
|
// unfortunate because it triggers the slow path all the time. We need |
|
// a better way to communicate whether this was a miss or null, |
|
// boolean, undefined, etc. |
|
if (oldFiber === null) { |
|
oldFiber = nextOldFiber; |
|
} |
|
|
|
break; |
|
} |
|
|
|
if (shouldTrackSideEffects) { |
|
if (oldFiber && newFiber.alternate === null) { |
|
// We matched the slot, but we didn't reuse the existing fiber, so we |
|
// need to delete the existing child. |
|
deleteChild(returnFiber, oldFiber); |
|
} |
|
} |
|
|
|
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); |
|
|
|
if (previousNewFiber === null) { |
|
// TODO: Move out of the loop. This only happens for the first run. |
|
resultingFirstChild = newFiber; |
|
} else { |
|
// TODO: Defer siblings if we're not at the right index for this slot. |
|
// I.e. if we had null values before, then we want to defer this |
|
// for each null value. However, we also don't want to call updateSlot |
|
// with the previous one. |
|
previousNewFiber.sibling = newFiber; |
|
} |
|
|
|
previousNewFiber = newFiber; |
|
oldFiber = nextOldFiber; |
|
} |
|
|
|
if (step.done) { |
|
// We've reached the end of the new children. We can delete the rest. |
|
deleteRemainingChildren(returnFiber, oldFiber); |
|
|
|
if (getIsHydrating()) { |
|
var numberOfForks = newIdx; |
|
pushTreeFork(returnFiber, numberOfForks); |
|
} |
|
|
|
return resultingFirstChild; |
|
} |
|
|
|
if (oldFiber === null) { |
|
// If we don't have any more existing children we can choose a fast path |
|
// since the rest will all be insertions. |
|
for (; !step.done; newIdx++, step = newChildren.next()) { |
|
var _newFiber3 = createChild(returnFiber, step.value, lanes); |
|
|
|
if (_newFiber3 === null) { |
|
continue; |
|
} |
|
|
|
lastPlacedIndex = placeChild(_newFiber3, lastPlacedIndex, newIdx); |
|
|
|
if (previousNewFiber === null) { |
|
// TODO: Move out of the loop. This only happens for the first run. |
|
resultingFirstChild = _newFiber3; |
|
} else { |
|
previousNewFiber.sibling = _newFiber3; |
|
} |
|
|
|
previousNewFiber = _newFiber3; |
|
} |
|
|
|
if (getIsHydrating()) { |
|
var _numberOfForks3 = newIdx; |
|
pushTreeFork(returnFiber, _numberOfForks3); |
|
} |
|
|
|
return resultingFirstChild; |
|
} // Add all children to a key map for quick lookups. |
|
|
|
|
|
var existingChildren = mapRemainingChildren(returnFiber, oldFiber); // Keep scanning and use the map to restore deleted items as moves. |
|
|
|
for (; !step.done; newIdx++, step = newChildren.next()) { |
|
var _newFiber4 = updateFromMap(existingChildren, returnFiber, newIdx, step.value, lanes); |
|
|
|
if (_newFiber4 !== null) { |
|
if (shouldTrackSideEffects) { |
|
if (_newFiber4.alternate !== null) { |
|
// The new fiber is a work in progress, but if there exists a |
|
// current, that means that we reused the fiber. We need to delete |
|
// it from the child list so that we don't add it to the deletion |
|
// list. |
|
existingChildren.delete(_newFiber4.key === null ? newIdx : _newFiber4.key); |
|
} |
|
} |
|
|
|
lastPlacedIndex = placeChild(_newFiber4, lastPlacedIndex, newIdx); |
|
|
|
if (previousNewFiber === null) { |
|
resultingFirstChild = _newFiber4; |
|
} else { |
|
previousNewFiber.sibling = _newFiber4; |
|
} |
|
|
|
previousNewFiber = _newFiber4; |
|
} |
|
} |
|
|
|
if (shouldTrackSideEffects) { |
|
// Any existing children that weren't consumed above were deleted. We need |
|
// to add them to the deletion list. |
|
existingChildren.forEach(function (child) { |
|
return deleteChild(returnFiber, child); |
|
}); |
|
} |
|
|
|
if (getIsHydrating()) { |
|
var _numberOfForks4 = newIdx; |
|
pushTreeFork(returnFiber, _numberOfForks4); |
|
} |
|
|
|
return resultingFirstChild; |
|
} |
|
|
|
function reconcileSingleTextNode(returnFiber, currentFirstChild, textContent, lanes) { |
|
// There's no need to check for keys on text nodes since we don't have a |
|
// way to define them. |
|
if (currentFirstChild !== null && currentFirstChild.tag === HostText) { |
|
// We already have an existing node so let's just update it and delete |
|
// the rest. |
|
deleteRemainingChildren(returnFiber, currentFirstChild.sibling); |
|
var existing = useFiber(currentFirstChild, textContent); |
|
existing.return = returnFiber; |
|
return existing; |
|
} // The existing first child is not a text node so we need to create one |
|
// and delete the existing ones. |
|
|
|
|
|
deleteRemainingChildren(returnFiber, currentFirstChild); |
|
var created = createFiberFromText(textContent, returnFiber.mode, lanes); |
|
created.return = returnFiber; |
|
return created; |
|
} |
|
|
|
function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) { |
|
var key = element.key; |
|
var child = currentFirstChild; |
|
|
|
while (child !== null) { |
|
// TODO: If key === null and child.key === null, then this only applies to |
|
// the first item in the list. |
|
if (child.key === key) { |
|
var elementType = element.type; |
|
|
|
if (elementType === REACT_FRAGMENT_TYPE) { |
|
if (child.tag === Fragment) { |
|
deleteRemainingChildren(returnFiber, child.sibling); |
|
var existing = useFiber(child, element.props.children); |
|
existing.return = returnFiber; |
|
|
|
{ |
|
existing._debugSource = element._source; |
|
existing._debugOwner = element._owner; |
|
} |
|
|
|
return existing; |
|
} |
|
} else { |
|
if (child.elementType === elementType || ( // Keep this check inline so it only runs on the false path: |
|
isCompatibleFamilyForHotReloading(child, element) ) || // Lazy types should reconcile their resolved type. |
|
// We need to do this after the Hot Reloading check above, |
|
// because hot reloading has different semantics than prod because |
|
// it doesn't resuspend. So we can't let the call below suspend. |
|
typeof elementType === 'object' && elementType !== null && elementType.$$typeof === REACT_LAZY_TYPE && resolveLazy(elementType) === child.type) { |
|
deleteRemainingChildren(returnFiber, child.sibling); |
|
|
|
var _existing = useFiber(child, element.props); |
|
|
|
_existing.ref = coerceRef(returnFiber, child, element); |
|
_existing.return = returnFiber; |
|
|
|
{ |
|
_existing._debugSource = element._source; |
|
_existing._debugOwner = element._owner; |
|
} |
|
|
|
return _existing; |
|
} |
|
} // Didn't match. |
|
|
|
|
|
deleteRemainingChildren(returnFiber, child); |
|
break; |
|
} else { |
|
deleteChild(returnFiber, child); |
|
} |
|
|
|
child = child.sibling; |
|
} |
|
|
|
if (element.type === REACT_FRAGMENT_TYPE) { |
|
var created = createFiberFromFragment(element.props.children, returnFiber.mode, lanes, element.key); |
|
created.return = returnFiber; |
|
return created; |
|
} else { |
|
var _created4 = createFiberFromElement(element, returnFiber.mode, lanes); |
|
|
|
_created4.ref = coerceRef(returnFiber, currentFirstChild, element); |
|
_created4.return = returnFiber; |
|
return _created4; |
|
} |
|
} |
|
|
|
function reconcileSinglePortal(returnFiber, currentFirstChild, portal, lanes) { |
|
var key = portal.key; |
|
var child = currentFirstChild; |
|
|
|
while (child !== null) { |
|
// TODO: If key === null and child.key === null, then this only applies to |
|
// the first item in the list. |
|
if (child.key === key) { |
|
if (child.tag === HostPortal && child.stateNode.containerInfo === portal.containerInfo && child.stateNode.implementation === portal.implementation) { |
|
deleteRemainingChildren(returnFiber, child.sibling); |
|
var existing = useFiber(child, portal.children || []); |
|
existing.return = returnFiber; |
|
return existing; |
|
} else { |
|
deleteRemainingChildren(returnFiber, child); |
|
break; |
|
} |
|
} else { |
|
deleteChild(returnFiber, child); |
|
} |
|
|
|
child = child.sibling; |
|
} |
|
|
|
var created = createFiberFromPortal(portal, returnFiber.mode, lanes); |
|
created.return = returnFiber; |
|
return created; |
|
} // This API will tag the children with the side-effect of the reconciliation |
|
// itself. They will be added to the side-effect list as we pass through the |
|
// children and the parent. |
|
|
|
|
|
function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) { |
|
// This function is not recursive. |
|
// If the top level item is an array, we treat it as a set of children, |
|
// not as a fragment. Nested arrays on the other hand will be treated as |
|
// fragment nodes. Recursion happens at the normal flow. |
|
// Handle top level unkeyed fragments as if they were arrays. |
|
// This leads to an ambiguity between <>{[...]}</> and <>...</>. |
|
// We treat the ambiguous cases above the same. |
|
var isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null; |
|
|
|
if (isUnkeyedTopLevelFragment) { |
|
newChild = newChild.props.children; |
|
} // Handle object types |
|
|
|
|
|
if (typeof newChild === 'object' && newChild !== null) { |
|
switch (newChild.$$typeof) { |
|
case REACT_ELEMENT_TYPE: |
|
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes)); |
|
|
|
case REACT_PORTAL_TYPE: |
|
return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes)); |
|
|
|
case REACT_LAZY_TYPE: |
|
var payload = newChild._payload; |
|
var init = newChild._init; // TODO: This function is supposed to be non-recursive. |
|
|
|
return reconcileChildFibers(returnFiber, currentFirstChild, init(payload), lanes); |
|
} |
|
|
|
if (isArray(newChild)) { |
|
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, lanes); |
|
} |
|
|
|
if (getIteratorFn(newChild)) { |
|
return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, lanes); |
|
} |
|
|
|
throwOnInvalidObjectType(returnFiber, newChild); |
|
} |
|
|
|
if (typeof newChild === 'string' && newChild !== '' || typeof newChild === 'number') { |
|
return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, lanes)); |
|
} |
|
|
|
{ |
|
if (typeof newChild === 'function') { |
|
warnOnFunctionType(returnFiber); |
|
} |
|
} // Remaining cases are all treated as empty. |
|
|
|
|
|
return deleteRemainingChildren(returnFiber, currentFirstChild); |
|
} |
|
|
|
return reconcileChildFibers; |
|
} |
|
|
|
var reconcileChildFibers = ChildReconciler(true); |
|
var mountChildFibers = ChildReconciler(false); |
|
function cloneChildFibers(current, workInProgress) { |
|
if (current !== null && workInProgress.child !== current.child) { |
|
throw new Error('Resuming work not yet implemented.'); |
|
} |
|
|
|
if (workInProgress.child === null) { |
|
return; |
|
} |
|
|
|
var currentChild = workInProgress.child; |
|
var newChild = createWorkInProgress(currentChild, currentChild.pendingProps); |
|
workInProgress.child = newChild; |
|
newChild.return = workInProgress; |
|
|
|
while (currentChild.sibling !== null) { |
|
currentChild = currentChild.sibling; |
|
newChild = newChild.sibling = createWorkInProgress(currentChild, currentChild.pendingProps); |
|
newChild.return = workInProgress; |
|
} |
|
|
|
newChild.sibling = null; |
|
} // Reset a workInProgress child set to prepare it for a second pass. |
|
|
|
function resetChildFibers(workInProgress, lanes) { |
|
var child = workInProgress.child; |
|
|
|
while (child !== null) { |
|
resetWorkInProgress(child, lanes); |
|
child = child.sibling; |
|
} |
|
} |
|
|
|
var NO_CONTEXT = {}; |
|
var contextStackCursor$1 = createCursor(NO_CONTEXT); |
|
var contextFiberStackCursor = createCursor(NO_CONTEXT); |
|
var rootInstanceStackCursor = createCursor(NO_CONTEXT); |
|
|
|
function requiredContext(c) { |
|
if (c === NO_CONTEXT) { |
|
throw new Error('Expected host context to exist. This error is likely caused by a bug ' + 'in React. Please file an issue.'); |
|
} |
|
|
|
return c; |
|
} |
|
|
|
function getRootHostContainer() { |
|
var rootInstance = requiredContext(rootInstanceStackCursor.current); |
|
return rootInstance; |
|
} |
|
|
|
function pushHostContainer(fiber, nextRootInstance) { |
|
// Push current root instance onto the stack; |
|
// This allows us to reset root when portals are popped. |
|
push(rootInstanceStackCursor, nextRootInstance, fiber); // Track the context and the Fiber that provided it. |
|
// This enables us to pop only Fibers that provide unique contexts. |
|
|
|
push(contextFiberStackCursor, fiber, fiber); // Finally, we need to push the host context to the stack. |
|
// However, we can't just call getRootHostContext() and push it because |
|
// we'd have a different number of entries on the stack depending on |
|
// whether getRootHostContext() throws somewhere in renderer code or not. |
|
// So we push an empty value first. This lets us safely unwind on errors. |
|
|
|
push(contextStackCursor$1, NO_CONTEXT, fiber); |
|
var nextRootContext = getRootHostContext(nextRootInstance); // Now that we know this function doesn't throw, replace it. |
|
|
|
pop(contextStackCursor$1, fiber); |
|
push(contextStackCursor$1, nextRootContext, fiber); |
|
} |
|
|
|
function popHostContainer(fiber) { |
|
pop(contextStackCursor$1, fiber); |
|
pop(contextFiberStackCursor, fiber); |
|
pop(rootInstanceStackCursor, fiber); |
|
} |
|
|
|
function getHostContext() { |
|
var context = requiredContext(contextStackCursor$1.current); |
|
return context; |
|
} |
|
|
|
function pushHostContext(fiber) { |
|
var rootInstance = requiredContext(rootInstanceStackCursor.current); |
|
var context = requiredContext(contextStackCursor$1.current); |
|
var nextContext = getChildHostContext(context, fiber.type); // Don't push this Fiber's context unless it's unique. |
|
|
|
if (context === nextContext) { |
|
return; |
|
} // Track the context and the Fiber that provided it. |
|
// This enables us to pop only Fibers that provide unique contexts. |
|
|
|
|
|
push(contextFiberStackCursor, fiber, fiber); |
|
push(contextStackCursor$1, nextContext, fiber); |
|
} |
|
|
|
function popHostContext(fiber) { |
|
// Do not pop unless this Fiber provided the current context. |
|
// pushHostContext() only pushes Fibers that provide unique contexts. |
|
if (contextFiberStackCursor.current !== fiber) { |
|
return; |
|
} |
|
|
|
pop(contextStackCursor$1, fiber); |
|
pop(contextFiberStackCursor, fiber); |
|
} |
|
|
|
var DefaultSuspenseContext = 0; // The Suspense Context is split into two parts. The lower bits is |
|
// inherited deeply down the subtree. The upper bits only affect |
|
// this immediate suspense boundary and gets reset each new |
|
// boundary or suspense list. |
|
|
|
var SubtreeSuspenseContextMask = 1; // Subtree Flags: |
|
// InvisibleParentSuspenseContext indicates that one of our parent Suspense |
|
// boundaries is not currently showing visible main content. |
|
// Either because it is already showing a fallback or is not mounted at all. |
|
// We can use this to determine if it is desirable to trigger a fallback at |
|
// the parent. If not, then we might need to trigger undesirable boundaries |
|
// and/or suspend the commit to avoid hiding the parent content. |
|
|
|
var InvisibleParentSuspenseContext = 1; // Shallow Flags: |
|
// ForceSuspenseFallback can be used by SuspenseList to force newly added |
|
// items into their fallback state during one of the render passes. |
|
|
|
var ForceSuspenseFallback = 2; |
|
var suspenseStackCursor = createCursor(DefaultSuspenseContext); |
|
function hasSuspenseContext(parentContext, flag) { |
|
return (parentContext & flag) !== 0; |
|
} |
|
function setDefaultShallowSuspenseContext(parentContext) { |
|
return parentContext & SubtreeSuspenseContextMask; |
|
} |
|
function setShallowSuspenseContext(parentContext, shallowContext) { |
|
return parentContext & SubtreeSuspenseContextMask | shallowContext; |
|
} |
|
function addSubtreeSuspenseContext(parentContext, subtreeContext) { |
|
return parentContext | subtreeContext; |
|
} |
|
function pushSuspenseContext(fiber, newContext) { |
|
push(suspenseStackCursor, newContext, fiber); |
|
} |
|
function popSuspenseContext(fiber) { |
|
pop(suspenseStackCursor, fiber); |
|
} |
|
|
|
function shouldCaptureSuspense(workInProgress, hasInvisibleParent) { |
|
// If it was the primary children that just suspended, capture and render the |
|
// fallback. Otherwise, don't capture and bubble to the next boundary. |
|
var nextState = workInProgress.memoizedState; |
|
|
|
if (nextState !== null) { |
|
if (nextState.dehydrated !== null) { |
|
// A dehydrated boundary always captures. |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
var props = workInProgress.memoizedProps; // Regular boundaries always capture. |
|
|
|
{ |
|
return true; |
|
} // If it's a boundary we should avoid, then we prefer to bubble up to the |
|
} |
|
function findFirstSuspended(row) { |
|
var node = row; |
|
|
|
while (node !== null) { |
|
if (node.tag === SuspenseComponent) { |
|
var state = node.memoizedState; |
|
|
|
if (state !== null) { |
|
var dehydrated = state.dehydrated; |
|
|
|
if (dehydrated === null || isSuspenseInstancePending(dehydrated) || isSuspenseInstanceFallback(dehydrated)) { |
|
return node; |
|
} |
|
} |
|
} else if (node.tag === SuspenseListComponent && // revealOrder undefined can't be trusted because it don't |
|
// keep track of whether it suspended or not. |
|
node.memoizedProps.revealOrder !== undefined) { |
|
var didSuspend = (node.flags & DidCapture) !== NoFlags; |
|
|
|
if (didSuspend) { |
|
return node; |
|
} |
|
} else if (node.child !== null) { |
|
node.child.return = node; |
|
node = node.child; |
|
continue; |
|
} |
|
|
|
if (node === row) { |
|
return null; |
|
} |
|
|
|
while (node.sibling === null) { |
|
if (node.return === null || node.return === row) { |
|
return null; |
|
} |
|
|
|
node = node.return; |
|
} |
|
|
|
node.sibling.return = node.return; |
|
node = node.sibling; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
var NoFlags$1 = |
|
/* */ |
|
0; // Represents whether effect should fire. |
|
|
|
var HasEffect = |
|
/* */ |
|
1; // Represents the phase in which the effect (not the clean-up) fires. |
|
|
|
var Insertion = |
|
/* */ |
|
2; |
|
var Layout = |
|
/* */ |
|
4; |
|
var Passive$1 = |
|
/* */ |
|
8; |
|
|
|
// and should be reset before starting a new render. |
|
// This tracks which mutable sources need to be reset after a render. |
|
|
|
var workInProgressSources = []; |
|
function resetWorkInProgressVersions() { |
|
for (var i = 0; i < workInProgressSources.length; i++) { |
|
var mutableSource = workInProgressSources[i]; |
|
|
|
{ |
|
mutableSource._workInProgressVersionPrimary = null; |
|
} |
|
} |
|
|
|
workInProgressSources.length = 0; |
|
} |
|
// This ensures that the version used for server rendering matches the one |
|
// that is eventually read during hydration. |
|
// If they don't match there's a potential tear and a full deopt render is required. |
|
|
|
function registerMutableSourceForHydration(root, mutableSource) { |
|
var getVersion = mutableSource._getVersion; |
|
var version = getVersion(mutableSource._source); // TODO Clear this data once all pending hydration work is finished. |
|
// Retaining it forever may interfere with GC. |
|
|
|
if (root.mutableSourceEagerHydrationData == null) { |
|
root.mutableSourceEagerHydrationData = [mutableSource, version]; |
|
} else { |
|
root.mutableSourceEagerHydrationData.push(mutableSource, version); |
|
} |
|
} |
|
|
|
var ReactCurrentDispatcher$1 = ReactSharedInternals.ReactCurrentDispatcher, |
|
ReactCurrentBatchConfig$2 = ReactSharedInternals.ReactCurrentBatchConfig; |
|
var didWarnAboutMismatchedHooksForComponent; |
|
var didWarnUncachedGetSnapshot; |
|
|
|
{ |
|
didWarnAboutMismatchedHooksForComponent = new Set(); |
|
} |
|
|
|
// These are set right before calling the component. |
|
var renderLanes = NoLanes; // The work-in-progress fiber. I've named it differently to distinguish it from |
|
// the work-in-progress hook. |
|
|
|
var currentlyRenderingFiber$1 = null; // Hooks are stored as a linked list on the fiber's memoizedState field. The |
|
// current hook list is the list that belongs to the current fiber. The |
|
// work-in-progress hook list is a new list that will be added to the |
|
// work-in-progress fiber. |
|
|
|
var currentHook = null; |
|
var workInProgressHook = null; // Whether an update was scheduled at any point during the render phase. This |
|
// does not get reset if we do another render pass; only when we're completely |
|
// finished evaluating this component. This is an optimization so we know |
|
// whether we need to clear render phase updates after a throw. |
|
|
|
var didScheduleRenderPhaseUpdate = false; // Where an update was scheduled only during the current render pass. This |
|
// gets reset after each attempt. |
|
// TODO: Maybe there's some way to consolidate this with |
|
// `didScheduleRenderPhaseUpdate`. Or with `numberOfReRenders`. |
|
|
|
var didScheduleRenderPhaseUpdateDuringThisPass = false; // Counts the number of useId hooks in this component. |
|
|
|
var localIdCounter = 0; // Used for ids that are generated completely client-side (i.e. not during |
|
// hydration). This counter is global, so client ids are not stable across |
|
// render attempts. |
|
|
|
var globalClientIdCounter = 0; |
|
var RE_RENDER_LIMIT = 25; // In DEV, this is the name of the currently executing primitive hook |
|
|
|
var currentHookNameInDev = null; // In DEV, this list ensures that hooks are called in the same order between renders. |
|
// The list stores the order of hooks used during the initial render (mount). |
|
// Subsequent renders (updates) reference this list. |
|
|
|
var hookTypesDev = null; |
|
var hookTypesUpdateIndexDev = -1; // In DEV, this tracks whether currently rendering component needs to ignore |
|
// the dependencies for Hooks that need them (e.g. useEffect or useMemo). |
|
// When true, such Hooks will always be "remounted". Only used during hot reload. |
|
|
|
var ignorePreviousDependencies = false; |
|
|
|
function mountHookTypesDev() { |
|
{ |
|
var hookName = currentHookNameInDev; |
|
|
|
if (hookTypesDev === null) { |
|
hookTypesDev = [hookName]; |
|
} else { |
|
hookTypesDev.push(hookName); |
|
} |
|
} |
|
} |
|
|
|
function updateHookTypesDev() { |
|
{ |
|
var hookName = currentHookNameInDev; |
|
|
|
if (hookTypesDev !== null) { |
|
hookTypesUpdateIndexDev++; |
|
|
|
if (hookTypesDev[hookTypesUpdateIndexDev] !== hookName) { |
|
warnOnHookMismatchInDev(hookName); |
|
} |
|
} |
|
} |
|
} |
|
|
|
function checkDepsAreArrayDev(deps) { |
|
{ |
|
if (deps !== undefined && deps !== null && !isArray(deps)) { |
|
// Verify deps, but only on mount to avoid extra checks. |
|
// It's unlikely their type would change as usually you define them inline. |
|
error('%s received a final argument that is not an array (instead, received `%s`). When ' + 'specified, the final argument must be an array.', currentHookNameInDev, typeof deps); |
|
} |
|
} |
|
} |
|
|
|
function warnOnHookMismatchInDev(currentHookName) { |
|
{ |
|
var componentName = getComponentNameFromFiber(currentlyRenderingFiber$1); |
|
|
|
if (!didWarnAboutMismatchedHooksForComponent.has(componentName)) { |
|
didWarnAboutMismatchedHooksForComponent.add(componentName); |
|
|
|
if (hookTypesDev !== null) { |
|
var table = ''; |
|
var secondColumnStart = 30; |
|
|
|
for (var i = 0; i <= hookTypesUpdateIndexDev; i++) { |
|
var oldHookName = hookTypesDev[i]; |
|
var newHookName = i === hookTypesUpdateIndexDev ? currentHookName : oldHookName; |
|
var row = i + 1 + ". " + oldHookName; // Extra space so second column lines up |
|
// lol @ IE not supporting String#repeat |
|
|
|
while (row.length < secondColumnStart) { |
|
row += ' '; |
|
} |
|
|
|
row += newHookName + '\n'; |
|
table += row; |
|
} |
|
|
|
error('React has detected a change in the order of Hooks called by %s. ' + 'This will lead to bugs and errors if not fixed. ' + 'For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks\n\n' + ' Previous render Next render\n' + ' ------------------------------------------------------\n' + '%s' + ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n', componentName, table); |
|
} |
|
} |
|
} |
|
} |
|
|
|
function throwInvalidHookError() { |
|
throw new Error('Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' + ' one of the following reasons:\n' + '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' + '2. You might be breaking the Rules of Hooks\n' + '3. You might have more than one copy of React in the same app\n' + 'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.'); |
|
} |
|
|
|
function areHookInputsEqual(nextDeps, prevDeps) { |
|
{ |
|
if (ignorePreviousDependencies) { |
|
// Only true when this component is being hot reloaded. |
|
return false; |
|
} |
|
} |
|
|
|
if (prevDeps === null) { |
|
{ |
|
error('%s received a final argument during this render, but not during ' + 'the previous render. Even though the final argument is optional, ' + 'its type cannot change between renders.', currentHookNameInDev); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
{ |
|
// Don't bother comparing lengths in prod because these arrays should be |
|
// passed inline. |
|
if (nextDeps.length !== prevDeps.length) { |
|
error('The final argument passed to %s changed size between renders. The ' + 'order and size of this array must remain constant.\n\n' + 'Previous: %s\n' + 'Incoming: %s', currentHookNameInDev, "[" + prevDeps.join(', ') + "]", "[" + nextDeps.join(', ') + "]"); |
|
} |
|
} |
|
|
|
for (var i = 0; i < prevDeps.length && i < nextDeps.length; i++) { |
|
if (objectIs(nextDeps[i], prevDeps[i])) { |
|
continue; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) { |
|
renderLanes = nextRenderLanes; |
|
currentlyRenderingFiber$1 = workInProgress; |
|
|
|
{ |
|
hookTypesDev = current !== null ? current._debugHookTypes : null; |
|
hookTypesUpdateIndexDev = -1; // Used for hot reloading: |
|
|
|
ignorePreviousDependencies = current !== null && current.type !== workInProgress.type; |
|
} |
|
|
|
workInProgress.memoizedState = null; |
|
workInProgress.updateQueue = null; |
|
workInProgress.lanes = NoLanes; // The following should have already been reset |
|
// currentHook = null; |
|
// workInProgressHook = null; |
|
// didScheduleRenderPhaseUpdate = false; |
|
// localIdCounter = 0; |
|
// TODO Warn if no hooks are used at all during mount, then some are used during update. |
|
// Currently we will identify the update render as a mount because memoizedState === null. |
|
// This is tricky because it's valid for certain types of components (e.g. React.lazy) |
|
// Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used. |
|
// Non-stateful hooks (e.g. context) don't get added to memoizedState, |
|
// so memoizedState would be null during updates and mounts. |
|
|
|
{ |
|
if (current !== null && current.memoizedState !== null) { |
|
ReactCurrentDispatcher$1.current = HooksDispatcherOnUpdateInDEV; |
|
} else if (hookTypesDev !== null) { |
|
// This dispatcher handles an edge case where a component is updating, |
|
// but no stateful hooks have been used. |
|
// We want to match the production code behavior (which will use HooksDispatcherOnMount), |
|
// but with the extra DEV validation to ensure hooks ordering hasn't changed. |
|
// This dispatcher does that. |
|
ReactCurrentDispatcher$1.current = HooksDispatcherOnMountWithHookTypesInDEV; |
|
} else { |
|
ReactCurrentDispatcher$1.current = HooksDispatcherOnMountInDEV; |
|
} |
|
} |
|
|
|
var children = Component(props, secondArg); // Check if there was a render phase update |
|
|
|
if (didScheduleRenderPhaseUpdateDuringThisPass) { |
|
// Keep rendering in a loop for as long as render phase updates continue to |
|
// be scheduled. Use a counter to prevent infinite loops. |
|
var numberOfReRenders = 0; |
|
|
|
do { |
|
didScheduleRenderPhaseUpdateDuringThisPass = false; |
|
localIdCounter = 0; |
|
|
|
if (numberOfReRenders >= RE_RENDER_LIMIT) { |
|
throw new Error('Too many re-renders. React limits the number of renders to prevent ' + 'an infinite loop.'); |
|
} |
|
|
|
numberOfReRenders += 1; |
|
|
|
{ |
|
// Even when hot reloading, allow dependencies to stabilize |
|
// after first render to prevent infinite render phase updates. |
|
ignorePreviousDependencies = false; |
|
} // Start over from the beginning of the list |
|
|
|
|
|
currentHook = null; |
|
workInProgressHook = null; |
|
workInProgress.updateQueue = null; |
|
|
|
{ |
|
// Also validate hook order for cascading updates. |
|
hookTypesUpdateIndexDev = -1; |
|
} |
|
|
|
ReactCurrentDispatcher$1.current = HooksDispatcherOnRerenderInDEV ; |
|
children = Component(props, secondArg); |
|
} while (didScheduleRenderPhaseUpdateDuringThisPass); |
|
} // We can assume the previous dispatcher is always this one, since we set it |
|
// at the beginning of the render phase and there's no re-entrance. |
|
|
|
|
|
ReactCurrentDispatcher$1.current = ContextOnlyDispatcher; |
|
|
|
{ |
|
workInProgress._debugHookTypes = hookTypesDev; |
|
} // This check uses currentHook so that it works the same in DEV and prod bundles. |
|
// hookTypesDev could catch more cases (e.g. context) but only in DEV bundles. |
|
|
|
|
|
var didRenderTooFewHooks = currentHook !== null && currentHook.next !== null; |
|
renderLanes = NoLanes; |
|
currentlyRenderingFiber$1 = null; |
|
currentHook = null; |
|
workInProgressHook = null; |
|
|
|
{ |
|
currentHookNameInDev = null; |
|
hookTypesDev = null; |
|
hookTypesUpdateIndexDev = -1; // Confirm that a static flag was not added or removed since the last |
|
// render. If this fires, it suggests that we incorrectly reset the static |
|
// flags in some other part of the codebase. This has happened before, for |
|
// example, in the SuspenseList implementation. |
|
|
|
if (current !== null && (current.flags & StaticMask) !== (workInProgress.flags & StaticMask) && // Disable this warning in legacy mode, because legacy Suspense is weird |
|
// and creates false positives. To make this work in legacy mode, we'd |
|
// need to mark fibers that commit in an incomplete state, somehow. For |
|
// now I'll disable the warning that most of the bugs that would trigger |
|
// it are either exclusive to concurrent mode or exist in both. |
|
(current.mode & ConcurrentMode) !== NoMode) { |
|
error('Internal React error: Expected static flag was missing. Please ' + 'notify the React team.'); |
|
} |
|
} |
|
|
|
didScheduleRenderPhaseUpdate = false; // This is reset by checkDidRenderIdHook |
|
// localIdCounter = 0; |
|
|
|
if (didRenderTooFewHooks) { |
|
throw new Error('Rendered fewer hooks than expected. This may be caused by an accidental ' + 'early return statement.'); |
|
} |
|
|
|
return children; |
|
} |
|
function checkDidRenderIdHook() { |
|
// This should be called immediately after every renderWithHooks call. |
|
// Conceptually, it's part of the return value of renderWithHooks; it's only a |
|
// separate function to avoid using an array tuple. |
|
var didRenderIdHook = localIdCounter !== 0; |
|
localIdCounter = 0; |
|
return didRenderIdHook; |
|
} |
|
function bailoutHooks(current, workInProgress, lanes) { |
|
workInProgress.updateQueue = current.updateQueue; // TODO: Don't need to reset the flags here, because they're reset in the |
|
// complete phase (bubbleProperties). |
|
|
|
if ( (workInProgress.mode & StrictEffectsMode) !== NoMode) { |
|
workInProgress.flags &= ~(MountPassiveDev | MountLayoutDev | Passive | Update); |
|
} else { |
|
workInProgress.flags &= ~(Passive | Update); |
|
} |
|
|
|
current.lanes = removeLanes(current.lanes, lanes); |
|
} |
|
function resetHooksAfterThrow() { |
|
// We can assume the previous dispatcher is always this one, since we set it |
|
// at the beginning of the render phase and there's no re-entrance. |
|
ReactCurrentDispatcher$1.current = ContextOnlyDispatcher; |
|
|
|
if (didScheduleRenderPhaseUpdate) { |
|
// There were render phase updates. These are only valid for this render |
|
// phase, which we are now aborting. Remove the updates from the queues so |
|
// they do not persist to the next render. Do not remove updates from hooks |
|
// that weren't processed. |
|
// |
|
// Only reset the updates from the queue if it has a clone. If it does |
|
// not have a clone, that means it wasn't processed, and the updates were |
|
// scheduled before we entered the render phase. |
|
var hook = currentlyRenderingFiber$1.memoizedState; |
|
|
|
while (hook !== null) { |
|
var queue = hook.queue; |
|
|
|
if (queue !== null) { |
|
queue.pending = null; |
|
} |
|
|
|
hook = hook.next; |
|
} |
|
|
|
didScheduleRenderPhaseUpdate = false; |
|
} |
|
|
|
renderLanes = NoLanes; |
|
currentlyRenderingFiber$1 = null; |
|
currentHook = null; |
|
workInProgressHook = null; |
|
|
|
{ |
|
hookTypesDev = null; |
|
hookTypesUpdateIndexDev = -1; |
|
currentHookNameInDev = null; |
|
isUpdatingOpaqueValueInRenderPhase = false; |
|
} |
|
|
|
didScheduleRenderPhaseUpdateDuringThisPass = false; |
|
localIdCounter = 0; |
|
} |
|
|
|
function mountWorkInProgressHook() { |
|
var hook = { |
|
memoizedState: null, |
|
baseState: null, |
|
baseQueue: null, |
|
queue: null, |
|
next: null |
|
}; |
|
|
|
if (workInProgressHook === null) { |
|
// This is the first hook in the list |
|
currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook; |
|
} else { |
|
// Append to the end of the list |
|
workInProgressHook = workInProgressHook.next = hook; |
|
} |
|
|
|
return workInProgressHook; |
|
} |
|
|
|
function updateWorkInProgressHook() { |
|
// This function is used both for updates and for re-renders triggered by a |
|
// render phase update. It assumes there is either a current hook we can |
|
// clone, or a work-in-progress hook from a previous render pass that we can |
|
// use as a base. When we reach the end of the base list, we must switch to |
|
// the dispatcher used for mounts. |
|
var nextCurrentHook; |
|
|
|
if (currentHook === null) { |
|
var current = currentlyRenderingFiber$1.alternate; |
|
|
|
if (current !== null) { |
|
nextCurrentHook = current.memoizedState; |
|
} else { |
|
nextCurrentHook = null; |
|
} |
|
} else { |
|
nextCurrentHook = currentHook.next; |
|
} |
|
|
|
var nextWorkInProgressHook; |
|
|
|
if (workInProgressHook === null) { |
|
nextWorkInProgressHook = currentlyRenderingFiber$1.memoizedState; |
|
} else { |
|
nextWorkInProgressHook = workInProgressHook.next; |
|
} |
|
|
|
if (nextWorkInProgressHook !== null) { |
|
// There's already a work-in-progress. Reuse it. |
|
workInProgressHook = nextWorkInProgressHook; |
|
nextWorkInProgressHook = workInProgressHook.next; |
|
currentHook = nextCurrentHook; |
|
} else { |
|
// Clone from the current hook. |
|
if (nextCurrentHook === null) { |
|
throw new Error('Rendered more hooks than during the previous render.'); |
|
} |
|
|
|
currentHook = nextCurrentHook; |
|
var newHook = { |
|
memoizedState: currentHook.memoizedState, |
|
baseState: currentHook.baseState, |
|
baseQueue: currentHook.baseQueue, |
|
queue: currentHook.queue, |
|
next: null |
|
}; |
|
|
|
if (workInProgressHook === null) { |
|
// This is the first hook in the list. |
|
currentlyRenderingFiber$1.memoizedState = workInProgressHook = newHook; |
|
} else { |
|
// Append to the end of the list. |
|
workInProgressHook = workInProgressHook.next = newHook; |
|
} |
|
} |
|
|
|
return workInProgressHook; |
|
} |
|
|
|
function createFunctionComponentUpdateQueue() { |
|
return { |
|
lastEffect: null, |
|
stores: null |
|
}; |
|
} |
|
|
|
function basicStateReducer(state, action) { |
|
// $FlowFixMe: Flow doesn't like mixed types |
|
return typeof action === 'function' ? action(state) : action; |
|
} |
|
|
|
function mountReducer(reducer, initialArg, init) { |
|
var hook = mountWorkInProgressHook(); |
|
var initialState; |
|
|
|
if (init !== undefined) { |
|
initialState = init(initialArg); |
|
} else { |
|
initialState = initialArg; |
|
} |
|
|
|
hook.memoizedState = hook.baseState = initialState; |
|
var queue = { |
|
pending: null, |
|
interleaved: null, |
|
lanes: NoLanes, |
|
dispatch: null, |
|
lastRenderedReducer: reducer, |
|
lastRenderedState: initialState |
|
}; |
|
hook.queue = queue; |
|
var dispatch = queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber$1, queue); |
|
return [hook.memoizedState, dispatch]; |
|
} |
|
|
|
function updateReducer(reducer, initialArg, init) { |
|
var hook = updateWorkInProgressHook(); |
|
var queue = hook.queue; |
|
|
|
if (queue === null) { |
|
throw new Error('Should have a queue. This is likely a bug in React. Please file an issue.'); |
|
} |
|
|
|
queue.lastRenderedReducer = reducer; |
|
var current = currentHook; // The last rebase update that is NOT part of the base state. |
|
|
|
var baseQueue = current.baseQueue; // The last pending update that hasn't been processed yet. |
|
|
|
var pendingQueue = queue.pending; |
|
|
|
if (pendingQueue !== null) { |
|
// We have new updates that haven't been processed yet. |
|
// We'll add them to the base queue. |
|
if (baseQueue !== null) { |
|
// Merge the pending queue and the base queue. |
|
var baseFirst = baseQueue.next; |
|
var pendingFirst = pendingQueue.next; |
|
baseQueue.next = pendingFirst; |
|
pendingQueue.next = baseFirst; |
|
} |
|
|
|
{ |
|
if (current.baseQueue !== baseQueue) { |
|
// Internal invariant that should never happen, but feasibly could in |
|
// the future if we implement resuming, or some form of that. |
|
error('Internal error: Expected work-in-progress queue to be a clone. ' + 'This is a bug in React.'); |
|
} |
|
} |
|
|
|
current.baseQueue = baseQueue = pendingQueue; |
|
queue.pending = null; |
|
} |
|
|
|
if (baseQueue !== null) { |
|
// We have a queue to process. |
|
var first = baseQueue.next; |
|
var newState = current.baseState; |
|
var newBaseState = null; |
|
var newBaseQueueFirst = null; |
|
var newBaseQueueLast = null; |
|
var update = first; |
|
|
|
do { |
|
var updateLane = update.lane; |
|
|
|
if (!isSubsetOfLanes(renderLanes, updateLane)) { |
|
// Priority is insufficient. Skip this update. If this is the first |
|
// skipped update, the previous update/state is the new base |
|
// update/state. |
|
var clone = { |
|
lane: updateLane, |
|
action: update.action, |
|
hasEagerState: update.hasEagerState, |
|
eagerState: update.eagerState, |
|
next: null |
|
}; |
|
|
|
if (newBaseQueueLast === null) { |
|
newBaseQueueFirst = newBaseQueueLast = clone; |
|
newBaseState = newState; |
|
} else { |
|
newBaseQueueLast = newBaseQueueLast.next = clone; |
|
} // Update the remaining priority in the queue. |
|
// TODO: Don't need to accumulate this. Instead, we can remove |
|
// renderLanes from the original lanes. |
|
|
|
|
|
currentlyRenderingFiber$1.lanes = mergeLanes(currentlyRenderingFiber$1.lanes, updateLane); |
|
markSkippedUpdateLanes(updateLane); |
|
} else { |
|
// This update does have sufficient priority. |
|
if (newBaseQueueLast !== null) { |
|
var _clone = { |
|
// This update is going to be committed so we never want uncommit |
|
// it. Using NoLane works because 0 is a subset of all bitmasks, so |
|
// this will never be skipped by the check above. |
|
lane: NoLane, |
|
action: update.action, |
|
hasEagerState: update.hasEagerState, |
|
eagerState: update.eagerState, |
|
next: null |
|
}; |
|
newBaseQueueLast = newBaseQueueLast.next = _clone; |
|
} // Process this update. |
|
|
|
|
|
if (update.hasEagerState) { |
|
// If this update is a state update (not a reducer) and was processed eagerly, |
|
// we can use the eagerly computed state |
|
newState = update.eagerState; |
|
} else { |
|
var action = update.action; |
|
newState = reducer(newState, action); |
|
} |
|
} |
|
|
|
update = update.next; |
|
} while (update !== null && update !== first); |
|
|
|
if (newBaseQueueLast === null) { |
|
newBaseState = newState; |
|
} else { |
|
newBaseQueueLast.next = newBaseQueueFirst; |
|
} // Mark that the fiber performed work, but only if the new state is |
|
// different from the current state. |
|
|
|
|
|
if (!objectIs(newState, hook.memoizedState)) { |
|
markWorkInProgressReceivedUpdate(); |
|
} |
|
|
|
hook.memoizedState = newState; |
|
hook.baseState = newBaseState; |
|
hook.baseQueue = newBaseQueueLast; |
|
queue.lastRenderedState = newState; |
|
} // Interleaved updates are stored on a separate queue. We aren't going to |
|
// process them during this render, but we do need to track which lanes |
|
// are remaining. |
|
|
|
|
|
var lastInterleaved = queue.interleaved; |
|
|
|
if (lastInterleaved !== null) { |
|
var interleaved = lastInterleaved; |
|
|
|
do { |
|
var interleavedLane = interleaved.lane; |
|
currentlyRenderingFiber$1.lanes = mergeLanes(currentlyRenderingFiber$1.lanes, interleavedLane); |
|
markSkippedUpdateLanes(interleavedLane); |
|
interleaved = interleaved.next; |
|
} while (interleaved !== lastInterleaved); |
|
} else if (baseQueue === null) { |
|
// `queue.lanes` is used for entangling transitions. We can set it back to |
|
// zero once the queue is empty. |
|
queue.lanes = NoLanes; |
|
} |
|
|
|
var dispatch = queue.dispatch; |
|
return [hook.memoizedState, dispatch]; |
|
} |
|
|
|
function rerenderReducer(reducer, initialArg, init) { |
|
var hook = updateWorkInProgressHook(); |
|
var queue = hook.queue; |
|
|
|
if (queue === null) { |
|
throw new Error('Should have a queue. This is likely a bug in React. Please file an issue.'); |
|
} |
|
|
|
queue.lastRenderedReducer = reducer; // This is a re-render. Apply the new render phase updates to the previous |
|
// work-in-progress hook. |
|
|
|
var dispatch = queue.dispatch; |
|
var lastRenderPhaseUpdate = queue.pending; |
|
var newState = hook.memoizedState; |
|
|
|
if (lastRenderPhaseUpdate !== null) { |
|
// The queue doesn't persist past this render pass. |
|
queue.pending = null; |
|
var firstRenderPhaseUpdate = lastRenderPhaseUpdate.next; |
|
var update = firstRenderPhaseUpdate; |
|
|
|
do { |
|
// Process this render phase update. We don't have to check the |
|
// priority because it will always be the same as the current |
|
// render's. |
|
var action = update.action; |
|
newState = reducer(newState, action); |
|
update = update.next; |
|
} while (update !== firstRenderPhaseUpdate); // Mark that the fiber performed work, but only if the new state is |
|
// different from the current state. |
|
|
|
|
|
if (!objectIs(newState, hook.memoizedState)) { |
|
markWorkInProgressReceivedUpdate(); |
|
} |
|
|
|
hook.memoizedState = newState; // Don't persist the state accumulated from the render phase updates to |
|
// the base state unless the queue is empty. |
|
// TODO: Not sure if this is the desired semantics, but it's what we |
|
// do for gDSFP. I can't remember why. |
|
|
|
if (hook.baseQueue === null) { |
|
hook.baseState = newState; |
|
} |
|
|
|
queue.lastRenderedState = newState; |
|
} |
|
|
|
return [newState, dispatch]; |
|
} |
|
|
|
function mountMutableSource(source, getSnapshot, subscribe) { |
|
{ |
|
return undefined; |
|
} |
|
} |
|
|
|
function updateMutableSource(source, getSnapshot, subscribe) { |
|
{ |
|
return undefined; |
|
} |
|
} |
|
|
|
function mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) { |
|
var fiber = currentlyRenderingFiber$1; |
|
var hook = mountWorkInProgressHook(); |
|
var nextSnapshot; |
|
var isHydrating = getIsHydrating(); |
|
|
|
if (isHydrating) { |
|
if (getServerSnapshot === undefined) { |
|
throw new Error('Missing getServerSnapshot, which is required for ' + 'server-rendered content. Will revert to client rendering.'); |
|
} |
|
|
|
nextSnapshot = getServerSnapshot(); |
|
|
|
{ |
|
if (!didWarnUncachedGetSnapshot) { |
|
if (nextSnapshot !== getServerSnapshot()) { |
|
error('The result of getServerSnapshot should be cached to avoid an infinite loop'); |
|
|
|
didWarnUncachedGetSnapshot = true; |
|
} |
|
} |
|
} |
|
} else { |
|
nextSnapshot = getSnapshot(); |
|
|
|
{ |
|
if (!didWarnUncachedGetSnapshot) { |
|
var cachedSnapshot = getSnapshot(); |
|
|
|
if (!objectIs(nextSnapshot, cachedSnapshot)) { |
|
error('The result of getSnapshot should be cached to avoid an infinite loop'); |
|
|
|
didWarnUncachedGetSnapshot = true; |
|
} |
|
} |
|
} // Unless we're rendering a blocking lane, schedule a consistency check. |
|
// Right before committing, we will walk the tree and check if any of the |
|
// stores were mutated. |
|
// |
|
// We won't do this if we're hydrating server-rendered content, because if |
|
// the content is stale, it's already visible anyway. Instead we'll patch |
|
// it up in a passive effect. |
|
|
|
|
|
var root = getWorkInProgressRoot(); |
|
|
|
if (root === null) { |
|
throw new Error('Expected a work-in-progress root. This is a bug in React. Please file an issue.'); |
|
} |
|
|
|
if (!includesBlockingLane(root, renderLanes)) { |
|
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); |
|
} |
|
} // Read the current snapshot from the store on every render. This breaks the |
|
// normal rules of React, and only works because store updates are |
|
// always synchronous. |
|
|
|
|
|
hook.memoizedState = nextSnapshot; |
|
var inst = { |
|
value: nextSnapshot, |
|
getSnapshot: getSnapshot |
|
}; |
|
hook.queue = inst; // Schedule an effect to subscribe to the store. |
|
|
|
mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]); // Schedule an effect to update the mutable instance fields. We will update |
|
// this whenever subscribe, getSnapshot, or value changes. Because there's no |
|
// clean-up function, and we track the deps correctly, we can call pushEffect |
|
// directly, without storing any additional state. For the same reason, we |
|
// don't need to set a static flag, either. |
|
// TODO: We can move this to the passive phase once we add a pre-commit |
|
// consistency check. See the next comment. |
|
|
|
fiber.flags |= Passive; |
|
pushEffect(HasEffect | Passive$1, updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot), undefined, null); |
|
return nextSnapshot; |
|
} |
|
|
|
function updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) { |
|
var fiber = currentlyRenderingFiber$1; |
|
var hook = updateWorkInProgressHook(); // Read the current snapshot from the store on every render. This breaks the |
|
// normal rules of React, and only works because store updates are |
|
// always synchronous. |
|
|
|
var nextSnapshot = getSnapshot(); |
|
|
|
{ |
|
if (!didWarnUncachedGetSnapshot) { |
|
var cachedSnapshot = getSnapshot(); |
|
|
|
if (!objectIs(nextSnapshot, cachedSnapshot)) { |
|
error('The result of getSnapshot should be cached to avoid an infinite loop'); |
|
|
|
didWarnUncachedGetSnapshot = true; |
|
} |
|
} |
|
} |
|
|
|
var prevSnapshot = hook.memoizedState; |
|
var snapshotChanged = !objectIs(prevSnapshot, nextSnapshot); |
|
|
|
if (snapshotChanged) { |
|
hook.memoizedState = nextSnapshot; |
|
markWorkInProgressReceivedUpdate(); |
|
} |
|
|
|
var inst = hook.queue; |
|
updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]); // Whenever getSnapshot or subscribe changes, we need to check in the |
|
// commit phase if there was an interleaved mutation. In concurrent mode |
|
// this can happen all the time, but even in synchronous mode, an earlier |
|
// effect may have mutated the store. |
|
|
|
if (inst.getSnapshot !== getSnapshot || snapshotChanged || // Check if the susbcribe function changed. We can save some memory by |
|
// checking whether we scheduled a subscription effect above. |
|
workInProgressHook !== null && workInProgressHook.memoizedState.tag & HasEffect) { |
|
fiber.flags |= Passive; |
|
pushEffect(HasEffect | Passive$1, updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot), undefined, null); // Unless we're rendering a blocking lane, schedule a consistency check. |
|
// Right before committing, we will walk the tree and check if any of the |
|
// stores were mutated. |
|
|
|
var root = getWorkInProgressRoot(); |
|
|
|
if (root === null) { |
|
throw new Error('Expected a work-in-progress root. This is a bug in React. Please file an issue.'); |
|
} |
|
|
|
if (!includesBlockingLane(root, renderLanes)) { |
|
pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); |
|
} |
|
} |
|
|
|
return nextSnapshot; |
|
} |
|
|
|
function pushStoreConsistencyCheck(fiber, getSnapshot, renderedSnapshot) { |
|
fiber.flags |= StoreConsistency; |
|
var check = { |
|
getSnapshot: getSnapshot, |
|
value: renderedSnapshot |
|
}; |
|
var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue; |
|
|
|
if (componentUpdateQueue === null) { |
|
componentUpdateQueue = createFunctionComponentUpdateQueue(); |
|
currentlyRenderingFiber$1.updateQueue = componentUpdateQueue; |
|
componentUpdateQueue.stores = [check]; |
|
} else { |
|
var stores = componentUpdateQueue.stores; |
|
|
|
if (stores === null) { |
|
componentUpdateQueue.stores = [check]; |
|
} else { |
|
stores.push(check); |
|
} |
|
} |
|
} |
|
|
|
function updateStoreInstance(fiber, inst, nextSnapshot, getSnapshot) { |
|
// These are updated in the passive phase |
|
inst.value = nextSnapshot; |
|
inst.getSnapshot = getSnapshot; // Something may have been mutated in between render and commit. This could |
|
// have been in an event that fired before the passive effects, or it could |
|
// have been in a layout effect. In that case, we would have used the old |
|
// snapsho and getSnapshot values to bail out. We need to check one more time. |
|
|
|
if (checkIfSnapshotChanged(inst)) { |
|
// Force a re-render. |
|
forceStoreRerender(fiber); |
|
} |
|
} |
|
|
|
function subscribeToStore(fiber, inst, subscribe) { |
|
var handleStoreChange = function () { |
|
// The store changed. Check if the snapshot changed since the last time we |
|
// read from the store. |
|
if (checkIfSnapshotChanged(inst)) { |
|
// Force a re-render. |
|
forceStoreRerender(fiber); |
|
} |
|
}; // Subscribe to the store and return a clean-up function. |
|
|
|
|
|
return subscribe(handleStoreChange); |
|
} |
|
|
|
function checkIfSnapshotChanged(inst) { |
|
var latestGetSnapshot = inst.getSnapshot; |
|
var prevValue = inst.value; |
|
|
|
try { |
|
var nextValue = latestGetSnapshot(); |
|
return !objectIs(prevValue, nextValue); |
|
} catch (error) { |
|
return true; |
|
} |
|
} |
|
|
|
function forceStoreRerender(fiber) { |
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane); |
|
|
|
if (root !== null) { |
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); |
|
} |
|
} |
|
|
|
function mountState(initialState) { |
|
var hook = mountWorkInProgressHook(); |
|
|
|
if (typeof initialState === 'function') { |
|
// $FlowFixMe: Flow doesn't like mixed types |
|
initialState = initialState(); |
|
} |
|
|
|
hook.memoizedState = hook.baseState = initialState; |
|
var queue = { |
|
pending: null, |
|
interleaved: null, |
|
lanes: NoLanes, |
|
dispatch: null, |
|
lastRenderedReducer: basicStateReducer, |
|
lastRenderedState: initialState |
|
}; |
|
hook.queue = queue; |
|
var dispatch = queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber$1, queue); |
|
return [hook.memoizedState, dispatch]; |
|
} |
|
|
|
function updateState(initialState) { |
|
return updateReducer(basicStateReducer); |
|
} |
|
|
|
function rerenderState(initialState) { |
|
return rerenderReducer(basicStateReducer); |
|
} |
|
|
|
function pushEffect(tag, create, destroy, deps) { |
|
var effect = { |
|
tag: tag, |
|
create: create, |
|
destroy: destroy, |
|
deps: deps, |
|
// Circular |
|
next: null |
|
}; |
|
var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue; |
|
|
|
if (componentUpdateQueue === null) { |
|
componentUpdateQueue = createFunctionComponentUpdateQueue(); |
|
currentlyRenderingFiber$1.updateQueue = componentUpdateQueue; |
|
componentUpdateQueue.lastEffect = effect.next = effect; |
|
} else { |
|
var lastEffect = componentUpdateQueue.lastEffect; |
|
|
|
if (lastEffect === null) { |
|
componentUpdateQueue.lastEffect = effect.next = effect; |
|
} else { |
|
var firstEffect = lastEffect.next; |
|
lastEffect.next = effect; |
|
effect.next = firstEffect; |
|
componentUpdateQueue.lastEffect = effect; |
|
} |
|
} |
|
|
|
return effect; |
|
} |
|
|
|
function mountRef(initialValue) { |
|
var hook = mountWorkInProgressHook(); |
|
|
|
{ |
|
var _ref2 = { |
|
current: initialValue |
|
}; |
|
hook.memoizedState = _ref2; |
|
return _ref2; |
|
} |
|
} |
|
|
|
function updateRef(initialValue) { |
|
var hook = updateWorkInProgressHook(); |
|
return hook.memoizedState; |
|
} |
|
|
|
function mountEffectImpl(fiberFlags, hookFlags, create, deps) { |
|
var hook = mountWorkInProgressHook(); |
|
var nextDeps = deps === undefined ? null : deps; |
|
currentlyRenderingFiber$1.flags |= fiberFlags; |
|
hook.memoizedState = pushEffect(HasEffect | hookFlags, create, undefined, nextDeps); |
|
} |
|
|
|
function updateEffectImpl(fiberFlags, hookFlags, create, deps) { |
|
var hook = updateWorkInProgressHook(); |
|
var nextDeps = deps === undefined ? null : deps; |
|
var destroy = undefined; |
|
|
|
if (currentHook !== null) { |
|
var prevEffect = currentHook.memoizedState; |
|
destroy = prevEffect.destroy; |
|
|
|
if (nextDeps !== null) { |
|
var prevDeps = prevEffect.deps; |
|
|
|
if (areHookInputsEqual(nextDeps, prevDeps)) { |
|
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
currentlyRenderingFiber$1.flags |= fiberFlags; |
|
hook.memoizedState = pushEffect(HasEffect | hookFlags, create, destroy, nextDeps); |
|
} |
|
|
|
function mountEffect(create, deps) { |
|
if ( (currentlyRenderingFiber$1.mode & StrictEffectsMode) !== NoMode) { |
|
return mountEffectImpl(MountPassiveDev | Passive | PassiveStatic, Passive$1, create, deps); |
|
} else { |
|
return mountEffectImpl(Passive | PassiveStatic, Passive$1, create, deps); |
|
} |
|
} |
|
|
|
function updateEffect(create, deps) { |
|
return updateEffectImpl(Passive, Passive$1, create, deps); |
|
} |
|
|
|
function mountInsertionEffect(create, deps) { |
|
return mountEffectImpl(Update, Insertion, create, deps); |
|
} |
|
|
|
function updateInsertionEffect(create, deps) { |
|
return updateEffectImpl(Update, Insertion, create, deps); |
|
} |
|
|
|
function mountLayoutEffect(create, deps) { |
|
var fiberFlags = Update; |
|
|
|
{ |
|
fiberFlags |= LayoutStatic; |
|
} |
|
|
|
if ( (currentlyRenderingFiber$1.mode & StrictEffectsMode) !== NoMode) { |
|
fiberFlags |= MountLayoutDev; |
|
} |
|
|
|
return mountEffectImpl(fiberFlags, Layout, create, deps); |
|
} |
|
|
|
function updateLayoutEffect(create, deps) { |
|
return updateEffectImpl(Update, Layout, create, deps); |
|
} |
|
|
|
function imperativeHandleEffect(create, ref) { |
|
if (typeof ref === 'function') { |
|
var refCallback = ref; |
|
|
|
var _inst = create(); |
|
|
|
refCallback(_inst); |
|
return function () { |
|
refCallback(null); |
|
}; |
|
} else if (ref !== null && ref !== undefined) { |
|
var refObject = ref; |
|
|
|
{ |
|
if (!refObject.hasOwnProperty('current')) { |
|
error('Expected useImperativeHandle() first argument to either be a ' + 'ref callback or React.createRef() object. Instead received: %s.', 'an object with keys {' + Object.keys(refObject).join(', ') + '}'); |
|
} |
|
} |
|
|
|
var _inst2 = create(); |
|
|
|
refObject.current = _inst2; |
|
return function () { |
|
refObject.current = null; |
|
}; |
|
} |
|
} |
|
|
|
function mountImperativeHandle(ref, create, deps) { |
|
{ |
|
if (typeof create !== 'function') { |
|
error('Expected useImperativeHandle() second argument to be a function ' + 'that creates a handle. Instead received: %s.', create !== null ? typeof create : 'null'); |
|
} |
|
} // TODO: If deps are provided, should we skip comparing the ref itself? |
|
|
|
|
|
var effectDeps = deps !== null && deps !== undefined ? deps.concat([ref]) : null; |
|
var fiberFlags = Update; |
|
|
|
{ |
|
fiberFlags |= LayoutStatic; |
|
} |
|
|
|
if ( (currentlyRenderingFiber$1.mode & StrictEffectsMode) !== NoMode) { |
|
fiberFlags |= MountLayoutDev; |
|
} |
|
|
|
return mountEffectImpl(fiberFlags, Layout, imperativeHandleEffect.bind(null, create, ref), effectDeps); |
|
} |
|
|
|
function updateImperativeHandle(ref, create, deps) { |
|
{ |
|
if (typeof create !== 'function') { |
|
error('Expected useImperativeHandle() second argument to be a function ' + 'that creates a handle. Instead received: %s.', create !== null ? typeof create : 'null'); |
|
} |
|
} // TODO: If deps are provided, should we skip comparing the ref itself? |
|
|
|
|
|
var effectDeps = deps !== null && deps !== undefined ? deps.concat([ref]) : null; |
|
return updateEffectImpl(Update, Layout, imperativeHandleEffect.bind(null, create, ref), effectDeps); |
|
} |
|
|
|
function mountDebugValue(value, formatterFn) {// This hook is normally a no-op. |
|
// The react-debug-hooks package injects its own implementation |
|
// so that e.g. DevTools can display custom hook values. |
|
} |
|
|
|
var updateDebugValue = mountDebugValue; |
|
|
|
function mountCallback(callback, deps) { |
|
var hook = mountWorkInProgressHook(); |
|
var nextDeps = deps === undefined ? null : deps; |
|
hook.memoizedState = [callback, nextDeps]; |
|
return callback; |
|
} |
|
|
|
function updateCallback(callback, deps) { |
|
var hook = updateWorkInProgressHook(); |
|
var nextDeps = deps === undefined ? null : deps; |
|
var prevState = hook.memoizedState; |
|
|
|
if (prevState !== null) { |
|
if (nextDeps !== null) { |
|
var prevDeps = prevState[1]; |
|
|
|
if (areHookInputsEqual(nextDeps, prevDeps)) { |
|
return prevState[0]; |
|
} |
|
} |
|
} |
|
|
|
hook.memoizedState = [callback, nextDeps]; |
|
return callback; |
|
} |
|
|
|
function mountMemo(nextCreate, deps) { |
|
var hook = mountWorkInProgressHook(); |
|
var nextDeps = deps === undefined ? null : deps; |
|
var nextValue = nextCreate(); |
|
hook.memoizedState = [nextValue, nextDeps]; |
|
return nextValue; |
|
} |
|
|
|
function updateMemo(nextCreate, deps) { |
|
var hook = updateWorkInProgressHook(); |
|
var nextDeps = deps === undefined ? null : deps; |
|
var prevState = hook.memoizedState; |
|
|
|
if (prevState !== null) { |
|
// Assume these are defined. If they're not, areHookInputsEqual will warn. |
|
if (nextDeps !== null) { |
|
var prevDeps = prevState[1]; |
|
|
|
if (areHookInputsEqual(nextDeps, prevDeps)) { |
|
return prevState[0]; |
|
} |
|
} |
|
} |
|
|
|
var nextValue = nextCreate(); |
|
hook.memoizedState = [nextValue, nextDeps]; |
|
return nextValue; |
|
} |
|
|
|
function mountDeferredValue(value) { |
|
var hook = mountWorkInProgressHook(); |
|
hook.memoizedState = value; |
|
return value; |
|
} |
|
|
|
function updateDeferredValue(value) { |
|
var hook = updateWorkInProgressHook(); |
|
var resolvedCurrentHook = currentHook; |
|
var prevValue = resolvedCurrentHook.memoizedState; |
|
return updateDeferredValueImpl(hook, prevValue, value); |
|
} |
|
|
|
function rerenderDeferredValue(value) { |
|
var hook = updateWorkInProgressHook(); |
|
|
|
if (currentHook === null) { |
|
// This is a rerender during a mount. |
|
hook.memoizedState = value; |
|
return value; |
|
} else { |
|
// This is a rerender during an update. |
|
var prevValue = currentHook.memoizedState; |
|
return updateDeferredValueImpl(hook, prevValue, value); |
|
} |
|
} |
|
|
|
function updateDeferredValueImpl(hook, prevValue, value) { |
|
var shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes); |
|
|
|
if (shouldDeferValue) { |
|
// This is an urgent update. If the value has changed, keep using the |
|
// previous value and spawn a deferred render to update it later. |
|
if (!objectIs(value, prevValue)) { |
|
// Schedule a deferred render |
|
var deferredLane = claimNextTransitionLane(); |
|
currentlyRenderingFiber$1.lanes = mergeLanes(currentlyRenderingFiber$1.lanes, deferredLane); |
|
markSkippedUpdateLanes(deferredLane); // Set this to true to indicate that the rendered value is inconsistent |
|
// from the latest value. The name "baseState" doesn't really match how we |
|
// use it because we're reusing a state hook field instead of creating a |
|
// new one. |
|
|
|
hook.baseState = true; |
|
} // Reuse the previous value |
|
|
|
|
|
return prevValue; |
|
} else { |
|
// This is not an urgent update, so we can use the latest value regardless |
|
// of what it is. No need to defer it. |
|
// However, if we're currently inside a spawned render, then we need to mark |
|
// this as an update to prevent the fiber from bailing out. |
|
// |
|
// `baseState` is true when the current value is different from the rendered |
|
// value. The name doesn't really match how we use it because we're reusing |
|
// a state hook field instead of creating a new one. |
|
if (hook.baseState) { |
|
// Flip this back to false. |
|
hook.baseState = false; |
|
markWorkInProgressReceivedUpdate(); |
|
} |
|
|
|
hook.memoizedState = value; |
|
return value; |
|
} |
|
} |
|
|
|
function startTransition(setPending, callback, options) { |
|
var previousPriority = getCurrentUpdatePriority(); |
|
setCurrentUpdatePriority(higherEventPriority(previousPriority, ContinuousEventPriority)); |
|
setPending(true); |
|
var prevTransition = ReactCurrentBatchConfig$2.transition; |
|
ReactCurrentBatchConfig$2.transition = {}; |
|
var currentTransition = ReactCurrentBatchConfig$2.transition; |
|
|
|
{ |
|
ReactCurrentBatchConfig$2.transition._updatedFibers = new Set(); |
|
} |
|
|
|
try { |
|
setPending(false); |
|
callback(); |
|
} finally { |
|
setCurrentUpdatePriority(previousPriority); |
|
ReactCurrentBatchConfig$2.transition = prevTransition; |
|
|
|
{ |
|
if (prevTransition === null && currentTransition._updatedFibers) { |
|
var updatedFibersCount = currentTransition._updatedFibers.size; |
|
|
|
if (updatedFibersCount > 10) { |
|
warn('Detected a large number of updates inside startTransition. ' + 'If this is due to a subscription please re-write it to use React provided hooks. ' + 'Otherwise concurrent mode guarantees are off the table.'); |
|
} |
|
|
|
currentTransition._updatedFibers.clear(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
function mountTransition() { |
|
var _mountState = mountState(false), |
|
isPending = _mountState[0], |
|
setPending = _mountState[1]; // The `start` method never changes. |
|
|
|
|
|
var start = startTransition.bind(null, setPending); |
|
var hook = mountWorkInProgressHook(); |
|
hook.memoizedState = start; |
|
return [isPending, start]; |
|
} |
|
|
|
function updateTransition() { |
|
var _updateState = updateState(), |
|
isPending = _updateState[0]; |
|
|
|
var hook = updateWorkInProgressHook(); |
|
var start = hook.memoizedState; |
|
return [isPending, start]; |
|
} |
|
|
|
function rerenderTransition() { |
|
var _rerenderState = rerenderState(), |
|
isPending = _rerenderState[0]; |
|
|
|
var hook = updateWorkInProgressHook(); |
|
var start = hook.memoizedState; |
|
return [isPending, start]; |
|
} |
|
|
|
var isUpdatingOpaqueValueInRenderPhase = false; |
|
function getIsUpdatingOpaqueValueInRenderPhaseInDEV() { |
|
{ |
|
return isUpdatingOpaqueValueInRenderPhase; |
|
} |
|
} |
|
|
|
function mountId() { |
|
var hook = mountWorkInProgressHook(); |
|
var root = getWorkInProgressRoot(); // TODO: In Fizz, id generation is specific to each server config. Maybe we |
|
// should do this in Fiber, too? Deferring this decision for now because |
|
// there's no other place to store the prefix except for an internal field on |
|
// the public createRoot object, which the fiber tree does not currently have |
|
// a reference to. |
|
|
|
var identifierPrefix = root.identifierPrefix; |
|
var id; |
|
|
|
if (getIsHydrating()) { |
|
var treeId = getTreeId(); // Use a captial R prefix for server-generated ids. |
|
|
|
id = ':' + identifierPrefix + 'R' + treeId; // Unless this is the first id at this level, append a number at the end |
|
// that represents the position of this useId hook among all the useId |
|
// hooks for this fiber. |
|
|
|
var localId = localIdCounter++; |
|
|
|
if (localId > 0) { |
|
id += 'H' + localId.toString(32); |
|
} |
|
|
|
id += ':'; |
|
} else { |
|
// Use a lowercase r prefix for client-generated ids. |
|
var globalClientId = globalClientIdCounter++; |
|
id = ':' + identifierPrefix + 'r' + globalClientId.toString(32) + ':'; |
|
} |
|
|
|
hook.memoizedState = id; |
|
return id; |
|
} |
|
|
|
function updateId() { |
|
var hook = updateWorkInProgressHook(); |
|
var id = hook.memoizedState; |
|
return id; |
|
} |
|
|
|
function dispatchReducerAction(fiber, queue, action) { |
|
{ |
|
if (typeof arguments[3] === 'function') { |
|
error("State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + 'rendering, declare it in the component body with useEffect().'); |
|
} |
|
} |
|
|
|
var lane = requestUpdateLane(fiber); |
|
var update = { |
|
lane: lane, |
|
action: action, |
|
hasEagerState: false, |
|
eagerState: null, |
|
next: null |
|
}; |
|
|
|
if (isRenderPhaseUpdate(fiber)) { |
|
enqueueRenderPhaseUpdate(queue, update); |
|
} else { |
|
var root = enqueueConcurrentHookUpdate(fiber, queue, update, lane); |
|
|
|
if (root !== null) { |
|
var eventTime = requestEventTime(); |
|
scheduleUpdateOnFiber(root, fiber, lane, eventTime); |
|
entangleTransitionUpdate(root, queue, lane); |
|
} |
|
} |
|
|
|
markUpdateInDevTools(fiber, lane); |
|
} |
|
|
|
function dispatchSetState(fiber, queue, action) { |
|
{ |
|
if (typeof arguments[3] === 'function') { |
|
error("State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + 'rendering, declare it in the component body with useEffect().'); |
|
} |
|
} |
|
|
|
var lane = requestUpdateLane(fiber); |
|
var update = { |
|
lane: lane, |
|
action: action, |
|
hasEagerState: false, |
|
eagerState: null, |
|
next: null |
|
}; |
|
|
|
if (isRenderPhaseUpdate(fiber)) { |
|
enqueueRenderPhaseUpdate(queue, update); |
|
} else { |
|
var alternate = fiber.alternate; |
|
|
|
if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) { |
|
// The queue is currently empty, which means we can eagerly compute the |
|
// next state before entering the render phase. If the new state is the |
|
// same as the current state, we may be able to bail out entirely. |
|
var lastRenderedReducer = queue.lastRenderedReducer; |
|
|
|
if (lastRenderedReducer !== null) { |
|
var prevDispatcher; |
|
|
|
{ |
|
prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV; |
|
} |
|
|
|
try { |
|
var currentState = queue.lastRenderedState; |
|
var eagerState = lastRenderedReducer(currentState, action); // Stash the eagerly computed state, and the reducer used to compute |
|
// it, on the update object. If the reducer hasn't changed by the |
|
// time we enter the render phase, then the eager state can be used |
|
// without calling the reducer again. |
|
|
|
update.hasEagerState = true; |
|
update.eagerState = eagerState; |
|
|
|
if (objectIs(eagerState, currentState)) { |
|
// Fast path. We can bail out without scheduling React to re-render. |
|
// It's still possible that we'll need to rebase this update later, |
|
// if the component re-renders for a different reason and by that |
|
// time the reducer has changed. |
|
// TODO: Do we still need to entangle transitions in this case? |
|
enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update, lane); |
|
return; |
|
} |
|
} catch (error) {// Suppress the error. It will throw again in the render phase. |
|
} finally { |
|
{ |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
} |
|
} |
|
} |
|
|
|
var root = enqueueConcurrentHookUpdate(fiber, queue, update, lane); |
|
|
|
if (root !== null) { |
|
var eventTime = requestEventTime(); |
|
scheduleUpdateOnFiber(root, fiber, lane, eventTime); |
|
entangleTransitionUpdate(root, queue, lane); |
|
} |
|
} |
|
|
|
markUpdateInDevTools(fiber, lane); |
|
} |
|
|
|
function isRenderPhaseUpdate(fiber) { |
|
var alternate = fiber.alternate; |
|
return fiber === currentlyRenderingFiber$1 || alternate !== null && alternate === currentlyRenderingFiber$1; |
|
} |
|
|
|
function enqueueRenderPhaseUpdate(queue, update) { |
|
// This is a render phase update. Stash it in a lazily-created map of |
|
// queue -> linked list of updates. After this render pass, we'll restart |
|
// and apply the stashed updates on top of the work-in-progress hook. |
|
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true; |
|
var pending = queue.pending; |
|
|
|
if (pending === null) { |
|
// This is the first update. Create a circular list. |
|
update.next = update; |
|
} else { |
|
update.next = pending.next; |
|
pending.next = update; |
|
} |
|
|
|
queue.pending = update; |
|
} // TODO: Move to ReactFiberConcurrentUpdates? |
|
|
|
|
|
function entangleTransitionUpdate(root, queue, lane) { |
|
if (isTransitionLane(lane)) { |
|
var queueLanes = queue.lanes; // If any entangled lanes are no longer pending on the root, then they |
|
// must have finished. We can remove them from the shared queue, which |
|
// represents a superset of the actually pending lanes. In some cases we |
|
// may entangle more than we need to, but that's OK. In fact it's worse if |
|
// we *don't* entangle when we should. |
|
|
|
queueLanes = intersectLanes(queueLanes, root.pendingLanes); // Entangle the new transition lane with the other transition lanes. |
|
|
|
var newQueueLanes = mergeLanes(queueLanes, lane); |
|
queue.lanes = newQueueLanes; // Even if queue.lanes already include lane, we don't know for certain if |
|
// the lane finished since the last time we entangled it. So we need to |
|
// entangle it again, just to be sure. |
|
|
|
markRootEntangled(root, newQueueLanes); |
|
} |
|
} |
|
|
|
function markUpdateInDevTools(fiber, lane, action) { |
|
|
|
{ |
|
markStateUpdateScheduled(fiber, lane); |
|
} |
|
} |
|
|
|
var ContextOnlyDispatcher = { |
|
readContext: readContext, |
|
useCallback: throwInvalidHookError, |
|
useContext: throwInvalidHookError, |
|
useEffect: throwInvalidHookError, |
|
useImperativeHandle: throwInvalidHookError, |
|
useInsertionEffect: throwInvalidHookError, |
|
useLayoutEffect: throwInvalidHookError, |
|
useMemo: throwInvalidHookError, |
|
useReducer: throwInvalidHookError, |
|
useRef: throwInvalidHookError, |
|
useState: throwInvalidHookError, |
|
useDebugValue: throwInvalidHookError, |
|
useDeferredValue: throwInvalidHookError, |
|
useTransition: throwInvalidHookError, |
|
useMutableSource: throwInvalidHookError, |
|
useSyncExternalStore: throwInvalidHookError, |
|
useId: throwInvalidHookError, |
|
unstable_isNewReconciler: enableNewReconciler |
|
}; |
|
|
|
var HooksDispatcherOnMountInDEV = null; |
|
var HooksDispatcherOnMountWithHookTypesInDEV = null; |
|
var HooksDispatcherOnUpdateInDEV = null; |
|
var HooksDispatcherOnRerenderInDEV = null; |
|
var InvalidNestedHooksDispatcherOnMountInDEV = null; |
|
var InvalidNestedHooksDispatcherOnUpdateInDEV = null; |
|
var InvalidNestedHooksDispatcherOnRerenderInDEV = null; |
|
|
|
{ |
|
var warnInvalidContextAccess = function () { |
|
error('Context can only be read while React is rendering. ' + 'In classes, you can read it in the render method or getDerivedStateFromProps. ' + 'In function components, you can read it directly in the function body, but not ' + 'inside Hooks like useReducer() or useMemo().'); |
|
}; |
|
|
|
var warnInvalidHookAccess = function () { |
|
error('Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' + 'You can only call Hooks at the top level of your React function. ' + 'For more information, see ' + 'https://reactjs.org/link/rules-of-hooks'); |
|
}; |
|
|
|
HooksDispatcherOnMountInDEV = { |
|
readContext: function (context) { |
|
return readContext(context); |
|
}, |
|
useCallback: function (callback, deps) { |
|
currentHookNameInDev = 'useCallback'; |
|
mountHookTypesDev(); |
|
checkDepsAreArrayDev(deps); |
|
return mountCallback(callback, deps); |
|
}, |
|
useContext: function (context) { |
|
currentHookNameInDev = 'useContext'; |
|
mountHookTypesDev(); |
|
return readContext(context); |
|
}, |
|
useEffect: function (create, deps) { |
|
currentHookNameInDev = 'useEffect'; |
|
mountHookTypesDev(); |
|
checkDepsAreArrayDev(deps); |
|
return mountEffect(create, deps); |
|
}, |
|
useImperativeHandle: function (ref, create, deps) { |
|
currentHookNameInDev = 'useImperativeHandle'; |
|
mountHookTypesDev(); |
|
checkDepsAreArrayDev(deps); |
|
return mountImperativeHandle(ref, create, deps); |
|
}, |
|
useInsertionEffect: function (create, deps) { |
|
currentHookNameInDev = 'useInsertionEffect'; |
|
mountHookTypesDev(); |
|
checkDepsAreArrayDev(deps); |
|
return mountInsertionEffect(create, deps); |
|
}, |
|
useLayoutEffect: function (create, deps) { |
|
currentHookNameInDev = 'useLayoutEffect'; |
|
mountHookTypesDev(); |
|
checkDepsAreArrayDev(deps); |
|
return mountLayoutEffect(create, deps); |
|
}, |
|
useMemo: function (create, deps) { |
|
currentHookNameInDev = 'useMemo'; |
|
mountHookTypesDev(); |
|
checkDepsAreArrayDev(deps); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV; |
|
|
|
try { |
|
return mountMemo(create, deps); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useReducer: function (reducer, initialArg, init) { |
|
currentHookNameInDev = 'useReducer'; |
|
mountHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV; |
|
|
|
try { |
|
return mountReducer(reducer, initialArg, init); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useRef: function (initialValue) { |
|
currentHookNameInDev = 'useRef'; |
|
mountHookTypesDev(); |
|
return mountRef(initialValue); |
|
}, |
|
useState: function (initialState) { |
|
currentHookNameInDev = 'useState'; |
|
mountHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV; |
|
|
|
try { |
|
return mountState(initialState); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useDebugValue: function (value, formatterFn) { |
|
currentHookNameInDev = 'useDebugValue'; |
|
mountHookTypesDev(); |
|
return mountDebugValue(); |
|
}, |
|
useDeferredValue: function (value) { |
|
currentHookNameInDev = 'useDeferredValue'; |
|
mountHookTypesDev(); |
|
return mountDeferredValue(value); |
|
}, |
|
useTransition: function () { |
|
currentHookNameInDev = 'useTransition'; |
|
mountHookTypesDev(); |
|
return mountTransition(); |
|
}, |
|
useMutableSource: function (source, getSnapshot, subscribe) { |
|
currentHookNameInDev = 'useMutableSource'; |
|
mountHookTypesDev(); |
|
return mountMutableSource(); |
|
}, |
|
useSyncExternalStore: function (subscribe, getSnapshot, getServerSnapshot) { |
|
currentHookNameInDev = 'useSyncExternalStore'; |
|
mountHookTypesDev(); |
|
return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); |
|
}, |
|
useId: function () { |
|
currentHookNameInDev = 'useId'; |
|
mountHookTypesDev(); |
|
return mountId(); |
|
}, |
|
unstable_isNewReconciler: enableNewReconciler |
|
}; |
|
|
|
HooksDispatcherOnMountWithHookTypesInDEV = { |
|
readContext: function (context) { |
|
return readContext(context); |
|
}, |
|
useCallback: function (callback, deps) { |
|
currentHookNameInDev = 'useCallback'; |
|
updateHookTypesDev(); |
|
return mountCallback(callback, deps); |
|
}, |
|
useContext: function (context) { |
|
currentHookNameInDev = 'useContext'; |
|
updateHookTypesDev(); |
|
return readContext(context); |
|
}, |
|
useEffect: function (create, deps) { |
|
currentHookNameInDev = 'useEffect'; |
|
updateHookTypesDev(); |
|
return mountEffect(create, deps); |
|
}, |
|
useImperativeHandle: function (ref, create, deps) { |
|
currentHookNameInDev = 'useImperativeHandle'; |
|
updateHookTypesDev(); |
|
return mountImperativeHandle(ref, create, deps); |
|
}, |
|
useInsertionEffect: function (create, deps) { |
|
currentHookNameInDev = 'useInsertionEffect'; |
|
updateHookTypesDev(); |
|
return mountInsertionEffect(create, deps); |
|
}, |
|
useLayoutEffect: function (create, deps) { |
|
currentHookNameInDev = 'useLayoutEffect'; |
|
updateHookTypesDev(); |
|
return mountLayoutEffect(create, deps); |
|
}, |
|
useMemo: function (create, deps) { |
|
currentHookNameInDev = 'useMemo'; |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV; |
|
|
|
try { |
|
return mountMemo(create, deps); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useReducer: function (reducer, initialArg, init) { |
|
currentHookNameInDev = 'useReducer'; |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV; |
|
|
|
try { |
|
return mountReducer(reducer, initialArg, init); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useRef: function (initialValue) { |
|
currentHookNameInDev = 'useRef'; |
|
updateHookTypesDev(); |
|
return mountRef(initialValue); |
|
}, |
|
useState: function (initialState) { |
|
currentHookNameInDev = 'useState'; |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV; |
|
|
|
try { |
|
return mountState(initialState); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useDebugValue: function (value, formatterFn) { |
|
currentHookNameInDev = 'useDebugValue'; |
|
updateHookTypesDev(); |
|
return mountDebugValue(); |
|
}, |
|
useDeferredValue: function (value) { |
|
currentHookNameInDev = 'useDeferredValue'; |
|
updateHookTypesDev(); |
|
return mountDeferredValue(value); |
|
}, |
|
useTransition: function () { |
|
currentHookNameInDev = 'useTransition'; |
|
updateHookTypesDev(); |
|
return mountTransition(); |
|
}, |
|
useMutableSource: function (source, getSnapshot, subscribe) { |
|
currentHookNameInDev = 'useMutableSource'; |
|
updateHookTypesDev(); |
|
return mountMutableSource(); |
|
}, |
|
useSyncExternalStore: function (subscribe, getSnapshot, getServerSnapshot) { |
|
currentHookNameInDev = 'useSyncExternalStore'; |
|
updateHookTypesDev(); |
|
return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); |
|
}, |
|
useId: function () { |
|
currentHookNameInDev = 'useId'; |
|
updateHookTypesDev(); |
|
return mountId(); |
|
}, |
|
unstable_isNewReconciler: enableNewReconciler |
|
}; |
|
|
|
HooksDispatcherOnUpdateInDEV = { |
|
readContext: function (context) { |
|
return readContext(context); |
|
}, |
|
useCallback: function (callback, deps) { |
|
currentHookNameInDev = 'useCallback'; |
|
updateHookTypesDev(); |
|
return updateCallback(callback, deps); |
|
}, |
|
useContext: function (context) { |
|
currentHookNameInDev = 'useContext'; |
|
updateHookTypesDev(); |
|
return readContext(context); |
|
}, |
|
useEffect: function (create, deps) { |
|
currentHookNameInDev = 'useEffect'; |
|
updateHookTypesDev(); |
|
return updateEffect(create, deps); |
|
}, |
|
useImperativeHandle: function (ref, create, deps) { |
|
currentHookNameInDev = 'useImperativeHandle'; |
|
updateHookTypesDev(); |
|
return updateImperativeHandle(ref, create, deps); |
|
}, |
|
useInsertionEffect: function (create, deps) { |
|
currentHookNameInDev = 'useInsertionEffect'; |
|
updateHookTypesDev(); |
|
return updateInsertionEffect(create, deps); |
|
}, |
|
useLayoutEffect: function (create, deps) { |
|
currentHookNameInDev = 'useLayoutEffect'; |
|
updateHookTypesDev(); |
|
return updateLayoutEffect(create, deps); |
|
}, |
|
useMemo: function (create, deps) { |
|
currentHookNameInDev = 'useMemo'; |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV; |
|
|
|
try { |
|
return updateMemo(create, deps); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useReducer: function (reducer, initialArg, init) { |
|
currentHookNameInDev = 'useReducer'; |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV; |
|
|
|
try { |
|
return updateReducer(reducer, initialArg, init); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useRef: function (initialValue) { |
|
currentHookNameInDev = 'useRef'; |
|
updateHookTypesDev(); |
|
return updateRef(); |
|
}, |
|
useState: function (initialState) { |
|
currentHookNameInDev = 'useState'; |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV; |
|
|
|
try { |
|
return updateState(initialState); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useDebugValue: function (value, formatterFn) { |
|
currentHookNameInDev = 'useDebugValue'; |
|
updateHookTypesDev(); |
|
return updateDebugValue(); |
|
}, |
|
useDeferredValue: function (value) { |
|
currentHookNameInDev = 'useDeferredValue'; |
|
updateHookTypesDev(); |
|
return updateDeferredValue(value); |
|
}, |
|
useTransition: function () { |
|
currentHookNameInDev = 'useTransition'; |
|
updateHookTypesDev(); |
|
return updateTransition(); |
|
}, |
|
useMutableSource: function (source, getSnapshot, subscribe) { |
|
currentHookNameInDev = 'useMutableSource'; |
|
updateHookTypesDev(); |
|
return updateMutableSource(); |
|
}, |
|
useSyncExternalStore: function (subscribe, getSnapshot, getServerSnapshot) { |
|
currentHookNameInDev = 'useSyncExternalStore'; |
|
updateHookTypesDev(); |
|
return updateSyncExternalStore(subscribe, getSnapshot); |
|
}, |
|
useId: function () { |
|
currentHookNameInDev = 'useId'; |
|
updateHookTypesDev(); |
|
return updateId(); |
|
}, |
|
unstable_isNewReconciler: enableNewReconciler |
|
}; |
|
|
|
HooksDispatcherOnRerenderInDEV = { |
|
readContext: function (context) { |
|
return readContext(context); |
|
}, |
|
useCallback: function (callback, deps) { |
|
currentHookNameInDev = 'useCallback'; |
|
updateHookTypesDev(); |
|
return updateCallback(callback, deps); |
|
}, |
|
useContext: function (context) { |
|
currentHookNameInDev = 'useContext'; |
|
updateHookTypesDev(); |
|
return readContext(context); |
|
}, |
|
useEffect: function (create, deps) { |
|
currentHookNameInDev = 'useEffect'; |
|
updateHookTypesDev(); |
|
return updateEffect(create, deps); |
|
}, |
|
useImperativeHandle: function (ref, create, deps) { |
|
currentHookNameInDev = 'useImperativeHandle'; |
|
updateHookTypesDev(); |
|
return updateImperativeHandle(ref, create, deps); |
|
}, |
|
useInsertionEffect: function (create, deps) { |
|
currentHookNameInDev = 'useInsertionEffect'; |
|
updateHookTypesDev(); |
|
return updateInsertionEffect(create, deps); |
|
}, |
|
useLayoutEffect: function (create, deps) { |
|
currentHookNameInDev = 'useLayoutEffect'; |
|
updateHookTypesDev(); |
|
return updateLayoutEffect(create, deps); |
|
}, |
|
useMemo: function (create, deps) { |
|
currentHookNameInDev = 'useMemo'; |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnRerenderInDEV; |
|
|
|
try { |
|
return updateMemo(create, deps); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useReducer: function (reducer, initialArg, init) { |
|
currentHookNameInDev = 'useReducer'; |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnRerenderInDEV; |
|
|
|
try { |
|
return rerenderReducer(reducer, initialArg, init); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useRef: function (initialValue) { |
|
currentHookNameInDev = 'useRef'; |
|
updateHookTypesDev(); |
|
return updateRef(); |
|
}, |
|
useState: function (initialState) { |
|
currentHookNameInDev = 'useState'; |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnRerenderInDEV; |
|
|
|
try { |
|
return rerenderState(initialState); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useDebugValue: function (value, formatterFn) { |
|
currentHookNameInDev = 'useDebugValue'; |
|
updateHookTypesDev(); |
|
return updateDebugValue(); |
|
}, |
|
useDeferredValue: function (value) { |
|
currentHookNameInDev = 'useDeferredValue'; |
|
updateHookTypesDev(); |
|
return rerenderDeferredValue(value); |
|
}, |
|
useTransition: function () { |
|
currentHookNameInDev = 'useTransition'; |
|
updateHookTypesDev(); |
|
return rerenderTransition(); |
|
}, |
|
useMutableSource: function (source, getSnapshot, subscribe) { |
|
currentHookNameInDev = 'useMutableSource'; |
|
updateHookTypesDev(); |
|
return updateMutableSource(); |
|
}, |
|
useSyncExternalStore: function (subscribe, getSnapshot, getServerSnapshot) { |
|
currentHookNameInDev = 'useSyncExternalStore'; |
|
updateHookTypesDev(); |
|
return updateSyncExternalStore(subscribe, getSnapshot); |
|
}, |
|
useId: function () { |
|
currentHookNameInDev = 'useId'; |
|
updateHookTypesDev(); |
|
return updateId(); |
|
}, |
|
unstable_isNewReconciler: enableNewReconciler |
|
}; |
|
|
|
InvalidNestedHooksDispatcherOnMountInDEV = { |
|
readContext: function (context) { |
|
warnInvalidContextAccess(); |
|
return readContext(context); |
|
}, |
|
useCallback: function (callback, deps) { |
|
currentHookNameInDev = 'useCallback'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
return mountCallback(callback, deps); |
|
}, |
|
useContext: function (context) { |
|
currentHookNameInDev = 'useContext'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
return readContext(context); |
|
}, |
|
useEffect: function (create, deps) { |
|
currentHookNameInDev = 'useEffect'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
return mountEffect(create, deps); |
|
}, |
|
useImperativeHandle: function (ref, create, deps) { |
|
currentHookNameInDev = 'useImperativeHandle'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
return mountImperativeHandle(ref, create, deps); |
|
}, |
|
useInsertionEffect: function (create, deps) { |
|
currentHookNameInDev = 'useInsertionEffect'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
return mountInsertionEffect(create, deps); |
|
}, |
|
useLayoutEffect: function (create, deps) { |
|
currentHookNameInDev = 'useLayoutEffect'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
return mountLayoutEffect(create, deps); |
|
}, |
|
useMemo: function (create, deps) { |
|
currentHookNameInDev = 'useMemo'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV; |
|
|
|
try { |
|
return mountMemo(create, deps); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useReducer: function (reducer, initialArg, init) { |
|
currentHookNameInDev = 'useReducer'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV; |
|
|
|
try { |
|
return mountReducer(reducer, initialArg, init); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useRef: function (initialValue) { |
|
currentHookNameInDev = 'useRef'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
return mountRef(initialValue); |
|
}, |
|
useState: function (initialState) { |
|
currentHookNameInDev = 'useState'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV; |
|
|
|
try { |
|
return mountState(initialState); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useDebugValue: function (value, formatterFn) { |
|
currentHookNameInDev = 'useDebugValue'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
return mountDebugValue(); |
|
}, |
|
useDeferredValue: function (value) { |
|
currentHookNameInDev = 'useDeferredValue'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
return mountDeferredValue(value); |
|
}, |
|
useTransition: function () { |
|
currentHookNameInDev = 'useTransition'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
return mountTransition(); |
|
}, |
|
useMutableSource: function (source, getSnapshot, subscribe) { |
|
currentHookNameInDev = 'useMutableSource'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
return mountMutableSource(); |
|
}, |
|
useSyncExternalStore: function (subscribe, getSnapshot, getServerSnapshot) { |
|
currentHookNameInDev = 'useSyncExternalStore'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
return mountSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); |
|
}, |
|
useId: function () { |
|
currentHookNameInDev = 'useId'; |
|
warnInvalidHookAccess(); |
|
mountHookTypesDev(); |
|
return mountId(); |
|
}, |
|
unstable_isNewReconciler: enableNewReconciler |
|
}; |
|
|
|
InvalidNestedHooksDispatcherOnUpdateInDEV = { |
|
readContext: function (context) { |
|
warnInvalidContextAccess(); |
|
return readContext(context); |
|
}, |
|
useCallback: function (callback, deps) { |
|
currentHookNameInDev = 'useCallback'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateCallback(callback, deps); |
|
}, |
|
useContext: function (context) { |
|
currentHookNameInDev = 'useContext'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return readContext(context); |
|
}, |
|
useEffect: function (create, deps) { |
|
currentHookNameInDev = 'useEffect'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateEffect(create, deps); |
|
}, |
|
useImperativeHandle: function (ref, create, deps) { |
|
currentHookNameInDev = 'useImperativeHandle'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateImperativeHandle(ref, create, deps); |
|
}, |
|
useInsertionEffect: function (create, deps) { |
|
currentHookNameInDev = 'useInsertionEffect'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateInsertionEffect(create, deps); |
|
}, |
|
useLayoutEffect: function (create, deps) { |
|
currentHookNameInDev = 'useLayoutEffect'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateLayoutEffect(create, deps); |
|
}, |
|
useMemo: function (create, deps) { |
|
currentHookNameInDev = 'useMemo'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV; |
|
|
|
try { |
|
return updateMemo(create, deps); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useReducer: function (reducer, initialArg, init) { |
|
currentHookNameInDev = 'useReducer'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV; |
|
|
|
try { |
|
return updateReducer(reducer, initialArg, init); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useRef: function (initialValue) { |
|
currentHookNameInDev = 'useRef'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateRef(); |
|
}, |
|
useState: function (initialState) { |
|
currentHookNameInDev = 'useState'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV; |
|
|
|
try { |
|
return updateState(initialState); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useDebugValue: function (value, formatterFn) { |
|
currentHookNameInDev = 'useDebugValue'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateDebugValue(); |
|
}, |
|
useDeferredValue: function (value) { |
|
currentHookNameInDev = 'useDeferredValue'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateDeferredValue(value); |
|
}, |
|
useTransition: function () { |
|
currentHookNameInDev = 'useTransition'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateTransition(); |
|
}, |
|
useMutableSource: function (source, getSnapshot, subscribe) { |
|
currentHookNameInDev = 'useMutableSource'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateMutableSource(); |
|
}, |
|
useSyncExternalStore: function (subscribe, getSnapshot, getServerSnapshot) { |
|
currentHookNameInDev = 'useSyncExternalStore'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateSyncExternalStore(subscribe, getSnapshot); |
|
}, |
|
useId: function () { |
|
currentHookNameInDev = 'useId'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateId(); |
|
}, |
|
unstable_isNewReconciler: enableNewReconciler |
|
}; |
|
|
|
InvalidNestedHooksDispatcherOnRerenderInDEV = { |
|
readContext: function (context) { |
|
warnInvalidContextAccess(); |
|
return readContext(context); |
|
}, |
|
useCallback: function (callback, deps) { |
|
currentHookNameInDev = 'useCallback'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateCallback(callback, deps); |
|
}, |
|
useContext: function (context) { |
|
currentHookNameInDev = 'useContext'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return readContext(context); |
|
}, |
|
useEffect: function (create, deps) { |
|
currentHookNameInDev = 'useEffect'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateEffect(create, deps); |
|
}, |
|
useImperativeHandle: function (ref, create, deps) { |
|
currentHookNameInDev = 'useImperativeHandle'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateImperativeHandle(ref, create, deps); |
|
}, |
|
useInsertionEffect: function (create, deps) { |
|
currentHookNameInDev = 'useInsertionEffect'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateInsertionEffect(create, deps); |
|
}, |
|
useLayoutEffect: function (create, deps) { |
|
currentHookNameInDev = 'useLayoutEffect'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateLayoutEffect(create, deps); |
|
}, |
|
useMemo: function (create, deps) { |
|
currentHookNameInDev = 'useMemo'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV; |
|
|
|
try { |
|
return updateMemo(create, deps); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useReducer: function (reducer, initialArg, init) { |
|
currentHookNameInDev = 'useReducer'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV; |
|
|
|
try { |
|
return rerenderReducer(reducer, initialArg, init); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useRef: function (initialValue) { |
|
currentHookNameInDev = 'useRef'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateRef(); |
|
}, |
|
useState: function (initialState) { |
|
currentHookNameInDev = 'useState'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
var prevDispatcher = ReactCurrentDispatcher$1.current; |
|
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV; |
|
|
|
try { |
|
return rerenderState(initialState); |
|
} finally { |
|
ReactCurrentDispatcher$1.current = prevDispatcher; |
|
} |
|
}, |
|
useDebugValue: function (value, formatterFn) { |
|
currentHookNameInDev = 'useDebugValue'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateDebugValue(); |
|
}, |
|
useDeferredValue: function (value) { |
|
currentHookNameInDev = 'useDeferredValue'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return rerenderDeferredValue(value); |
|
}, |
|
useTransition: function () { |
|
currentHookNameInDev = 'useTransition'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return rerenderTransition(); |
|
}, |
|
useMutableSource: function (source, getSnapshot, subscribe) { |
|
currentHookNameInDev = 'useMutableSource'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateMutableSource(); |
|
}, |
|
useSyncExternalStore: function (subscribe, getSnapshot, getServerSnapshot) { |
|
currentHookNameInDev = 'useSyncExternalStore'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateSyncExternalStore(subscribe, getSnapshot); |
|
}, |
|
useId: function () { |
|
currentHookNameInDev = 'useId'; |
|
warnInvalidHookAccess(); |
|
updateHookTypesDev(); |
|
return updateId(); |
|
}, |
|
unstable_isNewReconciler: enableNewReconciler |
|
}; |
|
} |
|
|
|
var now$1 = unstable_now; |
|
var commitTime = 0; |
|
var layoutEffectStartTime = -1; |
|
var profilerStartTime = -1; |
|
var passiveEffectStartTime = -1; |
|
/** |
|
* Tracks whether the current update was a nested/cascading update (scheduled from a layout effect). |
|
* |
|
* The overall sequence is: |
|
* 1. render |
|
* 2. commit (and call `onRender`, `onCommit`) |
|
* 3. check for nested updates |
|
* 4. flush passive effects (and call `onPostCommit`) |
|
* |
|
* Nested updates are identified in step 3 above, |
|
* but step 4 still applies to the work that was just committed. |
|
* We use two flags to track nested updates then: |
|
* one tracks whether the upcoming update is a nested update, |
|
* and the other tracks whether the current update was a nested update. |
|
* The first value gets synced to the second at the start of the render phase. |
|
*/ |
|
|
|
var currentUpdateIsNested = false; |
|
var nestedUpdateScheduled = false; |
|
|
|
function isCurrentUpdateNested() { |
|
return currentUpdateIsNested; |
|
} |
|
|
|
function markNestedUpdateScheduled() { |
|
{ |
|
nestedUpdateScheduled = true; |
|
} |
|
} |
|
|
|
function resetNestedUpdateFlag() { |
|
{ |
|
currentUpdateIsNested = false; |
|
nestedUpdateScheduled = false; |
|
} |
|
} |
|
|
|
function syncNestedUpdateFlag() { |
|
{ |
|
currentUpdateIsNested = nestedUpdateScheduled; |
|
nestedUpdateScheduled = false; |
|
} |
|
} |
|
|
|
function getCommitTime() { |
|
return commitTime; |
|
} |
|
|
|
function recordCommitTime() { |
|
|
|
commitTime = now$1(); |
|
} |
|
|
|
function startProfilerTimer(fiber) { |
|
|
|
profilerStartTime = now$1(); |
|
|
|
if (fiber.actualStartTime < 0) { |
|
fiber.actualStartTime = now$1(); |
|
} |
|
} |
|
|
|
function stopProfilerTimerIfRunning(fiber) { |
|
|
|
profilerStartTime = -1; |
|
} |
|
|
|
function stopProfilerTimerIfRunningAndRecordDelta(fiber, overrideBaseTime) { |
|
|
|
if (profilerStartTime >= 0) { |
|
var elapsedTime = now$1() - profilerStartTime; |
|
fiber.actualDuration += elapsedTime; |
|
|
|
if (overrideBaseTime) { |
|
fiber.selfBaseDuration = elapsedTime; |
|
} |
|
|
|
profilerStartTime = -1; |
|
} |
|
} |
|
|
|
function recordLayoutEffectDuration(fiber) { |
|
|
|
if (layoutEffectStartTime >= 0) { |
|
var elapsedTime = now$1() - layoutEffectStartTime; |
|
layoutEffectStartTime = -1; // Store duration on the next nearest Profiler ancestor |
|
// Or the root (for the DevTools Profiler to read) |
|
|
|
var parentFiber = fiber.return; |
|
|
|
while (parentFiber !== null) { |
|
switch (parentFiber.tag) { |
|
case HostRoot: |
|
var root = parentFiber.stateNode; |
|
root.effectDuration += elapsedTime; |
|
return; |
|
|
|
case Profiler: |
|
var parentStateNode = parentFiber.stateNode; |
|
parentStateNode.effectDuration += elapsedTime; |
|
return; |
|
} |
|
|
|
parentFiber = parentFiber.return; |
|
} |
|
} |
|
} |
|
|
|
function recordPassiveEffectDuration(fiber) { |
|
|
|
if (passiveEffectStartTime >= 0) { |
|
var elapsedTime = now$1() - passiveEffectStartTime; |
|
passiveEffectStartTime = -1; // Store duration on the next nearest Profiler ancestor |
|
// Or the root (for the DevTools Profiler to read) |
|
|
|
var parentFiber = fiber.return; |
|
|
|
while (parentFiber !== null) { |
|
switch (parentFiber.tag) { |
|
case HostRoot: |
|
var root = parentFiber.stateNode; |
|
|
|
if (root !== null) { |
|
root.passiveEffectDuration += elapsedTime; |
|
} |
|
|
|
return; |
|
|
|
case Profiler: |
|
var parentStateNode = parentFiber.stateNode; |
|
|
|
if (parentStateNode !== null) { |
|
// Detached fibers have their state node cleared out. |
|
// In this case, the return pointer is also cleared out, |
|
// so we won't be able to report the time spent in this Profiler's subtree. |
|
parentStateNode.passiveEffectDuration += elapsedTime; |
|
} |
|
|
|
return; |
|
} |
|
|
|
parentFiber = parentFiber.return; |
|
} |
|
} |
|
} |
|
|
|
function startLayoutEffectTimer() { |
|
|
|
layoutEffectStartTime = now$1(); |
|
} |
|
|
|
function startPassiveEffectTimer() { |
|
|
|
passiveEffectStartTime = now$1(); |
|
} |
|
|
|
function transferActualDuration(fiber) { |
|
// Transfer time spent rendering these children so we don't lose it |
|
// after we rerender. This is used as a helper in special cases |
|
// where we should count the work of multiple passes. |
|
var child = fiber.child; |
|
|
|
while (child) { |
|
fiber.actualDuration += child.actualDuration; |
|
child = child.sibling; |
|
} |
|
} |
|
|
|
function createCapturedValueAtFiber(value, source) { |
|
// If the value is an error, call this function immediately after it is thrown |
|
// so the stack is accurate. |
|
return { |
|
value: value, |
|
source: source, |
|
stack: getStackByFiberInDevAndProd(source), |
|
digest: null |
|
}; |
|
} |
|
function createCapturedValue(value, digest, stack) { |
|
return { |
|
value: value, |
|
source: null, |
|
stack: stack != null ? stack : null, |
|
digest: digest != null ? digest : null |
|
}; |
|
} |
|
|
|
// This module is forked in different environments. |
|
// By default, return `true` to log errors to the console. |
|
// Forks can return `false` if this isn't desirable. |
|
function showErrorDialog(boundary, errorInfo) { |
|
return true; |
|
} |
|
|
|
function logCapturedError(boundary, errorInfo) { |
|
try { |
|
var logError = showErrorDialog(boundary, errorInfo); // Allow injected showErrorDialog() to prevent default console.error logging. |
|
// This enables renderers like ReactNative to better manage redbox behavior. |
|
|
|
if (logError === false) { |
|
return; |
|
} |
|
|
|
var error = errorInfo.value; |
|
|
|
if (true) { |
|
var source = errorInfo.source; |
|
var stack = errorInfo.stack; |
|
var componentStack = stack !== null ? stack : ''; // Browsers support silencing uncaught errors by calling |
|
// `preventDefault()` in window `error` handler. |
|
// We record this information as an expando on the error. |
|
|
|
if (error != null && error._suppressLogging) { |
|
if (boundary.tag === ClassComponent) { |
|
// The error is recoverable and was silenced. |
|
// Ignore it and don't print the stack addendum. |
|
// This is handy for testing error boundaries without noise. |
|
return; |
|
} // The error is fatal. Since the silencing might have |
|
// been accidental, we'll surface it anyway. |
|
// However, the browser would have silenced the original error |
|
// so we'll print it first, and then print the stack addendum. |
|
|
|
|
|
console['error'](error); // Don't transform to our wrapper |
|
// For a more detailed description of this block, see: |
|
// https://github.com/facebook/react/pull/13384 |
|
} |
|
|
|
var componentName = source ? getComponentNameFromFiber(source) : null; |
|
var componentNameMessage = componentName ? "The above error occurred in the <" + componentName + "> component:" : 'The above error occurred in one of your React components:'; |
|
var errorBoundaryMessage; |
|
|
|
if (boundary.tag === HostRoot) { |
|
errorBoundaryMessage = 'Consider adding an error boundary to your tree to customize error handling behavior.\n' + 'Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.'; |
|
} else { |
|
var errorBoundaryName = getComponentNameFromFiber(boundary) || 'Anonymous'; |
|
errorBoundaryMessage = "React will try to recreate this component tree from scratch " + ("using the error boundary you provided, " + errorBoundaryName + "."); |
|
} |
|
|
|
var combinedMessage = componentNameMessage + "\n" + componentStack + "\n\n" + ("" + errorBoundaryMessage); // In development, we provide our own message with just the component stack. |
|
// We don't include the original error message and JS stack because the browser |
|
// has already printed it. Even if the application swallows the error, it is still |
|
// displayed by the browser thanks to the DEV-only fake event trick in ReactErrorUtils. |
|
|
|
console['error'](combinedMessage); // Don't transform to our wrapper |
|
} else { |
|
// In production, we print the error directly. |
|
// This will include the message, the JS stack, and anything the browser wants to show. |
|
// We pass the error object instead of custom message so that the browser displays the error natively. |
|
console['error'](error); // Don't transform to our wrapper |
|
} |
|
} catch (e) { |
|
// This method must not throw, or React internal state will get messed up. |
|
// If console.error is overridden, or logCapturedError() shows a dialog that throws, |
|
// we want to report this error outside of the normal stack as a last resort. |
|
// https://github.com/facebook/react/issues/13188 |
|
setTimeout(function () { |
|
throw e; |
|
}); |
|
} |
|
} |
|
|
|
var PossiblyWeakMap$1 = typeof WeakMap === 'function' ? WeakMap : Map; |
|
|
|
function createRootErrorUpdate(fiber, errorInfo, lane) { |
|
var update = createUpdate(NoTimestamp, lane); // Unmount the root by rendering null. |
|
|
|
update.tag = CaptureUpdate; // Caution: React DevTools currently depends on this property |
|
// being called "element". |
|
|
|
update.payload = { |
|
element: null |
|
}; |
|
var error = errorInfo.value; |
|
|
|
update.callback = function () { |
|
onUncaughtError(error); |
|
logCapturedError(fiber, errorInfo); |
|
}; |
|
|
|
return update; |
|
} |
|
|
|
function createClassErrorUpdate(fiber, errorInfo, lane) { |
|
var update = createUpdate(NoTimestamp, lane); |
|
update.tag = CaptureUpdate; |
|
var getDerivedStateFromError = fiber.type.getDerivedStateFromError; |
|
|
|
if (typeof getDerivedStateFromError === 'function') { |
|
var error$1 = errorInfo.value; |
|
|
|
update.payload = function () { |
|
return getDerivedStateFromError(error$1); |
|
}; |
|
|
|
update.callback = function () { |
|
{ |
|
markFailedErrorBoundaryForHotReloading(fiber); |
|
} |
|
|
|
logCapturedError(fiber, errorInfo); |
|
}; |
|
} |
|
|
|
var inst = fiber.stateNode; |
|
|
|
if (inst !== null && typeof inst.componentDidCatch === 'function') { |
|
update.callback = function callback() { |
|
{ |
|
markFailedErrorBoundaryForHotReloading(fiber); |
|
} |
|
|
|
logCapturedError(fiber, errorInfo); |
|
|
|
if (typeof getDerivedStateFromError !== 'function') { |
|
// To preserve the preexisting retry behavior of error boundaries, |
|
// we keep track of which ones already failed during this batch. |
|
// This gets reset before we yield back to the browser. |
|
// TODO: Warn in strict mode if getDerivedStateFromError is |
|
// not defined. |
|
markLegacyErrorBoundaryAsFailed(this); |
|
} |
|
|
|
var error$1 = errorInfo.value; |
|
var stack = errorInfo.stack; |
|
this.componentDidCatch(error$1, { |
|
componentStack: stack !== null ? stack : '' |
|
}); |
|
|
|
{ |
|
if (typeof getDerivedStateFromError !== 'function') { |
|
// If componentDidCatch is the only error boundary method defined, |
|
// then it needs to call setState to recover from errors. |
|
// If no state update is scheduled then the boundary will swallow the error. |
|
if (!includesSomeLane(fiber.lanes, SyncLane)) { |
|
error('%s: Error boundaries should implement getDerivedStateFromError(). ' + 'In that method, return a state update to display an error message or fallback UI.', getComponentNameFromFiber(fiber) || 'Unknown'); |
|
} |
|
} |
|
} |
|
}; |
|
} |
|
|
|
return update; |
|
} |
|
|
|
function attachPingListener(root, wakeable, lanes) { |
|
// Attach a ping listener |
|
// |
|
// The data might resolve before we have a chance to commit the fallback. Or, |
|
// in the case of a refresh, we'll never commit a fallback. So we need to |
|
// attach a listener now. When it resolves ("pings"), we can decide whether to |
|
// try rendering the tree again. |
|
// |
|
// Only attach a listener if one does not already exist for the lanes |
|
// we're currently rendering (which acts like a "thread ID" here). |
|
// |
|
// We only need to do this in concurrent mode. Legacy Suspense always |
|
// commits fallbacks synchronously, so there are no pings. |
|
var pingCache = root.pingCache; |
|
var threadIDs; |
|
|
|
if (pingCache === null) { |
|
pingCache = root.pingCache = new PossiblyWeakMap$1(); |
|
threadIDs = new Set(); |
|
pingCache.set(wakeable, threadIDs); |
|
} else { |
|
threadIDs = pingCache.get(wakeable); |
|
|
|
if (threadIDs === undefined) { |
|
threadIDs = new Set(); |
|
pingCache.set(wakeable, threadIDs); |
|
} |
|
} |
|
|
|
if (!threadIDs.has(lanes)) { |
|
// Memoize using the thread ID to prevent redundant listeners. |
|
threadIDs.add(lanes); |
|
var ping = pingSuspendedRoot.bind(null, root, wakeable, lanes); |
|
|
|
{ |
|
if (isDevToolsPresent) { |
|
// If we have pending work still, restore the original updaters |
|
restorePendingUpdaters(root, lanes); |
|
} |
|
} |
|
|
|
wakeable.then(ping, ping); |
|
} |
|
} |
|
|
|
function attachRetryListener(suspenseBoundary, root, wakeable, lanes) { |
|
// Retry listener |
|
// |
|
// If the fallback does commit, we need to attach a different type of |
|
// listener. This one schedules an update on the Suspense boundary to turn |
|
// the fallback state off. |
|
// |
|
// Stash the wakeable on the boundary fiber so we can access it in the |
|
// commit phase. |
|
// |
|
// When the wakeable resolves, we'll attempt to render the boundary |
|
// again ("retry"). |
|
var wakeables = suspenseBoundary.updateQueue; |
|
|
|
if (wakeables === null) { |
|
var updateQueue = new Set(); |
|
updateQueue.add(wakeable); |
|
suspenseBoundary.updateQueue = updateQueue; |
|
} else { |
|
wakeables.add(wakeable); |
|
} |
|
} |
|
|
|
function resetSuspendedComponent(sourceFiber, rootRenderLanes) { |
|
// A legacy mode Suspense quirk, only relevant to hook components. |
|
|
|
|
|
var tag = sourceFiber.tag; |
|
|
|
if ((sourceFiber.mode & ConcurrentMode) === NoMode && (tag === FunctionComponent || tag === ForwardRef || tag === SimpleMemoComponent)) { |
|
var currentSource = sourceFiber.alternate; |
|
|
|
if (currentSource) { |
|
sourceFiber.updateQueue = currentSource.updateQueue; |
|
sourceFiber.memoizedState = currentSource.memoizedState; |
|
sourceFiber.lanes = currentSource.lanes; |
|
} else { |
|
sourceFiber.updateQueue = null; |
|
sourceFiber.memoizedState = null; |
|
} |
|
} |
|
} |
|
|
|
function getNearestSuspenseBoundaryToCapture(returnFiber) { |
|
var node = returnFiber; |
|
|
|
do { |
|
if (node.tag === SuspenseComponent && shouldCaptureSuspense(node)) { |
|
return node; |
|
} // This boundary already captured during this render. Continue to the next |
|
// boundary. |
|
|
|
|
|
node = node.return; |
|
} while (node !== null); |
|
|
|
return null; |
|
} |
|
|
|
function markSuspenseBoundaryShouldCapture(suspenseBoundary, returnFiber, sourceFiber, root, rootRenderLanes) { |
|
// This marks a Suspense boundary so that when we're unwinding the stack, |
|
// it captures the suspended "exception" and does a second (fallback) pass. |
|
if ((suspenseBoundary.mode & ConcurrentMode) === NoMode) { |
|
// Legacy Mode Suspense |
|
// |
|
// If the boundary is in legacy mode, we should *not* |
|
// suspend the commit. Pretend as if the suspended component rendered |
|
// null and keep rendering. When the Suspense boundary completes, |
|
// we'll do a second pass to render the fallback. |
|
if (suspenseBoundary === returnFiber) { |
|
// Special case where we suspended while reconciling the children of |
|
// a Suspense boundary's inner Offscreen wrapper fiber. This happens |
|
// when a React.lazy component is a direct child of a |
|
// Suspense boundary. |
|
// |
|
// Suspense boundaries are implemented as multiple fibers, but they |
|
// are a single conceptual unit. The legacy mode behavior where we |
|
// pretend the suspended fiber committed as `null` won't work, |
|
// because in this case the "suspended" fiber is the inner |
|
// Offscreen wrapper. |
|
// |
|
// Because the contents of the boundary haven't started rendering |
|
// yet (i.e. nothing in the tree has partially rendered) we can |
|
// switch to the regular, concurrent mode behavior: mark the |
|
// boundary with ShouldCapture and enter the unwind phase. |
|
suspenseBoundary.flags |= ShouldCapture; |
|
} else { |
|
suspenseBoundary.flags |= DidCapture; |
|
sourceFiber.flags |= ForceUpdateForLegacySuspense; // We're going to commit this fiber even though it didn't complete. |
|
// But we shouldn't call any lifecycle methods or callbacks. Remove |
|
// all lifecycle effect tags. |
|
|
|
sourceFiber.flags &= ~(LifecycleEffectMask | Incomplete); |
|
|
|
if (sourceFiber.tag === ClassComponent) { |
|
var currentSourceFiber = sourceFiber.alternate; |
|
|
|
if (currentSourceFiber === null) { |
|
// This is a new mount. Change the tag so it's not mistaken for a |
|
// completed class component. For example, we should not call |
|
// componentWillUnmount if it is deleted. |
|
sourceFiber.tag = IncompleteClassComponent; |
|
} else { |
|
// When we try rendering again, we should not reuse the current fiber, |
|
// since it's known to be in an inconsistent state. Use a force update to |
|
// prevent a bail out. |
|
var update = createUpdate(NoTimestamp, SyncLane); |
|
update.tag = ForceUpdate; |
|
enqueueUpdate(sourceFiber, update, SyncLane); |
|
} |
|
} // The source fiber did not complete. Mark it with Sync priority to |
|
// indicate that it still has pending work. |
|
|
|
|
|
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, SyncLane); |
|
} |
|
|
|
return suspenseBoundary; |
|
} // Confirmed that the boundary is in a concurrent mode tree. Continue |
|
// with the normal suspend path. |
|
// |
|
// After this we'll use a set of heuristics to determine whether this |
|
// render pass will run to completion or restart or "suspend" the commit. |
|
// The actual logic for this is spread out in different places. |
|
// |
|
// This first principle is that if we're going to suspend when we complete |
|
// a root, then we should also restart if we get an update or ping that |
|
// might unsuspend it, and vice versa. The only reason to suspend is |
|
// because you think you might want to restart before committing. However, |
|
// it doesn't make sense to restart only while in the period we're suspended. |
|
// |
|
// Restarting too aggressively is also not good because it starves out any |
|
// intermediate loading state. So we use heuristics to determine when. |
|
// Suspense Heuristics |
|
// |
|
// If nothing threw a Promise or all the same fallbacks are already showing, |
|
// then don't suspend/restart. |
|
// |
|
// If this is an initial render of a new tree of Suspense boundaries and |
|
// those trigger a fallback, then don't suspend/restart. We want to ensure |
|
// that we can show the initial loading state as quickly as possible. |
|
// |
|
// If we hit a "Delayed" case, such as when we'd switch from content back into |
|
// a fallback, then we should always suspend/restart. Transitions apply |
|
// to this case. If none is defined, JND is used instead. |
|
// |
|
// If we're already showing a fallback and it gets "retried", allowing us to show |
|
// another level, but there's still an inner boundary that would show a fallback, |
|
// then we suspend/restart for 500ms since the last time we showed a fallback |
|
// anywhere in the tree. This effectively throttles progressive loading into a |
|
// consistent train of commits. This also gives us an opportunity to restart to |
|
// get to the completed state slightly earlier. |
|
// |
|
// If there's ambiguity due to batching it's resolved in preference of: |
|
// 1) "delayed", 2) "initial render", 3) "retry". |
|
// |
|
// We want to ensure that a "busy" state doesn't get force committed. We want to |
|
// ensure that new initial loading states can commit as soon as possible. |
|
|
|
|
|
suspenseBoundary.flags |= ShouldCapture; // TODO: I think we can remove this, since we now use `DidCapture` in |
|
// the begin phase to prevent an early bailout. |
|
|
|
suspenseBoundary.lanes = rootRenderLanes; |
|
return suspenseBoundary; |
|
} |
|
|
|
function throwException(root, returnFiber, sourceFiber, value, rootRenderLanes) { |
|
// The source fiber did not complete. |
|
sourceFiber.flags |= Incomplete; |
|
|
|
{ |
|
if (isDevToolsPresent) { |
|
// If we have pending work still, restore the original updaters |
|
restorePendingUpdaters(root, rootRenderLanes); |
|
} |
|
} |
|
|
|
if (value !== null && typeof value === 'object' && typeof value.then === 'function') { |
|
// This is a wakeable. The component suspended. |
|
var wakeable = value; |
|
resetSuspendedComponent(sourceFiber); |
|
|
|
{ |
|
if (getIsHydrating() && sourceFiber.mode & ConcurrentMode) { |
|
markDidThrowWhileHydratingDEV(); |
|
} |
|
} |
|
|
|
|
|
var suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber); |
|
|
|
if (suspenseBoundary !== null) { |
|
suspenseBoundary.flags &= ~ForceClientRender; |
|
markSuspenseBoundaryShouldCapture(suspenseBoundary, returnFiber, sourceFiber, root, rootRenderLanes); // We only attach ping listeners in concurrent mode. Legacy Suspense always |
|
// commits fallbacks synchronously, so there are no pings. |
|
|
|
if (suspenseBoundary.mode & ConcurrentMode) { |
|
attachPingListener(root, wakeable, rootRenderLanes); |
|
} |
|
|
|
attachRetryListener(suspenseBoundary, root, wakeable); |
|
return; |
|
} else { |
|
// No boundary was found. Unless this is a sync update, this is OK. |
|
// We can suspend and wait for more data to arrive. |
|
if (!includesSyncLane(rootRenderLanes)) { |
|
// This is not a sync update. Suspend. Since we're not activating a |
|
// Suspense boundary, this will unwind all the way to the root without |
|
// performing a second pass to render a fallback. (This is arguably how |
|
// refresh transitions should work, too, since we're not going to commit |
|
// the fallbacks anyway.) |
|
// |
|
// This case also applies to initial hydration. |
|
attachPingListener(root, wakeable, rootRenderLanes); |
|
renderDidSuspendDelayIfPossible(); |
|
return; |
|
} // This is a sync/discrete update. We treat this case like an error |
|
// because discrete renders are expected to produce a complete tree |
|
// synchronously to maintain consistency with external state. |
|
|
|
|
|
var uncaughtSuspenseError = new Error('A component suspended while responding to synchronous input. This ' + 'will cause the UI to be replaced with a loading indicator. To ' + 'fix, updates that suspend should be wrapped ' + 'with startTransition.'); // If we're outside a transition, fall through to the regular error path. |
|
// The error will be caught by the nearest suspense boundary. |
|
|
|
value = uncaughtSuspenseError; |
|
} |
|
} else { |
|
// This is a regular error, not a Suspense wakeable. |
|
if (getIsHydrating() && sourceFiber.mode & ConcurrentMode) { |
|
markDidThrowWhileHydratingDEV(); |
|
|
|
var _suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber); // If the error was thrown during hydration, we may be able to recover by |
|
// discarding the dehydrated content and switching to a client render. |
|
// Instead of surfacing the error, find the nearest Suspense boundary |
|
// and render it again without hydration. |
|
|
|
|
|
if (_suspenseBoundary !== null) { |
|
if ((_suspenseBoundary.flags & ShouldCapture) === NoFlags) { |
|
// Set a flag to indicate that we should try rendering the normal |
|
// children again, not the fallback. |
|
_suspenseBoundary.flags |= ForceClientRender; |
|
} |
|
|
|
markSuspenseBoundaryShouldCapture(_suspenseBoundary, returnFiber, sourceFiber, root, rootRenderLanes); // Even though the user may not be affected by this error, we should |
|
// still log it so it can be fixed. |
|
|
|
queueHydrationError(createCapturedValueAtFiber(value, sourceFiber)); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
value = createCapturedValueAtFiber(value, sourceFiber); |
|
renderDidError(value); // We didn't find a boundary that could handle this type of exception. Start |
|
// over and traverse parent path again, this time treating the exception |
|
// as an error. |
|
|
|
var workInProgress = returnFiber; |
|
|
|
do { |
|
switch (workInProgress.tag) { |
|
case HostRoot: |
|
{ |
|
var _errorInfo = value; |
|
workInProgress.flags |= ShouldCapture; |
|
var lane = pickArbitraryLane(rootRenderLanes); |
|
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane); |
|
var update = createRootErrorUpdate(workInProgress, _errorInfo, lane); |
|
enqueueCapturedUpdate(workInProgress, update); |
|
return; |
|
} |
|
|
|
case ClassComponent: |
|
// Capture and retry |
|
var errorInfo = value; |
|
var ctor = workInProgress.type; |
|
var instance = workInProgress.stateNode; |
|
|
|
if ((workInProgress.flags & DidCapture) === NoFlags && (typeof ctor.getDerivedStateFromError === 'function' || instance !== null && typeof instance.componentDidCatch === 'function' && !isAlreadyFailedLegacyErrorBoundary(instance))) { |
|
workInProgress.flags |= ShouldCapture; |
|
|
|
var _lane = pickArbitraryLane(rootRenderLanes); |
|
|
|
workInProgress.lanes = mergeLanes(workInProgress.lanes, _lane); // Schedule the error boundary to re-render using updated state |
|
|
|
var _update = createClassErrorUpdate(workInProgress, errorInfo, _lane); |
|
|
|
enqueueCapturedUpdate(workInProgress, _update); |
|
return; |
|
} |
|
|
|
break; |
|
} |
|
|
|
workInProgress = workInProgress.return; |
|
} while (workInProgress !== null); |
|
} |
|
|
|
function getSuspendedCache() { |
|
{ |
|
return null; |
|
} // This function is called when a Suspense boundary suspends. It returns the |
|
} |
|
|
|
var ReactCurrentOwner$1 = ReactSharedInternals.ReactCurrentOwner; |
|
var didReceiveUpdate = false; |
|
var didWarnAboutBadClass; |
|
var didWarnAboutModulePatternComponent; |
|
var didWarnAboutContextTypeOnFunctionComponent; |
|
var didWarnAboutGetDerivedStateOnFunctionComponent; |
|
var didWarnAboutFunctionRefs; |
|
var didWarnAboutReassigningProps; |
|
var didWarnAboutRevealOrder; |
|
var didWarnAboutTailOptions; |
|
|
|
{ |
|
didWarnAboutBadClass = {}; |
|
didWarnAboutModulePatternComponent = {}; |
|
didWarnAboutContextTypeOnFunctionComponent = {}; |
|
didWarnAboutGetDerivedStateOnFunctionComponent = {}; |
|
didWarnAboutFunctionRefs = {}; |
|
didWarnAboutReassigningProps = false; |
|
didWarnAboutRevealOrder = {}; |
|
didWarnAboutTailOptions = {}; |
|
} |
|
|
|
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) { |
|
if (current === null) { |
|
// If this is a fresh new component that hasn't been rendered yet, we |
|
// won't update its child set by applying minimal side-effects. Instead, |
|
// we will add them all to the child before it gets rendered. That means |
|
// we can optimize this reconciliation pass by not tracking side-effects. |
|
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes); |
|
} else { |
|
// If the current child is the same as the work in progress, it means that |
|
// we haven't yet started any work on these children. Therefore, we use |
|
// the clone algorithm to create a copy of all the current children. |
|
// If we had any progressed work already, that is invalid at this point so |
|
// let's throw it out. |
|
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes); |
|
} |
|
} |
|
|
|
function forceUnmountCurrentAndReconcile(current, workInProgress, nextChildren, renderLanes) { |
|
// This function is fork of reconcileChildren. It's used in cases where we |
|
// want to reconcile without matching against the existing set. This has the |
|
// effect of all current children being unmounted; even if the type and key |
|
// are the same, the old child is unmounted and a new child is created. |
|
// |
|
// To do this, we're going to go through the reconcile algorithm twice. In |
|
// the first pass, we schedule a deletion for all the current children by |
|
// passing null. |
|
workInProgress.child = reconcileChildFibers(workInProgress, current.child, null, renderLanes); // In the second pass, we mount the new children. The trick here is that we |
|
// pass null in place of where we usually pass the current child set. This has |
|
// the effect of remounting all children regardless of whether their |
|
// identities match. |
|
|
|
workInProgress.child = reconcileChildFibers(workInProgress, null, nextChildren, renderLanes); |
|
} |
|
|
|
function updateForwardRef(current, workInProgress, Component, nextProps, renderLanes) { |
|
// TODO: current can be non-null here even if the component |
|
// hasn't yet mounted. This happens after the first render suspends. |
|
// We'll need to figure out if this is fine or can cause issues. |
|
{ |
|
if (workInProgress.type !== workInProgress.elementType) { |
|
// Lazy component props can't be validated in createElement |
|
// because they're only guaranteed to be resolved here. |
|
var innerPropTypes = Component.propTypes; |
|
|
|
if (innerPropTypes) { |
|
checkPropTypes(innerPropTypes, nextProps, // Resolved props |
|
'prop', getComponentNameFromType(Component)); |
|
} |
|
} |
|
} |
|
|
|
var render = Component.render; |
|
var ref = workInProgress.ref; // The rest is a fork of updateFunctionComponent |
|
|
|
var nextChildren; |
|
var hasId; |
|
prepareToReadContext(workInProgress, renderLanes); |
|
|
|
{ |
|
markComponentRenderStarted(workInProgress); |
|
} |
|
|
|
{ |
|
ReactCurrentOwner$1.current = workInProgress; |
|
setIsRendering(true); |
|
nextChildren = renderWithHooks(current, workInProgress, render, nextProps, ref, renderLanes); |
|
hasId = checkDidRenderIdHook(); |
|
|
|
if ( workInProgress.mode & StrictLegacyMode) { |
|
setIsStrictModeForDevtools(true); |
|
|
|
try { |
|
nextChildren = renderWithHooks(current, workInProgress, render, nextProps, ref, renderLanes); |
|
hasId = checkDidRenderIdHook(); |
|
} finally { |
|
setIsStrictModeForDevtools(false); |
|
} |
|
} |
|
|
|
setIsRendering(false); |
|
} |
|
|
|
{ |
|
markComponentRenderStopped(); |
|
} |
|
|
|
if (current !== null && !didReceiveUpdate) { |
|
bailoutHooks(current, workInProgress, renderLanes); |
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); |
|
} |
|
|
|
if (getIsHydrating() && hasId) { |
|
pushMaterializedTreeId(workInProgress); |
|
} // React DevTools reads this flag. |
|
|
|
|
|
workInProgress.flags |= PerformedWork; |
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes); |
|
return workInProgress.child; |
|
} |
|
|
|
function updateMemoComponent(current, workInProgress, Component, nextProps, renderLanes) { |
|
if (current === null) { |
|
var type = Component.type; |
|
|
|
if (isSimpleFunctionComponent(type) && Component.compare === null && // SimpleMemoComponent codepath doesn't resolve outer props either. |
|
Component.defaultProps === undefined) { |
|
var resolvedType = type; |
|
|
|
{ |
|
resolvedType = resolveFunctionForHotReloading(type); |
|
} // If this is a plain function component without default props, |
|
// and with only the default shallow comparison, we upgrade it |
|
// to a SimpleMemoComponent to allow fast path updates. |
|
|
|
|
|
workInProgress.tag = SimpleMemoComponent; |
|
workInProgress.type = resolvedType; |
|
|
|
{ |
|
validateFunctionComponentInDev(workInProgress, type); |
|
} |
|
|
|
return updateSimpleMemoComponent(current, workInProgress, resolvedType, nextProps, renderLanes); |
|
} |
|
|
|
{ |
|
var innerPropTypes = type.propTypes; |
|
|
|
if (innerPropTypes) { |
|
// Inner memo component props aren't currently validated in createElement. |
|
// We could move it there, but we'd still need this for lazy code path. |
|
checkPropTypes(innerPropTypes, nextProps, // Resolved props |
|
'prop', getComponentNameFromType(type)); |
|
} |
|
} |
|
|
|
var child = createFiberFromTypeAndProps(Component.type, null, nextProps, workInProgress, workInProgress.mode, renderLanes); |
|
child.ref = workInProgress.ref; |
|
child.return = workInProgress; |
|
workInProgress.child = child; |
|
return child; |
|
} |
|
|
|
{ |
|
var _type = Component.type; |
|
var _innerPropTypes = _type.propTypes; |
|
|
|
if (_innerPropTypes) { |
|
// Inner memo component props aren't currently validated in createElement. |
|
// We could move it there, but we'd still need this for lazy code path. |
|
checkPropTypes(_innerPropTypes, nextProps, // Resolved props |
|
'prop', getComponentNameFromType(_type)); |
|
} |
|
} |
|
|
|
var currentChild = current.child; // This is always exactly one child |
|
|
|
var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes); |
|
|
|
if (!hasScheduledUpdateOrContext) { |
|
// This will be the props with resolved defaultProps, |
|
// unlike current.memoizedProps which will be the unresolved ones. |
|
var prevProps = currentChild.memoizedProps; // Default to shallow comparison |
|
|
|
var compare = Component.compare; |
|
compare = compare !== null ? compare : shallowEqual; |
|
|
|
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) { |
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); |
|
} |
|
} // React DevTools reads this flag. |
|
|
|
|
|
workInProgress.flags |= PerformedWork; |
|
var newChild = createWorkInProgress(currentChild, nextProps); |
|
newChild.ref = workInProgress.ref; |
|
newChild.return = workInProgress; |
|
workInProgress.child = newChild; |
|
return newChild; |
|
} |
|
|
|
function updateSimpleMemoComponent(current, workInProgress, Component, nextProps, renderLanes) { |
|
// TODO: current can be non-null here even if the component |
|
// hasn't yet mounted. This happens when the inner render suspends. |
|
// We'll need to figure out if this is fine or can cause issues. |
|
{ |
|
if (workInProgress.type !== workInProgress.elementType) { |
|
// Lazy component props can't be validated in createElement |
|
// because they're only guaranteed to be resolved here. |
|
var outerMemoType = workInProgress.elementType; |
|
|
|
if (outerMemoType.$$typeof === REACT_LAZY_TYPE) { |
|
// We warn when you define propTypes on lazy() |
|
// so let's just skip over it to find memo() outer wrapper. |
|
// Inner props for memo are validated later. |
|
var lazyComponent = outerMemoType; |
|
var payload = lazyComponent._payload; |
|
var init = lazyComponent._init; |
|
|
|
try { |
|
outerMemoType = init(payload); |
|
} catch (x) { |
|
outerMemoType = null; |
|
} // Inner propTypes will be validated in the function component path. |
|
|
|
|
|
var outerPropTypes = outerMemoType && outerMemoType.propTypes; |
|
|
|
if (outerPropTypes) { |
|
checkPropTypes(outerPropTypes, nextProps, // Resolved (SimpleMemoComponent has no defaultProps) |
|
'prop', getComponentNameFromType(outerMemoType)); |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (current !== null) { |
|
var prevProps = current.memoizedProps; |
|
|
|
if (shallowEqual(prevProps, nextProps) && current.ref === workInProgress.ref && ( // Prevent bailout if the implementation changed due to hot reload. |
|
workInProgress.type === current.type )) { |
|
didReceiveUpdate = false; // The props are shallowly equal. Reuse the previous props object, like we |
|
// would during a normal fiber bailout. |
|
// |
|
// We don't have strong guarantees that the props object is referentially |
|
// equal during updates where we can't bail out anyway — like if the props |
|
// are shallowly equal, but there's a local state or context update in the |
|
// same batch. |
|
// |
|
// However, as a principle, we should aim to make the behavior consistent |
|
// across different ways of memoizing a component. For example, React.memo |
|
// has a different internal Fiber layout if you pass a normal function |
|
// component (SimpleMemoComponent) versus if you pass a different type |
|
// like forwardRef (MemoComponent). But this is an implementation detail. |
|
// Wrapping a component in forwardRef (or React.lazy, etc) shouldn't |
|
// affect whether the props object is reused during a bailout. |
|
|
|
workInProgress.pendingProps = nextProps = prevProps; |
|
|
|
if (!checkScheduledUpdateOrContext(current, renderLanes)) { |
|
// The pending lanes were cleared at the beginning of beginWork. We're |
|
// about to bail out, but there might be other lanes that weren't |
|
// included in the current render. Usually, the priority level of the |
|
// remaining updates is accumulated during the evaluation of the |
|
// component (i.e. when processing the update queue). But since since |
|
// we're bailing out early *without* evaluating the component, we need |
|
// to account for it here, too. Reset to the value of the current fiber. |
|
// NOTE: This only applies to SimpleMemoComponent, not MemoComponent, |
|
// because a MemoComponent fiber does not have hooks or an update queue; |
|
// rather, it wraps around an inner component, which may or may not |
|
// contains hooks. |
|
// TODO: Move the reset at in beginWork out of the common path so that |
|
// this is no longer necessary. |
|
workInProgress.lanes = current.lanes; |
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); |
|
} else if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) { |
|
// This is a special case that only exists for legacy mode. |
|
// See https://github.com/facebook/react/pull/19216. |
|
didReceiveUpdate = true; |
|
} |
|
} |
|
} |
|
|
|
return updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes); |
|
} |
|
|
|
function updateOffscreenComponent(current, workInProgress, renderLanes) { |
|
var nextProps = workInProgress.pendingProps; |
|
var nextChildren = nextProps.children; |
|
var prevState = current !== null ? current.memoizedState : null; |
|
|
|
if (nextProps.mode === 'hidden' || enableLegacyHidden ) { |
|
// Rendering a hidden tree. |
|
if ((workInProgress.mode & ConcurrentMode) === NoMode) { |
|
// In legacy sync mode, don't defer the subtree. Render it now. |
|
// TODO: Consider how Offscreen should work with transitions in the future |
|
var nextState = { |
|
baseLanes: NoLanes, |
|
cachePool: null, |
|
transitions: null |
|
}; |
|
workInProgress.memoizedState = nextState; |
|
|
|
pushRenderLanes(workInProgress, renderLanes); |
|
} else if (!includesSomeLane(renderLanes, OffscreenLane)) { |
|
var spawnedCachePool = null; // We're hidden, and we're not rendering at Offscreen. We will bail out |
|
// and resume this tree later. |
|
|
|
var nextBaseLanes; |
|
|
|
if (prevState !== null) { |
|
var prevBaseLanes = prevState.baseLanes; |
|
nextBaseLanes = mergeLanes(prevBaseLanes, renderLanes); |
|
} else { |
|
nextBaseLanes = renderLanes; |
|
} // Schedule this fiber to re-render at offscreen priority. Then bailout. |
|
|
|
|
|
workInProgress.lanes = workInProgress.childLanes = laneToLanes(OffscreenLane); |
|
var _nextState = { |
|
baseLanes: nextBaseLanes, |
|
cachePool: spawnedCachePool, |
|
transitions: null |
|
}; |
|
workInProgress.memoizedState = _nextState; |
|
workInProgress.updateQueue = null; |
|
// to avoid a push/pop misalignment. |
|
|
|
|
|
pushRenderLanes(workInProgress, nextBaseLanes); |
|
|
|
return null; |
|
} else { |
|
// This is the second render. The surrounding visible content has already |
|
// committed. Now we resume rendering the hidden tree. |
|
// Rendering at offscreen, so we can clear the base lanes. |
|
var _nextState2 = { |
|
baseLanes: NoLanes, |
|
cachePool: null, |
|
transitions: null |
|
}; |
|
workInProgress.memoizedState = _nextState2; // Push the lanes that were skipped when we bailed out. |
|
|
|
var subtreeRenderLanes = prevState !== null ? prevState.baseLanes : renderLanes; |
|
|
|
pushRenderLanes(workInProgress, subtreeRenderLanes); |
|
} |
|
} else { |
|
// Rendering a visible tree. |
|
var _subtreeRenderLanes; |
|
|
|
if (prevState !== null) { |
|
// We're going from hidden -> visible. |
|
_subtreeRenderLanes = mergeLanes(prevState.baseLanes, renderLanes); |
|
|
|
workInProgress.memoizedState = null; |
|
} else { |
|
// We weren't previously hidden, and we still aren't, so there's nothing |
|
// special to do. Need to push to the stack regardless, though, to avoid |
|
// a push/pop misalignment. |
|
_subtreeRenderLanes = renderLanes; |
|
} |
|
|
|
pushRenderLanes(workInProgress, _subtreeRenderLanes); |
|
} |
|
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes); |
|
return workInProgress.child; |
|
} // Note: These happen to have identical begin phases, for now. We shouldn't hold |
|
|
|
function updateFragment(current, workInProgress, renderLanes) { |
|
var nextChildren = workInProgress.pendingProps; |
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes); |
|
return workInProgress.child; |
|
} |
|
|
|
function updateMode(current, workInProgress, renderLanes) { |
|
var nextChildren = workInProgress.pendingProps.children; |
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes); |
|
return workInProgress.child; |
|
} |
|
|
|
function updateProfiler(current, workInProgress, renderLanes) { |
|
{ |
|
workInProgress.flags |= Update; |
|
|
|
{ |
|
// Reset effect durations for the next eventual effect phase. |
|
// These are reset during render to allow the DevTools commit hook a chance to read them, |
|
var stateNode = workInProgress.stateNode; |
|
stateNode.effectDuration = 0; |
|
stateNode.passiveEffectDuration = 0; |
|
} |
|
} |
|
|
|
var nextProps = workInProgress.pendingProps; |
|
var nextChildren = nextProps.children; |
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes); |
|
return workInProgress.child; |
|
} |
|
|
|
function markRef(current, workInProgress) { |
|
var ref = workInProgress.ref; |
|
|
|
if (current === null && ref !== null || current !== null && current.ref !== ref) { |
|
// Schedule a Ref effect |
|
workInProgress.flags |= Ref; |
|
|
|
{ |
|
workInProgress.flags |= RefStatic; |
|
} |
|
} |
|
} |
|
|
|
function updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes) { |
|
{ |
|
if (workInProgress.type !== workInProgress.elementType) { |
|
// Lazy component props can't be validated in createElement |
|
// because they're only guaranteed to be resolved here. |
|
var innerPropTypes = Component.propTypes; |
|
|
|
if (innerPropTypes) { |
|
checkPropTypes(innerPropTypes, nextProps, // Resolved props |
|
'prop', getComponentNameFromType(Component)); |
|
} |
|
} |
|
} |
|
|
|
var context; |
|
|
|
{ |
|
var unmaskedContext = getUnmaskedContext(workInProgress, Component, true); |
|
context = getMaskedContext(workInProgress, unmaskedContext); |
|
} |
|
|
|
var nextChildren; |
|
var hasId; |
|
prepareToReadContext(workInProgress, renderLanes); |
|
|
|
{ |
|
markComponentRenderStarted(workInProgress); |
|
} |
|
|
|
{ |
|
ReactCurrentOwner$1.current = workInProgress; |
|
setIsRendering(true); |
|
nextChildren = renderWithHooks(current, workInProgress, Component, nextProps, context, renderLanes); |
|
hasId = checkDidRenderIdHook(); |
|
|
|
if ( workInProgress.mode & StrictLegacyMode) { |
|
setIsStrictModeForDevtools(true); |
|
|
|
try { |
|
nextChildren = renderWithHooks(current, workInProgress, Component, nextProps, context, renderLanes); |
|
hasId = checkDidRenderIdHook(); |
|
} finally { |
|
setIsStrictModeForDevtools(false); |
|
} |
|
} |
|
|
|
setIsRendering(false); |
|
} |
|
|
|
{ |
|
markComponentRenderStopped(); |
|
} |
|
|
|
if (current !== null && !didReceiveUpdate) { |
|
bailoutHooks(current, workInProgress, renderLanes); |
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); |
|
} |
|
|
|
if (getIsHydrating() && hasId) { |
|
pushMaterializedTreeId(workInProgress); |
|
} // React DevTools reads this flag. |
|
|
|
|
|
workInProgress.flags |= PerformedWork; |
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes); |
|
return workInProgress.child; |
|
} |
|
|
|
function updateClassComponent(current, workInProgress, Component, nextProps, renderLanes) { |
|
{ |
|
// This is used by DevTools to force a boundary to error. |
|
switch (shouldError(workInProgress)) { |
|
case false: |
|
{ |
|
var _instance = workInProgress.stateNode; |
|
var ctor = workInProgress.type; // TODO This way of resetting the error boundary state is a hack. |
|
// Is there a better way to do this? |
|
|
|
var tempInstance = new ctor(workInProgress.memoizedProps, _instance.context); |
|
var state = tempInstance.state; |
|
|
|
_instance.updater.enqueueSetState(_instance, state, null); |
|
|
|
break; |
|
} |
|
|
|
case true: |
|
{ |
|
workInProgress.flags |= DidCapture; |
|
workInProgress.flags |= ShouldCapture; // eslint-disable-next-line react-internal/prod-error-codes |
|
|
|
var error$1 = new Error('Simulated error coming from DevTools'); |
|
var lane = pickArbitraryLane(renderLanes); |
|
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane); // Schedule the error boundary to re-render using updated state |
|
|
|
var update = createClassErrorUpdate(workInProgress, createCapturedValueAtFiber(error$1, workInProgress), lane); |
|
enqueueCapturedUpdate(workInProgress, update); |
|
break; |
|
} |
|
} |
|
|
|
if (workInProgress.type !== workInProgress.elementType) { |
|
// Lazy component props can't be validated in createElement |
|
// because they're only guaranteed to be resolved here. |
|
var innerPropTypes = Component.propTypes; |
|
|
|
if (innerPropTypes) { |
|
checkPropTypes(innerPropTypes, nextProps, // Resolved props |
|
'prop', getComponentNameFromType(Component)); |
|
} |
|
} |
|
} // Push context providers early to prevent context stack mismatches. |
|
// During mounting we don't know the child context yet as the instance doesn't exist. |
|
// We will invalidate the child context in finishClassComponent() right after rendering. |
|
|
|
|
|
var hasContext; |
|
|
|
if (isContextProvider(Component)) { |
|
hasContext = true; |
|
pushContextProvider(workInProgress); |
|
} else { |
|
hasContext = false; |
|
} |
|
|
|
prepareToReadContext(workInProgress, renderLanes); |
|
var instance = workInProgress.stateNode; |
|
var shouldUpdate; |
|
|
|
if (instance === null) { |
|
resetSuspendedCurrentOnMountInLegacyMode(current, workInProgress); // In the initial pass we might need to construct the instance. |
|
|
|
constructClassInstance(workInProgress, Component, nextProps); |
|
mountClassInstance(workInProgress, Component, nextProps, renderLanes); |
|
shouldUpdate = true; |
|
} else if (current === null) { |
|
// In a resume, we'll already have an instance we can reuse. |
|
shouldUpdate = resumeMountClassInstance(workInProgress, Component, nextProps, renderLanes); |
|
} else { |
|
shouldUpdate = updateClassInstance(current, workInProgress, Component, nextProps, renderLanes); |
|
} |
|
|
|
var nextUnitOfWork = finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes); |
|
|
|
{ |
|
var inst = workInProgress.stateNode; |
|
|
|
if (shouldUpdate && inst.props !== nextProps) { |
|
if (!didWarnAboutReassigningProps) { |
|
error('It looks like %s is reassigning its own `this.props` while rendering. ' + 'This is not supported and can lead to confusing bugs.', getComponentNameFromFiber(workInProgress) || 'a component'); |
|
} |
|
|
|
didWarnAboutReassigningProps = true; |
|
} |
|
} |
|
|
|
return nextUnitOfWork; |
|
} |
|
|
|
function finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderLanes) { |
|
// Refs should update even if shouldComponentUpdate returns false |
|
markRef(current, workInProgress); |
|
var didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags; |
|
|
|
if (!shouldUpdate && !didCaptureError) { |
|
// Context providers should defer to sCU for rendering |
|
if (hasContext) { |
|
invalidateContextProvider(workInProgress, Component, false); |
|
} |
|
|
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); |
|
} |
|
|
|
var instance = workInProgress.stateNode; // Rerender |
|
|
|
ReactCurrentOwner$1.current = workInProgress; |
|
var nextChildren; |
|
|
|
if (didCaptureError && typeof Component.getDerivedStateFromError !== 'function') { |
|
// If we captured an error, but getDerivedStateFromError is not defined, |
|
// unmount all the children. componentDidCatch will schedule an update to |
|
// re-render a fallback. This is temporary until we migrate everyone to |
|
// the new API. |
|
// TODO: Warn in a future release. |
|
nextChildren = null; |
|
|
|
{ |
|
stopProfilerTimerIfRunning(); |
|
} |
|
} else { |
|
{ |
|
markComponentRenderStarted(workInProgress); |
|
} |
|
|
|
{ |
|
setIsRendering(true); |
|
nextChildren = instance.render(); |
|
|
|
if ( workInProgress.mode & StrictLegacyMode) { |
|
setIsStrictModeForDevtools(true); |
|
|
|
try { |
|
instance.render(); |
|
} finally { |
|
setIsStrictModeForDevtools(false); |
|
} |
|
} |
|
|
|
setIsRendering(false); |
|
} |
|
|
|
{ |
|
markComponentRenderStopped(); |
|
} |
|
} // React DevTools reads this flag. |
|
|
|
|
|
workInProgress.flags |= PerformedWork; |
|
|
|
if (current !== null && didCaptureError) { |
|
// If we're recovering from an error, reconcile without reusing any of |
|
// the existing children. Conceptually, the normal children and the children |
|
// that are shown on error are two different sets, so we shouldn't reuse |
|
// normal children even if their identities match. |
|
forceUnmountCurrentAndReconcile(current, workInProgress, nextChildren, renderLanes); |
|
} else { |
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes); |
|
} // Memoize state using the values we just used to render. |
|
// TODO: Restructure so we never read values from the instance. |
|
|
|
|
|
workInProgress.memoizedState = instance.state; // The context might have changed so we need to recalculate it. |
|
|
|
if (hasContext) { |
|
invalidateContextProvider(workInProgress, Component, true); |
|
} |
|
|
|
return workInProgress.child; |
|
} |
|
|
|
function pushHostRootContext(workInProgress) { |
|
var root = workInProgress.stateNode; |
|
|
|
if (root.pendingContext) { |
|
pushTopLevelContextObject(workInProgress, root.pendingContext, root.pendingContext !== root.context); |
|
} else if (root.context) { |
|
// Should always be set |
|
pushTopLevelContextObject(workInProgress, root.context, false); |
|
} |
|
|
|
pushHostContainer(workInProgress, root.containerInfo); |
|
} |
|
|
|
function updateHostRoot(current, workInProgress, renderLanes) { |
|
pushHostRootContext(workInProgress); |
|
|
|
if (current === null) { |
|
throw new Error('Should have a current fiber. This is a bug in React.'); |
|
} |
|
|
|
var nextProps = workInProgress.pendingProps; |
|
var prevState = workInProgress.memoizedState; |
|
var prevChildren = prevState.element; |
|
cloneUpdateQueue(current, workInProgress); |
|
processUpdateQueue(workInProgress, nextProps, null, renderLanes); |
|
var nextState = workInProgress.memoizedState; |
|
var root = workInProgress.stateNode; |
|
// being called "element". |
|
|
|
|
|
var nextChildren = nextState.element; |
|
|
|
if ( prevState.isDehydrated) { |
|
// This is a hydration root whose shell has not yet hydrated. We should |
|
// attempt to hydrate. |
|
// Flip isDehydrated to false to indicate that when this render |
|
// finishes, the root will no longer be dehydrated. |
|
var overrideState = { |
|
element: nextChildren, |
|
isDehydrated: false, |
|
cache: nextState.cache, |
|
pendingSuspenseBoundaries: nextState.pendingSuspenseBoundaries, |
|
transitions: nextState.transitions |
|
}; |
|
var updateQueue = workInProgress.updateQueue; // `baseState` can always be the last state because the root doesn't |
|
// have reducer functions so it doesn't need rebasing. |
|
|
|
updateQueue.baseState = overrideState; |
|
workInProgress.memoizedState = overrideState; |
|
|
|
if (workInProgress.flags & ForceClientRender) { |
|
// Something errored during a previous attempt to hydrate the shell, so we |
|
// forced a client render. |
|
var recoverableError = createCapturedValueAtFiber(new Error('There was an error while hydrating. Because the error happened outside ' + 'of a Suspense boundary, the entire root will switch to ' + 'client rendering.'), workInProgress); |
|
return mountHostRootWithoutHydrating(current, workInProgress, nextChildren, renderLanes, recoverableError); |
|
} else if (nextChildren !== prevChildren) { |
|
var _recoverableError = createCapturedValueAtFiber(new Error('This root received an early update, before anything was able ' + 'hydrate. Switched the entire root to client rendering.'), workInProgress); |
|
|
|
return mountHostRootWithoutHydrating(current, workInProgress, nextChildren, renderLanes, _recoverableError); |
|
} else { |
|
// The outermost shell has not hydrated yet. Start hydrating. |
|
enterHydrationState(workInProgress); |
|
|
|
var child = mountChildFibers(workInProgress, null, nextChildren, renderLanes); |
|
workInProgress.child = child; |
|
var node = child; |
|
|
|
while (node) { |
|
// Mark each child as hydrating. This is a fast path to know whether this |
|
// tree is part of a hydrating tree. This is used to determine if a child |
|
// node has fully mounted yet, and for scheduling event replaying. |
|
// Conceptually this is similar to Placement in that a new subtree is |
|
// inserted into the React tree here. It just happens to not need DOM |
|
// mutations because it already exists. |
|
node.flags = node.flags & ~Placement | Hydrating; |
|
node = node.sibling; |
|
} |
|
} |
|
} else { |
|
// Root is not dehydrated. Either this is a client-only root, or it |
|
// already hydrated. |
|
resetHydrationState(); |
|
|
|
if (nextChildren === prevChildren) { |
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); |
|
} |
|
|
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes); |
|
} |
|
|
|
return workInProgress.child; |
|
} |
|
|
|
function mountHostRootWithoutHydrating(current, workInProgress, nextChildren, renderLanes, recoverableError) { |
|
// Revert to client rendering. |
|
resetHydrationState(); |
|
queueHydrationError(recoverableError); |
|
workInProgress.flags |= ForceClientRender; |
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes); |
|
return workInProgress.child; |
|
} |
|
|
|
function updateHostComponent(current, workInProgress, renderLanes) { |
|
pushHostContext(workInProgress); |
|
|
|
if (current === null) { |
|
tryToClaimNextHydratableInstance(workInProgress); |
|
} |
|
|
|
var type = workInProgress.type; |
|
var nextProps = workInProgress.pendingProps; |
|
var prevProps = current !== null ? current.memoizedProps : null; |
|
var nextChildren = nextProps.children; |
|
var isDirectTextChild = shouldSetTextContent(type, nextProps); |
|
|
|
if (isDirectTextChild) { |
|
// We special case a direct text child of a host node. This is a common |
|
// case. We won't handle it as a reified child. We will instead handle |
|
// this in the host environment that also has access to this prop. That |
|
// avoids allocating another HostText fiber and traversing it. |
|
nextChildren = null; |
|
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) { |
|
// If we're switching from a direct text child to a normal child, or to |
|
// empty, we need to schedule the text content to be reset. |
|
workInProgress.flags |= ContentReset; |
|
} |
|
|
|
markRef(current, workInProgress); |
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes); |
|
return workInProgress.child; |
|
} |
|
|
|
function updateHostText(current, workInProgress) { |
|
if (current === null) { |
|
tryToClaimNextHydratableInstance(workInProgress); |
|
} // Nothing to do here. This is terminal. We'll do the completion step |
|
// immediately after. |
|
|
|
|
|
return null; |
|
} |
|
|
|
function mountLazyComponent(_current, workInProgress, elementType, renderLanes) { |
|
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress); |
|
var props = workInProgress.pendingProps; |
|
var lazyComponent = elementType; |
|
var payload = lazyComponent._payload; |
|
var init = lazyComponent._init; |
|
var Component = init(payload); // Store the unwrapped component in the type. |
|
|
|
workInProgress.type = Component; |
|
var resolvedTag = workInProgress.tag = resolveLazyComponentTag(Component); |
|
var resolvedProps = resolveDefaultProps(Component, props); |
|
var child; |
|
|
|
switch (resolvedTag) { |
|
case FunctionComponent: |
|
{ |
|
{ |
|
validateFunctionComponentInDev(workInProgress, Component); |
|
workInProgress.type = Component = resolveFunctionForHotReloading(Component); |
|
} |
|
|
|
child = updateFunctionComponent(null, workInProgress, Component, resolvedProps, renderLanes); |
|
return child; |
|
} |
|
|
|
case ClassComponent: |
|
{ |
|
{ |
|
workInProgress.type = Component = resolveClassForHotReloading(Component); |
|
} |
|
|
|
child = updateClassComponent(null, workInProgress, Component, resolvedProps, renderLanes); |
|
return child; |
|
} |
|
|
|
case ForwardRef: |
|
{ |
|
{ |
|
workInProgress.type = Component = resolveForwardRefForHotReloading(Component); |
|
} |
|
|
|
child = updateForwardRef(null, workInProgress, Component, resolvedProps, renderLanes); |
|
return child; |
|
} |
|
|
|
case MemoComponent: |
|
{ |
|
{ |
|
if (workInProgress.type !== workInProgress.elementType) { |
|
var outerPropTypes = Component.propTypes; |
|
|
|
if (outerPropTypes) { |
|
checkPropTypes(outerPropTypes, resolvedProps, // Resolved for outer only |
|
'prop', getComponentNameFromType(Component)); |
|
} |
|
} |
|
} |
|
|
|
child = updateMemoComponent(null, workInProgress, Component, resolveDefaultProps(Component.type, resolvedProps), // The inner type can have defaults too |
|
renderLanes); |
|
return child; |
|
} |
|
} |
|
|
|
var hint = ''; |
|
|
|
{ |
|
if (Component !== null && typeof Component === 'object' && Component.$$typeof === REACT_LAZY_TYPE) { |
|
hint = ' Did you wrap a component in React.lazy() more than once?'; |
|
} |
|
} // This message intentionally doesn't mention ForwardRef or MemoComponent |
|
// because the fact that it's a separate type of work is an |
|
// implementation detail. |
|
|
|
|
|
throw new Error("Element type is invalid. Received a promise that resolves to: " + Component + ". " + ("Lazy element type must resolve to a class or function." + hint)); |
|
} |
|
|
|
function mountIncompleteClassComponent(_current, workInProgress, Component, nextProps, renderLanes) { |
|
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress); // Promote the fiber to a class and try rendering again. |
|
|
|
workInProgress.tag = ClassComponent; // The rest of this function is a fork of `updateClassComponent` |
|
// Push context providers early to prevent context stack mismatches. |
|
// During mounting we don't know the child context yet as the instance doesn't exist. |
|
// We will invalidate the child context in finishClassComponent() right after rendering. |
|
|
|
var hasContext; |
|
|
|
if (isContextProvider(Component)) { |
|
hasContext = true; |
|
pushContextProvider(workInProgress); |
|
} else { |
|
hasContext = false; |
|
} |
|
|
|
prepareToReadContext(workInProgress, renderLanes); |
|
constructClassInstance(workInProgress, Component, nextProps); |
|
mountClassInstance(workInProgress, Component, nextProps, renderLanes); |
|
return finishClassComponent(null, workInProgress, Component, true, hasContext, renderLanes); |
|
} |
|
|
|
function mountIndeterminateComponent(_current, workInProgress, Component, renderLanes) { |
|
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress); |
|
var props = workInProgress.pendingProps; |
|
var context; |
|
|
|
{ |
|
var unmaskedContext = getUnmaskedContext(workInProgress, Component, false); |
|
context = getMaskedContext(workInProgress, unmaskedContext); |
|
} |
|
|
|
prepareToReadContext(workInProgress, renderLanes); |
|
var value; |
|
var hasId; |
|
|
|
{ |
|
markComponentRenderStarted(workInProgress); |
|
} |
|
|
|
{ |
|
if (Component.prototype && typeof Component.prototype.render === 'function') { |
|
var componentName = getComponentNameFromType(Component) || 'Unknown'; |
|
|
|
if (!didWarnAboutBadClass[componentName]) { |
|
error("The <%s /> component appears to have a render method, but doesn't extend React.Component. " + 'This is likely to cause errors. Change %s to extend React.Component instead.', componentName, componentName); |
|
|
|
didWarnAboutBadClass[componentName] = true; |
|
} |
|
} |
|
|
|
if (workInProgress.mode & StrictLegacyMode) { |
|
ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null); |
|
} |
|
|
|
setIsRendering(true); |
|
ReactCurrentOwner$1.current = workInProgress; |
|
value = renderWithHooks(null, workInProgress, Component, props, context, renderLanes); |
|
hasId = checkDidRenderIdHook(); |
|
setIsRendering(false); |
|
} |
|
|
|
{ |
|
markComponentRenderStopped(); |
|
} // React DevTools reads this flag. |
|
|
|
|
|
workInProgress.flags |= PerformedWork; |
|
|
|
{ |
|
// Support for module components is deprecated and is removed behind a flag. |
|
// Whether or not it would crash later, we want to show a good message in DEV first. |
|
if (typeof value === 'object' && value !== null && typeof value.render === 'function' && value.$$typeof === undefined) { |
|
var _componentName = getComponentNameFromType(Component) || 'Unknown'; |
|
|
|
if (!didWarnAboutModulePatternComponent[_componentName]) { |
|
error('The <%s /> component appears to be a function component that returns a class instance. ' + 'Change %s to a class that extends React.Component instead. ' + "If you can't use a class try assigning the prototype on the function as a workaround. " + "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " + 'cannot be called with `new` by React.', _componentName, _componentName, _componentName); |
|
|
|
didWarnAboutModulePatternComponent[_componentName] = true; |
|
} |
|
} |
|
} |
|
|
|
if ( // Run these checks in production only if the flag is off. |
|
// Eventually we'll delete this branch altogether. |
|
typeof value === 'object' && value !== null && typeof value.render === 'function' && value.$$typeof === undefined) { |
|
{ |
|
var _componentName2 = getComponentNameFromType(Component) || 'Unknown'; |
|
|
|
if (!didWarnAboutModulePatternComponent[_componentName2]) { |
|
error('The <%s /> component appears to be a function component that returns a class instance. ' + 'Change %s to a class that extends React.Component instead. ' + "If you can't use a class try assigning the prototype on the function as a workaround. " + "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " + 'cannot be called with `new` by React.', _componentName2, _componentName2, _componentName2); |
|
|
|
didWarnAboutModulePatternComponent[_componentName2] = true; |
|
} |
|
} // Proceed under the assumption that this is a class instance |
|
|
|
|
|
workInProgress.tag = ClassComponent; // Throw out any hooks that were used. |
|
|
|
workInProgress.memoizedState = null; |
|
workInProgress.updateQueue = null; // Push context providers early to prevent context stack mismatches. |
|
// During mounting we don't know the child context yet as the instance doesn't exist. |
|
// We will invalidate the child context in finishClassComponent() right after rendering. |
|
|
|
var hasContext = false; |
|
|
|
if (isContextProvider(Component)) { |
|
hasContext = true; |
|
pushContextProvider(workInProgress); |
|
} else { |
|
hasContext = false; |
|
} |
|
|
|
workInProgress.memoizedState = value.state !== null && value.state !== undefined ? value.state : null; |
|
initializeUpdateQueue(workInProgress); |
|
adoptClassInstance(workInProgress, value); |
|
mountClassInstance(workInProgress, Component, props, renderLanes); |
|
return finishClassComponent(null, workInProgress, Component, true, hasContext, renderLanes); |
|
} else { |
|
// Proceed under the assumption that this is a function component |
|
workInProgress.tag = FunctionComponent; |
|
|
|
{ |
|
|
|
if ( workInProgress.mode & StrictLegacyMode) { |
|
setIsStrictModeForDevtools(true); |
|
|
|
try { |
|
value = renderWithHooks(null, workInProgress, Component, props, context, renderLanes); |
|
hasId = checkDidRenderIdHook(); |
|
} finally { |
|
setIsStrictModeForDevtools(false); |
|
} |
|
} |
|
} |
|
|
|
if (getIsHydrating() && hasId) { |
|
pushMaterializedTreeId(workInProgress); |
|
} |
|
|
|
reconcileChildren(null, workInProgress, value, renderLanes); |
|
|
|
{ |
|
validateFunctionComponentInDev(workInProgress, Component); |
|
} |
|
|
|
return workInProgress.child; |
|
} |
|
} |
|
|
|
function validateFunctionComponentInDev(workInProgress, Component) { |
|
{ |
|
if (Component) { |
|
if (Component.childContextTypes) { |
|
error('%s(...): childContextTypes cannot be defined on a function component.', Component.displayName || Component.name || 'Component'); |
|
} |
|
} |
|
|
|
if (workInProgress.ref !== null) { |
|
var info = ''; |
|
var ownerName = getCurrentFiberOwnerNameInDevOrNull(); |
|
|
|
if (ownerName) { |
|
info += '\n\nCheck the render method of `' + ownerName + '`.'; |
|
} |
|
|
|
var warningKey = ownerName || ''; |
|
var debugSource = workInProgress._debugSource; |
|
|
|
if (debugSource) { |
|
warningKey = debugSource.fileName + ':' + debugSource.lineNumber; |
|
} |
|
|
|
if (!didWarnAboutFunctionRefs[warningKey]) { |
|
didWarnAboutFunctionRefs[warningKey] = true; |
|
|
|
error('Function components cannot be given refs. ' + 'Attempts to access this ref will fail. ' + 'Did you mean to use React.forwardRef()?%s', info); |
|
} |
|
} |
|
|
|
if (typeof Component.getDerivedStateFromProps === 'function') { |
|
var _componentName3 = getComponentNameFromType(Component) || 'Unknown'; |
|
|
|
if (!didWarnAboutGetDerivedStateOnFunctionComponent[_componentName3]) { |
|
error('%s: Function components do not support getDerivedStateFromProps.', _componentName3); |
|
|
|
didWarnAboutGetDerivedStateOnFunctionComponent[_componentName3] = true; |
|
} |
|
} |
|
|
|
if (typeof Component.contextType === 'object' && Component.contextType !== null) { |
|
var _componentName4 = getComponentNameFromType(Component) || 'Unknown'; |
|
|
|
if (!didWarnAboutContextTypeOnFunctionComponent[_componentName4]) { |
|
error('%s: Function components do not support contextType.', _componentName4); |
|
|
|
didWarnAboutContextTypeOnFunctionComponent[_componentName4] = true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
var SUSPENDED_MARKER = { |
|
dehydrated: null, |
|
treeContext: null, |
|
retryLane: NoLane |
|
}; |
|
|
|
function mountSuspenseOffscreenState(renderLanes) { |
|
return { |
|
baseLanes: renderLanes, |
|
cachePool: getSuspendedCache(), |
|
transitions: null |
|
}; |
|
} |
|
|
|
function updateSuspenseOffscreenState(prevOffscreenState, renderLanes) { |
|
var cachePool = null; |
|
|
|
return { |
|
baseLanes: mergeLanes(prevOffscreenState.baseLanes, renderLanes), |
|
cachePool: cachePool, |
|
transitions: prevOffscreenState.transitions |
|
}; |
|
} // TODO: Probably should inline this back |
|
|
|
|
|
function shouldRemainOnFallback(suspenseContext, current, workInProgress, renderLanes) { |
|
// If we're already showing a fallback, there are cases where we need to |
|
// remain on that fallback regardless of whether the content has resolved. |
|
// For example, SuspenseList coordinates when nested content appears. |
|
if (current !== null) { |
|
var suspenseState = current.memoizedState; |
|
|
|
if (suspenseState === null) { |
|
// Currently showing content. Don't hide it, even if ForceSuspenseFallback |
|
// is true. More precise name might be "ForceRemainSuspenseFallback". |
|
// Note: This is a factoring smell. Can't remain on a fallback if there's |
|
// no fallback to remain on. |
|
return false; |
|
} |
|
} // Not currently showing content. Consult the Suspense context. |
|
|
|
|
|
return hasSuspenseContext(suspenseContext, ForceSuspenseFallback); |
|
} |
|
|
|
function getRemainingWorkInPrimaryTree(current, renderLanes) { |
|
// TODO: Should not remove render lanes that were pinged during this render |
|
return removeLanes(current.childLanes, renderLanes); |
|
} |
|
|
|
function updateSuspenseComponent(current, workInProgress, renderLanes) { |
|
var nextProps = workInProgress.pendingProps; // This is used by DevTools to force a boundary to suspend. |
|
|
|
{ |
|
if (shouldSuspend(workInProgress)) { |
|
workInProgress.flags |= DidCapture; |
|
} |
|
} |
|
|
|
var suspenseContext = suspenseStackCursor.current; |
|
var showFallback = false; |
|
var didSuspend = (workInProgress.flags & DidCapture) !== NoFlags; |
|
|
|
if (didSuspend || shouldRemainOnFallback(suspenseContext, current)) { |
|
// Something in this boundary's subtree already suspended. Switch to |
|
// rendering the fallback children. |
|
showFallback = true; |
|
workInProgress.flags &= ~DidCapture; |
|
} else { |
|
// Attempting the main content |
|
if (current === null || current.memoizedState !== null) { |
|
// This is a new mount or this boundary is already showing a fallback state. |
|
// Mark this subtree context as having at least one invisible parent that could |
|
// handle the fallback state. |
|
// Avoided boundaries are not considered since they cannot handle preferred fallback states. |
|
{ |
|
suspenseContext = addSubtreeSuspenseContext(suspenseContext, InvisibleParentSuspenseContext); |
|
} |
|
} |
|
} |
|
|
|
suspenseContext = setDefaultShallowSuspenseContext(suspenseContext); |
|
pushSuspenseContext(workInProgress, suspenseContext); // OK, the next part is confusing. We're about to reconcile the Suspense |
|
// boundary's children. This involves some custom reconciliation logic. Two |
|
// main reasons this is so complicated. |
|
// |
|
// First, Legacy Mode has different semantics for backwards compatibility. The |
|
// primary tree will commit in an inconsistent state, so when we do the |
|
// second pass to render the fallback, we do some exceedingly, uh, clever |
|
// hacks to make that not totally break. Like transferring effects and |
|
// deletions from hidden tree. In Concurrent Mode, it's much simpler, |
|
// because we bailout on the primary tree completely and leave it in its old |
|
// state, no effects. Same as what we do for Offscreen (except that |
|
// Offscreen doesn't have the first render pass). |
|
// |
|
// Second is hydration. During hydration, the Suspense fiber has a slightly |
|
// different layout, where the child points to a dehydrated fragment, which |
|
// contains the DOM rendered by the server. |
|
// |
|
// Third, even if you set all that aside, Suspense is like error boundaries in |
|
// that we first we try to render one tree, and if that fails, we render again |
|
// and switch to a different tree. Like a try/catch block. So we have to track |
|
// which branch we're currently rendering. Ideally we would model this using |
|
// a stack. |
|
|
|
if (current === null) { |
|
// Initial mount |
|
// Special path for hydration |
|
// If we're currently hydrating, try to hydrate this boundary. |
|
tryToClaimNextHydratableInstance(workInProgress); // This could've been a dehydrated suspense component. |
|
|
|
var suspenseState = workInProgress.memoizedState; |
|
|
|
if (suspenseState !== null) { |
|
var dehydrated = suspenseState.dehydrated; |
|
|
|
if (dehydrated !== null) { |
|
return mountDehydratedSuspenseComponent(workInProgress, dehydrated); |
|
} |
|
} |
|
|
|
var nextPrimaryChildren = nextProps.children; |
|
var nextFallbackChildren = nextProps.fallback; |
|
|
|
if (showFallback) { |
|
var fallbackFragment = mountSuspenseFallbackChildren(workInProgress, nextPrimaryChildren, nextFallbackChildren, renderLanes); |
|
var primaryChildFragment = workInProgress.child; |
|
primaryChildFragment.memoizedState = mountSuspenseOffscreenState(renderLanes); |
|
workInProgress.memoizedState = SUSPENDED_MARKER; |
|
|
|
return fallbackFragment; |
|
} else { |
|
return mountSuspensePrimaryChildren(workInProgress, nextPrimaryChildren); |
|
} |
|
} else { |
|
// This is an update. |
|
// Special path for hydration |
|
var prevState = current.memoizedState; |
|
|
|
if (prevState !== null) { |
|
var _dehydrated = prevState.dehydrated; |
|
|
|
if (_dehydrated !== null) { |
|
return updateDehydratedSuspenseComponent(current, workInProgress, didSuspend, nextProps, _dehydrated, prevState, renderLanes); |
|
} |
|
} |
|
|
|
if (showFallback) { |
|
var _nextFallbackChildren = nextProps.fallback; |
|
var _nextPrimaryChildren = nextProps.children; |
|
var fallbackChildFragment = updateSuspenseFallbackChildren(current, workInProgress, _nextPrimaryChildren, _nextFallbackChildren, renderLanes); |
|
var _primaryChildFragment2 = workInProgress.child; |
|
var prevOffscreenState = current.child.memoizedState; |
|
_primaryChildFragment2.memoizedState = prevOffscreenState === null ? mountSuspenseOffscreenState(renderLanes) : updateSuspenseOffscreenState(prevOffscreenState, renderLanes); |
|
|
|
_primaryChildFragment2.childLanes = getRemainingWorkInPrimaryTree(current, renderLanes); |
|
workInProgress.memoizedState = SUSPENDED_MARKER; |
|
return fallbackChildFragment; |
|
} else { |
|
var _nextPrimaryChildren2 = nextProps.children; |
|
|
|
var _primaryChildFragment3 = updateSuspensePrimaryChildren(current, workInProgress, _nextPrimaryChildren2, renderLanes); |
|
|
|
workInProgress.memoizedState = null; |
|
return _primaryChildFragment3; |
|
} |
|
} |
|
} |
|
|
|
function mountSuspensePrimaryChildren(workInProgress, primaryChildren, renderLanes) { |
|
var mode = workInProgress.mode; |
|
var primaryChildProps = { |
|
mode: 'visible', |
|
children: primaryChildren |
|
}; |
|
var primaryChildFragment = mountWorkInProgressOffscreenFiber(primaryChildProps, mode); |
|
primaryChildFragment.return = workInProgress; |
|
workInProgress.child = primaryChildFragment; |
|
return primaryChildFragment; |
|
} |
|
|
|
function mountSuspenseFallbackChildren(workInProgress, primaryChildren, fallbackChildren, renderLanes) { |
|
var mode = workInProgress.mode; |
|
var progressedPrimaryFragment = workInProgress.child; |
|
var primaryChildProps = { |
|
mode: 'hidden', |
|
children: primaryChildren |
|
}; |
|
var primaryChildFragment; |
|
var fallbackChildFragment; |
|
|
|
if ((mode & ConcurrentMode) === NoMode && progressedPrimaryFragment !== null) { |
|
// In legacy mode, we commit the primary tree as if it successfully |
|
// completed, even though it's in an inconsistent state. |
|
primaryChildFragment = progressedPrimaryFragment; |
|
primaryChildFragment.childLanes = NoLanes; |
|
primaryChildFragment.pendingProps = primaryChildProps; |
|
|
|
if ( workInProgress.mode & ProfileMode) { |
|
// Reset the durations from the first pass so they aren't included in the |
|
// final amounts. This seems counterintuitive, since we're intentionally |
|
// not measuring part of the render phase, but this makes it match what we |
|
// do in Concurrent Mode. |
|
primaryChildFragment.actualDuration = 0; |
|
primaryChildFragment.actualStartTime = -1; |
|
primaryChildFragment.selfBaseDuration = 0; |
|
primaryChildFragment.treeBaseDuration = 0; |
|
} |
|
|
|
fallbackChildFragment = createFiberFromFragment(fallbackChildren, mode, renderLanes, null); |
|
} else { |
|
primaryChildFragment = mountWorkInProgressOffscreenFiber(primaryChildProps, mode); |
|
fallbackChildFragment = createFiberFromFragment(fallbackChildren, mode, renderLanes, null); |
|
} |
|
|
|
primaryChildFragment.return = workInProgress; |
|
fallbackChildFragment.return = workInProgress; |
|
primaryChildFragment.sibling = fallbackChildFragment; |
|
workInProgress.child = primaryChildFragment; |
|
return fallbackChildFragment; |
|
} |
|
|
|
function mountWorkInProgressOffscreenFiber(offscreenProps, mode, renderLanes) { |
|
// The props argument to `createFiberFromOffscreen` is `any` typed, so we use |
|
// this wrapper function to constrain it. |
|
return createFiberFromOffscreen(offscreenProps, mode, NoLanes, null); |
|
} |
|
|
|
function updateWorkInProgressOffscreenFiber(current, offscreenProps) { |
|
// The props argument to `createWorkInProgress` is `any` typed, so we use this |
|
// wrapper function to constrain it. |
|
return createWorkInProgress(current, offscreenProps); |
|
} |
|
|
|
function updateSuspensePrimaryChildren(current, workInProgress, primaryChildren, renderLanes) { |
|
var currentPrimaryChildFragment = current.child; |
|
var currentFallbackChildFragment = currentPrimaryChildFragment.sibling; |
|
var primaryChildFragment = updateWorkInProgressOffscreenFiber(currentPrimaryChildFragment, { |
|
mode: 'visible', |
|
children: primaryChildren |
|
}); |
|
|
|
if ((workInProgress.mode & ConcurrentMode) === NoMode) { |
|
primaryChildFragment.lanes = renderLanes; |
|
} |
|
|
|
primaryChildFragment.return = workInProgress; |
|
primaryChildFragment.sibling = null; |
|
|
|
if (currentFallbackChildFragment !== null) { |
|
// Delete the fallback child fragment |
|
var deletions = workInProgress.deletions; |
|
|
|
if (deletions === null) { |
|
workInProgress.deletions = [currentFallbackChildFragment]; |
|
workInProgress.flags |= ChildDeletion; |
|
} else { |
|
deletions.push(currentFallbackChildFragment); |
|
} |
|
} |
|
|
|
workInProgress.child = primaryChildFragment; |
|
return primaryChildFragment; |
|
} |
|
|
|
function updateSuspenseFallbackChildren(current, workInProgress, primaryChildren, fallbackChildren, renderLanes) { |
|
var mode = workInProgress.mode; |
|
var currentPrimaryChildFragment = current.child; |
|
var currentFallbackChildFragment = currentPrimaryChildFragment.sibling; |
|
var primaryChildProps = { |
|
mode: 'hidden', |
|
children: primaryChildren |
|
}; |
|
var primaryChildFragment; |
|
|
|
if ( // In legacy mode, we commit the primary tree as if it successfully |
|
// completed, even though it's in an inconsistent state. |
|
(mode & ConcurrentMode) === NoMode && // Make sure we're on the second pass, i.e. the primary child fragment was |
|
// already cloned. In legacy mode, the only case where this isn't true is |
|
// when DevTools forces us to display a fallback; we skip the first render |
|
// pass entirely and go straight to rendering the fallback. (In Concurrent |
|
// Mode, SuspenseList can also trigger this scenario, but this is a legacy- |
|
// only codepath.) |
|
workInProgress.child !== currentPrimaryChildFragment) { |
|
var progressedPrimaryFragment = workInProgress.child; |
|
primaryChildFragment = progressedPrimaryFragment; |
|
primaryChildFragment.childLanes = NoLanes; |
|
primaryChildFragment.pendingProps = primaryChildProps; |
|
|
|
if ( workInProgress.mode & ProfileMode) { |
|
// Reset the durations from the first pass so they aren't included in the |
|
// final amounts. This seems counterintuitive, since we're intentionally |
|
// not measuring part of the render phase, but this makes it match what we |
|
// do in Concurrent Mode. |
|
primaryChildFragment.actualDuration = 0; |
|
primaryChildFragment.actualStartTime = -1; |
|
primaryChildFragment.selfBaseDuration = currentPrimaryChildFragment.selfBaseDuration; |
|
primaryChildFragment.treeBaseDuration = currentPrimaryChildFragment.treeBaseDuration; |
|
} // The fallback fiber was added as a deletion during the first pass. |
|
// However, since we're going to remain on the fallback, we no longer want |
|
// to delete it. |
|
|
|
|
|
workInProgress.deletions = null; |
|
} else { |
|
primaryChildFragment = updateWorkInProgressOffscreenFiber(currentPrimaryChildFragment, primaryChildProps); // Since we're reusing a current tree, we need to reuse the flags, too. |
|
// (We don't do this in legacy mode, because in legacy mode we don't re-use |
|
// the current tree; see previous branch.) |
|
|
|
primaryChildFragment.subtreeFlags = currentPrimaryChildFragment.subtreeFlags & StaticMask; |
|
} |
|
|
|
var fallbackChildFragment; |
|
|
|
if (currentFallbackChildFragment !== null) { |
|
fallbackChildFragment = createWorkInProgress(currentFallbackChildFragment, fallbackChildren); |
|
} else { |
|
fallbackChildFragment = createFiberFromFragment(fallbackChildren, mode, renderLanes, null); // Needs a placement effect because the parent (the Suspense boundary) already |
|
// mounted but this is a new fiber. |
|
|
|
fallbackChildFragment.flags |= Placement; |
|
} |
|
|
|
fallbackChildFragment.return = workInProgress; |
|
primaryChildFragment.return = workInProgress; |
|
primaryChildFragment.sibling = fallbackChildFragment; |
|
workInProgress.child = primaryChildFragment; |
|
return fallbackChildFragment; |
|
} |
|
|
|
function retrySuspenseComponentWithoutHydrating(current, workInProgress, renderLanes, recoverableError) { |
|
// Falling back to client rendering. Because this has performance |
|
// implications, it's considered a recoverable error, even though the user |
|
// likely won't observe anything wrong with the UI. |
|
// |
|
// The error is passed in as an argument to enforce that every caller provide |
|
// a custom message, or explicitly opt out (currently the only path that opts |
|
// out is legacy mode; every concurrent path provides an error). |
|
if (recoverableError !== null) { |
|
queueHydrationError(recoverableError); |
|
} // This will add the old fiber to the deletion list |
|
|
|
|
|
reconcileChildFibers(workInProgress, current.child, null, renderLanes); // We're now not suspended nor dehydrated. |
|
|
|
var nextProps = workInProgress.pendingProps; |
|
var primaryChildren = nextProps.children; |
|
var primaryChildFragment = mountSuspensePrimaryChildren(workInProgress, primaryChildren); // Needs a placement effect because the parent (the Suspense boundary) already |
|
// mounted but this is a new fiber. |
|
|
|
primaryChildFragment.flags |= Placement; |
|
workInProgress.memoizedState = null; |
|
return primaryChildFragment; |
|
} |
|
|
|
function mountSuspenseFallbackAfterRetryWithoutHydrating(current, workInProgress, primaryChildren, fallbackChildren, renderLanes) { |
|
var fiberMode = workInProgress.mode; |
|
var primaryChildProps = { |
|
mode: 'visible', |
|
children: primaryChildren |
|
}; |
|
var primaryChildFragment = mountWorkInProgressOffscreenFiber(primaryChildProps, fiberMode); |
|
var fallbackChildFragment = createFiberFromFragment(fallbackChildren, fiberMode, renderLanes, null); // Needs a placement effect because the parent (the Suspense |
|
// boundary) already mounted but this is a new fiber. |
|
|
|
fallbackChildFragment.flags |= Placement; |
|
primaryChildFragment.return = workInProgress; |
|
fallbackChildFragment.return = workInProgress; |
|
primaryChildFragment.sibling = fallbackChildFragment; |
|
workInProgress.child = primaryChildFragment; |
|
|
|
if ((workInProgress.mode & ConcurrentMode) !== NoMode) { |
|
// We will have dropped the effect list which contains the |
|
// deletion. We need to reconcile to delete the current child. |
|
reconcileChildFibers(workInProgress, current.child, null, renderLanes); |
|
} |
|
|
|
return fallbackChildFragment; |
|
} |
|
|
|
function mountDehydratedSuspenseComponent(workInProgress, suspenseInstance, renderLanes) { |
|
// During the first pass, we'll bail out and not drill into the children. |
|
// Instead, we'll leave the content in place and try to hydrate it later. |
|
if ((workInProgress.mode & ConcurrentMode) === NoMode) { |
|
{ |
|
error('Cannot hydrate Suspense in legacy mode. Switch from ' + 'ReactDOM.hydrate(element, container) to ' + 'ReactDOMClient.hydrateRoot(container, <App />)' + '.render(element) or remove the Suspense components from ' + 'the server rendered components.'); |
|
} |
|
|
|
workInProgress.lanes = laneToLanes(SyncLane); |
|
} else if (isSuspenseInstanceFallback(suspenseInstance)) { |
|
// This is a client-only boundary. Since we won't get any content from the server |
|
// for this, we need to schedule that at a higher priority based on when it would |
|
// have timed out. In theory we could render it in this pass but it would have the |
|
// wrong priority associated with it and will prevent hydration of parent path. |
|
// Instead, we'll leave work left on it to render it in a separate commit. |
|
// TODO This time should be the time at which the server rendered response that is |
|
// a parent to this boundary was displayed. However, since we currently don't have |
|
// a protocol to transfer that time, we'll just estimate it by using the current |
|
// time. This will mean that Suspense timeouts are slightly shifted to later than |
|
// they should be. |
|
// Schedule a normal pri update to render this content. |
|
workInProgress.lanes = laneToLanes(DefaultHydrationLane); |
|
} else { |
|
// We'll continue hydrating the rest at offscreen priority since we'll already |
|
// be showing the right content coming from the server, it is no rush. |
|
workInProgress.lanes = laneToLanes(OffscreenLane); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function updateDehydratedSuspenseComponent(current, workInProgress, didSuspend, nextProps, suspenseInstance, suspenseState, renderLanes) { |
|
if (!didSuspend) { |
|
// This is the first render pass. Attempt to hydrate. |
|
// We should never be hydrating at this point because it is the first pass, |
|
// but after we've already committed once. |
|
warnIfHydrating(); |
|
|
|
if ((workInProgress.mode & ConcurrentMode) === NoMode) { |
|
return retrySuspenseComponentWithoutHydrating(current, workInProgress, renderLanes, // TODO: When we delete legacy mode, we should make this error argument |
|
// required — every concurrent mode path that causes hydration to |
|
// de-opt to client rendering should have an error message. |
|
null); |
|
} |
|
|
|
if (isSuspenseInstanceFallback(suspenseInstance)) { |
|
// This boundary is in a permanent fallback state. In this case, we'll never |
|
// get an update and we'll never be able to hydrate the final content. Let's just try the |
|
// client side render instead. |
|
var digest, message, stack; |
|
|
|
{ |
|
var _getSuspenseInstanceF = getSuspenseInstanceFallbackErrorDetails(suspenseInstance); |
|
|
|
digest = _getSuspenseInstanceF.digest; |
|
message = _getSuspenseInstanceF.message; |
|
stack = _getSuspenseInstanceF.stack; |
|
} |
|
|
|
var error; |
|
|
|
if (message) { |
|
// eslint-disable-next-line react-internal/prod-error-codes |
|
error = new Error(message); |
|
} else { |
|
error = new Error('The server could not finish this Suspense boundary, likely ' + 'due to an error during server rendering. Switched to ' + 'client rendering.'); |
|
} |
|
|
|
var capturedValue = createCapturedValue(error, digest, stack); |
|
return retrySuspenseComponentWithoutHydrating(current, workInProgress, renderLanes, capturedValue); |
|
} |
|
// any context has changed, we need to treat is as if the input might have changed. |
|
|
|
|
|
var hasContextChanged = includesSomeLane(renderLanes, current.childLanes); |
|
|
|
if (didReceiveUpdate || hasContextChanged) { |
|
// This boundary has changed since the first render. This means that we are now unable to |
|
// hydrate it. We might still be able to hydrate it using a higher priority lane. |
|
var root = getWorkInProgressRoot(); |
|
|
|
if (root !== null) { |
|
var attemptHydrationAtLane = getBumpedLaneForHydration(root, renderLanes); |
|
|
|
if (attemptHydrationAtLane !== NoLane && attemptHydrationAtLane !== suspenseState.retryLane) { |
|
// Intentionally mutating since this render will get interrupted. This |
|
// is one of the very rare times where we mutate the current tree |
|
// during the render phase. |
|
suspenseState.retryLane = attemptHydrationAtLane; // TODO: Ideally this would inherit the event time of the current render |
|
|
|
var eventTime = NoTimestamp; |
|
enqueueConcurrentRenderForLane(current, attemptHydrationAtLane); |
|
scheduleUpdateOnFiber(root, current, attemptHydrationAtLane, eventTime); |
|
} |
|
} // If we have scheduled higher pri work above, this will probably just abort the render |
|
// since we now have higher priority work, but in case it doesn't, we need to prepare to |
|
// render something, if we time out. Even if that requires us to delete everything and |
|
// skip hydration. |
|
// Delay having to do this as long as the suspense timeout allows us. |
|
|
|
|
|
renderDidSuspendDelayIfPossible(); |
|
|
|
var _capturedValue = createCapturedValue(new Error('This Suspense boundary received an update before it finished ' + 'hydrating. This caused the boundary to switch to client rendering. ' + 'The usual way to fix this is to wrap the original update ' + 'in startTransition.')); |
|
|
|
return retrySuspenseComponentWithoutHydrating(current, workInProgress, renderLanes, _capturedValue); |
|
} else if (isSuspenseInstancePending(suspenseInstance)) { |
|
// This component is still pending more data from the server, so we can't hydrate its |
|
// content. We treat it as if this component suspended itself. It might seem as if |
|
// we could just try to render it client-side instead. However, this will perform a |
|
// lot of unnecessary work and is unlikely to complete since it often will suspend |
|
// on missing data anyway. Additionally, the server might be able to render more |
|
// than we can on the client yet. In that case we'd end up with more fallback states |
|
// on the client than if we just leave it alone. If the server times out or errors |
|
// these should update this boundary to the permanent Fallback state instead. |
|
// Mark it as having captured (i.e. suspended). |
|
workInProgress.flags |= DidCapture; // Leave the child in place. I.e. the dehydrated fragment. |
|
|
|
workInProgress.child = current.child; // Register a callback to retry this boundary once the server has sent the result. |
|
|
|
var retry = retryDehydratedSuspenseBoundary.bind(null, current); |
|
registerSuspenseInstanceRetry(suspenseInstance, retry); |
|
return null; |
|
} else { |
|
// This is the first attempt. |
|
reenterHydrationStateFromDehydratedSuspenseInstance(workInProgress, suspenseInstance, suspenseState.treeContext); |
|
var primaryChildren = nextProps.children; |
|
var primaryChildFragment = mountSuspensePrimaryChildren(workInProgress, primaryChildren); // Mark the children as hydrating. This is a fast path to know whether this |
|
// tree is part of a hydrating tree. This is used to determine if a child |
|
// node has fully mounted yet, and for scheduling event replaying. |
|
// Conceptually this is similar to Placement in that a new subtree is |
|
// inserted into the React tree here. It just happens to not need DOM |
|
// mutations because it already exists. |
|
|
|
primaryChildFragment.flags |= Hydrating; |
|
return primaryChildFragment; |
|
} |
|
} else { |
|
// This is the second render pass. We already attempted to hydrated, but |
|
// something either suspended or errored. |
|
if (workInProgress.flags & ForceClientRender) { |
|
// Something errored during hydration. Try again without hydrating. |
|
workInProgress.flags &= ~ForceClientRender; |
|
|
|
var _capturedValue2 = createCapturedValue(new Error('There was an error while hydrating this Suspense boundary. ' + 'Switched to client rendering.')); |
|
|
|
return retrySuspenseComponentWithoutHydrating(current, workInProgress, renderLanes, _capturedValue2); |
|
} else if (workInProgress.memoizedState !== null) { |
|
// Something suspended and we should still be in dehydrated mode. |
|
// Leave the existing child in place. |
|
workInProgress.child = current.child; // The dehydrated completion pass expects this flag to be there |
|
// but the normal suspense pass doesn't. |
|
|
|
workInProgress.flags |= DidCapture; |
|
return null; |
|
} else { |
|
// Suspended but we should no longer be in dehydrated mode. |
|
// Therefore we now have to render the fallback. |
|
var nextPrimaryChildren = nextProps.children; |
|
var nextFallbackChildren = nextProps.fallback; |
|
var fallbackChildFragment = mountSuspenseFallbackAfterRetryWithoutHydrating(current, workInProgress, nextPrimaryChildren, nextFallbackChildren, renderLanes); |
|
var _primaryChildFragment4 = workInProgress.child; |
|
_primaryChildFragment4.memoizedState = mountSuspenseOffscreenState(renderLanes); |
|
workInProgress.memoizedState = SUSPENDED_MARKER; |
|
return fallbackChildFragment; |
|
} |
|
} |
|
} |
|
|
|
function scheduleSuspenseWorkOnFiber(fiber, renderLanes, propagationRoot) { |
|
fiber.lanes = mergeLanes(fiber.lanes, renderLanes); |
|
var alternate = fiber.alternate; |
|
|
|
if (alternate !== null) { |
|
alternate.lanes = mergeLanes(alternate.lanes, renderLanes); |
|
} |
|
|
|
scheduleContextWorkOnParentPath(fiber.return, renderLanes, propagationRoot); |
|
} |
|
|
|
function propagateSuspenseContextChange(workInProgress, firstChild, renderLanes) { |
|
// Mark any Suspense boundaries with fallbacks as having work to do. |
|
// If they were previously forced into fallbacks, they may now be able |
|
// to unblock. |
|
var node = firstChild; |
|
|
|
while (node !== null) { |
|
if (node.tag === SuspenseComponent) { |
|
var state = node.memoizedState; |
|
|
|
if (state !== null) { |
|
scheduleSuspenseWorkOnFiber(node, renderLanes, workInProgress); |
|
} |
|
} else if (node.tag === SuspenseListComponent) { |
|
// If the tail is hidden there might not be an Suspense boundaries |
|
// to schedule work on. In this case we have to schedule it on the |
|
// list itself. |
|
// We don't have to traverse to the children of the list since |
|
// the list will propagate the change when it rerenders. |
|
scheduleSuspenseWorkOnFiber(node, renderLanes, workInProgress); |
|
} else if (node.child !== null) { |
|
node.child.return = node; |
|
node = node.child; |
|
continue; |
|
} |
|
|
|
if (node === workInProgress) { |
|
return; |
|
} |
|
|
|
while (node.sibling === null) { |
|
if (node.return === null || node.return === workInProgress) { |
|
return; |
|
} |
|
|
|
node = node.return; |
|
} |
|
|
|
node.sibling.return = node.return; |
|
node = node.sibling; |
|
} |
|
} |
|
|
|
function findLastContentRow(firstChild) { |
|
// This is going to find the last row among these children that is already |
|
// showing content on the screen, as opposed to being in fallback state or |
|
// new. If a row has multiple Suspense boundaries, any of them being in the |
|
// fallback state, counts as the whole row being in a fallback state. |
|
// Note that the "rows" will be workInProgress, but any nested children |
|
// will still be current since we haven't rendered them yet. The mounted |
|
// order may not be the same as the new order. We use the new order. |
|
var row = firstChild; |
|
var lastContentRow = null; |
|
|
|
while (row !== null) { |
|
var currentRow = row.alternate; // New rows can't be content rows. |
|
|
|
if (currentRow !== null && findFirstSuspended(currentRow) === null) { |
|
lastContentRow = row; |
|
} |
|
|
|
row = row.sibling; |
|
} |
|
|
|
return lastContentRow; |
|
} |
|
|
|
function validateRevealOrder(revealOrder) { |
|
{ |
|
if (revealOrder !== undefined && revealOrder !== 'forwards' && revealOrder !== 'backwards' && revealOrder !== 'together' && !didWarnAboutRevealOrder[revealOrder]) { |
|
didWarnAboutRevealOrder[revealOrder] = true; |
|
|
|
if (typeof revealOrder === 'string') { |
|
switch (revealOrder.toLowerCase()) { |
|
case 'together': |
|
case 'forwards': |
|
case 'backwards': |
|
{ |
|
error('"%s" is not a valid value for revealOrder on <SuspenseList />. ' + 'Use lowercase "%s" instead.', revealOrder, revealOrder.toLowerCase()); |
|
|
|
break; |
|
} |
|
|
|
case 'forward': |
|
case 'backward': |
|
{ |
|
error('"%s" is not a valid value for revealOrder on <SuspenseList />. ' + 'React uses the -s suffix in the spelling. Use "%ss" instead.', revealOrder, revealOrder.toLowerCase()); |
|
|
|
break; |
|
} |
|
|
|
default: |
|
error('"%s" is not a supported revealOrder on <SuspenseList />. ' + 'Did you mean "together", "forwards" or "backwards"?', revealOrder); |
|
|
|
break; |
|
} |
|
} else { |
|
error('%s is not a supported value for revealOrder on <SuspenseList />. ' + 'Did you mean "together", "forwards" or "backwards"?', revealOrder); |
|
} |
|
} |
|
} |
|
} |
|
|
|
function validateTailOptions(tailMode, revealOrder) { |
|
{ |
|
if (tailMode !== undefined && !didWarnAboutTailOptions[tailMode]) { |
|
if (tailMode !== 'collapsed' && tailMode !== 'hidden') { |
|
didWarnAboutTailOptions[tailMode] = true; |
|
|
|
error('"%s" is not a supported value for tail on <SuspenseList />. ' + 'Did you mean "collapsed" or "hidden"?', tailMode); |
|
} else if (revealOrder !== 'forwards' && revealOrder !== 'backwards') { |
|
didWarnAboutTailOptions[tailMode] = true; |
|
|
|
error('<SuspenseList tail="%s" /> is only valid if revealOrder is ' + '"forwards" or "backwards". ' + 'Did you mean to specify revealOrder="forwards"?', tailMode); |
|
} |
|
} |
|
} |
|
} |
|
|
|
function validateSuspenseListNestedChild(childSlot, index) { |
|
{ |
|
var isAnArray = isArray(childSlot); |
|
var isIterable = !isAnArray && typeof getIteratorFn(childSlot) === 'function'; |
|
|
|
if (isAnArray || isIterable) { |
|
var type = isAnArray ? 'array' : 'iterable'; |
|
|
|
error('A nested %s was passed to row #%s in <SuspenseList />. Wrap it in ' + 'an additional SuspenseList to configure its revealOrder: ' + '<SuspenseList revealOrder=...> ... ' + '<SuspenseList revealOrder=...>{%s}</SuspenseList> ... ' + '</SuspenseList>', type, index, type); |
|
|
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function validateSuspenseListChildren(children, revealOrder) { |
|
{ |
|
if ((revealOrder === 'forwards' || revealOrder === 'backwards') && children !== undefined && children !== null && children !== false) { |
|
if (isArray(children)) { |
|
for (var i = 0; i < children.length; i++) { |
|
if (!validateSuspenseListNestedChild(children[i], i)) { |
|
return; |
|
} |
|
} |
|
} else { |
|
var iteratorFn = getIteratorFn(children); |
|
|
|
if (typeof iteratorFn === 'function') { |
|
var childrenIterator = iteratorFn.call(children); |
|
|
|
if (childrenIterator) { |
|
var step = childrenIterator.next(); |
|
var _i = 0; |
|
|
|
for (; !step.done; step = childrenIterator.next()) { |
|
if (!validateSuspenseListNestedChild(step.value, _i)) { |
|
return; |
|
} |
|
|
|
_i++; |
|
} |
|
} |
|
} else { |
|
error('A single row was passed to a <SuspenseList revealOrder="%s" />. ' + 'This is not useful since it needs multiple rows. ' + 'Did you mean to pass multiple children or an array?', revealOrder); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
function initSuspenseListRenderState(workInProgress, isBackwards, tail, lastContentRow, tailMode) { |
|
var renderState = workInProgress.memoizedState; |
|
|
|
if (renderState === null) { |
|
workInProgress.memoizedState = { |
|
isBackwards: isBackwards, |
|
rendering: null, |
|
renderingStartTime: 0, |
|
last: lastContentRow, |
|
tail: tail, |
|
tailMode: tailMode |
|
}; |
|
} else { |
|
// We can reuse the existing object from previous renders. |
|
renderState.isBackwards = isBackwards; |
|
renderState.rendering = null; |
|
renderState.renderingStartTime = 0; |
|
renderState.last = lastContentRow; |
|
renderState.tail = tail; |
|
renderState.tailMode = tailMode; |
|
} |
|
} // This can end up rendering this component multiple passes. |
|
// The first pass splits the children fibers into two sets. A head and tail. |
|
// We first render the head. If anything is in fallback state, we do another |
|
// pass through beginWork to rerender all children (including the tail) with |
|
// the force suspend context. If the first render didn't have anything in |
|
// in fallback state. Then we render each row in the tail one-by-one. |
|
// That happens in the completeWork phase without going back to beginWork. |
|
|
|
|
|
function updateSuspenseListComponent(current, workInProgress, renderLanes) { |
|
var nextProps = workInProgress.pendingProps; |
|
var revealOrder = nextProps.revealOrder; |
|
var tailMode = nextProps.tail; |
|
var newChildren = nextProps.children; |
|
validateRevealOrder(revealOrder); |
|
validateTailOptions(tailMode, revealOrder); |
|
validateSuspenseListChildren(newChildren, revealOrder); |
|
reconcileChildren(current, workInProgress, newChildren, renderLanes); |
|
var suspenseContext = suspenseStackCursor.current; |
|
var shouldForceFallback = hasSuspenseContext(suspenseContext, ForceSuspenseFallback); |
|
|
|
if (shouldForceFallback) { |
|
suspenseContext = setShallowSuspenseContext(suspenseContext, ForceSuspenseFallback); |
|
workInProgress.flags |= DidCapture; |
|
} else { |
|
var didSuspendBefore = current !== null && (current.flags & DidCapture) !== NoFlags; |
|
|
|
if (didSuspendBefore) { |
|
// If we previously forced a fallback, we need to schedule work |
|
// on any nested boundaries to let them know to try to render |
|
// again. This is the same as context updating. |
|
propagateSuspenseContextChange(workInProgress, workInProgress.child, renderLanes); |
|
} |
|
|
|
suspenseContext = setDefaultShallowSuspenseContext(suspenseContext); |
|
} |
|
|
|
pushSuspenseContext(workInProgress, suspenseContext); |
|
|
|
if ((workInProgress.mode & ConcurrentMode) === NoMode) { |
|
// In legacy mode, SuspenseList doesn't work so we just |
|
// use make it a noop by treating it as the default revealOrder. |
|
workInProgress.memoizedState = null; |
|
} else { |
|
switch (revealOrder) { |
|
case 'forwards': |
|
{ |
|
var lastContentRow = findLastContentRow(workInProgress.child); |
|
var tail; |
|
|
|
if (lastContentRow === null) { |
|
// The whole list is part of the tail. |
|
// TODO: We could fast path by just rendering the tail now. |
|
tail = workInProgress.child; |
|
workInProgress.child = null; |
|
} else { |
|
// Disconnect the tail rows after the content row. |
|
// We're going to render them separately later. |
|
tail = lastContentRow.sibling; |
|
lastContentRow.sibling = null; |
|
} |
|
|
|
initSuspenseListRenderState(workInProgress, false, // isBackwards |
|
tail, lastContentRow, tailMode); |
|
break; |
|
} |
|
|
|
case 'backwards': |
|
{ |
|
// We're going to find the first row that has existing content. |
|
// At the same time we're going to reverse the list of everything |
|
// we pass in the meantime. That's going to be our tail in reverse |
|
// order. |
|
var _tail = null; |
|
var row = workInProgress.child; |
|
workInProgress.child = null; |
|
|
|
while (row !== null) { |
|
var currentRow = row.alternate; // New rows can't be content rows. |
|
|
|
if (currentRow !== null && findFirstSuspended(currentRow) === null) { |
|
// This is the beginning of the main content. |
|
workInProgress.child = row; |
|
break; |
|
} |
|
|
|
var nextRow = row.sibling; |
|
row.sibling = _tail; |
|
_tail = row; |
|
row = nextRow; |
|
} // TODO: If workInProgress.child is null, we can continue on the tail immediately. |
|
|
|
|
|
initSuspenseListRenderState(workInProgress, true, // isBackwards |
|
_tail, null, // last |
|
tailMode); |
|
break; |
|
} |
|
|
|
case 'together': |
|
{ |
|
initSuspenseListRenderState(workInProgress, false, // isBackwards |
|
null, // tail |
|
null, // last |
|
undefined); |
|
break; |
|
} |
|
|
|
default: |
|
{ |
|
// The default reveal order is the same as not having |
|
// a boundary. |
|
workInProgress.memoizedState = null; |
|
} |
|
} |
|
} |
|
|
|
return workInProgress.child; |
|
} |
|
|
|
function updatePortalComponent(current, workInProgress, renderLanes) { |
|
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo); |
|
var nextChildren = workInProgress.pendingProps; |
|
|
|
if (current === null) { |
|
// Portals are special because we don't append the children during mount |
|
// but at commit. Therefore we need to track insertions which the normal |
|
// flow doesn't do during mount. This doesn't happen at the root because |
|
// the root always starts with a "current" with a null child. |
|
// TODO: Consider unifying this with how the root works. |
|
workInProgress.child = reconcileChildFibers(workInProgress, null, nextChildren, renderLanes); |
|
} else { |
|
reconcileChildren(current, workInProgress, nextChildren, renderLanes); |
|
} |
|
|
|
return workInProgress.child; |
|
} |
|
|
|
var hasWarnedAboutUsingNoValuePropOnContextProvider = false; |
|
|
|
function updateContextProvider(current, workInProgress, renderLanes) { |
|
var providerType = workInProgress.type; |
|
var context = providerType._context; |
|
var newProps = workInProgress.pendingProps; |
|
var oldProps = workInProgress.memoizedProps; |
|
var newValue = newProps.value; |
|
|
|
{ |
|
if (!('value' in newProps)) { |
|
if (!hasWarnedAboutUsingNoValuePropOnContextProvider) { |
|
hasWarnedAboutUsingNoValuePropOnContextProvider = true; |
|
|
|
error('The `value` prop is required for the `<Context.Provider>`. Did you misspell it or forget to pass it?'); |
|
} |
|
} |
|
|
|
var providerPropTypes = workInProgress.type.propTypes; |
|
|
|
if (providerPropTypes) { |
|
checkPropTypes(providerPropTypes, newProps, 'prop', 'Context.Provider'); |
|
} |
|
} |
|
|
|
pushProvider(workInProgress, context, newValue); |
|
|
|
{ |
|
if (oldProps !== null) { |
|
var oldValue = oldProps.value; |
|
|
|
if (objectIs(oldValue, newValue)) { |
|
// No change. Bailout early if children are the same. |
|
if (oldProps.children === newProps.children && !hasContextChanged()) { |
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); |
|
} |
|
} else { |
|
// The context value changed. Search for matching consumers and schedule |
|
// them to update. |
|
propagateContextChange(workInProgress, context, renderLanes); |
|
} |
|
} |
|
} |
|
|
|
var newChildren = newProps.children; |
|
reconcileChildren(current, workInProgress, newChildren, renderLanes); |
|
return workInProgress.child; |
|
} |
|
|
|
var hasWarnedAboutUsingContextAsConsumer = false; |
|
|
|
function updateContextConsumer(current, workInProgress, renderLanes) { |
|
var context = workInProgress.type; // The logic below for Context differs depending on PROD or DEV mode. In |
|
// DEV mode, we create a separate object for Context.Consumer that acts |
|
// like a proxy to Context. This proxy object adds unnecessary code in PROD |
|
// so we use the old behaviour (Context.Consumer references Context) to |
|
// reduce size and overhead. The separate object references context via |
|
// a property called "_context", which also gives us the ability to check |
|
// in DEV mode if this property exists or not and warn if it does not. |
|
|
|
{ |
|
if (context._context === undefined) { |
|
// This may be because it's a Context (rather than a Consumer). |
|
// Or it may be because it's older React where they're the same thing. |
|
// We only want to warn if we're sure it's a new React. |
|
if (context !== context.Consumer) { |
|
if (!hasWarnedAboutUsingContextAsConsumer) { |
|
hasWarnedAboutUsingContextAsConsumer = true; |
|
|
|
error('Rendering <Context> directly is not supported and will be removed in ' + 'a future major release. Did you mean to render <Context.Consumer> instead?'); |
|
} |
|
} |
|
} else { |
|
context = context._context; |
|
} |
|
} |
|
|
|
var newProps = workInProgress.pendingProps; |
|
var render = newProps.children; |
|
|
|
{ |
|
if (typeof render !== 'function') { |
|
error('A context consumer was rendered with multiple children, or a child ' + "that isn't a function. A context consumer expects a single child " + 'that is a function. If you did pass a function, make sure there ' + 'is no trailing or leading whitespace around it.'); |
|
} |
|
} |
|
|
|
prepareToReadContext(workInProgress, renderLanes); |
|
var newValue = readContext(context); |
|
|
|
{ |
|
markComponentRenderStarted(workInProgress); |
|
} |
|
|
|
var newChildren; |
|
|
|
{ |
|
ReactCurrentOwner$1.current = workInProgress; |
|
setIsRendering(true); |
|
newChildren = render(newValue); |
|
setIsRendering(false); |
|
} |
|
|
|
{ |
|
markComponentRenderStopped(); |
|
} // React DevTools reads this flag. |
|
|
|
|
|
workInProgress.flags |= PerformedWork; |
|
reconcileChildren(current, workInProgress, newChildren, renderLanes); |
|
return workInProgress.child; |
|
} |
|
|
|
function markWorkInProgressReceivedUpdate() { |
|
didReceiveUpdate = true; |
|
} |
|
|
|
function resetSuspendedCurrentOnMountInLegacyMode(current, workInProgress) { |
|
if ((workInProgress.mode & ConcurrentMode) === NoMode) { |
|
if (current !== null) { |
|
// A lazy component only mounts if it suspended inside a non- |
|
// concurrent tree, in an inconsistent state. We want to treat it like |
|
// a new mount, even though an empty version of it already committed. |
|
// Disconnect the alternate pointers. |
|
current.alternate = null; |
|
workInProgress.alternate = null; // Since this is conceptually a new fiber, schedule a Placement effect |
|
|
|
workInProgress.flags |= Placement; |
|
} |
|
} |
|
} |
|
|
|
function bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes) { |
|
if (current !== null) { |
|
// Reuse previous dependencies |
|
workInProgress.dependencies = current.dependencies; |
|
} |
|
|
|
{ |
|
// Don't update "base" render times for bailouts. |
|
stopProfilerTimerIfRunning(); |
|
} |
|
|
|
markSkippedUpdateLanes(workInProgress.lanes); // Check if the children have any pending work. |
|
|
|
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) { |
|
// The children don't have any work either. We can skip them. |
|
// TODO: Once we add back resuming, we should check if the children are |
|
// a work-in-progress set. If so, we need to transfer their effects. |
|
{ |
|
return null; |
|
} |
|
} // This fiber doesn't have work, but its subtree does. Clone the child |
|
// fibers and continue. |
|
|
|
|
|
cloneChildFibers(current, workInProgress); |
|
return workInProgress.child; |
|
} |
|
|
|
function remountFiber(current, oldWorkInProgress, newWorkInProgress) { |
|
{ |
|
var returnFiber = oldWorkInProgress.return; |
|
|
|
if (returnFiber === null) { |
|
// eslint-disable-next-line react-internal/prod-error-codes |
|
throw new Error('Cannot swap the root fiber.'); |
|
} // Disconnect from the old current. |
|
// It will get deleted. |
|
|
|
|
|
current.alternate = null; |
|
oldWorkInProgress.alternate = null; // Connect to the new tree. |
|
|
|
newWorkInProgress.index = oldWorkInProgress.index; |
|
newWorkInProgress.sibling = oldWorkInProgress.sibling; |
|
newWorkInProgress.return = oldWorkInProgress.return; |
|
newWorkInProgress.ref = oldWorkInProgress.ref; // Replace the child/sibling pointers above it. |
|
|
|
if (oldWorkInProgress === returnFiber.child) { |
|
returnFiber.child = newWorkInProgress; |
|
} else { |
|
var prevSibling = returnFiber.child; |
|
|
|
if (prevSibling === null) { |
|
// eslint-disable-next-line react-internal/prod-error-codes |
|
throw new Error('Expected parent to have a child.'); |
|
} |
|
|
|
while (prevSibling.sibling !== oldWorkInProgress) { |
|
prevSibling = prevSibling.sibling; |
|
|
|
if (prevSibling === null) { |
|
// eslint-disable-next-line react-internal/prod-error-codes |
|
throw new Error('Expected to find the previous sibling.'); |
|
} |
|
} |
|
|
|
prevSibling.sibling = newWorkInProgress; |
|
} // Delete the old fiber and place the new one. |
|
// Since the old fiber is disconnected, we have to schedule it manually. |
|
|
|
|
|
var deletions = returnFiber.deletions; |
|
|
|
if (deletions === null) { |
|
returnFiber.deletions = [current]; |
|
returnFiber.flags |= ChildDeletion; |
|
} else { |
|
deletions.push(current); |
|
} |
|
|
|
newWorkInProgress.flags |= Placement; // Restart work from the new fiber. |
|
|
|
return newWorkInProgress; |
|
} |
|
} |
|
|
|
function checkScheduledUpdateOrContext(current, renderLanes) { |
|
// Before performing an early bailout, we must check if there are pending |
|
// updates or context. |
|
var updateLanes = current.lanes; |
|
|
|
if (includesSomeLane(updateLanes, renderLanes)) { |
|
return true; |
|
} // No pending update, but because context is propagated lazily, we need |
|
|
|
return false; |
|
} |
|
|
|
function attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes) { |
|
// This fiber does not have any pending work. Bailout without entering |
|
// the begin phase. There's still some bookkeeping we that needs to be done |
|
// in this optimized path, mostly pushing stuff onto the stack. |
|
switch (workInProgress.tag) { |
|
case HostRoot: |
|
pushHostRootContext(workInProgress); |
|
var root = workInProgress.stateNode; |
|
|
|
resetHydrationState(); |
|
break; |
|
|
|
case HostComponent: |
|
pushHostContext(workInProgress); |
|
break; |
|
|
|
case ClassComponent: |
|
{ |
|
var Component = workInProgress.type; |
|
|
|
if (isContextProvider(Component)) { |
|
pushContextProvider(workInProgress); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case HostPortal: |
|
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo); |
|
break; |
|
|
|
case ContextProvider: |
|
{ |
|
var newValue = workInProgress.memoizedProps.value; |
|
var context = workInProgress.type._context; |
|
pushProvider(workInProgress, context, newValue); |
|
break; |
|
} |
|
|
|
case Profiler: |
|
{ |
|
// Profiler should only call onRender when one of its descendants actually rendered. |
|
var hasChildWork = includesSomeLane(renderLanes, workInProgress.childLanes); |
|
|
|
if (hasChildWork) { |
|
workInProgress.flags |= Update; |
|
} |
|
|
|
{ |
|
// Reset effect durations for the next eventual effect phase. |
|
// These are reset during render to allow the DevTools commit hook a chance to read them, |
|
var stateNode = workInProgress.stateNode; |
|
stateNode.effectDuration = 0; |
|
stateNode.passiveEffectDuration = 0; |
|
} |
|
} |
|
|
|
break; |
|
|
|
case SuspenseComponent: |
|
{ |
|
var state = workInProgress.memoizedState; |
|
|
|
if (state !== null) { |
|
if (state.dehydrated !== null) { |
|
pushSuspenseContext(workInProgress, setDefaultShallowSuspenseContext(suspenseStackCursor.current)); // We know that this component will suspend again because if it has |
|
// been unsuspended it has committed as a resolved Suspense component. |
|
// If it needs to be retried, it should have work scheduled on it. |
|
|
|
workInProgress.flags |= DidCapture; // We should never render the children of a dehydrated boundary until we |
|
// upgrade it. We return null instead of bailoutOnAlreadyFinishedWork. |
|
|
|
return null; |
|
} // If this boundary is currently timed out, we need to decide |
|
// whether to retry the primary children, or to skip over it and |
|
// go straight to the fallback. Check the priority of the primary |
|
// child fragment. |
|
|
|
|
|
var primaryChildFragment = workInProgress.child; |
|
var primaryChildLanes = primaryChildFragment.childLanes; |
|
|
|
if (includesSomeLane(renderLanes, primaryChildLanes)) { |
|
// The primary children have pending work. Use the normal path |
|
// to attempt to render the primary children again. |
|
return updateSuspenseComponent(current, workInProgress, renderLanes); |
|
} else { |
|
// The primary child fragment does not have pending work marked |
|
// on it |
|
pushSuspenseContext(workInProgress, setDefaultShallowSuspenseContext(suspenseStackCursor.current)); // The primary children do not have pending work with sufficient |
|
// priority. Bailout. |
|
|
|
var child = bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); |
|
|
|
if (child !== null) { |
|
// The fallback children have pending work. Skip over the |
|
// primary children and work on the fallback. |
|
return child.sibling; |
|
} else { |
|
// Note: We can return `null` here because we already checked |
|
// whether there were nested context consumers, via the call to |
|
// `bailoutOnAlreadyFinishedWork` above. |
|
return null; |
|
} |
|
} |
|
} else { |
|
pushSuspenseContext(workInProgress, setDefaultShallowSuspenseContext(suspenseStackCursor.current)); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case SuspenseListComponent: |
|
{ |
|
var didSuspendBefore = (current.flags & DidCapture) !== NoFlags; |
|
|
|
var _hasChildWork = includesSomeLane(renderLanes, workInProgress.childLanes); |
|
|
|
if (didSuspendBefore) { |
|
if (_hasChildWork) { |
|
// If something was in fallback state last time, and we have all the |
|
// same children then we're still in progressive loading state. |
|
// Something might get unblocked by state updates or retries in the |
|
// tree which will affect the tail. So we need to use the normal |
|
// path to compute the correct tail. |
|
return updateSuspenseListComponent(current, workInProgress, renderLanes); |
|
} // If none of the children had any work, that means that none of |
|
// them got retried so they'll still be blocked in the same way |
|
// as before. We can fast bail out. |
|
|
|
|
|
workInProgress.flags |= DidCapture; |
|
} // If nothing suspended before and we're rendering the same children, |
|
// then the tail doesn't matter. Anything new that suspends will work |
|
// in the "together" mode, so we can continue from the state we had. |
|
|
|
|
|
var renderState = workInProgress.memoizedState; |
|
|
|
if (renderState !== null) { |
|
// Reset to the "together" mode in case we've started a different |
|
// update in the past but didn't complete it. |
|
renderState.rendering = null; |
|
renderState.tail = null; |
|
renderState.lastEffect = null; |
|
} |
|
|
|
pushSuspenseContext(workInProgress, suspenseStackCursor.current); |
|
|
|
if (_hasChildWork) { |
|
break; |
|
} else { |
|
// If none of the children had any work, that means that none of |
|
// them got retried so they'll still be blocked in the same way |
|
// as before. We can fast bail out. |
|
return null; |
|
} |
|
} |
|
|
|
case OffscreenComponent: |
|
case LegacyHiddenComponent: |
|
{ |
|
// Need to check if the tree still needs to be deferred. This is |
|
// almost identical to the logic used in the normal update path, |
|
// so we'll just enter that. The only difference is we'll bail out |
|
// at the next level instead of this one, because the child props |
|
// have not changed. Which is fine. |
|
// TODO: Probably should refactor `beginWork` to split the bailout |
|
// path from the normal path. I'm tempted to do a labeled break here |
|
// but I won't :) |
|
workInProgress.lanes = NoLanes; |
|
return updateOffscreenComponent(current, workInProgress, renderLanes); |
|
} |
|
} |
|
|
|
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes); |
|
} |
|
|
|
function beginWork(current, workInProgress, renderLanes) { |
|
{ |
|
if (workInProgress._debugNeedsRemount && current !== null) { |
|
// This will restart the begin phase with a new fiber. |
|
return remountFiber(current, workInProgress, createFiberFromTypeAndProps(workInProgress.type, workInProgress.key, workInProgress.pendingProps, workInProgress._debugOwner || null, workInProgress.mode, workInProgress.lanes)); |
|
} |
|
} |
|
|
|
if (current !== null) { |
|
var oldProps = current.memoizedProps; |
|
var newProps = workInProgress.pendingProps; |
|
|
|
if (oldProps !== newProps || hasContextChanged() || ( // Force a re-render if the implementation changed due to hot reload: |
|
workInProgress.type !== current.type )) { |
|
// If props or context changed, mark the fiber as having performed work. |
|
// This may be unset if the props are determined to be equal later (memo). |
|
didReceiveUpdate = true; |
|
} else { |
|
// Neither props nor legacy context changes. Check if there's a pending |
|
// update or context change. |
|
var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes); |
|
|
|
if (!hasScheduledUpdateOrContext && // If this is the second pass of an error or suspense boundary, there |
|
// may not be work scheduled on `current`, so we check for this flag. |
|
(workInProgress.flags & DidCapture) === NoFlags) { |
|
// No pending updates or context. Bail out now. |
|
didReceiveUpdate = false; |
|
return attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes); |
|
} |
|
|
|
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) { |
|
// This is a special case that only exists for legacy mode. |
|
// See https://github.com/facebook/react/pull/19216. |
|
didReceiveUpdate = true; |
|
} else { |
|
// An update was scheduled on this fiber, but there are no new props |
|
// nor legacy context. Set this to false. If an update queue or context |
|
// consumer produces a changed value, it will set this to true. Otherwise, |
|
// the component will assume the children have not changed and bail out. |
|
didReceiveUpdate = false; |
|
} |
|
} |
|
} else { |
|
didReceiveUpdate = false; |
|
|
|
if (getIsHydrating() && isForkedChild(workInProgress)) { |
|
// Check if this child belongs to a list of muliple children in |
|
// its parent. |
|
// |
|
// In a true multi-threaded implementation, we would render children on |
|
// parallel threads. This would represent the beginning of a new render |
|
// thread for this subtree. |
|
// |
|
// We only use this for id generation during hydration, which is why the |
|
// logic is located in this special branch. |
|
var slotIndex = workInProgress.index; |
|
var numberOfForks = getForksAtLevel(); |
|
pushTreeId(workInProgress, numberOfForks, slotIndex); |
|
} |
|
} // Before entering the begin phase, clear pending update priority. |
|
// TODO: This assumes that we're about to evaluate the component and process |
|
// the update queue. However, there's an exception: SimpleMemoComponent |
|
// sometimes bails out later in the begin phase. This indicates that we should |
|
// move this assignment out of the common path and into each branch. |
|
|
|
|
|
workInProgress.lanes = NoLanes; |
|
|
|
switch (workInProgress.tag) { |
|
case IndeterminateComponent: |
|
{ |
|
return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes); |
|
} |
|
|
|
case LazyComponent: |
|
{ |
|
var elementType = workInProgress.elementType; |
|
return mountLazyComponent(current, workInProgress, elementType, renderLanes); |
|
} |
|
|
|
case FunctionComponent: |
|
{ |
|
var Component = workInProgress.type; |
|
var unresolvedProps = workInProgress.pendingProps; |
|
var resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); |
|
return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderLanes); |
|
} |
|
|
|
case ClassComponent: |
|
{ |
|
var _Component = workInProgress.type; |
|
var _unresolvedProps = workInProgress.pendingProps; |
|
|
|
var _resolvedProps = workInProgress.elementType === _Component ? _unresolvedProps : resolveDefaultProps(_Component, _unresolvedProps); |
|
|
|
return updateClassComponent(current, workInProgress, _Component, _resolvedProps, renderLanes); |
|
} |
|
|
|
case HostRoot: |
|
return updateHostRoot(current, workInProgress, renderLanes); |
|
|
|
case HostComponent: |
|
return updateHostComponent(current, workInProgress, renderLanes); |
|
|
|
case HostText: |
|
return updateHostText(current, workInProgress); |
|
|
|
case SuspenseComponent: |
|
return updateSuspenseComponent(current, workInProgress, renderLanes); |
|
|
|
case HostPortal: |
|
return updatePortalComponent(current, workInProgress, renderLanes); |
|
|
|
case ForwardRef: |
|
{ |
|
var type = workInProgress.type; |
|
var _unresolvedProps2 = workInProgress.pendingProps; |
|
|
|
var _resolvedProps2 = workInProgress.elementType === type ? _unresolvedProps2 : resolveDefaultProps(type, _unresolvedProps2); |
|
|
|
return updateForwardRef(current, workInProgress, type, _resolvedProps2, renderLanes); |
|
} |
|
|
|
case Fragment: |
|
return updateFragment(current, workInProgress, renderLanes); |
|
|
|
case Mode: |
|
return updateMode(current, workInProgress, renderLanes); |
|
|
|
case Profiler: |
|
return updateProfiler(current, workInProgress, renderLanes); |
|
|
|
case ContextProvider: |
|
return updateContextProvider(current, workInProgress, renderLanes); |
|
|
|
case ContextConsumer: |
|
return updateContextConsumer(current, workInProgress, renderLanes); |
|
|
|
case MemoComponent: |
|
{ |
|
var _type2 = workInProgress.type; |
|
var _unresolvedProps3 = workInProgress.pendingProps; // Resolve outer props first, then resolve inner props. |
|
|
|
var _resolvedProps3 = resolveDefaultProps(_type2, _unresolvedProps3); |
|
|
|
{ |
|
if (workInProgress.type !== workInProgress.elementType) { |
|
var outerPropTypes = _type2.propTypes; |
|
|
|
if (outerPropTypes) { |
|
checkPropTypes(outerPropTypes, _resolvedProps3, // Resolved for outer only |
|
'prop', getComponentNameFromType(_type2)); |
|
} |
|
} |
|
} |
|
|
|
_resolvedProps3 = resolveDefaultProps(_type2.type, _resolvedProps3); |
|
return updateMemoComponent(current, workInProgress, _type2, _resolvedProps3, renderLanes); |
|
} |
|
|
|
case SimpleMemoComponent: |
|
{ |
|
return updateSimpleMemoComponent(current, workInProgress, workInProgress.type, workInProgress.pendingProps, renderLanes); |
|
} |
|
|
|
case IncompleteClassComponent: |
|
{ |
|
var _Component2 = workInProgress.type; |
|
var _unresolvedProps4 = workInProgress.pendingProps; |
|
|
|
var _resolvedProps4 = workInProgress.elementType === _Component2 ? _unresolvedProps4 : resolveDefaultProps(_Component2, _unresolvedProps4); |
|
|
|
return mountIncompleteClassComponent(current, workInProgress, _Component2, _resolvedProps4, renderLanes); |
|
} |
|
|
|
case SuspenseListComponent: |
|
{ |
|
return updateSuspenseListComponent(current, workInProgress, renderLanes); |
|
} |
|
|
|
case ScopeComponent: |
|
{ |
|
|
|
break; |
|
} |
|
|
|
case OffscreenComponent: |
|
{ |
|
return updateOffscreenComponent(current, workInProgress, renderLanes); |
|
} |
|
} |
|
|
|
throw new Error("Unknown unit of work tag (" + workInProgress.tag + "). This error is likely caused by a bug in " + 'React. Please file an issue.'); |
|
} |
|
|
|
function markUpdate(workInProgress) { |
|
// Tag the fiber with an update effect. This turns a Placement into |
|
// a PlacementAndUpdate. |
|
workInProgress.flags |= Update; |
|
} |
|
|
|
function markRef$1(workInProgress) { |
|
workInProgress.flags |= Ref; |
|
|
|
{ |
|
workInProgress.flags |= RefStatic; |
|
} |
|
} |
|
|
|
var appendAllChildren; |
|
var updateHostContainer; |
|
var updateHostComponent$1; |
|
var updateHostText$1; |
|
|
|
{ |
|
// Mutation mode |
|
appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) { |
|
// We only have the top Fiber that was created but we need recurse down its |
|
// children to find all the terminal nodes. |
|
var node = workInProgress.child; |
|
|
|
while (node !== null) { |
|
if (node.tag === HostComponent || node.tag === HostText) { |
|
appendInitialChild(parent, node.stateNode); |
|
} else if (node.tag === HostPortal) ; else if (node.child !== null) { |
|
node.child.return = node; |
|
node = node.child; |
|
continue; |
|
} |
|
|
|
if (node === workInProgress) { |
|
return; |
|
} |
|
|
|
while (node.sibling === null) { |
|
if (node.return === null || node.return === workInProgress) { |
|
return; |
|
} |
|
|
|
node = node.return; |
|
} |
|
|
|
node.sibling.return = node.return; |
|
node = node.sibling; |
|
} |
|
}; |
|
|
|
updateHostContainer = function (current, workInProgress) {// Noop |
|
}; |
|
|
|
updateHostComponent$1 = function (current, workInProgress, type, newProps, rootContainerInstance) { |
|
// If we have an alternate, that means this is an update and we need to |
|
// schedule a side-effect to do the updates. |
|
var oldProps = current.memoizedProps; |
|
|
|
if (oldProps === newProps) { |
|
// In mutation mode, this is sufficient for a bailout because |
|
// we won't touch this node even if children changed. |
|
return; |
|
} // If we get updated because one of our children updated, we don't |
|
// have newProps so we'll have to reuse them. |
|
// TODO: Split the update API as separate for the props vs. children. |
|
// Even better would be if children weren't special cased at all tho. |
|
|
|
|
|
var instance = workInProgress.stateNode; |
|
var currentHostContext = getHostContext(); // TODO: Experiencing an error where oldProps is null. Suggests a host |
|
// component is hitting the resume path. Figure out why. Possibly |
|
// related to `hidden`. |
|
|
|
var updatePayload = prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, currentHostContext); // TODO: Type this specific to this type of component. |
|
|
|
workInProgress.updateQueue = updatePayload; // If the update payload indicates that there is a change or if there |
|
// is a new ref we mark this as an update. All the work is done in commitWork. |
|
|
|
if (updatePayload) { |
|
markUpdate(workInProgress); |
|
} |
|
}; |
|
|
|
updateHostText$1 = function (current, workInProgress, oldText, newText) { |
|
// If the text differs, mark it as an update. All the work in done in commitWork. |
|
if (oldText !== newText) { |
|
markUpdate(workInProgress); |
|
} |
|
}; |
|
} |
|
|
|
function cutOffTailIfNeeded(renderState, hasRenderedATailFallback) { |
|
if (getIsHydrating()) { |
|
// If we're hydrating, we should consume as many items as we can |
|
// so we don't leave any behind. |
|
return; |
|
} |
|
|
|
switch (renderState.tailMode) { |
|
case 'hidden': |
|
{ |
|
// Any insertions at the end of the tail list after this point |
|
// should be invisible. If there are already mounted boundaries |
|
// anything before them are not considered for collapsing. |
|
// Therefore we need to go through the whole tail to find if |
|
// there are any. |
|
var tailNode = renderState.tail; |
|
var lastTailNode = null; |
|
|
|
while (tailNode !== null) { |
|
if (tailNode.alternate !== null) { |
|
lastTailNode = tailNode; |
|
} |
|
|
|
tailNode = tailNode.sibling; |
|
} // Next we're simply going to delete all insertions after the |
|
// last rendered item. |
|
|
|
|
|
if (lastTailNode === null) { |
|
// All remaining items in the tail are insertions. |
|
renderState.tail = null; |
|
} else { |
|
// Detach the insertion after the last node that was already |
|
// inserted. |
|
lastTailNode.sibling = null; |
|
} |
|
|
|
break; |
|
} |
|
|
|
case 'collapsed': |
|
{ |
|
// Any insertions at the end of the tail list after this point |
|
// should be invisible. If there are already mounted boundaries |
|
// anything before them are not considered for collapsing. |
|
// Therefore we need to go through the whole tail to find if |
|
// there are any. |
|
var _tailNode = renderState.tail; |
|
var _lastTailNode = null; |
|
|
|
while (_tailNode !== null) { |
|
if (_tailNode.alternate !== null) { |
|
_lastTailNode = _tailNode; |
|
} |
|
|
|
_tailNode = _tailNode.sibling; |
|
} // Next we're simply going to delete all insertions after the |
|
// last rendered item. |
|
|
|
|
|
if (_lastTailNode === null) { |
|
// All remaining items in the tail are insertions. |
|
if (!hasRenderedATailFallback && renderState.tail !== null) { |
|
// We suspended during the head. We want to show at least one |
|
// row at the tail. So we'll keep on and cut off the rest. |
|
renderState.tail.sibling = null; |
|
} else { |
|
renderState.tail = null; |
|
} |
|
} else { |
|
// Detach the insertion after the last node that was already |
|
// inserted. |
|
_lastTailNode.sibling = null; |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
|
|
function bubbleProperties(completedWork) { |
|
var didBailout = completedWork.alternate !== null && completedWork.alternate.child === completedWork.child; |
|
var newChildLanes = NoLanes; |
|
var subtreeFlags = NoFlags; |
|
|
|
if (!didBailout) { |
|
// Bubble up the earliest expiration time. |
|
if ( (completedWork.mode & ProfileMode) !== NoMode) { |
|
// In profiling mode, resetChildExpirationTime is also used to reset |
|
// profiler durations. |
|
var actualDuration = completedWork.actualDuration; |
|
var treeBaseDuration = completedWork.selfBaseDuration; |
|
var child = completedWork.child; |
|
|
|
while (child !== null) { |
|
newChildLanes = mergeLanes(newChildLanes, mergeLanes(child.lanes, child.childLanes)); |
|
subtreeFlags |= child.subtreeFlags; |
|
subtreeFlags |= child.flags; // When a fiber is cloned, its actualDuration is reset to 0. This value will |
|
// only be updated if work is done on the fiber (i.e. it doesn't bailout). |
|
// When work is done, it should bubble to the parent's actualDuration. If |
|
// the fiber has not been cloned though, (meaning no work was done), then |
|
// this value will reflect the amount of time spent working on a previous |
|
// render. In that case it should not bubble. We determine whether it was |
|
// cloned by comparing the child pointer. |
|
|
|
actualDuration += child.actualDuration; |
|
treeBaseDuration += child.treeBaseDuration; |
|
child = child.sibling; |
|
} |
|
|
|
completedWork.actualDuration = actualDuration; |
|
completedWork.treeBaseDuration = treeBaseDuration; |
|
} else { |
|
var _child = completedWork.child; |
|
|
|
while (_child !== null) { |
|
newChildLanes = mergeLanes(newChildLanes, mergeLanes(_child.lanes, _child.childLanes)); |
|
subtreeFlags |= _child.subtreeFlags; |
|
subtreeFlags |= _child.flags; // Update the return pointer so the tree is consistent. This is a code |
|
// smell because it assumes the commit phase is never concurrent with |
|
// the render phase. Will address during refactor to alternate model. |
|
|
|
_child.return = completedWork; |
|
_child = _child.sibling; |
|
} |
|
} |
|
|
|
completedWork.subtreeFlags |= subtreeFlags; |
|
} else { |
|
// Bubble up the earliest expiration time. |
|
if ( (completedWork.mode & ProfileMode) !== NoMode) { |
|
// In profiling mode, resetChildExpirationTime is also used to reset |
|
// profiler durations. |
|
var _treeBaseDuration = completedWork.selfBaseDuration; |
|
var _child2 = completedWork.child; |
|
|
|
while (_child2 !== null) { |
|
newChildLanes = mergeLanes(newChildLanes, mergeLanes(_child2.lanes, _child2.childLanes)); // "Static" flags share the lifetime of the fiber/hook they belong to, |
|
// so we should bubble those up even during a bailout. All the other |
|
// flags have a lifetime only of a single render + commit, so we should |
|
// ignore them. |
|
|
|
subtreeFlags |= _child2.subtreeFlags & StaticMask; |
|
subtreeFlags |= _child2.flags & StaticMask; |
|
_treeBaseDuration += _child2.treeBaseDuration; |
|
_child2 = _child2.sibling; |
|
} |
|
|
|
completedWork.treeBaseDuration = _treeBaseDuration; |
|
} else { |
|
var _child3 = completedWork.child; |
|
|
|
while (_child3 !== null) { |
|
newChildLanes = mergeLanes(newChildLanes, mergeLanes(_child3.lanes, _child3.childLanes)); // "Static" flags share the lifetime of the fiber/hook they belong to, |
|
// so we should bubble those up even during a bailout. All the other |
|
// flags have a lifetime only of a single render + commit, so we should |
|
// ignore them. |
|
|
|
subtreeFlags |= _child3.subtreeFlags & StaticMask; |
|
subtreeFlags |= _child3.flags & StaticMask; // Update the return pointer so the tree is consistent. This is a code |
|
// smell because it assumes the commit phase is never concurrent with |
|
// the render phase. Will address during refactor to alternate model. |
|
|
|
_child3.return = completedWork; |
|
_child3 = _child3.sibling; |
|
} |
|
} |
|
|
|
completedWork.subtreeFlags |= subtreeFlags; |
|
} |
|
|
|
completedWork.childLanes = newChildLanes; |
|
return didBailout; |
|
} |
|
|
|
function completeDehydratedSuspenseBoundary(current, workInProgress, nextState) { |
|
if (hasUnhydratedTailNodes() && (workInProgress.mode & ConcurrentMode) !== NoMode && (workInProgress.flags & DidCapture) === NoFlags) { |
|
warnIfUnhydratedTailNodes(workInProgress); |
|
resetHydrationState(); |
|
workInProgress.flags |= ForceClientRender | Incomplete | ShouldCapture; |
|
return false; |
|
} |
|
|
|
var wasHydrated = popHydrationState(workInProgress); |
|
|
|
if (nextState !== null && nextState.dehydrated !== null) { |
|
// We might be inside a hydration state the first time we're picking up this |
|
// Suspense boundary, and also after we've reentered it for further hydration. |
|
if (current === null) { |
|
if (!wasHydrated) { |
|
throw new Error('A dehydrated suspense component was completed without a hydrated node. ' + 'This is probably a bug in React.'); |
|
} |
|
|
|
prepareToHydrateHostSuspenseInstance(workInProgress); |
|
bubbleProperties(workInProgress); |
|
|
|
{ |
|
if ((workInProgress.mode & ProfileMode) !== NoMode) { |
|
var isTimedOutSuspense = nextState !== null; |
|
|
|
if (isTimedOutSuspense) { |
|
// Don't count time spent in a timed out Suspense subtree as part of the base duration. |
|
var primaryChildFragment = workInProgress.child; |
|
|
|
if (primaryChildFragment !== null) { |
|
// $FlowFixMe Flow doesn't support type casting in combination with the -= operator |
|
workInProgress.treeBaseDuration -= primaryChildFragment.treeBaseDuration; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} else { |
|
// We might have reentered this boundary to hydrate it. If so, we need to reset the hydration |
|
// state since we're now exiting out of it. popHydrationState doesn't do that for us. |
|
resetHydrationState(); |
|
|
|
if ((workInProgress.flags & DidCapture) === NoFlags) { |
|
// This boundary did not suspend so it's now hydrated and unsuspended. |
|
workInProgress.memoizedState = null; |
|
} // If nothing suspended, we need to schedule an effect to mark this boundary |
|
// as having hydrated so events know that they're free to be invoked. |
|
// It's also a signal to replay events and the suspense callback. |
|
// If something suspended, schedule an effect to attach retry listeners. |
|
// So we might as well always mark this. |
|
|
|
|
|
workInProgress.flags |= Update; |
|
bubbleProperties(workInProgress); |
|
|
|
{ |
|
if ((workInProgress.mode & ProfileMode) !== NoMode) { |
|
var _isTimedOutSuspense = nextState !== null; |
|
|
|
if (_isTimedOutSuspense) { |
|
// Don't count time spent in a timed out Suspense subtree as part of the base duration. |
|
var _primaryChildFragment = workInProgress.child; |
|
|
|
if (_primaryChildFragment !== null) { |
|
// $FlowFixMe Flow doesn't support type casting in combination with the -= operator |
|
workInProgress.treeBaseDuration -= _primaryChildFragment.treeBaseDuration; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
} else { |
|
// Successfully completed this tree. If this was a forced client render, |
|
// there may have been recoverable errors during first hydration |
|
// attempt. If so, add them to a queue so we can log them in the |
|
// commit phase. |
|
upgradeHydrationErrorsToRecoverable(); // Fall through to normal Suspense path |
|
|
|
return true; |
|
} |
|
} |
|
|
|
function completeWork(current, workInProgress, renderLanes) { |
|
var newProps = workInProgress.pendingProps; // Note: This intentionally doesn't check if we're hydrating because comparing |
|
// to the current tree provider fiber is just as fast and less error-prone. |
|
// Ideally we would have a special version of the work loop only |
|
// for hydration. |
|
|
|
popTreeContext(workInProgress); |
|
|
|
switch (workInProgress.tag) { |
|
case IndeterminateComponent: |
|
case LazyComponent: |
|
case SimpleMemoComponent: |
|
case FunctionComponent: |
|
case ForwardRef: |
|
case Fragment: |
|
case Mode: |
|
case Profiler: |
|
case ContextConsumer: |
|
case MemoComponent: |
|
bubbleProperties(workInProgress); |
|
return null; |
|
|
|
case ClassComponent: |
|
{ |
|
var Component = workInProgress.type; |
|
|
|
if (isContextProvider(Component)) { |
|
popContext(workInProgress); |
|
} |
|
|
|
bubbleProperties(workInProgress); |
|
return null; |
|
} |
|
|
|
case HostRoot: |
|
{ |
|
var fiberRoot = workInProgress.stateNode; |
|
popHostContainer(workInProgress); |
|
popTopLevelContextObject(workInProgress); |
|
resetWorkInProgressVersions(); |
|
|
|
if (fiberRoot.pendingContext) { |
|
fiberRoot.context = fiberRoot.pendingContext; |
|
fiberRoot.pendingContext = null; |
|
} |
|
|
|
if (current === null || current.child === null) { |
|
// If we hydrated, pop so that we can delete any remaining children |
|
// that weren't hydrated. |
|
var wasHydrated = popHydrationState(workInProgress); |
|
|
|
if (wasHydrated) { |
|
// If we hydrated, then we'll need to schedule an update for |
|
// the commit side-effects on the root. |
|
markUpdate(workInProgress); |
|
} else { |
|
if (current !== null) { |
|
var prevState = current.memoizedState; |
|
|
|
if ( // Check if this is a client root |
|
!prevState.isDehydrated || // Check if we reverted to client rendering (e.g. due to an error) |
|
(workInProgress.flags & ForceClientRender) !== NoFlags) { |
|
// Schedule an effect to clear this container at the start of the |
|
// next commit. This handles the case of React rendering into a |
|
// container with previous children. It's also safe to do for |
|
// updates too, because current.child would only be null if the |
|
// previous render was null (so the container would already |
|
// be empty). |
|
workInProgress.flags |= Snapshot; // If this was a forced client render, there may have been |
|
// recoverable errors during first hydration attempt. If so, add |
|
// them to a queue so we can log them in the commit phase. |
|
|
|
upgradeHydrationErrorsToRecoverable(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
updateHostContainer(current, workInProgress); |
|
bubbleProperties(workInProgress); |
|
|
|
return null; |
|
} |
|
|
|
case HostComponent: |
|
{ |
|
popHostContext(workInProgress); |
|
var rootContainerInstance = getRootHostContainer(); |
|
var type = workInProgress.type; |
|
|
|
if (current !== null && workInProgress.stateNode != null) { |
|
updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance); |
|
|
|
if (current.ref !== workInProgress.ref) { |
|
markRef$1(workInProgress); |
|
} |
|
} else { |
|
if (!newProps) { |
|
if (workInProgress.stateNode === null) { |
|
throw new Error('We must have new props for new mounts. This error is likely ' + 'caused by a bug in React. Please file an issue.'); |
|
} // This can happen when we abort work. |
|
|
|
|
|
bubbleProperties(workInProgress); |
|
return null; |
|
} |
|
|
|
var currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context |
|
// "stack" as the parent. Then append children as we go in beginWork |
|
// or completeWork depending on whether we want to add them top->down or |
|
// bottom->up. Top->down is faster in IE11. |
|
|
|
var _wasHydrated = popHydrationState(workInProgress); |
|
|
|
if (_wasHydrated) { |
|
// TODO: Move this and createInstance step into the beginPhase |
|
// to consolidate. |
|
if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) { |
|
// If changes to the hydrated node need to be applied at the |
|
// commit-phase we mark this as such. |
|
markUpdate(workInProgress); |
|
} |
|
} else { |
|
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress); |
|
appendAllChildren(instance, workInProgress, false, false); |
|
workInProgress.stateNode = instance; // Certain renderers require commit-time effects for initial mount. |
|
// (eg DOM renderer supports auto-focus for certain elements). |
|
// Make sure such renderers get scheduled for later work. |
|
|
|
if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) { |
|
markUpdate(workInProgress); |
|
} |
|
} |
|
|
|
if (workInProgress.ref !== null) { |
|
// If there is a ref on a host node we need to schedule a callback |
|
markRef$1(workInProgress); |
|
} |
|
} |
|
|
|
bubbleProperties(workInProgress); |
|
return null; |
|
} |
|
|
|
case HostText: |
|
{ |
|
var newText = newProps; |
|
|
|
if (current && workInProgress.stateNode != null) { |
|
var oldText = current.memoizedProps; // If we have an alternate, that means this is an update and we need |
|
// to schedule a side-effect to do the updates. |
|
|
|
updateHostText$1(current, workInProgress, oldText, newText); |
|
} else { |
|
if (typeof newText !== 'string') { |
|
if (workInProgress.stateNode === null) { |
|
throw new Error('We must have new props for new mounts. This error is likely ' + 'caused by a bug in React. Please file an issue.'); |
|
} // This can happen when we abort work. |
|
|
|
} |
|
|
|
var _rootContainerInstance = getRootHostContainer(); |
|
|
|
var _currentHostContext = getHostContext(); |
|
|
|
var _wasHydrated2 = popHydrationState(workInProgress); |
|
|
|
if (_wasHydrated2) { |
|
if (prepareToHydrateHostTextInstance(workInProgress)) { |
|
markUpdate(workInProgress); |
|
} |
|
} else { |
|
workInProgress.stateNode = createTextInstance(newText, _rootContainerInstance, _currentHostContext, workInProgress); |
|
} |
|
} |
|
|
|
bubbleProperties(workInProgress); |
|
return null; |
|
} |
|
|
|
case SuspenseComponent: |
|
{ |
|
popSuspenseContext(workInProgress); |
|
var nextState = workInProgress.memoizedState; // Special path for dehydrated boundaries. We may eventually move this |
|
// to its own fiber type so that we can add other kinds of hydration |
|
// boundaries that aren't associated with a Suspense tree. In anticipation |
|
// of such a refactor, all the hydration logic is contained in |
|
// this branch. |
|
|
|
if (current === null || current.memoizedState !== null && current.memoizedState.dehydrated !== null) { |
|
var fallthroughToNormalSuspensePath = completeDehydratedSuspenseBoundary(current, workInProgress, nextState); |
|
|
|
if (!fallthroughToNormalSuspensePath) { |
|
if (workInProgress.flags & ShouldCapture) { |
|
// Special case. There were remaining unhydrated nodes. We treat |
|
// this as a mismatch. Revert to client rendering. |
|
return workInProgress; |
|
} else { |
|
// Did not finish hydrating, either because this is the initial |
|
// render or because something suspended. |
|
return null; |
|
} |
|
} // Continue with the normal Suspense path. |
|
|
|
} |
|
|
|
if ((workInProgress.flags & DidCapture) !== NoFlags) { |
|
// Something suspended. Re-render with the fallback children. |
|
workInProgress.lanes = renderLanes; // Do not reset the effect list. |
|
|
|
if ( (workInProgress.mode & ProfileMode) !== NoMode) { |
|
transferActualDuration(workInProgress); |
|
} // Don't bubble properties in this case. |
|
|
|
|
|
return workInProgress; |
|
} |
|
|
|
var nextDidTimeout = nextState !== null; |
|
var prevDidTimeout = current !== null && current.memoizedState !== null; |
|
// a passive effect, which is when we process the transitions |
|
|
|
|
|
if (nextDidTimeout !== prevDidTimeout) { |
|
// an effect to toggle the subtree's visibility. When we switch from |
|
// fallback -> primary, the inner Offscreen fiber schedules this effect |
|
// as part of its normal complete phase. But when we switch from |
|
// primary -> fallback, the inner Offscreen fiber does not have a complete |
|
// phase. So we need to schedule its effect here. |
|
// |
|
// We also use this flag to connect/disconnect the effects, but the same |
|
// logic applies: when re-connecting, the Offscreen fiber's complete |
|
// phase will handle scheduling the effect. It's only when the fallback |
|
// is active that we have to do anything special. |
|
|
|
|
|
if (nextDidTimeout) { |
|
var _offscreenFiber2 = workInProgress.child; |
|
_offscreenFiber2.flags |= Visibility; // TODO: This will still suspend a synchronous tree if anything |
|
// in the concurrent tree already suspended during this render. |
|
// This is a known bug. |
|
|
|
if ((workInProgress.mode & ConcurrentMode) !== NoMode) { |
|
// TODO: Move this back to throwException because this is too late |
|
// if this is a large tree which is common for initial loads. We |
|
// don't know if we should restart a render or not until we get |
|
// this marker, and this is too late. |
|
// If this render already had a ping or lower pri updates, |
|
// and this is the first time we know we're going to suspend we |
|
// should be able to immediately restart from within throwException. |
|
var hasInvisibleChildContext = current === null && (workInProgress.memoizedProps.unstable_avoidThisFallback !== true || !enableSuspenseAvoidThisFallback); |
|
|
|
if (hasInvisibleChildContext || hasSuspenseContext(suspenseStackCursor.current, InvisibleParentSuspenseContext)) { |
|
// If this was in an invisible tree or a new render, then showing |
|
// this boundary is ok. |
|
renderDidSuspend(); |
|
} else { |
|
// Otherwise, we're going to have to hide content so we should |
|
// suspend for longer if possible. |
|
renderDidSuspendDelayIfPossible(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
var wakeables = workInProgress.updateQueue; |
|
|
|
if (wakeables !== null) { |
|
// Schedule an effect to attach a retry listener to the promise. |
|
// TODO: Move to passive phase |
|
workInProgress.flags |= Update; |
|
} |
|
|
|
bubbleProperties(workInProgress); |
|
|
|
{ |
|
if ((workInProgress.mode & ProfileMode) !== NoMode) { |
|
if (nextDidTimeout) { |
|
// Don't count time spent in a timed out Suspense subtree as part of the base duration. |
|
var primaryChildFragment = workInProgress.child; |
|
|
|
if (primaryChildFragment !== null) { |
|
// $FlowFixMe Flow doesn't support type casting in combination with the -= operator |
|
workInProgress.treeBaseDuration -= primaryChildFragment.treeBaseDuration; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return null; |
|
} |
|
|
|
case HostPortal: |
|
popHostContainer(workInProgress); |
|
updateHostContainer(current, workInProgress); |
|
|
|
if (current === null) { |
|
preparePortalMount(workInProgress.stateNode.containerInfo); |
|
} |
|
|
|
bubbleProperties(workInProgress); |
|
return null; |
|
|
|
case ContextProvider: |
|
// Pop provider fiber |
|
var context = workInProgress.type._context; |
|
popProvider(context, workInProgress); |
|
bubbleProperties(workInProgress); |
|
return null; |
|
|
|
case IncompleteClassComponent: |
|
{ |
|
// Same as class component case. I put it down here so that the tags are |
|
// sequential to ensure this switch is compiled to a jump table. |
|
var _Component = workInProgress.type; |
|
|
|
if (isContextProvider(_Component)) { |
|
popContext(workInProgress); |
|
} |
|
|
|
bubbleProperties(workInProgress); |
|
return null; |
|
} |
|
|
|
case SuspenseListComponent: |
|
{ |
|
popSuspenseContext(workInProgress); |
|
var renderState = workInProgress.memoizedState; |
|
|
|
if (renderState === null) { |
|
// We're running in the default, "independent" mode. |
|
// We don't do anything in this mode. |
|
bubbleProperties(workInProgress); |
|
return null; |
|
} |
|
|
|
var didSuspendAlready = (workInProgress.flags & DidCapture) !== NoFlags; |
|
var renderedTail = renderState.rendering; |
|
|
|
if (renderedTail === null) { |
|
// We just rendered the head. |
|
if (!didSuspendAlready) { |
|
// This is the first pass. We need to figure out if anything is still |
|
// suspended in the rendered set. |
|
// If new content unsuspended, but there's still some content that |
|
// didn't. Then we need to do a second pass that forces everything |
|
// to keep showing their fallbacks. |
|
// We might be suspended if something in this render pass suspended, or |
|
// something in the previous committed pass suspended. Otherwise, |
|
// there's no chance so we can skip the expensive call to |
|
// findFirstSuspended. |
|
var cannotBeSuspended = renderHasNotSuspendedYet() && (current === null || (current.flags & DidCapture) === NoFlags); |
|
|
|
if (!cannotBeSuspended) { |
|
var row = workInProgress.child; |
|
|
|
while (row !== null) { |
|
var suspended = findFirstSuspended(row); |
|
|
|
if (suspended !== null) { |
|
didSuspendAlready = true; |
|
workInProgress.flags |= DidCapture; |
|
cutOffTailIfNeeded(renderState, false); // If this is a newly suspended tree, it might not get committed as |
|
// part of the second pass. In that case nothing will subscribe to |
|
// its thenables. Instead, we'll transfer its thenables to the |
|
// SuspenseList so that it can retry if they resolve. |
|
// There might be multiple of these in the list but since we're |
|
// going to wait for all of them anyway, it doesn't really matter |
|
// which ones gets to ping. In theory we could get clever and keep |
|
// track of how many dependencies remain but it gets tricky because |
|
// in the meantime, we can add/remove/change items and dependencies. |
|
// We might bail out of the loop before finding any but that |
|
// doesn't matter since that means that the other boundaries that |
|
// we did find already has their listeners attached. |
|
|
|
var newThenables = suspended.updateQueue; |
|
|
|
if (newThenables !== null) { |
|
workInProgress.updateQueue = newThenables; |
|
workInProgress.flags |= Update; |
|
} // Rerender the whole list, but this time, we'll force fallbacks |
|
// to stay in place. |
|
// Reset the effect flags before doing the second pass since that's now invalid. |
|
// Reset the child fibers to their original state. |
|
|
|
|
|
workInProgress.subtreeFlags = NoFlags; |
|
resetChildFibers(workInProgress, renderLanes); // Set up the Suspense Context to force suspense and immediately |
|
// rerender the children. |
|
|
|
pushSuspenseContext(workInProgress, setShallowSuspenseContext(suspenseStackCursor.current, ForceSuspenseFallback)); // Don't bubble properties in this case. |
|
|
|
return workInProgress.child; |
|
} |
|
|
|
row = row.sibling; |
|
} |
|
} |
|
|
|
if (renderState.tail !== null && now() > getRenderTargetTime()) { |
|
// We have already passed our CPU deadline but we still have rows |
|
// left in the tail. We'll just give up further attempts to render |
|
// the main content and only render fallbacks. |
|
workInProgress.flags |= DidCapture; |
|
didSuspendAlready = true; |
|
cutOffTailIfNeeded(renderState, false); // Since nothing actually suspended, there will nothing to ping this |
|
// to get it started back up to attempt the next item. While in terms |
|
// of priority this work has the same priority as this current render, |
|
// it's not part of the same transition once the transition has |
|
// committed. If it's sync, we still want to yield so that it can be |
|
// painted. Conceptually, this is really the same as pinging. |
|
// We can use any RetryLane even if it's the one currently rendering |
|
// since we're leaving it behind on this node. |
|
|
|
workInProgress.lanes = SomeRetryLane; |
|
} |
|
} else { |
|
cutOffTailIfNeeded(renderState, false); |
|
} // Next we're going to render the tail. |
|
|
|
} else { |
|
// Append the rendered row to the child list. |
|
if (!didSuspendAlready) { |
|
var _suspended = findFirstSuspended(renderedTail); |
|
|
|
if (_suspended !== null) { |
|
workInProgress.flags |= DidCapture; |
|
didSuspendAlready = true; // Ensure we transfer the update queue to the parent so that it doesn't |
|
// get lost if this row ends up dropped during a second pass. |
|
|
|
var _newThenables = _suspended.updateQueue; |
|
|
|
if (_newThenables !== null) { |
|
workInProgress.updateQueue = _newThenables; |
|
workInProgress.flags |= Update; |
|
} |
|
|
|
cutOffTailIfNeeded(renderState, true); // This might have been modified. |
|
|
|
if (renderState.tail === null && renderState.tailMode === 'hidden' && !renderedTail.alternate && !getIsHydrating() // We don't cut it if we're hydrating. |
|
) { |
|
// We're done. |
|
bubbleProperties(workInProgress); |
|
return null; |
|
} |
|
} else if ( // The time it took to render last row is greater than the remaining |
|
// time we have to render. So rendering one more row would likely |
|
// exceed it. |
|
now() * 2 - renderState.renderingStartTime > getRenderTargetTime() && renderLanes !== OffscreenLane) { |
|
// We have now passed our CPU deadline and we'll just give up further |
|
// attempts to render the main content and only render fallbacks. |
|
// The assumption is that this is usually faster. |
|
workInProgress.flags |= DidCapture; |
|
didSuspendAlready = true; |
|
cutOffTailIfNeeded(renderState, false); // Since nothing actually suspended, there will nothing to ping this |
|
// to get it started back up to attempt the next item. While in terms |
|
// of priority this work has the same priority as this current render, |
|
// it's not part of the same transition once the transition has |
|
// committed. If it's sync, we still want to yield so that it can be |
|
// painted. Conceptually, this is really the same as pinging. |
|
// We can use any RetryLane even if it's the one currently rendering |
|
// since we're leaving it behind on this node. |
|
|
|
workInProgress.lanes = SomeRetryLane; |
|
} |
|
} |
|
|
|
if (renderState.isBackwards) { |
|
// The effect list of the backwards tail will have been added |
|
// to the end. This breaks the guarantee that life-cycles fire in |
|
// sibling order but that isn't a strong guarantee promised by React. |
|
// Especially since these might also just pop in during future commits. |
|
// Append to the beginning of the list. |
|
renderedTail.sibling = workInProgress.child; |
|
workInProgress.child = renderedTail; |
|
} else { |
|
var previousSibling = renderState.last; |
|
|
|
if (previousSibling !== null) { |
|
previousSibling.sibling = renderedTail; |
|
} else { |
|
workInProgress.child = renderedTail; |
|
} |
|
|
|
renderState.last = renderedTail; |
|
} |
|
} |
|
|
|
if (renderState.tail !== null) { |
|
// We still have tail rows to render. |
|
// Pop a row. |
|
var next = renderState.tail; |
|
renderState.rendering = next; |
|
renderState.tail = next.sibling; |
|
renderState.renderingStartTime = now(); |
|
next.sibling = null; // Restore the context. |
|
// TODO: We can probably just avoid popping it instead and only |
|
// setting it the first time we go from not suspended to suspended. |
|
|
|
var suspenseContext = suspenseStackCursor.current; |
|
|
|
if (didSuspendAlready) { |
|
suspenseContext = setShallowSuspenseContext(suspenseContext, ForceSuspenseFallback); |
|
} else { |
|
suspenseContext = setDefaultShallowSuspenseContext(suspenseContext); |
|
} |
|
|
|
pushSuspenseContext(workInProgress, suspenseContext); // Do a pass over the next row. |
|
// Don't bubble properties in this case. |
|
|
|
return next; |
|
} |
|
|
|
bubbleProperties(workInProgress); |
|
return null; |
|
} |
|
|
|
case ScopeComponent: |
|
{ |
|
|
|
break; |
|
} |
|
|
|
case OffscreenComponent: |
|
case LegacyHiddenComponent: |
|
{ |
|
popRenderLanes(workInProgress); |
|
var _nextState = workInProgress.memoizedState; |
|
var nextIsHidden = _nextState !== null; |
|
|
|
if (current !== null) { |
|
var _prevState = current.memoizedState; |
|
var prevIsHidden = _prevState !== null; |
|
|
|
if (prevIsHidden !== nextIsHidden && ( // LegacyHidden doesn't do any hiding — it only pre-renders. |
|
!enableLegacyHidden )) { |
|
workInProgress.flags |= Visibility; |
|
} |
|
} |
|
|
|
if (!nextIsHidden || (workInProgress.mode & ConcurrentMode) === NoMode) { |
|
bubbleProperties(workInProgress); |
|
} else { |
|
// Don't bubble properties for hidden children unless we're rendering |
|
// at offscreen priority. |
|
if (includesSomeLane(subtreeRenderLanes, OffscreenLane)) { |
|
bubbleProperties(workInProgress); |
|
|
|
{ |
|
// Check if there was an insertion or update in the hidden subtree. |
|
// If so, we need to hide those nodes in the commit phase, so |
|
// schedule a visibility effect. |
|
if ( workInProgress.subtreeFlags & (Placement | Update)) { |
|
workInProgress.flags |= Visibility; |
|
} |
|
} |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
case CacheComponent: |
|
{ |
|
|
|
return null; |
|
} |
|
|
|
case TracingMarkerComponent: |
|
{ |
|
|
|
return null; |
|
} |
|
} |
|
|
|
throw new Error("Unknown unit of work tag (" + workInProgress.tag + "). This error is likely caused by a bug in " + 'React. Please file an issue.'); |
|
} |
|
|
|
function unwindWork(current, workInProgress, renderLanes) { |
|
// Note: This intentionally doesn't check if we're hydrating because comparing |
|
// to the current tree provider fiber is just as fast and less error-prone. |
|
// Ideally we would have a special version of the work loop only |
|
// for hydration. |
|
popTreeContext(workInProgress); |
|
|
|
switch (workInProgress.tag) { |
|
case ClassComponent: |
|
{ |
|
var Component = workInProgress.type; |
|
|
|
if (isContextProvider(Component)) { |
|
popContext(workInProgress); |
|
} |
|
|
|
var flags = workInProgress.flags; |
|
|
|
if (flags & ShouldCapture) { |
|
workInProgress.flags = flags & ~ShouldCapture | DidCapture; |
|
|
|
if ( (workInProgress.mode & ProfileMode) !== NoMode) { |
|
transferActualDuration(workInProgress); |
|
} |
|
|
|
return workInProgress; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
case HostRoot: |
|
{ |
|
var root = workInProgress.stateNode; |
|
popHostContainer(workInProgress); |
|
popTopLevelContextObject(workInProgress); |
|
resetWorkInProgressVersions(); |
|
var _flags = workInProgress.flags; |
|
|
|
if ((_flags & ShouldCapture) !== NoFlags && (_flags & DidCapture) === NoFlags) { |
|
// There was an error during render that wasn't captured by a suspense |
|
// boundary. Do a second pass on the root to unmount the children. |
|
workInProgress.flags = _flags & ~ShouldCapture | DidCapture; |
|
return workInProgress; |
|
} // We unwound to the root without completing it. Exit. |
|
|
|
|
|
return null; |
|
} |
|
|
|
case HostComponent: |
|
{ |
|
// TODO: popHydrationState |
|
popHostContext(workInProgress); |
|
return null; |
|
} |
|
|
|
case SuspenseComponent: |
|
{ |
|
popSuspenseContext(workInProgress); |
|
var suspenseState = workInProgress.memoizedState; |
|
|
|
if (suspenseState !== null && suspenseState.dehydrated !== null) { |
|
if (workInProgress.alternate === null) { |
|
throw new Error('Threw in newly mounted dehydrated component. This is likely a bug in ' + 'React. Please file an issue.'); |
|
} |
|
|
|
resetHydrationState(); |
|
} |
|
|
|
var _flags2 = workInProgress.flags; |
|
|
|
if (_flags2 & ShouldCapture) { |
|
workInProgress.flags = _flags2 & ~ShouldCapture | DidCapture; // Captured a suspense effect. Re-render the boundary. |
|
|
|
if ( (workInProgress.mode & ProfileMode) !== NoMode) { |
|
transferActualDuration(workInProgress); |
|
} |
|
|
|
return workInProgress; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
case SuspenseListComponent: |
|
{ |
|
popSuspenseContext(workInProgress); // SuspenseList doesn't actually catch anything. It should've been |
|
// caught by a nested boundary. If not, it should bubble through. |
|
|
|
return null; |
|
} |
|
|
|
case HostPortal: |
|
popHostContainer(workInProgress); |
|
return null; |
|
|
|
case ContextProvider: |
|
var context = workInProgress.type._context; |
|
popProvider(context, workInProgress); |
|
return null; |
|
|
|
case OffscreenComponent: |
|
case LegacyHiddenComponent: |
|
popRenderLanes(workInProgress); |
|
return null; |
|
|
|
case CacheComponent: |
|
|
|
return null; |
|
|
|
default: |
|
return null; |
|
} |
|
} |
|
|
|
function unwindInterruptedWork(current, interruptedWork, renderLanes) { |
|
// Note: This intentionally doesn't check if we're hydrating because comparing |
|
// to the current tree provider fiber is just as fast and less error-prone. |
|
// Ideally we would have a special version of the work loop only |
|
// for hydration. |
|
popTreeContext(interruptedWork); |
|
|
|
switch (interruptedWork.tag) { |
|
case ClassComponent: |
|
{ |
|
var childContextTypes = interruptedWork.type.childContextTypes; |
|
|
|
if (childContextTypes !== null && childContextTypes !== undefined) { |
|
popContext(interruptedWork); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case HostRoot: |
|
{ |
|
var root = interruptedWork.stateNode; |
|
popHostContainer(interruptedWork); |
|
popTopLevelContextObject(interruptedWork); |
|
resetWorkInProgressVersions(); |
|
break; |
|
} |
|
|
|
case HostComponent: |
|
{ |
|
popHostContext(interruptedWork); |
|
break; |
|
} |
|
|
|
case HostPortal: |
|
popHostContainer(interruptedWork); |
|
break; |
|
|
|
case SuspenseComponent: |
|
popSuspenseContext(interruptedWork); |
|
break; |
|
|
|
case SuspenseListComponent: |
|
popSuspenseContext(interruptedWork); |
|
break; |
|
|
|
case ContextProvider: |
|
var context = interruptedWork.type._context; |
|
popProvider(context, interruptedWork); |
|
break; |
|
|
|
case OffscreenComponent: |
|
case LegacyHiddenComponent: |
|
popRenderLanes(interruptedWork); |
|
break; |
|
} |
|
} |
|
|
|
var didWarnAboutUndefinedSnapshotBeforeUpdate = null; |
|
|
|
{ |
|
didWarnAboutUndefinedSnapshotBeforeUpdate = new Set(); |
|
} // Used during the commit phase to track the state of the Offscreen component stack. |
|
// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor. |
|
// Only used when enableSuspenseLayoutEffectSemantics is enabled. |
|
|
|
|
|
var offscreenSubtreeIsHidden = false; |
|
var offscreenSubtreeWasHidden = false; |
|
var PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set; |
|
var nextEffect = null; // Used for Profiling builds to track updaters. |
|
|
|
var inProgressLanes = null; |
|
var inProgressRoot = null; |
|
function reportUncaughtErrorInDEV(error) { |
|
// Wrapping each small part of the commit phase into a guarded |
|
// callback is a bit too slow (https://github.com/facebook/react/pull/21666). |
|
// But we rely on it to surface errors to DEV tools like overlays |
|
// (https://github.com/facebook/react/issues/21712). |
|
// As a compromise, rethrow only caught errors in a guard. |
|
{ |
|
invokeGuardedCallback(null, function () { |
|
throw error; |
|
}); |
|
clearCaughtError(); |
|
} |
|
} |
|
|
|
var callComponentWillUnmountWithTimer = function (current, instance) { |
|
instance.props = current.memoizedProps; |
|
instance.state = current.memoizedState; |
|
|
|
if ( current.mode & ProfileMode) { |
|
try { |
|
startLayoutEffectTimer(); |
|
instance.componentWillUnmount(); |
|
} finally { |
|
recordLayoutEffectDuration(current); |
|
} |
|
} else { |
|
instance.componentWillUnmount(); |
|
} |
|
}; // Capture errors so they don't interrupt mounting. |
|
|
|
|
|
function safelyCallCommitHookLayoutEffectListMount(current, nearestMountedAncestor) { |
|
try { |
|
commitHookEffectListMount(Layout, current); |
|
} catch (error) { |
|
captureCommitPhaseError(current, nearestMountedAncestor, error); |
|
} |
|
} // Capture errors so they don't interrupt unmounting. |
|
|
|
|
|
function safelyCallComponentWillUnmount(current, nearestMountedAncestor, instance) { |
|
try { |
|
callComponentWillUnmountWithTimer(current, instance); |
|
} catch (error) { |
|
captureCommitPhaseError(current, nearestMountedAncestor, error); |
|
} |
|
} // Capture errors so they don't interrupt mounting. |
|
|
|
|
|
function safelyCallComponentDidMount(current, nearestMountedAncestor, instance) { |
|
try { |
|
instance.componentDidMount(); |
|
} catch (error) { |
|
captureCommitPhaseError(current, nearestMountedAncestor, error); |
|
} |
|
} // Capture errors so they don't interrupt mounting. |
|
|
|
|
|
function safelyAttachRef(current, nearestMountedAncestor) { |
|
try { |
|
commitAttachRef(current); |
|
} catch (error) { |
|
captureCommitPhaseError(current, nearestMountedAncestor, error); |
|
} |
|
} |
|
|
|
function safelyDetachRef(current, nearestMountedAncestor) { |
|
var ref = current.ref; |
|
|
|
if (ref !== null) { |
|
if (typeof ref === 'function') { |
|
var retVal; |
|
|
|
try { |
|
if (enableProfilerTimer && enableProfilerCommitHooks && current.mode & ProfileMode) { |
|
try { |
|
startLayoutEffectTimer(); |
|
retVal = ref(null); |
|
} finally { |
|
recordLayoutEffectDuration(current); |
|
} |
|
} else { |
|
retVal = ref(null); |
|
} |
|
} catch (error) { |
|
captureCommitPhaseError(current, nearestMountedAncestor, error); |
|
} |
|
|
|
{ |
|
if (typeof retVal === 'function') { |
|
error('Unexpected return value from a callback ref in %s. ' + 'A callback ref should not return a function.', getComponentNameFromFiber(current)); |
|
} |
|
} |
|
} else { |
|
ref.current = null; |
|
} |
|
} |
|
} |
|
|
|
function safelyCallDestroy(current, nearestMountedAncestor, destroy) { |
|
try { |
|
destroy(); |
|
} catch (error) { |
|
captureCommitPhaseError(current, nearestMountedAncestor, error); |
|
} |
|
} |
|
|
|
var focusedInstanceHandle = null; |
|
var shouldFireAfterActiveInstanceBlur = false; |
|
function commitBeforeMutationEffects(root, firstChild) { |
|
focusedInstanceHandle = prepareForCommit(root.containerInfo); |
|
nextEffect = firstChild; |
|
commitBeforeMutationEffects_begin(); // We no longer need to track the active instance fiber |
|
|
|
var shouldFire = shouldFireAfterActiveInstanceBlur; |
|
shouldFireAfterActiveInstanceBlur = false; |
|
focusedInstanceHandle = null; |
|
return shouldFire; |
|
} |
|
|
|
function commitBeforeMutationEffects_begin() { |
|
while (nextEffect !== null) { |
|
var fiber = nextEffect; // This phase is only used for beforeActiveInstanceBlur. |
|
|
|
var child = fiber.child; |
|
|
|
if ((fiber.subtreeFlags & BeforeMutationMask) !== NoFlags && child !== null) { |
|
child.return = fiber; |
|
nextEffect = child; |
|
} else { |
|
commitBeforeMutationEffects_complete(); |
|
} |
|
} |
|
} |
|
|
|
function commitBeforeMutationEffects_complete() { |
|
while (nextEffect !== null) { |
|
var fiber = nextEffect; |
|
setCurrentFiber(fiber); |
|
|
|
try { |
|
commitBeforeMutationEffectsOnFiber(fiber); |
|
} catch (error) { |
|
captureCommitPhaseError(fiber, fiber.return, error); |
|
} |
|
|
|
resetCurrentFiber(); |
|
var sibling = fiber.sibling; |
|
|
|
if (sibling !== null) { |
|
sibling.return = fiber.return; |
|
nextEffect = sibling; |
|
return; |
|
} |
|
|
|
nextEffect = fiber.return; |
|
} |
|
} |
|
|
|
function commitBeforeMutationEffectsOnFiber(finishedWork) { |
|
var current = finishedWork.alternate; |
|
var flags = finishedWork.flags; |
|
|
|
if ((flags & Snapshot) !== NoFlags) { |
|
setCurrentFiber(finishedWork); |
|
|
|
switch (finishedWork.tag) { |
|
case FunctionComponent: |
|
case ForwardRef: |
|
case SimpleMemoComponent: |
|
{ |
|
break; |
|
} |
|
|
|
case ClassComponent: |
|
{ |
|
if (current !== null) { |
|
var prevProps = current.memoizedProps; |
|
var prevState = current.memoizedState; |
|
var instance = finishedWork.stateNode; // We could update instance props and state here, |
|
// but instead we rely on them being set during last render. |
|
// TODO: revisit this when we implement resuming. |
|
|
|
{ |
|
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) { |
|
if (instance.props !== finishedWork.memoizedProps) { |
|
error('Expected %s props to match memoized props before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance'); |
|
} |
|
|
|
if (instance.state !== finishedWork.memoizedState) { |
|
error('Expected %s state to match memoized state before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance'); |
|
} |
|
} |
|
} |
|
|
|
var snapshot = instance.getSnapshotBeforeUpdate(finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState); |
|
|
|
{ |
|
var didWarnSet = didWarnAboutUndefinedSnapshotBeforeUpdate; |
|
|
|
if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) { |
|
didWarnSet.add(finishedWork.type); |
|
|
|
error('%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' + 'must be returned. You have returned undefined.', getComponentNameFromFiber(finishedWork)); |
|
} |
|
} |
|
|
|
instance.__reactInternalSnapshotBeforeUpdate = snapshot; |
|
} |
|
|
|
break; |
|
} |
|
|
|
case HostRoot: |
|
{ |
|
{ |
|
var root = finishedWork.stateNode; |
|
clearContainer(root.containerInfo); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case HostComponent: |
|
case HostText: |
|
case HostPortal: |
|
case IncompleteClassComponent: |
|
// Nothing to do for these component types |
|
break; |
|
|
|
default: |
|
{ |
|
throw new Error('This unit of work tag should not have side-effects. This error is ' + 'likely caused by a bug in React. Please file an issue.'); |
|
} |
|
} |
|
|
|
resetCurrentFiber(); |
|
} |
|
} |
|
|
|
function commitHookEffectListUnmount(flags, finishedWork, nearestMountedAncestor) { |
|
var updateQueue = finishedWork.updateQueue; |
|
var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; |
|
|
|
if (lastEffect !== null) { |
|
var firstEffect = lastEffect.next; |
|
var effect = firstEffect; |
|
|
|
do { |
|
if ((effect.tag & flags) === flags) { |
|
// Unmount |
|
var destroy = effect.destroy; |
|
effect.destroy = undefined; |
|
|
|
if (destroy !== undefined) { |
|
{ |
|
if ((flags & Passive$1) !== NoFlags$1) { |
|
markComponentPassiveEffectUnmountStarted(finishedWork); |
|
} else if ((flags & Layout) !== NoFlags$1) { |
|
markComponentLayoutEffectUnmountStarted(finishedWork); |
|
} |
|
} |
|
|
|
{ |
|
if ((flags & Insertion) !== NoFlags$1) { |
|
setIsRunningInsertionEffect(true); |
|
} |
|
} |
|
|
|
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy); |
|
|
|
{ |
|
if ((flags & Insertion) !== NoFlags$1) { |
|
setIsRunningInsertionEffect(false); |
|
} |
|
} |
|
|
|
{ |
|
if ((flags & Passive$1) !== NoFlags$1) { |
|
markComponentPassiveEffectUnmountStopped(); |
|
} else if ((flags & Layout) !== NoFlags$1) { |
|
markComponentLayoutEffectUnmountStopped(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
effect = effect.next; |
|
} while (effect !== firstEffect); |
|
} |
|
} |
|
|
|
function commitHookEffectListMount(flags, finishedWork) { |
|
var updateQueue = finishedWork.updateQueue; |
|
var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; |
|
|
|
if (lastEffect !== null) { |
|
var firstEffect = lastEffect.next; |
|
var effect = firstEffect; |
|
|
|
do { |
|
if ((effect.tag & flags) === flags) { |
|
{ |
|
if ((flags & Passive$1) !== NoFlags$1) { |
|
markComponentPassiveEffectMountStarted(finishedWork); |
|
} else if ((flags & Layout) !== NoFlags$1) { |
|
markComponentLayoutEffectMountStarted(finishedWork); |
|
} |
|
} // Mount |
|
|
|
|
|
var create = effect.create; |
|
|
|
{ |
|
if ((flags & Insertion) !== NoFlags$1) { |
|
setIsRunningInsertionEffect(true); |
|
} |
|
} |
|
|
|
effect.destroy = create(); |
|
|
|
{ |
|
if ((flags & Insertion) !== NoFlags$1) { |
|
setIsRunningInsertionEffect(false); |
|
} |
|
} |
|
|
|
{ |
|
if ((flags & Passive$1) !== NoFlags$1) { |
|
markComponentPassiveEffectMountStopped(); |
|
} else if ((flags & Layout) !== NoFlags$1) { |
|
markComponentLayoutEffectMountStopped(); |
|
} |
|
} |
|
|
|
{ |
|
var destroy = effect.destroy; |
|
|
|
if (destroy !== undefined && typeof destroy !== 'function') { |
|
var hookName = void 0; |
|
|
|
if ((effect.tag & Layout) !== NoFlags) { |
|
hookName = 'useLayoutEffect'; |
|
} else if ((effect.tag & Insertion) !== NoFlags) { |
|
hookName = 'useInsertionEffect'; |
|
} else { |
|
hookName = 'useEffect'; |
|
} |
|
|
|
var addendum = void 0; |
|
|
|
if (destroy === null) { |
|
addendum = ' You returned null. If your effect does not require clean ' + 'up, return undefined (or nothing).'; |
|
} else if (typeof destroy.then === 'function') { |
|
addendum = '\n\nIt looks like you wrote ' + hookName + '(async () => ...) or returned a Promise. ' + 'Instead, write the async function inside your effect ' + 'and call it immediately:\n\n' + hookName + '(() => {\n' + ' async function fetchData() {\n' + ' // You can await here\n' + ' const response = await MyAPI.getData(someId);\n' + ' // ...\n' + ' }\n' + ' fetchData();\n' + "}, [someId]); // Or [] if effect doesn't need props or state\n\n" + 'Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching'; |
|
} else { |
|
addendum = ' You returned: ' + destroy; |
|
} |
|
|
|
error('%s must not return anything besides a function, ' + 'which is used for clean-up.%s', hookName, addendum); |
|
} |
|
} |
|
} |
|
|
|
effect = effect.next; |
|
} while (effect !== firstEffect); |
|
} |
|
} |
|
|
|
function commitPassiveEffectDurations(finishedRoot, finishedWork) { |
|
{ |
|
// Only Profilers with work in their subtree will have an Update effect scheduled. |
|
if ((finishedWork.flags & Update) !== NoFlags) { |
|
switch (finishedWork.tag) { |
|
case Profiler: |
|
{ |
|
var passiveEffectDuration = finishedWork.stateNode.passiveEffectDuration; |
|
var _finishedWork$memoize = finishedWork.memoizedProps, |
|
id = _finishedWork$memoize.id, |
|
onPostCommit = _finishedWork$memoize.onPostCommit; // This value will still reflect the previous commit phase. |
|
// It does not get reset until the start of the next commit phase. |
|
|
|
var commitTime = getCommitTime(); |
|
var phase = finishedWork.alternate === null ? 'mount' : 'update'; |
|
|
|
{ |
|
if (isCurrentUpdateNested()) { |
|
phase = 'nested-update'; |
|
} |
|
} |
|
|
|
if (typeof onPostCommit === 'function') { |
|
onPostCommit(id, phase, passiveEffectDuration, commitTime); |
|
} // Bubble times to the next nearest ancestor Profiler. |
|
// After we process that Profiler, we'll bubble further up. |
|
|
|
|
|
var parentFiber = finishedWork.return; |
|
|
|
outer: while (parentFiber !== null) { |
|
switch (parentFiber.tag) { |
|
case HostRoot: |
|
var root = parentFiber.stateNode; |
|
root.passiveEffectDuration += passiveEffectDuration; |
|
break outer; |
|
|
|
case Profiler: |
|
var parentStateNode = parentFiber.stateNode; |
|
parentStateNode.passiveEffectDuration += passiveEffectDuration; |
|
break outer; |
|
} |
|
|
|
parentFiber = parentFiber.return; |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork, committedLanes) { |
|
if ((finishedWork.flags & LayoutMask) !== NoFlags) { |
|
switch (finishedWork.tag) { |
|
case FunctionComponent: |
|
case ForwardRef: |
|
case SimpleMemoComponent: |
|
{ |
|
if ( !offscreenSubtreeWasHidden) { |
|
// At this point layout effects have already been destroyed (during mutation phase). |
|
// This is done to prevent sibling component effects from interfering with each other, |
|
// e.g. a destroy function in one component should never override a ref set |
|
// by a create function in another component during the same commit. |
|
if ( finishedWork.mode & ProfileMode) { |
|
try { |
|
startLayoutEffectTimer(); |
|
commitHookEffectListMount(Layout | HasEffect, finishedWork); |
|
} finally { |
|
recordLayoutEffectDuration(finishedWork); |
|
} |
|
} else { |
|
commitHookEffectListMount(Layout | HasEffect, finishedWork); |
|
} |
|
} |
|
|
|
break; |
|
} |
|
|
|
case ClassComponent: |
|
{ |
|
var instance = finishedWork.stateNode; |
|
|
|
if (finishedWork.flags & Update) { |
|
if (!offscreenSubtreeWasHidden) { |
|
if (current === null) { |
|
// We could update instance props and state here, |
|
// but instead we rely on them being set during last render. |
|
// TODO: revisit this when we implement resuming. |
|
{ |
|
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) { |
|
if (instance.props !== finishedWork.memoizedProps) { |
|
error('Expected %s props to match memoized props before ' + 'componentDidMount. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance'); |
|
} |
|
|
|
if (instance.state !== finishedWork.memoizedState) { |
|
error('Expected %s state to match memoized state before ' + 'componentDidMount. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance'); |
|
} |
|
} |
|
} |
|
|
|
if ( finishedWork.mode & ProfileMode) { |
|
try { |
|
startLayoutEffectTimer(); |
|
instance.componentDidMount(); |
|
} finally { |
|
recordLayoutEffectDuration(finishedWork); |
|
} |
|
} else { |
|
instance.componentDidMount(); |
|
} |
|
} else { |
|
var prevProps = finishedWork.elementType === finishedWork.type ? current.memoizedProps : resolveDefaultProps(finishedWork.type, current.memoizedProps); |
|
var prevState = current.memoizedState; // We could update instance props and state here, |
|
// but instead we rely on them being set during last render. |
|
// TODO: revisit this when we implement resuming. |
|
|
|
{ |
|
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) { |
|
if (instance.props !== finishedWork.memoizedProps) { |
|
error('Expected %s props to match memoized props before ' + 'componentDidUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance'); |
|
} |
|
|
|
if (instance.state !== finishedWork.memoizedState) { |
|
error('Expected %s state to match memoized state before ' + 'componentDidUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance'); |
|
} |
|
} |
|
} |
|
|
|
if ( finishedWork.mode & ProfileMode) { |
|
try { |
|
startLayoutEffectTimer(); |
|
instance.componentDidUpdate(prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate); |
|
} finally { |
|
recordLayoutEffectDuration(finishedWork); |
|
} |
|
} else { |
|
instance.componentDidUpdate(prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate); |
|
} |
|
} |
|
} |
|
} // TODO: I think this is now always non-null by the time it reaches the |
|
// commit phase. Consider removing the type check. |
|
|
|
|
|
var updateQueue = finishedWork.updateQueue; |
|
|
|
if (updateQueue !== null) { |
|
{ |
|
if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) { |
|
if (instance.props !== finishedWork.memoizedProps) { |
|
error('Expected %s props to match memoized props before ' + 'processing the update queue. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance'); |
|
} |
|
|
|
if (instance.state !== finishedWork.memoizedState) { |
|
error('Expected %s state to match memoized state before ' + 'processing the update queue. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance'); |
|
} |
|
} |
|
} // We could update instance props and state here, |
|
// but instead we rely on them being set during last render. |
|
// TODO: revisit this when we implement resuming. |
|
|
|
|
|
commitUpdateQueue(finishedWork, updateQueue, instance); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case HostRoot: |
|
{ |
|
// TODO: I think this is now always non-null by the time it reaches the |
|
// commit phase. Consider removing the type check. |
|
var _updateQueue = finishedWork.updateQueue; |
|
|
|
if (_updateQueue !== null) { |
|
var _instance = null; |
|
|
|
if (finishedWork.child !== null) { |
|
switch (finishedWork.child.tag) { |
|
case HostComponent: |
|
_instance = getPublicInstance(finishedWork.child.stateNode); |
|
break; |
|
|
|
case ClassComponent: |
|
_instance = finishedWork.child.stateNode; |
|
break; |
|
} |
|
} |
|
|
|
commitUpdateQueue(finishedWork, _updateQueue, _instance); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case HostComponent: |
|
{ |
|
var _instance2 = finishedWork.stateNode; // Renderers may schedule work to be done after host components are mounted |
|
// (eg DOM renderer may schedule auto-focus for inputs and form controls). |
|
// These effects should only be committed when components are first mounted, |
|
// aka when there is no current/alternate. |
|
|
|
if (current === null && finishedWork.flags & Update) { |
|
var type = finishedWork.type; |
|
var props = finishedWork.memoizedProps; |
|
commitMount(_instance2, type, props); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case HostText: |
|
{ |
|
// We have no life-cycles associated with text. |
|
break; |
|
} |
|
|
|
case HostPortal: |
|
{ |
|
// We have no life-cycles associated with portals. |
|
break; |
|
} |
|
|
|
case Profiler: |
|
{ |
|
{ |
|
var _finishedWork$memoize2 = finishedWork.memoizedProps, |
|
onCommit = _finishedWork$memoize2.onCommit, |
|
onRender = _finishedWork$memoize2.onRender; |
|
var effectDuration = finishedWork.stateNode.effectDuration; |
|
var commitTime = getCommitTime(); |
|
var phase = current === null ? 'mount' : 'update'; |
|
|
|
{ |
|
if (isCurrentUpdateNested()) { |
|
phase = 'nested-update'; |
|
} |
|
} |
|
|
|
if (typeof onRender === 'function') { |
|
onRender(finishedWork.memoizedProps.id, phase, finishedWork.actualDuration, finishedWork.treeBaseDuration, finishedWork.actualStartTime, commitTime); |
|
} |
|
|
|
{ |
|
if (typeof onCommit === 'function') { |
|
onCommit(finishedWork.memoizedProps.id, phase, effectDuration, commitTime); |
|
} // Schedule a passive effect for this Profiler to call onPostCommit hooks. |
|
// This effect should be scheduled even if there is no onPostCommit callback for this Profiler, |
|
// because the effect is also where times bubble to parent Profilers. |
|
|
|
|
|
enqueuePendingPassiveProfilerEffect(finishedWork); // Propagate layout effect durations to the next nearest Profiler ancestor. |
|
// Do not reset these values until the next render so DevTools has a chance to read them first. |
|
|
|
var parentFiber = finishedWork.return; |
|
|
|
outer: while (parentFiber !== null) { |
|
switch (parentFiber.tag) { |
|
case HostRoot: |
|
var root = parentFiber.stateNode; |
|
root.effectDuration += effectDuration; |
|
break outer; |
|
|
|
case Profiler: |
|
var parentStateNode = parentFiber.stateNode; |
|
parentStateNode.effectDuration += effectDuration; |
|
break outer; |
|
} |
|
|
|
parentFiber = parentFiber.return; |
|
} |
|
} |
|
} |
|
|
|
break; |
|
} |
|
|
|
case SuspenseComponent: |
|
{ |
|
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork); |
|
break; |
|
} |
|
|
|
case SuspenseListComponent: |
|
case IncompleteClassComponent: |
|
case ScopeComponent: |
|
case OffscreenComponent: |
|
case LegacyHiddenComponent: |
|
case TracingMarkerComponent: |
|
{ |
|
break; |
|
} |
|
|
|
default: |
|
throw new Error('This unit of work tag should not have side-effects. This error is ' + 'likely caused by a bug in React. Please file an issue.'); |
|
} |
|
} |
|
|
|
if ( !offscreenSubtreeWasHidden) { |
|
{ |
|
if (finishedWork.flags & Ref) { |
|
commitAttachRef(finishedWork); |
|
} |
|
} |
|
} |
|
} |
|
|
|
function reappearLayoutEffectsOnFiber(node) { |
|
// Turn on layout effects in a tree that previously disappeared. |
|
// TODO (Offscreen) Check: flags & LayoutStatic |
|
switch (node.tag) { |
|
case FunctionComponent: |
|
case ForwardRef: |
|
case SimpleMemoComponent: |
|
{ |
|
if ( node.mode & ProfileMode) { |
|
try { |
|
startLayoutEffectTimer(); |
|
safelyCallCommitHookLayoutEffectListMount(node, node.return); |
|
} finally { |
|
recordLayoutEffectDuration(node); |
|
} |
|
} else { |
|
safelyCallCommitHookLayoutEffectListMount(node, node.return); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case ClassComponent: |
|
{ |
|
var instance = node.stateNode; |
|
|
|
if (typeof instance.componentDidMount === 'function') { |
|
safelyCallComponentDidMount(node, node.return, instance); |
|
} |
|
|
|
safelyAttachRef(node, node.return); |
|
break; |
|
} |
|
|
|
case HostComponent: |
|
{ |
|
safelyAttachRef(node, node.return); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
function hideOrUnhideAllChildren(finishedWork, isHidden) { |
|
// Only hide or unhide the top-most host nodes. |
|
var hostSubtreeRoot = null; |
|
|
|
{ |
|
// We only have the top Fiber that was inserted but we need to recurse down its |
|
// children to find all the terminal nodes. |
|
var node = finishedWork; |
|
|
|
while (true) { |
|
if (node.tag === HostComponent) { |
|
if (hostSubtreeRoot === null) { |
|
hostSubtreeRoot = node; |
|
|
|
try { |
|
var instance = node.stateNode; |
|
|
|
if (isHidden) { |
|
hideInstance(instance); |
|
} else { |
|
unhideInstance(node.stateNode, node.memoizedProps); |
|
} |
|
} catch (error) { |
|
captureCommitPhaseError(finishedWork, finishedWork.return, error); |
|
} |
|
} |
|
} else if (node.tag === HostText) { |
|
if (hostSubtreeRoot === null) { |
|
try { |
|
var _instance3 = node.stateNode; |
|
|
|
if (isHidden) { |
|
hideTextInstance(_instance3); |
|
} else { |
|
unhideTextInstance(_instance3, node.memoizedProps); |
|
} |
|
} catch (error) { |
|
captureCommitPhaseError(finishedWork, finishedWork.return, error); |
|
} |
|
} |
|
} else if ((node.tag === OffscreenComponent || node.tag === LegacyHiddenComponent) && node.memoizedState !== null && node !== finishedWork) ; else if (node.child !== null) { |
|
node.child.return = node; |
|
node = node.child; |
|
continue; |
|
} |
|
|
|
if (node === finishedWork) { |
|
return; |
|
} |
|
|
|
while (node.sibling === null) { |
|
if (node.return === null || node.return === finishedWork) { |
|
return; |
|
} |
|
|
|
if (hostSubtreeRoot === node) { |
|
hostSubtreeRoot = null; |
|
} |
|
|
|
node = node.return; |
|
} |
|
|
|
if (hostSubtreeRoot === node) { |
|
hostSubtreeRoot = null; |
|
} |
|
|
|
node.sibling.return = node.return; |
|
node = node.sibling; |
|
} |
|
} |
|
} |
|
|
|
function commitAttachRef(finishedWork) { |
|
var ref = finishedWork.ref; |
|
|
|
if (ref !== null) { |
|
var instance = finishedWork.stateNode; |
|
var instanceToUse; |
|
|
|
switch (finishedWork.tag) { |
|
case HostComponent: |
|
instanceToUse = getPublicInstance(instance); |
|
break; |
|
|
|
default: |
|
instanceToUse = instance; |
|
} // Moved outside to ensure DCE works with this flag |
|
|
|
if (typeof ref === 'function') { |
|
var retVal; |
|
|
|
if ( finishedWork.mode & ProfileMode) { |
|
try { |
|
startLayoutEffectTimer(); |
|
retVal = ref(instanceToUse); |
|
} finally { |
|
recordLayoutEffectDuration(finishedWork); |
|
} |
|
} else { |
|
retVal = ref(instanceToUse); |
|
} |
|
|
|
{ |
|
if (typeof retVal === 'function') { |
|
error('Unexpected return value from a callback ref in %s. ' + 'A callback ref should not return a function.', getComponentNameFromFiber(finishedWork)); |
|
} |
|
} |
|
} else { |
|
{ |
|
if (!ref.hasOwnProperty('current')) { |
|
error('Unexpected ref object provided for %s. ' + 'Use either a ref-setter function or React.createRef().', getComponentNameFromFiber(finishedWork)); |
|
} |
|
} |
|
|
|
ref.current = instanceToUse; |
|
} |
|
} |
|
} |
|
|
|
function detachFiberMutation(fiber) { |
|
// Cut off the return pointer to disconnect it from the tree. |
|
// This enables us to detect and warn against state updates on an unmounted component. |
|
// It also prevents events from bubbling from within disconnected components. |
|
// |
|
// Ideally, we should also clear the child pointer of the parent alternate to let this |
|
// get GC:ed but we don't know which for sure which parent is the current |
|
// one so we'll settle for GC:ing the subtree of this child. |
|
// This child itself will be GC:ed when the parent updates the next time. |
|
// |
|
// Note that we can't clear child or sibling pointers yet. |
|
// They're needed for passive effects and for findDOMNode. |
|
// We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects). |
|
// |
|
// Don't reset the alternate yet, either. We need that so we can detach the |
|
// alternate's fields in the passive phase. Clearing the return pointer is |
|
// sufficient for findDOMNode semantics. |
|
var alternate = fiber.alternate; |
|
|
|
if (alternate !== null) { |
|
alternate.return = null; |
|
} |
|
|
|
fiber.return = null; |
|
} |
|
|
|
function detachFiberAfterEffects(fiber) { |
|
var alternate = fiber.alternate; |
|
|
|
if (alternate !== null) { |
|
fiber.alternate = null; |
|
detachFiberAfterEffects(alternate); |
|
} // Note: Defensively using negation instead of < in case |
|
// `deletedTreeCleanUpLevel` is undefined. |
|
|
|
|
|
{ |
|
// Clear cyclical Fiber fields. This level alone is designed to roughly |
|
// approximate the planned Fiber refactor. In that world, `setState` will be |
|
// bound to a special "instance" object instead of a Fiber. The Instance |
|
// object will not have any of these fields. It will only be connected to |
|
// the fiber tree via a single link at the root. So if this level alone is |
|
// sufficient to fix memory issues, that bodes well for our plans. |
|
fiber.child = null; |
|
fiber.deletions = null; |
|
fiber.sibling = null; // The `stateNode` is cyclical because on host nodes it points to the host |
|
// tree, which has its own pointers to children, parents, and siblings. |
|
// The other host nodes also point back to fibers, so we should detach that |
|
// one, too. |
|
|
|
if (fiber.tag === HostComponent) { |
|
var hostInstance = fiber.stateNode; |
|
|
|
if (hostInstance !== null) { |
|
detachDeletedInstance(hostInstance); |
|
} |
|
} |
|
|
|
fiber.stateNode = null; // I'm intentionally not clearing the `return` field in this level. We |
|
// already disconnect the `return` pointer at the root of the deleted |
|
// subtree (in `detachFiberMutation`). Besides, `return` by itself is not |
|
// cyclical — it's only cyclical when combined with `child`, `sibling`, and |
|
// `alternate`. But we'll clear it in the next level anyway, just in case. |
|
|
|
{ |
|
fiber._debugOwner = null; |
|
} |
|
|
|
{ |
|
// Theoretically, nothing in here should be necessary, because we already |
|
// disconnected the fiber from the tree. So even if something leaks this |
|
// particular fiber, it won't leak anything else |
|
// |
|
// The purpose of this branch is to be super aggressive so we can measure |
|
// if there's any difference in memory impact. If there is, that could |
|
// indicate a React leak we don't know about. |
|
fiber.return = null; |
|
fiber.dependencies = null; |
|
fiber.memoizedProps = null; |
|
fiber.memoizedState = null; |
|
fiber.pendingProps = null; |
|
fiber.stateNode = null; // TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead. |
|
|
|
fiber.updateQueue = null; |
|
} |
|
} |
|
} |
|
|
|
function getHostParentFiber(fiber) { |
|
var parent = fiber.return; |
|
|
|
while (parent !== null) { |
|
if (isHostParent(parent)) { |
|
return parent; |
|
} |
|
|
|
parent = parent.return; |
|
} |
|
|
|
throw new Error('Expected to find a host parent. This error is likely caused by a bug ' + 'in React. Please file an issue.'); |
|
} |
|
|
|
function isHostParent(fiber) { |
|
return fiber.tag === HostComponent || fiber.tag === HostRoot || fiber.tag === HostPortal; |
|
} |
|
|
|
function getHostSibling(fiber) { |
|
// We're going to search forward into the tree until we find a sibling host |
|
// node. Unfortunately, if multiple insertions are done in a row we have to |
|
// search past them. This leads to exponential search for the next sibling. |
|
// TODO: Find a more efficient way to do this. |
|
var node = fiber; |
|
|
|
siblings: while (true) { |
|
// If we didn't find anything, let's try the next sibling. |
|
while (node.sibling === null) { |
|
if (node.return === null || isHostParent(node.return)) { |
|
// If we pop out of the root or hit the parent the fiber we are the |
|
// last sibling. |
|
return null; |
|
} |
|
|
|
node = node.return; |
|
} |
|
|
|
node.sibling.return = node.return; |
|
node = node.sibling; |
|
|
|
while (node.tag !== HostComponent && node.tag !== HostText && node.tag !== DehydratedFragment) { |
|
// If it is not host node and, we might have a host node inside it. |
|
// Try to search down until we find one. |
|
if (node.flags & Placement) { |
|
// If we don't have a child, try the siblings instead. |
|
continue siblings; |
|
} // If we don't have a child, try the siblings instead. |
|
// We also skip portals because they are not part of this host tree. |
|
|
|
|
|
if (node.child === null || node.tag === HostPortal) { |
|
continue siblings; |
|
} else { |
|
node.child.return = node; |
|
node = node.child; |
|
} |
|
} // Check if this host node is stable or about to be placed. |
|
|
|
|
|
if (!(node.flags & Placement)) { |
|
// Found it! |
|
return node.stateNode; |
|
} |
|
} |
|
} |
|
|
|
function commitPlacement(finishedWork) { |
|
|
|
|
|
var parentFiber = getHostParentFiber(finishedWork); // Note: these two variables *must* always be updated together. |
|
|
|
switch (parentFiber.tag) { |
|
case HostComponent: |
|
{ |
|
var parent = parentFiber.stateNode; |
|
|
|
if (parentFiber.flags & ContentReset) { |
|
// Reset the text content of the parent before doing any insertions |
|
resetTextContent(parent); // Clear ContentReset from the effect tag |
|
|
|
parentFiber.flags &= ~ContentReset; |
|
} |
|
|
|
var before = getHostSibling(finishedWork); // We only have the top Fiber that was inserted but we need to recurse down its |
|
// children to find all the terminal nodes. |
|
|
|
insertOrAppendPlacementNode(finishedWork, before, parent); |
|
break; |
|
} |
|
|
|
case HostRoot: |
|
case HostPortal: |
|
{ |
|
var _parent = parentFiber.stateNode.containerInfo; |
|
|
|
var _before = getHostSibling(finishedWork); |
|
|
|
insertOrAppendPlacementNodeIntoContainer(finishedWork, _before, _parent); |
|
break; |
|
} |
|
// eslint-disable-next-line-no-fallthrough |
|
|
|
default: |
|
throw new Error('Invalid host parent fiber. This error is likely caused by a bug ' + 'in React. Please file an issue.'); |
|
} |
|
} |
|
|
|
function insertOrAppendPlacementNodeIntoContainer(node, before, parent) { |
|
var tag = node.tag; |
|
var isHost = tag === HostComponent || tag === HostText; |
|
|
|
if (isHost) { |
|
var stateNode = node.stateNode; |
|
|
|
if (before) { |
|
insertInContainerBefore(parent, stateNode, before); |
|
} else { |
|
appendChildToContainer(parent, stateNode); |
|
} |
|
} else if (tag === HostPortal) ; else { |
|
var child = node.child; |
|
|
|
if (child !== null) { |
|
insertOrAppendPlacementNodeIntoContainer(child, before, parent); |
|
var sibling = child.sibling; |
|
|
|
while (sibling !== null) { |
|
insertOrAppendPlacementNodeIntoContainer(sibling, before, parent); |
|
sibling = sibling.sibling; |
|
} |
|
} |
|
} |
|
} |
|
|
|
function insertOrAppendPlacementNode(node, before, parent) { |
|
var tag = node.tag; |
|
var isHost = tag === HostComponent || tag === HostText; |
|
|
|
if (isHost) { |
|
var stateNode = node.stateNode; |
|
|
|
if (before) { |
|
insertBefore(parent, stateNode, before); |
|
} else { |
|
appendChild(parent, stateNode); |
|
} |
|
} else if (tag === HostPortal) ; else { |
|
var child = node.child; |
|
|
|
if (child !== null) { |
|
insertOrAppendPlacementNode(child, before, parent); |
|
var sibling = child.sibling; |
|
|
|
while (sibling !== null) { |
|
insertOrAppendPlacementNode(sibling, before, parent); |
|
sibling = sibling.sibling; |
|
} |
|
} |
|
} |
|
} // These are tracked on the stack as we recursively traverse a |
|
// deleted subtree. |
|
// TODO: Update these during the whole mutation phase, not just during |
|
// a deletion. |
|
|
|
|
|
var hostParent = null; |
|
var hostParentIsContainer = false; |
|
|
|
function commitDeletionEffects(root, returnFiber, deletedFiber) { |
|
{ |
|
// We only have the top Fiber that was deleted but we need to recurse down its |
|
// children to find all the terminal nodes. |
|
// Recursively delete all host nodes from the parent, detach refs, clean |
|
// up mounted layout effects, and call componentWillUnmount. |
|
// We only need to remove the topmost host child in each branch. But then we |
|
// still need to keep traversing to unmount effects, refs, and cWU. TODO: We |
|
// could split this into two separate traversals functions, where the second |
|
// one doesn't include any removeChild logic. This is maybe the same |
|
// function as "disappearLayoutEffects" (or whatever that turns into after |
|
// the layout phase is refactored to use recursion). |
|
// Before starting, find the nearest host parent on the stack so we know |
|
// which instance/container to remove the children from. |
|
// TODO: Instead of searching up the fiber return path on every deletion, we |
|
// can track the nearest host component on the JS stack as we traverse the |
|
// tree during the commit phase. This would make insertions faster, too. |
|
var parent = returnFiber; |
|
|
|
findParent: while (parent !== null) { |
|
switch (parent.tag) { |
|
case HostComponent: |
|
{ |
|
hostParent = parent.stateNode; |
|
hostParentIsContainer = false; |
|
break findParent; |
|
} |
|
|
|
case HostRoot: |
|
{ |
|
hostParent = parent.stateNode.containerInfo; |
|
hostParentIsContainer = true; |
|
break findParent; |
|
} |
|
|
|
case HostPortal: |
|
{ |
|
hostParent = parent.stateNode.containerInfo; |
|
hostParentIsContainer = true; |
|
break findParent; |
|
} |
|
} |
|
|
|
parent = parent.return; |
|
} |
|
|
|
if (hostParent === null) { |
|
throw new Error('Expected to find a host parent. This error is likely caused by ' + 'a bug in React. Please file an issue.'); |
|
} |
|
|
|
commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber); |
|
hostParent = null; |
|
hostParentIsContainer = false; |
|
} |
|
|
|
detachFiberMutation(deletedFiber); |
|
} |
|
|
|
function recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, parent) { |
|
// TODO: Use a static flag to skip trees that don't have unmount effects |
|
var child = parent.child; |
|
|
|
while (child !== null) { |
|
commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child); |
|
child = child.sibling; |
|
} |
|
} |
|
|
|
function commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, deletedFiber) { |
|
onCommitUnmount(deletedFiber); // The cases in this outer switch modify the stack before they traverse |
|
// into their subtree. There are simpler cases in the inner switch |
|
// that don't modify the stack. |
|
|
|
switch (deletedFiber.tag) { |
|
case HostComponent: |
|
{ |
|
if (!offscreenSubtreeWasHidden) { |
|
safelyDetachRef(deletedFiber, nearestMountedAncestor); |
|
} // Intentional fallthrough to next branch |
|
|
|
} |
|
// eslint-disable-next-line-no-fallthrough |
|
|
|
case HostText: |
|
{ |
|
// We only need to remove the nearest host child. Set the host parent |
|
// to `null` on the stack to indicate that nested children don't |
|
// need to be removed. |
|
{ |
|
var prevHostParent = hostParent; |
|
var prevHostParentIsContainer = hostParentIsContainer; |
|
hostParent = null; |
|
recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber); |
|
hostParent = prevHostParent; |
|
hostParentIsContainer = prevHostParentIsContainer; |
|
|
|
if (hostParent !== null) { |
|
// Now that all the child effects have unmounted, we can remove the |
|
// node from the tree. |
|
if (hostParentIsContainer) { |
|
removeChildFromContainer(hostParent, deletedFiber.stateNode); |
|
} else { |
|
removeChild(hostParent, deletedFiber.stateNode); |
|
} |
|
} |
|
} |
|
|
|
return; |
|
} |
|
|
|
case DehydratedFragment: |
|
{ |
|
// Delete the dehydrated suspense boundary and all of its content. |
|
|
|
|
|
{ |
|
if (hostParent !== null) { |
|
if (hostParentIsContainer) { |
|
clearSuspenseBoundaryFromContainer(hostParent, deletedFiber.stateNode); |
|
} else { |
|
clearSuspenseBoundary(hostParent, deletedFiber.stateNode); |
|
} |
|
} |
|
} |
|
|
|
return; |
|
} |
|
|
|
case HostPortal: |
|
{ |
|
{ |
|
// When we go into a portal, it becomes the parent to remove from. |
|
var _prevHostParent = hostParent; |
|
var _prevHostParentIsContainer = hostParentIsContainer; |
|
hostParent = deletedFiber.stateNode.containerInfo; |
|
hostParentIsContainer = true; |
|
recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber); |
|
hostParent = _prevHostParent; |
|
hostParentIsContainer = _prevHostParentIsContainer; |
|
} |
|
|
|
return; |
|
} |
|
|
|
case FunctionComponent: |
|
case ForwardRef: |
|
case MemoComponent: |
|
case SimpleMemoComponent: |
|
{ |
|
if (!offscreenSubtreeWasHidden) { |
|
var updateQueue = deletedFiber.updateQueue; |
|
|
|
if (updateQueue !== null) { |
|
var lastEffect = updateQueue.lastEffect; |
|
|
|
if (lastEffect !== null) { |
|
var firstEffect = lastEffect.next; |
|
var effect = firstEffect; |
|
|
|
do { |
|
var _effect = effect, |
|
destroy = _effect.destroy, |
|
tag = _effect.tag; |
|
|
|
if (destroy !== undefined) { |
|
if ((tag & Insertion) !== NoFlags$1) { |
|
safelyCallDestroy(deletedFiber, nearestMountedAncestor, destroy); |
|
} else if ((tag & Layout) !== NoFlags$1) { |
|
{ |
|
markComponentLayoutEffectUnmountStarted(deletedFiber); |
|
} |
|
|
|
if ( deletedFiber.mode & ProfileMode) { |
|
startLayoutEffectTimer(); |
|
safelyCallDestroy(deletedFiber, nearestMountedAncestor, destroy); |
|
recordLayoutEffectDuration(deletedFiber); |
|
} else { |
|
safelyCallDestroy(deletedFiber, nearestMountedAncestor, destroy); |
|
} |
|
|
|
{ |
|
markComponentLayoutEffectUnmountStopped(); |
|
} |
|
} |
|
} |
|
|
|
effect = effect.next; |
|
} while (effect !== firstEffect); |
|
} |
|
} |
|
} |
|
|
|
recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber); |
|
return; |
|
} |
|
|
|
case ClassComponent: |
|
{ |
|
if (!offscreenSubtreeWasHidden) { |
|
safelyDetachRef(deletedFiber, nearestMountedAncestor); |
|
var instance = deletedFiber.stateNode; |
|
|
|
if (typeof instance.componentWillUnmount === 'function') { |
|
safelyCallComponentWillUnmount(deletedFiber, nearestMountedAncestor, instance); |
|
} |
|
} |
|
|
|
recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber); |
|
return; |
|
} |
|
|
|
case ScopeComponent: |
|
{ |
|
|
|
recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber); |
|
return; |
|
} |
|
|
|
case OffscreenComponent: |
|
{ |
|
if ( // TODO: Remove this dead flag |
|
deletedFiber.mode & ConcurrentMode) { |
|
// If this offscreen component is hidden, we already unmounted it. Before |
|
// deleting the children, track that it's already unmounted so that we |
|
// don't attempt to unmount the effects again. |
|
// TODO: If the tree is hidden, in most cases we should be able to skip |
|
// over the nested children entirely. An exception is we haven't yet found |
|
// the topmost host node to delete, which we already track on the stack. |
|
// But the other case is portals, which need to be detached no matter how |
|
// deeply they are nested. We should use a subtree flag to track whether a |
|
// subtree includes a nested portal. |
|
var prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden; |
|
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null; |
|
recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber); |
|
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden; |
|
} else { |
|
recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber); |
|
} |
|
|
|
break; |
|
} |
|
|
|
default: |
|
{ |
|
recursivelyTraverseDeletionEffects(finishedRoot, nearestMountedAncestor, deletedFiber); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
function commitSuspenseCallback(finishedWork) { |
|
// TODO: Move this to passive phase |
|
var newState = finishedWork.memoizedState; |
|
} |
|
|
|
function commitSuspenseHydrationCallbacks(finishedRoot, finishedWork) { |
|
|
|
var newState = finishedWork.memoizedState; |
|
|
|
if (newState === null) { |
|
var current = finishedWork.alternate; |
|
|
|
if (current !== null) { |
|
var prevState = current.memoizedState; |
|
|
|
if (prevState !== null) { |
|
var suspenseInstance = prevState.dehydrated; |
|
|
|
if (suspenseInstance !== null) { |
|
commitHydratedSuspenseInstance(suspenseInstance); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
function attachSuspenseRetryListeners(finishedWork) { |
|
// If this boundary just timed out, then it will have a set of wakeables. |
|
// For each wakeable, attach a listener so that when it resolves, React |
|
// attempts to re-render the boundary in the primary (pre-timeout) state. |
|
var wakeables = finishedWork.updateQueue; |
|
|
|
if (wakeables !== null) { |
|
finishedWork.updateQueue = null; |
|
var retryCache = finishedWork.stateNode; |
|
|
|
if (retryCache === null) { |
|
retryCache = finishedWork.stateNode = new PossiblyWeakSet(); |
|
} |
|
|
|
wakeables.forEach(function (wakeable) { |
|
// Memoize using the boundary fiber to prevent redundant listeners. |
|
var retry = resolveRetryWakeable.bind(null, finishedWork, wakeable); |
|
|
|
if (!retryCache.has(wakeable)) { |
|
retryCache.add(wakeable); |
|
|
|
{ |
|
if (isDevToolsPresent) { |
|
if (inProgressLanes !== null && inProgressRoot !== null) { |
|
// If we have pending work still, associate the original updaters with it. |
|
restorePendingUpdaters(inProgressRoot, inProgressLanes); |
|
} else { |
|
throw Error('Expected finished root and lanes to be set. This is a bug in React.'); |
|
} |
|
} |
|
} |
|
|
|
wakeable.then(retry, retry); |
|
} |
|
}); |
|
} |
|
} // This function detects when a Suspense boundary goes from visible to hidden. |
|
function commitMutationEffects(root, finishedWork, committedLanes) { |
|
inProgressLanes = committedLanes; |
|
inProgressRoot = root; |
|
setCurrentFiber(finishedWork); |
|
commitMutationEffectsOnFiber(finishedWork, root); |
|
setCurrentFiber(finishedWork); |
|
inProgressLanes = null; |
|
inProgressRoot = null; |
|
} |
|
|
|
function recursivelyTraverseMutationEffects(root, parentFiber, lanes) { |
|
// Deletions effects can be scheduled on any fiber type. They need to happen |
|
// before the children effects hae fired. |
|
var deletions = parentFiber.deletions; |
|
|
|
if (deletions !== null) { |
|
for (var i = 0; i < deletions.length; i++) { |
|
var childToDelete = deletions[i]; |
|
|
|
try { |
|
commitDeletionEffects(root, parentFiber, childToDelete); |
|
} catch (error) { |
|
captureCommitPhaseError(childToDelete, parentFiber, error); |
|
} |
|
} |
|
} |
|
|
|
var prevDebugFiber = getCurrentFiber(); |
|
|
|
if (parentFiber.subtreeFlags & MutationMask) { |
|
var child = parentFiber.child; |
|
|
|
while (child !== null) { |
|
setCurrentFiber(child); |
|
commitMutationEffectsOnFiber(child, root); |
|
child = child.sibling; |
|
} |
|
} |
|
|
|
setCurrentFiber(prevDebugFiber); |
|
} |
|
|
|
function commitMutationEffectsOnFiber(finishedWork, root, lanes) { |
|
var current = finishedWork.alternate; |
|
var flags = finishedWork.flags; // The effect flag should be checked *after* we refine the type of fiber, |
|
// because the fiber tag is more specific. An exception is any flag related |
|
// to reconcilation, because those can be set on all fiber types. |
|
|
|
switch (finishedWork.tag) { |
|
case FunctionComponent: |
|
case ForwardRef: |
|
case MemoComponent: |
|
case SimpleMemoComponent: |
|
{ |
|
recursivelyTraverseMutationEffects(root, finishedWork); |
|
commitReconciliationEffects(finishedWork); |
|
|
|
if (flags & Update) { |
|
try { |
|
commitHookEffectListUnmount(Insertion | HasEffect, finishedWork, finishedWork.return); |
|
commitHookEffectListMount(Insertion | HasEffect, finishedWork); |
|
} catch (error) { |
|
captureCommitPhaseError(finishedWork, finishedWork.return, error); |
|
} // Layout effects are destroyed during the mutation phase so that all |
|
// destroy functions for all fibers are called before any create functions. |
|
// This prevents sibling component effects from interfering with each other, |
|
// e.g. a destroy function in one component should never override a ref set |
|
// by a create function in another component during the same commit. |
|
|
|
|
|
if ( finishedWork.mode & ProfileMode) { |
|
try { |
|
startLayoutEffectTimer(); |
|
commitHookEffectListUnmount(Layout | HasEffect, finishedWork, finishedWork.return); |
|
} catch (error) { |
|
captureCommitPhaseError(finishedWork, finishedWork.return, error); |
|
} |
|
|
|
recordLayoutEffectDuration(finishedWork); |
|
} else { |
|
try { |
|
commitHookEffectListUnmount(Layout | HasEffect, finishedWork, finishedWork.return); |
|
} catch (error) { |
|
captureCommitPhaseError(finishedWork, finishedWork.return, error); |
|
} |
|
} |
|
} |
|
|
|
return; |
|
} |
|
|
|
case ClassComponent: |
|
{ |
|
recursivelyTraverseMutationEffects(root, finishedWork); |
|
commitReconciliationEffects(finishedWork); |
|
|
|
if (flags & Ref) { |
|
if (current !== null) { |
|
safelyDetachRef(current, current.return); |
|
} |
|
} |
|
|
|
return; |
|
} |
|
|
|
case HostComponent: |
|
{ |
|
recursivelyTraverseMutationEffects(root, finishedWork); |
|
commitReconciliationEffects(finishedWork); |
|
|
|
if (flags & Ref) { |
|
if (current !== null) { |
|
safelyDetachRef(current, current.return); |
|
} |
|
} |
|
|
|
{ |
|
// TODO: ContentReset gets cleared by the children during the commit |
|
// phase. This is a refactor hazard because it means we must read |
|
// flags the flags after `commitReconciliationEffects` has already run; |
|
// the order matters. We should refactor so that ContentReset does not |
|
// rely on mutating the flag during commit. Like by setting a flag |
|
// during the render phase instead. |
|
if (finishedWork.flags & ContentReset) { |
|
var instance = finishedWork.stateNode; |
|
|
|
try { |
|
resetTextContent(instance); |
|
} catch (error) { |
|
captureCommitPhaseError(finishedWork, finishedWork.return, error); |
|
} |
|
} |
|
|
|
if (flags & Update) { |
|
var _instance4 = finishedWork.stateNode; |
|
|
|
if (_instance4 != null) { |
|
// Commit the work prepared earlier. |
|
var newProps = finishedWork.memoizedProps; // For hydration we reuse the update path but we treat the oldProps |
|
// as the newProps. The updatePayload will contain the real change in |
|
// this case. |
|
|
|
var oldProps = current !== null ? current.memoizedProps : newProps; |
|
var type = finishedWork.type; // TODO: Type the updateQueue to be specific to host components. |
|
|
|
var updatePayload = finishedWork.updateQueue; |
|
finishedWork.updateQueue = null; |
|
|
|
if (updatePayload !== null) { |
|
try { |
|
commitUpdate(_instance4, updatePayload, type, oldProps, newProps, finishedWork); |
|
} catch (error) { |
|
captureCommitPhaseError(finishedWork, finishedWork.return, error); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
return; |
|
} |
|
|
|
case HostText: |
|
{ |
|
recursivelyTraverseMutationEffects(root, finishedWork); |
|
commitReconciliationEffects(finishedWork); |
|
|
|
if (flags & Update) { |
|
{ |
|
if (finishedWork.stateNode === null) { |
|
throw new Error('This should have a text node initialized. This error is likely ' + 'caused by a bug in React. Please file an issue.'); |
|
} |
|
|
|
var textInstance = finishedWork.stateNode; |
|
var newText = finishedWork.memoizedProps; // For hydration we reuse the update path but we treat the oldProps |
|
// as the newProps. The updatePayload will contain the real change in |
|
// this case. |
|
|
|
var oldText = current !== null ? current.memoizedProps : newText; |
|
|
|
try { |
|
commitTextUpdate(textInstance, oldText, newText); |
|
} catch (error) { |
|
captureCommitPhaseError(finishedWork, finishedWork.return, error); |
|
} |
|
} |
|
} |
|
|
|
return; |
|
} |
|
|
|
case HostRoot: |
|
{ |
|
recursivelyTraverseMutationEffects(root, finishedWork); |
|
commitReconciliationEffects(finishedWork); |
|
|
|
if (flags & Update) { |
|
{ |
|
if (current !== null) { |
|
var prevRootState = current.memoizedState; |
|
|
|
if (prevRootState.isDehydrated) { |
|
try { |
|
commitHydratedContainer(root.containerInfo); |
|
} catch (error) { |
|
captureCommitPhaseError(finishedWork, finishedWork.return, error); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
return; |
|
} |
|
|
|
case HostPortal: |
|
{ |
|
recursivelyTraverseMutationEffects(root, finishedWork); |
|
commitReconciliationEffects(finishedWork); |
|
|
|
return; |
|
} |
|
|
|
case SuspenseComponent: |
|
{ |
|
recursivelyTraverseMutationEffects(root, finishedWork); |
|
commitReconciliationEffects(finishedWork); |
|
var offscreenFiber = finishedWork.child; |
|
|
|
if (offscreenFiber.flags & Visibility) { |
|
var offscreenInstance = offscreenFiber.stateNode; |
|
var newState = offscreenFiber.memoizedState; |
|
var isHidden = newState !== null; // Track the current state on the Offscreen instance so we can |
|
// read it during an event |
|
|
|
offscreenInstance.isHidden = isHidden; |
|
|
|
if (isHidden) { |
|
var wasHidden = offscreenFiber.alternate !== null && offscreenFiber.alternate.memoizedState !== null; |
|
|
|
if (!wasHidden) { |
|
// TODO: Move to passive phase |
|
markCommitTimeOfFallback(); |
|
} |
|
} |
|
} |
|
|
|
if (flags & Update) { |
|
try { |
|
commitSuspenseCallback(finishedWork); |
|
} catch (error) { |
|
captureCommitPhaseError(finishedWork, finishedWork.return, error); |
|
} |
|
|
|
attachSuspenseRetryListeners(finishedWork); |
|
} |
|
|
|
return; |
|
} |
|
|
|
case OffscreenComponent: |
|
{ |
|
var _wasHidden = current !== null && current.memoizedState !== null; |
|
|
|
if ( // TODO: Remove this dead flag |
|
finishedWork.mode & ConcurrentMode) { |
|
// Before committing the children, track on the stack whether this |
|
// offscreen subtree was already hidden, so that we don't unmount the |
|
// effects again. |
|
var prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden; |
|
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || _wasHidden; |
|
recursivelyTraverseMutationEffects(root, finishedWork); |
|
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden; |
|
} else { |
|
recursivelyTraverseMutationEffects(root, finishedWork); |
|
} |
|
|
|
commitReconciliationEffects(finishedWork); |
|
|
|
if (flags & Visibility) { |
|
var _offscreenInstance = finishedWork.stateNode; |
|
var _newState = finishedWork.memoizedState; |
|
|
|
var _isHidden = _newState !== null; |
|
|
|
var offscreenBoundary = finishedWork; // Track the current state on the Offscreen instance so we can |
|
// read it during an event |
|
|
|
_offscreenInstance.isHidden = _isHidden; |
|
|
|
{ |
|
if (_isHidden) { |
|
if (!_wasHidden) { |
|
if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) { |
|
nextEffect = offscreenBoundary; |
|
var offscreenChild = offscreenBoundary.child; |
|
|
|
while (offscreenChild !== null) { |
|
nextEffect = offscreenChild; |
|
disappearLayoutEffects_begin(offscreenChild); |
|
offscreenChild = offscreenChild.sibling; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
{ |
|
// TODO: This needs to run whenever there's an insertion or update |
|
// inside a hidden Offscreen tree. |
|
hideOrUnhideAllChildren(offscreenBoundary, _isHidden); |
|
} |
|
} |
|
|
|
return; |
|
} |
|
|
|
case SuspenseListComponent: |
|
{ |
|
recursivelyTraverseMutationEffects(root, finishedWork); |
|
commitReconciliationEffects(finishedWork); |
|
|
|
if (flags & Update) { |
|
attachSuspenseRetryListeners(finishedWork); |
|
} |
|
|
|
return; |
|
} |
|
|
|
case ScopeComponent: |
|
{ |
|
|
|
return; |
|
} |
|
|
|
default: |
|
{ |
|
recursivelyTraverseMutationEffects(root, finishedWork); |
|
commitReconciliationEffects(finishedWork); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
function commitReconciliationEffects(finishedWork) { |
|
// Placement effects (insertions, reorders) can be scheduled on any fiber |
|
// type. They needs to happen after the children effects have fired, but |
|
// before the effects on this fiber have fired. |
|
var flags = finishedWork.flags; |
|
|
|
if (flags & Placement) { |
|
try { |
|
commitPlacement(finishedWork); |
|
} catch (error) { |
|
captureCommitPhaseError(finishedWork, finishedWork.return, error); |
|
} // Clear the "placement" from effect tag so that we know that this is |
|
// inserted, before any life-cycles like componentDidMount gets called. |
|
// TODO: findDOMNode doesn't rely on this any more but isMounted does |
|
// and isMounted is deprecated anyway so we should be able to kill this. |
|
|
|
|
|
finishedWork.flags &= ~Placement; |
|
} |
|
|
|
if (flags & Hydrating) { |
|
finishedWork.flags &= ~Hydrating; |
|
} |
|
} |
|
|
|
function commitLayoutEffects(finishedWork, root, committedLanes) { |
|
inProgressLanes = committedLanes; |
|
inProgressRoot = root; |
|
nextEffect = finishedWork; |
|
commitLayoutEffects_begin(finishedWork, root, committedLanes); |
|
inProgressLanes = null; |
|
inProgressRoot = null; |
|
} |
|
|
|
function commitLayoutEffects_begin(subtreeRoot, root, committedLanes) { |
|
// Suspense layout effects semantics don't change for legacy roots. |
|
var isModernRoot = (subtreeRoot.mode & ConcurrentMode) !== NoMode; |
|
|
|
while (nextEffect !== null) { |
|
var fiber = nextEffect; |
|
var firstChild = fiber.child; |
|
|
|
if ( fiber.tag === OffscreenComponent && isModernRoot) { |
|
// Keep track of the current Offscreen stack's state. |
|
var isHidden = fiber.memoizedState !== null; |
|
var newOffscreenSubtreeIsHidden = isHidden || offscreenSubtreeIsHidden; |
|
|
|
if (newOffscreenSubtreeIsHidden) { |
|
// The Offscreen tree is hidden. Skip over its layout effects. |
|
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes); |
|
continue; |
|
} else { |
|
// TODO (Offscreen) Also check: subtreeFlags & LayoutMask |
|
var current = fiber.alternate; |
|
var wasHidden = current !== null && current.memoizedState !== null; |
|
var newOffscreenSubtreeWasHidden = wasHidden || offscreenSubtreeWasHidden; |
|
var prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden; |
|
var prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden; // Traverse the Offscreen subtree with the current Offscreen as the root. |
|
|
|
offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden; |
|
offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden; |
|
|
|
if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) { |
|
// This is the root of a reappearing boundary. Turn its layout effects |
|
// back on. |
|
nextEffect = fiber; |
|
reappearLayoutEffects_begin(fiber); |
|
} |
|
|
|
var child = firstChild; |
|
|
|
while (child !== null) { |
|
nextEffect = child; |
|
commitLayoutEffects_begin(child, // New root; bubble back up to here and stop. |
|
root, committedLanes); |
|
child = child.sibling; |
|
} // Restore Offscreen state and resume in our-progress traversal. |
|
|
|
|
|
nextEffect = fiber; |
|
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden; |
|
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden; |
|
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes); |
|
continue; |
|
} |
|
} |
|
|
|
if ((fiber.subtreeFlags & LayoutMask) !== NoFlags && firstChild !== null) { |
|
firstChild.return = fiber; |
|
nextEffect = firstChild; |
|
} else { |
|
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes); |
|
} |
|
} |
|
} |
|
|
|
function commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes) { |
|
while (nextEffect !== null) { |
|
var fiber = nextEffect; |
|
|
|
if ((fiber.flags & LayoutMask) !== NoFlags) { |
|
var current = fiber.alternate; |
|
setCurrentFiber(fiber); |
|
|
|
try { |
|
commitLayoutEffectOnFiber(root, current, fiber, committedLanes); |
|
} catch (error) { |
|
captureCommitPhaseError(fiber, fiber.return, error); |
|
} |
|
|
|
resetCurrentFiber(); |
|
} |
|
|
|
if (fiber === subtreeRoot) { |
|
nextEffect = null; |
|
return; |
|
} |
|
|
|
var sibling = fiber.sibling; |
|
|
|
if (sibling !== null) { |
|
sibling.return = fiber.return; |
|
nextEffect = sibling; |
|
return; |
|
} |
|
|
|
nextEffect = fiber.return; |
|
} |
|
} |
|
|
|
function disappearLayoutEffects_begin(subtreeRoot) { |
|
while (nextEffect !== null) { |
|
var fiber = nextEffect; |
|
var firstChild = fiber.child; // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic) |
|
|
|
switch (fiber.tag) { |
|
case FunctionComponent: |
|
case ForwardRef: |
|
case MemoComponent: |
|
case SimpleMemoComponent: |
|
{ |
|
if ( fiber.mode & ProfileMode) { |
|
try { |
|
startLayoutEffectTimer(); |
|
commitHookEffectListUnmount(Layout, fiber, fiber.return); |
|
} finally { |
|
recordLayoutEffectDuration(fiber); |
|
} |
|
} else { |
|
commitHookEffectListUnmount(Layout, fiber, fiber.return); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case ClassComponent: |
|
{ |
|
// TODO (Offscreen) Check: flags & RefStatic |
|
safelyDetachRef(fiber, fiber.return); |
|
var instance = fiber.stateNode; |
|
|
|
if (typeof instance.componentWillUnmount === 'function') { |
|
safelyCallComponentWillUnmount(fiber, fiber.return, instance); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case HostComponent: |
|
{ |
|
safelyDetachRef(fiber, fiber.return); |
|
break; |
|
} |
|
|
|
case OffscreenComponent: |
|
{ |
|
// Check if this is a |
|
var isHidden = fiber.memoizedState !== null; |
|
|
|
if (isHidden) { |
|
// Nested Offscreen tree is already hidden. Don't disappear |
|
// its effects. |
|
disappearLayoutEffects_complete(subtreeRoot); |
|
continue; |
|
} |
|
|
|
break; |
|
} |
|
} // TODO (Offscreen) Check: subtreeFlags & LayoutStatic |
|
|
|
|
|
if (firstChild !== null) { |
|
firstChild.return = fiber; |
|
nextEffect = firstChild; |
|
} else { |
|
disappearLayoutEffects_complete(subtreeRoot); |
|
} |
|
} |
|
} |
|
|
|
function disappearLayoutEffects_complete(subtreeRoot) { |
|
while (nextEffect !== null) { |
|
var fiber = nextEffect; |
|
|
|
if (fiber === subtreeRoot) { |
|
nextEffect = null; |
|
return; |
|
} |
|
|
|
var sibling = fiber.sibling; |
|
|
|
if (sibling !== null) { |
|
sibling.return = fiber.return; |
|
nextEffect = sibling; |
|
return; |
|
} |
|
|
|
nextEffect = fiber.return; |
|
} |
|
} |
|
|
|
function reappearLayoutEffects_begin(subtreeRoot) { |
|
while (nextEffect !== null) { |
|
var fiber = nextEffect; |
|
var firstChild = fiber.child; |
|
|
|
if (fiber.tag === OffscreenComponent) { |
|
var isHidden = fiber.memoizedState !== null; |
|
|
|
if (isHidden) { |
|
// Nested Offscreen tree is still hidden. Don't re-appear its effects. |
|
reappearLayoutEffects_complete(subtreeRoot); |
|
continue; |
|
} |
|
} // TODO (Offscreen) Check: subtreeFlags & LayoutStatic |
|
|
|
|
|
if (firstChild !== null) { |
|
// This node may have been reused from a previous render, so we can't |
|
// assume its return pointer is correct. |
|
firstChild.return = fiber; |
|
nextEffect = firstChild; |
|
} else { |
|
reappearLayoutEffects_complete(subtreeRoot); |
|
} |
|
} |
|
} |
|
|
|
function reappearLayoutEffects_complete(subtreeRoot) { |
|
while (nextEffect !== null) { |
|
var fiber = nextEffect; // TODO (Offscreen) Check: flags & LayoutStatic |
|
|
|
setCurrentFiber(fiber); |
|
|
|
try { |
|
reappearLayoutEffectsOnFiber(fiber); |
|
} catch (error) { |
|
captureCommitPhaseError(fiber, fiber.return, error); |
|
} |
|
|
|
resetCurrentFiber(); |
|
|
|
if (fiber === subtreeRoot) { |
|
nextEffect = null; |
|
return; |
|
} |
|
|
|
var sibling = fiber.sibling; |
|
|
|
if (sibling !== null) { |
|
// This node may have been reused from a previous render, so we can't |
|
// assume its return pointer is correct. |
|
sibling.return = fiber.return; |
|
nextEffect = sibling; |
|
return; |
|
} |
|
|
|
nextEffect = fiber.return; |
|
} |
|
} |
|
|
|
function commitPassiveMountEffects(root, finishedWork, committedLanes, committedTransitions) { |
|
nextEffect = finishedWork; |
|
commitPassiveMountEffects_begin(finishedWork, root, committedLanes, committedTransitions); |
|
} |
|
|
|
function commitPassiveMountEffects_begin(subtreeRoot, root, committedLanes, committedTransitions) { |
|
while (nextEffect !== null) { |
|
var fiber = nextEffect; |
|
var firstChild = fiber.child; |
|
|
|
if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && firstChild !== null) { |
|
firstChild.return = fiber; |
|
nextEffect = firstChild; |
|
} else { |
|
commitPassiveMountEffects_complete(subtreeRoot, root, committedLanes, committedTransitions); |
|
} |
|
} |
|
} |
|
|
|
function commitPassiveMountEffects_complete(subtreeRoot, root, committedLanes, committedTransitions) { |
|
while (nextEffect !== null) { |
|
var fiber = nextEffect; |
|
|
|
if ((fiber.flags & Passive) !== NoFlags) { |
|
setCurrentFiber(fiber); |
|
|
|
try { |
|
commitPassiveMountOnFiber(root, fiber, committedLanes, committedTransitions); |
|
} catch (error) { |
|
captureCommitPhaseError(fiber, fiber.return, error); |
|
} |
|
|
|
resetCurrentFiber(); |
|
} |
|
|
|
if (fiber === subtreeRoot) { |
|
nextEffect = null; |
|
return; |
|
} |
|
|
|
var sibling = fiber.sibling; |
|
|
|
if (sibling !== null) { |
|
sibling.return = fiber.return; |
|
nextEffect = sibling; |
|
return; |
|
} |
|
|
|
nextEffect = fiber.return; |
|
} |
|
} |
|
|
|
function commitPassiveMountOnFiber(finishedRoot, finishedWork, committedLanes, committedTransitions) { |
|
switch (finishedWork.tag) { |
|
case FunctionComponent: |
|
case ForwardRef: |
|
case SimpleMemoComponent: |
|
{ |
|
if ( finishedWork.mode & ProfileMode) { |
|
startPassiveEffectTimer(); |
|
|
|
try { |
|
commitHookEffectListMount(Passive$1 | HasEffect, finishedWork); |
|
} finally { |
|
recordPassiveEffectDuration(finishedWork); |
|
} |
|
} else { |
|
commitHookEffectListMount(Passive$1 | HasEffect, finishedWork); |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
|
|
function commitPassiveUnmountEffects(firstChild) { |
|
nextEffect = firstChild; |
|
commitPassiveUnmountEffects_begin(); |
|
} |
|
|
|
function commitPassiveUnmountEffects_begin() { |
|
while (nextEffect !== null) { |
|
var fiber = nextEffect; |
|
var child = fiber.child; |
|
|
|
if ((nextEffect.flags & ChildDeletion) !== NoFlags) { |
|
var deletions = fiber.deletions; |
|
|
|
if (deletions !== null) { |
|
for (var i = 0; i < deletions.length; i++) { |
|
var fiberToDelete = deletions[i]; |
|
nextEffect = fiberToDelete; |
|
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(fiberToDelete, fiber); |
|
} |
|
|
|
{ |
|
// A fiber was deleted from this parent fiber, but it's still part of |
|
// the previous (alternate) parent fiber's list of children. Because |
|
// children are a linked list, an earlier sibling that's still alive |
|
// will be connected to the deleted fiber via its `alternate`: |
|
// |
|
// live fiber |
|
// --alternate--> previous live fiber |
|
// --sibling--> deleted fiber |
|
// |
|
// We can't disconnect `alternate` on nodes that haven't been deleted |
|
// yet, but we can disconnect the `sibling` and `child` pointers. |
|
var previousFiber = fiber.alternate; |
|
|
|
if (previousFiber !== null) { |
|
var detachedChild = previousFiber.child; |
|
|
|
if (detachedChild !== null) { |
|
previousFiber.child = null; |
|
|
|
do { |
|
var detachedSibling = detachedChild.sibling; |
|
detachedChild.sibling = null; |
|
detachedChild = detachedSibling; |
|
} while (detachedChild !== null); |
|
} |
|
} |
|
} |
|
|
|
nextEffect = fiber; |
|
} |
|
} |
|
|
|
if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && child !== null) { |
|
child.return = fiber; |
|
nextEffect = child; |
|
} else { |
|
commitPassiveUnmountEffects_complete(); |
|
} |
|
} |
|
} |
|
|
|
function commitPassiveUnmountEffects_complete() { |
|
while (nextEffect !== null) { |
|
var fiber = nextEffect; |
|
|
|
if ((fiber.flags & Passive) !== NoFlags) { |
|
setCurrentFiber(fiber); |
|
commitPassiveUnmountOnFiber(fiber); |
|
resetCurrentFiber(); |
|
} |
|
|
|
var sibling = fiber.sibling; |
|
|
|
if (sibling !== null) { |
|
sibling.return = fiber.return; |
|
nextEffect = sibling; |
|
return; |
|
} |
|
|
|
nextEffect = fiber.return; |
|
} |
|
} |
|
|
|
function commitPassiveUnmountOnFiber(finishedWork) { |
|
switch (finishedWork.tag) { |
|
case FunctionComponent: |
|
case ForwardRef: |
|
case SimpleMemoComponent: |
|
{ |
|
if ( finishedWork.mode & ProfileMode) { |
|
startPassiveEffectTimer(); |
|
commitHookEffectListUnmount(Passive$1 | HasEffect, finishedWork, finishedWork.return); |
|
recordPassiveEffectDuration(finishedWork); |
|
} else { |
|
commitHookEffectListUnmount(Passive$1 | HasEffect, finishedWork, finishedWork.return); |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
|
|
function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(deletedSubtreeRoot, nearestMountedAncestor) { |
|
while (nextEffect !== null) { |
|
var fiber = nextEffect; // Deletion effects fire in parent -> child order |
|
// TODO: Check if fiber has a PassiveStatic flag |
|
|
|
setCurrentFiber(fiber); |
|
commitPassiveUnmountInsideDeletedTreeOnFiber(fiber, nearestMountedAncestor); |
|
resetCurrentFiber(); |
|
var child = fiber.child; // TODO: Only traverse subtree if it has a PassiveStatic flag. (But, if we |
|
// do this, still need to handle `deletedTreeCleanUpLevel` correctly.) |
|
|
|
if (child !== null) { |
|
child.return = fiber; |
|
nextEffect = child; |
|
} else { |
|
commitPassiveUnmountEffectsInsideOfDeletedTree_complete(deletedSubtreeRoot); |
|
} |
|
} |
|
} |
|
|
|
function commitPassiveUnmountEffectsInsideOfDeletedTree_complete(deletedSubtreeRoot) { |
|
while (nextEffect !== null) { |
|
var fiber = nextEffect; |
|
var sibling = fiber.sibling; |
|
var returnFiber = fiber.return; |
|
|
|
{ |
|
// Recursively traverse the entire deleted tree and clean up fiber fields. |
|
// This is more aggressive than ideal, and the long term goal is to only |
|
// have to detach the deleted tree at the root. |
|
detachFiberAfterEffects(fiber); |
|
|
|
if (fiber === deletedSubtreeRoot) { |
|
nextEffect = null; |
|
return; |
|
} |
|
} |
|
|
|
if (sibling !== null) { |
|
sibling.return = returnFiber; |
|
nextEffect = sibling; |
|
return; |
|
} |
|
|
|
nextEffect = returnFiber; |
|
} |
|
} |
|
|
|
function commitPassiveUnmountInsideDeletedTreeOnFiber(current, nearestMountedAncestor) { |
|
switch (current.tag) { |
|
case FunctionComponent: |
|
case ForwardRef: |
|
case SimpleMemoComponent: |
|
{ |
|
if ( current.mode & ProfileMode) { |
|
startPassiveEffectTimer(); |
|
commitHookEffectListUnmount(Passive$1, current, nearestMountedAncestor); |
|
recordPassiveEffectDuration(current); |
|
} else { |
|
commitHookEffectListUnmount(Passive$1, current, nearestMountedAncestor); |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} // TODO: Reuse reappearLayoutEffects traversal here? |
|
|
|
|
|
function invokeLayoutEffectMountInDEV(fiber) { |
|
{ |
|
// We don't need to re-check StrictEffectsMode here. |
|
// This function is only called if that check has already passed. |
|
switch (fiber.tag) { |
|
case FunctionComponent: |
|
case ForwardRef: |
|
case SimpleMemoComponent: |
|
{ |
|
try { |
|
commitHookEffectListMount(Layout | HasEffect, fiber); |
|
} catch (error) { |
|
captureCommitPhaseError(fiber, fiber.return, error); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case ClassComponent: |
|
{ |
|
var instance = fiber.stateNode; |
|
|
|
try { |
|
instance.componentDidMount(); |
|
} catch (error) { |
|
captureCommitPhaseError(fiber, fiber.return, error); |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
function invokePassiveEffectMountInDEV(fiber) { |
|
{ |
|
// We don't need to re-check StrictEffectsMode here. |
|
// This function is only called if that check has already passed. |
|
switch (fiber.tag) { |
|
case FunctionComponent: |
|
case ForwardRef: |
|
case SimpleMemoComponent: |
|
{ |
|
try { |
|
commitHookEffectListMount(Passive$1 | HasEffect, fiber); |
|
} catch (error) { |
|
captureCommitPhaseError(fiber, fiber.return, error); |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
function invokeLayoutEffectUnmountInDEV(fiber) { |
|
{ |
|
// We don't need to re-check StrictEffectsMode here. |
|
// This function is only called if that check has already passed. |
|
switch (fiber.tag) { |
|
case FunctionComponent: |
|
case ForwardRef: |
|
case SimpleMemoComponent: |
|
{ |
|
try { |
|
commitHookEffectListUnmount(Layout | HasEffect, fiber, fiber.return); |
|
} catch (error) { |
|
captureCommitPhaseError(fiber, fiber.return, error); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case ClassComponent: |
|
{ |
|
var instance = fiber.stateNode; |
|
|
|
if (typeof instance.componentWillUnmount === 'function') { |
|
safelyCallComponentWillUnmount(fiber, fiber.return, instance); |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
function invokePassiveEffectUnmountInDEV(fiber) { |
|
{ |
|
// We don't need to re-check StrictEffectsMode here. |
|
// This function is only called if that check has already passed. |
|
switch (fiber.tag) { |
|
case FunctionComponent: |
|
case ForwardRef: |
|
case SimpleMemoComponent: |
|
{ |
|
try { |
|
commitHookEffectListUnmount(Passive$1 | HasEffect, fiber, fiber.return); |
|
} catch (error) { |
|
captureCommitPhaseError(fiber, fiber.return, error); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
var COMPONENT_TYPE = 0; |
|
var HAS_PSEUDO_CLASS_TYPE = 1; |
|
var ROLE_TYPE = 2; |
|
var TEST_NAME_TYPE = 3; |
|
var TEXT_TYPE = 4; |
|
|
|
if (typeof Symbol === 'function' && Symbol.for) { |
|
var symbolFor = Symbol.for; |
|
COMPONENT_TYPE = symbolFor('selector.component'); |
|
HAS_PSEUDO_CLASS_TYPE = symbolFor('selector.has_pseudo_class'); |
|
ROLE_TYPE = symbolFor('selector.role'); |
|
TEST_NAME_TYPE = symbolFor('selector.test_id'); |
|
TEXT_TYPE = symbolFor('selector.text'); |
|
} |
|
var commitHooks = []; |
|
function onCommitRoot$1() { |
|
{ |
|
commitHooks.forEach(function (commitHook) { |
|
return commitHook(); |
|
}); |
|
} |
|
} |
|
|
|
var ReactCurrentActQueue = ReactSharedInternals.ReactCurrentActQueue; |
|
function isLegacyActEnvironment(fiber) { |
|
{ |
|
// Legacy mode. We preserve the behavior of React 17's act. It assumes an |
|
// act environment whenever `jest` is defined, but you can still turn off |
|
// spurious warnings by setting IS_REACT_ACT_ENVIRONMENT explicitly |
|
// to false. |
|
var isReactActEnvironmentGlobal = // $FlowExpectedError – Flow doesn't know about IS_REACT_ACT_ENVIRONMENT global |
|
typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined' ? IS_REACT_ACT_ENVIRONMENT : undefined; // $FlowExpectedError - Flow doesn't know about jest |
|
|
|
var jestIsDefined = typeof jest !== 'undefined'; |
|
return jestIsDefined && isReactActEnvironmentGlobal !== false; |
|
} |
|
} |
|
function isConcurrentActEnvironment() { |
|
{ |
|
var isReactActEnvironmentGlobal = // $FlowExpectedError – Flow doesn't know about IS_REACT_ACT_ENVIRONMENT global |
|
typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined' ? IS_REACT_ACT_ENVIRONMENT : undefined; |
|
|
|
if (!isReactActEnvironmentGlobal && ReactCurrentActQueue.current !== null) { |
|
// TODO: Include link to relevant documentation page. |
|
error('The current testing environment is not configured to support ' + 'act(...)'); |
|
} |
|
|
|
return isReactActEnvironmentGlobal; |
|
} |
|
} |
|
|
|
var ceil = Math.ceil; |
|
var ReactCurrentDispatcher$2 = ReactSharedInternals.ReactCurrentDispatcher, |
|
ReactCurrentOwner$2 = ReactSharedInternals.ReactCurrentOwner, |
|
ReactCurrentBatchConfig$3 = ReactSharedInternals.ReactCurrentBatchConfig, |
|
ReactCurrentActQueue$1 = ReactSharedInternals.ReactCurrentActQueue; |
|
var NoContext = |
|
/* */ |
|
0; |
|
var BatchedContext = |
|
/* */ |
|
1; |
|
var RenderContext = |
|
/* */ |
|
2; |
|
var CommitContext = |
|
/* */ |
|
4; |
|
var RootInProgress = 0; |
|
var RootFatalErrored = 1; |
|
var RootErrored = 2; |
|
var RootSuspended = 3; |
|
var RootSuspendedWithDelay = 4; |
|
var RootCompleted = 5; |
|
var RootDidNotComplete = 6; // Describes where we are in the React execution stack |
|
|
|
var executionContext = NoContext; // The root we're working on |
|
|
|
var workInProgressRoot = null; // The fiber we're working on |
|
|
|
var workInProgress = null; // The lanes we're rendering |
|
|
|
var workInProgressRootRenderLanes = NoLanes; // Stack that allows components to change the render lanes for its subtree |
|
// This is a superset of the lanes we started working on at the root. The only |
|
// case where it's different from `workInProgressRootRenderLanes` is when we |
|
// enter a subtree that is hidden and needs to be unhidden: Suspense and |
|
// Offscreen component. |
|
// |
|
// Most things in the work loop should deal with workInProgressRootRenderLanes. |
|
// Most things in begin/complete phases should deal with subtreeRenderLanes. |
|
|
|
var subtreeRenderLanes = NoLanes; |
|
var subtreeRenderLanesCursor = createCursor(NoLanes); // Whether to root completed, errored, suspended, etc. |
|
|
|
var workInProgressRootExitStatus = RootInProgress; // A fatal error, if one is thrown |
|
|
|
var workInProgressRootFatalError = null; // "Included" lanes refer to lanes that were worked on during this render. It's |
|
// slightly different than `renderLanes` because `renderLanes` can change as you |
|
// enter and exit an Offscreen tree. This value is the combination of all render |
|
// lanes for the entire render phase. |
|
|
|
var workInProgressRootIncludedLanes = NoLanes; // The work left over by components that were visited during this render. Only |
|
// includes unprocessed updates, not work in bailed out children. |
|
|
|
var workInProgressRootSkippedLanes = NoLanes; // Lanes that were updated (in an interleaved event) during this render. |
|
|
|
var workInProgressRootInterleavedUpdatedLanes = NoLanes; // Lanes that were updated during the render phase (*not* an interleaved event). |
|
|
|
var workInProgressRootPingedLanes = NoLanes; // Errors that are thrown during the render phase. |
|
|
|
var workInProgressRootConcurrentErrors = null; // These are errors that we recovered from without surfacing them to the UI. |
|
// We will log them once the tree commits. |
|
|
|
var workInProgressRootRecoverableErrors = null; // The most recent time we committed a fallback. This lets us ensure a train |
|
// model where we don't commit new loading states in too quick succession. |
|
|
|
var globalMostRecentFallbackTime = 0; |
|
var FALLBACK_THROTTLE_MS = 500; // The absolute time for when we should start giving up on rendering |
|
// more and prefer CPU suspense heuristics instead. |
|
|
|
var workInProgressRootRenderTargetTime = Infinity; // How long a render is supposed to take before we start following CPU |
|
// suspense heuristics and opt out of rendering more content. |
|
|
|
var RENDER_TIMEOUT_MS = 500; |
|
var workInProgressTransitions = null; |
|
|
|
function resetRenderTimer() { |
|
workInProgressRootRenderTargetTime = now() + RENDER_TIMEOUT_MS; |
|
} |
|
|
|
function getRenderTargetTime() { |
|
return workInProgressRootRenderTargetTime; |
|
} |
|
var hasUncaughtError = false; |
|
var firstUncaughtError = null; |
|
var legacyErrorBoundariesThatAlreadyFailed = null; // Only used when enableProfilerNestedUpdateScheduledHook is true; |
|
var rootDoesHavePassiveEffects = false; |
|
var rootWithPendingPassiveEffects = null; |
|
var pendingPassiveEffectsLanes = NoLanes; |
|
var pendingPassiveProfilerEffects = []; |
|
var pendingPassiveTransitions = null; // Use these to prevent an infinite loop of nested updates |
|
|
|
var NESTED_UPDATE_LIMIT = 50; |
|
var nestedUpdateCount = 0; |
|
var rootWithNestedUpdates = null; |
|
var isFlushingPassiveEffects = false; |
|
var didScheduleUpdateDuringPassiveEffects = false; |
|
var NESTED_PASSIVE_UPDATE_LIMIT = 50; |
|
var nestedPassiveUpdateCount = 0; |
|
var rootWithPassiveNestedUpdates = null; // If two updates are scheduled within the same event, we should treat their |
|
// event times as simultaneous, even if the actual clock time has advanced |
|
// between the first and second call. |
|
|
|
var currentEventTime = NoTimestamp; |
|
var currentEventTransitionLane = NoLanes; |
|
var isRunningInsertionEffect = false; |
|
function getWorkInProgressRoot() { |
|
return workInProgressRoot; |
|
} |
|
function requestEventTime() { |
|
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { |
|
// We're inside React, so it's fine to read the actual time. |
|
return now(); |
|
} // We're not inside React, so we may be in the middle of a browser event. |
|
|
|
|
|
if (currentEventTime !== NoTimestamp) { |
|
// Use the same start time for all updates until we enter React again. |
|
return currentEventTime; |
|
} // This is the first update since React yielded. Compute a new start time. |
|
|
|
|
|
currentEventTime = now(); |
|
return currentEventTime; |
|
} |
|
function requestUpdateLane(fiber) { |
|
// Special cases |
|
var mode = fiber.mode; |
|
|
|
if ((mode & ConcurrentMode) === NoMode) { |
|
return SyncLane; |
|
} else if ( (executionContext & RenderContext) !== NoContext && workInProgressRootRenderLanes !== NoLanes) { |
|
// This is a render phase update. These are not officially supported. The |
|
// old behavior is to give this the same "thread" (lanes) as |
|
// whatever is currently rendering. So if you call `setState` on a component |
|
// that happens later in the same render, it will flush. Ideally, we want to |
|
// remove the special case and treat them as if they came from an |
|
// interleaved event. Regardless, this pattern is not officially supported. |
|
// This behavior is only a fallback. The flag only exists until we can roll |
|
// out the setState warning, since existing code might accidentally rely on |
|
// the current behavior. |
|
return pickArbitraryLane(workInProgressRootRenderLanes); |
|
} |
|
|
|
var isTransition = requestCurrentTransition() !== NoTransition; |
|
|
|
if (isTransition) { |
|
if ( ReactCurrentBatchConfig$3.transition !== null) { |
|
var transition = ReactCurrentBatchConfig$3.transition; |
|
|
|
if (!transition._updatedFibers) { |
|
transition._updatedFibers = new Set(); |
|
} |
|
|
|
transition._updatedFibers.add(fiber); |
|
} // The algorithm for assigning an update to a lane should be stable for all |
|
// updates at the same priority within the same event. To do this, the |
|
// inputs to the algorithm must be the same. |
|
// |
|
// The trick we use is to cache the first of each of these inputs within an |
|
// event. Then reset the cached values once we can be sure the event is |
|
// over. Our heuristic for that is whenever we enter a concurrent work loop. |
|
|
|
|
|
if (currentEventTransitionLane === NoLane) { |
|
// All transitions within the same event are assigned the same lane. |
|
currentEventTransitionLane = claimNextTransitionLane(); |
|
} |
|
|
|
return currentEventTransitionLane; |
|
} // Updates originating inside certain React methods, like flushSync, have |
|
// their priority set by tracking it with a context variable. |
|
// |
|
// The opaque type returned by the host config is internally a lane, so we can |
|
// use that directly. |
|
// TODO: Move this type conversion to the event priority module. |
|
|
|
|
|
var updateLane = getCurrentUpdatePriority(); |
|
|
|
if (updateLane !== NoLane) { |
|
return updateLane; |
|
} // This update originated outside React. Ask the host environment for an |
|
// appropriate priority, based on the type of event. |
|
// |
|
// The opaque type returned by the host config is internally a lane, so we can |
|
// use that directly. |
|
// TODO: Move this type conversion to the event priority module. |
|
|
|
|
|
var eventLane = getCurrentEventPriority(); |
|
return eventLane; |
|
} |
|
|
|
function requestRetryLane(fiber) { |
|
// This is a fork of `requestUpdateLane` designed specifically for Suspense |
|
// "retries" — a special update that attempts to flip a Suspense boundary |
|
// from its placeholder state to its primary/resolved state. |
|
// Special cases |
|
var mode = fiber.mode; |
|
|
|
if ((mode & ConcurrentMode) === NoMode) { |
|
return SyncLane; |
|
} |
|
|
|
return claimNextRetryLane(); |
|
} |
|
|
|
function scheduleUpdateOnFiber(root, fiber, lane, eventTime) { |
|
checkForNestedUpdates(); |
|
|
|
{ |
|
if (isRunningInsertionEffect) { |
|
error('useInsertionEffect must not schedule updates.'); |
|
} |
|
} |
|
|
|
{ |
|
if (isFlushingPassiveEffects) { |
|
didScheduleUpdateDuringPassiveEffects = true; |
|
} |
|
} // Mark that the root has a pending update. |
|
|
|
|
|
markRootUpdated(root, lane, eventTime); |
|
|
|
if ((executionContext & RenderContext) !== NoLanes && root === workInProgressRoot) { |
|
// This update was dispatched during the render phase. This is a mistake |
|
// if the update originates from user space (with the exception of local |
|
// hook updates, which are handled differently and don't reach this |
|
// function), but there are some internal React features that use this as |
|
// an implementation detail, like selective hydration. |
|
warnAboutRenderPhaseUpdatesInDEV(fiber); // Track lanes that were updated during the render phase |
|
} else { |
|
// This is a normal update, scheduled from outside the render phase. For |
|
// example, during an input event. |
|
{ |
|
if (isDevToolsPresent) { |
|
addFiberToLanesMap(root, fiber, lane); |
|
} |
|
} |
|
|
|
warnIfUpdatesNotWrappedWithActDEV(fiber); |
|
|
|
if (root === workInProgressRoot) { |
|
// Received an update to a tree that's in the middle of rendering. Mark |
|
// that there was an interleaved update work on this root. Unless the |
|
// `deferRenderPhaseUpdateToNextBatch` flag is off and this is a render |
|
// phase update. In that case, we don't treat render phase updates as if |
|
// they were interleaved, for backwards compat reasons. |
|
if ( (executionContext & RenderContext) === NoContext) { |
|
workInProgressRootInterleavedUpdatedLanes = mergeLanes(workInProgressRootInterleavedUpdatedLanes, lane); |
|
} |
|
|
|
if (workInProgressRootExitStatus === RootSuspendedWithDelay) { |
|
// The root already suspended with a delay, which means this render |
|
// definitely won't finish. Since we have a new update, let's mark it as |
|
// suspended now, right before marking the incoming update. This has the |
|
// effect of interrupting the current render and switching to the update. |
|
// TODO: Make sure this doesn't override pings that happen while we've |
|
// already started rendering. |
|
markRootSuspended$1(root, workInProgressRootRenderLanes); |
|
} |
|
} |
|
|
|
ensureRootIsScheduled(root, eventTime); |
|
|
|
if (lane === SyncLane && executionContext === NoContext && (fiber.mode & ConcurrentMode) === NoMode && // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode. |
|
!( ReactCurrentActQueue$1.isBatchingLegacy)) { |
|
// Flush the synchronous work now, unless we're already working or inside |
|
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of |
|
// scheduleCallbackForFiber to preserve the ability to schedule a callback |
|
// without immediately flushing it. We only do this for user-initiated |
|
// updates, to preserve historical behavior of legacy mode. |
|
resetRenderTimer(); |
|
flushSyncCallbacksOnlyInLegacyMode(); |
|
} |
|
} |
|
} |
|
function scheduleInitialHydrationOnRoot(root, lane, eventTime) { |
|
// This is a special fork of scheduleUpdateOnFiber that is only used to |
|
// schedule the initial hydration of a root that has just been created. Most |
|
// of the stuff in scheduleUpdateOnFiber can be skipped. |
|
// |
|
// The main reason for this separate path, though, is to distinguish the |
|
// initial children from subsequent updates. In fully client-rendered roots |
|
// (createRoot instead of hydrateRoot), all top-level renders are modeled as |
|
// updates, but hydration roots are special because the initial render must |
|
// match what was rendered on the server. |
|
var current = root.current; |
|
current.lanes = lane; |
|
markRootUpdated(root, lane, eventTime); |
|
ensureRootIsScheduled(root, eventTime); |
|
} |
|
function isUnsafeClassRenderPhaseUpdate(fiber) { |
|
// Check if this is a render phase update. Only called by class components, |
|
// which special (deprecated) behavior for UNSAFE_componentWillReceive props. |
|
return (// TODO: Remove outdated deferRenderPhaseUpdateToNextBatch experiment. We |
|
// decided not to enable it. |
|
(executionContext & RenderContext) !== NoContext |
|
); |
|
} // Use this function to schedule a task for a root. There's only one task per |
|
// root; if a task was already scheduled, we'll check to make sure the priority |
|
// of the existing task is the same as the priority of the next level that the |
|
// root has work on. This function is called on every update, and right before |
|
// exiting a task. |
|
|
|
function ensureRootIsScheduled(root, currentTime) { |
|
var existingCallbackNode = root.callbackNode; // Check if any lanes are being starved by other work. If so, mark them as |
|
// expired so we know to work on those next. |
|
|
|
markStarvedLanesAsExpired(root, currentTime); // Determine the next lanes to work on, and their priority. |
|
|
|
var nextLanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes); |
|
|
|
if (nextLanes === NoLanes) { |
|
// Special case: There's nothing to work on. |
|
if (existingCallbackNode !== null) { |
|
cancelCallback$1(existingCallbackNode); |
|
} |
|
|
|
root.callbackNode = null; |
|
root.callbackPriority = NoLane; |
|
return; |
|
} // We use the highest priority lane to represent the priority of the callback. |
|
|
|
|
|
var newCallbackPriority = getHighestPriorityLane(nextLanes); // Check if there's an existing task. We may be able to reuse it. |
|
|
|
var existingCallbackPriority = root.callbackPriority; |
|
|
|
if (existingCallbackPriority === newCallbackPriority && // Special case related to `act`. If the currently scheduled task is a |
|
// Scheduler task, rather than an `act` task, cancel it and re-scheduled |
|
// on the `act` queue. |
|
!( ReactCurrentActQueue$1.current !== null && existingCallbackNode !== fakeActCallbackNode)) { |
|
{ |
|
// If we're going to re-use an existing task, it needs to exist. |
|
// Assume that discrete update microtasks are non-cancellable and null. |
|
// TODO: Temporary until we confirm this warning is not fired. |
|
if (existingCallbackNode == null && existingCallbackPriority !== SyncLane) { |
|
error('Expected scheduled callback to exist. This error is likely caused by a bug in React. Please file an issue.'); |
|
} |
|
} // The priority hasn't changed. We can reuse the existing task. Exit. |
|
|
|
|
|
return; |
|
} |
|
|
|
if (existingCallbackNode != null) { |
|
// Cancel the existing callback. We'll schedule a new one below. |
|
cancelCallback$1(existingCallbackNode); |
|
} // Schedule a new callback. |
|
|
|
|
|
var newCallbackNode; |
|
|
|
if (newCallbackPriority === SyncLane) { |
|
// Special case: Sync React callbacks are scheduled on a special |
|
// internal queue |
|
if (root.tag === LegacyRoot) { |
|
if ( ReactCurrentActQueue$1.isBatchingLegacy !== null) { |
|
ReactCurrentActQueue$1.didScheduleLegacyUpdate = true; |
|
} |
|
|
|
scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root)); |
|
} else { |
|
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root)); |
|
} |
|
|
|
{ |
|
// Flush the queue in a microtask. |
|
if ( ReactCurrentActQueue$1.current !== null) { |
|
// Inside `act`, use our internal `act` queue so that these get flushed |
|
// at the end of the current scope even when using the sync version |
|
// of `act`. |
|
ReactCurrentActQueue$1.current.push(flushSyncCallbacks); |
|
} else { |
|
scheduleMicrotask(function () { |
|
// In Safari, appending an iframe forces microtasks to run. |
|
// https://github.com/facebook/react/issues/22459 |
|
// We don't support running callbacks in the middle of render |
|
// or commit so we need to check against that. |
|
if ((executionContext & (RenderContext | CommitContext)) === NoContext) { |
|
// Note that this would still prematurely flush the callbacks |
|
// if this happens outside render or commit phase (e.g. in an event). |
|
flushSyncCallbacks(); |
|
} |
|
}); |
|
} |
|
} |
|
|
|
newCallbackNode = null; |
|
} else { |
|
var schedulerPriorityLevel; |
|
|
|
switch (lanesToEventPriority(nextLanes)) { |
|
case DiscreteEventPriority: |
|
schedulerPriorityLevel = ImmediatePriority; |
|
break; |
|
|
|
case ContinuousEventPriority: |
|
schedulerPriorityLevel = UserBlockingPriority; |
|
break; |
|
|
|
case DefaultEventPriority: |
|
schedulerPriorityLevel = NormalPriority; |
|
break; |
|
|
|
case IdleEventPriority: |
|
schedulerPriorityLevel = IdlePriority; |
|
break; |
|
|
|
default: |
|
schedulerPriorityLevel = NormalPriority; |
|
break; |
|
} |
|
|
|
newCallbackNode = scheduleCallback$1(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root)); |
|
} |
|
|
|
root.callbackPriority = newCallbackPriority; |
|
root.callbackNode = newCallbackNode; |
|
} // This is the entry point for every concurrent task, i.e. anything that |
|
// goes through Scheduler. |
|
|
|
|
|
function performConcurrentWorkOnRoot(root, didTimeout) { |
|
{ |
|
resetNestedUpdateFlag(); |
|
} // Since we know we're in a React event, we can clear the current |
|
// event time. The next update will compute a new event time. |
|
|
|
|
|
currentEventTime = NoTimestamp; |
|
currentEventTransitionLane = NoLanes; |
|
|
|
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { |
|
throw new Error('Should not already be working.'); |
|
} // Flush any pending passive effects before deciding which lanes to work on, |
|
// in case they schedule additional work. |
|
|
|
|
|
var originalCallbackNode = root.callbackNode; |
|
var didFlushPassiveEffects = flushPassiveEffects(); |
|
|
|
if (didFlushPassiveEffects) { |
|
// Something in the passive effect phase may have canceled the current task. |
|
// Check if the task node for this root was changed. |
|
if (root.callbackNode !== originalCallbackNode) { |
|
// The current task was canceled. Exit. We don't need to call |
|
// `ensureRootIsScheduled` because the check above implies either that |
|
// there's a new task, or that there's no remaining work on this root. |
|
return null; |
|
} |
|
} // Determine the next lanes to work on, using the fields stored |
|
// on the root. |
|
|
|
|
|
var lanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes); |
|
|
|
if (lanes === NoLanes) { |
|
// Defensive coding. This is never expected to happen. |
|
return null; |
|
} // We disable time-slicing in some cases: if the work has been CPU-bound |
|
// for too long ("expired" work, to prevent starvation), or we're in |
|
// sync-updates-by-default mode. |
|
// TODO: We only check `didTimeout` defensively, to account for a Scheduler |
|
// bug we're still investigating. Once the bug in Scheduler is fixed, |
|
// we can remove this, since we track expiration ourselves. |
|
|
|
|
|
var shouldTimeSlice = !includesBlockingLane(root, lanes) && !includesExpiredLane(root, lanes) && ( !didTimeout); |
|
var exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes); |
|
|
|
if (exitStatus !== RootInProgress) { |
|
if (exitStatus === RootErrored) { |
|
// If something threw an error, try rendering one more time. We'll |
|
// render synchronously to block concurrent data mutations, and we'll |
|
// includes all pending updates are included. If it still fails after |
|
// the second attempt, we'll give up and commit the resulting tree. |
|
var errorRetryLanes = getLanesToRetrySynchronouslyOnError(root); |
|
|
|
if (errorRetryLanes !== NoLanes) { |
|
lanes = errorRetryLanes; |
|
exitStatus = recoverFromConcurrentError(root, errorRetryLanes); |
|
} |
|
} |
|
|
|
if (exitStatus === RootFatalErrored) { |
|
var fatalError = workInProgressRootFatalError; |
|
prepareFreshStack(root, NoLanes); |
|
markRootSuspended$1(root, lanes); |
|
ensureRootIsScheduled(root, now()); |
|
throw fatalError; |
|
} |
|
|
|
if (exitStatus === RootDidNotComplete) { |
|
// The render unwound without completing the tree. This happens in special |
|
// cases where need to exit the current render without producing a |
|
// consistent tree or committing. |
|
// |
|
// This should only happen during a concurrent render, not a discrete or |
|
// synchronous update. We should have already checked for this when we |
|
// unwound the stack. |
|
markRootSuspended$1(root, lanes); |
|
} else { |
|
// The render completed. |
|
// Check if this render may have yielded to a concurrent event, and if so, |
|
// confirm that any newly rendered stores are consistent. |
|
// TODO: It's possible that even a concurrent render may never have yielded |
|
// to the main thread, if it was fast enough, or if it expired. We could |
|
// skip the consistency check in that case, too. |
|
var renderWasConcurrent = !includesBlockingLane(root, lanes); |
|
var finishedWork = root.current.alternate; |
|
|
|
if (renderWasConcurrent && !isRenderConsistentWithExternalStores(finishedWork)) { |
|
// A store was mutated in an interleaved event. Render again, |
|
// synchronously, to block further mutations. |
|
exitStatus = renderRootSync(root, lanes); // We need to check again if something threw |
|
|
|
if (exitStatus === RootErrored) { |
|
var _errorRetryLanes = getLanesToRetrySynchronouslyOnError(root); |
|
|
|
if (_errorRetryLanes !== NoLanes) { |
|
lanes = _errorRetryLanes; |
|
exitStatus = recoverFromConcurrentError(root, _errorRetryLanes); // We assume the tree is now consistent because we didn't yield to any |
|
// concurrent events. |
|
} |
|
} |
|
|
|
if (exitStatus === RootFatalErrored) { |
|
var _fatalError = workInProgressRootFatalError; |
|
prepareFreshStack(root, NoLanes); |
|
markRootSuspended$1(root, lanes); |
|
ensureRootIsScheduled(root, now()); |
|
throw _fatalError; |
|
} |
|
} // We now have a consistent tree. The next step is either to commit it, |
|
// or, if something suspended, wait to commit it after a timeout. |
|
|
|
|
|
root.finishedWork = finishedWork; |
|
root.finishedLanes = lanes; |
|
finishConcurrentRender(root, exitStatus, lanes); |
|
} |
|
} |
|
|
|
ensureRootIsScheduled(root, now()); |
|
|
|
if (root.callbackNode === originalCallbackNode) { |
|
// The task node scheduled for this root is the same one that's |
|
// currently executed. Need to return a continuation. |
|
return performConcurrentWorkOnRoot.bind(null, root); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function recoverFromConcurrentError(root, errorRetryLanes) { |
|
// If an error occurred during hydration, discard server response and fall |
|
// back to client side render. |
|
// Before rendering again, save the errors from the previous attempt. |
|
var errorsFromFirstAttempt = workInProgressRootConcurrentErrors; |
|
|
|
if (isRootDehydrated(root)) { |
|
// The shell failed to hydrate. Set a flag to force a client rendering |
|
// during the next attempt. To do this, we call prepareFreshStack now |
|
// to create the root work-in-progress fiber. This is a bit weird in terms |
|
// of factoring, because it relies on renderRootSync not calling |
|
// prepareFreshStack again in the call below, which happens because the |
|
// root and lanes haven't changed. |
|
// |
|
// TODO: I think what we should do is set ForceClientRender inside |
|
// throwException, like we do for nested Suspense boundaries. The reason |
|
// it's here instead is so we can switch to the synchronous work loop, too. |
|
// Something to consider for a future refactor. |
|
var rootWorkInProgress = prepareFreshStack(root, errorRetryLanes); |
|
rootWorkInProgress.flags |= ForceClientRender; |
|
|
|
{ |
|
errorHydratingContainer(root.containerInfo); |
|
} |
|
} |
|
|
|
var exitStatus = renderRootSync(root, errorRetryLanes); |
|
|
|
if (exitStatus !== RootErrored) { |
|
// Successfully finished rendering on retry |
|
// The errors from the failed first attempt have been recovered. Add |
|
// them to the collection of recoverable errors. We'll log them in the |
|
// commit phase. |
|
var errorsFromSecondAttempt = workInProgressRootRecoverableErrors; |
|
workInProgressRootRecoverableErrors = errorsFromFirstAttempt; // The errors from the second attempt should be queued after the errors |
|
// from the first attempt, to preserve the causal sequence. |
|
|
|
if (errorsFromSecondAttempt !== null) { |
|
queueRecoverableErrors(errorsFromSecondAttempt); |
|
} |
|
} |
|
|
|
return exitStatus; |
|
} |
|
|
|
function queueRecoverableErrors(errors) { |
|
if (workInProgressRootRecoverableErrors === null) { |
|
workInProgressRootRecoverableErrors = errors; |
|
} else { |
|
workInProgressRootRecoverableErrors.push.apply(workInProgressRootRecoverableErrors, errors); |
|
} |
|
} |
|
|
|
function finishConcurrentRender(root, exitStatus, lanes) { |
|
switch (exitStatus) { |
|
case RootInProgress: |
|
case RootFatalErrored: |
|
{ |
|
throw new Error('Root did not complete. This is a bug in React.'); |
|
} |
|
// Flow knows about invariant, so it complains if I add a break |
|
// statement, but eslint doesn't know about invariant, so it complains |
|
// if I do. eslint-disable-next-line no-fallthrough |
|
|
|
case RootErrored: |
|
{ |
|
// We should have already attempted to retry this tree. If we reached |
|
// this point, it errored again. Commit it. |
|
commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions); |
|
break; |
|
} |
|
|
|
case RootSuspended: |
|
{ |
|
markRootSuspended$1(root, lanes); // We have an acceptable loading state. We need to figure out if we |
|
// should immediately commit it or wait a bit. |
|
|
|
if (includesOnlyRetries(lanes) && // do not delay if we're inside an act() scope |
|
!shouldForceFlushFallbacksInDEV()) { |
|
// This render only included retries, no updates. Throttle committing |
|
// retries so that we don't show too many loading states too quickly. |
|
var msUntilTimeout = globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now(); // Don't bother with a very short suspense time. |
|
|
|
if (msUntilTimeout > 10) { |
|
var nextLanes = getNextLanes(root, NoLanes); |
|
|
|
if (nextLanes !== NoLanes) { |
|
// There's additional work on this root. |
|
break; |
|
} |
|
|
|
var suspendedLanes = root.suspendedLanes; |
|
|
|
if (!isSubsetOfLanes(suspendedLanes, lanes)) { |
|
// We should prefer to render the fallback of at the last |
|
// suspended level. Ping the last suspended level to try |
|
// rendering it again. |
|
// FIXME: What if the suspended lanes are Idle? Should not restart. |
|
var eventTime = requestEventTime(); |
|
markRootPinged(root, suspendedLanes); |
|
break; |
|
} // The render is suspended, it hasn't timed out, and there's no |
|
// lower priority work to do. Instead of committing the fallback |
|
// immediately, wait for more data to arrive. |
|
|
|
|
|
root.timeoutHandle = scheduleTimeout(commitRoot.bind(null, root, workInProgressRootRecoverableErrors, workInProgressTransitions), msUntilTimeout); |
|
break; |
|
} |
|
} // The work expired. Commit immediately. |
|
|
|
|
|
commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions); |
|
break; |
|
} |
|
|
|
case RootSuspendedWithDelay: |
|
{ |
|
markRootSuspended$1(root, lanes); |
|
|
|
if (includesOnlyTransitions(lanes)) { |
|
// This is a transition, so we should exit without committing a |
|
// placeholder and without scheduling a timeout. Delay indefinitely |
|
// until we receive more data. |
|
break; |
|
} |
|
|
|
if (!shouldForceFlushFallbacksInDEV()) { |
|
// This is not a transition, but we did trigger an avoided state. |
|
// Schedule a placeholder to display after a short delay, using the Just |
|
// Noticeable Difference. |
|
// TODO: Is the JND optimization worth the added complexity? If this is |
|
// the only reason we track the event time, then probably not. |
|
// Consider removing. |
|
var mostRecentEventTime = getMostRecentEventTime(root, lanes); |
|
var eventTimeMs = mostRecentEventTime; |
|
var timeElapsedMs = now() - eventTimeMs; |
|
|
|
var _msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs; // Don't bother with a very short suspense time. |
|
|
|
|
|
if (_msUntilTimeout > 10) { |
|
// Instead of committing the fallback immediately, wait for more data |
|
// to arrive. |
|
root.timeoutHandle = scheduleTimeout(commitRoot.bind(null, root, workInProgressRootRecoverableErrors, workInProgressTransitions), _msUntilTimeout); |
|
break; |
|
} |
|
} // Commit the placeholder. |
|
|
|
|
|
commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions); |
|
break; |
|
} |
|
|
|
case RootCompleted: |
|
{ |
|
// The work completed. Ready to commit. |
|
commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions); |
|
break; |
|
} |
|
|
|
default: |
|
{ |
|
throw new Error('Unknown root exit status.'); |
|
} |
|
} |
|
} |
|
|
|
function isRenderConsistentWithExternalStores(finishedWork) { |
|
// Search the rendered tree for external store reads, and check whether the |
|
// stores were mutated in a concurrent event. Intentionally using an iterative |
|
// loop instead of recursion so we can exit early. |
|
var node = finishedWork; |
|
|
|
while (true) { |
|
if (node.flags & StoreConsistency) { |
|
var updateQueue = node.updateQueue; |
|
|
|
if (updateQueue !== null) { |
|
var checks = updateQueue.stores; |
|
|
|
if (checks !== null) { |
|
for (var i = 0; i < checks.length; i++) { |
|
var check = checks[i]; |
|
var getSnapshot = check.getSnapshot; |
|
var renderedValue = check.value; |
|
|
|
try { |
|
if (!objectIs(getSnapshot(), renderedValue)) { |
|
// Found an inconsistent store. |
|
return false; |
|
} |
|
} catch (error) { |
|
// If `getSnapshot` throws, return `false`. This will schedule |
|
// a re-render, and the error will be rethrown during render. |
|
return false; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
var child = node.child; |
|
|
|
if (node.subtreeFlags & StoreConsistency && child !== null) { |
|
child.return = node; |
|
node = child; |
|
continue; |
|
} |
|
|
|
if (node === finishedWork) { |
|
return true; |
|
} |
|
|
|
while (node.sibling === null) { |
|
if (node.return === null || node.return === finishedWork) { |
|
return true; |
|
} |
|
|
|
node = node.return; |
|
} |
|
|
|
node.sibling.return = node.return; |
|
node = node.sibling; |
|
} // Flow doesn't know this is unreachable, but eslint does |
|
// eslint-disable-next-line no-unreachable |
|
|
|
|
|
return true; |
|
} |
|
|
|
function markRootSuspended$1(root, suspendedLanes) { |
|
// When suspending, we should always exclude lanes that were pinged or (more |
|
// rarely, since we try to avoid it) updated during the render phase. |
|
// TODO: Lol maybe there's a better way to factor this besides this |
|
// obnoxiously named function :) |
|
suspendedLanes = removeLanes(suspendedLanes, workInProgressRootPingedLanes); |
|
suspendedLanes = removeLanes(suspendedLanes, workInProgressRootInterleavedUpdatedLanes); |
|
markRootSuspended(root, suspendedLanes); |
|
} // This is the entry point for synchronous tasks that don't go |
|
// through Scheduler |
|
|
|
|
|
function performSyncWorkOnRoot(root) { |
|
{ |
|
syncNestedUpdateFlag(); |
|
} |
|
|
|
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { |
|
throw new Error('Should not already be working.'); |
|
} |
|
|
|
flushPassiveEffects(); |
|
var lanes = getNextLanes(root, NoLanes); |
|
|
|
if (!includesSomeLane(lanes, SyncLane)) { |
|
// There's no remaining sync work left. |
|
ensureRootIsScheduled(root, now()); |
|
return null; |
|
} |
|
|
|
var exitStatus = renderRootSync(root, lanes); |
|
|
|
if (root.tag !== LegacyRoot && exitStatus === RootErrored) { |
|
// If something threw an error, try rendering one more time. We'll render |
|
// synchronously to block concurrent data mutations, and we'll includes |
|
// all pending updates are included. If it still fails after the second |
|
// attempt, we'll give up and commit the resulting tree. |
|
var errorRetryLanes = getLanesToRetrySynchronouslyOnError(root); |
|
|
|
if (errorRetryLanes !== NoLanes) { |
|
lanes = errorRetryLanes; |
|
exitStatus = recoverFromConcurrentError(root, errorRetryLanes); |
|
} |
|
} |
|
|
|
if (exitStatus === RootFatalErrored) { |
|
var fatalError = workInProgressRootFatalError; |
|
prepareFreshStack(root, NoLanes); |
|
markRootSuspended$1(root, lanes); |
|
ensureRootIsScheduled(root, now()); |
|
throw fatalError; |
|
} |
|
|
|
if (exitStatus === RootDidNotComplete) { |
|
throw new Error('Root did not complete. This is a bug in React.'); |
|
} // We now have a consistent tree. Because this is a sync render, we |
|
// will commit it even if something suspended. |
|
|
|
|
|
var finishedWork = root.current.alternate; |
|
root.finishedWork = finishedWork; |
|
root.finishedLanes = lanes; |
|
commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions); // Before exiting, make sure there's a callback scheduled for the next |
|
// pending level. |
|
|
|
ensureRootIsScheduled(root, now()); |
|
return null; |
|
} |
|
|
|
function flushRoot(root, lanes) { |
|
if (lanes !== NoLanes) { |
|
markRootEntangled(root, mergeLanes(lanes, SyncLane)); |
|
ensureRootIsScheduled(root, now()); |
|
|
|
if ((executionContext & (RenderContext | CommitContext)) === NoContext) { |
|
resetRenderTimer(); |
|
flushSyncCallbacks(); |
|
} |
|
} |
|
} |
|
function batchedUpdates$1(fn, a) { |
|
var prevExecutionContext = executionContext; |
|
executionContext |= BatchedContext; |
|
|
|
try { |
|
return fn(a); |
|
} finally { |
|
executionContext = prevExecutionContext; // If there were legacy sync updates, flush them at the end of the outer |
|
// most batchedUpdates-like method. |
|
|
|
if (executionContext === NoContext && // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode. |
|
!( ReactCurrentActQueue$1.isBatchingLegacy)) { |
|
resetRenderTimer(); |
|
flushSyncCallbacksOnlyInLegacyMode(); |
|
} |
|
} |
|
} |
|
function discreteUpdates(fn, a, b, c, d) { |
|
var previousPriority = getCurrentUpdatePriority(); |
|
var prevTransition = ReactCurrentBatchConfig$3.transition; |
|
|
|
try { |
|
ReactCurrentBatchConfig$3.transition = null; |
|
setCurrentUpdatePriority(DiscreteEventPriority); |
|
return fn(a, b, c, d); |
|
} finally { |
|
setCurrentUpdatePriority(previousPriority); |
|
ReactCurrentBatchConfig$3.transition = prevTransition; |
|
|
|
if (executionContext === NoContext) { |
|
resetRenderTimer(); |
|
} |
|
} |
|
} // Overload the definition to the two valid signatures. |
|
// Warning, this opts-out of checking the function body. |
|
|
|
// eslint-disable-next-line no-redeclare |
|
function flushSync(fn) { |
|
// In legacy mode, we flush pending passive effects at the beginning of the |
|
// next event, not at the end of the previous one. |
|
if (rootWithPendingPassiveEffects !== null && rootWithPendingPassiveEffects.tag === LegacyRoot && (executionContext & (RenderContext | CommitContext)) === NoContext) { |
|
flushPassiveEffects(); |
|
} |
|
|
|
var prevExecutionContext = executionContext; |
|
executionContext |= BatchedContext; |
|
var prevTransition = ReactCurrentBatchConfig$3.transition; |
|
var previousPriority = getCurrentUpdatePriority(); |
|
|
|
try { |
|
ReactCurrentBatchConfig$3.transition = null; |
|
setCurrentUpdatePriority(DiscreteEventPriority); |
|
|
|
if (fn) { |
|
return fn(); |
|
} else { |
|
return undefined; |
|
} |
|
} finally { |
|
setCurrentUpdatePriority(previousPriority); |
|
ReactCurrentBatchConfig$3.transition = prevTransition; |
|
executionContext = prevExecutionContext; // Flush the immediate callbacks that were scheduled during this batch. |
|
// Note that this will happen even if batchedUpdates is higher up |
|
// the stack. |
|
|
|
if ((executionContext & (RenderContext | CommitContext)) === NoContext) { |
|
flushSyncCallbacks(); |
|
} |
|
} |
|
} |
|
function isAlreadyRendering() { |
|
// Used by the renderer to print a warning if certain APIs are called from |
|
// the wrong context. |
|
return (executionContext & (RenderContext | CommitContext)) !== NoContext; |
|
} |
|
function pushRenderLanes(fiber, lanes) { |
|
push(subtreeRenderLanesCursor, subtreeRenderLanes, fiber); |
|
subtreeRenderLanes = mergeLanes(subtreeRenderLanes, lanes); |
|
workInProgressRootIncludedLanes = mergeLanes(workInProgressRootIncludedLanes, lanes); |
|
} |
|
function popRenderLanes(fiber) { |
|
subtreeRenderLanes = subtreeRenderLanesCursor.current; |
|
pop(subtreeRenderLanesCursor, fiber); |
|
} |
|
|
|
function prepareFreshStack(root, lanes) { |
|
root.finishedWork = null; |
|
root.finishedLanes = NoLanes; |
|
var timeoutHandle = root.timeoutHandle; |
|
|
|
if (timeoutHandle !== noTimeout) { |
|
// The root previous suspended and scheduled a timeout to commit a fallback |
|
// state. Now that we have additional work, cancel the timeout. |
|
root.timeoutHandle = noTimeout; // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above |
|
|
|
cancelTimeout(timeoutHandle); |
|
} |
|
|
|
if (workInProgress !== null) { |
|
var interruptedWork = workInProgress.return; |
|
|
|
while (interruptedWork !== null) { |
|
var current = interruptedWork.alternate; |
|
unwindInterruptedWork(current, interruptedWork); |
|
interruptedWork = interruptedWork.return; |
|
} |
|
} |
|
|
|
workInProgressRoot = root; |
|
var rootWorkInProgress = createWorkInProgress(root.current, null); |
|
workInProgress = rootWorkInProgress; |
|
workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes; |
|
workInProgressRootExitStatus = RootInProgress; |
|
workInProgressRootFatalError = null; |
|
workInProgressRootSkippedLanes = NoLanes; |
|
workInProgressRootInterleavedUpdatedLanes = NoLanes; |
|
workInProgressRootPingedLanes = NoLanes; |
|
workInProgressRootConcurrentErrors = null; |
|
workInProgressRootRecoverableErrors = null; |
|
finishQueueingConcurrentUpdates(); |
|
|
|
{ |
|
ReactStrictModeWarnings.discardPendingWarnings(); |
|
} |
|
|
|
return rootWorkInProgress; |
|
} |
|
|
|
function handleError(root, thrownValue) { |
|
do { |
|
var erroredWork = workInProgress; |
|
|
|
try { |
|
// Reset module-level state that was set during the render phase. |
|
resetContextDependencies(); |
|
resetHooksAfterThrow(); |
|
resetCurrentFiber(); // TODO: I found and added this missing line while investigating a |
|
// separate issue. Write a regression test using string refs. |
|
|
|
ReactCurrentOwner$2.current = null; |
|
|
|
if (erroredWork === null || erroredWork.return === null) { |
|
// Expected to be working on a non-root fiber. This is a fatal error |
|
// because there's no ancestor that can handle it; the root is |
|
// supposed to capture all errors that weren't caught by an error |
|
// boundary. |
|
workInProgressRootExitStatus = RootFatalErrored; |
|
workInProgressRootFatalError = thrownValue; // Set `workInProgress` to null. This represents advancing to the next |
|
// sibling, or the parent if there are no siblings. But since the root |
|
// has no siblings nor a parent, we set it to null. Usually this is |
|
// handled by `completeUnitOfWork` or `unwindWork`, but since we're |
|
// intentionally not calling those, we need set it here. |
|
// TODO: Consider calling `unwindWork` to pop the contexts. |
|
|
|
workInProgress = null; |
|
return; |
|
} |
|
|
|
if (enableProfilerTimer && erroredWork.mode & ProfileMode) { |
|
// Record the time spent rendering before an error was thrown. This |
|
// avoids inaccurate Profiler durations in the case of a |
|
// suspended render. |
|
stopProfilerTimerIfRunningAndRecordDelta(erroredWork, true); |
|
} |
|
|
|
if (enableSchedulingProfiler) { |
|
markComponentRenderStopped(); |
|
|
|
if (thrownValue !== null && typeof thrownValue === 'object' && typeof thrownValue.then === 'function') { |
|
var wakeable = thrownValue; |
|
markComponentSuspended(erroredWork, wakeable, workInProgressRootRenderLanes); |
|
} else { |
|
markComponentErrored(erroredWork, thrownValue, workInProgressRootRenderLanes); |
|
} |
|
} |
|
|
|
throwException(root, erroredWork.return, erroredWork, thrownValue, workInProgressRootRenderLanes); |
|
completeUnitOfWork(erroredWork); |
|
} catch (yetAnotherThrownValue) { |
|
// Something in the return path also threw. |
|
thrownValue = yetAnotherThrownValue; |
|
|
|
if (workInProgress === erroredWork && erroredWork !== null) { |
|
// If this boundary has already errored, then we had trouble processing |
|
// the error. Bubble it to the next boundary. |
|
erroredWork = erroredWork.return; |
|
workInProgress = erroredWork; |
|
} else { |
|
erroredWork = workInProgress; |
|
} |
|
|
|
continue; |
|
} // Return to the normal work loop. |
|
|
|
|
|
return; |
|
} while (true); |
|
} |
|
|
|
function pushDispatcher() { |
|
var prevDispatcher = ReactCurrentDispatcher$2.current; |
|
ReactCurrentDispatcher$2.current = ContextOnlyDispatcher; |
|
|
|
if (prevDispatcher === null) { |
|
// The React isomorphic package does not include a default dispatcher. |
|
// Instead the first renderer will lazily attach one, in order to give |
|
// nicer error messages. |
|
return ContextOnlyDispatcher; |
|
} else { |
|
return prevDispatcher; |
|
} |
|
} |
|
|
|
function popDispatcher(prevDispatcher) { |
|
ReactCurrentDispatcher$2.current = prevDispatcher; |
|
} |
|
|
|
function markCommitTimeOfFallback() { |
|
globalMostRecentFallbackTime = now(); |
|
} |
|
function markSkippedUpdateLanes(lane) { |
|
workInProgressRootSkippedLanes = mergeLanes(lane, workInProgressRootSkippedLanes); |
|
} |
|
function renderDidSuspend() { |
|
if (workInProgressRootExitStatus === RootInProgress) { |
|
workInProgressRootExitStatus = RootSuspended; |
|
} |
|
} |
|
function renderDidSuspendDelayIfPossible() { |
|
if (workInProgressRootExitStatus === RootInProgress || workInProgressRootExitStatus === RootSuspended || workInProgressRootExitStatus === RootErrored) { |
|
workInProgressRootExitStatus = RootSuspendedWithDelay; |
|
} // Check if there are updates that we skipped tree that might have unblocked |
|
// this render. |
|
|
|
|
|
if (workInProgressRoot !== null && (includesNonIdleWork(workInProgressRootSkippedLanes) || includesNonIdleWork(workInProgressRootInterleavedUpdatedLanes))) { |
|
// Mark the current render as suspended so that we switch to working on |
|
// the updates that were skipped. Usually we only suspend at the end of |
|
// the render phase. |
|
// TODO: We should probably always mark the root as suspended immediately |
|
// (inside this function), since by suspending at the end of the render |
|
// phase introduces a potential mistake where we suspend lanes that were |
|
// pinged or updated while we were rendering. |
|
markRootSuspended$1(workInProgressRoot, workInProgressRootRenderLanes); |
|
} |
|
} |
|
function renderDidError(error) { |
|
if (workInProgressRootExitStatus !== RootSuspendedWithDelay) { |
|
workInProgressRootExitStatus = RootErrored; |
|
} |
|
|
|
if (workInProgressRootConcurrentErrors === null) { |
|
workInProgressRootConcurrentErrors = [error]; |
|
} else { |
|
workInProgressRootConcurrentErrors.push(error); |
|
} |
|
} // Called during render to determine if anything has suspended. |
|
// Returns false if we're not sure. |
|
|
|
function renderHasNotSuspendedYet() { |
|
// If something errored or completed, we can't really be sure, |
|
// so those are false. |
|
return workInProgressRootExitStatus === RootInProgress; |
|
} |
|
|
|
function renderRootSync(root, lanes) { |
|
var prevExecutionContext = executionContext; |
|
executionContext |= RenderContext; |
|
var prevDispatcher = pushDispatcher(); // If the root or lanes have changed, throw out the existing stack |
|
// and prepare a fresh one. Otherwise we'll continue where we left off. |
|
|
|
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) { |
|
{ |
|
if (isDevToolsPresent) { |
|
var memoizedUpdaters = root.memoizedUpdaters; |
|
|
|
if (memoizedUpdaters.size > 0) { |
|
restorePendingUpdaters(root, workInProgressRootRenderLanes); |
|
memoizedUpdaters.clear(); |
|
} // At this point, move Fibers that scheduled the upcoming work from the Map to the Set. |
|
// If we bailout on this work, we'll move them back (like above). |
|
// It's important to move them now in case the work spawns more work at the same priority with different updaters. |
|
// That way we can keep the current update and future updates separate. |
|
|
|
|
|
movePendingFibersToMemoized(root, lanes); |
|
} |
|
} |
|
|
|
workInProgressTransitions = getTransitionsForLanes(); |
|
prepareFreshStack(root, lanes); |
|
} |
|
|
|
{ |
|
markRenderStarted(lanes); |
|
} |
|
|
|
do { |
|
try { |
|
workLoopSync(); |
|
break; |
|
} catch (thrownValue) { |
|
handleError(root, thrownValue); |
|
} |
|
} while (true); |
|
|
|
resetContextDependencies(); |
|
executionContext = prevExecutionContext; |
|
popDispatcher(prevDispatcher); |
|
|
|
if (workInProgress !== null) { |
|
// This is a sync render, so we should have finished the whole tree. |
|
throw new Error('Cannot commit an incomplete root. This error is likely caused by a ' + 'bug in React. Please file an issue.'); |
|
} |
|
|
|
{ |
|
markRenderStopped(); |
|
} // Set this to null to indicate there's no in-progress render. |
|
|
|
|
|
workInProgressRoot = null; |
|
workInProgressRootRenderLanes = NoLanes; |
|
return workInProgressRootExitStatus; |
|
} // The work loop is an extremely hot path. Tell Closure not to inline it. |
|
|
|
/** @noinline */ |
|
|
|
|
|
function workLoopSync() { |
|
// Already timed out, so perform work without checking if we need to yield. |
|
while (workInProgress !== null) { |
|
performUnitOfWork(workInProgress); |
|
} |
|
} |
|
|
|
function renderRootConcurrent(root, lanes) { |
|
var prevExecutionContext = executionContext; |
|
executionContext |= RenderContext; |
|
var prevDispatcher = pushDispatcher(); // If the root or lanes have changed, throw out the existing stack |
|
// and prepare a fresh one. Otherwise we'll continue where we left off. |
|
|
|
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) { |
|
{ |
|
if (isDevToolsPresent) { |
|
var memoizedUpdaters = root.memoizedUpdaters; |
|
|
|
if (memoizedUpdaters.size > 0) { |
|
restorePendingUpdaters(root, workInProgressRootRenderLanes); |
|
memoizedUpdaters.clear(); |
|
} // At this point, move Fibers that scheduled the upcoming work from the Map to the Set. |
|
// If we bailout on this work, we'll move them back (like above). |
|
// It's important to move them now in case the work spawns more work at the same priority with different updaters. |
|
// That way we can keep the current update and future updates separate. |
|
|
|
|
|
movePendingFibersToMemoized(root, lanes); |
|
} |
|
} |
|
|
|
workInProgressTransitions = getTransitionsForLanes(); |
|
resetRenderTimer(); |
|
prepareFreshStack(root, lanes); |
|
} |
|
|
|
{ |
|
markRenderStarted(lanes); |
|
} |
|
|
|
do { |
|
try { |
|
workLoopConcurrent(); |
|
break; |
|
} catch (thrownValue) { |
|
handleError(root, thrownValue); |
|
} |
|
} while (true); |
|
|
|
resetContextDependencies(); |
|
popDispatcher(prevDispatcher); |
|
executionContext = prevExecutionContext; |
|
|
|
|
|
if (workInProgress !== null) { |
|
// Still work remaining. |
|
{ |
|
markRenderYielded(); |
|
} |
|
|
|
return RootInProgress; |
|
} else { |
|
// Completed the tree. |
|
{ |
|
markRenderStopped(); |
|
} // Set this to null to indicate there's no in-progress render. |
|
|
|
|
|
workInProgressRoot = null; |
|
workInProgressRootRenderLanes = NoLanes; // Return the final exit status. |
|
|
|
return workInProgressRootExitStatus; |
|
} |
|
} |
|
/** @noinline */ |
|
|
|
|
|
function workLoopConcurrent() { |
|
// Perform work until Scheduler asks us to yield |
|
while (workInProgress !== null && !shouldYield()) { |
|
performUnitOfWork(workInProgress); |
|
} |
|
} |
|
|
|
function performUnitOfWork(unitOfWork) { |
|
// The current, flushed, state of this fiber is the alternate. Ideally |
|
// nothing should rely on this, but relying on it here means that we don't |
|
// need an additional field on the work in progress. |
|
var current = unitOfWork.alternate; |
|
setCurrentFiber(unitOfWork); |
|
var next; |
|
|
|
if ( (unitOfWork.mode & ProfileMode) !== NoMode) { |
|
startProfilerTimer(unitOfWork); |
|
next = beginWork$1(current, unitOfWork, subtreeRenderLanes); |
|
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true); |
|
} else { |
|
next = beginWork$1(current, unitOfWork, subtreeRenderLanes); |
|
} |
|
|
|
resetCurrentFiber(); |
|
unitOfWork.memoizedProps = unitOfWork.pendingProps; |
|
|
|
if (next === null) { |
|
// If this doesn't spawn new work, complete the current work. |
|
completeUnitOfWork(unitOfWork); |
|
} else { |
|
workInProgress = next; |
|
} |
|
|
|
ReactCurrentOwner$2.current = null; |
|
} |
|
|
|
function completeUnitOfWork(unitOfWork) { |
|
// Attempt to complete the current unit of work, then move to the next |
|
// sibling. If there are no more siblings, return to the parent fiber. |
|
var completedWork = unitOfWork; |
|
|
|
do { |
|
// The current, flushed, state of this fiber is the alternate. Ideally |
|
// nothing should rely on this, but relying on it here means that we don't |
|
// need an additional field on the work in progress. |
|
var current = completedWork.alternate; |
|
var returnFiber = completedWork.return; // Check if the work completed or if something threw. |
|
|
|
if ((completedWork.flags & Incomplete) === NoFlags) { |
|
setCurrentFiber(completedWork); |
|
var next = void 0; |
|
|
|
if ( (completedWork.mode & ProfileMode) === NoMode) { |
|
next = completeWork(current, completedWork, subtreeRenderLanes); |
|
} else { |
|
startProfilerTimer(completedWork); |
|
next = completeWork(current, completedWork, subtreeRenderLanes); // Update render duration assuming we didn't error. |
|
|
|
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false); |
|
} |
|
|
|
resetCurrentFiber(); |
|
|
|
if (next !== null) { |
|
// Completing this fiber spawned new work. Work on that next. |
|
workInProgress = next; |
|
return; |
|
} |
|
} else { |
|
// This fiber did not complete because something threw. Pop values off |
|
// the stack without entering the complete phase. If this is a boundary, |
|
// capture values if possible. |
|
var _next = unwindWork(current, completedWork); // Because this fiber did not complete, don't reset its lanes. |
|
|
|
|
|
if (_next !== null) { |
|
// If completing this work spawned new work, do that next. We'll come |
|
// back here again. |
|
// Since we're restarting, remove anything that is not a host effect |
|
// from the effect tag. |
|
_next.flags &= HostEffectMask; |
|
workInProgress = _next; |
|
return; |
|
} |
|
|
|
if ( (completedWork.mode & ProfileMode) !== NoMode) { |
|
// Record the render duration for the fiber that errored. |
|
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false); // Include the time spent working on failed children before continuing. |
|
|
|
var actualDuration = completedWork.actualDuration; |
|
var child = completedWork.child; |
|
|
|
while (child !== null) { |
|
actualDuration += child.actualDuration; |
|
child = child.sibling; |
|
} |
|
|
|
completedWork.actualDuration = actualDuration; |
|
} |
|
|
|
if (returnFiber !== null) { |
|
// Mark the parent fiber as incomplete and clear its subtree flags. |
|
returnFiber.flags |= Incomplete; |
|
returnFiber.subtreeFlags = NoFlags; |
|
returnFiber.deletions = null; |
|
} else { |
|
// We've unwound all the way to the root. |
|
workInProgressRootExitStatus = RootDidNotComplete; |
|
workInProgress = null; |
|
return; |
|
} |
|
} |
|
|
|
var siblingFiber = completedWork.sibling; |
|
|
|
if (siblingFiber !== null) { |
|
// If there is more work to do in this returnFiber, do that next. |
|
workInProgress = siblingFiber; |
|
return; |
|
} // Otherwise, return to the parent |
|
|
|
|
|
completedWork = returnFiber; // Update the next thing we're working on in case something throws. |
|
|
|
workInProgress = completedWork; |
|
} while (completedWork !== null); // We've reached the root. |
|
|
|
|
|
if (workInProgressRootExitStatus === RootInProgress) { |
|
workInProgressRootExitStatus = RootCompleted; |
|
} |
|
} |
|
|
|
function commitRoot(root, recoverableErrors, transitions) { |
|
// TODO: This no longer makes any sense. We already wrap the mutation and |
|
// layout phases. Should be able to remove. |
|
var previousUpdateLanePriority = getCurrentUpdatePriority(); |
|
var prevTransition = ReactCurrentBatchConfig$3.transition; |
|
|
|
try { |
|
ReactCurrentBatchConfig$3.transition = null; |
|
setCurrentUpdatePriority(DiscreteEventPriority); |
|
commitRootImpl(root, recoverableErrors, transitions, previousUpdateLanePriority); |
|
} finally { |
|
ReactCurrentBatchConfig$3.transition = prevTransition; |
|
setCurrentUpdatePriority(previousUpdateLanePriority); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function commitRootImpl(root, recoverableErrors, transitions, renderPriorityLevel) { |
|
do { |
|
// `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which |
|
// means `flushPassiveEffects` will sometimes result in additional |
|
// passive effects. So we need to keep flushing in a loop until there are |
|
// no more pending effects. |
|
// TODO: Might be better if `flushPassiveEffects` did not automatically |
|
// flush synchronous work at the end, to avoid factoring hazards like this. |
|
flushPassiveEffects(); |
|
} while (rootWithPendingPassiveEffects !== null); |
|
|
|
flushRenderPhaseStrictModeWarningsInDEV(); |
|
|
|
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { |
|
throw new Error('Should not already be working.'); |
|
} |
|
|
|
var finishedWork = root.finishedWork; |
|
var lanes = root.finishedLanes; |
|
|
|
{ |
|
markCommitStarted(lanes); |
|
} |
|
|
|
if (finishedWork === null) { |
|
|
|
{ |
|
markCommitStopped(); |
|
} |
|
|
|
return null; |
|
} else { |
|
{ |
|
if (lanes === NoLanes) { |
|
error('root.finishedLanes should not be empty during a commit. This is a ' + 'bug in React.'); |
|
} |
|
} |
|
} |
|
|
|
root.finishedWork = null; |
|
root.finishedLanes = NoLanes; |
|
|
|
if (finishedWork === root.current) { |
|
throw new Error('Cannot commit the same tree as before. This error is likely caused by ' + 'a bug in React. Please file an issue.'); |
|
} // commitRoot never returns a continuation; it always finishes synchronously. |
|
// So we can clear these now to allow a new callback to be scheduled. |
|
|
|
|
|
root.callbackNode = null; |
|
root.callbackPriority = NoLane; // Update the first and last pending times on this root. The new first |
|
// pending time is whatever is left on the root fiber. |
|
|
|
var remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes); |
|
markRootFinished(root, remainingLanes); |
|
|
|
if (root === workInProgressRoot) { |
|
// We can reset these now that they are finished. |
|
workInProgressRoot = null; |
|
workInProgress = null; |
|
workInProgressRootRenderLanes = NoLanes; |
|
} // If there are pending passive effects, schedule a callback to process them. |
|
// Do this as early as possible, so it is queued before anything else that |
|
// might get scheduled in the commit phase. (See #16714.) |
|
// TODO: Delete all other places that schedule the passive effect callback |
|
// They're redundant. |
|
|
|
|
|
if ((finishedWork.subtreeFlags & PassiveMask) !== NoFlags || (finishedWork.flags & PassiveMask) !== NoFlags) { |
|
if (!rootDoesHavePassiveEffects) { |
|
rootDoesHavePassiveEffects = true; |
|
// to store it in pendingPassiveTransitions until they get processed |
|
// We need to pass this through as an argument to commitRoot |
|
// because workInProgressTransitions might have changed between |
|
// the previous render and commit if we throttle the commit |
|
// with setTimeout |
|
|
|
pendingPassiveTransitions = transitions; |
|
scheduleCallback$1(NormalPriority, function () { |
|
flushPassiveEffects(); // This render triggered passive effects: release the root cache pool |
|
// *after* passive effects fire to avoid freeing a cache pool that may |
|
// be referenced by a node in the tree (HostRoot, Cache boundary etc) |
|
|
|
return null; |
|
}); |
|
} |
|
} // Check if there are any effects in the whole tree. |
|
// TODO: This is left over from the effect list implementation, where we had |
|
// to check for the existence of `firstEffect` to satisfy Flow. I think the |
|
// only other reason this optimization exists is because it affects profiling. |
|
// Reconsider whether this is necessary. |
|
|
|
|
|
var subtreeHasEffects = (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags; |
|
var rootHasEffect = (finishedWork.flags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags; |
|
|
|
if (subtreeHasEffects || rootHasEffect) { |
|
var prevTransition = ReactCurrentBatchConfig$3.transition; |
|
ReactCurrentBatchConfig$3.transition = null; |
|
var previousPriority = getCurrentUpdatePriority(); |
|
setCurrentUpdatePriority(DiscreteEventPriority); |
|
var prevExecutionContext = executionContext; |
|
executionContext |= CommitContext; // Reset this to null before calling lifecycles |
|
|
|
ReactCurrentOwner$2.current = null; // The commit phase is broken into several sub-phases. We do a separate pass |
|
// of the effect list for each phase: all mutation effects come before all |
|
// layout effects, and so on. |
|
// The first phase a "before mutation" phase. We use this phase to read the |
|
// state of the host tree right before we mutate it. This is where |
|
// getSnapshotBeforeUpdate is called. |
|
|
|
var shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(root, finishedWork); |
|
|
|
{ |
|
// Mark the current commit time to be shared by all Profilers in this |
|
// batch. This enables them to be grouped later. |
|
recordCommitTime(); |
|
} |
|
|
|
|
|
commitMutationEffects(root, finishedWork, lanes); |
|
|
|
resetAfterCommit(root.containerInfo); // The work-in-progress tree is now the current tree. This must come after |
|
// the mutation phase, so that the previous tree is still current during |
|
// componentWillUnmount, but before the layout phase, so that the finished |
|
// work is current during componentDidMount/Update. |
|
|
|
root.current = finishedWork; // The next phase is the layout phase, where we call effects that read |
|
|
|
{ |
|
markLayoutEffectsStarted(lanes); |
|
} |
|
|
|
commitLayoutEffects(finishedWork, root, lanes); |
|
|
|
{ |
|
markLayoutEffectsStopped(); |
|
} |
|
// opportunity to paint. |
|
|
|
|
|
requestPaint(); |
|
executionContext = prevExecutionContext; // Reset the priority to the previous non-sync value. |
|
|
|
setCurrentUpdatePriority(previousPriority); |
|
ReactCurrentBatchConfig$3.transition = prevTransition; |
|
} else { |
|
// No effects. |
|
root.current = finishedWork; // Measure these anyway so the flamegraph explicitly shows that there were |
|
// no effects. |
|
// TODO: Maybe there's a better way to report this. |
|
|
|
{ |
|
recordCommitTime(); |
|
} |
|
} |
|
|
|
var rootDidHavePassiveEffects = rootDoesHavePassiveEffects; |
|
|
|
if (rootDoesHavePassiveEffects) { |
|
// This commit has passive effects. Stash a reference to them. But don't |
|
// schedule a callback until after flushing layout work. |
|
rootDoesHavePassiveEffects = false; |
|
rootWithPendingPassiveEffects = root; |
|
pendingPassiveEffectsLanes = lanes; |
|
} else { |
|
|
|
{ |
|
nestedPassiveUpdateCount = 0; |
|
rootWithPassiveNestedUpdates = null; |
|
} |
|
} // Read this again, since an effect might have updated it |
|
|
|
|
|
remainingLanes = root.pendingLanes; // Check if there's remaining work on this root |
|
// TODO: This is part of the `componentDidCatch` implementation. Its purpose |
|
// is to detect whether something might have called setState inside |
|
// `componentDidCatch`. The mechanism is known to be flawed because `setState` |
|
// inside `componentDidCatch` is itself flawed — that's why we recommend |
|
// `getDerivedStateFromError` instead. However, it could be improved by |
|
// checking if remainingLanes includes Sync work, instead of whether there's |
|
// any work remaining at all (which would also include stuff like Suspense |
|
// retries or transitions). It's been like this for a while, though, so fixing |
|
// it probably isn't that urgent. |
|
|
|
if (remainingLanes === NoLanes) { |
|
// If there's no remaining work, we can clear the set of already failed |
|
// error boundaries. |
|
legacyErrorBoundariesThatAlreadyFailed = null; |
|
} |
|
|
|
{ |
|
if (!rootDidHavePassiveEffects) { |
|
commitDoubleInvokeEffectsInDEV(root.current, false); |
|
} |
|
} |
|
|
|
onCommitRoot(finishedWork.stateNode, renderPriorityLevel); |
|
|
|
{ |
|
if (isDevToolsPresent) { |
|
root.memoizedUpdaters.clear(); |
|
} |
|
} |
|
|
|
{ |
|
onCommitRoot$1(); |
|
} // Always call this before exiting `commitRoot`, to ensure that any |
|
// additional work on this root is scheduled. |
|
|
|
|
|
ensureRootIsScheduled(root, now()); |
|
|
|
if (recoverableErrors !== null) { |
|
// There were errors during this render, but recovered from them without |
|
// needing to surface it to the UI. We log them here. |
|
var onRecoverableError = root.onRecoverableError; |
|
|
|
for (var i = 0; i < recoverableErrors.length; i++) { |
|
var recoverableError = recoverableErrors[i]; |
|
var componentStack = recoverableError.stack; |
|
var digest = recoverableError.digest; |
|
onRecoverableError(recoverableError.value, { |
|
componentStack: componentStack, |
|
digest: digest |
|
}); |
|
} |
|
} |
|
|
|
if (hasUncaughtError) { |
|
hasUncaughtError = false; |
|
var error$1 = firstUncaughtError; |
|
firstUncaughtError = null; |
|
throw error$1; |
|
} // If the passive effects are the result of a discrete render, flush them |
|
// synchronously at the end of the current task so that the result is |
|
// immediately observable. Otherwise, we assume that they are not |
|
// order-dependent and do not need to be observed by external systems, so we |
|
// can wait until after paint. |
|
// TODO: We can optimize this by not scheduling the callback earlier. Since we |
|
// currently schedule the callback in multiple places, will wait until those |
|
// are consolidated. |
|
|
|
|
|
if (includesSomeLane(pendingPassiveEffectsLanes, SyncLane) && root.tag !== LegacyRoot) { |
|
flushPassiveEffects(); |
|
} // Read this again, since a passive effect might have updated it |
|
|
|
|
|
remainingLanes = root.pendingLanes; |
|
|
|
if (includesSomeLane(remainingLanes, SyncLane)) { |
|
{ |
|
markNestedUpdateScheduled(); |
|
} // Count the number of times the root synchronously re-renders without |
|
// finishing. If there are too many, it indicates an infinite update loop. |
|
|
|
|
|
if (root === rootWithNestedUpdates) { |
|
nestedUpdateCount++; |
|
} else { |
|
nestedUpdateCount = 0; |
|
rootWithNestedUpdates = root; |
|
} |
|
} else { |
|
nestedUpdateCount = 0; |
|
} // If layout work was scheduled, flush it now. |
|
|
|
|
|
flushSyncCallbacks(); |
|
|
|
{ |
|
markCommitStopped(); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
function flushPassiveEffects() { |
|
// Returns whether passive effects were flushed. |
|
// TODO: Combine this check with the one in flushPassiveEFfectsImpl. We should |
|
// probably just combine the two functions. I believe they were only separate |
|
// in the first place because we used to wrap it with |
|
// `Scheduler.runWithPriority`, which accepts a function. But now we track the |
|
// priority within React itself, so we can mutate the variable directly. |
|
if (rootWithPendingPassiveEffects !== null) { |
|
var renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes); |
|
var priority = lowerEventPriority(DefaultEventPriority, renderPriority); |
|
var prevTransition = ReactCurrentBatchConfig$3.transition; |
|
var previousPriority = getCurrentUpdatePriority(); |
|
|
|
try { |
|
ReactCurrentBatchConfig$3.transition = null; |
|
setCurrentUpdatePriority(priority); |
|
return flushPassiveEffectsImpl(); |
|
} finally { |
|
setCurrentUpdatePriority(previousPriority); |
|
ReactCurrentBatchConfig$3.transition = prevTransition; // Once passive effects have run for the tree - giving components a |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
function enqueuePendingPassiveProfilerEffect(fiber) { |
|
{ |
|
pendingPassiveProfilerEffects.push(fiber); |
|
|
|
if (!rootDoesHavePassiveEffects) { |
|
rootDoesHavePassiveEffects = true; |
|
scheduleCallback$1(NormalPriority, function () { |
|
flushPassiveEffects(); |
|
return null; |
|
}); |
|
} |
|
} |
|
} |
|
|
|
function flushPassiveEffectsImpl() { |
|
if (rootWithPendingPassiveEffects === null) { |
|
return false; |
|
} // Cache and clear the transitions flag |
|
|
|
|
|
var transitions = pendingPassiveTransitions; |
|
pendingPassiveTransitions = null; |
|
var root = rootWithPendingPassiveEffects; |
|
var lanes = pendingPassiveEffectsLanes; |
|
rootWithPendingPassiveEffects = null; // TODO: This is sometimes out of sync with rootWithPendingPassiveEffects. |
|
// Figure out why and fix it. It's not causing any known issues (probably |
|
// because it's only used for profiling), but it's a refactor hazard. |
|
|
|
pendingPassiveEffectsLanes = NoLanes; |
|
|
|
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { |
|
throw new Error('Cannot flush passive effects while already rendering.'); |
|
} |
|
|
|
{ |
|
isFlushingPassiveEffects = true; |
|
didScheduleUpdateDuringPassiveEffects = false; |
|
} |
|
|
|
{ |
|
markPassiveEffectsStarted(lanes); |
|
} |
|
|
|
var prevExecutionContext = executionContext; |
|
executionContext |= CommitContext; |
|
commitPassiveUnmountEffects(root.current); |
|
commitPassiveMountEffects(root, root.current, lanes, transitions); // TODO: Move to commitPassiveMountEffects |
|
|
|
{ |
|
var profilerEffects = pendingPassiveProfilerEffects; |
|
pendingPassiveProfilerEffects = []; |
|
|
|
for (var i = 0; i < profilerEffects.length; i++) { |
|
var _fiber = profilerEffects[i]; |
|
commitPassiveEffectDurations(root, _fiber); |
|
} |
|
} |
|
|
|
{ |
|
markPassiveEffectsStopped(); |
|
} |
|
|
|
{ |
|
commitDoubleInvokeEffectsInDEV(root.current, true); |
|
} |
|
|
|
executionContext = prevExecutionContext; |
|
flushSyncCallbacks(); |
|
|
|
{ |
|
// If additional passive effects were scheduled, increment a counter. If this |
|
// exceeds the limit, we'll fire a warning. |
|
if (didScheduleUpdateDuringPassiveEffects) { |
|
if (root === rootWithPassiveNestedUpdates) { |
|
nestedPassiveUpdateCount++; |
|
} else { |
|
nestedPassiveUpdateCount = 0; |
|
rootWithPassiveNestedUpdates = root; |
|
} |
|
} else { |
|
nestedPassiveUpdateCount = 0; |
|
} |
|
|
|
isFlushingPassiveEffects = false; |
|
didScheduleUpdateDuringPassiveEffects = false; |
|
} // TODO: Move to commitPassiveMountEffects |
|
|
|
|
|
onPostCommitRoot(root); |
|
|
|
{ |
|
var stateNode = root.current.stateNode; |
|
stateNode.effectDuration = 0; |
|
stateNode.passiveEffectDuration = 0; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
function isAlreadyFailedLegacyErrorBoundary(instance) { |
|
return legacyErrorBoundariesThatAlreadyFailed !== null && legacyErrorBoundariesThatAlreadyFailed.has(instance); |
|
} |
|
function markLegacyErrorBoundaryAsFailed(instance) { |
|
if (legacyErrorBoundariesThatAlreadyFailed === null) { |
|
legacyErrorBoundariesThatAlreadyFailed = new Set([instance]); |
|
} else { |
|
legacyErrorBoundariesThatAlreadyFailed.add(instance); |
|
} |
|
} |
|
|
|
function prepareToThrowUncaughtError(error) { |
|
if (!hasUncaughtError) { |
|
hasUncaughtError = true; |
|
firstUncaughtError = error; |
|
} |
|
} |
|
|
|
var onUncaughtError = prepareToThrowUncaughtError; |
|
|
|
function captureCommitPhaseErrorOnRoot(rootFiber, sourceFiber, error) { |
|
var errorInfo = createCapturedValueAtFiber(error, sourceFiber); |
|
var update = createRootErrorUpdate(rootFiber, errorInfo, SyncLane); |
|
var root = enqueueUpdate(rootFiber, update, SyncLane); |
|
var eventTime = requestEventTime(); |
|
|
|
if (root !== null) { |
|
markRootUpdated(root, SyncLane, eventTime); |
|
ensureRootIsScheduled(root, eventTime); |
|
} |
|
} |
|
|
|
function captureCommitPhaseError(sourceFiber, nearestMountedAncestor, error$1) { |
|
{ |
|
reportUncaughtErrorInDEV(error$1); |
|
setIsRunningInsertionEffect(false); |
|
} |
|
|
|
if (sourceFiber.tag === HostRoot) { |
|
// Error was thrown at the root. There is no parent, so the root |
|
// itself should capture it. |
|
captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error$1); |
|
return; |
|
} |
|
|
|
var fiber = null; |
|
|
|
{ |
|
fiber = nearestMountedAncestor; |
|
} |
|
|
|
while (fiber !== null) { |
|
if (fiber.tag === HostRoot) { |
|
captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error$1); |
|
return; |
|
} else if (fiber.tag === ClassComponent) { |
|
var ctor = fiber.type; |
|
var instance = fiber.stateNode; |
|
|
|
if (typeof ctor.getDerivedStateFromError === 'function' || typeof instance.componentDidCatch === 'function' && !isAlreadyFailedLegacyErrorBoundary(instance)) { |
|
var errorInfo = createCapturedValueAtFiber(error$1, sourceFiber); |
|
var update = createClassErrorUpdate(fiber, errorInfo, SyncLane); |
|
var root = enqueueUpdate(fiber, update, SyncLane); |
|
var eventTime = requestEventTime(); |
|
|
|
if (root !== null) { |
|
markRootUpdated(root, SyncLane, eventTime); |
|
ensureRootIsScheduled(root, eventTime); |
|
} |
|
|
|
return; |
|
} |
|
} |
|
|
|
fiber = fiber.return; |
|
} |
|
|
|
{ |
|
// TODO: Until we re-land skipUnmountedBoundaries (see #20147), this warning |
|
// will fire for errors that are thrown by destroy functions inside deleted |
|
// trees. What it should instead do is propagate the error to the parent of |
|
// the deleted tree. In the meantime, do not add this warning to the |
|
// allowlist; this is only for our internal use. |
|
error('Internal React error: Attempted to capture a commit phase error ' + 'inside a detached tree. This indicates a bug in React. Likely ' + 'causes include deleting the same fiber more than once, committing an ' + 'already-finished tree, or an inconsistent return pointer.\n\n' + 'Error message:\n\n%s', error$1); |
|
} |
|
} |
|
function pingSuspendedRoot(root, wakeable, pingedLanes) { |
|
var pingCache = root.pingCache; |
|
|
|
if (pingCache !== null) { |
|
// The wakeable resolved, so we no longer need to memoize, because it will |
|
// never be thrown again. |
|
pingCache.delete(wakeable); |
|
} |
|
|
|
var eventTime = requestEventTime(); |
|
markRootPinged(root, pingedLanes); |
|
warnIfSuspenseResolutionNotWrappedWithActDEV(root); |
|
|
|
if (workInProgressRoot === root && isSubsetOfLanes(workInProgressRootRenderLanes, pingedLanes)) { |
|
// Received a ping at the same priority level at which we're currently |
|
// rendering. We might want to restart this render. This should mirror |
|
// the logic of whether or not a root suspends once it completes. |
|
// TODO: If we're rendering sync either due to Sync, Batched or expired, |
|
// we should probably never restart. |
|
// If we're suspended with delay, or if it's a retry, we'll always suspend |
|
// so we can always restart. |
|
if (workInProgressRootExitStatus === RootSuspendedWithDelay || workInProgressRootExitStatus === RootSuspended && includesOnlyRetries(workInProgressRootRenderLanes) && now() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS) { |
|
// Restart from the root. |
|
prepareFreshStack(root, NoLanes); |
|
} else { |
|
// Even though we can't restart right now, we might get an |
|
// opportunity later. So we mark this render as having a ping. |
|
workInProgressRootPingedLanes = mergeLanes(workInProgressRootPingedLanes, pingedLanes); |
|
} |
|
} |
|
|
|
ensureRootIsScheduled(root, eventTime); |
|
} |
|
|
|
function retryTimedOutBoundary(boundaryFiber, retryLane) { |
|
// The boundary fiber (a Suspense component or SuspenseList component) |
|
// previously was rendered in its fallback state. One of the promises that |
|
// suspended it has resolved, which means at least part of the tree was |
|
// likely unblocked. Try rendering again, at a new lanes. |
|
if (retryLane === NoLane) { |
|
// TODO: Assign this to `suspenseState.retryLane`? to avoid |
|
// unnecessary entanglement? |
|
retryLane = requestRetryLane(boundaryFiber); |
|
} // TODO: Special case idle priority? |
|
|
|
|
|
var eventTime = requestEventTime(); |
|
var root = enqueueConcurrentRenderForLane(boundaryFiber, retryLane); |
|
|
|
if (root !== null) { |
|
markRootUpdated(root, retryLane, eventTime); |
|
ensureRootIsScheduled(root, eventTime); |
|
} |
|
} |
|
|
|
function retryDehydratedSuspenseBoundary(boundaryFiber) { |
|
var suspenseState = boundaryFiber.memoizedState; |
|
var retryLane = NoLane; |
|
|
|
if (suspenseState !== null) { |
|
retryLane = suspenseState.retryLane; |
|
} |
|
|
|
retryTimedOutBoundary(boundaryFiber, retryLane); |
|
} |
|
function resolveRetryWakeable(boundaryFiber, wakeable) { |
|
var retryLane = NoLane; // Default |
|
|
|
var retryCache; |
|
|
|
switch (boundaryFiber.tag) { |
|
case SuspenseComponent: |
|
retryCache = boundaryFiber.stateNode; |
|
var suspenseState = boundaryFiber.memoizedState; |
|
|
|
if (suspenseState !== null) { |
|
retryLane = suspenseState.retryLane; |
|
} |
|
|
|
break; |
|
|
|
case SuspenseListComponent: |
|
retryCache = boundaryFiber.stateNode; |
|
break; |
|
|
|
default: |
|
throw new Error('Pinged unknown suspense boundary type. ' + 'This is probably a bug in React.'); |
|
} |
|
|
|
if (retryCache !== null) { |
|
// The wakeable resolved, so we no longer need to memoize, because it will |
|
// never be thrown again. |
|
retryCache.delete(wakeable); |
|
} |
|
|
|
retryTimedOutBoundary(boundaryFiber, retryLane); |
|
} // Computes the next Just Noticeable Difference (JND) boundary. |
|
// The theory is that a person can't tell the difference between small differences in time. |
|
// Therefore, if we wait a bit longer than necessary that won't translate to a noticeable |
|
// difference in the experience. However, waiting for longer might mean that we can avoid |
|
// showing an intermediate loading state. The longer we have already waited, the harder it |
|
// is to tell small differences in time. Therefore, the longer we've already waited, |
|
// the longer we can wait additionally. At some point we have to give up though. |
|
// We pick a train model where the next boundary commits at a consistent schedule. |
|
// These particular numbers are vague estimates. We expect to adjust them based on research. |
|
|
|
function jnd(timeElapsed) { |
|
return timeElapsed < 120 ? 120 : timeElapsed < 480 ? 480 : timeElapsed < 1080 ? 1080 : timeElapsed < 1920 ? 1920 : timeElapsed < 3000 ? 3000 : timeElapsed < 4320 ? 4320 : ceil(timeElapsed / 1960) * 1960; |
|
} |
|
|
|
function checkForNestedUpdates() { |
|
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) { |
|
nestedUpdateCount = 0; |
|
rootWithNestedUpdates = null; |
|
throw new Error('Maximum update depth exceeded. This can happen when a component ' + 'repeatedly calls setState inside componentWillUpdate or ' + 'componentDidUpdate. React limits the number of nested updates to ' + 'prevent infinite loops.'); |
|
} |
|
|
|
{ |
|
if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) { |
|
nestedPassiveUpdateCount = 0; |
|
rootWithPassiveNestedUpdates = null; |
|
|
|
error('Maximum update depth exceeded. This can happen when a component ' + "calls setState inside useEffect, but useEffect either doesn't " + 'have a dependency array, or one of the dependencies changes on ' + 'every render.'); |
|
} |
|
} |
|
} |
|
|
|
function flushRenderPhaseStrictModeWarningsInDEV() { |
|
{ |
|
ReactStrictModeWarnings.flushLegacyContextWarning(); |
|
|
|
{ |
|
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings(); |
|
} |
|
} |
|
} |
|
|
|
function commitDoubleInvokeEffectsInDEV(fiber, hasPassiveEffects) { |
|
{ |
|
// TODO (StrictEffects) Should we set a marker on the root if it contains strict effects |
|
// so we don't traverse unnecessarily? similar to subtreeFlags but just at the root level. |
|
// Maybe not a big deal since this is DEV only behavior. |
|
setCurrentFiber(fiber); |
|
invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV); |
|
|
|
if (hasPassiveEffects) { |
|
invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectUnmountInDEV); |
|
} |
|
|
|
invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV); |
|
|
|
if (hasPassiveEffects) { |
|
invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV); |
|
} |
|
|
|
resetCurrentFiber(); |
|
} |
|
} |
|
|
|
function invokeEffectsInDev(firstChild, fiberFlags, invokeEffectFn) { |
|
{ |
|
// We don't need to re-check StrictEffectsMode here. |
|
// This function is only called if that check has already passed. |
|
var current = firstChild; |
|
var subtreeRoot = null; |
|
|
|
while (current !== null) { |
|
var primarySubtreeFlag = current.subtreeFlags & fiberFlags; |
|
|
|
if (current !== subtreeRoot && current.child !== null && primarySubtreeFlag !== NoFlags) { |
|
current = current.child; |
|
} else { |
|
if ((current.flags & fiberFlags) !== NoFlags) { |
|
invokeEffectFn(current); |
|
} |
|
|
|
if (current.sibling !== null) { |
|
current = current.sibling; |
|
} else { |
|
current = subtreeRoot = current.return; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
var didWarnStateUpdateForNotYetMountedComponent = null; |
|
function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) { |
|
{ |
|
if ((executionContext & RenderContext) !== NoContext) { |
|
// We let the other warning about render phase updates deal with this one. |
|
return; |
|
} |
|
|
|
if (!(fiber.mode & ConcurrentMode)) { |
|
return; |
|
} |
|
|
|
var tag = fiber.tag; |
|
|
|
if (tag !== IndeterminateComponent && tag !== HostRoot && tag !== ClassComponent && tag !== FunctionComponent && tag !== ForwardRef && tag !== MemoComponent && tag !== SimpleMemoComponent) { |
|
// Only warn for user-defined components, not internal ones like Suspense. |
|
return; |
|
} // We show the whole stack but dedupe on the top component's name because |
|
// the problematic code almost always lies inside that component. |
|
|
|
|
|
var componentName = getComponentNameFromFiber(fiber) || 'ReactComponent'; |
|
|
|
if (didWarnStateUpdateForNotYetMountedComponent !== null) { |
|
if (didWarnStateUpdateForNotYetMountedComponent.has(componentName)) { |
|
return; |
|
} |
|
|
|
didWarnStateUpdateForNotYetMountedComponent.add(componentName); |
|
} else { |
|
didWarnStateUpdateForNotYetMountedComponent = new Set([componentName]); |
|
} |
|
|
|
var previousFiber = current; |
|
|
|
try { |
|
setCurrentFiber(fiber); |
|
|
|
error("Can't perform a React state update on a component that hasn't mounted yet. " + 'This indicates that you have a side-effect in your render function that ' + 'asynchronously later calls tries to update the component. Move this work to ' + 'useEffect instead.'); |
|
} finally { |
|
if (previousFiber) { |
|
setCurrentFiber(fiber); |
|
} else { |
|
resetCurrentFiber(); |
|
} |
|
} |
|
} |
|
} |
|
var beginWork$1; |
|
|
|
{ |
|
var dummyFiber = null; |
|
|
|
beginWork$1 = function (current, unitOfWork, lanes) { |
|
// If a component throws an error, we replay it again in a synchronously |
|
// dispatched event, so that the debugger will treat it as an uncaught |
|
// error See ReactErrorUtils for more information. |
|
// Before entering the begin phase, copy the work-in-progress onto a dummy |
|
// fiber. If beginWork throws, we'll use this to reset the state. |
|
var originalWorkInProgressCopy = assignFiberPropertiesInDEV(dummyFiber, unitOfWork); |
|
|
|
try { |
|
return beginWork(current, unitOfWork, lanes); |
|
} catch (originalError) { |
|
if (didSuspendOrErrorWhileHydratingDEV() || originalError !== null && typeof originalError === 'object' && typeof originalError.then === 'function') { |
|
// Don't replay promises. |
|
// Don't replay errors if we are hydrating and have already suspended or handled an error |
|
throw originalError; |
|
} // Keep this code in sync with handleError; any changes here must have |
|
// corresponding changes there. |
|
|
|
|
|
resetContextDependencies(); |
|
resetHooksAfterThrow(); // Don't reset current debug fiber, since we're about to work on the |
|
// same fiber again. |
|
// Unwind the failed stack frame |
|
|
|
unwindInterruptedWork(current, unitOfWork); // Restore the original properties of the fiber. |
|
|
|
assignFiberPropertiesInDEV(unitOfWork, originalWorkInProgressCopy); |
|
|
|
if ( unitOfWork.mode & ProfileMode) { |
|
// Reset the profiler timer. |
|
startProfilerTimer(unitOfWork); |
|
} // Run beginWork again. |
|
|
|
|
|
invokeGuardedCallback(null, beginWork, null, current, unitOfWork, lanes); |
|
|
|
if (hasCaughtError()) { |
|
var replayError = clearCaughtError(); |
|
|
|
if (typeof replayError === 'object' && replayError !== null && replayError._suppressLogging && typeof originalError === 'object' && originalError !== null && !originalError._suppressLogging) { |
|
// If suppressed, let the flag carry over to the original error which is the one we'll rethrow. |
|
originalError._suppressLogging = true; |
|
} |
|
} // We always throw the original error in case the second render pass is not idempotent. |
|
// This can happen if a memoized function or CommonJS module doesn't throw after first invocation. |
|
|
|
|
|
throw originalError; |
|
} |
|
}; |
|
} |
|
|
|
var didWarnAboutUpdateInRender = false; |
|
var didWarnAboutUpdateInRenderForAnotherComponent; |
|
|
|
{ |
|
didWarnAboutUpdateInRenderForAnotherComponent = new Set(); |
|
} |
|
|
|
function warnAboutRenderPhaseUpdatesInDEV(fiber) { |
|
{ |
|
if (isRendering && !getIsUpdatingOpaqueValueInRenderPhaseInDEV()) { |
|
switch (fiber.tag) { |
|
case FunctionComponent: |
|
case ForwardRef: |
|
case SimpleMemoComponent: |
|
{ |
|
var renderingComponentName = workInProgress && getComponentNameFromFiber(workInProgress) || 'Unknown'; // Dedupe by the rendering component because it's the one that needs to be fixed. |
|
|
|
var dedupeKey = renderingComponentName; |
|
|
|
if (!didWarnAboutUpdateInRenderForAnotherComponent.has(dedupeKey)) { |
|
didWarnAboutUpdateInRenderForAnotherComponent.add(dedupeKey); |
|
var setStateComponentName = getComponentNameFromFiber(fiber) || 'Unknown'; |
|
|
|
error('Cannot update a component (`%s`) while rendering a ' + 'different component (`%s`). To locate the bad setState() call inside `%s`, ' + 'follow the stack trace as described in https://reactjs.org/link/setstate-in-render', setStateComponentName, renderingComponentName, renderingComponentName); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case ClassComponent: |
|
{ |
|
if (!didWarnAboutUpdateInRender) { |
|
error('Cannot update during an existing state transition (such as ' + 'within `render`). Render methods should be a pure ' + 'function of props and state.'); |
|
|
|
didWarnAboutUpdateInRender = true; |
|
} |
|
|
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
function restorePendingUpdaters(root, lanes) { |
|
{ |
|
if (isDevToolsPresent) { |
|
var memoizedUpdaters = root.memoizedUpdaters; |
|
memoizedUpdaters.forEach(function (schedulingFiber) { |
|
addFiberToLanesMap(root, schedulingFiber, lanes); |
|
}); // This function intentionally does not clear memoized updaters. |
|
// Those may still be relevant to the current commit |
|
// and a future one (e.g. Suspense). |
|
} |
|
} |
|
} |
|
var fakeActCallbackNode = {}; |
|
|
|
function scheduleCallback$1(priorityLevel, callback) { |
|
{ |
|
// If we're currently inside an `act` scope, bypass Scheduler and push to |
|
// the `act` queue instead. |
|
var actQueue = ReactCurrentActQueue$1.current; |
|
|
|
if (actQueue !== null) { |
|
actQueue.push(callback); |
|
return fakeActCallbackNode; |
|
} else { |
|
return scheduleCallback(priorityLevel, callback); |
|
} |
|
} |
|
} |
|
|
|
function cancelCallback$1(callbackNode) { |
|
if ( callbackNode === fakeActCallbackNode) { |
|
return; |
|
} // In production, always call Scheduler. This function will be stripped out. |
|
|
|
|
|
return cancelCallback(callbackNode); |
|
} |
|
|
|
function shouldForceFlushFallbacksInDEV() { |
|
// Never force flush in production. This function should get stripped out. |
|
return ReactCurrentActQueue$1.current !== null; |
|
} |
|
|
|
function warnIfUpdatesNotWrappedWithActDEV(fiber) { |
|
{ |
|
if (fiber.mode & ConcurrentMode) { |
|
if (!isConcurrentActEnvironment()) { |
|
// Not in an act environment. No need to warn. |
|
return; |
|
} |
|
} else { |
|
// Legacy mode has additional cases where we suppress a warning. |
|
if (!isLegacyActEnvironment()) { |
|
// Not in an act environment. No need to warn. |
|
return; |
|
} |
|
|
|
if (executionContext !== NoContext) { |
|
// Legacy mode doesn't warn if the update is batched, i.e. |
|
// batchedUpdates or flushSync. |
|
return; |
|
} |
|
|
|
if (fiber.tag !== FunctionComponent && fiber.tag !== ForwardRef && fiber.tag !== SimpleMemoComponent) { |
|
// For backwards compatibility with pre-hooks code, legacy mode only |
|
// warns for updates that originate from a hook. |
|
return; |
|
} |
|
} |
|
|
|
if (ReactCurrentActQueue$1.current === null) { |
|
var previousFiber = current; |
|
|
|
try { |
|
setCurrentFiber(fiber); |
|
|
|
error('An update to %s inside a test was not wrapped in act(...).\n\n' + 'When testing, code that causes React state updates should be ' + 'wrapped into act(...):\n\n' + 'act(() => {\n' + ' /* fire events that update state */\n' + '});\n' + '/* assert on the output */\n\n' + "This ensures that you're testing the behavior the user would see " + 'in the browser.' + ' Learn more at https://reactjs.org/link/wrap-tests-with-act', getComponentNameFromFiber(fiber)); |
|
} finally { |
|
if (previousFiber) { |
|
setCurrentFiber(fiber); |
|
} else { |
|
resetCurrentFiber(); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
function warnIfSuspenseResolutionNotWrappedWithActDEV(root) { |
|
{ |
|
if (root.tag !== LegacyRoot && isConcurrentActEnvironment() && ReactCurrentActQueue$1.current === null) { |
|
error('A suspended resource finished loading inside a test, but the event ' + 'was not wrapped in act(...).\n\n' + 'When testing, code that resolves suspended data should be wrapped ' + 'into act(...):\n\n' + 'act(() => {\n' + ' /* finish loading suspended data */\n' + '});\n' + '/* assert on the output */\n\n' + "This ensures that you're testing the behavior the user would see " + 'in the browser.' + ' Learn more at https://reactjs.org/link/wrap-tests-with-act'); |
|
} |
|
} |
|
} |
|
|
|
function setIsRunningInsertionEffect(isRunning) { |
|
{ |
|
isRunningInsertionEffect = isRunning; |
|
} |
|
} |
|
|
|
/* eslint-disable react-internal/prod-error-codes */ |
|
var resolveFamily = null; // $FlowFixMe Flow gets confused by a WeakSet feature check below. |
|
|
|
var failedBoundaries = null; |
|
var setRefreshHandler = function (handler) { |
|
{ |
|
resolveFamily = handler; |
|
} |
|
}; |
|
function resolveFunctionForHotReloading(type) { |
|
{ |
|
if (resolveFamily === null) { |
|
// Hot reloading is disabled. |
|
return type; |
|
} |
|
|
|
var family = resolveFamily(type); |
|
|
|
if (family === undefined) { |
|
return type; |
|
} // Use the latest known implementation. |
|
|
|
|
|
return family.current; |
|
} |
|
} |
|
function resolveClassForHotReloading(type) { |
|
// No implementation differences. |
|
return resolveFunctionForHotReloading(type); |
|
} |
|
function resolveForwardRefForHotReloading(type) { |
|
{ |
|
if (resolveFamily === null) { |
|
// Hot reloading is disabled. |
|
return type; |
|
} |
|
|
|
var family = resolveFamily(type); |
|
|
|
if (family === undefined) { |
|
// Check if we're dealing with a real forwardRef. Don't want to crash early. |
|
if (type !== null && type !== undefined && typeof type.render === 'function') { |
|
// ForwardRef is special because its resolved .type is an object, |
|
// but it's possible that we only have its inner render function in the map. |
|
// If that inner render function is different, we'll build a new forwardRef type. |
|
var currentRender = resolveFunctionForHotReloading(type.render); |
|
|
|
if (type.render !== currentRender) { |
|
var syntheticType = { |
|
$$typeof: REACT_FORWARD_REF_TYPE, |
|
render: currentRender |
|
}; |
|
|
|
if (type.displayName !== undefined) { |
|
syntheticType.displayName = type.displayName; |
|
} |
|
|
|
return syntheticType; |
|
} |
|
} |
|
|
|
return type; |
|
} // Use the latest known implementation. |
|
|
|
|
|
return family.current; |
|
} |
|
} |
|
function isCompatibleFamilyForHotReloading(fiber, element) { |
|
{ |
|
if (resolveFamily === null) { |
|
// Hot reloading is disabled. |
|
return false; |
|
} |
|
|
|
var prevType = fiber.elementType; |
|
var nextType = element.type; // If we got here, we know types aren't === equal. |
|
|
|
var needsCompareFamilies = false; |
|
var $$typeofNextType = typeof nextType === 'object' && nextType !== null ? nextType.$$typeof : null; |
|
|
|
switch (fiber.tag) { |
|
case ClassComponent: |
|
{ |
|
if (typeof nextType === 'function') { |
|
needsCompareFamilies = true; |
|
} |
|
|
|
break; |
|
} |
|
|
|
case FunctionComponent: |
|
{ |
|
if (typeof nextType === 'function') { |
|
needsCompareFamilies = true; |
|
} else if ($$typeofNextType === REACT_LAZY_TYPE) { |
|
// We don't know the inner type yet. |
|
// We're going to assume that the lazy inner type is stable, |
|
// and so it is sufficient to avoid reconciling it away. |
|
// We're not going to unwrap or actually use the new lazy type. |
|
needsCompareFamilies = true; |
|
} |
|
|
|
break; |
|
} |
|
|
|
case ForwardRef: |
|
{ |
|
if ($$typeofNextType === REACT_FORWARD_REF_TYPE) { |
|
needsCompareFamilies = true; |
|
} else if ($$typeofNextType === REACT_LAZY_TYPE) { |
|
needsCompareFamilies = true; |
|
} |
|
|
|
break; |
|
} |
|
|
|
case MemoComponent: |
|
case SimpleMemoComponent: |
|
{ |
|
if ($$typeofNextType === REACT_MEMO_TYPE) { |
|
// TODO: if it was but can no longer be simple, |
|
// we shouldn't set this. |
|
needsCompareFamilies = true; |
|
} else if ($$typeofNextType === REACT_LAZY_TYPE) { |
|
needsCompareFamilies = true; |
|
} |
|
|
|
break; |
|
} |
|
|
|
default: |
|
return false; |
|
} // Check if both types have a family and it's the same one. |
|
|
|
|
|
if (needsCompareFamilies) { |
|
// Note: memo() and forwardRef() we'll compare outer rather than inner type. |
|
// This means both of them need to be registered to preserve state. |
|
// If we unwrapped and compared the inner types for wrappers instead, |
|
// then we would risk falsely saying two separate memo(Foo) |
|
// calls are equivalent because they wrap the same Foo function. |
|
var prevFamily = resolveFamily(prevType); |
|
|
|
if (prevFamily !== undefined && prevFamily === resolveFamily(nextType)) { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
} |
|
function markFailedErrorBoundaryForHotReloading(fiber) { |
|
{ |
|
if (resolveFamily === null) { |
|
// Hot reloading is disabled. |
|
return; |
|
} |
|
|
|
if (typeof WeakSet !== 'function') { |
|
return; |
|
} |
|
|
|
if (failedBoundaries === null) { |
|
failedBoundaries = new WeakSet(); |
|
} |
|
|
|
failedBoundaries.add(fiber); |
|
} |
|
} |
|
var scheduleRefresh = function (root, update) { |
|
{ |
|
if (resolveFamily === null) { |
|
// Hot reloading is disabled. |
|
return; |
|
} |
|
|
|
var staleFamilies = update.staleFamilies, |
|
updatedFamilies = update.updatedFamilies; |
|
flushPassiveEffects(); |
|
flushSync(function () { |
|
scheduleFibersWithFamiliesRecursively(root.current, updatedFamilies, staleFamilies); |
|
}); |
|
} |
|
}; |
|
var scheduleRoot = function (root, element) { |
|
{ |
|
if (root.context !== emptyContextObject) { |
|
// Super edge case: root has a legacy _renderSubtree context |
|
// but we don't know the parentComponent so we can't pass it. |
|
// Just ignore. We'll delete this with _renderSubtree code path later. |
|
return; |
|
} |
|
|
|
flushPassiveEffects(); |
|
flushSync(function () { |
|
updateContainer(element, root, null, null); |
|
}); |
|
} |
|
}; |
|
|
|
function scheduleFibersWithFamiliesRecursively(fiber, updatedFamilies, staleFamilies) { |
|
{ |
|
var alternate = fiber.alternate, |
|
child = fiber.child, |
|
sibling = fiber.sibling, |
|
tag = fiber.tag, |
|
type = fiber.type; |
|
var candidateType = null; |
|
|
|
switch (tag) { |
|
case FunctionComponent: |
|
case SimpleMemoComponent: |
|
case ClassComponent: |
|
candidateType = type; |
|
break; |
|
|
|
case ForwardRef: |
|
candidateType = type.render; |
|
break; |
|
} |
|
|
|
if (resolveFamily === null) { |
|
throw new Error('Expected resolveFamily to be set during hot reload.'); |
|
} |
|
|
|
var needsRender = false; |
|
var needsRemount = false; |
|
|
|
if (candidateType !== null) { |
|
var family = resolveFamily(candidateType); |
|
|
|
if (family !== undefined) { |
|
if (staleFamilies.has(family)) { |
|
needsRemount = true; |
|
} else if (updatedFamilies.has(family)) { |
|
if (tag === ClassComponent) { |
|
needsRemount = true; |
|
} else { |
|
needsRender = true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
if (failedBoundaries !== null) { |
|
if (failedBoundaries.has(fiber) || alternate !== null && failedBoundaries.has(alternate)) { |
|
needsRemount = true; |
|
} |
|
} |
|
|
|
if (needsRemount) { |
|
fiber._debugNeedsRemount = true; |
|
} |
|
|
|
if (needsRemount || needsRender) { |
|
var _root = enqueueConcurrentRenderForLane(fiber, SyncLane); |
|
|
|
if (_root !== null) { |
|
scheduleUpdateOnFiber(_root, fiber, SyncLane, NoTimestamp); |
|
} |
|
} |
|
|
|
if (child !== null && !needsRemount) { |
|
scheduleFibersWithFamiliesRecursively(child, updatedFamilies, staleFamilies); |
|
} |
|
|
|
if (sibling !== null) { |
|
scheduleFibersWithFamiliesRecursively(sibling, updatedFamilies, staleFamilies); |
|
} |
|
} |
|
} |
|
|
|
var findHostInstancesForRefresh = function (root, families) { |
|
{ |
|
var hostInstances = new Set(); |
|
var types = new Set(families.map(function (family) { |
|
return family.current; |
|
})); |
|
findHostInstancesForMatchingFibersRecursively(root.current, types, hostInstances); |
|
return hostInstances; |
|
} |
|
}; |
|
|
|
function findHostInstancesForMatchingFibersRecursively(fiber, types, hostInstances) { |
|
{ |
|
var child = fiber.child, |
|
sibling = fiber.sibling, |
|
tag = fiber.tag, |
|
type = fiber.type; |
|
var candidateType = null; |
|
|
|
switch (tag) { |
|
case FunctionComponent: |
|
case SimpleMemoComponent: |
|
case ClassComponent: |
|
candidateType = type; |
|
break; |
|
|
|
case ForwardRef: |
|
candidateType = type.render; |
|
break; |
|
} |
|
|
|
var didMatch = false; |
|
|
|
if (candidateType !== null) { |
|
if (types.has(candidateType)) { |
|
didMatch = true; |
|
} |
|
} |
|
|
|
if (didMatch) { |
|
// We have a match. This only drills down to the closest host components. |
|
// There's no need to search deeper because for the purpose of giving |
|
// visual feedback, "flashing" outermost parent rectangles is sufficient. |
|
findHostInstancesForFiberShallowly(fiber, hostInstances); |
|
} else { |
|
// If there's no match, maybe there will be one further down in the child tree. |
|
if (child !== null) { |
|
findHostInstancesForMatchingFibersRecursively(child, types, hostInstances); |
|
} |
|
} |
|
|
|
if (sibling !== null) { |
|
findHostInstancesForMatchingFibersRecursively(sibling, types, hostInstances); |
|
} |
|
} |
|
} |
|
|
|
function findHostInstancesForFiberShallowly(fiber, hostInstances) { |
|
{ |
|
var foundHostInstances = findChildHostInstancesForFiberShallowly(fiber, hostInstances); |
|
|
|
if (foundHostInstances) { |
|
return; |
|
} // If we didn't find any host children, fallback to closest host parent. |
|
|
|
|
|
var node = fiber; |
|
|
|
while (true) { |
|
switch (node.tag) { |
|
case HostComponent: |
|
hostInstances.add(node.stateNode); |
|
return; |
|
|
|
case HostPortal: |
|
hostInstances.add(node.stateNode.containerInfo); |
|
return; |
|
|
|
case HostRoot: |
|
hostInstances.add(node.stateNode.containerInfo); |
|
return; |
|
} |
|
|
|
if (node.return === null) { |
|
throw new Error('Expected to reach root first.'); |
|
} |
|
|
|
node = node.return; |
|
} |
|
} |
|
} |
|
|
|
function findChildHostInstancesForFiberShallowly(fiber, hostInstances) { |
|
{ |
|
var node = fiber; |
|
var foundHostInstances = false; |
|
|
|
while (true) { |
|
if (node.tag === HostComponent) { |
|
// We got a match. |
|
foundHostInstances = true; |
|
hostInstances.add(node.stateNode); // There may still be more, so keep searching. |
|
} else if (node.child !== null) { |
|
node.child.return = node; |
|
node = node.child; |
|
continue; |
|
} |
|
|
|
if (node === fiber) { |
|
return foundHostInstances; |
|
} |
|
|
|
while (node.sibling === null) { |
|
if (node.return === null || node.return === fiber) { |
|
return foundHostInstances; |
|
} |
|
|
|
node = node.return; |
|
} |
|
|
|
node.sibling.return = node.return; |
|
node = node.sibling; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
var hasBadMapPolyfill; |
|
|
|
{ |
|
hasBadMapPolyfill = false; |
|
|
|
try { |
|
var nonExtensibleObject = Object.preventExtensions({}); |
|
/* eslint-disable no-new */ |
|
|
|
new Map([[nonExtensibleObject, null]]); |
|
new Set([nonExtensibleObject]); |
|
/* eslint-enable no-new */ |
|
} catch (e) { |
|
// TODO: Consider warning about bad polyfills |
|
hasBadMapPolyfill = true; |
|
} |
|
} |
|
|
|
function FiberNode(tag, pendingProps, key, mode) { |
|
// Instance |
|
this.tag = tag; |
|
this.key = key; |
|
this.elementType = null; |
|
this.type = null; |
|
this.stateNode = null; // Fiber |
|
|
|
this.return = null; |
|
this.child = null; |
|
this.sibling = null; |
|
this.index = 0; |
|
this.ref = null; |
|
this.pendingProps = pendingProps; |
|
this.memoizedProps = null; |
|
this.updateQueue = null; |
|
this.memoizedState = null; |
|
this.dependencies = null; |
|
this.mode = mode; // Effects |
|
|
|
this.flags = NoFlags; |
|
this.subtreeFlags = NoFlags; |
|
this.deletions = null; |
|
this.lanes = NoLanes; |
|
this.childLanes = NoLanes; |
|
this.alternate = null; |
|
|
|
{ |
|
// Note: The following is done to avoid a v8 performance cliff. |
|
// |
|
// Initializing the fields below to smis and later updating them with |
|
// double values will cause Fibers to end up having separate shapes. |
|
// This behavior/bug has something to do with Object.preventExtension(). |
|
// Fortunately this only impacts DEV builds. |
|
// Unfortunately it makes React unusably slow for some applications. |
|
// To work around this, initialize the fields below with doubles. |
|
// |
|
// Learn more about this here: |
|
// https://github.com/facebook/react/issues/14365 |
|
// https://bugs.chromium.org/p/v8/issues/detail?id=8538 |
|
this.actualDuration = Number.NaN; |
|
this.actualStartTime = Number.NaN; |
|
this.selfBaseDuration = Number.NaN; |
|
this.treeBaseDuration = Number.NaN; // It's okay to replace the initial doubles with smis after initialization. |
|
// This won't trigger the performance cliff mentioned above, |
|
// and it simplifies other profiler code (including DevTools). |
|
|
|
this.actualDuration = 0; |
|
this.actualStartTime = -1; |
|
this.selfBaseDuration = 0; |
|
this.treeBaseDuration = 0; |
|
} |
|
|
|
{ |
|
// This isn't directly used but is handy for debugging internals: |
|
this._debugSource = null; |
|
this._debugOwner = null; |
|
this._debugNeedsRemount = false; |
|
this._debugHookTypes = null; |
|
|
|
if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') { |
|
Object.preventExtensions(this); |
|
} |
|
} |
|
} // This is a constructor function, rather than a POJO constructor, still |
|
// please ensure we do the following: |
|
// 1) Nobody should add any instance methods on this. Instance methods can be |
|
// more difficult to predict when they get optimized and they are almost |
|
// never inlined properly in static compilers. |
|
// 2) Nobody should rely on `instanceof Fiber` for type testing. We should |
|
// always know when it is a fiber. |
|
// 3) We might want to experiment with using numeric keys since they are easier |
|
// to optimize in a non-JIT environment. |
|
// 4) We can easily go from a constructor to a createFiber object literal if that |
|
// is faster. |
|
// 5) It should be easy to port this to a C struct and keep a C implementation |
|
// compatible. |
|
|
|
|
|
var createFiber = function (tag, pendingProps, key, mode) { |
|
// $FlowFixMe: the shapes are exact here but Flow doesn't like constructors |
|
return new FiberNode(tag, pendingProps, key, mode); |
|
}; |
|
|
|
function shouldConstruct$1(Component) { |
|
var prototype = Component.prototype; |
|
return !!(prototype && prototype.isReactComponent); |
|
} |
|
|
|
function isSimpleFunctionComponent(type) { |
|
return typeof type === 'function' && !shouldConstruct$1(type) && type.defaultProps === undefined; |
|
} |
|
function resolveLazyComponentTag(Component) { |
|
if (typeof Component === 'function') { |
|
return shouldConstruct$1(Component) ? ClassComponent : FunctionComponent; |
|
} else if (Component !== undefined && Component !== null) { |
|
var $$typeof = Component.$$typeof; |
|
|
|
if ($$typeof === REACT_FORWARD_REF_TYPE) { |
|
return ForwardRef; |
|
} |
|
|
|
if ($$typeof === REACT_MEMO_TYPE) { |
|
return MemoComponent; |
|
} |
|
} |
|
|
|
return IndeterminateComponent; |
|
} // This is used to create an alternate fiber to do work on. |
|
|
|
function createWorkInProgress(current, pendingProps) { |
|
var workInProgress = current.alternate; |
|
|
|
if (workInProgress === null) { |
|
// We use a double buffering pooling technique because we know that we'll |
|
// only ever need at most two versions of a tree. We pool the "other" unused |
|
// node that we're free to reuse. This is lazily created to avoid allocating |
|
// extra objects for things that are never updated. It also allow us to |
|
// reclaim the extra memory if needed. |
|
workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode); |
|
workInProgress.elementType = current.elementType; |
|
workInProgress.type = current.type; |
|
workInProgress.stateNode = current.stateNode; |
|
|
|
{ |
|
// DEV-only fields |
|
workInProgress._debugSource = current._debugSource; |
|
workInProgress._debugOwner = current._debugOwner; |
|
workInProgress._debugHookTypes = current._debugHookTypes; |
|
} |
|
|
|
workInProgress.alternate = current; |
|
current.alternate = workInProgress; |
|
} else { |
|
workInProgress.pendingProps = pendingProps; // Needed because Blocks store data on type. |
|
|
|
workInProgress.type = current.type; // We already have an alternate. |
|
// Reset the effect tag. |
|
|
|
workInProgress.flags = NoFlags; // The effects are no longer valid. |
|
|
|
workInProgress.subtreeFlags = NoFlags; |
|
workInProgress.deletions = null; |
|
|
|
{ |
|
// We intentionally reset, rather than copy, actualDuration & actualStartTime. |
|
// This prevents time from endlessly accumulating in new commits. |
|
// This has the downside of resetting values for different priority renders, |
|
// But works for yielding (the common case) and should support resuming. |
|
workInProgress.actualDuration = 0; |
|
workInProgress.actualStartTime = -1; |
|
} |
|
} // Reset all effects except static ones. |
|
// Static effects are not specific to a render. |
|
|
|
|
|
workInProgress.flags = current.flags & StaticMask; |
|
workInProgress.childLanes = current.childLanes; |
|
workInProgress.lanes = current.lanes; |
|
workInProgress.child = current.child; |
|
workInProgress.memoizedProps = current.memoizedProps; |
|
workInProgress.memoizedState = current.memoizedState; |
|
workInProgress.updateQueue = current.updateQueue; // Clone the dependencies object. This is mutated during the render phase, so |
|
// it cannot be shared with the current fiber. |
|
|
|
var currentDependencies = current.dependencies; |
|
workInProgress.dependencies = currentDependencies === null ? null : { |
|
lanes: currentDependencies.lanes, |
|
firstContext: currentDependencies.firstContext |
|
}; // These will be overridden during the parent's reconciliation |
|
|
|
workInProgress.sibling = current.sibling; |
|
workInProgress.index = current.index; |
|
workInProgress.ref = current.ref; |
|
|
|
{ |
|
workInProgress.selfBaseDuration = current.selfBaseDuration; |
|
workInProgress.treeBaseDuration = current.treeBaseDuration; |
|
} |
|
|
|
{ |
|
workInProgress._debugNeedsRemount = current._debugNeedsRemount; |
|
|
|
switch (workInProgress.tag) { |
|
case IndeterminateComponent: |
|
case FunctionComponent: |
|
case SimpleMemoComponent: |
|
workInProgress.type = resolveFunctionForHotReloading(current.type); |
|
break; |
|
|
|
case ClassComponent: |
|
workInProgress.type = resolveClassForHotReloading(current.type); |
|
break; |
|
|
|
case ForwardRef: |
|
workInProgress.type = resolveForwardRefForHotReloading(current.type); |
|
break; |
|
} |
|
} |
|
|
|
return workInProgress; |
|
} // Used to reuse a Fiber for a second pass. |
|
|
|
function resetWorkInProgress(workInProgress, renderLanes) { |
|
// This resets the Fiber to what createFiber or createWorkInProgress would |
|
// have set the values to before during the first pass. Ideally this wouldn't |
|
// be necessary but unfortunately many code paths reads from the workInProgress |
|
// when they should be reading from current and writing to workInProgress. |
|
// We assume pendingProps, index, key, ref, return are still untouched to |
|
// avoid doing another reconciliation. |
|
// Reset the effect flags but keep any Placement tags, since that's something |
|
// that child fiber is setting, not the reconciliation. |
|
workInProgress.flags &= StaticMask | Placement; // The effects are no longer valid. |
|
|
|
var current = workInProgress.alternate; |
|
|
|
if (current === null) { |
|
// Reset to createFiber's initial values. |
|
workInProgress.childLanes = NoLanes; |
|
workInProgress.lanes = renderLanes; |
|
workInProgress.child = null; |
|
workInProgress.subtreeFlags = NoFlags; |
|
workInProgress.memoizedProps = null; |
|
workInProgress.memoizedState = null; |
|
workInProgress.updateQueue = null; |
|
workInProgress.dependencies = null; |
|
workInProgress.stateNode = null; |
|
|
|
{ |
|
// Note: We don't reset the actualTime counts. It's useful to accumulate |
|
// actual time across multiple render passes. |
|
workInProgress.selfBaseDuration = 0; |
|
workInProgress.treeBaseDuration = 0; |
|
} |
|
} else { |
|
// Reset to the cloned values that createWorkInProgress would've. |
|
workInProgress.childLanes = current.childLanes; |
|
workInProgress.lanes = current.lanes; |
|
workInProgress.child = current.child; |
|
workInProgress.subtreeFlags = NoFlags; |
|
workInProgress.deletions = null; |
|
workInProgress.memoizedProps = current.memoizedProps; |
|
workInProgress.memoizedState = current.memoizedState; |
|
workInProgress.updateQueue = current.updateQueue; // Needed because Blocks store data on type. |
|
|
|
workInProgress.type = current.type; // Clone the dependencies object. This is mutated during the render phase, so |
|
// it cannot be shared with the current fiber. |
|
|
|
var currentDependencies = current.dependencies; |
|
workInProgress.dependencies = currentDependencies === null ? null : { |
|
lanes: currentDependencies.lanes, |
|
firstContext: currentDependencies.firstContext |
|
}; |
|
|
|
{ |
|
// Note: We don't reset the actualTime counts. It's useful to accumulate |
|
// actual time across multiple render passes. |
|
workInProgress.selfBaseDuration = current.selfBaseDuration; |
|
workInProgress.treeBaseDuration = current.treeBaseDuration; |
|
} |
|
} |
|
|
|
return workInProgress; |
|
} |
|
function createHostRootFiber(tag, isStrictMode, concurrentUpdatesByDefaultOverride) { |
|
var mode; |
|
|
|
if (tag === ConcurrentRoot) { |
|
mode = ConcurrentMode; |
|
|
|
if (isStrictMode === true) { |
|
mode |= StrictLegacyMode; |
|
|
|
{ |
|
mode |= StrictEffectsMode; |
|
} |
|
} |
|
} else { |
|
mode = NoMode; |
|
} |
|
|
|
if ( isDevToolsPresent) { |
|
// Always collect profile timings when DevTools are present. |
|
// This enables DevTools to start capturing timing at any point– |
|
// Without some nodes in the tree having empty base times. |
|
mode |= ProfileMode; |
|
} |
|
|
|
return createFiber(HostRoot, null, null, mode); |
|
} |
|
function createFiberFromTypeAndProps(type, // React$ElementType |
|
key, pendingProps, owner, mode, lanes) { |
|
var fiberTag = IndeterminateComponent; // The resolved type is set if we know what the final type will be. I.e. it's not lazy. |
|
|
|
var resolvedType = type; |
|
|
|
if (typeof type === 'function') { |
|
if (shouldConstruct$1(type)) { |
|
fiberTag = ClassComponent; |
|
|
|
{ |
|
resolvedType = resolveClassForHotReloading(resolvedType); |
|
} |
|
} else { |
|
{ |
|
resolvedType = resolveFunctionForHotReloading(resolvedType); |
|
} |
|
} |
|
} else if (typeof type === 'string') { |
|
fiberTag = HostComponent; |
|
} else { |
|
getTag: switch (type) { |
|
case REACT_FRAGMENT_TYPE: |
|
return createFiberFromFragment(pendingProps.children, mode, lanes, key); |
|
|
|
case REACT_STRICT_MODE_TYPE: |
|
fiberTag = Mode; |
|
mode |= StrictLegacyMode; |
|
|
|
if ( (mode & ConcurrentMode) !== NoMode) { |
|
// Strict effects should never run on legacy roots |
|
mode |= StrictEffectsMode; |
|
} |
|
|
|
break; |
|
|
|
case REACT_PROFILER_TYPE: |
|
return createFiberFromProfiler(pendingProps, mode, lanes, key); |
|
|
|
case REACT_SUSPENSE_TYPE: |
|
return createFiberFromSuspense(pendingProps, mode, lanes, key); |
|
|
|
case REACT_SUSPENSE_LIST_TYPE: |
|
return createFiberFromSuspenseList(pendingProps, mode, lanes, key); |
|
|
|
case REACT_OFFSCREEN_TYPE: |
|
return createFiberFromOffscreen(pendingProps, mode, lanes, key); |
|
|
|
case REACT_LEGACY_HIDDEN_TYPE: |
|
|
|
// eslint-disable-next-line no-fallthrough |
|
|
|
case REACT_SCOPE_TYPE: |
|
|
|
// eslint-disable-next-line no-fallthrough |
|
|
|
case REACT_CACHE_TYPE: |
|
|
|
// eslint-disable-next-line no-fallthrough |
|
|
|
case REACT_TRACING_MARKER_TYPE: |
|
|
|
// eslint-disable-next-line no-fallthrough |
|
|
|
case REACT_DEBUG_TRACING_MODE_TYPE: |
|
|
|
// eslint-disable-next-line no-fallthrough |
|
|
|
default: |
|
{ |
|
if (typeof type === 'object' && type !== null) { |
|
switch (type.$$typeof) { |
|
case REACT_PROVIDER_TYPE: |
|
fiberTag = ContextProvider; |
|
break getTag; |
|
|
|
case REACT_CONTEXT_TYPE: |
|
// This is a consumer |
|
fiberTag = ContextConsumer; |
|
break getTag; |
|
|
|
case REACT_FORWARD_REF_TYPE: |
|
fiberTag = ForwardRef; |
|
|
|
{ |
|
resolvedType = resolveForwardRefForHotReloading(resolvedType); |
|
} |
|
|
|
break getTag; |
|
|
|
case REACT_MEMO_TYPE: |
|
fiberTag = MemoComponent; |
|
break getTag; |
|
|
|
case REACT_LAZY_TYPE: |
|
fiberTag = LazyComponent; |
|
resolvedType = null; |
|
break getTag; |
|
} |
|
} |
|
|
|
var info = ''; |
|
|
|
{ |
|
if (type === undefined || typeof type === 'object' && type !== null && Object.keys(type).length === 0) { |
|
info += ' You likely forgot to export your component from the file ' + "it's defined in, or you might have mixed up default and " + 'named imports.'; |
|
} |
|
|
|
var ownerName = owner ? getComponentNameFromFiber(owner) : null; |
|
|
|
if (ownerName) { |
|
info += '\n\nCheck the render method of `' + ownerName + '`.'; |
|
} |
|
} |
|
|
|
throw new Error('Element type is invalid: expected a string (for built-in ' + 'components) or a class/function (for composite components) ' + ("but got: " + (type == null ? type : typeof type) + "." + info)); |
|
} |
|
} |
|
} |
|
|
|
var fiber = createFiber(fiberTag, pendingProps, key, mode); |
|
fiber.elementType = type; |
|
fiber.type = resolvedType; |
|
fiber.lanes = lanes; |
|
|
|
{ |
|
fiber._debugOwner = owner; |
|
} |
|
|
|
return fiber; |
|
} |
|
function createFiberFromElement(element, mode, lanes) { |
|
var owner = null; |
|
|
|
{ |
|
owner = element._owner; |
|
} |
|
|
|
var type = element.type; |
|
var key = element.key; |
|
var pendingProps = element.props; |
|
var fiber = createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, lanes); |
|
|
|
{ |
|
fiber._debugSource = element._source; |
|
fiber._debugOwner = element._owner; |
|
} |
|
|
|
return fiber; |
|
} |
|
function createFiberFromFragment(elements, mode, lanes, key) { |
|
var fiber = createFiber(Fragment, elements, key, mode); |
|
fiber.lanes = lanes; |
|
return fiber; |
|
} |
|
|
|
function createFiberFromProfiler(pendingProps, mode, lanes, key) { |
|
{ |
|
if (typeof pendingProps.id !== 'string') { |
|
error('Profiler must specify an "id" of type `string` as a prop. Received the type `%s` instead.', typeof pendingProps.id); |
|
} |
|
} |
|
|
|
var fiber = createFiber(Profiler, pendingProps, key, mode | ProfileMode); |
|
fiber.elementType = REACT_PROFILER_TYPE; |
|
fiber.lanes = lanes; |
|
|
|
{ |
|
fiber.stateNode = { |
|
effectDuration: 0, |
|
passiveEffectDuration: 0 |
|
}; |
|
} |
|
|
|
return fiber; |
|
} |
|
|
|
function createFiberFromSuspense(pendingProps, mode, lanes, key) { |
|
var fiber = createFiber(SuspenseComponent, pendingProps, key, mode); |
|
fiber.elementType = REACT_SUSPENSE_TYPE; |
|
fiber.lanes = lanes; |
|
return fiber; |
|
} |
|
function createFiberFromSuspenseList(pendingProps, mode, lanes, key) { |
|
var fiber = createFiber(SuspenseListComponent, pendingProps, key, mode); |
|
fiber.elementType = REACT_SUSPENSE_LIST_TYPE; |
|
fiber.lanes = lanes; |
|
return fiber; |
|
} |
|
function createFiberFromOffscreen(pendingProps, mode, lanes, key) { |
|
var fiber = createFiber(OffscreenComponent, pendingProps, key, mode); |
|
fiber.elementType = REACT_OFFSCREEN_TYPE; |
|
fiber.lanes = lanes; |
|
var primaryChildInstance = { |
|
isHidden: false |
|
}; |
|
fiber.stateNode = primaryChildInstance; |
|
return fiber; |
|
} |
|
function createFiberFromText(content, mode, lanes) { |
|
var fiber = createFiber(HostText, content, null, mode); |
|
fiber.lanes = lanes; |
|
return fiber; |
|
} |
|
function createFiberFromHostInstanceForDeletion() { |
|
var fiber = createFiber(HostComponent, null, null, NoMode); |
|
fiber.elementType = 'DELETED'; |
|
return fiber; |
|
} |
|
function createFiberFromDehydratedFragment(dehydratedNode) { |
|
var fiber = createFiber(DehydratedFragment, null, null, NoMode); |
|
fiber.stateNode = dehydratedNode; |
|
return fiber; |
|
} |
|
function createFiberFromPortal(portal, mode, lanes) { |
|
var pendingProps = portal.children !== null ? portal.children : []; |
|
var fiber = createFiber(HostPortal, pendingProps, portal.key, mode); |
|
fiber.lanes = lanes; |
|
fiber.stateNode = { |
|
containerInfo: portal.containerInfo, |
|
pendingChildren: null, |
|
// Used by persistent updates |
|
implementation: portal.implementation |
|
}; |
|
return fiber; |
|
} // Used for stashing WIP properties to replay failed work in DEV. |
|
|
|
function assignFiberPropertiesInDEV(target, source) { |
|
if (target === null) { |
|
// This Fiber's initial properties will always be overwritten. |
|
// We only use a Fiber to ensure the same hidden class so DEV isn't slow. |
|
target = createFiber(IndeterminateComponent, null, null, NoMode); |
|
} // This is intentionally written as a list of all properties. |
|
// We tried to use Object.assign() instead but this is called in |
|
// the hottest path, and Object.assign() was too slow: |
|
// https://github.com/facebook/react/issues/12502 |
|
// This code is DEV-only so size is not a concern. |
|
|
|
|
|
target.tag = source.tag; |
|
target.key = source.key; |
|
target.elementType = source.elementType; |
|
target.type = source.type; |
|
target.stateNode = source.stateNode; |
|
target.return = source.return; |
|
target.child = source.child; |
|
target.sibling = source.sibling; |
|
target.index = source.index; |
|
target.ref = source.ref; |
|
target.pendingProps = source.pendingProps; |
|
target.memoizedProps = source.memoizedProps; |
|
target.updateQueue = source.updateQueue; |
|
target.memoizedState = source.memoizedState; |
|
target.dependencies = source.dependencies; |
|
target.mode = source.mode; |
|
target.flags = source.flags; |
|
target.subtreeFlags = source.subtreeFlags; |
|
target.deletions = source.deletions; |
|
target.lanes = source.lanes; |
|
target.childLanes = source.childLanes; |
|
target.alternate = source.alternate; |
|
|
|
{ |
|
target.actualDuration = source.actualDuration; |
|
target.actualStartTime = source.actualStartTime; |
|
target.selfBaseDuration = source.selfBaseDuration; |
|
target.treeBaseDuration = source.treeBaseDuration; |
|
} |
|
|
|
target._debugSource = source._debugSource; |
|
target._debugOwner = source._debugOwner; |
|
target._debugNeedsRemount = source._debugNeedsRemount; |
|
target._debugHookTypes = source._debugHookTypes; |
|
return target; |
|
} |
|
|
|
function FiberRootNode(containerInfo, tag, hydrate, identifierPrefix, onRecoverableError) { |
|
this.tag = tag; |
|
this.containerInfo = containerInfo; |
|
this.pendingChildren = null; |
|
this.current = null; |
|
this.pingCache = null; |
|
this.finishedWork = null; |
|
this.timeoutHandle = noTimeout; |
|
this.context = null; |
|
this.pendingContext = null; |
|
this.callbackNode = null; |
|
this.callbackPriority = NoLane; |
|
this.eventTimes = createLaneMap(NoLanes); |
|
this.expirationTimes = createLaneMap(NoTimestamp); |
|
this.pendingLanes = NoLanes; |
|
this.suspendedLanes = NoLanes; |
|
this.pingedLanes = NoLanes; |
|
this.expiredLanes = NoLanes; |
|
this.mutableReadLanes = NoLanes; |
|
this.finishedLanes = NoLanes; |
|
this.entangledLanes = NoLanes; |
|
this.entanglements = createLaneMap(NoLanes); |
|
this.identifierPrefix = identifierPrefix; |
|
this.onRecoverableError = onRecoverableError; |
|
|
|
{ |
|
this.mutableSourceEagerHydrationData = null; |
|
} |
|
|
|
{ |
|
this.effectDuration = 0; |
|
this.passiveEffectDuration = 0; |
|
} |
|
|
|
{ |
|
this.memoizedUpdaters = new Set(); |
|
var pendingUpdatersLaneMap = this.pendingUpdatersLaneMap = []; |
|
|
|
for (var _i = 0; _i < TotalLanes; _i++) { |
|
pendingUpdatersLaneMap.push(new Set()); |
|
} |
|
} |
|
|
|
{ |
|
switch (tag) { |
|
case ConcurrentRoot: |
|
this._debugRootType = hydrate ? 'hydrateRoot()' : 'createRoot()'; |
|
break; |
|
|
|
case LegacyRoot: |
|
this._debugRootType = hydrate ? 'hydrate()' : 'render()'; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
function createFiberRoot(containerInfo, tag, hydrate, initialChildren, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, // TODO: We have several of these arguments that are conceptually part of the |
|
// host config, but because they are passed in at runtime, we have to thread |
|
// them through the root constructor. Perhaps we should put them all into a |
|
// single type, like a DynamicHostConfig that is defined by the renderer. |
|
identifierPrefix, onRecoverableError, transitionCallbacks) { |
|
var root = new FiberRootNode(containerInfo, tag, hydrate, identifierPrefix, onRecoverableError); |
|
// stateNode is any. |
|
|
|
|
|
var uninitializedFiber = createHostRootFiber(tag, isStrictMode); |
|
root.current = uninitializedFiber; |
|
uninitializedFiber.stateNode = root; |
|
|
|
{ |
|
var _initialState = { |
|
element: initialChildren, |
|
isDehydrated: hydrate, |
|
cache: null, |
|
// not enabled yet |
|
transitions: null, |
|
pendingSuspenseBoundaries: null |
|
}; |
|
uninitializedFiber.memoizedState = _initialState; |
|
} |
|
|
|
initializeUpdateQueue(uninitializedFiber); |
|
return root; |
|
} |
|
|
|
var ReactVersion = '18.2.0'; |
|
|
|
function createPortal(children, containerInfo, // TODO: figure out the API for cross-renderer implementation. |
|
implementation) { |
|
var key = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; |
|
|
|
{ |
|
checkKeyStringCoercion(key); |
|
} |
|
|
|
return { |
|
// This tag allow us to uniquely identify this as a React Portal |
|
$$typeof: REACT_PORTAL_TYPE, |
|
key: key == null ? null : '' + key, |
|
children: children, |
|
containerInfo: containerInfo, |
|
implementation: implementation |
|
}; |
|
} |
|
|
|
var didWarnAboutNestedUpdates; |
|
var didWarnAboutFindNodeInStrictMode; |
|
|
|
{ |
|
didWarnAboutNestedUpdates = false; |
|
didWarnAboutFindNodeInStrictMode = {}; |
|
} |
|
|
|
function getContextForSubtree(parentComponent) { |
|
if (!parentComponent) { |
|
return emptyContextObject; |
|
} |
|
|
|
var fiber = get(parentComponent); |
|
var parentContext = findCurrentUnmaskedContext(fiber); |
|
|
|
if (fiber.tag === ClassComponent) { |
|
var Component = fiber.type; |
|
|
|
if (isContextProvider(Component)) { |
|
return processChildContext(fiber, Component, parentContext); |
|
} |
|
} |
|
|
|
return parentContext; |
|
} |
|
|
|
function findHostInstanceWithWarning(component, methodName) { |
|
{ |
|
var fiber = get(component); |
|
|
|
if (fiber === undefined) { |
|
if (typeof component.render === 'function') { |
|
throw new Error('Unable to find node on an unmounted component.'); |
|
} else { |
|
var keys = Object.keys(component).join(','); |
|
throw new Error("Argument appears to not be a ReactComponent. Keys: " + keys); |
|
} |
|
} |
|
|
|
var hostFiber = findCurrentHostFiber(fiber); |
|
|
|
if (hostFiber === null) { |
|
return null; |
|
} |
|
|
|
if (hostFiber.mode & StrictLegacyMode) { |
|
var componentName = getComponentNameFromFiber(fiber) || 'Component'; |
|
|
|
if (!didWarnAboutFindNodeInStrictMode[componentName]) { |
|
didWarnAboutFindNodeInStrictMode[componentName] = true; |
|
var previousFiber = current; |
|
|
|
try { |
|
setCurrentFiber(hostFiber); |
|
|
|
if (fiber.mode & StrictLegacyMode) { |
|
error('%s is deprecated in StrictMode. ' + '%s was passed an instance of %s which is inside StrictMode. ' + 'Instead, add a ref directly to the element you want to reference. ' + 'Learn more about using refs safely here: ' + 'https://reactjs.org/link/strict-mode-find-node', methodName, methodName, componentName); |
|
} else { |
|
error('%s is deprecated in StrictMode. ' + '%s was passed an instance of %s which renders StrictMode children. ' + 'Instead, add a ref directly to the element you want to reference. ' + 'Learn more about using refs safely here: ' + 'https://reactjs.org/link/strict-mode-find-node', methodName, methodName, componentName); |
|
} |
|
} finally { |
|
// Ideally this should reset to previous but this shouldn't be called in |
|
// render and there's another warning for that anyway. |
|
if (previousFiber) { |
|
setCurrentFiber(previousFiber); |
|
} else { |
|
resetCurrentFiber(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
return hostFiber.stateNode; |
|
} |
|
} |
|
|
|
function createContainer(containerInfo, tag, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError, transitionCallbacks) { |
|
var hydrate = false; |
|
var initialChildren = null; |
|
return createFiberRoot(containerInfo, tag, hydrate, initialChildren, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError); |
|
} |
|
function createHydrationContainer(initialChildren, // TODO: Remove `callback` when we delete legacy mode. |
|
callback, containerInfo, tag, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError, transitionCallbacks) { |
|
var hydrate = true; |
|
var root = createFiberRoot(containerInfo, tag, hydrate, initialChildren, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError); // TODO: Move this to FiberRoot constructor |
|
|
|
root.context = getContextForSubtree(null); // Schedule the initial render. In a hydration root, this is different from |
|
// a regular update because the initial render must match was was rendered |
|
// on the server. |
|
// NOTE: This update intentionally doesn't have a payload. We're only using |
|
// the update to schedule work on the root fiber (and, for legacy roots, to |
|
// enqueue the callback if one is provided). |
|
|
|
var current = root.current; |
|
var eventTime = requestEventTime(); |
|
var lane = requestUpdateLane(current); |
|
var update = createUpdate(eventTime, lane); |
|
update.callback = callback !== undefined && callback !== null ? callback : null; |
|
enqueueUpdate(current, update, lane); |
|
scheduleInitialHydrationOnRoot(root, lane, eventTime); |
|
return root; |
|
} |
|
function updateContainer(element, container, parentComponent, callback) { |
|
{ |
|
onScheduleRoot(container, element); |
|
} |
|
|
|
var current$1 = container.current; |
|
var eventTime = requestEventTime(); |
|
var lane = requestUpdateLane(current$1); |
|
|
|
{ |
|
markRenderScheduled(lane); |
|
} |
|
|
|
var context = getContextForSubtree(parentComponent); |
|
|
|
if (container.context === null) { |
|
container.context = context; |
|
} else { |
|
container.pendingContext = context; |
|
} |
|
|
|
{ |
|
if (isRendering && current !== null && !didWarnAboutNestedUpdates) { |
|
didWarnAboutNestedUpdates = true; |
|
|
|
error('Render methods should be a pure function of props and state; ' + 'triggering nested component updates from render is not allowed. ' + 'If necessary, trigger nested updates in componentDidUpdate.\n\n' + 'Check the render method of %s.', getComponentNameFromFiber(current) || 'Unknown'); |
|
} |
|
} |
|
|
|
var update = createUpdate(eventTime, lane); // Caution: React DevTools currently depends on this property |
|
// being called "element". |
|
|
|
update.payload = { |
|
element: element |
|
}; |
|
callback = callback === undefined ? null : callback; |
|
|
|
if (callback !== null) { |
|
{ |
|
if (typeof callback !== 'function') { |
|
error('render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback); |
|
} |
|
} |
|
|
|
update.callback = callback; |
|
} |
|
|
|
var root = enqueueUpdate(current$1, update, lane); |
|
|
|
if (root !== null) { |
|
scheduleUpdateOnFiber(root, current$1, lane, eventTime); |
|
entangleTransitions(root, current$1, lane); |
|
} |
|
|
|
return lane; |
|
} |
|
function getPublicRootInstance(container) { |
|
var containerFiber = container.current; |
|
|
|
if (!containerFiber.child) { |
|
return null; |
|
} |
|
|
|
switch (containerFiber.child.tag) { |
|
case HostComponent: |
|
return getPublicInstance(containerFiber.child.stateNode); |
|
|
|
default: |
|
return containerFiber.child.stateNode; |
|
} |
|
} |
|
function attemptSynchronousHydration$1(fiber) { |
|
switch (fiber.tag) { |
|
case HostRoot: |
|
{ |
|
var root = fiber.stateNode; |
|
|
|
if (isRootDehydrated(root)) { |
|
// Flush the first scheduled "update". |
|
var lanes = getHighestPriorityPendingLanes(root); |
|
flushRoot(root, lanes); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case SuspenseComponent: |
|
{ |
|
flushSync(function () { |
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane); |
|
|
|
if (root !== null) { |
|
var eventTime = requestEventTime(); |
|
scheduleUpdateOnFiber(root, fiber, SyncLane, eventTime); |
|
} |
|
}); // If we're still blocked after this, we need to increase |
|
// the priority of any promises resolving within this |
|
// boundary so that they next attempt also has higher pri. |
|
|
|
var retryLane = SyncLane; |
|
markRetryLaneIfNotHydrated(fiber, retryLane); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
function markRetryLaneImpl(fiber, retryLane) { |
|
var suspenseState = fiber.memoizedState; |
|
|
|
if (suspenseState !== null && suspenseState.dehydrated !== null) { |
|
suspenseState.retryLane = higherPriorityLane(suspenseState.retryLane, retryLane); |
|
} |
|
} // Increases the priority of thenables when they resolve within this boundary. |
|
|
|
|
|
function markRetryLaneIfNotHydrated(fiber, retryLane) { |
|
markRetryLaneImpl(fiber, retryLane); |
|
var alternate = fiber.alternate; |
|
|
|
if (alternate) { |
|
markRetryLaneImpl(alternate, retryLane); |
|
} |
|
} |
|
function attemptContinuousHydration$1(fiber) { |
|
if (fiber.tag !== SuspenseComponent) { |
|
// We ignore HostRoots here because we can't increase |
|
// their priority and they should not suspend on I/O, |
|
// since you have to wrap anything that might suspend in |
|
// Suspense. |
|
return; |
|
} |
|
|
|
var lane = SelectiveHydrationLane; |
|
var root = enqueueConcurrentRenderForLane(fiber, lane); |
|
|
|
if (root !== null) { |
|
var eventTime = requestEventTime(); |
|
scheduleUpdateOnFiber(root, fiber, lane, eventTime); |
|
} |
|
|
|
markRetryLaneIfNotHydrated(fiber, lane); |
|
} |
|
function attemptHydrationAtCurrentPriority$1(fiber) { |
|
if (fiber.tag !== SuspenseComponent) { |
|
// We ignore HostRoots here because we can't increase |
|
// their priority other than synchronously flush it. |
|
return; |
|
} |
|
|
|
var lane = requestUpdateLane(fiber); |
|
var root = enqueueConcurrentRenderForLane(fiber, lane); |
|
|
|
if (root !== null) { |
|
var eventTime = requestEventTime(); |
|
scheduleUpdateOnFiber(root, fiber, lane, eventTime); |
|
} |
|
|
|
markRetryLaneIfNotHydrated(fiber, lane); |
|
} |
|
function findHostInstanceWithNoPortals(fiber) { |
|
var hostFiber = findCurrentHostFiberWithNoPortals(fiber); |
|
|
|
if (hostFiber === null) { |
|
return null; |
|
} |
|
|
|
return hostFiber.stateNode; |
|
} |
|
|
|
var shouldErrorImpl = function (fiber) { |
|
return null; |
|
}; |
|
|
|
function shouldError(fiber) { |
|
return shouldErrorImpl(fiber); |
|
} |
|
|
|
var shouldSuspendImpl = function (fiber) { |
|
return false; |
|
}; |
|
|
|
function shouldSuspend(fiber) { |
|
return shouldSuspendImpl(fiber); |
|
} |
|
var overrideHookState = null; |
|
var overrideHookStateDeletePath = null; |
|
var overrideHookStateRenamePath = null; |
|
var overrideProps = null; |
|
var overridePropsDeletePath = null; |
|
var overridePropsRenamePath = null; |
|
var scheduleUpdate = null; |
|
var setErrorHandler = null; |
|
var setSuspenseHandler = null; |
|
|
|
{ |
|
var copyWithDeleteImpl = function (obj, path, index) { |
|
var key = path[index]; |
|
var updated = isArray(obj) ? obj.slice() : assign({}, obj); |
|
|
|
if (index + 1 === path.length) { |
|
if (isArray(updated)) { |
|
updated.splice(key, 1); |
|
} else { |
|
delete updated[key]; |
|
} |
|
|
|
return updated; |
|
} // $FlowFixMe number or string is fine here |
|
|
|
|
|
updated[key] = copyWithDeleteImpl(obj[key], path, index + 1); |
|
return updated; |
|
}; |
|
|
|
var copyWithDelete = function (obj, path) { |
|
return copyWithDeleteImpl(obj, path, 0); |
|
}; |
|
|
|
var copyWithRenameImpl = function (obj, oldPath, newPath, index) { |
|
var oldKey = oldPath[index]; |
|
var updated = isArray(obj) ? obj.slice() : assign({}, obj); |
|
|
|
if (index + 1 === oldPath.length) { |
|
var newKey = newPath[index]; // $FlowFixMe number or string is fine here |
|
|
|
updated[newKey] = updated[oldKey]; |
|
|
|
if (isArray(updated)) { |
|
updated.splice(oldKey, 1); |
|
} else { |
|
delete updated[oldKey]; |
|
} |
|
} else { |
|
// $FlowFixMe number or string is fine here |
|
updated[oldKey] = copyWithRenameImpl( // $FlowFixMe number or string is fine here |
|
obj[oldKey], oldPath, newPath, index + 1); |
|
} |
|
|
|
return updated; |
|
}; |
|
|
|
var copyWithRename = function (obj, oldPath, newPath) { |
|
if (oldPath.length !== newPath.length) { |
|
warn('copyWithRename() expects paths of the same length'); |
|
|
|
return; |
|
} else { |
|
for (var i = 0; i < newPath.length - 1; i++) { |
|
if (oldPath[i] !== newPath[i]) { |
|
warn('copyWithRename() expects paths to be the same except for the deepest key'); |
|
|
|
return; |
|
} |
|
} |
|
} |
|
|
|
return copyWithRenameImpl(obj, oldPath, newPath, 0); |
|
}; |
|
|
|
var copyWithSetImpl = function (obj, path, index, value) { |
|
if (index >= path.length) { |
|
return value; |
|
} |
|
|
|
var key = path[index]; |
|
var updated = isArray(obj) ? obj.slice() : assign({}, obj); // $FlowFixMe number or string is fine here |
|
|
|
updated[key] = copyWithSetImpl(obj[key], path, index + 1, value); |
|
return updated; |
|
}; |
|
|
|
var copyWithSet = function (obj, path, value) { |
|
return copyWithSetImpl(obj, path, 0, value); |
|
}; |
|
|
|
var findHook = function (fiber, id) { |
|
// For now, the "id" of stateful hooks is just the stateful hook index. |
|
// This may change in the future with e.g. nested hooks. |
|
var currentHook = fiber.memoizedState; |
|
|
|
while (currentHook !== null && id > 0) { |
|
currentHook = currentHook.next; |
|
id--; |
|
} |
|
|
|
return currentHook; |
|
}; // Support DevTools editable values for useState and useReducer. |
|
|
|
|
|
overrideHookState = function (fiber, id, path, value) { |
|
var hook = findHook(fiber, id); |
|
|
|
if (hook !== null) { |
|
var newState = copyWithSet(hook.memoizedState, path, value); |
|
hook.memoizedState = newState; |
|
hook.baseState = newState; // We aren't actually adding an update to the queue, |
|
// because there is no update we can add for useReducer hooks that won't trigger an error. |
|
// (There's no appropriate action type for DevTools overrides.) |
|
// As a result though, React will see the scheduled update as a noop and bailout. |
|
// Shallow cloning props works as a workaround for now to bypass the bailout check. |
|
|
|
fiber.memoizedProps = assign({}, fiber.memoizedProps); |
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane); |
|
|
|
if (root !== null) { |
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); |
|
} |
|
} |
|
}; |
|
|
|
overrideHookStateDeletePath = function (fiber, id, path) { |
|
var hook = findHook(fiber, id); |
|
|
|
if (hook !== null) { |
|
var newState = copyWithDelete(hook.memoizedState, path); |
|
hook.memoizedState = newState; |
|
hook.baseState = newState; // We aren't actually adding an update to the queue, |
|
// because there is no update we can add for useReducer hooks that won't trigger an error. |
|
// (There's no appropriate action type for DevTools overrides.) |
|
// As a result though, React will see the scheduled update as a noop and bailout. |
|
// Shallow cloning props works as a workaround for now to bypass the bailout check. |
|
|
|
fiber.memoizedProps = assign({}, fiber.memoizedProps); |
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane); |
|
|
|
if (root !== null) { |
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); |
|
} |
|
} |
|
}; |
|
|
|
overrideHookStateRenamePath = function (fiber, id, oldPath, newPath) { |
|
var hook = findHook(fiber, id); |
|
|
|
if (hook !== null) { |
|
var newState = copyWithRename(hook.memoizedState, oldPath, newPath); |
|
hook.memoizedState = newState; |
|
hook.baseState = newState; // We aren't actually adding an update to the queue, |
|
// because there is no update we can add for useReducer hooks that won't trigger an error. |
|
// (There's no appropriate action type for DevTools overrides.) |
|
// As a result though, React will see the scheduled update as a noop and bailout. |
|
// Shallow cloning props works as a workaround for now to bypass the bailout check. |
|
|
|
fiber.memoizedProps = assign({}, fiber.memoizedProps); |
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane); |
|
|
|
if (root !== null) { |
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); |
|
} |
|
} |
|
}; // Support DevTools props for function components, forwardRef, memo, host components, etc. |
|
|
|
|
|
overrideProps = function (fiber, path, value) { |
|
fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value); |
|
|
|
if (fiber.alternate) { |
|
fiber.alternate.pendingProps = fiber.pendingProps; |
|
} |
|
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane); |
|
|
|
if (root !== null) { |
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); |
|
} |
|
}; |
|
|
|
overridePropsDeletePath = function (fiber, path) { |
|
fiber.pendingProps = copyWithDelete(fiber.memoizedProps, path); |
|
|
|
if (fiber.alternate) { |
|
fiber.alternate.pendingProps = fiber.pendingProps; |
|
} |
|
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane); |
|
|
|
if (root !== null) { |
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); |
|
} |
|
}; |
|
|
|
overridePropsRenamePath = function (fiber, oldPath, newPath) { |
|
fiber.pendingProps = copyWithRename(fiber.memoizedProps, oldPath, newPath); |
|
|
|
if (fiber.alternate) { |
|
fiber.alternate.pendingProps = fiber.pendingProps; |
|
} |
|
|
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane); |
|
|
|
if (root !== null) { |
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); |
|
} |
|
}; |
|
|
|
scheduleUpdate = function (fiber) { |
|
var root = enqueueConcurrentRenderForLane(fiber, SyncLane); |
|
|
|
if (root !== null) { |
|
scheduleUpdateOnFiber(root, fiber, SyncLane, NoTimestamp); |
|
} |
|
}; |
|
|
|
setErrorHandler = function (newShouldErrorImpl) { |
|
shouldErrorImpl = newShouldErrorImpl; |
|
}; |
|
|
|
setSuspenseHandler = function (newShouldSuspendImpl) { |
|
shouldSuspendImpl = newShouldSuspendImpl; |
|
}; |
|
} |
|
|
|
function findHostInstanceByFiber(fiber) { |
|
var hostFiber = findCurrentHostFiber(fiber); |
|
|
|
if (hostFiber === null) { |
|
return null; |
|
} |
|
|
|
return hostFiber.stateNode; |
|
} |
|
|
|
function emptyFindFiberByHostInstance(instance) { |
|
return null; |
|
} |
|
|
|
function getCurrentFiberForDevTools() { |
|
return current; |
|
} |
|
|
|
function injectIntoDevTools(devToolsConfig) { |
|
var findFiberByHostInstance = devToolsConfig.findFiberByHostInstance; |
|
var ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher; |
|
return injectInternals({ |
|
bundleType: devToolsConfig.bundleType, |
|
version: devToolsConfig.version, |
|
rendererPackageName: devToolsConfig.rendererPackageName, |
|
rendererConfig: devToolsConfig.rendererConfig, |
|
overrideHookState: overrideHookState, |
|
overrideHookStateDeletePath: overrideHookStateDeletePath, |
|
overrideHookStateRenamePath: overrideHookStateRenamePath, |
|
overrideProps: overrideProps, |
|
overridePropsDeletePath: overridePropsDeletePath, |
|
overridePropsRenamePath: overridePropsRenamePath, |
|
setErrorHandler: setErrorHandler, |
|
setSuspenseHandler: setSuspenseHandler, |
|
scheduleUpdate: scheduleUpdate, |
|
currentDispatcherRef: ReactCurrentDispatcher, |
|
findHostInstanceByFiber: findHostInstanceByFiber, |
|
findFiberByHostInstance: findFiberByHostInstance || emptyFindFiberByHostInstance, |
|
// React Refresh |
|
findHostInstancesForRefresh: findHostInstancesForRefresh , |
|
scheduleRefresh: scheduleRefresh , |
|
scheduleRoot: scheduleRoot , |
|
setRefreshHandler: setRefreshHandler , |
|
// Enables DevTools to append owner stacks to error messages in DEV mode. |
|
getCurrentFiber: getCurrentFiberForDevTools , |
|
// Enables DevTools to detect reconciler version rather than renderer version |
|
// which may not match for third party renderers. |
|
reconcilerVersion: ReactVersion |
|
}); |
|
} |
|
|
|
/* global reportError */ |
|
|
|
var defaultOnRecoverableError = typeof reportError === 'function' ? // In modern browsers, reportError will dispatch an error event, |
|
// emulating an uncaught JavaScript error. |
|
reportError : function (error) { |
|
// In older browsers and test environments, fallback to console.error. |
|
// eslint-disable-next-line react-internal/no-production-logging |
|
console['error'](error); |
|
}; |
|
|
|
function ReactDOMRoot(internalRoot) { |
|
this._internalRoot = internalRoot; |
|
} |
|
|
|
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = function (children) { |
|
var root = this._internalRoot; |
|
|
|
if (root === null) { |
|
throw new Error('Cannot update an unmounted root.'); |
|
} |
|
|
|
{ |
|
if (typeof arguments[1] === 'function') { |
|
error('render(...): does not support the second callback argument. ' + 'To execute a side effect after rendering, declare it in a component body with useEffect().'); |
|
} else if (isValidContainer(arguments[1])) { |
|
error('You passed a container to the second argument of root.render(...). ' + "You don't need to pass it again since you already passed it to create the root."); |
|
} else if (typeof arguments[1] !== 'undefined') { |
|
error('You passed a second argument to root.render(...) but it only accepts ' + 'one argument.'); |
|
} |
|
|
|
var container = root.containerInfo; |
|
|
|
if (container.nodeType !== COMMENT_NODE) { |
|
var hostInstance = findHostInstanceWithNoPortals(root.current); |
|
|
|
if (hostInstance) { |
|
if (hostInstance.parentNode !== container) { |
|
error('render(...): It looks like the React-rendered content of the ' + 'root container was removed without using React. This is not ' + 'supported and will cause errors. Instead, call ' + "root.unmount() to empty a root's container."); |
|
} |
|
} |
|
} |
|
} |
|
|
|
updateContainer(children, root, null, null); |
|
}; |
|
|
|
ReactDOMHydrationRoot.prototype.unmount = ReactDOMRoot.prototype.unmount = function () { |
|
{ |
|
if (typeof arguments[0] === 'function') { |
|
error('unmount(...): does not support a callback argument. ' + 'To execute a side effect after rendering, declare it in a component body with useEffect().'); |
|
} |
|
} |
|
|
|
var root = this._internalRoot; |
|
|
|
if (root !== null) { |
|
this._internalRoot = null; |
|
var container = root.containerInfo; |
|
|
|
{ |
|
if (isAlreadyRendering()) { |
|
error('Attempted to synchronously unmount a root while React was already ' + 'rendering. React cannot finish unmounting the root until the ' + 'current render has completed, which may lead to a race condition.'); |
|
} |
|
} |
|
|
|
flushSync(function () { |
|
updateContainer(null, root, null, null); |
|
}); |
|
unmarkContainerAsRoot(container); |
|
} |
|
}; |
|
|
|
function createRoot(container, options) { |
|
if (!isValidContainer(container)) { |
|
throw new Error('createRoot(...): Target container is not a DOM element.'); |
|
} |
|
|
|
warnIfReactDOMContainerInDEV(container); |
|
var isStrictMode = false; |
|
var concurrentUpdatesByDefaultOverride = false; |
|
var identifierPrefix = ''; |
|
var onRecoverableError = defaultOnRecoverableError; |
|
var transitionCallbacks = null; |
|
|
|
if (options !== null && options !== undefined) { |
|
{ |
|
if (options.hydrate) { |
|
warn('hydrate through createRoot is deprecated. Use ReactDOMClient.hydrateRoot(container, <App />) instead.'); |
|
} else { |
|
if (typeof options === 'object' && options !== null && options.$$typeof === REACT_ELEMENT_TYPE) { |
|
error('You passed a JSX element to createRoot. You probably meant to ' + 'call root.render instead. ' + 'Example usage:\n\n' + ' let root = createRoot(domContainer);\n' + ' root.render(<App />);'); |
|
} |
|
} |
|
} |
|
|
|
if (options.unstable_strictMode === true) { |
|
isStrictMode = true; |
|
} |
|
|
|
if (options.identifierPrefix !== undefined) { |
|
identifierPrefix = options.identifierPrefix; |
|
} |
|
|
|
if (options.onRecoverableError !== undefined) { |
|
onRecoverableError = options.onRecoverableError; |
|
} |
|
|
|
if (options.transitionCallbacks !== undefined) { |
|
transitionCallbacks = options.transitionCallbacks; |
|
} |
|
} |
|
|
|
var root = createContainer(container, ConcurrentRoot, null, isStrictMode, concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError); |
|
markContainerAsRoot(root.current, container); |
|
var rootContainerElement = container.nodeType === COMMENT_NODE ? container.parentNode : container; |
|
listenToAllSupportedEvents(rootContainerElement); |
|
return new ReactDOMRoot(root); |
|
} |
|
|
|
function ReactDOMHydrationRoot(internalRoot) { |
|
this._internalRoot = internalRoot; |
|
} |
|
|
|
function scheduleHydration(target) { |
|
if (target) { |
|
queueExplicitHydrationTarget(target); |
|
} |
|
} |
|
|
|
ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = scheduleHydration; |
|
function hydrateRoot(container, initialChildren, options) { |
|
if (!isValidContainer(container)) { |
|
throw new Error('hydrateRoot(...): Target container is not a DOM element.'); |
|
} |
|
|
|
warnIfReactDOMContainerInDEV(container); |
|
|
|
{ |
|
if (initialChildren === undefined) { |
|
error('Must provide initial children as second argument to hydrateRoot. ' + 'Example usage: hydrateRoot(domContainer, <App />)'); |
|
} |
|
} // For now we reuse the whole bag of options since they contain |
|
// the hydration callbacks. |
|
|
|
|
|
var hydrationCallbacks = options != null ? options : null; // TODO: Delete this option |
|
|
|
var mutableSources = options != null && options.hydratedSources || null; |
|
var isStrictMode = false; |
|
var concurrentUpdatesByDefaultOverride = false; |
|
var identifierPrefix = ''; |
|
var onRecoverableError = defaultOnRecoverableError; |
|
|
|
if (options !== null && options !== undefined) { |
|
if (options.unstable_strictMode === true) { |
|
isStrictMode = true; |
|
} |
|
|
|
if (options.identifierPrefix !== undefined) { |
|
identifierPrefix = options.identifierPrefix; |
|
} |
|
|
|
if (options.onRecoverableError !== undefined) { |
|
onRecoverableError = options.onRecoverableError; |
|
} |
|
} |
|
|
|
var root = createHydrationContainer(initialChildren, null, container, ConcurrentRoot, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError); |
|
markContainerAsRoot(root.current, container); // This can't be a comment node since hydration doesn't work on comment nodes anyway. |
|
|
|
listenToAllSupportedEvents(container); |
|
|
|
if (mutableSources) { |
|
for (var i = 0; i < mutableSources.length; i++) { |
|
var mutableSource = mutableSources[i]; |
|
registerMutableSourceForHydration(root, mutableSource); |
|
} |
|
} |
|
|
|
return new ReactDOMHydrationRoot(root); |
|
} |
|
function isValidContainer(node) { |
|
return !!(node && (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE || !disableCommentsAsDOMContainers )); |
|
} // TODO: Remove this function which also includes comment nodes. |
|
// We only use it in places that are currently more relaxed. |
|
|
|
function isValidContainerLegacy(node) { |
|
return !!(node && (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE || node.nodeType === COMMENT_NODE && node.nodeValue === ' react-mount-point-unstable ')); |
|
} |
|
|
|
function warnIfReactDOMContainerInDEV(container) { |
|
{ |
|
if (container.nodeType === ELEMENT_NODE && container.tagName && container.tagName.toUpperCase() === 'BODY') { |
|
error('createRoot(): Creating roots directly with document.body is ' + 'discouraged, since its children are often manipulated by third-party ' + 'scripts and browser extensions. This may lead to subtle ' + 'reconciliation issues. Try using a container element created ' + 'for your app.'); |
|
} |
|
|
|
if (isContainerMarkedAsRoot(container)) { |
|
if (container._reactRootContainer) { |
|
error('You are calling ReactDOMClient.createRoot() on a container that was previously ' + 'passed to ReactDOM.render(). This is not supported.'); |
|
} else { |
|
error('You are calling ReactDOMClient.createRoot() on a container that ' + 'has already been passed to createRoot() before. Instead, call ' + 'root.render() on the existing root instead if you want to update it.'); |
|
} |
|
} |
|
} |
|
} |
|
|
|
var ReactCurrentOwner$3 = ReactSharedInternals.ReactCurrentOwner; |
|
var topLevelUpdateWarnings; |
|
|
|
{ |
|
topLevelUpdateWarnings = function (container) { |
|
if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) { |
|
var hostInstance = findHostInstanceWithNoPortals(container._reactRootContainer.current); |
|
|
|
if (hostInstance) { |
|
if (hostInstance.parentNode !== container) { |
|
error('render(...): It looks like the React-rendered content of this ' + 'container was removed without using React. This is not ' + 'supported and will cause errors. Instead, call ' + 'ReactDOM.unmountComponentAtNode to empty a container.'); |
|
} |
|
} |
|
} |
|
|
|
var isRootRenderedBySomeReact = !!container._reactRootContainer; |
|
var rootEl = getReactRootElementInContainer(container); |
|
var hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl)); |
|
|
|
if (hasNonRootReactChild && !isRootRenderedBySomeReact) { |
|
error('render(...): Replacing React-rendered children with a new root ' + 'component. If you intended to update the children of this node, ' + 'you should instead have the existing children update their state ' + 'and render the new components instead of calling ReactDOM.render.'); |
|
} |
|
|
|
if (container.nodeType === ELEMENT_NODE && container.tagName && container.tagName.toUpperCase() === 'BODY') { |
|
error('render(): Rendering components directly into document.body is ' + 'discouraged, since its children are often manipulated by third-party ' + 'scripts and browser extensions. This may lead to subtle ' + 'reconciliation issues. Try rendering into a container element created ' + 'for your app.'); |
|
} |
|
}; |
|
} |
|
|
|
function getReactRootElementInContainer(container) { |
|
if (!container) { |
|
return null; |
|
} |
|
|
|
if (container.nodeType === DOCUMENT_NODE) { |
|
return container.documentElement; |
|
} else { |
|
return container.firstChild; |
|
} |
|
} |
|
|
|
function noopOnRecoverableError() {// This isn't reachable because onRecoverableError isn't called in the |
|
// legacy API. |
|
} |
|
|
|
function legacyCreateRootFromDOMContainer(container, initialChildren, parentComponent, callback, isHydrationContainer) { |
|
if (isHydrationContainer) { |
|
if (typeof callback === 'function') { |
|
var originalCallback = callback; |
|
|
|
callback = function () { |
|
var instance = getPublicRootInstance(root); |
|
originalCallback.call(instance); |
|
}; |
|
} |
|
|
|
var root = createHydrationContainer(initialChildren, callback, container, LegacyRoot, null, // hydrationCallbacks |
|
false, // isStrictMode |
|
false, // concurrentUpdatesByDefaultOverride, |
|
'', // identifierPrefix |
|
noopOnRecoverableError); |
|
container._reactRootContainer = root; |
|
markContainerAsRoot(root.current, container); |
|
var rootContainerElement = container.nodeType === COMMENT_NODE ? container.parentNode : container; |
|
listenToAllSupportedEvents(rootContainerElement); |
|
flushSync(); |
|
return root; |
|
} else { |
|
// First clear any existing content. |
|
var rootSibling; |
|
|
|
while (rootSibling = container.lastChild) { |
|
container.removeChild(rootSibling); |
|
} |
|
|
|
if (typeof callback === 'function') { |
|
var _originalCallback = callback; |
|
|
|
callback = function () { |
|
var instance = getPublicRootInstance(_root); |
|
|
|
_originalCallback.call(instance); |
|
}; |
|
} |
|
|
|
var _root = createContainer(container, LegacyRoot, null, // hydrationCallbacks |
|
false, // isStrictMode |
|
false, // concurrentUpdatesByDefaultOverride, |
|
'', // identifierPrefix |
|
noopOnRecoverableError); |
|
|
|
container._reactRootContainer = _root; |
|
markContainerAsRoot(_root.current, container); |
|
|
|
var _rootContainerElement = container.nodeType === COMMENT_NODE ? container.parentNode : container; |
|
|
|
listenToAllSupportedEvents(_rootContainerElement); // Initial mount should not be batched. |
|
|
|
flushSync(function () { |
|
updateContainer(initialChildren, _root, parentComponent, callback); |
|
}); |
|
return _root; |
|
} |
|
} |
|
|
|
function warnOnInvalidCallback$1(callback, callerName) { |
|
{ |
|
if (callback !== null && typeof callback !== 'function') { |
|
error('%s(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callerName, callback); |
|
} |
|
} |
|
} |
|
|
|
function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) { |
|
{ |
|
topLevelUpdateWarnings(container); |
|
warnOnInvalidCallback$1(callback === undefined ? null : callback, 'render'); |
|
} |
|
|
|
var maybeRoot = container._reactRootContainer; |
|
var root; |
|
|
|
if (!maybeRoot) { |
|
// Initial mount |
|
root = legacyCreateRootFromDOMContainer(container, children, parentComponent, callback, forceHydrate); |
|
} else { |
|
root = maybeRoot; |
|
|
|
if (typeof callback === 'function') { |
|
var originalCallback = callback; |
|
|
|
callback = function () { |
|
var instance = getPublicRootInstance(root); |
|
originalCallback.call(instance); |
|
}; |
|
} // Update |
|
|
|
|
|
updateContainer(children, root, parentComponent, callback); |
|
} |
|
|
|
return getPublicRootInstance(root); |
|
} |
|
|
|
function findDOMNode(componentOrElement) { |
|
{ |
|
var owner = ReactCurrentOwner$3.current; |
|
|
|
if (owner !== null && owner.stateNode !== null) { |
|
var warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender; |
|
|
|
if (!warnedAboutRefsInRender) { |
|
error('%s is accessing findDOMNode inside its render(). ' + 'render() should be a pure function of props and state. It should ' + 'never access something that requires stale data from the previous ' + 'render, such as refs. Move this logic to componentDidMount and ' + 'componentDidUpdate instead.', getComponentNameFromType(owner.type) || 'A component'); |
|
} |
|
|
|
owner.stateNode._warnedAboutRefsInRender = true; |
|
} |
|
} |
|
|
|
if (componentOrElement == null) { |
|
return null; |
|
} |
|
|
|
if (componentOrElement.nodeType === ELEMENT_NODE) { |
|
return componentOrElement; |
|
} |
|
|
|
{ |
|
return findHostInstanceWithWarning(componentOrElement, 'findDOMNode'); |
|
} |
|
} |
|
function hydrate(element, container, callback) { |
|
{ |
|
error('ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot ' + 'instead. Until you switch to the new API, your app will behave as ' + "if it's running React 17. Learn " + 'more: https://reactjs.org/link/switch-to-createroot'); |
|
} |
|
|
|
if (!isValidContainerLegacy(container)) { |
|
throw new Error('Target container is not a DOM element.'); |
|
} |
|
|
|
{ |
|
var isModernRoot = isContainerMarkedAsRoot(container) && container._reactRootContainer === undefined; |
|
|
|
if (isModernRoot) { |
|
error('You are calling ReactDOM.hydrate() on a container that was previously ' + 'passed to ReactDOMClient.createRoot(). This is not supported. ' + 'Did you mean to call hydrateRoot(container, element)?'); |
|
} |
|
} // TODO: throw or warn if we couldn't hydrate? |
|
|
|
|
|
return legacyRenderSubtreeIntoContainer(null, element, container, true, callback); |
|
} |
|
function render(element, container, callback) { |
|
{ |
|
error('ReactDOM.render is no longer supported in React 18. Use createRoot ' + 'instead. Until you switch to the new API, your app will behave as ' + "if it's running React 17. Learn " + 'more: https://reactjs.org/link/switch-to-createroot'); |
|
} |
|
|
|
if (!isValidContainerLegacy(container)) { |
|
throw new Error('Target container is not a DOM element.'); |
|
} |
|
|
|
{ |
|
var isModernRoot = isContainerMarkedAsRoot(container) && container._reactRootContainer === undefined; |
|
|
|
if (isModernRoot) { |
|
error('You are calling ReactDOM.render() on a container that was previously ' + 'passed to ReactDOMClient.createRoot(). This is not supported. ' + 'Did you mean to call root.render(element)?'); |
|
} |
|
} |
|
|
|
return legacyRenderSubtreeIntoContainer(null, element, container, false, callback); |
|
} |
|
function unstable_renderSubtreeIntoContainer(parentComponent, element, containerNode, callback) { |
|
{ |
|
error('ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported ' + 'in React 18. Consider using a portal instead. Until you switch to ' + "the createRoot API, your app will behave as if it's running React " + '17. Learn more: https://reactjs.org/link/switch-to-createroot'); |
|
} |
|
|
|
if (!isValidContainerLegacy(containerNode)) { |
|
throw new Error('Target container is not a DOM element.'); |
|
} |
|
|
|
if (parentComponent == null || !has(parentComponent)) { |
|
throw new Error('parentComponent must be a valid React Component'); |
|
} |
|
|
|
return legacyRenderSubtreeIntoContainer(parentComponent, element, containerNode, false, callback); |
|
} |
|
function unmountComponentAtNode(container) { |
|
if (!isValidContainerLegacy(container)) { |
|
throw new Error('unmountComponentAtNode(...): Target container is not a DOM element.'); |
|
} |
|
|
|
{ |
|
var isModernRoot = isContainerMarkedAsRoot(container) && container._reactRootContainer === undefined; |
|
|
|
if (isModernRoot) { |
|
error('You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' + 'passed to ReactDOMClient.createRoot(). This is not supported. Did you mean to call root.unmount()?'); |
|
} |
|
} |
|
|
|
if (container._reactRootContainer) { |
|
{ |
|
var rootEl = getReactRootElementInContainer(container); |
|
var renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl); |
|
|
|
if (renderedByDifferentReact) { |
|
error("unmountComponentAtNode(): The node you're attempting to unmount " + 'was rendered by another copy of React.'); |
|
} |
|
} // Unmount should not be batched. |
|
|
|
|
|
flushSync(function () { |
|
legacyRenderSubtreeIntoContainer(null, null, container, false, function () { |
|
// $FlowFixMe This should probably use `delete container._reactRootContainer` |
|
container._reactRootContainer = null; |
|
unmarkContainerAsRoot(container); |
|
}); |
|
}); // If you call unmountComponentAtNode twice in quick succession, you'll |
|
// get `true` twice. That's probably fine? |
|
|
|
return true; |
|
} else { |
|
{ |
|
var _rootEl = getReactRootElementInContainer(container); |
|
|
|
var hasNonRootReactChild = !!(_rootEl && getInstanceFromNode(_rootEl)); // Check if the container itself is a React root node. |
|
|
|
var isContainerReactRoot = container.nodeType === ELEMENT_NODE && isValidContainerLegacy(container.parentNode) && !!container.parentNode._reactRootContainer; |
|
|
|
if (hasNonRootReactChild) { |
|
error("unmountComponentAtNode(): The node you're attempting to unmount " + 'was rendered by React and is not a top-level container. %s', isContainerReactRoot ? 'You may have accidentally passed in a React root node instead ' + 'of its container.' : 'Instead, have the parent component update its state and ' + 'rerender in order to remove this component.'); |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
} |
|
|
|
setAttemptSynchronousHydration(attemptSynchronousHydration$1); |
|
setAttemptContinuousHydration(attemptContinuousHydration$1); |
|
setAttemptHydrationAtCurrentPriority(attemptHydrationAtCurrentPriority$1); |
|
setGetCurrentUpdatePriority(getCurrentUpdatePriority); |
|
setAttemptHydrationAtPriority(runWithPriority); |
|
|
|
{ |
|
if (typeof Map !== 'function' || // $FlowIssue Flow incorrectly thinks Map has no prototype |
|
Map.prototype == null || typeof Map.prototype.forEach !== 'function' || typeof Set !== 'function' || // $FlowIssue Flow incorrectly thinks Set has no prototype |
|
Set.prototype == null || typeof Set.prototype.clear !== 'function' || typeof Set.prototype.forEach !== 'function') { |
|
error('React depends on Map and Set built-in types. Make sure that you load a ' + 'polyfill in older browsers. https://reactjs.org/link/react-polyfills'); |
|
} |
|
} |
|
|
|
setRestoreImplementation(restoreControlledState$3); |
|
setBatchingImplementation(batchedUpdates$1, discreteUpdates, flushSync); |
|
|
|
function createPortal$1(children, container) { |
|
var key = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; |
|
|
|
if (!isValidContainer(container)) { |
|
throw new Error('Target container is not a DOM element.'); |
|
} // TODO: pass ReactDOM portal implementation as third argument |
|
// $FlowFixMe The Flow type is opaque but there's no way to actually create it. |
|
|
|
|
|
return createPortal(children, container, null, key); |
|
} |
|
|
|
function renderSubtreeIntoContainer(parentComponent, element, containerNode, callback) { |
|
return unstable_renderSubtreeIntoContainer(parentComponent, element, containerNode, callback); |
|
} |
|
|
|
var Internals = { |
|
usingClientEntryPoint: false, |
|
// Keep in sync with ReactTestUtils.js. |
|
// This is an array for better minification. |
|
Events: [getInstanceFromNode, getNodeFromInstance, getFiberCurrentPropsFromNode, enqueueStateRestore, restoreStateIfNeeded, batchedUpdates$1] |
|
}; |
|
|
|
function createRoot$1(container, options) { |
|
{ |
|
if (!Internals.usingClientEntryPoint && !true) { |
|
error('You are importing createRoot from "react-dom" which is not supported. ' + 'You should instead import it from "react-dom/client".'); |
|
} |
|
} |
|
|
|
return createRoot(container, options); |
|
} |
|
|
|
function hydrateRoot$1(container, initialChildren, options) { |
|
{ |
|
if (!Internals.usingClientEntryPoint && !true) { |
|
error('You are importing hydrateRoot from "react-dom" which is not supported. ' + 'You should instead import it from "react-dom/client".'); |
|
} |
|
} |
|
|
|
return hydrateRoot(container, initialChildren, options); |
|
} // Overload the definition to the two valid signatures. |
|
// Warning, this opts-out of checking the function body. |
|
|
|
|
|
// eslint-disable-next-line no-redeclare |
|
function flushSync$1(fn) { |
|
{ |
|
if (isAlreadyRendering()) { |
|
error('flushSync was called from inside a lifecycle method. React cannot ' + 'flush when React is already rendering. Consider moving this call to ' + 'a scheduler task or micro task.'); |
|
} |
|
} |
|
|
|
return flushSync(fn); |
|
} |
|
var foundDevTools = injectIntoDevTools({ |
|
findFiberByHostInstance: getClosestInstanceFromNode, |
|
bundleType: 1 , |
|
version: ReactVersion, |
|
rendererPackageName: 'react-dom' |
|
}); |
|
|
|
{ |
|
if (!foundDevTools && canUseDOM && window.top === window.self) { |
|
// If we're in Chrome or Firefox, provide a download link if not installed. |
|
if (navigator.userAgent.indexOf('Chrome') > -1 && navigator.userAgent.indexOf('Edge') === -1 || navigator.userAgent.indexOf('Firefox') > -1) { |
|
var protocol = window.location.protocol; // Don't warn in exotic cases like chrome-extension://. |
|
|
|
if (/^(https?|file):$/.test(protocol)) { |
|
// eslint-disable-next-line react-internal/no-production-logging |
|
console.info('%cDownload the React DevTools ' + 'for a better development experience: ' + 'https://reactjs.org/link/react-devtools' + (protocol === 'file:' ? '\nYou might need to use a local HTTP server (instead of file://): ' + 'https://reactjs.org/link/react-devtools-faq' : ''), 'font-weight:bold'); |
|
} |
|
} |
|
} |
|
} |
|
|
|
exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = Internals; |
|
exports.createPortal = createPortal$1; |
|
exports.createRoot = createRoot$1; |
|
exports.findDOMNode = findDOMNode; |
|
exports.flushSync = flushSync$1; |
|
exports.hydrate = hydrate; |
|
exports.hydrateRoot = hydrateRoot$1; |
|
exports.render = render; |
|
exports.unmountComponentAtNode = unmountComponentAtNode; |
|
exports.unstable_batchedUpdates = batchedUpdates$1; |
|
exports.unstable_renderSubtreeIntoContainer = renderSubtreeIntoContainer; |
|
exports.version = ReactVersion; |
|
|
|
})));
|