Skip to content

Commit 83bcaab

Browse files
committed
Add support for newState and oldState
1 parent b394c00 commit 83bcaab

File tree

3 files changed

+134
-0
lines changed

3 files changed

+134
-0
lines changed

packages/react-dom-bindings/src/events/SyntheticEvent.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,3 +592,11 @@ const WheelEventInterface = {
592592
};
593593
export const SyntheticWheelEvent: $FlowFixMe =
594594
createSyntheticEvent(WheelEventInterface);
595+
596+
const ToggleEventInterface = {
597+
...EventInterface,
598+
newState: '',
599+
oldState: '',
600+
};
601+
export const SyntheticToggleEvent: EventInterfaceType =
602+
createSyntheticEvent(ToggleEventInterface);

packages/react-dom-bindings/src/events/plugins/SimpleEventPlugin.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
SyntheticWheelEvent,
2828
SyntheticClipboardEvent,
2929
SyntheticPointerEvent,
30+
SyntheticToggleEvent,
3031
} from '../../events/SyntheticEvent';
3132

3233
import {
@@ -161,6 +162,11 @@ function extractEvents(
161162
case 'pointerup':
162163
SyntheticEventCtor = SyntheticPointerEvent;
163164
break;
165+
case 'toggle':
166+
case 'beforetoggle':
167+
// MDN claims <details> should not receive ToggleEvent contradicting the spec: https://html.spec.whatwg.org/multipage/indices.html#event-toggle
168+
SyntheticEventCtor = SyntheticToggleEvent;
169+
break;
164170
default:
165171
// Unknown event. This is used by createEventHandle.
166172
break;

packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@
99

1010
'use strict';
1111

12+
// polyfill missing JSDOM support
13+
class ToggleEvent extends Event {
14+
constructor(type, eventInit) {
15+
super(type, eventInit);
16+
this.newState = eventInit.newState;
17+
this.oldState = eventInit.oldState;
18+
}
19+
}
20+
1221
describe('SimpleEventPlugin', function () {
1322
let React;
1423
let ReactDOMClient;
@@ -469,5 +478,116 @@ describe('SimpleEventPlugin', function () {
469478
'wheel',
470479
]);
471480
});
481+
482+
it('dispatches synthetic toggle events when the Popover API is used', async () => {
483+
container = document.createElement('div');
484+
485+
const onToggle = jest.fn();
486+
const root = ReactDOMClient.createRoot(container);
487+
await act(() => {
488+
root.render(
489+
<>
490+
<button popoverTargetElement="popover">Toggle popover</button>
491+
<div id="popover" popover="" onToggle={onToggle}>
492+
popover content
493+
</div>
494+
</>,
495+
);
496+
});
497+
498+
const target = container.querySelector('#popover');
499+
target.dispatchEvent(
500+
new ToggleEvent('toggle', {
501+
bubbles: false,
502+
cancelable: true,
503+
oldState: 'closed',
504+
newState: 'open',
505+
}),
506+
);
507+
508+
expect(onToggle).toHaveBeenCalledTimes(1);
509+
let event = onToggle.mock.calls[0][0];
510+
expect(event).toEqual(
511+
expect.objectContaining({
512+
oldState: 'closed',
513+
newState: 'open',
514+
}),
515+
);
516+
517+
target.dispatchEvent(
518+
new ToggleEvent('toggle', {
519+
bubbles: false,
520+
cancelable: true,
521+
oldState: 'open',
522+
newState: 'closed',
523+
}),
524+
);
525+
526+
expect(onToggle).toHaveBeenCalledTimes(2);
527+
event = onToggle.mock.calls[1][0];
528+
expect(event).toEqual(
529+
expect.objectContaining({
530+
oldState: 'open',
531+
newState: 'closed',
532+
}),
533+
);
534+
});
535+
536+
it('dispatches synthetic toggle events when <details> is used', async () => {
537+
// This test just replays browser behavior.
538+
// The real test would be if browsers dispatch ToggleEvent on <details>.
539+
// This case only exists because MDN claims <details> doesn't receive ToggleEvent.
540+
// However, Chrome dispatches ToggleEvent on <details> and the spec confirms that behavior: https://html.spec.whatwg.org/multipage/indices.html#event-toggle
541+
542+
container = document.createElement('div');
543+
544+
const onToggle = jest.fn();
545+
const root = ReactDOMClient.createRoot(container);
546+
await act(() => {
547+
root.render(
548+
<details id="details" onToggle={onToggle}>
549+
<summary>Summary</summary>
550+
Details
551+
</details>,
552+
);
553+
});
554+
555+
const target = container.querySelector('#details');
556+
target.dispatchEvent(
557+
new ToggleEvent('toggle', {
558+
bubbles: false,
559+
cancelable: true,
560+
oldState: 'closed',
561+
newState: 'open',
562+
}),
563+
);
564+
565+
expect(onToggle).toHaveBeenCalledTimes(1);
566+
let event = onToggle.mock.calls[0][0];
567+
expect(event).toEqual(
568+
expect.objectContaining({
569+
oldState: 'closed',
570+
newState: 'open',
571+
}),
572+
);
573+
574+
target.dispatchEvent(
575+
new ToggleEvent('toggle', {
576+
bubbles: false,
577+
cancelable: true,
578+
oldState: 'open',
579+
newState: 'closed',
580+
}),
581+
);
582+
583+
expect(onToggle).toHaveBeenCalledTimes(2);
584+
event = onToggle.mock.calls[1][0];
585+
expect(event).toEqual(
586+
expect.objectContaining({
587+
oldState: 'open',
588+
newState: 'closed',
589+
}),
590+
);
591+
});
472592
});
473593
});

0 commit comments

Comments
 (0)