mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
Refine the heuristics around beforeblur/afterblur (#18668)
* Refine the heuristics around beforeblur/afterblur
This commit is contained in:
9
packages/react-art/src/ReactARTHostConfig.js
vendored
9
packages/react-art/src/ReactARTHostConfig.js
vendored
@@ -306,6 +306,7 @@ export function getPublicInstance(instance) {
|
||||
|
||||
export function prepareForCommit() {
|
||||
// Noop
|
||||
return null;
|
||||
}
|
||||
|
||||
export function prepareUpdate(domElement, type, oldProps, newProps) {
|
||||
@@ -507,3 +508,11 @@ export function unmountEventListener(listener: any) {
|
||||
export function validateEventListenerTarget(target: any, listener: any) {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
export function beforeActiveInstanceBlur() {
|
||||
// noop
|
||||
}
|
||||
|
||||
export function afterActiveInstanceBlur() {
|
||||
// noop
|
||||
}
|
||||
|
||||
@@ -91,10 +91,6 @@ import {
|
||||
import {getListenerMapForElement} from '../events/DOMEventListenerMap';
|
||||
import {TOP_BEFORE_BLUR, TOP_AFTER_BLUR} from '../events/DOMTopLevelEventTypes';
|
||||
|
||||
// TODO: This is an exposed internal, we should move this around
|
||||
// so this isn't the case.
|
||||
import {isFiberInsideHiddenOrRemovedTree} from 'react-reconciler/src/ReactFiberTreeReflection';
|
||||
|
||||
export type ReactListenerEvent = ReactDOMListenerEvent;
|
||||
export type ReactListenerMap = ReactDOMListenerMap;
|
||||
export type ReactListener = ReactDOMListener;
|
||||
@@ -159,7 +155,6 @@ export opaque type OpaqueIDType =
|
||||
};
|
||||
|
||||
type SelectionInformation = {|
|
||||
activeElementDetached: null | HTMLElement,
|
||||
focusedElem: null | HTMLElement,
|
||||
selectionRange: mixed,
|
||||
|};
|
||||
@@ -247,32 +242,40 @@ export function getPublicInstance(instance: Instance): * {
|
||||
return instance;
|
||||
}
|
||||
|
||||
export function prepareForCommit(containerInfo: Container): void {
|
||||
export function prepareForCommit(containerInfo: Container): Object | null {
|
||||
eventsEnabled = ReactBrowserEventEmitterIsEnabled();
|
||||
selectionInformation = getSelectionInformation();
|
||||
let activeInstance = null;
|
||||
if (enableDeprecatedFlareAPI || enableUseEventAPI) {
|
||||
const focusedElem = selectionInformation.focusedElem;
|
||||
if (focusedElem !== null) {
|
||||
const instance = getClosestInstanceFromNode(focusedElem);
|
||||
if (instance !== null && isFiberInsideHiddenOrRemovedTree(instance)) {
|
||||
dispatchBeforeDetachedBlur(focusedElem);
|
||||
}
|
||||
activeInstance = getClosestInstanceFromNode(focusedElem);
|
||||
}
|
||||
}
|
||||
ReactBrowserEventEmitterSetEnabled(false);
|
||||
return activeInstance;
|
||||
}
|
||||
|
||||
export function beforeActiveInstanceBlur(): void {
|
||||
if (enableDeprecatedFlareAPI || enableUseEventAPI) {
|
||||
ReactBrowserEventEmitterSetEnabled(true);
|
||||
dispatchBeforeDetachedBlur((selectionInformation: any).focusedElem);
|
||||
ReactBrowserEventEmitterSetEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
export function afterActiveInstanceBlur(): void {
|
||||
if (enableDeprecatedFlareAPI || enableUseEventAPI) {
|
||||
ReactBrowserEventEmitterSetEnabled(true);
|
||||
dispatchAfterDetachedBlur((selectionInformation: any).focusedElem);
|
||||
ReactBrowserEventEmitterSetEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
export function resetAfterCommit(containerInfo: Container): void {
|
||||
restoreSelection(selectionInformation);
|
||||
ReactBrowserEventEmitterSetEnabled(eventsEnabled);
|
||||
eventsEnabled = null;
|
||||
if (enableDeprecatedFlareAPI || enableUseEventAPI) {
|
||||
const activeElementDetached = (selectionInformation: any)
|
||||
.activeElementDetached;
|
||||
if (activeElementDetached !== null) {
|
||||
dispatchAfterDetachedBlur(activeElementDetached);
|
||||
}
|
||||
}
|
||||
selectionInformation = null;
|
||||
}
|
||||
|
||||
@@ -525,8 +528,6 @@ function createEvent(type: TopLevelType): Event {
|
||||
}
|
||||
|
||||
function dispatchBeforeDetachedBlur(target: HTMLElement): void {
|
||||
((selectionInformation: any): SelectionInformation).activeElementDetached = target;
|
||||
|
||||
if (enableDeprecatedFlareAPI || enableUseEventAPI) {
|
||||
const event = createEvent(TOP_BEFORE_BLUR);
|
||||
// Dispatch "beforeblur" directly on the target,
|
||||
|
||||
@@ -100,8 +100,6 @@ export function hasSelectionCapabilities(elem) {
|
||||
export function getSelectionInformation() {
|
||||
const focusedElem = getActiveElementDeep();
|
||||
return {
|
||||
// Used by Flare
|
||||
activeElementDetached: null,
|
||||
focusedElem: focusedElem,
|
||||
selectionRange: hasSelectionCapabilities(focusedElem)
|
||||
? getSelection(focusedElem)
|
||||
|
||||
@@ -16,7 +16,8 @@ let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let FocusWithinResponder;
|
||||
let useFocusWithin;
|
||||
let Scheduler;
|
||||
let ReactTestRenderer;
|
||||
let act;
|
||||
|
||||
const initializeModules = hasPointerEvents => {
|
||||
setPointerEvent(hasPointerEvents);
|
||||
@@ -26,7 +27,8 @@ const initializeModules = hasPointerEvents => {
|
||||
ReactFeatureFlags.enableScopeAPI = true;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
Scheduler = require('scheduler');
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
act = ReactTestRenderer.act;
|
||||
|
||||
// TODO: This import throws outside of experimental mode. Figure out better
|
||||
// strategy for gated imports.
|
||||
@@ -43,17 +45,22 @@ const table = [[forcePointerEvents], [!forcePointerEvents]];
|
||||
|
||||
describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
let container;
|
||||
let container2;
|
||||
|
||||
beforeEach(() => {
|
||||
initializeModules();
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
container2 = document.createElement('div');
|
||||
document.body.appendChild(container2);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
ReactDOM.render(null, container);
|
||||
document.body.removeChild(container);
|
||||
document.body.removeChild(container2);
|
||||
container = null;
|
||||
container2 = null;
|
||||
});
|
||||
|
||||
describe('disabled', () => {
|
||||
@@ -366,6 +373,40 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('is called after many elements are unmounted', () => {
|
||||
const buttonRef = React.createRef();
|
||||
const inputRef = React.createRef();
|
||||
|
||||
const Component = ({show}) => {
|
||||
const listener = useFocusWithin({
|
||||
onBeforeBlurWithin,
|
||||
onAfterBlurWithin,
|
||||
});
|
||||
return (
|
||||
<div ref={ref} DEPRECATED_flareListeners={listener}>
|
||||
{show && <button>Press me!</button>}
|
||||
{show && <button>Press me!</button>}
|
||||
{show && <input ref={inputRef} />}
|
||||
{show && <button>Press me!</button>}
|
||||
{!show && <button ref={buttonRef}>Press me!</button>}
|
||||
{show && <button>Press me!</button>}
|
||||
<button>Press me!</button>
|
||||
<button>Press me!</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<Component show={true} />, container);
|
||||
|
||||
inputRef.current.focus();
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
|
||||
ReactDOM.render(<Component show={false} />, container);
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('is called after a nested focused element is unmounted (with scope query)', () => {
|
||||
const TestScope = React.unstable_createScope();
|
||||
@@ -430,12 +471,10 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
);
|
||||
};
|
||||
|
||||
const container2 = document.createElement('div');
|
||||
document.body.appendChild(container2);
|
||||
|
||||
const root = ReactDOM.createRoot(container2);
|
||||
root.render(<Component />);
|
||||
Scheduler.unstable_flushAll();
|
||||
act(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
jest.runAllTimers();
|
||||
expect(container2.innerHTML).toBe('<div><input></div>');
|
||||
|
||||
@@ -447,8 +486,9 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
|
||||
|
||||
suspend = true;
|
||||
root.render(<Component />);
|
||||
Scheduler.unstable_flushAll();
|
||||
act(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
jest.runAllTimers();
|
||||
expect(container2.innerHTML).toBe(
|
||||
'<div><input style="display: none;">Loading...</div>',
|
||||
@@ -456,8 +496,74 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
|
||||
resolve();
|
||||
});
|
||||
|
||||
document.body.removeChild(container2);
|
||||
// @gate experimental
|
||||
it('is called after a focused suspended element is hidden then shown', () => {
|
||||
const Suspense = React.Suspense;
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
const buttonRef = React.createRef();
|
||||
|
||||
function Child() {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return <input ref={innerRef} />;
|
||||
}
|
||||
}
|
||||
|
||||
const Component = ({show}) => {
|
||||
const listener = useFocusWithin({
|
||||
onBeforeBlurWithin,
|
||||
onAfterBlurWithin,
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={ref} DEPRECATED_flareListeners={listener}>
|
||||
<Suspense fallback={<button ref={buttonRef}>Loading...</button>}>
|
||||
<Child />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const root = ReactDOM.createRoot(container2);
|
||||
|
||||
act(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
|
||||
|
||||
suspend = true;
|
||||
act(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
jest.runAllTimers();
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
|
||||
|
||||
act(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
jest.runAllTimers();
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
|
||||
|
||||
buttonRef.current.focus();
|
||||
suspend = false;
|
||||
act(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
jest.runAllTimers();
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -42,17 +42,22 @@ const table = [[forcePointerEvents], [!forcePointerEvents]];
|
||||
|
||||
describe.each(table)(`useFocus`, hasPointerEvents => {
|
||||
let container;
|
||||
let container2;
|
||||
|
||||
beforeEach(() => {
|
||||
initializeModules(hasPointerEvents);
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
container2 = document.createElement('div');
|
||||
document.body.appendChild(container2);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
ReactDOM.render(null, container);
|
||||
document.body.removeChild(container);
|
||||
document.body.removeChild(container2);
|
||||
container = null;
|
||||
container2 = null;
|
||||
});
|
||||
|
||||
describe('disabled', () => {
|
||||
@@ -367,6 +372,40 @@ describe.each(table)(`useFocus`, hasPointerEvents => {
|
||||
);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('is called after many elements are unmounted', () => {
|
||||
const buttonRef = React.createRef();
|
||||
const inputRef = React.createRef();
|
||||
|
||||
const Component = ({show}) => {
|
||||
useFocusWithin(ref, {
|
||||
onBeforeBlurWithin,
|
||||
onAfterBlurWithin,
|
||||
});
|
||||
return (
|
||||
<div ref={ref}>
|
||||
{show && <button>Press me!</button>}
|
||||
{show && <button>Press me!</button>}
|
||||
{show && <input ref={inputRef} />}
|
||||
{show && <button>Press me!</button>}
|
||||
{!show && <button ref={buttonRef}>Press me!</button>}
|
||||
{show && <button>Press me!</button>}
|
||||
<button>Press me!</button>
|
||||
<button>Press me!</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<Component show={true} />, container);
|
||||
|
||||
inputRef.current.focus();
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
|
||||
ReactDOM.render(<Component show={false} />, container);
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('is called after a nested focused element is unmounted (with scope query)', () => {
|
||||
const TestScope = React.unstable_createScope();
|
||||
@@ -431,9 +470,6 @@ describe.each(table)(`useFocus`, hasPointerEvents => {
|
||||
);
|
||||
};
|
||||
|
||||
const container2 = document.createElement('div');
|
||||
document.body.appendChild(container2);
|
||||
|
||||
const root = ReactDOM.createRoot(container2);
|
||||
|
||||
act(() => {
|
||||
@@ -460,8 +496,74 @@ describe.each(table)(`useFocus`, hasPointerEvents => {
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
|
||||
resolve();
|
||||
});
|
||||
|
||||
document.body.removeChild(container2);
|
||||
// @gate experimental
|
||||
it('is called after a focused suspended element is hidden then shown', () => {
|
||||
const Suspense = React.Suspense;
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
const buttonRef = React.createRef();
|
||||
|
||||
function Child() {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return <input ref={innerRef} />;
|
||||
}
|
||||
}
|
||||
|
||||
const Component = ({show}) => {
|
||||
useFocusWithin(ref, {
|
||||
onBeforeBlurWithin,
|
||||
onAfterBlurWithin,
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<Suspense fallback={<button ref={buttonRef}>Loading...</button>}>
|
||||
<Child />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const root = ReactDOM.createRoot(container2);
|
||||
|
||||
act(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
|
||||
|
||||
suspend = true;
|
||||
act(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
jest.runAllTimers();
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
|
||||
|
||||
act(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
jest.runAllTimers();
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(0);
|
||||
|
||||
buttonRef.current.focus();
|
||||
suspend = false;
|
||||
act(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
jest.runAllTimers();
|
||||
expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
|
||||
expect(onAfterBlurWithin).toHaveBeenCalledTimes(1);
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -304,8 +304,9 @@ export function getPublicInstance(instance: Instance): * {
|
||||
return instance.canonical;
|
||||
}
|
||||
|
||||
export function prepareForCommit(containerInfo: Container): void {
|
||||
export function prepareForCommit(containerInfo: Container): null | Object {
|
||||
// Noop
|
||||
return null;
|
||||
}
|
||||
|
||||
export function prepareUpdate(
|
||||
@@ -524,3 +525,11 @@ export function unmountEventListener(listener: any) {
|
||||
export function validateEventListenerTarget(target: any, listener: any) {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
export function beforeActiveInstanceBlur() {
|
||||
// noop
|
||||
}
|
||||
|
||||
export function afterActiveInstanceBlur() {
|
||||
// noop
|
||||
}
|
||||
|
||||
@@ -223,8 +223,9 @@ export function getPublicInstance(instance: Instance): * {
|
||||
return instance;
|
||||
}
|
||||
|
||||
export function prepareForCommit(containerInfo: Container): void {
|
||||
export function prepareForCommit(containerInfo: Container): null | Object {
|
||||
// Noop
|
||||
return null;
|
||||
}
|
||||
|
||||
export function prepareUpdate(
|
||||
@@ -573,3 +574,11 @@ export function unmountEventListener(listener: any) {
|
||||
export function validateEventListenerTarget(target: any, listener: any) {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
export function beforeActiveInstanceBlur() {
|
||||
// noop
|
||||
}
|
||||
|
||||
export function afterActiveInstanceBlur() {
|
||||
// noop
|
||||
}
|
||||
|
||||
@@ -363,7 +363,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||
cancelTimeout: clearTimeout,
|
||||
noTimeout: -1,
|
||||
|
||||
prepareForCommit(): void {},
|
||||
prepareForCommit(): null | Object {
|
||||
return null;
|
||||
},
|
||||
|
||||
resetAfterCommit(): void {},
|
||||
|
||||
@@ -439,6 +441,14 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
|
||||
beforeRemoveInstance(instance: any): void {
|
||||
// NO-OP
|
||||
},
|
||||
|
||||
beforeActiveInstanceBlur() {
|
||||
// NO-OP
|
||||
},
|
||||
|
||||
afterActiveInstanceBlur() {
|
||||
// NO-OP
|
||||
},
|
||||
};
|
||||
|
||||
const hostConfig = useMutation
|
||||
|
||||
@@ -342,20 +342,45 @@ export function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
// This is only safe to call in the commit phase when the return tree is consistent.
|
||||
// It should not be used anywhere else. See PR #18609 for details.
|
||||
export function isFiberInsideHiddenOrRemovedTree(fiber: Fiber): boolean {
|
||||
let node = fiber;
|
||||
let lastChild = null;
|
||||
function doesFiberContain(parentFiber: Fiber, childFiber: Fiber): boolean {
|
||||
let node = childFiber;
|
||||
const parentFiberAlternate = parentFiber.alternate;
|
||||
while (node !== null) {
|
||||
if (
|
||||
node.effectTag & Deletion ||
|
||||
(isFiberSuspenseAndTimedOut(node) && node.child === lastChild)
|
||||
) {
|
||||
if (node === parentFiber || node === parentFiberAlternate) {
|
||||
return true;
|
||||
}
|
||||
lastChild = node;
|
||||
node = node.return;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isFiberTimedOutSuspenseThatContainsTargetFiber(
|
||||
fiber: Fiber,
|
||||
targetFiber: Fiber,
|
||||
): boolean {
|
||||
const child = fiber.child;
|
||||
return (
|
||||
isFiberSuspenseAndTimedOut(fiber) &&
|
||||
child !== null &&
|
||||
doesFiberContain(child, targetFiber)
|
||||
);
|
||||
}
|
||||
|
||||
function isFiberDeletedAndContainsTargetFiber(
|
||||
fiber: Fiber,
|
||||
targetFiber: Fiber,
|
||||
): boolean {
|
||||
return (
|
||||
(fiber.effectTag & Deletion) !== 0 && doesFiberContain(fiber, targetFiber)
|
||||
);
|
||||
}
|
||||
|
||||
export function isFiberHiddenOrDeletedAndContains(
|
||||
parentFiber: Fiber,
|
||||
childFiber: Fiber,
|
||||
): boolean {
|
||||
return (
|
||||
isFiberDeletedAndContainsTargetFiber(parentFiber, childFiber) ||
|
||||
isFiberTimedOutSuspenseThatContainsTargetFiber(parentFiber, childFiber)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,6 +72,8 @@ import {
|
||||
cancelTimeout,
|
||||
noTimeout,
|
||||
warnsIfNotActing,
|
||||
beforeActiveInstanceBlur,
|
||||
afterActiveInstanceBlur,
|
||||
} from './ReactFiberHostConfig';
|
||||
|
||||
import {
|
||||
@@ -193,6 +195,7 @@ import {onCommitRoot} from './ReactFiberDevToolsHook.new';
|
||||
|
||||
// Used by `act`
|
||||
import enqueueTask from 'shared/enqueueTask';
|
||||
import {isFiberHiddenOrDeletedAndContains} from './ReactFiberTreeReflection';
|
||||
|
||||
const ceil = Math.ceil;
|
||||
|
||||
@@ -298,6 +301,9 @@ let currentEventTime: ExpirationTime = NoWork;
|
||||
// We warn about state updates for unmounted components differently in this case.
|
||||
let isFlushingPassiveEffects = false;
|
||||
|
||||
let focusedInstanceHandle: null | Fiber = null;
|
||||
let shouldFireAfterActiveInstanceBlur: boolean = false;
|
||||
|
||||
export function getWorkInProgressRoot(): FiberRoot | null {
|
||||
return workInProgressRoot;
|
||||
}
|
||||
@@ -1902,7 +1908,9 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
// 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.
|
||||
prepareForCommit(root.containerInfo);
|
||||
focusedInstanceHandle = prepareForCommit(root.containerInfo);
|
||||
shouldFireAfterActiveInstanceBlur = false;
|
||||
|
||||
nextEffect = firstEffect;
|
||||
do {
|
||||
if (__DEV__) {
|
||||
@@ -1924,6 +1932,9 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
}
|
||||
} while (nextEffect !== null);
|
||||
|
||||
// We no longer need to track the active instance fiber
|
||||
focusedInstanceHandle = null;
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// Mark the current commit time to be shared by all Profilers in this
|
||||
// batch. This enables them to be grouped later.
|
||||
@@ -1957,6 +1968,10 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
}
|
||||
}
|
||||
} while (nextEffect !== null);
|
||||
|
||||
if (shouldFireAfterActiveInstanceBlur) {
|
||||
afterActiveInstanceBlur();
|
||||
}
|
||||
resetAfterCommit(root.containerInfo);
|
||||
|
||||
// The work-in-progress tree is now the current tree. This must come after
|
||||
@@ -2124,6 +2139,14 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
|
||||
function commitBeforeMutationEffects() {
|
||||
while (nextEffect !== null) {
|
||||
if (
|
||||
!shouldFireAfterActiveInstanceBlur &&
|
||||
focusedInstanceHandle !== null &&
|
||||
isFiberHiddenOrDeletedAndContains(nextEffect, focusedInstanceHandle)
|
||||
) {
|
||||
shouldFireAfterActiveInstanceBlur = true;
|
||||
beforeActiveInstanceBlur();
|
||||
}
|
||||
const effectTag = nextEffect.effectTag;
|
||||
if ((effectTag & Snapshot) !== NoEffect) {
|
||||
setCurrentDebugFiberInDEV(nextEffect);
|
||||
|
||||
@@ -72,6 +72,8 @@ import {
|
||||
cancelTimeout,
|
||||
noTimeout,
|
||||
warnsIfNotActing,
|
||||
beforeActiveInstanceBlur,
|
||||
afterActiveInstanceBlur,
|
||||
} from './ReactFiberHostConfig';
|
||||
|
||||
import {
|
||||
@@ -191,6 +193,7 @@ import {onCommitRoot} from './ReactFiberDevToolsHook.old';
|
||||
|
||||
// Used by `act`
|
||||
import enqueueTask from 'shared/enqueueTask';
|
||||
import {isFiberHiddenOrDeletedAndContains} from './ReactFiberTreeReflection';
|
||||
|
||||
const ceil = Math.ceil;
|
||||
|
||||
@@ -296,6 +299,9 @@ let currentEventTime: ExpirationTime = NoWork;
|
||||
// We warn about state updates for unmounted components differently in this case.
|
||||
let isFlushingPassiveEffects = false;
|
||||
|
||||
let focusedInstanceHandle: null | Fiber = null;
|
||||
let shouldFireAfterActiveInstanceBlur: boolean = false;
|
||||
|
||||
export function getWorkInProgressRoot(): FiberRoot | null {
|
||||
return workInProgressRoot;
|
||||
}
|
||||
@@ -1921,7 +1927,9 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
// 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.
|
||||
prepareForCommit(root.containerInfo);
|
||||
focusedInstanceHandle = prepareForCommit(root.containerInfo);
|
||||
shouldFireAfterActiveInstanceBlur = false;
|
||||
|
||||
nextEffect = firstEffect;
|
||||
do {
|
||||
if (__DEV__) {
|
||||
@@ -1943,6 +1951,9 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
}
|
||||
} while (nextEffect !== null);
|
||||
|
||||
// We no longer need to track the active instance fiber
|
||||
focusedInstanceHandle = null;
|
||||
|
||||
if (enableProfilerTimer) {
|
||||
// Mark the current commit time to be shared by all Profilers in this
|
||||
// batch. This enables them to be grouped later.
|
||||
@@ -1976,6 +1987,10 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
}
|
||||
}
|
||||
} while (nextEffect !== null);
|
||||
|
||||
if (shouldFireAfterActiveInstanceBlur) {
|
||||
afterActiveInstanceBlur();
|
||||
}
|
||||
resetAfterCommit(root.containerInfo);
|
||||
|
||||
// The work-in-progress tree is now the current tree. This must come after
|
||||
@@ -2143,6 +2158,14 @@ function commitRootImpl(root, renderPriorityLevel) {
|
||||
|
||||
function commitBeforeMutationEffects() {
|
||||
while (nextEffect !== null) {
|
||||
if (
|
||||
!shouldFireAfterActiveInstanceBlur &&
|
||||
focusedInstanceHandle !== null &&
|
||||
isFiberHiddenOrDeletedAndContains(nextEffect, focusedInstanceHandle)
|
||||
) {
|
||||
shouldFireAfterActiveInstanceBlur = true;
|
||||
beforeActiveInstanceBlur();
|
||||
}
|
||||
const effectTag = nextEffect.effectTag;
|
||||
if ((effectTag & Snapshot) !== NoEffect) {
|
||||
setCurrentDebugFiberInDEV(nextEffect);
|
||||
|
||||
@@ -25,7 +25,9 @@ describe('ReactFiberHostContext', () => {
|
||||
it('works with null host context', () => {
|
||||
let creates = 0;
|
||||
const Renderer = ReactFiberReconciler({
|
||||
prepareForCommit: function() {},
|
||||
prepareForCommit: function() {
|
||||
return null;
|
||||
},
|
||||
resetAfterCommit: function() {},
|
||||
getRootHostContext: function() {
|
||||
return null;
|
||||
@@ -76,6 +78,7 @@ describe('ReactFiberHostContext', () => {
|
||||
const Renderer = ReactFiberReconciler({
|
||||
prepareForCommit: function(hostContext) {
|
||||
expect(hostContext).toBe(rootContext);
|
||||
return null;
|
||||
},
|
||||
resetAfterCommit: function(hostContext) {
|
||||
expect(hostContext).toBe(rootContext);
|
||||
|
||||
@@ -89,6 +89,8 @@ export const makeOpaqueHydratingObject =
|
||||
export const makeClientId = $$$hostConfig.makeClientId;
|
||||
export const makeClientIdInDEV = $$$hostConfig.makeClientIdInDEV;
|
||||
export const makeServerId = $$$hostConfig.makeServerId;
|
||||
export const beforeActiveInstanceBlur = $$$hostConfig.beforeActiveInstanceBlur;
|
||||
export const afterActiveInstanceBlur = $$$hostConfig.afterActiveInstanceBlur;
|
||||
|
||||
// -------------------
|
||||
// Mutation
|
||||
|
||||
@@ -143,8 +143,9 @@ export function getChildHostContext(
|
||||
return NO_CONTEXT;
|
||||
}
|
||||
|
||||
export function prepareForCommit(containerInfo: Container): void {
|
||||
export function prepareForCommit(containerInfo: Container): null | Object {
|
||||
// noop
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resetAfterCommit(containerInfo: Container): void {
|
||||
@@ -445,3 +446,11 @@ export function unmountEventListener(listener: any) {
|
||||
export function validateEventListenerTarget(target: any, listener: any) {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
export function beforeActiveInstanceBlur() {
|
||||
// noop
|
||||
}
|
||||
|
||||
export function afterActiveInstanceBlur() {
|
||||
// noop
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user