56
attemptHydrationAtCurrentPriority,
57
} from 'react-reconciler/src/ReactFiberReconciler';
58
59
+import {enableHydrationChangeEvent} from 'shared/ReactFeatureFlags';
60
+
61
// TODO: Upgrade this definition once we're on a newer version of Flow that
62
// has this definition built-in.
61
-type PointerEvent = Event & {
63
+type PointerEventType = Event & {
64
pointerId: number,
65
relatedTarget: EventTarget | null,
66
...
86
const queuedPointerCaptures: Map<number, QueuedReplayableEvent> = new Map();
87
// We could consider replaying selectionchange and touchmoves too.
88
89
+const queuedChangeEventTargets: Array<EventTarget> = [];
90
+
91
type QueuedHydrationTarget = {
92
blockedOn: null | Container | ActivityInstance | SuspenseInstance,
93
target: Node,
168
break;
169
case 'pointerover':
170
case 'pointerout': {
167
- const pointerId = ((nativeEvent: any): PointerEvent).pointerId;
171
+ const pointerId = ((nativeEvent: any): PointerEventType).pointerId;
172
queuedPointers.delete(pointerId);
173
break;
174
}
175
case 'gotpointercapture':
176
case 'lostpointercapture': {
173
- const pointerId = ((nativeEvent: any): PointerEvent).pointerId;
177
+ const pointerId = ((nativeEvent: any): PointerEventType).pointerId;
178
queuedPointerCaptures.delete(pointerId);
179
break;
180
}
272
return true;
273
}
274
case 'pointerover': {
271
- const pointerEvent = ((nativeEvent: any): PointerEvent);
275
+ const pointerEvent = ((nativeEvent: any): PointerEventType);
276
const pointerId = pointerEvent.pointerId;
277
queuedPointers.set(
278
pointerId,
288
return true;
289
}
290
case 'gotpointercapture': {
287
- const pointerEvent = ((nativeEvent: any): PointerEvent);
291
+ const pointerEvent = ((nativeEvent: any): PointerEventType);
292
const pointerId = pointerEvent.pointerId;
293
queuedPointerCaptures.set(
294
pointerId,
425
}
426
}
427
428
+function replayChangeEvent(target: EventTarget): void {
429
+ // Dispatch a fake "change" event for the input.
430
+ const element: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =
431
+ (target: any);
432
+ if (element.nodeName === 'INPUT') {
433
+ if (element.type === 'checkbox' || element.type === 'radio') {
434
+ // Checkboxes always fire a click event regardless of how the change was made.
435
+ const EventCtr =
436
+ typeof PointerEvent === 'function' ? PointerEvent : Event;
437
+ target.dispatchEvent(new EventCtr('click', {bubbles: true}));
438
+ // For checkboxes the input event uses the Event constructor instead of InputEvent.
439
+ target.dispatchEvent(new Event('input', {bubbles: true}));
440
+ } else {
441
+ if (typeof InputEvent === 'function') {
442
+ target.dispatchEvent(new InputEvent('input', {bubbles: true}));
443
+ }
444
+ }
445
+ } else if (element.nodeName === 'TEXTAREA') {
446
+ if (typeof InputEvent === 'function') {
447
+ target.dispatchEvent(new InputEvent('input', {bubbles: true}));
448
+ }
449
+ }
450
+ target.dispatchEvent(new Event('change', {bubbles: true}));
451
+}
452
+
453
function replayUnblockedEvents() {
454
hasScheduledReplayAttempt = false;
455
// Replay any continuous events.
464
}
465
queuedPointers.forEach(attemptReplayContinuousQueuedEventInMap);
466
queuedPointerCaptures.forEach(attemptReplayContinuousQueuedEventInMap);
467
+ if (enableHydrationChangeEvent) {
468
+ for (let i = 0; i < queuedChangeEventTargets.length; i++) {
469
+ replayChangeEvent(queuedChangeEventTargets[i]);
470
+ }
471
+ queuedChangeEventTargets.length = 0;
472
+ }
473
+}
474
+
475
+export function queueChangeEvent(target: EventTarget): void {
476
+ if (enableHydrationChangeEvent) {
477
+ queuedChangeEventTargets.push(target);
478
+ if (!hasScheduledReplayAttempt) {
479
+ hasScheduledReplayAttempt = true;
480
+ scheduleCallback(NormalPriority, replayUnblockedEvents);
481
+ }
482
+ }
483
}
484
485
function scheduleCallbackIfUnblocked(