mirror of
https://github.com/facebook/react.git
synced 2026-02-23 20:23:02 +00:00
Merge branch 'master' into devtools-v4-merge
This commit is contained in:
@@ -142,6 +142,8 @@ module.exports = {
|
||||
],
|
||||
|
||||
globals: {
|
||||
SharedArrayBuffer: true,
|
||||
|
||||
spyOnDev: true,
|
||||
spyOnDevAndProd: true,
|
||||
spyOnProd: true,
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
* Warn in Strict Mode if effects are scheduled outside an `act()` call. ([@threepointone](https://github.com/threepointone) in [#15763](https://github.com/facebook/react/pull/15763) and [#16041](https://github.com/facebook/react/pull/16041))
|
||||
* Warn when using `act` from the wrong renderer. ([@threepointone](https://github.com/threepointone) in [#15756](https://github.com/facebook/react/pull/15756))
|
||||
|
||||
### ESLint Plugin: React Hooks
|
||||
|
||||
* Report Hook calls at the top level as a violation. ([gaearon](https://github.com/gaearon) in [#16455](https://github.com/facebook/react/pull/16455))
|
||||
|
||||
## 16.8.6 (March 27, 2019)
|
||||
|
||||
### React DOM
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eslint-plugin-react-hooks",
|
||||
"description": "ESLint rules for React Hooks",
|
||||
"version": "1.7.0",
|
||||
"version": "2.0.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/facebook/react.git",
|
||||
|
||||
@@ -432,8 +432,7 @@ export default {
|
||||
'React Hook function.';
|
||||
context.report({node: hook, message});
|
||||
} else if (codePathNode.type === 'Program') {
|
||||
// We could warn here but there are false positives related
|
||||
// configuring libraries like `history`.
|
||||
// These are dangerous if you have inline requires enabled.
|
||||
const message =
|
||||
`React Hook "${context.getSource(hook)}" cannot be called ` +
|
||||
'at the top level. React Hooks must be called in a ' +
|
||||
|
||||
1
packages/react-art/src/ReactARTHostConfig.js
vendored
1
packages/react-art/src/ReactARTHostConfig.js
vendored
@@ -432,7 +432,6 @@ export function mountResponderInstance(
|
||||
props: Object,
|
||||
state: Object,
|
||||
instance: Object,
|
||||
rootContainerInstance: Object,
|
||||
) {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
<!-- Upcoming changes go here -->
|
||||
</details>
|
||||
|
||||
## 4.0.6 (August 26, 2019)
|
||||
#### Bug fixes
|
||||
* Remove ⚛️ emoji prefix from Firefox extension tab labels
|
||||
* Standalone polyfills `Symbol` usage
|
||||
|
||||
## 4.0.5 (August 19, 2019)
|
||||
#### Bug fixes
|
||||
* Props, state, and context values are alpha sorted.
|
||||
|
||||
@@ -25,6 +25,7 @@ describe('ReactDOMServerPartialHydration', () => {
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableSuspenseServerRenderer = true;
|
||||
ReactFeatureFlags.enableSuspenseCallback = true;
|
||||
ReactFeatureFlags.enableFlareAPI = true;
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
@@ -1729,4 +1730,169 @@ describe('ReactDOMServerPartialHydration', () => {
|
||||
// patched up the tree, which might mean we haven't patched the className.
|
||||
expect(newSpan.className).toBe('hi');
|
||||
});
|
||||
|
||||
it('does not invoke an event on a hydrated node until it commits', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
|
||||
function Sibling({text}) {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return 'Hello';
|
||||
}
|
||||
}
|
||||
|
||||
let clicks = 0;
|
||||
|
||||
function Button() {
|
||||
let [clicked, setClicked] = React.useState(false);
|
||||
if (clicked) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<a
|
||||
onClick={() => {
|
||||
setClicked(true);
|
||||
clicks++;
|
||||
}}>
|
||||
Click me
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<Button />
|
||||
<Sibling />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(<App />);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
// We need this to be in the document since we'll dispatch events on it.
|
||||
document.body.appendChild(container);
|
||||
|
||||
let a = container.getElementsByTagName('a')[0];
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App />);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(container.textContent).toBe('Click meHello');
|
||||
|
||||
// We're now partially hydrated.
|
||||
a.click();
|
||||
expect(clicks).toBe(0);
|
||||
|
||||
// Resolving the promise so that rendering can complete.
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
|
||||
// TODO: With selective hydration the event should've been replayed
|
||||
// but for now we'll have to issue it again.
|
||||
act(() => {
|
||||
a.click();
|
||||
});
|
||||
|
||||
expect(clicks).toBe(1);
|
||||
|
||||
expect(container.textContent).toBe('Hello');
|
||||
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('does not invoke an event on a hydrated EventResponder until it commits', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
let promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
|
||||
function Sibling({text}) {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
} else {
|
||||
return 'Hello';
|
||||
}
|
||||
}
|
||||
|
||||
const onEvent = jest.fn();
|
||||
const TestResponder = React.unstable_createResponder('TestEventResponder', {
|
||||
targetEventTypes: ['click'],
|
||||
onEvent,
|
||||
});
|
||||
|
||||
function Button() {
|
||||
let listener = React.unstable_useResponder(TestResponder, {});
|
||||
return <a listeners={listener}>Click me</a>;
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<Button />
|
||||
<Sibling />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
suspend = false;
|
||||
let finalHTML = ReactDOMServer.renderToString(<App />);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
// We need this to be in the document since we'll dispatch events on it.
|
||||
document.body.appendChild(container);
|
||||
|
||||
let a = container.getElementsByTagName('a')[0];
|
||||
|
||||
// On the client we don't have all data yet but we want to start
|
||||
// hydrating anyway.
|
||||
suspend = true;
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App />);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
|
||||
// We're now partially hydrated.
|
||||
a.click();
|
||||
// We should not have invoked the event yet because we're not
|
||||
// yet hydrated.
|
||||
expect(onEvent).toHaveBeenCalledTimes(0);
|
||||
|
||||
// Resolving the promise so that rendering can complete.
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
|
||||
// TODO: With selective hydration the event should've been replayed
|
||||
// but for now we'll have to issue it again.
|
||||
act(() => {
|
||||
a.click();
|
||||
});
|
||||
|
||||
expect(onEvent).toHaveBeenCalledTimes(1);
|
||||
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ let React;
|
||||
let ReactDOM;
|
||||
let ReactDOMServer;
|
||||
let Scheduler;
|
||||
let act;
|
||||
|
||||
// These tests rely both on ReactDOMServer and ReactDOM.
|
||||
// If a test only needs ReactDOMServer, put it in ReactServerRendering-test instead.
|
||||
@@ -23,6 +24,7 @@ describe('ReactDOMServerHydration', () => {
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
Scheduler = require('scheduler');
|
||||
act = require('react-dom/test-utils').act;
|
||||
});
|
||||
|
||||
it('should have the correct mounting behavior (old hydrate API)', () => {
|
||||
@@ -499,4 +501,89 @@ describe('ReactDOMServerHydration', () => {
|
||||
Scheduler.unstable_flushAll();
|
||||
expect(element.textContent).toBe('Hello world');
|
||||
});
|
||||
|
||||
it('does not invoke an event on a concurrent hydrating node until it commits', () => {
|
||||
function Sibling({text}) {
|
||||
Scheduler.unstable_yieldValue('Sibling');
|
||||
return <span>Sibling</span>;
|
||||
}
|
||||
|
||||
function Sibling2({text}) {
|
||||
Scheduler.unstable_yieldValue('Sibling2');
|
||||
return null;
|
||||
}
|
||||
|
||||
let clicks = 0;
|
||||
|
||||
function Button() {
|
||||
Scheduler.unstable_yieldValue('Button');
|
||||
let [clicked, setClicked] = React.useState(false);
|
||||
if (clicked) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<a
|
||||
onClick={() => {
|
||||
setClicked(true);
|
||||
clicks++;
|
||||
}}>
|
||||
Click me
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Button />
|
||||
<Sibling />
|
||||
<Sibling2 />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let finalHTML = ReactDOMServer.renderToString(<App />);
|
||||
let container = document.createElement('div');
|
||||
container.innerHTML = finalHTML;
|
||||
expect(Scheduler).toHaveYielded(['Button', 'Sibling', 'Sibling2']);
|
||||
|
||||
// We need this to be in the document since we'll dispatch events on it.
|
||||
document.body.appendChild(container);
|
||||
|
||||
let a = container.getElementsByTagName('a')[0];
|
||||
|
||||
// Hydrate asynchronously.
|
||||
let root = ReactDOM.unstable_createRoot(container, {hydrate: true});
|
||||
root.render(<App />);
|
||||
// Flush part way through the render.
|
||||
if (__DEV__) {
|
||||
// In DEV effects gets double invoked.
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Button', 'Button', 'Sibling']);
|
||||
} else {
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Button', 'Sibling']);
|
||||
}
|
||||
|
||||
expect(container.textContent).toBe('Click meSibling');
|
||||
|
||||
// We're now partially hydrated.
|
||||
a.click();
|
||||
// Clicking should not invoke the event yet because we haven't committed
|
||||
// the hydration yet.
|
||||
expect(clicks).toBe(0);
|
||||
|
||||
// Finish the rest of the hydration.
|
||||
expect(Scheduler).toFlushAndYield(['Sibling2']);
|
||||
|
||||
// TODO: With selective hydration the event should've been replayed
|
||||
// but for now we'll have to issue it again.
|
||||
act(() => {
|
||||
a.click();
|
||||
});
|
||||
|
||||
expect(clicks).toBe(1);
|
||||
|
||||
expect(container.textContent).toBe('Sibling');
|
||||
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,26 +23,29 @@ export function precacheFiberNode(hostInst, node) {
|
||||
* ReactDOMTextComponent instance ancestor.
|
||||
*/
|
||||
export function getClosestInstanceFromNode(node) {
|
||||
if (node[internalInstanceKey]) {
|
||||
return node[internalInstanceKey];
|
||||
let inst = node[internalInstanceKey];
|
||||
if (inst) {
|
||||
return inst;
|
||||
}
|
||||
|
||||
while (!node[internalInstanceKey]) {
|
||||
if (node.parentNode) {
|
||||
node = node.parentNode;
|
||||
do {
|
||||
node = node.parentNode;
|
||||
if (node) {
|
||||
inst = node[internalInstanceKey];
|
||||
} else {
|
||||
// Top of the tree. This node must not be part of a React tree (or is
|
||||
// unmounted, potentially).
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} while (!inst);
|
||||
|
||||
let inst = node[internalInstanceKey];
|
||||
if (inst.tag === HostComponent || inst.tag === HostText) {
|
||||
// In Fiber, this will always be the deepest root.
|
||||
return inst;
|
||||
let tag = inst.tag;
|
||||
switch (tag) {
|
||||
case HostComponent:
|
||||
case HostText:
|
||||
// In Fiber, this will always be the deepest root.
|
||||
return inst;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -824,10 +824,9 @@ export function mountResponderInstance(
|
||||
responderProps: Object,
|
||||
responderState: Object,
|
||||
instance: Instance,
|
||||
rootContainerInstance: Container,
|
||||
): ReactDOMEventResponderInstance {
|
||||
// Listen to events
|
||||
const doc = rootContainerInstance.ownerDocument;
|
||||
const doc = instance.ownerDocument;
|
||||
const documentBody = doc.body || doc;
|
||||
const {
|
||||
rootEventTypes,
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
PASSIVE_NOT_SUPPORTED,
|
||||
} from 'legacy-events/EventSystemFlags';
|
||||
import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
|
||||
import {HostComponent} from 'shared/ReactWorkTags';
|
||||
import {HostComponent, SuspenseComponent} from 'shared/ReactWorkTags';
|
||||
import type {EventPriority} from 'shared/ReactTypes';
|
||||
import type {
|
||||
ReactDOMEventResponder,
|
||||
@@ -32,10 +32,6 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import warning from 'shared/warning';
|
||||
import {enableFlareAPI} from 'shared/ReactFeatureFlags';
|
||||
import invariant from 'shared/invariant';
|
||||
import {
|
||||
isFiberSuspenseAndTimedOut,
|
||||
getSuspenseFallbackChild,
|
||||
} from 'react-reconciler/src/ReactFiberEvents';
|
||||
|
||||
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
|
||||
import {
|
||||
@@ -445,7 +441,7 @@ function createDOMResponderEvent(
|
||||
passive: boolean,
|
||||
passiveSupported: boolean,
|
||||
): ReactDOMResponderEvent {
|
||||
const {pointerType} = (nativeEvent: any);
|
||||
const {buttons, pointerType} = (nativeEvent: any);
|
||||
let eventPointerType = '';
|
||||
let pointerId = null;
|
||||
|
||||
@@ -454,7 +450,7 @@ function createDOMResponderEvent(
|
||||
pointerId = (nativeEvent: any).pointerId;
|
||||
} else if (nativeEvent.key !== undefined) {
|
||||
eventPointerType = 'keyboard';
|
||||
} else if (nativeEvent.button !== undefined) {
|
||||
} else if (buttons !== undefined) {
|
||||
eventPointerType = 'mouse';
|
||||
} else if ((nativeEvent: any).changedTouches !== undefined) {
|
||||
eventPointerType = 'touch';
|
||||
@@ -630,6 +626,14 @@ function validateResponderContext(): void {
|
||||
);
|
||||
}
|
||||
|
||||
function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean {
|
||||
return fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
|
||||
}
|
||||
|
||||
function getSuspenseFallbackChild(fiber: Fiber): Fiber | null {
|
||||
return ((((fiber.child: any): Fiber).sibling: any): Fiber).child;
|
||||
}
|
||||
|
||||
export function dispatchEventForResponderEventSystem(
|
||||
topLevelType: string,
|
||||
targetFiber: null | Fiber,
|
||||
|
||||
@@ -14,6 +14,7 @@ let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let ReactDOMServer;
|
||||
let ReactTestRenderer;
|
||||
let Scheduler;
|
||||
|
||||
// FIXME: What should the public API be for setting an event's priority? Right
|
||||
// now it's an enum but is that what we want? Hard coding this for now.
|
||||
@@ -72,6 +73,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
Scheduler = require('scheduler');
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
@@ -811,8 +813,8 @@ describe('DOMEventResponderSystem', () => {
|
||||
|
||||
it('the event responder system should warn on accessing invalid properties', () => {
|
||||
const TestResponder = createEventResponder({
|
||||
rootEventTypes: ['click'],
|
||||
onRootEvent: (event, context, props) => {
|
||||
targetEventTypes: ['click'],
|
||||
onEvent: (event, context, props) => {
|
||||
const syntheticEvent = {
|
||||
target: event.target,
|
||||
type: 'click',
|
||||
@@ -823,19 +825,24 @@ describe('DOMEventResponderSystem', () => {
|
||||
});
|
||||
|
||||
let handler;
|
||||
let buttonRef = React.createRef();
|
||||
const Test = () => {
|
||||
const listener = React.unstable_useResponder(TestResponder, {
|
||||
onClick: handler,
|
||||
});
|
||||
|
||||
return <button listeners={listener}>Click me!</button>;
|
||||
return (
|
||||
<button listeners={listener} ref={buttonRef}>
|
||||
Click me!
|
||||
</button>
|
||||
);
|
||||
};
|
||||
expect(() => {
|
||||
handler = event => {
|
||||
event.preventDefault();
|
||||
};
|
||||
ReactDOM.render(<Test />, container);
|
||||
dispatchClickEvent(document.body);
|
||||
dispatchClickEvent(buttonRef.current);
|
||||
}).toWarnDev(
|
||||
'Warning: preventDefault() is not available on event objects created from event responder modules ' +
|
||||
'(React Flare).' +
|
||||
@@ -847,7 +854,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
ReactDOM.render(<Test />, container);
|
||||
dispatchClickEvent(document.body);
|
||||
dispatchClickEvent(buttonRef.current);
|
||||
}).toWarnDev(
|
||||
'Warning: stopPropagation() is not available on event objects created from event responder modules ' +
|
||||
'(React Flare).' +
|
||||
@@ -859,7 +866,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
event.isDefaultPrevented();
|
||||
};
|
||||
ReactDOM.render(<Test />, container);
|
||||
dispatchClickEvent(document.body);
|
||||
dispatchClickEvent(buttonRef.current);
|
||||
}).toWarnDev(
|
||||
'Warning: isDefaultPrevented() is not available on event objects created from event responder modules ' +
|
||||
'(React Flare).' +
|
||||
@@ -871,7 +878,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
event.isPropagationStopped();
|
||||
};
|
||||
ReactDOM.render(<Test />, container);
|
||||
dispatchClickEvent(document.body);
|
||||
dispatchClickEvent(buttonRef.current);
|
||||
}).toWarnDev(
|
||||
'Warning: isPropagationStopped() is not available on event objects created from event responder modules ' +
|
||||
'(React Flare).' +
|
||||
@@ -883,7 +890,7 @@ describe('DOMEventResponderSystem', () => {
|
||||
return event.nativeEvent;
|
||||
};
|
||||
ReactDOM.render(<Test />, container);
|
||||
dispatchClickEvent(document.body);
|
||||
dispatchClickEvent(buttonRef.current);
|
||||
}).toWarnDev(
|
||||
'Warning: nativeEvent is not available on event objects created from event responder modules ' +
|
||||
'(React Flare).' +
|
||||
@@ -934,4 +941,57 @@ describe('DOMEventResponderSystem', () => {
|
||||
ReactDOM.render(<Test2 />, container);
|
||||
buttonRef.current.dispatchEvent(createEvent('foobar'));
|
||||
});
|
||||
|
||||
it('should work with concurrent mode updates', async () => {
|
||||
const log = [];
|
||||
const TestResponder = createEventResponder({
|
||||
targetEventTypes: ['click'],
|
||||
onEvent(event, context, props) {
|
||||
log.push(props);
|
||||
},
|
||||
});
|
||||
const ref = React.createRef();
|
||||
|
||||
function Test({counter}) {
|
||||
const listener = React.unstable_useResponder(TestResponder, {counter});
|
||||
|
||||
return (
|
||||
<button listeners={listener} ref={ref}>
|
||||
Press me
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
let root = ReactDOM.unstable_createRoot(container);
|
||||
let batch = root.createBatch();
|
||||
batch.render(<Test counter={0} />);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
batch.commit();
|
||||
|
||||
// Click the button
|
||||
dispatchClickEvent(ref.current);
|
||||
expect(log).toEqual([{counter: 0}]);
|
||||
|
||||
// Clear log
|
||||
log.length = 0;
|
||||
|
||||
// Increase counter
|
||||
batch = root.createBatch();
|
||||
batch.render(<Test counter={1} />);
|
||||
Scheduler.unstable_flushAll();
|
||||
jest.runAllTimers();
|
||||
|
||||
// Click the button again
|
||||
dispatchClickEvent(ref.current);
|
||||
expect(log).toEqual([{counter: 0}]);
|
||||
|
||||
// Clear log
|
||||
log.length = 0;
|
||||
|
||||
// Commit
|
||||
batch.commit();
|
||||
dispatchClickEvent(ref.current);
|
||||
expect(log).toEqual([{counter: 1}]);
|
||||
});
|
||||
});
|
||||
|
||||
54
packages/react-events/docs/ContextMenu.md
Normal file
54
packages/react-events/docs/ContextMenu.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# ContextMenu
|
||||
|
||||
The `useContextMenu` hooks responds to context-menu events.
|
||||
|
||||
```js
|
||||
// Example
|
||||
const Button = (props) => {
|
||||
const contextmenu = useContextMenu({
|
||||
disabled,
|
||||
onContextMenu,
|
||||
preventDefault
|
||||
});
|
||||
|
||||
return (
|
||||
<div listeners={contextmenu}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
```js
|
||||
type ContextMenuEvent = {
|
||||
altKey: boolean,
|
||||
buttons: 0 | 1 | 2,
|
||||
ctrlKey: boolean,
|
||||
metaKey: boolean,
|
||||
pageX: number,
|
||||
pageY: number,
|
||||
pointerType: PointerType,
|
||||
shiftKey: boolean,
|
||||
target: Element,
|
||||
timeStamp: number,
|
||||
type: 'contextmenu',
|
||||
x: number,
|
||||
y: number,
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
### disabled: boolean = false
|
||||
|
||||
Disables the responder.
|
||||
|
||||
### onContextMenu: (e: ContextMenuEvent) => void
|
||||
|
||||
Called when the user performs a gesture to display a context menu.
|
||||
|
||||
### preventDefault: boolean = true
|
||||
|
||||
Prevents the native behavior (i.e., context menu).
|
||||
@@ -1,29 +1,29 @@
|
||||
# Focus
|
||||
|
||||
The `Focus` module responds to focus and blur events on its child. Focus events
|
||||
The `useFocus` hook responds to focus and blur events on its child. Focus events
|
||||
are dispatched for all input types, with the exception of `onFocusVisibleChange`
|
||||
which is only dispatched when focusing with a keyboard.
|
||||
|
||||
Focus events do not propagate between `Focus` event responders.
|
||||
Focus events do not propagate between `useFocus` event responders.
|
||||
|
||||
```js
|
||||
// Example
|
||||
const Button = (props) => {
|
||||
const [ focusVisible, setFocusVisible ] = useState(false);
|
||||
const [ isFocusVisible, setFocusVisible ] = useState(false);
|
||||
const focus = useFocus({
|
||||
onBlur={props.onBlur}
|
||||
onFocus={props.onFocus}
|
||||
onFocusVisibleChange={setFocusVisible}
|
||||
});
|
||||
|
||||
return (
|
||||
<Focus
|
||||
onBlur={props.onBlur}
|
||||
onFocus={props.onFocus}
|
||||
onFocusVisibleChange={setFocusVisible}
|
||||
<button
|
||||
children={props.children}
|
||||
listeners={focus}
|
||||
style={{
|
||||
...(isFocusVisible && focusVisibleStyles)
|
||||
}}
|
||||
>
|
||||
<button
|
||||
children={props.children}
|
||||
style={{
|
||||
...(focusVisible && focusVisibleStyles)
|
||||
}}
|
||||
>
|
||||
</Focus>
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -33,6 +33,8 @@ const Button = (props) => {
|
||||
```js
|
||||
type FocusEvent = {
|
||||
target: Element,
|
||||
pointerType: 'mouse' | 'touch' | 'pen' | 'keyboard',
|
||||
timeStamp: number,
|
||||
type: 'blur' | 'focus' | 'focuschange' | 'focusvisiblechange'
|
||||
}
|
||||
```
|
||||
@@ -41,7 +43,7 @@ type FocusEvent = {
|
||||
|
||||
### disabled: boolean = false
|
||||
|
||||
Disables all `Focus` events.
|
||||
Disables the responder.
|
||||
|
||||
### onBlur: (e: FocusEvent) => void
|
||||
|
||||
|
||||
@@ -1,48 +1,39 @@
|
||||
# FocusWithin
|
||||
|
||||
The `FocusWithin` module responds to focus and blur events on its child. Focus events
|
||||
The `useFocusWithin` hooks responds to focus and blur events on its child. Focus events
|
||||
are dispatched for all input types, with the exception of `onFocusVisibleChange`
|
||||
which is only dispatched when focusing with a keyboard.
|
||||
|
||||
Focus events do not propagate between `FocusWithin` event responders.
|
||||
Focus events do not propagate between `useFocusWithin` event responders.
|
||||
|
||||
```js
|
||||
// Example
|
||||
const Button = (props) => {
|
||||
const [ focusWithin, updateFocusWithin ] = useState(false);
|
||||
const [ focusWithinVisible, updateFocusWithinVisible ] = useState(false);
|
||||
const [ isFocusWithin, updateFocusWithin ] = useState(false);
|
||||
const [ isFocusWithinVisible, updateFocusWithinVisible ] = useState(false);
|
||||
const focusWithin = useFocusWithin({
|
||||
onFocusWithinChange={updateFocusWithin}
|
||||
onFocusWithinVisibleChange={updateFocusWithinVisible}
|
||||
});
|
||||
|
||||
return (
|
||||
<FocusWithin
|
||||
onFocusWithinChange={updateFocusWithin}
|
||||
onFocusWithinVisibleChange={updateFocusWithinVisible}
|
||||
<button
|
||||
children={props.children}
|
||||
listeners={focusWithin}
|
||||
style={{
|
||||
...(isFocusWithin && focusWithinStyles),
|
||||
...(isFocusWithinVisible && focusWithinVisibleStyles)
|
||||
}}
|
||||
>
|
||||
<button
|
||||
children={props.children}
|
||||
style={{
|
||||
...(focusWithin && focusWithinStyles),
|
||||
...(focusWithinVisible && focusWithinVisibleStyles)
|
||||
}}
|
||||
>
|
||||
</FocusWithin>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
```js
|
||||
type FocusEvent = {
|
||||
target: Element,
|
||||
type: 'focuswithinchange' | 'focuswithinvisiblechange'
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
### disabled: boolean = false
|
||||
|
||||
Disables all `FocusWithin` events.
|
||||
Disables the responder.
|
||||
|
||||
### onFocusWithinChange: boolean => void
|
||||
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
# Hover
|
||||
|
||||
The `Hover` module responds to hover events on the element it wraps. Hover
|
||||
The `useHover` hook responds to hover events on the element it wraps. Hover
|
||||
events are only dispatched for `mouse` and `pen` pointer types. Hover begins
|
||||
when the pointer enters the element's bounds and ends when the pointer leaves.
|
||||
|
||||
Hover events do not propagate between `Hover` event responders.
|
||||
Hover events do not propagate between `useHover` event responders.
|
||||
|
||||
```js
|
||||
// Example
|
||||
const Link = (props) => (
|
||||
const [ hovered, setHovered ] = useState(false);
|
||||
const [ isHovered, setHovered ] = useState(false);
|
||||
const hover = useHover({
|
||||
onHoverChange: setHovered
|
||||
});
|
||||
|
||||
return (
|
||||
<Hover onHoverChange={setHovered}>
|
||||
<a
|
||||
{...props}
|
||||
href={props.href}
|
||||
style={{
|
||||
...props.style,
|
||||
textDecoration: hovered ? 'underline': 'none'
|
||||
}}
|
||||
/>
|
||||
</Hover>
|
||||
<a
|
||||
{...props}
|
||||
href={props.href}
|
||||
listeners={hover}
|
||||
style={{
|
||||
...props.style,
|
||||
textDecoration: isHovered ? 'underline': 'none'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
);
|
||||
```
|
||||
@@ -28,18 +31,27 @@ const Link = (props) => (
|
||||
## Types
|
||||
|
||||
```js
|
||||
type HoverEvent = {
|
||||
pointerType: 'mouse' | 'pen',
|
||||
type HoverEventType = 'hoverstart' | 'hoverend' | 'hoverchange' | 'hovermove';
|
||||
|
||||
type HoverEvent = {|
|
||||
clientX: number,
|
||||
clientY: number,
|
||||
pageX: number,
|
||||
pageY: number,
|
||||
pointerType: PointerType,
|
||||
target: Element,
|
||||
type: 'hoverstart' | 'hoverend' | 'hovermove' | 'hoverchange'
|
||||
}
|
||||
timeStamp: number,
|
||||
type: HoverEventType,
|
||||
x: number,
|
||||
y: number,
|
||||
|};
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
### disabled: boolean
|
||||
|
||||
Disables all `Hover` events.
|
||||
Disables the responder.
|
||||
|
||||
### onHoverChange: boolean => void
|
||||
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
# Press
|
||||
|
||||
The `Press` module responds to press events on the element it wraps. Press
|
||||
The `usePress` hook responds to press events on the element it wraps. Press
|
||||
events are dispatched for `mouse`, `pen`, `touch`, `trackpad`, and `keyboard`
|
||||
pointer types. Press events are only dispatched for keyboards when pressing the
|
||||
Enter or Spacebar keys. If `onPress` is not called, this signifies that the
|
||||
press ended outside of the element hit bounds (i.e., the user aborted the
|
||||
press).
|
||||
|
||||
Press events do not propagate between `Press` event responders.
|
||||
Press events do not propagate between `usePress` event responders.
|
||||
|
||||
```js
|
||||
// Example
|
||||
const Button = (props) => (
|
||||
const [ pressed, setPressed ] = useState(false);
|
||||
const [ isPressed, setPressed ] = useState(false);
|
||||
const press = usePress({
|
||||
onPress={props.onPress}
|
||||
onPressChange={setPressed}
|
||||
});
|
||||
|
||||
return (
|
||||
<Press
|
||||
onPress={props.onPress}
|
||||
onPressChange={setPressed}
|
||||
>
|
||||
<div
|
||||
{...props}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
style={
|
||||
...buttonStyles,
|
||||
...(pressed && pressedStyles)
|
||||
}}
|
||||
/>
|
||||
</Press>
|
||||
<div
|
||||
{...props}
|
||||
listeners={press}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
style={
|
||||
...buttonStyles,
|
||||
...(isPressed && pressedStyles)
|
||||
}}
|
||||
/>
|
||||
);
|
||||
);
|
||||
```
|
||||
@@ -37,6 +38,7 @@ const Button = (props) => (
|
||||
```js
|
||||
type PressEvent = {
|
||||
altKey: boolean,
|
||||
buttons: 0 | 1 | 4,
|
||||
ctrlKey: boolean,
|
||||
defaultPrevented: boolean,
|
||||
metaKey: boolean,
|
||||
@@ -76,12 +78,7 @@ type PressOffset = {
|
||||
|
||||
### disabled: boolean = false
|
||||
|
||||
Disables all `Press` events.
|
||||
|
||||
### onContextMenu: (e: PressEvent) => void
|
||||
|
||||
Called when the context menu is shown. When a press is active, the context menu
|
||||
will only be shown (and the press cancelled) if `preventDefault` is `false`.
|
||||
Disables the responder.
|
||||
|
||||
### onPress: (e: PressEvent) => void
|
||||
|
||||
@@ -115,11 +112,6 @@ down) can be moved back within the bounds of the element to reactivate it.
|
||||
Ensure you pass in a constant to reduce memory allocations. Default is `20` for
|
||||
each offset.
|
||||
|
||||
### preventContextMenu: boolean = false
|
||||
|
||||
Prevents the native context menu from being shown, but `onContextMenu`
|
||||
is still called.
|
||||
|
||||
### preventDefault: boolean = true
|
||||
|
||||
Whether to `preventDefault()` native events. Native behavior is prevented by
|
||||
|
||||
33
packages/react-events/src/dom/ContextMenu.js
vendored
33
packages/react-events/src/dom/ContextMenu.js
vendored
@@ -28,20 +28,19 @@ type ContextMenuState = {
|
||||
};
|
||||
|
||||
type ContextMenuEvent = {|
|
||||
target: Element | Document,
|
||||
type: 'contextmenu',
|
||||
pointerType: PointerType,
|
||||
timeStamp: number,
|
||||
clientX: null | number,
|
||||
clientY: null | number,
|
||||
pageX: null | number,
|
||||
pageY: null | number,
|
||||
x: null | number,
|
||||
y: null | number,
|
||||
altKey: boolean,
|
||||
buttons: 0 | 1 | 2,
|
||||
ctrlKey: boolean,
|
||||
metaKey: boolean,
|
||||
pageX: null | number,
|
||||
pageY: null | number,
|
||||
pointerType: PointerType,
|
||||
shiftKey: boolean,
|
||||
target: Element | Document,
|
||||
timeStamp: number,
|
||||
type: 'contextmenu',
|
||||
x: null | number,
|
||||
y: null | number,
|
||||
|};
|
||||
|
||||
const hasPointerEvents =
|
||||
@@ -60,13 +59,13 @@ function dispatchContextMenuEvent(
|
||||
|
||||
const gestureState = {
|
||||
altKey: nativeEvent.altKey,
|
||||
button: nativeEvent.button === 0 ? 'primary' : 'auxillary',
|
||||
ctrlKey: nativeEvent.altKey,
|
||||
metaKey: nativeEvent.altKey,
|
||||
pageX: nativeEvent.altKey,
|
||||
pageY: nativeEvent.altKey,
|
||||
buttons: nativeEvent.buttons != null ? nativeEvent.buttons : 0,
|
||||
ctrlKey: nativeEvent.ctrlKey,
|
||||
metaKey: nativeEvent.metaKey,
|
||||
pageX: nativeEvent.pageX,
|
||||
pageY: nativeEvent.pageY,
|
||||
pointerType,
|
||||
shiftKey: nativeEvent.altKey,
|
||||
shiftKey: nativeEvent.shiftKey,
|
||||
target,
|
||||
timeStamp,
|
||||
type: 'contextmenu',
|
||||
@@ -121,7 +120,7 @@ export const ContextMenuResponder = React.unstable_createResponder(
|
||||
contextMenuImpl,
|
||||
);
|
||||
|
||||
export function useContextMenuResponder(
|
||||
export function useContextMenu(
|
||||
props: ContextMenuProps,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
return React.unstable_useResponder(ContextMenuResponder, props);
|
||||
|
||||
2
packages/react-events/src/dom/Drag.js
vendored
2
packages/react-events/src/dom/Drag.js
vendored
@@ -244,7 +244,7 @@ export const DragResponder = React.unstable_createResponder(
|
||||
dragResponderImpl,
|
||||
);
|
||||
|
||||
export function useDragResponder(
|
||||
export function useDrag(
|
||||
props: DragProps,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
return React.unstable_useResponder(DragResponder, props);
|
||||
|
||||
4
packages/react-events/src/dom/Focus.js
vendored
4
packages/react-events/src/dom/Focus.js
vendored
@@ -348,7 +348,7 @@ export const FocusResponder = React.unstable_createResponder(
|
||||
focusResponderImpl,
|
||||
);
|
||||
|
||||
export function useFocusResponder(
|
||||
export function useFocus(
|
||||
props: FocusProps,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
return React.unstable_useResponder(FocusResponder, props);
|
||||
@@ -485,7 +485,7 @@ export const FocusWithinResponder = React.unstable_createResponder(
|
||||
focusWithinResponderImpl,
|
||||
);
|
||||
|
||||
export function useFocusWithinResponder(
|
||||
export function useFocusWithin(
|
||||
props: FocusWithinProps,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
return React.unstable_useResponder(FocusWithinResponder, props);
|
||||
|
||||
10
packages/react-events/src/dom/Hover.js
vendored
10
packages/react-events/src/dom/Hover.js
vendored
@@ -37,16 +37,16 @@ type HoverState = {
|
||||
type HoverEventType = 'hoverstart' | 'hoverend' | 'hoverchange' | 'hovermove';
|
||||
|
||||
type HoverEvent = {|
|
||||
pointerType: PointerType,
|
||||
target: Element | Document,
|
||||
type: HoverEventType,
|
||||
timeStamp: number,
|
||||
clientX: null | number,
|
||||
clientY: null | number,
|
||||
pageX: null | number,
|
||||
pageY: null | number,
|
||||
pointerType: PointerType,
|
||||
screenX: null | number,
|
||||
screenY: null | number,
|
||||
target: Element | Document,
|
||||
timeStamp: number,
|
||||
type: HoverEventType,
|
||||
x: null | number,
|
||||
y: null | number,
|
||||
|};
|
||||
@@ -341,7 +341,7 @@ export const HoverResponder = React.unstable_createResponder(
|
||||
hasPointerEvents ? hoverResponderImpl : hoverResponderFallbackImpl,
|
||||
);
|
||||
|
||||
export function useHoverResponder(
|
||||
export function useHover(
|
||||
props: HoverProps,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
return React.unstable_useResponder(HoverResponder, props);
|
||||
|
||||
2
packages/react-events/src/dom/Input.js
vendored
2
packages/react-events/src/dom/Input.js
vendored
@@ -215,7 +215,7 @@ export const InputResponder = React.unstable_createResponder(
|
||||
inputResponderImpl,
|
||||
);
|
||||
|
||||
export function useInputResponder(
|
||||
export function useInput(
|
||||
props: InputResponderProps,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
return React.unstable_useResponder(InputResponder, props);
|
||||
|
||||
2
packages/react-events/src/dom/Keyboard.js
vendored
2
packages/react-events/src/dom/Keyboard.js
vendored
@@ -209,7 +209,7 @@ export const KeyboardResponder = React.unstable_createResponder(
|
||||
keyboardResponderImpl,
|
||||
);
|
||||
|
||||
export function useKeyboardResponder(
|
||||
export function useKeyboard(
|
||||
props: KeyboardProps,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
return React.unstable_useResponder(KeyboardResponder, props);
|
||||
|
||||
62
packages/react-events/src/dom/Press.js
vendored
62
packages/react-events/src/dom/Press.js
vendored
@@ -43,6 +43,7 @@ type PressState = {
|
||||
y: number,
|
||||
|}>,
|
||||
addedRootEvents: boolean,
|
||||
buttons: 0 | 1 | 4,
|
||||
isActivePressed: boolean,
|
||||
isActivePressStart: boolean,
|
||||
isPressed: boolean,
|
||||
@@ -75,24 +76,24 @@ type PressEventType =
|
||||
| 'presschange';
|
||||
|
||||
type PressEvent = {|
|
||||
button: 'primary' | 'auxillary',
|
||||
defaultPrevented: boolean,
|
||||
target: Element | Document,
|
||||
type: PressEventType,
|
||||
pointerType: PointerType,
|
||||
timeStamp: number,
|
||||
altKey: boolean,
|
||||
buttons: 0 | 1 | 4,
|
||||
clientX: null | number,
|
||||
clientY: null | number,
|
||||
ctrlKey: boolean,
|
||||
defaultPrevented: boolean,
|
||||
metaKey: boolean,
|
||||
pageX: null | number,
|
||||
pageY: null | number,
|
||||
pointerType: PointerType,
|
||||
screenX: null | number,
|
||||
screenY: null | number,
|
||||
shiftKey: boolean,
|
||||
target: Element | Document,
|
||||
timeStamp: number,
|
||||
type: PressEventType,
|
||||
x: null | number,
|
||||
y: null | number,
|
||||
altKey: boolean,
|
||||
ctrlKey: boolean,
|
||||
metaKey: boolean,
|
||||
shiftKey: boolean,
|
||||
|};
|
||||
|
||||
const hasPointerEvents =
|
||||
@@ -149,9 +150,9 @@ function createPressEvent(
|
||||
event: ?ReactDOMResponderEvent,
|
||||
touchEvent: null | Touch,
|
||||
defaultPrevented: boolean,
|
||||
state: PressState,
|
||||
): PressEvent {
|
||||
const timeStamp = context.getTimeStamp();
|
||||
let button = 'primary';
|
||||
let clientX = null;
|
||||
let clientY = null;
|
||||
let pageX = null;
|
||||
@@ -173,29 +174,26 @@ function createPressEvent(
|
||||
if (eventObject) {
|
||||
({clientX, clientY, pageX, pageY, screenX, screenY} = eventObject);
|
||||
}
|
||||
if (nativeEvent.button === 1) {
|
||||
button = 'auxillary';
|
||||
}
|
||||
}
|
||||
return {
|
||||
button,
|
||||
defaultPrevented,
|
||||
target,
|
||||
type,
|
||||
pointerType,
|
||||
timeStamp,
|
||||
altKey,
|
||||
buttons: state.buttons,
|
||||
clientX,
|
||||
clientY,
|
||||
ctrlKey,
|
||||
defaultPrevented,
|
||||
metaKey,
|
||||
pageX,
|
||||
pageY,
|
||||
pointerType,
|
||||
screenX,
|
||||
screenY,
|
||||
shiftKey,
|
||||
target,
|
||||
timeStamp,
|
||||
type,
|
||||
x: clientX,
|
||||
y: clientY,
|
||||
altKey,
|
||||
ctrlKey,
|
||||
metaKey,
|
||||
shiftKey,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -221,6 +219,7 @@ function dispatchEvent(
|
||||
event,
|
||||
touchEvent,
|
||||
defaultPrevented,
|
||||
state,
|
||||
);
|
||||
context.dispatchEvent(syntheticEvent, listener, eventPriority);
|
||||
}
|
||||
@@ -488,6 +487,7 @@ const pressResponderImpl = {
|
||||
return {
|
||||
activationPosition: null,
|
||||
addedRootEvents: false,
|
||||
buttons: 0,
|
||||
isActivePressed: false,
|
||||
isActivePressStart: false,
|
||||
isPressed: false,
|
||||
@@ -581,11 +581,12 @@ const pressResponderImpl = {
|
||||
state.activePointerId = touchEvent.identifier;
|
||||
}
|
||||
|
||||
// Ignore any device buttons except primary/auxillary and touch/pen contact.
|
||||
// Ignore any device buttons except primary/middle and touch/pen contact.
|
||||
// Additionally we ignore primary-button + ctrl-key with Macs as that
|
||||
// acts like right-click and opens the contextmenu.
|
||||
if (
|
||||
nativeEvent.button > 1 ||
|
||||
nativeEvent.buttons === 2 ||
|
||||
nativeEvent.buttons > 4 ||
|
||||
(isMac && isMouseEvent && nativeEvent.ctrlKey)
|
||||
) {
|
||||
return;
|
||||
@@ -600,6 +601,7 @@ const pressResponderImpl = {
|
||||
}
|
||||
state.responderRegionOnDeactivation = null;
|
||||
state.isPressWithinResponderRegion = true;
|
||||
state.buttons = nativeEvent.buttons;
|
||||
dispatchPressStartEvents(event, context, props, state);
|
||||
addRootEventTypes(context, state);
|
||||
} else {
|
||||
@@ -703,7 +705,7 @@ const pressResponderImpl = {
|
||||
case 'mouseup':
|
||||
case 'touchend': {
|
||||
if (isPressed) {
|
||||
const button = nativeEvent.button;
|
||||
const buttons = state.buttons;
|
||||
let isKeyboardEvent = false;
|
||||
let touchEvent;
|
||||
if (type === 'pointerup' && activePointerId !== pointerId) {
|
||||
@@ -722,7 +724,7 @@ const pressResponderImpl = {
|
||||
}
|
||||
isKeyboardEvent = true;
|
||||
removeRootEventTypes(context, state);
|
||||
} else if (button === 1) {
|
||||
} else if (buttons === 4) {
|
||||
// Remove the root events here as no 'click' event is dispatched when this 'button' is pressed.
|
||||
removeRootEventTypes(context, state);
|
||||
}
|
||||
@@ -780,7 +782,7 @@ const pressResponderImpl = {
|
||||
}
|
||||
}
|
||||
|
||||
if (state.isPressWithinResponderRegion && button !== 1) {
|
||||
if (state.isPressWithinResponderRegion && buttons !== 4) {
|
||||
dispatchEvent(
|
||||
event,
|
||||
onPress,
|
||||
@@ -848,7 +850,7 @@ export const PressResponder = React.unstable_createResponder(
|
||||
pressResponderImpl,
|
||||
);
|
||||
|
||||
export function usePressResponder(
|
||||
export function usePress(
|
||||
props: PressProps,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
return React.unstable_useResponder(PressResponder, props);
|
||||
|
||||
2
packages/react-events/src/dom/Scroll.js
vendored
2
packages/react-events/src/dom/Scroll.js
vendored
@@ -310,7 +310,7 @@ export const ScrollResponder = React.unstable_createResponder(
|
||||
scrollResponderImpl,
|
||||
);
|
||||
|
||||
export function useScrollResponder(
|
||||
export function useScroll(
|
||||
props: ScrollProps,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
return React.unstable_useResponder(ScrollResponder, props);
|
||||
|
||||
4
packages/react-events/src/dom/Swipe.js
vendored
4
packages/react-events/src/dom/Swipe.js
vendored
@@ -281,8 +281,8 @@ export const SwipeResponder = React.unstable_createResponder(
|
||||
swipeResponderImpl,
|
||||
);
|
||||
|
||||
export function useSwipeListener(
|
||||
export function useSwipe(
|
||||
props: SwipeProps,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
return React.unstable_useListener(SwipeResponder, props);
|
||||
return React.unstable_useResponder(SwipeResponder, props);
|
||||
}
|
||||
|
||||
@@ -9,12 +9,17 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {createEventTarget, platform, setPointerEvent} from '../testing-library';
|
||||
import {
|
||||
buttonsType,
|
||||
createEventTarget,
|
||||
platform,
|
||||
setPointerEvent,
|
||||
} from '../testing-library';
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let useContextMenuResponder;
|
||||
let useContextMenu;
|
||||
|
||||
function initializeModules(hasPointerEvents) {
|
||||
setPointerEvent(hasPointerEvents);
|
||||
@@ -23,8 +28,7 @@ function initializeModules(hasPointerEvents) {
|
||||
ReactFeatureFlags.enableFlareAPI = true;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
useContextMenuResponder = require('react-events/context-menu')
|
||||
.useContextMenuResponder;
|
||||
useContextMenu = require('react-events/context-menu').useContextMenu;
|
||||
}
|
||||
|
||||
const forcePointerEvents = true;
|
||||
@@ -51,7 +55,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
const preventDefault = jest.fn();
|
||||
const ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useContextMenuResponder({onContextMenu});
|
||||
const listener = useContextMenu({onContextMenu});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
@@ -61,7 +65,11 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
expect(preventDefault).toHaveBeenCalledTimes(1);
|
||||
expect(onContextMenu).toHaveBeenCalledTimes(1);
|
||||
expect(onContextMenu).toHaveBeenCalledWith(
|
||||
expect.objectContaining({pointerType: 'mouse', type: 'contextmenu'}),
|
||||
expect.objectContaining({
|
||||
buttons: buttonsType.secondary,
|
||||
pointerType: 'mouse',
|
||||
type: 'contextmenu',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -70,7 +78,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
const preventDefault = jest.fn();
|
||||
const ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useContextMenuResponder({onContextMenu});
|
||||
const listener = useContextMenu({onContextMenu});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
@@ -80,7 +88,11 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
expect(preventDefault).toHaveBeenCalledTimes(1);
|
||||
expect(onContextMenu).toHaveBeenCalledTimes(1);
|
||||
expect(onContextMenu).toHaveBeenCalledWith(
|
||||
expect.objectContaining({pointerType: 'touch', type: 'contextmenu'}),
|
||||
expect.objectContaining({
|
||||
buttons: buttonsType.none,
|
||||
pointerType: 'touch',
|
||||
type: 'contextmenu',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -88,7 +100,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
const onContextMenu = jest.fn();
|
||||
const ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useContextMenuResponder({
|
||||
const listener = useContextMenu({
|
||||
onContextMenu,
|
||||
disabled: true,
|
||||
});
|
||||
@@ -106,7 +118,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
const onContextMenu = jest.fn();
|
||||
const ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useContextMenuResponder({
|
||||
const listener = useContextMenu({
|
||||
onContextMenu,
|
||||
preventDefault: false,
|
||||
});
|
||||
@@ -135,7 +147,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
const onContextMenu = jest.fn();
|
||||
const ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useContextMenuResponder({onContextMenu});
|
||||
const listener = useContextMenu({onContextMenu});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
@@ -144,7 +156,11 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
target.contextmenu({}, {modified: true});
|
||||
expect(onContextMenu).toHaveBeenCalledTimes(1);
|
||||
expect(onContextMenu).toHaveBeenCalledWith(
|
||||
expect.objectContaining({pointerType: 'mouse', type: 'contextmenu'}),
|
||||
expect.objectContaining({
|
||||
buttons: buttonsType.primary,
|
||||
pointerType: 'mouse',
|
||||
type: 'contextmenu',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -163,7 +179,7 @@ describe.each(table)('ContextMenu responder', hasPointerEvents => {
|
||||
const onContextMenu = jest.fn();
|
||||
const ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useContextMenuResponder({onContextMenu});
|
||||
const listener = useContextMenu({onContextMenu});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let useDragResponder;
|
||||
let useDrag;
|
||||
|
||||
describe('Drag event responder', () => {
|
||||
let container;
|
||||
@@ -23,7 +23,7 @@ describe('Drag event responder', () => {
|
||||
ReactFeatureFlags.enableFlareAPI = true;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
useDragResponder = require('react-events/drag').useDragResponder;
|
||||
useDrag = require('react-events/drag').useDrag;
|
||||
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
@@ -44,7 +44,7 @@ describe('Drag event responder', () => {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const listener = useDragResponder({
|
||||
const listener = useDrag({
|
||||
onDragChange: handleOnDrag,
|
||||
});
|
||||
return (
|
||||
@@ -100,7 +100,7 @@ describe('Drag event responder', () => {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const listener = useDragResponder({
|
||||
const listener = useDrag({
|
||||
onDragStart: handleDragStart,
|
||||
onDragEnd: handleDragEnd,
|
||||
});
|
||||
@@ -153,7 +153,7 @@ describe('Drag event responder', () => {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const listener = useDragResponder({
|
||||
const listener = useDrag({
|
||||
onDragMove: handleDragMove,
|
||||
});
|
||||
return (
|
||||
|
||||
@@ -15,7 +15,7 @@ let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let FocusResponder;
|
||||
let useFocusResponder;
|
||||
let useFocus;
|
||||
|
||||
function initializeModules(hasPointerEvents) {
|
||||
setPointerEvent(hasPointerEvents);
|
||||
@@ -25,7 +25,7 @@ function initializeModules(hasPointerEvents) {
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
FocusResponder = require('react-events/focus').FocusResponder;
|
||||
useFocusResponder = require('react-events/focus').useFocusResponder;
|
||||
useFocus = require('react-events/focus').useFocus;
|
||||
}
|
||||
|
||||
const forcePointerEvents = true;
|
||||
@@ -54,7 +54,7 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
onFocus = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useFocusResponder({
|
||||
const listener = useFocus({
|
||||
disabled: true,
|
||||
onBlur,
|
||||
onFocus,
|
||||
@@ -80,7 +80,7 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
onBlur = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useFocusResponder({
|
||||
const listener = useFocus({
|
||||
onBlur,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
@@ -104,7 +104,7 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
ref = React.createRef();
|
||||
innerRef = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useFocusResponder({
|
||||
const listener = useFocus({
|
||||
onFocus,
|
||||
});
|
||||
return (
|
||||
@@ -203,7 +203,7 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
ref = React.createRef();
|
||||
innerRef = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useFocusResponder({
|
||||
const listener = useFocus({
|
||||
onFocusChange,
|
||||
});
|
||||
return (
|
||||
@@ -242,7 +242,7 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
ref = React.createRef();
|
||||
innerRef = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useFocusResponder({
|
||||
const listener = useFocus({
|
||||
onFocusVisibleChange,
|
||||
});
|
||||
return (
|
||||
@@ -315,7 +315,7 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
};
|
||||
|
||||
const Inner = () => {
|
||||
const listener = useFocusResponder({
|
||||
const listener = useFocus({
|
||||
onBlur: createEventHandler('inner: onBlur'),
|
||||
onFocus: createEventHandler('inner: onFocus'),
|
||||
onFocusChange: createEventHandler('inner: onFocusChange'),
|
||||
@@ -324,7 +324,7 @@ describe.each(table)('Focus responder', hasPointerEvents => {
|
||||
};
|
||||
|
||||
const Outer = () => {
|
||||
const listener = useFocusResponder({
|
||||
const listener = useFocus({
|
||||
onBlur: createEventHandler('outer: onBlur'),
|
||||
onFocus: createEventHandler('outer: onFocus'),
|
||||
onFocusChange: createEventHandler('outer: onFocusChange'),
|
||||
|
||||
@@ -15,7 +15,7 @@ let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let FocusWithinResponder;
|
||||
let useFocusWithinResponder;
|
||||
let useFocusWithin;
|
||||
|
||||
const initializeModules = hasPointerEvents => {
|
||||
setPointerEvent(hasPointerEvents);
|
||||
@@ -25,8 +25,7 @@ const initializeModules = hasPointerEvents => {
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
FocusWithinResponder = require('react-events/focus').FocusWithinResponder;
|
||||
useFocusWithinResponder = require('react-events/focus')
|
||||
.useFocusWithinResponder;
|
||||
useFocusWithin = require('react-events/focus').useFocusWithin;
|
||||
};
|
||||
|
||||
const forcePointerEvents = true;
|
||||
@@ -55,7 +54,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
onFocusWithinVisibleChange = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useFocusWithinResponder({
|
||||
const listener = useFocusWithin({
|
||||
disabled: true,
|
||||
onFocusWithinChange,
|
||||
onFocusWithinVisibleChange,
|
||||
@@ -83,7 +82,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
innerRef = React.createRef();
|
||||
innerRef2 = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useFocusWithinResponder({
|
||||
const listener = useFocusWithin({
|
||||
onFocusWithinChange,
|
||||
});
|
||||
return (
|
||||
@@ -151,7 +150,7 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
|
||||
innerRef = React.createRef();
|
||||
innerRef2 = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useFocusWithinResponder({
|
||||
const listener = useFocusWithin({
|
||||
onFocusWithinVisibleChange,
|
||||
});
|
||||
return (
|
||||
|
||||
@@ -15,7 +15,7 @@ let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let HoverResponder;
|
||||
let useHoverResponder;
|
||||
let useHover;
|
||||
|
||||
function initializeModules(hasPointerEvents) {
|
||||
jest.resetModules();
|
||||
@@ -26,7 +26,7 @@ function initializeModules(hasPointerEvents) {
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
HoverResponder = require('react-events/hover').HoverResponder;
|
||||
useHoverResponder = require('react-events/hover').useHoverResponder;
|
||||
useHover = require('react-events/hover').useHover;
|
||||
}
|
||||
|
||||
const forcePointerEvents = true;
|
||||
@@ -57,7 +57,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
onHoverEnd = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useHoverResponder({
|
||||
const listener = useHover({
|
||||
disabled: true,
|
||||
onHoverChange,
|
||||
onHoverStart,
|
||||
@@ -87,7 +87,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
onHoverStart = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useHoverResponder({
|
||||
const listener = useHover({
|
||||
onHoverStart: onHoverStart,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
@@ -124,7 +124,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
onHoverChange = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useHoverResponder({
|
||||
const listener = useHover({
|
||||
onHoverChange,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
@@ -157,7 +157,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
onHoverEnd = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useHoverResponder({
|
||||
const listener = useHover({
|
||||
onHoverEnd,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
@@ -201,7 +201,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
const onHoverMove = jest.fn();
|
||||
const ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useHoverResponder({
|
||||
const listener = useHover({
|
||||
onHoverMove,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
@@ -229,7 +229,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
};
|
||||
|
||||
const Inner = () => {
|
||||
const listener = useHoverResponder({
|
||||
const listener = useHover({
|
||||
onHoverStart: createEventHandler('inner: onHoverStart'),
|
||||
onHoverEnd: createEventHandler('inner: onHoverEnd'),
|
||||
onHoverChange: createEventHandler('inner: onHoverChange'),
|
||||
@@ -238,7 +238,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
};
|
||||
|
||||
const Outer = () => {
|
||||
const listener = useHoverResponder({
|
||||
const listener = useHover({
|
||||
onHoverStart: createEventHandler('outer: onHoverStart'),
|
||||
onHoverEnd: createEventHandler('outer: onHoverEnd'),
|
||||
onHoverChange: createEventHandler('outer: onHoverChange'),
|
||||
@@ -305,7 +305,7 @@ describe.each(table)('Hover responder', hasPointerEvents => {
|
||||
eventLog.push(propertiesWeCareAbout);
|
||||
};
|
||||
const Component = () => {
|
||||
const listener = useHoverResponder({
|
||||
const listener = useHover({
|
||||
onHoverStart: logEvent,
|
||||
onHoverEnd: logEvent,
|
||||
onHoverMove: logEvent,
|
||||
|
||||
@@ -13,8 +13,8 @@ let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let InputResponder;
|
||||
let useInputResponder;
|
||||
let usePressResponder;
|
||||
let useInput;
|
||||
let usePress;
|
||||
let Scheduler;
|
||||
|
||||
const setUntrackedChecked = Object.getOwnPropertyDescriptor(
|
||||
@@ -41,8 +41,8 @@ const modulesInit = () => {
|
||||
ReactDOM = require('react-dom');
|
||||
Scheduler = require('scheduler');
|
||||
InputResponder = require('react-events/input').InputResponder;
|
||||
useInputResponder = require('react-events/input').useInputResponder;
|
||||
usePressResponder = require('react-events/press').usePressResponder;
|
||||
useInput = require('react-events/input').useInput;
|
||||
usePress = require('react-events/press').usePress;
|
||||
};
|
||||
|
||||
describe('Input event responder', () => {
|
||||
@@ -70,7 +70,7 @@ describe('Input event responder', () => {
|
||||
ref = React.createRef();
|
||||
|
||||
function Component() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
disabled: true,
|
||||
onChange,
|
||||
onValueChange,
|
||||
@@ -117,7 +117,7 @@ describe('Input event responder', () => {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
onValueChange,
|
||||
});
|
||||
@@ -157,7 +157,7 @@ describe('Input event responder', () => {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
onValueChange,
|
||||
});
|
||||
@@ -200,7 +200,7 @@ describe('Input event responder', () => {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
onValueChange,
|
||||
});
|
||||
@@ -242,7 +242,7 @@ describe('Input event responder', () => {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
onValueChange,
|
||||
});
|
||||
@@ -283,7 +283,7 @@ describe('Input event responder', () => {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
onValueChange,
|
||||
});
|
||||
@@ -345,7 +345,7 @@ describe('Input event responder', () => {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
onValueChange,
|
||||
});
|
||||
@@ -385,7 +385,7 @@ describe('Input event responder', () => {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
onValueChange,
|
||||
});
|
||||
@@ -438,7 +438,7 @@ describe('Input event responder', () => {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
onValueChange,
|
||||
});
|
||||
@@ -483,7 +483,7 @@ describe('Input event responder', () => {
|
||||
}
|
||||
|
||||
function Radio1() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange: onChange1,
|
||||
onValueChange: onValueChange1,
|
||||
});
|
||||
@@ -491,7 +491,7 @@ describe('Input event responder', () => {
|
||||
}
|
||||
|
||||
function Radio2() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange: onChange2,
|
||||
onValueChange: onValueChange2,
|
||||
});
|
||||
@@ -558,7 +558,7 @@ describe('Input event responder', () => {
|
||||
onChangeCalled = 0;
|
||||
onValueChangeCalled = 0;
|
||||
function Component() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
onValueChange,
|
||||
});
|
||||
@@ -586,7 +586,7 @@ describe('Input event responder', () => {
|
||||
onChangeCalled = 0;
|
||||
onValueChangeCalled = 0;
|
||||
function Component2() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
onValueChange,
|
||||
});
|
||||
@@ -612,7 +612,7 @@ describe('Input event responder', () => {
|
||||
onChangeCalled = 0;
|
||||
onValueChangeCalled = 0;
|
||||
function Component3() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
onValueChange,
|
||||
});
|
||||
@@ -652,7 +652,7 @@ describe('Input event responder', () => {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
onValueChange,
|
||||
});
|
||||
@@ -689,7 +689,7 @@ describe('Input event responder', () => {
|
||||
}
|
||||
|
||||
function Component() {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
onValueChange,
|
||||
});
|
||||
@@ -761,7 +761,7 @@ describe('Input event responder', () => {
|
||||
let ops = [];
|
||||
|
||||
function Component({innerRef, onChange, controlledValue}) {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
});
|
||||
return (
|
||||
@@ -821,7 +821,7 @@ describe('Input event responder', () => {
|
||||
let ops = [];
|
||||
|
||||
function Component({innerRef, onChange, controlledValue}) {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
});
|
||||
return (
|
||||
@@ -896,7 +896,7 @@ describe('Input event responder', () => {
|
||||
let ops = [];
|
||||
|
||||
function Component({innerRef, onChange, controlledValue}) {
|
||||
const listener = useInputResponder({
|
||||
const listener = useInput({
|
||||
onChange,
|
||||
});
|
||||
return (
|
||||
@@ -961,7 +961,7 @@ describe('Input event responder', () => {
|
||||
controlledValue,
|
||||
pressListener,
|
||||
}) {
|
||||
const inputListener = useInputResponder({
|
||||
const inputListener = useInput({
|
||||
onChange,
|
||||
});
|
||||
return (
|
||||
@@ -975,7 +975,7 @@ describe('Input event responder', () => {
|
||||
}
|
||||
|
||||
function PressWrapper({innerRef, onPress, onChange, controlledValue}) {
|
||||
const pressListener = usePressResponder({
|
||||
const pressListener = usePress({
|
||||
onPress,
|
||||
});
|
||||
return (
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let useKeyboardResponder;
|
||||
let useKeyboard;
|
||||
|
||||
import {createEventTarget} from '../testing-library';
|
||||
|
||||
@@ -22,7 +22,7 @@ function initializeModules(hasPointerEvents) {
|
||||
ReactFeatureFlags.enableFlareAPI = true;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
useKeyboardResponder = require('react-events/keyboard').useKeyboardResponder;
|
||||
useKeyboard = require('react-events/keyboard').useKeyboard;
|
||||
}
|
||||
|
||||
describe('Keyboard event responder', () => {
|
||||
@@ -48,7 +48,7 @@ describe('Keyboard event responder', () => {
|
||||
onKeyUp = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useKeyboardResponder({
|
||||
const listener = useKeyboard({
|
||||
disabled: true,
|
||||
onKeyDown,
|
||||
onKeyUp,
|
||||
@@ -74,7 +74,7 @@ describe('Keyboard event responder', () => {
|
||||
onKeyDown = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useKeyboardResponder({
|
||||
const listener = useKeyboard({
|
||||
onKeyDown,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
@@ -100,7 +100,7 @@ describe('Keyboard event responder', () => {
|
||||
onKeyUp = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useKeyboardResponder({
|
||||
const listener = useKeyboard({
|
||||
onKeyDown,
|
||||
onKeyUp,
|
||||
});
|
||||
|
||||
@@ -9,13 +9,17 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {createEventTarget, setPointerEvent} from '../testing-library';
|
||||
import {
|
||||
buttonsType,
|
||||
createEventTarget,
|
||||
setPointerEvent,
|
||||
} from '../testing-library';
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let PressResponder;
|
||||
let usePressResponder;
|
||||
let usePress;
|
||||
|
||||
function initializeModules(hasPointerEvents) {
|
||||
jest.resetModules();
|
||||
@@ -25,7 +29,7 @@ function initializeModules(hasPointerEvents) {
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
PressResponder = require('react-events/press').PressResponder;
|
||||
usePressResponder = require('react-events/press').usePressResponder;
|
||||
usePress = require('react-events/press').usePress;
|
||||
}
|
||||
|
||||
function removePressMoveStrings(eventString) {
|
||||
@@ -64,7 +68,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
onPressEnd = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({
|
||||
const listener = usePress({
|
||||
disabled: true,
|
||||
onPressStart,
|
||||
onPress,
|
||||
@@ -93,7 +97,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
onPressStart = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({
|
||||
const listener = usePress({
|
||||
onPressStart,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
@@ -114,36 +118,36 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
},
|
||||
);
|
||||
|
||||
it('is called after auxillary-button pointer down', () => {
|
||||
it('is called after middle-button pointer down', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({button: 1, pointerType: 'mouse'});
|
||||
target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
|
||||
expect(onPressStart).toHaveBeenCalledTimes(1);
|
||||
expect(onPressStart).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
button: 'auxillary',
|
||||
buttons: buttonsType.middle,
|
||||
pointerType: 'mouse',
|
||||
type: 'pressstart',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('is not called after pointer move following auxillary-button press', () => {
|
||||
it('is not called after pointer move following middle-button press', () => {
|
||||
const node = ref.current;
|
||||
const target = createEventTarget(node);
|
||||
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
|
||||
target.pointerdown({button: 1, pointerType: 'mouse'});
|
||||
target.pointerup({button: 1, pointerType: 'mouse'});
|
||||
target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
|
||||
target.pointerup({pointerType: 'mouse'});
|
||||
target.pointerhover({x: 110, y: 110});
|
||||
target.pointerhover({x: 50, y: 50});
|
||||
expect(onPressStart).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('ignores any events not caused by primary/auxillary-click or touch/pen contact', () => {
|
||||
it('ignores any events not caused by primary/middle-click or touch/pen contact', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({button: 2});
|
||||
target.pointerup({button: 2});
|
||||
target.pointerdown({button: 5});
|
||||
target.pointerup({button: 5});
|
||||
target.pointerdown({buttons: buttonsType.secondary});
|
||||
target.pointerup({buttons: buttonsType.secondary});
|
||||
target.pointerdown({buttons: buttonsType.eraser});
|
||||
target.pointerup({buttons: buttonsType.eraser});
|
||||
expect(onPressStart).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
@@ -187,7 +191,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
onPressEnd = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({
|
||||
const listener = usePress({
|
||||
onPressEnd,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
@@ -209,14 +213,14 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
},
|
||||
);
|
||||
|
||||
it('is called after auxillary-button pointer up', () => {
|
||||
it('is called after middle-button pointer up', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({button: 1, pointerType: 'mouse'});
|
||||
target.pointerup({button: 1, pointerType: 'mouse'});
|
||||
target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
|
||||
target.pointerup({pointerType: 'mouse'});
|
||||
expect(onPressEnd).toHaveBeenCalledTimes(1);
|
||||
expect(onPressEnd).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
button: 'auxillary',
|
||||
buttons: buttonsType.middle,
|
||||
pointerType: 'mouse',
|
||||
type: 'pressend',
|
||||
}),
|
||||
@@ -282,7 +286,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
onPressChange = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({
|
||||
const listener = usePress({
|
||||
onPressChange,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
@@ -322,7 +326,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
onPress = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({
|
||||
const listener = usePress({
|
||||
onPress,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
@@ -350,10 +354,10 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
},
|
||||
);
|
||||
|
||||
it('is not called after auxillary-button press', () => {
|
||||
it('is not called after middle-button press', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({button: 1, pointerType: 'mouse'});
|
||||
target.pointerup({button: 1, pointerType: 'mouse'});
|
||||
target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
|
||||
target.pointerup({pointerType: 'mouse'});
|
||||
expect(onPress).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -370,7 +374,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
it('is not called after invalid "keyup" event', () => {
|
||||
const inputRef = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({onPress});
|
||||
const listener = usePress({onPress});
|
||||
return <input ref={inputRef} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
@@ -400,7 +404,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const divRef = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({onPress});
|
||||
const listener = usePress({onPress});
|
||||
return (
|
||||
<div ref={divRef} listeners={listener}>
|
||||
<button ref={buttonRef} />
|
||||
@@ -425,7 +429,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
onPressMove = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({
|
||||
const listener = usePress({
|
||||
onPressMove,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
@@ -460,7 +464,12 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
|
||||
target.keydown({key: 'Enter'});
|
||||
target.pointermove({button: -1, pointerType: 'mouse', x: 10, y: 10});
|
||||
target.pointermove({
|
||||
buttons: buttonsType.none,
|
||||
pointerType: 'mouse',
|
||||
x: 10,
|
||||
y: 10,
|
||||
});
|
||||
expect(onPressMove).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -476,7 +485,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
events.push(msg);
|
||||
};
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({
|
||||
const listener = usePress({
|
||||
onPress: createEventHandler('onPress'),
|
||||
onPressChange: createEventHandler('onPressChange'),
|
||||
onPressMove: createEventHandler('onPressMove'),
|
||||
@@ -566,7 +575,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const pressRetentionOffset = {top: 40, bottom: 40, left: 40, right: 40};
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({
|
||||
const listener = usePress({
|
||||
onPress: createEventHandler('onPress'),
|
||||
onPressChange: createEventHandler('onPressChange'),
|
||||
onPressMove: createEventHandler('onPressMove'),
|
||||
@@ -710,7 +719,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
};
|
||||
|
||||
const Inner = () => {
|
||||
const listener = usePressResponder({
|
||||
const listener = usePress({
|
||||
onPress: createEventHandler('inner: onPress'),
|
||||
onPressChange: createEventHandler('inner: onPressChange'),
|
||||
onPressMove: createEventHandler('inner: onPressMove'),
|
||||
@@ -731,7 +740,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
};
|
||||
|
||||
const Outer = () => {
|
||||
const listener = usePressResponder({
|
||||
const listener = usePress({
|
||||
onPress: createEventHandler('outer: onPress'),
|
||||
onPressChange: createEventHandler('outer: onPressChange'),
|
||||
onPressMove: createEventHandler('outer: onPressMove'),
|
||||
@@ -768,12 +777,12 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const onPress = jest.fn();
|
||||
|
||||
const Inner = () => {
|
||||
const listener = usePressResponder({onPress});
|
||||
const listener = usePress({onPress});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
};
|
||||
|
||||
const Outer = () => {
|
||||
const listener = usePressResponder({onPress});
|
||||
const listener = usePress({onPress});
|
||||
return (
|
||||
<div listeners={listener}>
|
||||
<Inner />
|
||||
@@ -795,12 +804,12 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const onPressEnd = jest.fn();
|
||||
|
||||
const Inner = () => {
|
||||
const listener = usePressResponder({onPressStart, onPressEnd});
|
||||
const listener = usePress({onPressStart, onPressEnd});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
};
|
||||
|
||||
const Outer = () => {
|
||||
const listener = usePressResponder({onPressStart, onPressEnd});
|
||||
const listener = usePress({onPressStart, onPressEnd});
|
||||
return (
|
||||
<div listeners={listener}>
|
||||
<Inner />
|
||||
@@ -823,12 +832,12 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const onPressChange = jest.fn();
|
||||
|
||||
const Inner = () => {
|
||||
const listener = usePressResponder({onPressChange});
|
||||
const listener = usePress({onPressChange});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
};
|
||||
|
||||
const Outer = () => {
|
||||
const listener = usePressResponder({onPressChange});
|
||||
const listener = usePress({onPressChange});
|
||||
return (
|
||||
<div listeners={listener}>
|
||||
<Inner />
|
||||
@@ -853,7 +862,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const ref = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({onPress});
|
||||
const listener = usePress({onPress});
|
||||
return <a href="#" ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
@@ -873,7 +882,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const ref = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({onPress});
|
||||
const listener = usePress({onPress});
|
||||
return <a href="#" ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
@@ -894,7 +903,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const buttonRef = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({onPress});
|
||||
const listener = usePress({onPress});
|
||||
return (
|
||||
<a href="#">
|
||||
<button ref={buttonRef} listeners={listener} />
|
||||
@@ -915,7 +924,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const ref = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({onPress});
|
||||
const listener = usePress({onPress});
|
||||
return (
|
||||
<a href="#" listeners={listener}>
|
||||
<div ref={ref} />
|
||||
@@ -939,7 +948,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const ref = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({onPress});
|
||||
const listener = usePress({onPress});
|
||||
return <a href="#" ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
@@ -961,7 +970,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const ref = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({onPress, preventDefault: false});
|
||||
const listener = usePress({onPress, preventDefault: false});
|
||||
return <a href="#" ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
@@ -981,7 +990,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const ref = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({onPress, preventDefault: false});
|
||||
const listener = usePress({onPress, preventDefault: false});
|
||||
return <a href="#" ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
@@ -1003,7 +1012,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const ref = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({onPressEnd});
|
||||
const listener = usePress({onPressEnd});
|
||||
return <a href="#" ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
@@ -1020,7 +1029,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const ref = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({onPressEnd});
|
||||
const listener = usePress({onPressEnd});
|
||||
return <a href="#" ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
@@ -1038,7 +1047,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const containerRef = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({onPressEnd});
|
||||
const listener = usePress({onPressEnd});
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<a ref={ref} listeners={listener} />
|
||||
@@ -1060,7 +1069,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const outsideRef = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({onPressEnd});
|
||||
const listener = usePress({onPressEnd});
|
||||
return (
|
||||
<div>
|
||||
<a ref={ref} listeners={listener} />
|
||||
@@ -1085,7 +1094,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const ref = React.createRef();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder();
|
||||
const listener = usePress();
|
||||
return <button ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
@@ -1119,7 +1128,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
};
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({
|
||||
const listener = usePress({
|
||||
onPressStart: logEvent,
|
||||
onPressEnd: logEvent,
|
||||
onPressMove: logEvent,
|
||||
@@ -1239,7 +1248,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
updateCounter(count => count + 1);
|
||||
}
|
||||
|
||||
const listener = usePressResponder({
|
||||
const listener = usePress({
|
||||
onPress: handlePress,
|
||||
});
|
||||
|
||||
@@ -1307,7 +1316,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
updateCounter(count => count + 1);
|
||||
}
|
||||
|
||||
const listener = usePressResponder({
|
||||
const listener = usePress({
|
||||
onPress: handlePress,
|
||||
});
|
||||
|
||||
@@ -1389,7 +1398,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
updatePressesCount(pressesCount + 1);
|
||||
}
|
||||
|
||||
const listener = usePressResponder({
|
||||
const listener = usePress({
|
||||
onPress: handlePress,
|
||||
});
|
||||
|
||||
@@ -1430,7 +1439,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const pointerDownEvent = jest.fn();
|
||||
|
||||
const Component = () => {
|
||||
const listener = usePressResponder({stopPropagation: true});
|
||||
const listener = usePress({stopPropagation: true});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import {createEventTarget, setPointerEvent} from '../testing-library';
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let useScrollResponder;
|
||||
let useScroll;
|
||||
|
||||
const forcePointerEvents = true;
|
||||
const table = [[forcePointerEvents], [!forcePointerEvents]];
|
||||
@@ -26,7 +26,7 @@ const initializeModules = hasPointerEvents => {
|
||||
ReactFeatureFlags.enableFlareAPI = true;
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
useScrollResponder = require('react-events/scroll').useScrollResponder;
|
||||
useScroll = require('react-events/scroll').useScroll;
|
||||
};
|
||||
|
||||
describe.each(table)('Scroll responder', hasPointerEvents => {
|
||||
@@ -51,7 +51,7 @@ describe.each(table)('Scroll responder', hasPointerEvents => {
|
||||
onScroll = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useScrollResponder({
|
||||
const listener = useScroll({
|
||||
disabled: true,
|
||||
onScroll,
|
||||
});
|
||||
@@ -74,7 +74,7 @@ describe.each(table)('Scroll responder', hasPointerEvents => {
|
||||
onScroll = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useScrollResponder({
|
||||
const listener = useScroll({
|
||||
onScroll,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
@@ -145,7 +145,7 @@ describe.each(table)('Scroll responder', hasPointerEvents => {
|
||||
onScrollDragStart = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useScrollResponder({
|
||||
const listener = useScroll({
|
||||
onScrollDragStart,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
@@ -175,7 +175,7 @@ describe.each(table)('Scroll responder', hasPointerEvents => {
|
||||
onScrollDragEnd = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useScrollResponder({
|
||||
const listener = useScroll({
|
||||
onScrollDragEnd,
|
||||
});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Change environment support for PointerEvent.
|
||||
*/
|
||||
@@ -49,3 +50,26 @@ export const platform = {
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Buttons bitmask
|
||||
*/
|
||||
|
||||
export const buttonsType = {
|
||||
none: 0,
|
||||
// left-mouse
|
||||
// touch contact
|
||||
// pen contact
|
||||
primary: 1,
|
||||
// right-mouse
|
||||
// pen barrel button
|
||||
secondary: 2,
|
||||
// middle mouse
|
||||
middle: 4,
|
||||
// back mouse
|
||||
back: 8,
|
||||
// forward mouse
|
||||
forward: 16,
|
||||
// pen eraser
|
||||
eraser: 32,
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
'use strict';
|
||||
|
||||
import * as domEvents from './domEvents';
|
||||
import {hasPointerEvent, platform} from './domEnvironment';
|
||||
import {buttonsType, hasPointerEvent, platform} from './domEnvironment';
|
||||
|
||||
function emptyFunction() {}
|
||||
|
||||
@@ -29,30 +29,33 @@ export function contextmenu(
|
||||
) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
if (pointerType === 'touch') {
|
||||
const button = 0;
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerdown({button, pointerType}));
|
||||
dispatch(
|
||||
domEvents.pointerdown({buttons: buttonsType.primary, pointerType}),
|
||||
);
|
||||
}
|
||||
dispatch(domEvents.touchstart());
|
||||
dispatch(domEvents.contextmenu({button, preventDefault}));
|
||||
dispatch(
|
||||
domEvents.contextmenu({buttons: buttonsType.none, preventDefault}),
|
||||
);
|
||||
} else if (pointerType === 'mouse') {
|
||||
if (modified === true) {
|
||||
const button = 0;
|
||||
const buttons = buttonsType.primary;
|
||||
const ctrlKey = true;
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerdown({button, ctrlKey, pointerType}));
|
||||
dispatch(domEvents.pointerdown({buttons, ctrlKey, pointerType}));
|
||||
}
|
||||
dispatch(domEvents.mousedown({button, ctrlKey}));
|
||||
dispatch(domEvents.mousedown({buttons, ctrlKey}));
|
||||
if (platform.get() === 'mac') {
|
||||
dispatch(domEvents.contextmenu({button, ctrlKey, preventDefault}));
|
||||
dispatch(domEvents.contextmenu({buttons, ctrlKey, preventDefault}));
|
||||
}
|
||||
} else {
|
||||
const button = 2;
|
||||
const buttons = buttonsType.secondary;
|
||||
if (hasPointerEvent()) {
|
||||
dispatch(domEvents.pointerdown({button, pointerType}));
|
||||
dispatch(domEvents.pointerdown({buttons, pointerType}));
|
||||
}
|
||||
dispatch(domEvents.mousedown({button}));
|
||||
dispatch(domEvents.contextmenu({button, preventDefault}));
|
||||
dispatch(domEvents.mousedown({buttons}));
|
||||
dispatch(domEvents.contextmenu({buttons, preventDefault}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,7 +77,7 @@ export function pointercancel(target, payload) {
|
||||
export function pointerdown(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(defaultPayload);
|
||||
const payload = {button: 0, buttons: 2, ...defaultPayload};
|
||||
const payload = {buttons: buttonsType.primary, ...defaultPayload};
|
||||
|
||||
if (pointerType === 'mouse') {
|
||||
if (hasPointerEvent()) {
|
||||
@@ -147,7 +150,7 @@ export function pointermove(target, payload) {
|
||||
export function pointerup(target, defaultPayload) {
|
||||
const dispatch = arg => target.dispatchEvent(arg);
|
||||
const pointerType = getPointerType(defaultPayload);
|
||||
const payload = {button: 0, buttons: 2, ...defaultPayload};
|
||||
const payload = {buttons: buttonsType.none, ...defaultPayload};
|
||||
|
||||
if (pointerType === 'mouse') {
|
||||
if (hasPointerEvent()) {
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import {buttonsType} from './domEnvironment';
|
||||
|
||||
/**
|
||||
* Native event object mocks for higher-level events.
|
||||
*
|
||||
@@ -24,6 +26,9 @@
|
||||
* 3. PointerEvent and TouchEvent fields are normalized (e.g., 'rotationAngle' -> 'twist')
|
||||
*/
|
||||
|
||||
const defaultPointerSize = 23;
|
||||
const defaultBrowserChromeSize = 50;
|
||||
|
||||
function emptyFunction() {}
|
||||
|
||||
function createEvent(type, data = {}) {
|
||||
@@ -57,8 +62,7 @@ function createPointerEvent(
|
||||
type,
|
||||
{
|
||||
altKey = false,
|
||||
button = -1,
|
||||
buttons = 0,
|
||||
buttons = buttonsType.none,
|
||||
ctrlKey = false,
|
||||
height,
|
||||
metaKey = false,
|
||||
@@ -86,7 +90,6 @@ function createPointerEvent(
|
||||
|
||||
return createEvent(type, {
|
||||
altKey,
|
||||
button,
|
||||
buttons,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
@@ -94,7 +97,12 @@ function createPointerEvent(
|
||||
getModifierState(keyArg) {
|
||||
createGetModifierState(keyArg, modifierState);
|
||||
},
|
||||
height: pointerType === 'mouse' ? 1 : height != null ? height : 11.5,
|
||||
height:
|
||||
pointerType === 'mouse'
|
||||
? 1
|
||||
: height != null
|
||||
? height
|
||||
: defaultPointerSize,
|
||||
metaKey,
|
||||
movementX,
|
||||
movementY,
|
||||
@@ -107,13 +115,14 @@ function createPointerEvent(
|
||||
pressure,
|
||||
preventDefault,
|
||||
screenX: x,
|
||||
screenY: y + 50, // arbitrary value to emulate browser chrome, etc
|
||||
screenY: y + defaultBrowserChromeSize,
|
||||
shiftKey,
|
||||
tangentialPressure,
|
||||
tiltX,
|
||||
tiltY,
|
||||
twist,
|
||||
width: pointerType === 'mouse' ? 1 : width != null ? width : 11.5,
|
||||
width:
|
||||
pointerType === 'mouse' ? 1 : width != null ? width : defaultPointerSize,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -147,8 +156,7 @@ function createMouseEvent(
|
||||
type,
|
||||
{
|
||||
altKey = false,
|
||||
button = -1,
|
||||
buttons = 0,
|
||||
buttons = buttonsType.none,
|
||||
ctrlKey = false,
|
||||
metaKey = false,
|
||||
movementX = 0,
|
||||
@@ -167,7 +175,6 @@ function createMouseEvent(
|
||||
|
||||
return createEvent(type, {
|
||||
altKey,
|
||||
button,
|
||||
buttons,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
@@ -184,7 +191,7 @@ function createMouseEvent(
|
||||
pageY: pageY || y,
|
||||
preventDefault,
|
||||
screenX: x,
|
||||
screenY: y + 50, // arbitrary value to emulate browser chrome, etc
|
||||
screenY: y + defaultBrowserChromeSize,
|
||||
shiftKey,
|
||||
});
|
||||
}
|
||||
@@ -194,7 +201,7 @@ function createTouchEvent(
|
||||
{
|
||||
altKey = false,
|
||||
ctrlKey = false,
|
||||
height = 11.5,
|
||||
height = defaultPointerSize,
|
||||
metaKey = false,
|
||||
pageX,
|
||||
pageY,
|
||||
@@ -202,7 +209,7 @@ function createTouchEvent(
|
||||
preventDefault = emptyFunction,
|
||||
shiftKey = false,
|
||||
twist = 0,
|
||||
width = 11.5,
|
||||
width = defaultPointerSize,
|
||||
x = 0,
|
||||
y = 0,
|
||||
} = {},
|
||||
@@ -214,13 +221,15 @@ function createTouchEvent(
|
||||
identifier: pointerId,
|
||||
pageX: pageX || x,
|
||||
pageY: pageY || y,
|
||||
radiusX: width,
|
||||
radiusY: height,
|
||||
radiusX: width / 2,
|
||||
radiusY: height / 2,
|
||||
rotationAngle: twist,
|
||||
screenX: x,
|
||||
screenY: y + 50, // arbitrary value to emulate browser chrome, etc
|
||||
screenY: y + defaultBrowserChromeSize,
|
||||
};
|
||||
|
||||
const activeTouch = type !== 'touchend' ? [touch] : null;
|
||||
|
||||
return createEvent(type, {
|
||||
altKey,
|
||||
changedTouches: [touch],
|
||||
@@ -228,8 +237,8 @@ function createTouchEvent(
|
||||
metaKey,
|
||||
preventDefault,
|
||||
shiftKey,
|
||||
targetTouches: type !== 'touchend' ? [touch] : null,
|
||||
touches: [touch],
|
||||
targetTouches: activeTouch,
|
||||
touches: activeTouch,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -290,7 +299,12 @@ export function pointercancel(payload) {
|
||||
}
|
||||
|
||||
export function pointerdown(payload) {
|
||||
return createPointerEvent('pointerdown', {button: 0, buttons: 2, ...payload});
|
||||
const isTouch = payload != null && payload.pointerType === 'mouse';
|
||||
return createPointerEvent('pointerdown', {
|
||||
buttons: buttonsType.primary,
|
||||
pressure: isTouch ? 1 : 0.5,
|
||||
...payload,
|
||||
});
|
||||
}
|
||||
|
||||
export function pointerenter(payload) {
|
||||
@@ -314,7 +328,10 @@ export function pointerover(payload) {
|
||||
}
|
||||
|
||||
export function pointerup(payload) {
|
||||
return createPointerEvent('pointerup', {button: 0, buttons: 2, ...payload});
|
||||
return createPointerEvent('pointerup', {
|
||||
...payload,
|
||||
buttons: buttonsType.none,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,7 +339,10 @@ export function pointerup(payload) {
|
||||
*/
|
||||
|
||||
export function mousedown(payload) {
|
||||
return createMouseEvent('mousedown', {button: 0, buttons: 2, ...payload});
|
||||
return createMouseEvent('mousedown', {
|
||||
buttons: buttonsType.primary,
|
||||
...payload,
|
||||
});
|
||||
}
|
||||
|
||||
export function mouseenter(payload) {
|
||||
@@ -346,7 +366,10 @@ export function mouseover(payload) {
|
||||
}
|
||||
|
||||
export function mouseup(payload) {
|
||||
return createMouseEvent('mouseup', {button: 0, buttons: 2, ...payload});
|
||||
return createMouseEvent('mouseup', {
|
||||
...payload,
|
||||
buttons: buttonsType.none,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,12 @@
|
||||
|
||||
import * as domEvents from './domEvents';
|
||||
import * as domEventSequences from './domEventSequences';
|
||||
import {hasPointerEvent, setPointerEvent, platform} from './domEnvironment';
|
||||
import {
|
||||
buttonsType,
|
||||
hasPointerEvent,
|
||||
setPointerEvent,
|
||||
platform,
|
||||
} from './domEnvironment';
|
||||
|
||||
const createEventTarget = node => ({
|
||||
node,
|
||||
@@ -101,4 +106,10 @@ const createEventTarget = node => ({
|
||||
},
|
||||
});
|
||||
|
||||
export {createEventTarget, platform, hasPointerEvent, setPointerEvent};
|
||||
export {
|
||||
buttonsType,
|
||||
createEventTarget,
|
||||
platform,
|
||||
hasPointerEvent,
|
||||
setPointerEvent,
|
||||
};
|
||||
|
||||
2
packages/react-events/src/rn/Press.js
vendored
2
packages/react-events/src/rn/Press.js
vendored
@@ -533,7 +533,7 @@ export const PressResponder = React.unstable_createResponder(
|
||||
pressResponderImpl,
|
||||
);
|
||||
|
||||
export function usePressResponder(
|
||||
export function usePress(
|
||||
props: PressProps,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
return React.unstable_useResponder(PressResponder, props);
|
||||
|
||||
@@ -449,7 +449,6 @@ export function mountResponderInstance(
|
||||
props: Object,
|
||||
state: Object,
|
||||
instance: Instance,
|
||||
rootContainerInstance: Container,
|
||||
) {
|
||||
if (enableFlareAPI) {
|
||||
const {rootEventTypes} = responder;
|
||||
|
||||
@@ -501,7 +501,6 @@ export function mountResponderInstance(
|
||||
props: Object,
|
||||
state: Object,
|
||||
instance: Instance,
|
||||
rootContainerInstance: Container,
|
||||
) {
|
||||
throw new Error('Not yet implemented.');
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
NoEffect,
|
||||
PerformedWork,
|
||||
Placement,
|
||||
Hydrating,
|
||||
ContentReset,
|
||||
DidCapture,
|
||||
Update,
|
||||
@@ -944,11 +945,10 @@ function updateHostRoot(current, workInProgress, renderExpirationTime) {
|
||||
// be any children to hydrate which is effectively the same thing as
|
||||
// not hydrating.
|
||||
|
||||
// This is a bit of a hack. We track the host root as a placement to
|
||||
// know that we're currently in a mounting state. That way isMounted
|
||||
// works as expected. We must reset this before committing.
|
||||
// TODO: Delete this when we delete isMounted and findDOMNode.
|
||||
workInProgress.effectTag |= Placement;
|
||||
// Mark the host root with a Hydrating effect to know that we're
|
||||
// currently in a mounting state. That way isMounted, findDOMNode and
|
||||
// event replaying works as expected.
|
||||
workInProgress.effectTag |= Hydrating;
|
||||
|
||||
// Ensure that children mount into this root without tracking
|
||||
// side-effects. This ensures that we don't store Placement effects on
|
||||
@@ -2095,12 +2095,24 @@ function updateDehydratedSuspenseComponent(
|
||||
);
|
||||
const nextProps = workInProgress.pendingProps;
|
||||
const nextChildren = nextProps.children;
|
||||
workInProgress.child = mountChildFibers(
|
||||
const child = mountChildFibers(
|
||||
workInProgress,
|
||||
null,
|
||||
nextChildren,
|
||||
renderExpirationTime,
|
||||
);
|
||||
let 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.effectTag |= Hydrating;
|
||||
node = node.sibling;
|
||||
}
|
||||
workInProgress.child = child;
|
||||
return workInProgress.child;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ import {
|
||||
} from './ReactHookEffectTags';
|
||||
import {didWarnAboutReassigningProps} from './ReactFiberBeginWork';
|
||||
import {runWithPriority, NormalPriority} from './SchedulerWithReactIntegration';
|
||||
import {updateEventListeners} from './ReactFiberEvents';
|
||||
|
||||
let didWarnAboutUndefinedSnapshotBeforeUpdate: Set<mixed> | null = null;
|
||||
if (__DEV__) {
|
||||
@@ -1331,6 +1332,13 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
|
||||
finishedWork,
|
||||
);
|
||||
}
|
||||
if (enableFlareAPI) {
|
||||
const prevListeners = oldProps.listeners;
|
||||
const nextListeners = newProps.listeners;
|
||||
if (prevListeners !== nextListeners) {
|
||||
updateEventListeners(nextListeners, instance, finishedWork);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,12 +9,7 @@
|
||||
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {ExpirationTime} from './ReactFiberExpirationTime';
|
||||
import type {
|
||||
ReactEventResponder,
|
||||
ReactEventResponderInstance,
|
||||
ReactFundamentalComponentInstance,
|
||||
ReactEventResponderListener,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {ReactFundamentalComponentInstance} from 'shared/ReactTypes';
|
||||
import type {FiberRoot} from './ReactFiberRoot';
|
||||
import type {
|
||||
Instance,
|
||||
@@ -31,7 +26,6 @@ import type {SuspenseContext} from './ReactFiberSuspenseContext';
|
||||
|
||||
import {now} from './SchedulerWithReactIntegration';
|
||||
|
||||
import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols';
|
||||
import {
|
||||
IndeterminateComponent,
|
||||
FunctionComponent,
|
||||
@@ -56,7 +50,6 @@ import {
|
||||
} from 'shared/ReactWorkTags';
|
||||
import {NoMode, BatchedMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
Placement,
|
||||
Ref,
|
||||
Update,
|
||||
NoEffect,
|
||||
@@ -79,8 +72,6 @@ import {
|
||||
createContainerChildSet,
|
||||
appendChildToContainerChildSet,
|
||||
finalizeContainerChildren,
|
||||
mountResponderInstance,
|
||||
unmountResponderInstance,
|
||||
getFundamentalComponentInstance,
|
||||
mountFundamentalComponent,
|
||||
cloneFundamentalInstance,
|
||||
@@ -92,8 +83,6 @@ import {
|
||||
getHostContext,
|
||||
popHostContainer,
|
||||
} from './ReactFiberHostContext';
|
||||
import {NoWork} from './ReactFiberExpirationTime';
|
||||
import {createResponderInstance} from './ReactFiberEvents';
|
||||
import {
|
||||
suspenseStackCursor,
|
||||
InvisibleParentSuspenseContext,
|
||||
@@ -133,10 +122,7 @@ import {
|
||||
import {createFundamentalStateInstance} from './ReactFiberFundamental';
|
||||
import {Never} from './ReactFiberExpirationTime';
|
||||
import {resetChildFibers} from './ReactChildFiber';
|
||||
import warning from 'shared/warning';
|
||||
|
||||
const emptyObject = {};
|
||||
const isArray = Array.isArray;
|
||||
import {updateEventListeners} from './ReactFiberEvents';
|
||||
|
||||
function markUpdate(workInProgress: Fiber) {
|
||||
// Tag the fiber with an update effect. This turns a Placement into
|
||||
@@ -670,9 +656,6 @@ function completeWork(
|
||||
// If we hydrated, pop so that we can delete any remaining children
|
||||
// that weren't hydrated.
|
||||
popHydrationState(workInProgress);
|
||||
// This resets the hacky state to fix isMounted before committing.
|
||||
// TODO: Delete this when we delete isMounted and findDOMNode.
|
||||
workInProgress.effectTag &= ~Placement;
|
||||
}
|
||||
updateHostContainer(workInProgress);
|
||||
break;
|
||||
@@ -693,14 +676,8 @@ function completeWork(
|
||||
if (enableFlareAPI) {
|
||||
const prevListeners = current.memoizedProps.listeners;
|
||||
const nextListeners = newProps.listeners;
|
||||
const instance = workInProgress.stateNode;
|
||||
if (prevListeners !== nextListeners) {
|
||||
updateEventListeners(
|
||||
nextListeners,
|
||||
instance,
|
||||
rootContainerInstance,
|
||||
workInProgress,
|
||||
);
|
||||
markUpdate(workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,12 +719,7 @@ function completeWork(
|
||||
const instance = workInProgress.stateNode;
|
||||
const listeners = newProps.listeners;
|
||||
if (listeners != null) {
|
||||
updateEventListeners(
|
||||
listeners,
|
||||
instance,
|
||||
rootContainerInstance,
|
||||
workInProgress,
|
||||
);
|
||||
updateEventListeners(listeners, instance, workInProgress);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -764,12 +736,7 @@ function completeWork(
|
||||
if (enableFlareAPI) {
|
||||
const listeners = newProps.listeners;
|
||||
if (listeners != null) {
|
||||
updateEventListeners(
|
||||
listeners,
|
||||
instance,
|
||||
rootContainerInstance,
|
||||
workInProgress,
|
||||
);
|
||||
updateEventListeners(listeners, instance, workInProgress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -859,14 +826,13 @@ function completeWork(
|
||||
if ((workInProgress.effectTag & DidCapture) === NoEffect) {
|
||||
// This boundary did not suspend so it's now hydrated and unsuspended.
|
||||
workInProgress.memoizedState = null;
|
||||
if (enableSuspenseCallback) {
|
||||
// Notify the callback.
|
||||
workInProgress.effectTag |= Update;
|
||||
}
|
||||
} else {
|
||||
// Something suspended. Schedule an effect to attach retry listeners.
|
||||
workInProgress.effectTag |= Update;
|
||||
}
|
||||
// If nothing suspended, we need to schedule an effect to mark this boundary
|
||||
// as having hydrated so events know that they're free 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.effectTag |= Update;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1258,156 +1224,4 @@ function completeWork(
|
||||
return null;
|
||||
}
|
||||
|
||||
function mountEventResponder(
|
||||
responder: ReactEventResponder<any, any>,
|
||||
responderProps: Object,
|
||||
instance: Instance,
|
||||
rootContainerInstance: Container,
|
||||
fiber: Fiber,
|
||||
respondersMap: Map<
|
||||
ReactEventResponder<any, any>,
|
||||
ReactEventResponderInstance<any, any>,
|
||||
>,
|
||||
) {
|
||||
let responderState = emptyObject;
|
||||
const getInitialState = responder.getInitialState;
|
||||
if (getInitialState !== null) {
|
||||
responderState = getInitialState(responderProps);
|
||||
}
|
||||
const responderInstance = createResponderInstance(
|
||||
responder,
|
||||
responderProps,
|
||||
responderState,
|
||||
instance,
|
||||
fiber,
|
||||
);
|
||||
mountResponderInstance(
|
||||
responder,
|
||||
responderInstance,
|
||||
responderProps,
|
||||
responderState,
|
||||
instance,
|
||||
rootContainerInstance,
|
||||
);
|
||||
respondersMap.set(responder, responderInstance);
|
||||
}
|
||||
|
||||
function updateEventListener(
|
||||
listener: ReactEventResponderListener<any, any>,
|
||||
fiber: Fiber,
|
||||
visistedResponders: Set<ReactEventResponder<any, any>>,
|
||||
respondersMap: Map<
|
||||
ReactEventResponder<any, any>,
|
||||
ReactEventResponderInstance<any, any>,
|
||||
>,
|
||||
instance: Instance,
|
||||
rootContainerInstance: Container,
|
||||
): void {
|
||||
let responder;
|
||||
let props;
|
||||
|
||||
if (listener) {
|
||||
responder = listener.responder;
|
||||
props = listener.props;
|
||||
}
|
||||
invariant(
|
||||
responder && responder.$$typeof === REACT_RESPONDER_TYPE,
|
||||
'An invalid value was used as an event listener. Expect one or many event ' +
|
||||
'listeners created via React.unstable_useResponder().',
|
||||
);
|
||||
const listenerProps = ((props: any): Object);
|
||||
if (visistedResponders.has(responder)) {
|
||||
// show warning
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
false,
|
||||
'Duplicate event responder "%s" found in event listeners. ' +
|
||||
'Event listeners passed to elements cannot use the same event responder more than once.',
|
||||
responder.displayName,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
visistedResponders.add(responder);
|
||||
const responderInstance = respondersMap.get(responder);
|
||||
|
||||
if (responderInstance === undefined) {
|
||||
// Mount
|
||||
mountEventResponder(
|
||||
responder,
|
||||
listenerProps,
|
||||
instance,
|
||||
rootContainerInstance,
|
||||
fiber,
|
||||
respondersMap,
|
||||
);
|
||||
} else {
|
||||
// Update
|
||||
responderInstance.props = listenerProps;
|
||||
responderInstance.fiber = fiber;
|
||||
}
|
||||
}
|
||||
|
||||
function updateEventListeners(
|
||||
listeners: any,
|
||||
instance: Instance,
|
||||
rootContainerInstance: Container,
|
||||
fiber: Fiber,
|
||||
): void {
|
||||
const visistedResponders = new Set();
|
||||
let dependencies = fiber.dependencies;
|
||||
if (listeners != null) {
|
||||
if (dependencies === null) {
|
||||
dependencies = fiber.dependencies = {
|
||||
expirationTime: NoWork,
|
||||
firstContext: null,
|
||||
responders: new Map(),
|
||||
};
|
||||
}
|
||||
let respondersMap = dependencies.responders;
|
||||
if (respondersMap === null) {
|
||||
respondersMap = new Map();
|
||||
}
|
||||
if (isArray(listeners)) {
|
||||
for (let i = 0, length = listeners.length; i < length; i++) {
|
||||
const listener = listeners[i];
|
||||
updateEventListener(
|
||||
listener,
|
||||
fiber,
|
||||
visistedResponders,
|
||||
respondersMap,
|
||||
instance,
|
||||
rootContainerInstance,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
updateEventListener(
|
||||
listeners,
|
||||
fiber,
|
||||
visistedResponders,
|
||||
respondersMap,
|
||||
instance,
|
||||
rootContainerInstance,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (dependencies !== null) {
|
||||
const respondersMap = dependencies.responders;
|
||||
if (respondersMap !== null) {
|
||||
// Unmount
|
||||
const mountedResponders = Array.from(respondersMap.keys());
|
||||
for (let i = 0, length = mountedResponders.length; i < length; i++) {
|
||||
const mountedResponder = mountedResponders[i];
|
||||
if (!visistedResponders.has(mountedResponder)) {
|
||||
const responderInstance = ((respondersMap.get(
|
||||
mountedResponder,
|
||||
): any): ReactEventResponderInstance<any, any>);
|
||||
unmountResponderInstance(responderInstance);
|
||||
respondersMap.delete(mountedResponder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {completeWork};
|
||||
|
||||
214
packages/react-reconciler/src/ReactFiberEvents.js
vendored
214
packages/react-reconciler/src/ReactFiberEvents.js
vendored
@@ -8,59 +8,26 @@
|
||||
*/
|
||||
|
||||
import type {Fiber} from './ReactFiber';
|
||||
import type {Instance} from './ReactFiberHostConfig';
|
||||
import type {
|
||||
ReactEventResponder,
|
||||
ReactEventResponderInstance,
|
||||
ReactEventResponderListener,
|
||||
} from 'shared/ReactTypes';
|
||||
import type {Instance} from './ReactFiberHostConfig';
|
||||
|
||||
import {SuspenseComponent, Fragment} from 'shared/ReactWorkTags';
|
||||
import {
|
||||
mountResponderInstance,
|
||||
unmountResponderInstance,
|
||||
} from './ReactFiberHostConfig';
|
||||
import {NoWork} from './ReactFiberExpirationTime';
|
||||
|
||||
export function createResponderListener(
|
||||
responder: ReactEventResponder<any, any>,
|
||||
props: Object,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
const eventResponderListener = {
|
||||
responder,
|
||||
props,
|
||||
};
|
||||
if (__DEV__) {
|
||||
Object.freeze(eventResponderListener);
|
||||
}
|
||||
return eventResponderListener;
|
||||
}
|
||||
import warning from 'shared/warning';
|
||||
import {REACT_RESPONDER_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
export function isFiberSuspenseAndTimedOut(fiber: Fiber): boolean {
|
||||
return fiber.tag === SuspenseComponent && fiber.memoizedState !== null;
|
||||
}
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
export function getSuspenseFallbackChild(fiber: Fiber): Fiber | null {
|
||||
return ((((fiber.child: any): Fiber).sibling: any): Fiber).child;
|
||||
}
|
||||
|
||||
export function isFiberSuspenseTimedOutChild(fiber: Fiber | null): boolean {
|
||||
if (fiber === null) {
|
||||
return false;
|
||||
}
|
||||
const parent = fiber.return;
|
||||
if (parent !== null && parent.tag === Fragment) {
|
||||
const grandParent = parent.return;
|
||||
|
||||
if (
|
||||
grandParent !== null &&
|
||||
grandParent.tag === SuspenseComponent &&
|
||||
grandParent.stateNode !== null
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getSuspenseFiberFromTimedOutChild(fiber: Fiber): Fiber {
|
||||
return ((((fiber.return: any): Fiber).return: any): Fiber);
|
||||
}
|
||||
const emptyObject = {};
|
||||
const isArray = Array.isArray;
|
||||
|
||||
export function createResponderInstance(
|
||||
responder: ReactEventResponder<any, any>,
|
||||
@@ -78,3 +45,162 @@ export function createResponderInstance(
|
||||
target,
|
||||
};
|
||||
}
|
||||
|
||||
function mountEventResponder(
|
||||
responder: ReactEventResponder<any, any>,
|
||||
responderProps: Object,
|
||||
instance: Instance,
|
||||
fiber: Fiber,
|
||||
respondersMap: Map<
|
||||
ReactEventResponder<any, any>,
|
||||
ReactEventResponderInstance<any, any>,
|
||||
>,
|
||||
) {
|
||||
let responderState = emptyObject;
|
||||
const getInitialState = responder.getInitialState;
|
||||
if (getInitialState !== null) {
|
||||
responderState = getInitialState(responderProps);
|
||||
}
|
||||
const responderInstance = createResponderInstance(
|
||||
responder,
|
||||
responderProps,
|
||||
responderState,
|
||||
instance,
|
||||
fiber,
|
||||
);
|
||||
mountResponderInstance(
|
||||
responder,
|
||||
responderInstance,
|
||||
responderProps,
|
||||
responderState,
|
||||
instance,
|
||||
);
|
||||
respondersMap.set(responder, responderInstance);
|
||||
}
|
||||
|
||||
function updateEventListener(
|
||||
listener: ReactEventResponderListener<any, any>,
|
||||
fiber: Fiber,
|
||||
visistedResponders: Set<ReactEventResponder<any, any>>,
|
||||
respondersMap: Map<
|
||||
ReactEventResponder<any, any>,
|
||||
ReactEventResponderInstance<any, any>,
|
||||
>,
|
||||
instance: Instance,
|
||||
): void {
|
||||
let responder;
|
||||
let props;
|
||||
|
||||
if (listener) {
|
||||
responder = listener.responder;
|
||||
props = listener.props;
|
||||
}
|
||||
invariant(
|
||||
responder && responder.$$typeof === REACT_RESPONDER_TYPE,
|
||||
'An invalid value was used as an event listener. Expect one or many event ' +
|
||||
'listeners created via React.unstable_useResponder().',
|
||||
);
|
||||
const listenerProps = ((props: any): Object);
|
||||
if (visistedResponders.has(responder)) {
|
||||
// show warning
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
false,
|
||||
'Duplicate event responder "%s" found in event listeners. ' +
|
||||
'Event listeners passed to elements cannot use the same event responder more than once.',
|
||||
responder.displayName,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
visistedResponders.add(responder);
|
||||
const responderInstance = respondersMap.get(responder);
|
||||
|
||||
if (responderInstance === undefined) {
|
||||
// Mount (happens in either complete or commit phase)
|
||||
mountEventResponder(
|
||||
responder,
|
||||
listenerProps,
|
||||
instance,
|
||||
fiber,
|
||||
respondersMap,
|
||||
);
|
||||
} else {
|
||||
// Update (happens during commit phase only)
|
||||
responderInstance.props = listenerProps;
|
||||
responderInstance.fiber = fiber;
|
||||
}
|
||||
}
|
||||
|
||||
export function updateEventListeners(
|
||||
listeners: any,
|
||||
instance: Instance,
|
||||
fiber: Fiber,
|
||||
): void {
|
||||
const visistedResponders = new Set();
|
||||
let dependencies = fiber.dependencies;
|
||||
if (listeners != null) {
|
||||
if (dependencies === null) {
|
||||
dependencies = fiber.dependencies = {
|
||||
expirationTime: NoWork,
|
||||
firstContext: null,
|
||||
responders: new Map(),
|
||||
};
|
||||
}
|
||||
let respondersMap = dependencies.responders;
|
||||
if (respondersMap === null) {
|
||||
respondersMap = new Map();
|
||||
}
|
||||
if (isArray(listeners)) {
|
||||
for (let i = 0, length = listeners.length; i < length; i++) {
|
||||
const listener = listeners[i];
|
||||
updateEventListener(
|
||||
listener,
|
||||
fiber,
|
||||
visistedResponders,
|
||||
respondersMap,
|
||||
instance,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
updateEventListener(
|
||||
listeners,
|
||||
fiber,
|
||||
visistedResponders,
|
||||
respondersMap,
|
||||
instance,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (dependencies !== null) {
|
||||
const respondersMap = dependencies.responders;
|
||||
if (respondersMap !== null) {
|
||||
// Unmount
|
||||
const mountedResponders = Array.from(respondersMap.keys());
|
||||
for (let i = 0, length = mountedResponders.length; i < length; i++) {
|
||||
const mountedResponder = mountedResponders[i];
|
||||
if (!visistedResponders.has(mountedResponder)) {
|
||||
const responderInstance = ((respondersMap.get(
|
||||
mountedResponder,
|
||||
): any): ReactEventResponderInstance<any, any>);
|
||||
unmountResponderInstance(responderInstance);
|
||||
respondersMap.delete(mountedResponder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createResponderListener(
|
||||
responder: ReactEventResponder<any, any>,
|
||||
props: Object,
|
||||
): ReactEventResponderListener<any, any> {
|
||||
const eventResponderListener = {
|
||||
responder,
|
||||
props,
|
||||
};
|
||||
if (__DEV__) {
|
||||
Object.freeze(eventResponderListener);
|
||||
}
|
||||
return eventResponderListener;
|
||||
}
|
||||
|
||||
@@ -1122,7 +1122,7 @@ function dispatchAction<S, A>(
|
||||
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
arguments.length <= 3,
|
||||
typeof arguments[3] !== 'function',
|
||||
"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().',
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
HostText,
|
||||
FundamentalComponent,
|
||||
} from 'shared/ReactWorkTags';
|
||||
import {NoEffect, Placement} from 'shared/ReactSideEffectTags';
|
||||
import {NoEffect, Placement, Hydrating} from 'shared/ReactSideEffectTags';
|
||||
import {enableFundamentalAPI} from 'shared/ReactFeatureFlags';
|
||||
|
||||
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
|
||||
@@ -32,20 +32,21 @@ const MOUNTING = 1;
|
||||
const MOUNTED = 2;
|
||||
const UNMOUNTED = 3;
|
||||
|
||||
function isFiberMountedImpl(fiber: Fiber): number {
|
||||
type MountState = 1 | 2 | 3;
|
||||
|
||||
function isFiberMountedImpl(fiber: Fiber): MountState {
|
||||
let node = 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.
|
||||
if ((node.effectTag & Placement) !== NoEffect) {
|
||||
return MOUNTING;
|
||||
}
|
||||
while (node.return) {
|
||||
node = node.return;
|
||||
if ((node.effectTag & Placement) !== NoEffect) {
|
||||
let nextNode = node;
|
||||
do {
|
||||
node = nextNode;
|
||||
if ((node.effectTag & (Placement | Hydrating)) !== NoEffect) {
|
||||
return MOUNTING;
|
||||
}
|
||||
}
|
||||
nextNode = node.return;
|
||||
} while (nextNode);
|
||||
} else {
|
||||
while (node.return) {
|
||||
node = node.return;
|
||||
|
||||
@@ -96,6 +96,8 @@ import {
|
||||
Passive,
|
||||
Incomplete,
|
||||
HostEffectMask,
|
||||
Hydrating,
|
||||
HydratingAndUpdate,
|
||||
} from 'shared/ReactSideEffectTags';
|
||||
import {
|
||||
NoWork,
|
||||
@@ -1860,7 +1862,8 @@ function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
|
||||
// updates, and deletions. To avoid needing to add a case for every possible
|
||||
// bitmap value, we remove the secondary effects from the effect tag and
|
||||
// switch on that value.
|
||||
let primaryEffectTag = effectTag & (Placement | Update | Deletion);
|
||||
let primaryEffectTag =
|
||||
effectTag & (Placement | Update | Deletion | Hydrating);
|
||||
switch (primaryEffectTag) {
|
||||
case Placement: {
|
||||
commitPlacement(nextEffect);
|
||||
@@ -1883,6 +1886,18 @@ function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
|
||||
commitWork(current, nextEffect);
|
||||
break;
|
||||
}
|
||||
case Hydrating: {
|
||||
nextEffect.effectTag &= ~Hydrating;
|
||||
break;
|
||||
}
|
||||
case HydratingAndUpdate: {
|
||||
nextEffect.effectTag &= ~Hydrating;
|
||||
|
||||
// Update
|
||||
const current = nextEffect.alternate;
|
||||
commitWork(current, nextEffect);
|
||||
break;
|
||||
}
|
||||
case Update: {
|
||||
const current = nextEffect.alternate;
|
||||
commitWork(current, nextEffect);
|
||||
|
||||
@@ -160,7 +160,9 @@ export function cancelCallback(callbackNode: mixed) {
|
||||
|
||||
export function flushSyncCallbackQueue() {
|
||||
if (immediateQueueCallbackNode !== null) {
|
||||
Scheduler_cancelCallback(immediateQueueCallbackNode);
|
||||
const node = immediateQueueCallbackNode;
|
||||
immediateQueueCallbackNode = null;
|
||||
Scheduler_cancelCallback(node);
|
||||
}
|
||||
flushSyncCallbackQueueImpl();
|
||||
}
|
||||
|
||||
@@ -136,6 +136,7 @@ describe('ReactDebugFiberPerf', () => {
|
||||
require('shared/ReactFeatureFlags').enableProfilerTimer = false;
|
||||
require('shared/ReactFeatureFlags').replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
|
||||
require('shared/ReactFeatureFlags').debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
require('scheduler/src/SchedulerFeatureFlags').enableProfiling = false;
|
||||
|
||||
// Import after the polyfill is set up:
|
||||
React = require('react');
|
||||
|
||||
@@ -296,7 +296,6 @@ export function mountResponderInstance(
|
||||
props: Object,
|
||||
state: Object,
|
||||
instance: Instance,
|
||||
rootContainerInstance: Container,
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
@@ -144,5 +144,9 @@
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_UserBlockingPriority;
|
||||
},
|
||||
get unstable_Profiling() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_Profiling;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -138,5 +138,9 @@
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_UserBlockingPriority;
|
||||
},
|
||||
get unstable_Profiling() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_Profiling;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -138,5 +138,9 @@
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_UserBlockingPriority;
|
||||
},
|
||||
get unstable_Profiling() {
|
||||
return global.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.Scheduler.unstable_Profiling;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
|
||||
/* eslint-disable no-var */
|
||||
|
||||
import {enableSchedulerDebugging} from './SchedulerFeatureFlags';
|
||||
import {
|
||||
enableSchedulerDebugging,
|
||||
enableProfiling,
|
||||
} from './SchedulerFeatureFlags';
|
||||
import {
|
||||
requestHostCallback,
|
||||
requestHostTimeout,
|
||||
@@ -21,11 +24,26 @@ import {
|
||||
import {push, pop, peek} from './SchedulerMinHeap';
|
||||
|
||||
// TODO: Use symbols?
|
||||
var ImmediatePriority = 1;
|
||||
var UserBlockingPriority = 2;
|
||||
var NormalPriority = 3;
|
||||
var LowPriority = 4;
|
||||
var IdlePriority = 5;
|
||||
import {
|
||||
ImmediatePriority,
|
||||
UserBlockingPriority,
|
||||
NormalPriority,
|
||||
LowPriority,
|
||||
IdlePriority,
|
||||
} from './SchedulerPriorities';
|
||||
import {
|
||||
sharedProfilingBuffer,
|
||||
markTaskRun,
|
||||
markTaskYield,
|
||||
markTaskCompleted,
|
||||
markTaskCanceled,
|
||||
markTaskErrored,
|
||||
markSchedulerSuspended,
|
||||
markSchedulerUnsuspended,
|
||||
markTaskStart,
|
||||
stopLoggingProfilingEvents,
|
||||
startLoggingProfilingEvents,
|
||||
} from './SchedulerProfiling';
|
||||
|
||||
// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
|
||||
// Math.pow(2, 30) - 1
|
||||
@@ -46,7 +64,7 @@ var taskQueue = [];
|
||||
var timerQueue = [];
|
||||
|
||||
// Incrementing id counter. Used to maintain insertion order.
|
||||
var taskIdCounter = 0;
|
||||
var taskIdCounter = 1;
|
||||
|
||||
// Pausing the scheduler is useful for debugging.
|
||||
var isSchedulerPaused = false;
|
||||
@@ -60,15 +78,6 @@ var isPerformingWork = false;
|
||||
var isHostCallbackScheduled = false;
|
||||
var isHostTimeoutScheduled = false;
|
||||
|
||||
function flushTask(task, callback, currentTime) {
|
||||
currentPriorityLevel = task.priorityLevel;
|
||||
var didUserCallbackTimeout = task.expirationTime <= currentTime;
|
||||
var continuationCallback = callback(didUserCallbackTimeout);
|
||||
return typeof continuationCallback === 'function'
|
||||
? continuationCallback
|
||||
: null;
|
||||
}
|
||||
|
||||
function advanceTimers(currentTime) {
|
||||
// Check for tasks that are no longer delayed and add them to the queue.
|
||||
let timer = peek(timerQueue);
|
||||
@@ -81,6 +90,10 @@ function advanceTimers(currentTime) {
|
||||
pop(timerQueue);
|
||||
timer.sortIndex = timer.expirationTime;
|
||||
push(taskQueue, timer);
|
||||
if (enableProfiling) {
|
||||
markTaskStart(timer);
|
||||
timer.isQueued = true;
|
||||
}
|
||||
} else {
|
||||
// Remaining timers are pending.
|
||||
return;
|
||||
@@ -107,6 +120,10 @@ function handleTimeout(currentTime) {
|
||||
}
|
||||
|
||||
function flushWork(hasTimeRemaining, initialTime) {
|
||||
if (enableProfiling) {
|
||||
markSchedulerUnsuspended(initialTime);
|
||||
}
|
||||
|
||||
// We'll need a host callback the next time work is scheduled.
|
||||
isHostCallbackScheduled = false;
|
||||
if (isHostTimeoutScheduled) {
|
||||
@@ -118,52 +135,82 @@ function flushWork(hasTimeRemaining, initialTime) {
|
||||
isPerformingWork = true;
|
||||
const previousPriorityLevel = currentPriorityLevel;
|
||||
try {
|
||||
let currentTime = initialTime;
|
||||
advanceTimers(currentTime);
|
||||
currentTask = peek(taskQueue);
|
||||
while (
|
||||
currentTask !== null &&
|
||||
!(enableSchedulerDebugging && isSchedulerPaused)
|
||||
) {
|
||||
if (
|
||||
currentTask.expirationTime > currentTime &&
|
||||
(!hasTimeRemaining || shouldYieldToHost())
|
||||
) {
|
||||
// This currentTask hasn't expired, and we've reached the deadline.
|
||||
break;
|
||||
}
|
||||
const callback = currentTask.callback;
|
||||
if (callback !== null) {
|
||||
currentTask.callback = null;
|
||||
const continuation = flushTask(currentTask, callback, currentTime);
|
||||
if (continuation !== null) {
|
||||
currentTask.callback = continuation;
|
||||
} else {
|
||||
if (currentTask === peek(taskQueue)) {
|
||||
pop(taskQueue);
|
||||
}
|
||||
if (enableProfiling) {
|
||||
try {
|
||||
return workLoop(hasTimeRemaining, initialTime);
|
||||
} catch (error) {
|
||||
if (currentTask !== null) {
|
||||
const currentTime = getCurrentTime();
|
||||
markTaskErrored(currentTask, currentTime);
|
||||
currentTask.isQueued = false;
|
||||
}
|
||||
currentTime = getCurrentTime();
|
||||
advanceTimers(currentTime);
|
||||
} else {
|
||||
pop(taskQueue);
|
||||
throw error;
|
||||
}
|
||||
currentTask = peek(taskQueue);
|
||||
}
|
||||
// Return whether there's additional work
|
||||
if (currentTask !== null) {
|
||||
return true;
|
||||
} else {
|
||||
let firstTimer = peek(timerQueue);
|
||||
if (firstTimer !== null) {
|
||||
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
|
||||
}
|
||||
return false;
|
||||
// No catch in prod codepath.
|
||||
return workLoop(hasTimeRemaining, initialTime);
|
||||
}
|
||||
} finally {
|
||||
currentTask = null;
|
||||
currentPriorityLevel = previousPriorityLevel;
|
||||
isPerformingWork = false;
|
||||
if (enableProfiling) {
|
||||
const currentTime = getCurrentTime();
|
||||
markSchedulerSuspended(currentTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function workLoop(hasTimeRemaining, initialTime) {
|
||||
let currentTime = initialTime;
|
||||
advanceTimers(currentTime);
|
||||
currentTask = peek(taskQueue);
|
||||
while (
|
||||
currentTask !== null &&
|
||||
!(enableSchedulerDebugging && isSchedulerPaused)
|
||||
) {
|
||||
if (
|
||||
currentTask.expirationTime > currentTime &&
|
||||
(!hasTimeRemaining || shouldYieldToHost())
|
||||
) {
|
||||
// This currentTask hasn't expired, and we've reached the deadline.
|
||||
break;
|
||||
}
|
||||
const callback = currentTask.callback;
|
||||
if (callback !== null) {
|
||||
currentTask.callback = null;
|
||||
currentPriorityLevel = currentTask.priorityLevel;
|
||||
const didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
|
||||
markTaskRun(currentTask, currentTime);
|
||||
const continuationCallback = callback(didUserCallbackTimeout);
|
||||
currentTime = getCurrentTime();
|
||||
if (typeof continuationCallback === 'function') {
|
||||
currentTask.callback = continuationCallback;
|
||||
markTaskYield(currentTask, currentTime);
|
||||
} else {
|
||||
if (enableProfiling) {
|
||||
markTaskCompleted(currentTask, currentTime);
|
||||
currentTask.isQueued = false;
|
||||
}
|
||||
if (currentTask === peek(taskQueue)) {
|
||||
pop(taskQueue);
|
||||
}
|
||||
}
|
||||
advanceTimers(currentTime);
|
||||
} else {
|
||||
pop(taskQueue);
|
||||
}
|
||||
currentTask = peek(taskQueue);
|
||||
}
|
||||
// Return whether there's additional work
|
||||
if (currentTask !== null) {
|
||||
return true;
|
||||
} else {
|
||||
let firstTimer = peek(timerQueue);
|
||||
if (firstTimer !== null) {
|
||||
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +323,9 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
|
||||
expirationTime,
|
||||
sortIndex: -1,
|
||||
};
|
||||
if (enableProfiling) {
|
||||
newTask.isQueued = false;
|
||||
}
|
||||
|
||||
if (startTime > currentTime) {
|
||||
// This is a delayed task.
|
||||
@@ -295,6 +345,10 @@ function unstable_scheduleCallback(priorityLevel, callback, options) {
|
||||
} else {
|
||||
newTask.sortIndex = expirationTime;
|
||||
push(taskQueue, newTask);
|
||||
if (enableProfiling) {
|
||||
markTaskStart(newTask, currentTime);
|
||||
newTask.isQueued = true;
|
||||
}
|
||||
// Schedule a host callback, if needed. If we're already performing work,
|
||||
// wait until the next time we yield.
|
||||
if (!isHostCallbackScheduled && !isPerformingWork) {
|
||||
@@ -323,9 +377,17 @@ function unstable_getFirstCallbackNode() {
|
||||
}
|
||||
|
||||
function unstable_cancelCallback(task) {
|
||||
// Null out the callback to indicate the task has been canceled. (Can't remove
|
||||
// from the queue because you can't remove arbitrary nodes from an array based
|
||||
// heap, only the first one.)
|
||||
if (enableProfiling) {
|
||||
if (task.isQueued) {
|
||||
const currentTime = getCurrentTime();
|
||||
markTaskCanceled(task, currentTime);
|
||||
task.isQueued = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Null out the callback to indicate the task has been canceled. (Can't
|
||||
// remove from the queue because you can't remove arbitrary nodes from an
|
||||
// array based heap, only the first one.)
|
||||
task.callback = null;
|
||||
}
|
||||
|
||||
@@ -370,3 +432,11 @@ export {
|
||||
getCurrentTime as unstable_now,
|
||||
forceFrameRate as unstable_forceFrameRate,
|
||||
};
|
||||
|
||||
export const unstable_Profiling = enableProfiling
|
||||
? {
|
||||
startLoggingProfilingEvents,
|
||||
stopLoggingProfilingEvents,
|
||||
sharedProfilingBuffer,
|
||||
}
|
||||
: null;
|
||||
|
||||
@@ -11,3 +11,4 @@ export const enableIsInputPending = false;
|
||||
export const requestIdleCallbackBeforeFirstFrame = false;
|
||||
export const requestTimerEventBeforeFirstFrame = false;
|
||||
export const enableMessageLoopImplementation = false;
|
||||
export const enableProfiling = __PROFILE__;
|
||||
|
||||
18
packages/scheduler/src/SchedulerPriorities.js
Normal file
18
packages/scheduler/src/SchedulerPriorities.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;
|
||||
|
||||
// TODO: Use symbols?
|
||||
export const NoPriority = 0;
|
||||
export const ImmediatePriority = 1;
|
||||
export const UserBlockingPriority = 2;
|
||||
export const NormalPriority = 3;
|
||||
export const LowPriority = 4;
|
||||
export const IdlePriority = 5;
|
||||
203
packages/scheduler/src/SchedulerProfiling.js
Normal file
203
packages/scheduler/src/SchedulerProfiling.js
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {PriorityLevel} from './SchedulerPriorities';
|
||||
import {enableProfiling} from './SchedulerFeatureFlags';
|
||||
|
||||
import {NoPriority} from './SchedulerPriorities';
|
||||
|
||||
let runIdCounter: number = 0;
|
||||
let mainThreadIdCounter: number = 0;
|
||||
|
||||
const profilingStateSize = 4;
|
||||
export const sharedProfilingBuffer = enableProfiling
|
||||
? // $FlowFixMe Flow doesn't know about SharedArrayBuffer
|
||||
typeof SharedArrayBuffer === 'function'
|
||||
? new SharedArrayBuffer(profilingStateSize * Int32Array.BYTES_PER_ELEMENT)
|
||||
: // $FlowFixMe Flow doesn't know about ArrayBuffer
|
||||
typeof ArrayBuffer === 'function'
|
||||
? new ArrayBuffer(profilingStateSize * Int32Array.BYTES_PER_ELEMENT)
|
||||
: null // Don't crash the init path on IE9
|
||||
: null;
|
||||
|
||||
const profilingState =
|
||||
enableProfiling && sharedProfilingBuffer !== null
|
||||
? new Int32Array(sharedProfilingBuffer)
|
||||
: []; // We can't read this but it helps save bytes for null checks
|
||||
|
||||
const PRIORITY = 0;
|
||||
const CURRENT_TASK_ID = 1;
|
||||
const CURRENT_RUN_ID = 2;
|
||||
const QUEUE_SIZE = 3;
|
||||
|
||||
if (enableProfiling) {
|
||||
profilingState[PRIORITY] = NoPriority;
|
||||
// This is maintained with a counter, because the size of the priority queue
|
||||
// array might include canceled tasks.
|
||||
profilingState[QUEUE_SIZE] = 0;
|
||||
profilingState[CURRENT_TASK_ID] = 0;
|
||||
}
|
||||
|
||||
const INITIAL_EVENT_LOG_SIZE = 1000;
|
||||
|
||||
let eventLogSize = 0;
|
||||
let eventLogBuffer = null;
|
||||
let eventLog = null;
|
||||
let eventLogIndex = 0;
|
||||
|
||||
const TaskStartEvent = 1;
|
||||
const TaskCompleteEvent = 2;
|
||||
const TaskErrorEvent = 3;
|
||||
const TaskCancelEvent = 4;
|
||||
const TaskRunEvent = 5;
|
||||
const TaskYieldEvent = 6;
|
||||
const SchedulerSuspendEvent = 7;
|
||||
const SchedulerResumeEvent = 8;
|
||||
|
||||
function logEvent(entries) {
|
||||
if (eventLog !== null) {
|
||||
const offset = eventLogIndex;
|
||||
eventLogIndex += entries.length;
|
||||
if (eventLogIndex + 1 > eventLogSize) {
|
||||
eventLogSize = eventLogIndex + 1;
|
||||
const newEventLog = new Int32Array(
|
||||
eventLogSize * Int32Array.BYTES_PER_ELEMENT,
|
||||
);
|
||||
newEventLog.set(eventLog);
|
||||
eventLogBuffer = newEventLog.buffer;
|
||||
eventLog = newEventLog;
|
||||
}
|
||||
eventLog.set(entries, offset);
|
||||
}
|
||||
}
|
||||
|
||||
export function startLoggingProfilingEvents(): void {
|
||||
eventLogSize = INITIAL_EVENT_LOG_SIZE;
|
||||
eventLogBuffer = new ArrayBuffer(eventLogSize * Int32Array.BYTES_PER_ELEMENT);
|
||||
eventLog = new Int32Array(eventLogBuffer);
|
||||
eventLogIndex = 0;
|
||||
}
|
||||
|
||||
export function stopLoggingProfilingEvents(): ArrayBuffer | null {
|
||||
const buffer = eventLogBuffer;
|
||||
eventLogBuffer = eventLog = null;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export function markTaskStart(
|
||||
task: {id: number, priorityLevel: PriorityLevel},
|
||||
time: number,
|
||||
) {
|
||||
if (enableProfiling) {
|
||||
profilingState[QUEUE_SIZE]++;
|
||||
|
||||
if (eventLog !== null) {
|
||||
logEvent([TaskStartEvent, time, task.id, task.priorityLevel]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markTaskCompleted(
|
||||
task: {
|
||||
id: number,
|
||||
priorityLevel: PriorityLevel,
|
||||
},
|
||||
time: number,
|
||||
) {
|
||||
if (enableProfiling) {
|
||||
profilingState[PRIORITY] = NoPriority;
|
||||
profilingState[CURRENT_TASK_ID] = 0;
|
||||
profilingState[QUEUE_SIZE]--;
|
||||
|
||||
if (eventLog !== null) {
|
||||
logEvent([TaskCompleteEvent, time, task.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markTaskCanceled(
|
||||
task: {
|
||||
id: number,
|
||||
priorityLevel: PriorityLevel,
|
||||
},
|
||||
time: number,
|
||||
) {
|
||||
if (enableProfiling) {
|
||||
profilingState[QUEUE_SIZE]--;
|
||||
|
||||
if (eventLog !== null) {
|
||||
logEvent([TaskCancelEvent, time, task.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markTaskErrored(
|
||||
task: {
|
||||
id: number,
|
||||
priorityLevel: PriorityLevel,
|
||||
},
|
||||
time: number,
|
||||
) {
|
||||
if (enableProfiling) {
|
||||
profilingState[PRIORITY] = NoPriority;
|
||||
profilingState[CURRENT_TASK_ID] = 0;
|
||||
profilingState[QUEUE_SIZE]--;
|
||||
|
||||
if (eventLog !== null) {
|
||||
logEvent([TaskErrorEvent, time, task.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markTaskRun(
|
||||
task: {id: number, priorityLevel: PriorityLevel},
|
||||
time: number,
|
||||
) {
|
||||
if (enableProfiling) {
|
||||
runIdCounter++;
|
||||
|
||||
profilingState[PRIORITY] = task.priorityLevel;
|
||||
profilingState[CURRENT_TASK_ID] = task.id;
|
||||
profilingState[CURRENT_RUN_ID] = runIdCounter;
|
||||
|
||||
if (eventLog !== null) {
|
||||
logEvent([TaskRunEvent, time, task.id, runIdCounter]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markTaskYield(task: {id: number}, time: number) {
|
||||
if (enableProfiling) {
|
||||
profilingState[PRIORITY] = NoPriority;
|
||||
profilingState[CURRENT_TASK_ID] = 0;
|
||||
profilingState[CURRENT_RUN_ID] = 0;
|
||||
|
||||
if (eventLog !== null) {
|
||||
logEvent([TaskYieldEvent, time, task.id, runIdCounter]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markSchedulerSuspended(time: number) {
|
||||
if (enableProfiling) {
|
||||
mainThreadIdCounter++;
|
||||
|
||||
if (eventLog !== null) {
|
||||
logEvent([SchedulerSuspendEvent, time, mainThreadIdCounter]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function markSchedulerUnsuspended(time: number) {
|
||||
if (enableProfiling) {
|
||||
if (eventLog !== null) {
|
||||
logEvent([SchedulerResumeEvent, time, mainThreadIdCounter]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,11 +59,15 @@ describe('SchedulerDOM', () => {
|
||||
runPostMessageCallbacks(config);
|
||||
}
|
||||
|
||||
let frameSize = 33;
|
||||
let startOfLatestFrame = 0;
|
||||
let currentTime = 0;
|
||||
let frameSize;
|
||||
let startOfLatestFrame;
|
||||
let currentTime;
|
||||
|
||||
beforeEach(() => {
|
||||
frameSize = 33;
|
||||
startOfLatestFrame = 0;
|
||||
currentTime = 0;
|
||||
|
||||
delete global.performance;
|
||||
global.requestAnimationFrame = function(cb) {
|
||||
return rAFCallbacks.push(() => {
|
||||
|
||||
508
packages/scheduler/src/__tests__/SchedulerProfiling-test.js
Normal file
508
packages/scheduler/src/__tests__/SchedulerProfiling-test.js
Normal file
@@ -0,0 +1,508 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @emails react-core
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
/* eslint-disable no-for-of-loops/no-for-of-loops */
|
||||
|
||||
'use strict';
|
||||
|
||||
let Scheduler;
|
||||
let sharedProfilingArray;
|
||||
// let runWithPriority;
|
||||
let ImmediatePriority;
|
||||
let UserBlockingPriority;
|
||||
let NormalPriority;
|
||||
let LowPriority;
|
||||
let IdlePriority;
|
||||
let scheduleCallback;
|
||||
let cancelCallback;
|
||||
// let wrapCallback;
|
||||
// let getCurrentPriorityLevel;
|
||||
// let shouldYield;
|
||||
|
||||
function priorityLevelToString(priorityLevel) {
|
||||
switch (priorityLevel) {
|
||||
case ImmediatePriority:
|
||||
return 'Immediate';
|
||||
case UserBlockingPriority:
|
||||
return 'User-blocking';
|
||||
case NormalPriority:
|
||||
return 'Normal';
|
||||
case LowPriority:
|
||||
return 'Low';
|
||||
case IdlePriority:
|
||||
return 'Idle';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
describe('Scheduler', () => {
|
||||
if (!__PROFILE__) {
|
||||
// The tests in this suite only apply when profiling is on
|
||||
it('profiling APIs are not available', () => {
|
||||
Scheduler = require('scheduler');
|
||||
expect(Scheduler.unstable_Profiling).toBe(null);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
jest.mock('scheduler', () => require('scheduler/unstable_mock'));
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
sharedProfilingArray = new Int32Array(
|
||||
Scheduler.unstable_Profiling.sharedProfilingBuffer,
|
||||
);
|
||||
|
||||
// runWithPriority = Scheduler.unstable_runWithPriority;
|
||||
ImmediatePriority = Scheduler.unstable_ImmediatePriority;
|
||||
UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
|
||||
NormalPriority = Scheduler.unstable_NormalPriority;
|
||||
LowPriority = Scheduler.unstable_LowPriority;
|
||||
IdlePriority = Scheduler.unstable_IdlePriority;
|
||||
scheduleCallback = Scheduler.unstable_scheduleCallback;
|
||||
cancelCallback = Scheduler.unstable_cancelCallback;
|
||||
// wrapCallback = Scheduler.unstable_wrapCallback;
|
||||
// getCurrentPriorityLevel = Scheduler.unstable_getCurrentPriorityLevel;
|
||||
// shouldYield = Scheduler.unstable_shouldYield;
|
||||
});
|
||||
|
||||
const PRIORITY = 0;
|
||||
const CURRENT_TASK_ID = 1;
|
||||
const CURRENT_RUN_ID = 2;
|
||||
const QUEUE_SIZE = 3;
|
||||
|
||||
afterEach(() => {
|
||||
if (sharedProfilingArray[QUEUE_SIZE] !== 0) {
|
||||
throw Error(
|
||||
'Test exited, but the shared profiling buffer indicates that a task ' +
|
||||
'is still running',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const TaskStartEvent = 1;
|
||||
const TaskCompleteEvent = 2;
|
||||
const TaskErrorEvent = 3;
|
||||
const TaskCancelEvent = 4;
|
||||
const TaskRunEvent = 5;
|
||||
const TaskYieldEvent = 6;
|
||||
const SchedulerSuspendEvent = 7;
|
||||
const SchedulerResumeEvent = 8;
|
||||
|
||||
function stopProfilingAndPrintFlamegraph() {
|
||||
const eventLog = new Int32Array(
|
||||
Scheduler.unstable_Profiling.stopLoggingProfilingEvents(),
|
||||
);
|
||||
|
||||
const tasks = new Map();
|
||||
const mainThreadRuns = [];
|
||||
|
||||
let isSuspended = true;
|
||||
let i = 0;
|
||||
processLog: while (i < eventLog.length) {
|
||||
const instruction = eventLog[i];
|
||||
const time = eventLog[i + 1];
|
||||
switch (instruction) {
|
||||
case 0: {
|
||||
break processLog;
|
||||
}
|
||||
case TaskStartEvent: {
|
||||
const taskId = eventLog[i + 2];
|
||||
const priorityLevel = eventLog[i + 3];
|
||||
const task = {
|
||||
id: taskId,
|
||||
priorityLevel,
|
||||
label: null,
|
||||
start: time,
|
||||
end: -1,
|
||||
exitStatus: null,
|
||||
runs: [],
|
||||
};
|
||||
tasks.set(taskId, task);
|
||||
i += 4;
|
||||
break;
|
||||
}
|
||||
case TaskCompleteEvent: {
|
||||
if (isSuspended) {
|
||||
throw Error('Task cannot Complete outside the work loop.');
|
||||
}
|
||||
const taskId = eventLog[i + 2];
|
||||
const task = tasks.get(taskId);
|
||||
if (task === undefined) {
|
||||
throw Error('Task does not exist.');
|
||||
}
|
||||
task.end = time;
|
||||
task.exitStatus = 'completed';
|
||||
i += 3;
|
||||
break;
|
||||
}
|
||||
case TaskErrorEvent: {
|
||||
if (isSuspended) {
|
||||
throw Error('Task cannot Error outside the work loop.');
|
||||
}
|
||||
const taskId = eventLog[i + 2];
|
||||
const task = tasks.get(taskId);
|
||||
if (task === undefined) {
|
||||
throw Error('Task does not exist.');
|
||||
}
|
||||
task.end = time;
|
||||
task.exitStatus = 'errored';
|
||||
i += 3;
|
||||
break;
|
||||
}
|
||||
case TaskCancelEvent: {
|
||||
const taskId = eventLog[i + 2];
|
||||
const task = tasks.get(taskId);
|
||||
if (task === undefined) {
|
||||
throw Error('Task does not exist.');
|
||||
}
|
||||
task.end = time;
|
||||
task.exitStatus = 'canceled';
|
||||
i += 3;
|
||||
break;
|
||||
}
|
||||
case TaskRunEvent:
|
||||
case TaskYieldEvent: {
|
||||
if (isSuspended) {
|
||||
throw Error('Task cannot Run or Yield outside the work loop.');
|
||||
}
|
||||
const taskId = eventLog[i + 2];
|
||||
const task = tasks.get(taskId);
|
||||
if (task === undefined) {
|
||||
throw Error('Task does not exist.');
|
||||
}
|
||||
task.runs.push(time);
|
||||
i += 4;
|
||||
break;
|
||||
}
|
||||
case SchedulerSuspendEvent: {
|
||||
if (isSuspended) {
|
||||
throw Error('Scheduler cannot Suspend outside the work loop.');
|
||||
}
|
||||
isSuspended = true;
|
||||
mainThreadRuns.push(time);
|
||||
i += 3;
|
||||
break;
|
||||
}
|
||||
case SchedulerResumeEvent: {
|
||||
if (!isSuspended) {
|
||||
throw Error('Scheduler cannot Resume inside the work loop.');
|
||||
}
|
||||
isSuspended = false;
|
||||
mainThreadRuns.push(time);
|
||||
i += 3;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw Error('Unknown instruction type: ' + instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we can render the tasks as a flamegraph.
|
||||
const labelColumnWidth = 30;
|
||||
const msPerChar = 50;
|
||||
|
||||
let result = '';
|
||||
|
||||
const mainThreadLabelColumn = '!!! Main thread ';
|
||||
let mainThreadTimelineColumn = '';
|
||||
let isMainThreadBusy = true;
|
||||
for (const time of mainThreadRuns) {
|
||||
const index = time / msPerChar;
|
||||
mainThreadTimelineColumn += (isMainThreadBusy ? '█' : '░').repeat(
|
||||
index - mainThreadTimelineColumn.length,
|
||||
);
|
||||
isMainThreadBusy = !isMainThreadBusy;
|
||||
}
|
||||
result += `${mainThreadLabelColumn}│${mainThreadTimelineColumn}\n`;
|
||||
|
||||
const tasksByPriority = Array.from(tasks.values()).sort(
|
||||
(t1, t2) => t1.priorityLevel - t2.priorityLevel,
|
||||
);
|
||||
|
||||
for (const task of tasksByPriority) {
|
||||
let label = task.label;
|
||||
if (label === undefined) {
|
||||
label = 'Task';
|
||||
}
|
||||
let labelColumn = `Task ${task.id} [${priorityLevelToString(
|
||||
task.priorityLevel,
|
||||
)}]`;
|
||||
labelColumn += ' '.repeat(labelColumnWidth - labelColumn.length - 1);
|
||||
|
||||
// Add empty space up until the start mark
|
||||
let timelineColumn = ' '.repeat(task.start / msPerChar);
|
||||
|
||||
let isRunning = false;
|
||||
for (const time of task.runs) {
|
||||
const index = time / msPerChar;
|
||||
timelineColumn += (isRunning ? '█' : '░').repeat(
|
||||
index - timelineColumn.length,
|
||||
);
|
||||
isRunning = !isRunning;
|
||||
}
|
||||
|
||||
const endIndex = task.end / msPerChar;
|
||||
timelineColumn += (isRunning ? '█' : '░').repeat(
|
||||
endIndex - timelineColumn.length,
|
||||
);
|
||||
|
||||
if (task.exitStatus !== 'completed') {
|
||||
timelineColumn += `🡐 ${task.exitStatus}`;
|
||||
}
|
||||
|
||||
result += `${labelColumn}│${timelineColumn}\n`;
|
||||
}
|
||||
|
||||
return '\n' + result;
|
||||
}
|
||||
|
||||
function getProfilingInfo() {
|
||||
const queueSize = sharedProfilingArray[QUEUE_SIZE];
|
||||
if (queueSize === 0) {
|
||||
return 'Empty Queue';
|
||||
}
|
||||
const priorityLevel = sharedProfilingArray[PRIORITY];
|
||||
if (priorityLevel === 0) {
|
||||
return 'Suspended, Queue Size: ' + queueSize;
|
||||
}
|
||||
return (
|
||||
`Task: ${sharedProfilingArray[CURRENT_TASK_ID]}, ` +
|
||||
`Run: ${sharedProfilingArray[CURRENT_RUN_ID]}, ` +
|
||||
`Priority: ${priorityLevelToString(priorityLevel)}, ` +
|
||||
`Queue Size: ${sharedProfilingArray[QUEUE_SIZE]}`
|
||||
);
|
||||
}
|
||||
|
||||
it('creates a basic flamegraph', () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
scheduleCallback(
|
||||
NormalPriority,
|
||||
() => {
|
||||
Scheduler.unstable_advanceTime(300);
|
||||
Scheduler.unstable_yieldValue(getProfilingInfo());
|
||||
scheduleCallback(
|
||||
UserBlockingPriority,
|
||||
() => {
|
||||
Scheduler.unstable_yieldValue(getProfilingInfo());
|
||||
Scheduler.unstable_advanceTime(300);
|
||||
},
|
||||
{label: 'Bar'},
|
||||
);
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
Scheduler.unstable_yieldValue('Yield');
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue(getProfilingInfo());
|
||||
Scheduler.unstable_advanceTime(300);
|
||||
};
|
||||
},
|
||||
{label: 'Foo'},
|
||||
);
|
||||
expect(Scheduler).toFlushAndYieldThrough([
|
||||
'Task: 1, Run: 1, Priority: Normal, Queue Size: 1',
|
||||
'Yield',
|
||||
]);
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Task: 2, Run: 2, Priority: User-blocking, Queue Size: 2',
|
||||
'Task: 1, Run: 3, Priority: Normal, Queue Size: 1',
|
||||
]);
|
||||
|
||||
expect(getProfilingInfo()).toEqual('Empty Queue');
|
||||
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │██░░░░░░░░██░░░░░░░░░░░░
|
||||
Task 2 [User-blocking] │ ░░░░██████
|
||||
Task 1 [Normal] │ ████████░░░░░░░░██████
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
it('marks when a task is canceled', () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
|
||||
const task = scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.unstable_yieldValue(getProfilingInfo());
|
||||
Scheduler.unstable_advanceTime(300);
|
||||
Scheduler.unstable_yieldValue('Yield');
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue('Continuation');
|
||||
Scheduler.unstable_advanceTime(200);
|
||||
};
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYieldThrough([
|
||||
'Task: 1, Run: 1, Priority: Normal, Queue Size: 1',
|
||||
'Yield',
|
||||
]);
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
|
||||
cancelCallback(task);
|
||||
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │░░░░░░██████████████████████
|
||||
Task 1 [Normal] │██████░░🡐 canceled
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
it('marks when a task errors', () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.unstable_advanceTime(300);
|
||||
throw Error('Oops');
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndThrow('Oops');
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │░░░░░░██████████████████████
|
||||
Task 1 [Normal] │██████🡐 errored
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
it('marks when multiple tasks are canceled', () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
|
||||
const task1 = scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.unstable_yieldValue(getProfilingInfo());
|
||||
Scheduler.unstable_advanceTime(300);
|
||||
Scheduler.unstable_yieldValue('Yield');
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue('Continuation');
|
||||
Scheduler.unstable_advanceTime(200);
|
||||
};
|
||||
});
|
||||
const task2 = scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.unstable_yieldValue(getProfilingInfo());
|
||||
Scheduler.unstable_advanceTime(300);
|
||||
Scheduler.unstable_yieldValue('Yield');
|
||||
return () => {
|
||||
Scheduler.unstable_yieldValue('Continuation');
|
||||
Scheduler.unstable_advanceTime(200);
|
||||
};
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYieldThrough([
|
||||
'Task: 1, Run: 1, Priority: Normal, Queue Size: 2',
|
||||
'Yield',
|
||||
]);
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
|
||||
cancelCallback(task1);
|
||||
cancelCallback(task2);
|
||||
|
||||
// Advance more time. This should not affect the size of the main
|
||||
// thread row, since the Scheduler queue is empty.
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
|
||||
// The main thread row should end when the callback is cancelled.
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │░░░░░░██████████████████████
|
||||
Task 1 [Normal] │██████░░🡐 canceled
|
||||
Task 2 [Normal] │░░░░░░░░🡐 canceled
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
it('handles cancelling a task that already finished', () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
|
||||
const task = scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.unstable_yieldValue('A');
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
});
|
||||
expect(Scheduler).toFlushAndYield(['A']);
|
||||
cancelCallback(task);
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │░░░░░░░░░░░░░░░░░░░░
|
||||
Task 1 [Normal] │████████████████████
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
it('handles cancelling a task multiple times', () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
|
||||
scheduleCallback(
|
||||
NormalPriority,
|
||||
() => {
|
||||
Scheduler.unstable_yieldValue('A');
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
},
|
||||
{label: 'A'},
|
||||
);
|
||||
Scheduler.unstable_advanceTime(200);
|
||||
const task = scheduleCallback(
|
||||
NormalPriority,
|
||||
() => {
|
||||
Scheduler.unstable_yieldValue('B');
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
},
|
||||
{label: 'B'},
|
||||
);
|
||||
Scheduler.unstable_advanceTime(400);
|
||||
cancelCallback(task);
|
||||
cancelCallback(task);
|
||||
cancelCallback(task);
|
||||
expect(Scheduler).toFlushAndYield(['A']);
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │████████████░░░░░░░░░░░░░░░░░░░░
|
||||
Task 1 [Normal] │░░░░░░░░░░░░████████████████████
|
||||
Task 2 [Normal] │ ░░░░░░░░🡐 canceled
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
it('handles cancelling a delayed task', () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
const task = scheduleCallback(
|
||||
NormalPriority,
|
||||
() => Scheduler.unstable_yieldValue('A'),
|
||||
{delay: 1000},
|
||||
);
|
||||
cancelCallback(task);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
it('resizes event log buffer if there are many events', () => {
|
||||
const tasks = [];
|
||||
for (let i = 0; i < 5000; i++) {
|
||||
tasks.push(scheduleCallback(NormalPriority, () => {}));
|
||||
}
|
||||
expect(getProfilingInfo()).toEqual('Suspended, Queue Size: 5000');
|
||||
tasks.forEach(task => cancelCallback(task));
|
||||
expect(getProfilingInfo()).toEqual('Empty Queue');
|
||||
});
|
||||
});
|
||||
@@ -13,3 +13,5 @@ export const {
|
||||
requestTimerEventBeforeFirstFrame,
|
||||
enableMessageLoopImplementation,
|
||||
} = require('SchedulerFeatureFlags');
|
||||
|
||||
export const enableProfiling = __PROFILE__;
|
||||
|
||||
@@ -53,8 +53,9 @@ if (
|
||||
}
|
||||
}
|
||||
};
|
||||
const initialTime = Date.now();
|
||||
getCurrentTime = function() {
|
||||
return Date.now();
|
||||
return Date.now() - initialTime;
|
||||
};
|
||||
requestHostCallback = function(cb) {
|
||||
if (_callback !== null) {
|
||||
@@ -111,10 +112,15 @@ if (
|
||||
typeof requestIdleCallback === 'function' &&
|
||||
typeof cancelIdleCallback === 'function';
|
||||
|
||||
getCurrentTime =
|
||||
typeof performance === 'object' && typeof performance.now === 'function'
|
||||
? () => performance.now()
|
||||
: () => Date.now();
|
||||
if (
|
||||
typeof performance === 'object' &&
|
||||
typeof performance.now === 'function'
|
||||
) {
|
||||
getCurrentTime = () => performance.now();
|
||||
} else {
|
||||
const initialTime = Date.now();
|
||||
getCurrentTime = () => Date.now() - initialTime;
|
||||
}
|
||||
|
||||
let isRAFLoopRunning = false;
|
||||
let isMessageLoopRunning = false;
|
||||
|
||||
@@ -10,26 +10,28 @@
|
||||
export type SideEffectTag = number;
|
||||
|
||||
// Don't change these two values. They're used by React Dev Tools.
|
||||
export const NoEffect = /* */ 0b000000000000;
|
||||
export const PerformedWork = /* */ 0b000000000001;
|
||||
export const NoEffect = /* */ 0b0000000000000;
|
||||
export const PerformedWork = /* */ 0b0000000000001;
|
||||
|
||||
// You can change the rest (and add more).
|
||||
export const Placement = /* */ 0b000000000010;
|
||||
export const Update = /* */ 0b000000000100;
|
||||
export const PlacementAndUpdate = /* */ 0b000000000110;
|
||||
export const Deletion = /* */ 0b000000001000;
|
||||
export const ContentReset = /* */ 0b000000010000;
|
||||
export const Callback = /* */ 0b000000100000;
|
||||
export const DidCapture = /* */ 0b000001000000;
|
||||
export const Ref = /* */ 0b000010000000;
|
||||
export const Snapshot = /* */ 0b000100000000;
|
||||
export const Passive = /* */ 0b001000000000;
|
||||
export const Placement = /* */ 0b0000000000010;
|
||||
export const Update = /* */ 0b0000000000100;
|
||||
export const PlacementAndUpdate = /* */ 0b0000000000110;
|
||||
export const Deletion = /* */ 0b0000000001000;
|
||||
export const ContentReset = /* */ 0b0000000010000;
|
||||
export const Callback = /* */ 0b0000000100000;
|
||||
export const DidCapture = /* */ 0b0000001000000;
|
||||
export const Ref = /* */ 0b0000010000000;
|
||||
export const Snapshot = /* */ 0b0000100000000;
|
||||
export const Passive = /* */ 0b0001000000000;
|
||||
export const Hydrating = /* */ 0b0010000000000;
|
||||
export const HydratingAndUpdate = /* */ 0b0010000000100;
|
||||
|
||||
// Passive & Update & Callback & Ref & Snapshot
|
||||
export const LifecycleEffectMask = /* */ 0b001110100100;
|
||||
export const LifecycleEffectMask = /* */ 0b0001110100100;
|
||||
|
||||
// Union of all host effects
|
||||
export const HostEffectMask = /* */ 0b001111111111;
|
||||
export const HostEffectMask = /* */ 0b0011111111111;
|
||||
|
||||
export const Incomplete = /* */ 0b010000000000;
|
||||
export const ShouldCapture = /* */ 0b100000000000;
|
||||
export const Incomplete = /* */ 0b0100000000000;
|
||||
export const ShouldCapture = /* */ 0b1000000000000;
|
||||
|
||||
@@ -411,7 +411,13 @@ const bundles = [
|
||||
|
||||
/******* React Scheduler (experimental) *******/
|
||||
{
|
||||
bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD],
|
||||
bundleTypes: [
|
||||
NODE_DEV,
|
||||
NODE_PROD,
|
||||
FB_WWW_DEV,
|
||||
FB_WWW_PROD,
|
||||
FB_WWW_PROFILING,
|
||||
],
|
||||
moduleType: ISOMORPHIC,
|
||||
entry: 'scheduler',
|
||||
global: 'Scheduler',
|
||||
|
||||
@@ -21,6 +21,11 @@ module.exports = {
|
||||
process: true,
|
||||
setImmediate: true,
|
||||
Buffer: true,
|
||||
|
||||
// Scheduler profiling
|
||||
SharedArrayBuffer: true,
|
||||
Int32Array: true,
|
||||
ArrayBuffer: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 5,
|
||||
|
||||
@@ -22,6 +22,11 @@ module.exports = {
|
||||
// Node.js Server Rendering
|
||||
setImmediate: true,
|
||||
Buffer: true,
|
||||
|
||||
// Scheduler profiling
|
||||
SharedArrayBuffer: true,
|
||||
Int32Array: true,
|
||||
ArrayBuffer: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 5,
|
||||
|
||||
@@ -21,6 +21,11 @@ module.exports = {
|
||||
// Fabric. See https://github.com/facebook/react/pull/15490
|
||||
// for more information
|
||||
nativeFabricUIManager: true,
|
||||
|
||||
// Scheduler profiling
|
||||
SharedArrayBuffer: true,
|
||||
Int32Array: true,
|
||||
ArrayBuffer: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 5,
|
||||
|
||||
@@ -24,6 +24,11 @@ module.exports = {
|
||||
define: true,
|
||||
require: true,
|
||||
global: true,
|
||||
|
||||
// Scheduler profiling
|
||||
SharedArrayBuffer: true,
|
||||
Int32Array: true,
|
||||
ArrayBuffer: true,
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 5,
|
||||
|
||||
Reference in New Issue
Block a user