Commit 4bcf67e

Browse files
authored
Support onGestureEnter/Exit/Share/Update events (#35556)
This is like the onEnter/Exit/Share/Update events but for gestures. It allows manually controlling the animation using the passed timeline.
1 parent 41b3e9a commit 4bcf67e

5 files changed

Lines changed: 154 additions & 6 deletions

File tree

‎fixtures/view-transition/src/components/Page.js‎

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,48 @@ export default function Page({url, navigate}) {
8686
viewTransition.new.animate(keyframes,250);
8787
}
8888

89+
functiononGestureTransition(
90+
timeline,
91+
{rangeStart, rangeEnd},
92+
viewTransition,
93+
types
94+
){
95+
constkeyframes=[
96+
{rotate: '0deg',transformOrigin: '30px 8px'},
97+
{rotate: '360deg',transformOrigin: '30px 8px'},
98+
];
99+
constreverse=rangeStart>rangeEnd;
100+
if(timelineinstanceofAnimationTimeline){
101+
// Native Timeline
102+
constoptions={
103+
timeline: timeline,
104+
direction: reverse ? 'normal' : 'reverse',
105+
rangeStart: (reverse ? rangeEnd : rangeStart)+'%',
106+
rangeEnd: (reverse ? rangeStart : rangeEnd)+'%',
107+
};
108+
viewTransition.old.animate(keyframes,options);
109+
viewTransition.new.animate(keyframes,options);
110+
}else{
111+
// Custom Timeline
112+
constoptions={
113+
direction: reverse ? 'normal' : 'reverse',
114+
// We set the delay and duration to represent the span of the range.
115+
delay: reverse ? rangeEnd : rangeStart,
116+
duration: reverse ? rangeStart-rangeEnd : rangeEnd-rangeStart,
117+
};
118+
constanimation1=viewTransition.old.animate(keyframes,options);
119+
constanimation2=viewTransition.new.animate(keyframes,options);
120+
// Let the custom timeline take control of driving the animations.
121+
constcleanup1=timeline.animate(animation1);
122+
constcleanup2=timeline.animate(animation2);
123+
// TODO: Support returning a clean up function from ViewTransition events.
124+
// return () => {
125+
// cleanup1();
126+
// cleanup2();
127+
// };
128+
}
129+
}
130+
89131
functionswipeAction(){
90132
navigate(show ? '/?a' : '/?b');
91133
}
@@ -131,7 +173,10 @@ export default function Page({url, navigate}) {
131173
);
132174

133175
constexclamation=(
134-
<ViewTransitionname="exclamation"onShare={onTransition}>
176+
<ViewTransition
177+
name="exclamation"
178+
onShare={onTransition}
179+
onGestureShare={onGestureTransition}>
135180
<span>
136181
<div>!</div>
137182
</span>

‎packages/react-reconciler/src/ReactFiberApplyGesture.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import {
8282
enableComponentPerformanceTrack,
8383
}from'shared/ReactFeatureFlags';
8484
import{trackAnimatingTask}from'./ReactProfilerTimer';
85+
import{scheduleGestureTransitionEvent}from'./ReactFiberWorkLoop';
8586

8687
letdidWarnForRootClone=false;
8788

@@ -280,6 +281,7 @@ function applyAppearingPairViewTransition(child: Fiber): void {
280281
if(clones!==null){
281282
applyViewTransitionToClones(name,className,clones,child);
282283
}
284+
scheduleGestureTransitionEvent(child,props.onGestureShare);
283285
}
284286
}
285287
}
@@ -310,6 +312,11 @@ function applyExitViewTransition(placement: Fiber): void {
310312
if(clones!==null){
311313
applyViewTransitionToClones(name,className,clones,placement);
312314
}
315+
if(state.paired){
316+
scheduleGestureTransitionEvent(placement,props.onGestureShare);
317+
}else{
318+
scheduleGestureTransitionEvent(placement,props.onGestureExit);
319+
}
313320
}
314321
}
315322

@@ -1123,7 +1130,8 @@ function applyViewTransitionsOnFiber(finishedWork: Fiber) {
11231130
// TODO: If this doesn't end up canceled, because a parent animates,
11241131
// then we should probably issue an event since this instance is part of it.
11251132
}else{
1126-
// TODO: Schedule gesture events.
1133+
constprops: ViewTransitionProps=finishedWork.memoizedProps;
1134+
scheduleGestureTransitionEvent(finishedWork,props.onGestureUpdate);
11271135
// If this boundary did update, we cannot cancel its children so those are dropped.
11281136
popViewTransitionCancelableScope(prevCancelableChildren);
11291137
}

‎packages/react-reconciler/src/ReactFiberCommitViewTransitions.js‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ import {
3434
hasInstanceAffectedParent,
3535
wasInstanceInViewport,
3636
}from'./ReactFiberConfig';
37-
import{scheduleViewTransitionEvent}from'./ReactFiberWorkLoop';
37+
import{
38+
scheduleViewTransitionEvent,
39+
scheduleGestureTransitionEvent,
40+
}from'./ReactFiberWorkLoop';
3841
import{
3942
getViewTransitionName,
4043
getViewTransitionClassName,
@@ -312,7 +315,7 @@ export function commitEnterViewTransitions(
312315

313316
if(!state.paired){
314317
if(gesture){
315-
// TODO: Schedule gesture events.
318+
scheduleGestureTransitionEvent(placement,props.onGestureEnter);
316319
}else{
317320
scheduleViewTransitionEvent(placement,props.onEnter);
318321
}
@@ -848,7 +851,7 @@ export function measureNestedViewTransitions(
848851
// Nothing changed.
849852
}else{
850853
if(gesture){
851-
// TODO: Schedule gesture events.
854+
scheduleGestureTransitionEvent(child,props.onGestureUpdate);
852855
}else{
853856
scheduleViewTransitionEvent(child,props.onUpdate);
854857
}

‎packages/react-reconciler/src/ReactFiberWorkLoop.js‎

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99

1010
import{REACT_STRICT_MODE_TYPE}from'shared/ReactSymbols';
1111

12-
importtype{Wakeable,Thenable}from'shared/ReactTypes';
12+
importtype{
13+
Wakeable,
14+
Thenable,
15+
GestureOptionsRequired,
16+
}from'shared/ReactTypes';
1317
importtype{Fiber,FiberRoot}from'./ReactInternalTypes';
1418
importtype{Lanes,Lane}from'./ReactFiberLane';
1519
importtype{ActivityState}from'./ReactFiberActivityComponent';
@@ -26,6 +30,7 @@ import type {
2630
Resource,
2731
ViewTransitionInstance,
2832
RunningViewTransition,
33+
GestureTimeline,
2934
SuspendedState,
3035
}from'./ReactFiberConfig';
3136
importtype{RootState}from'./ReactFiberRoot';
@@ -913,6 +918,42 @@ export function scheduleViewTransitionEvent(
913918
}
914919
}
915920

921+
exportfunctionscheduleGestureTransitionEvent(
922+
fiber: Fiber,
923+
callback: ?(
924+
timeline: GestureTimeline,
925+
options: GestureOptionsRequired,
926+
instance: ViewTransitionInstance,
927+
types: Array<string>,
928+
)=>void,
929+
): void{
930+
if(enableGestureTransition){
931+
if(callback!=null){
932+
constapplyingGesture=pendingEffectsRoot.pendingGestures;
933+
if(applyingGesture!==null){
934+
conststate: ViewTransitionState=fiber.stateNode;
935+
letinstance=state.ref;
936+
if(instance===null){
937+
instance=state.ref=createViewTransitionInstance(
938+
getViewTransitionName(fiber.memoizedProps,state),
939+
);
940+
}
941+
consttimeline=applyingGesture.provider;
942+
constoptions={
943+
rangeStart: applyingGesture.rangeStart,
944+
rangeEnd: applyingGesture.rangeEnd,
945+
};
946+
if(pendingViewTransitionEvents===null){
947+
pendingViewTransitionEvents=[];
948+
}
949+
pendingViewTransitionEvents.push(
950+
callback.bind(null,timeline,options,instance),
951+
);
952+
}
953+
}
954+
}
955+
}
956+
916957
exportfunctionpeekDeferredLane(): Lane{
917958
returnworkInProgressDeferredLane;
918959
}
@@ -4352,6 +4393,8 @@ function applyGestureOnRoot(
43524393
startAnimating(pendingEffectsLanes);
43534394
}
43544395

4396+
pendingViewTransitionEvents=null;
4397+
43554398
constprevTransition=ReactSharedInternals.T;
43564399
ReactSharedInternals.T=null;
43574400
constpreviousPriority=getCurrentUpdatePriority();
@@ -4476,6 +4519,26 @@ function flushGestureAnimations(): void {
44764519
ReactSharedInternals.T=prevTransition;
44774520
}
44784521

4522+
if(enableViewTransition){
4523+
// We should now be after the startGestureTransition's .ready call which is late enough
4524+
// to start animating any pseudo-elements. We have also already applied any adjustments
4525+
// we do to the built-in animations which can now be read by the refs.
4526+
constpendingEvents=pendingViewTransitionEvents;
4527+
letpendingTypes=pendingTransitionTypes;
4528+
pendingTransitionTypes=null;
4529+
if(pendingEvents!==null){
4530+
pendingViewTransitionEvents=null;
4531+
if(pendingTypes===null){
4532+
// Normalize the type. This is lazily created only for events.
4533+
pendingTypes=[];
4534+
}
4535+
for(leti=0;i<pendingEvents.length;i++){
4536+
const viewTransitionEvent =pendingEvents[i];
4537+
viewTransitionEvent(pendingTypes);
4538+
}
4539+
}
4540+
}
4541+
44794542
if(enableProfilerTimer&&enableComponentPerformanceTrack){
44804543
finalizeRender(lanes,commitEndTime);
44814544
}

‎packages/shared/ReactTypes.js‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@ export type ViewTransitionClass =
290290
| string
291291
| ViewTransitionClassPerType;
292292

293+
export type GestureOptionsRequired = {
294+
rangeStart: number,
295+
rangeEnd: number,
296+
};
297+
293298
export type ViewTransitionProps = {
294299
name?: string,
295300
children?: ReactNodeList,
@@ -302,6 +307,30 @@ export type ViewTransitionProps = {
302307
onExit?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
303308
onShare?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
304309
onUpdate?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
310+
onGestureEnter?: (
311+
timeline: GestureProvider,
312+
options: GestureOptionsRequired,
313+
instance: ViewTransitionInstance,
314+
types: Array<string>,
315+
)=>void,
316+
onGestureExit?: (
317+
timeline: GestureProvider,
318+
options: GestureOptionsRequired,
319+
instance: ViewTransitionInstance,
320+
types: Array<string>,
321+
)=>void,
322+
onGestureShare?: (
323+
timeline: GestureProvider,
324+
options: GestureOptionsRequired,
325+
instance: ViewTransitionInstance,
326+
types: Array<string>,
327+
)=>void,
328+
onGestureUpdate?: (
329+
timeline: GestureProvider,
330+
options: GestureOptionsRequired,
331+
instance: ViewTransitionInstance,
332+
types: Array<string>,
333+
)=>void,
305334
};
306335

307336
export type ActivityProps = {

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit 4bcf67e

Browse files
authored
Support onGestureEnter/Exit/Share/Update events (#35556)
This is like the onEnter/Exit/Share/Update events but for gestures. It allows manually controlling the animation using the passed timeline.
1 parent 41b3e9a commit 4bcf67e

5 files changed

Lines changed: 154 additions & 6 deletions

File tree

‎fixtures/view-transition/src/components/Page.js‎

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,48 @@ export default function Page({url, navigate}) {
8686
viewTransition.new.animate(keyframes,250);
8787
}
8888

89+
functiononGestureTransition(
90+
timeline,
91+
{rangeStart, rangeEnd},
92+
viewTransition,
93+
types
94+
){
95+
constkeyframes=[
96+
{rotate: '0deg',transformOrigin: '30px 8px'},
97+
{rotate: '360deg',transformOrigin: '30px 8px'},
98+
];
99+
constreverse=rangeStart>rangeEnd;
100+
if(timelineinstanceofAnimationTimeline){
101+
// Native Timeline
102+
constoptions={
103+
timeline: timeline,
104+
direction: reverse ? 'normal' : 'reverse',
105+
rangeStart: (reverse ? rangeEnd : rangeStart)+'%',
106+
rangeEnd: (reverse ? rangeStart : rangeEnd)+'%',
107+
};
108+
viewTransition.old.animate(keyframes,options);
109+
viewTransition.new.animate(keyframes,options);
110+
}else{
111+
// Custom Timeline
112+
constoptions={
113+
direction: reverse ? 'normal' : 'reverse',
114+
// We set the delay and duration to represent the span of the range.
115+
delay: reverse ? rangeEnd : rangeStart,
116+
duration: reverse ? rangeStart-rangeEnd : rangeEnd-rangeStart,
117+
};
118+
constanimation1=viewTransition.old.animate(keyframes,options);
119+
constanimation2=viewTransition.new.animate(keyframes,options);
120+
// Let the custom timeline take control of driving the animations.
121+
constcleanup1=timeline.animate(animation1);
122+
constcleanup2=timeline.animate(animation2);
123+
// TODO: Support returning a clean up function from ViewTransition events.
124+
// return () => {
125+
// cleanup1();
126+
// cleanup2();
127+
// };
128+
}
129+
}
130+
89131
functionswipeAction(){
90132
navigate(show ? '/?a' : '/?b');
91133
}
@@ -131,7 +173,10 @@ export default function Page({url, navigate}) {
131173
);
132174

133175
constexclamation=(
134-
<ViewTransitionname="exclamation"onShare={onTransition}>
176+
<ViewTransition
177+
name="exclamation"
178+
onShare={onTransition}
179+
onGestureShare={onGestureTransition}>
135180
<span>
136181
<div>!</div>
137182
</span>

‎packages/react-reconciler/src/ReactFiberApplyGesture.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import {
8282
enableComponentPerformanceTrack,
8383
}from'shared/ReactFeatureFlags';
8484
import{trackAnimatingTask}from'./ReactProfilerTimer';
85+
import{scheduleGestureTransitionEvent}from'./ReactFiberWorkLoop';
8586

8687
letdidWarnForRootClone=false;
8788

@@ -280,6 +281,7 @@ function applyAppearingPairViewTransition(child: Fiber): void {
280281
if(clones!==null){
281282
applyViewTransitionToClones(name,className,clones,child);
282283
}
284+
scheduleGestureTransitionEvent(child,props.onGestureShare);
283285
}
284286
}
285287
}
@@ -310,6 +312,11 @@ function applyExitViewTransition(placement: Fiber): void {
310312
if(clones!==null){
311313
applyViewTransitionToClones(name,className,clones,placement);
312314
}
315+
if(state.paired){
316+
scheduleGestureTransitionEvent(placement,props.onGestureShare);
317+
}else{
318+
scheduleGestureTransitionEvent(placement,props.onGestureExit);
319+
}
313320
}
314321
}
315322

@@ -1123,7 +1130,8 @@ function applyViewTransitionsOnFiber(finishedWork: Fiber) {
11231130
// TODO: If this doesn't end up canceled, because a parent animates,
11241131
// then we should probably issue an event since this instance is part of it.
11251132
}else{
1126-
// TODO: Schedule gesture events.
1133+
constprops: ViewTransitionProps=finishedWork.memoizedProps;
1134+
scheduleGestureTransitionEvent(finishedWork,props.onGestureUpdate);
11271135
// If this boundary did update, we cannot cancel its children so those are dropped.
11281136
popViewTransitionCancelableScope(prevCancelableChildren);
11291137
}

‎packages/react-reconciler/src/ReactFiberCommitViewTransitions.js‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ import {
3434
hasInstanceAffectedParent,
3535
wasInstanceInViewport,
3636
}from'./ReactFiberConfig';
37-
import{scheduleViewTransitionEvent}from'./ReactFiberWorkLoop';
37+
import{
38+
scheduleViewTransitionEvent,
39+
scheduleGestureTransitionEvent,
40+
}from'./ReactFiberWorkLoop';
3841
import{
3942
getViewTransitionName,
4043
getViewTransitionClassName,
@@ -312,7 +315,7 @@ export function commitEnterViewTransitions(
312315

313316
if(!state.paired){
314317
if(gesture){
315-
// TODO: Schedule gesture events.
318+
scheduleGestureTransitionEvent(placement,props.onGestureEnter);
316319
}else{
317320
scheduleViewTransitionEvent(placement,props.onEnter);
318321
}
@@ -848,7 +851,7 @@ export function measureNestedViewTransitions(
848851
// Nothing changed.
849852
}else{
850853
if(gesture){
851-
// TODO: Schedule gesture events.
854+
scheduleGestureTransitionEvent(child,props.onGestureUpdate);
852855
}else{
853856
scheduleViewTransitionEvent(child,props.onUpdate);
854857
}

‎packages/react-reconciler/src/ReactFiberWorkLoop.js‎

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99

1010
import{REACT_STRICT_MODE_TYPE}from'shared/ReactSymbols';
1111

12-
importtype{Wakeable,Thenable}from'shared/ReactTypes';
12+
importtype{
13+
Wakeable,
14+
Thenable,
15+
GestureOptionsRequired,
16+
}from'shared/ReactTypes';
1317
importtype{Fiber,FiberRoot}from'./ReactInternalTypes';
1418
importtype{Lanes,Lane}from'./ReactFiberLane';
1519
importtype{ActivityState}from'./ReactFiberActivityComponent';
@@ -26,6 +30,7 @@ import type {
2630
Resource,
2731
ViewTransitionInstance,
2832
RunningViewTransition,
33+
GestureTimeline,
2934
SuspendedState,
3035
}from'./ReactFiberConfig';
3136
importtype{RootState}from'./ReactFiberRoot';
@@ -913,6 +918,42 @@ export function scheduleViewTransitionEvent(
913918
}
914919
}
915920

921+
exportfunctionscheduleGestureTransitionEvent(
922+
fiber: Fiber,
923+
callback: ?(
924+
timeline: GestureTimeline,
925+
options: GestureOptionsRequired,
926+
instance: ViewTransitionInstance,
927+
types: Array<string>,
928+
)=>void,
929+
): void{
930+
if(enableGestureTransition){
931+
if(callback!=null){
932+
constapplyingGesture=pendingEffectsRoot.pendingGestures;
933+
if(applyingGesture!==null){
934+
conststate: ViewTransitionState=fiber.stateNode;
935+
letinstance=state.ref;
936+
if(instance===null){
937+
instance=state.ref=createViewTransitionInstance(
938+
getViewTransitionName(fiber.memoizedProps,state),
939+
);
940+
}
941+
consttimeline=applyingGesture.provider;
942+
constoptions={
943+
rangeStart: applyingGesture.rangeStart,
944+
rangeEnd: applyingGesture.rangeEnd,
945+
};
946+
if(pendingViewTransitionEvents===null){
947+
pendingViewTransitionEvents=[];
948+
}
949+
pendingViewTransitionEvents.push(
950+
callback.bind(null,timeline,options,instance),
951+
);
952+
}
953+
}
954+
}
955+
}
956+
916957
exportfunctionpeekDeferredLane(): Lane{
917958
returnworkInProgressDeferredLane;
918959
}
@@ -4352,6 +4393,8 @@ function applyGestureOnRoot(
43524393
startAnimating(pendingEffectsLanes);
43534394
}
43544395

4396+
pendingViewTransitionEvents=null;
4397+
43554398
constprevTransition=ReactSharedInternals.T;
43564399
ReactSharedInternals.T=null;
43574400
constpreviousPriority=getCurrentUpdatePriority();
@@ -4476,6 +4519,26 @@ function flushGestureAnimations(): void {
44764519
ReactSharedInternals.T=prevTransition;
44774520
}
44784521

4522+
if(enableViewTransition){
4523+
// We should now be after the startGestureTransition's .ready call which is late enough
4524+
// to start animating any pseudo-elements. We have also already applied any adjustments
4525+
// we do to the built-in animations which can now be read by the refs.
4526+
constpendingEvents=pendingViewTransitionEvents;
4527+
letpendingTypes=pendingTransitionTypes;
4528+
pendingTransitionTypes=null;
4529+
if(pendingEvents!==null){
4530+
pendingViewTransitionEvents=null;
4531+
if(pendingTypes===null){
4532+
// Normalize the type. This is lazily created only for events.
4533+
pendingTypes=[];
4534+
}
4535+
for(leti=0;i<pendingEvents.length;i++){
4536+
const viewTransitionEvent =pendingEvents[i];
4537+
viewTransitionEvent(pendingTypes);
4538+
}
4539+
}
4540+
}
4541+
44794542
if(enableProfilerTimer&&enableComponentPerformanceTrack){
44804543
finalizeRender(lanes,commitEndTime);
44814544
}

‎packages/shared/ReactTypes.js‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@ export type ViewTransitionClass =
290290
| string
291291
| ViewTransitionClassPerType;
292292

293+
export type GestureOptionsRequired = {
294+
rangeStart: number,
295+
rangeEnd: number,
296+
};
297+
293298
export type ViewTransitionProps = {
294299
name?: string,
295300
children?: ReactNodeList,
@@ -302,6 +307,30 @@ export type ViewTransitionProps = {
302307
onExit?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
303308
onShare?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
304309
onUpdate?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
310+
onGestureEnter?: (
311+
timeline: GestureProvider,
312+
options: GestureOptionsRequired,
313+
instance: ViewTransitionInstance,
314+
types: Array<string>,
315+
)=>void,
316+
onGestureExit?: (
317+
timeline: GestureProvider,
318+
options: GestureOptionsRequired,
319+
instance: ViewTransitionInstance,
320+
types: Array<string>,
321+
)=>void,
322+
onGestureShare?: (
323+
timeline: GestureProvider,
324+
options: GestureOptionsRequired,
325+
instance: ViewTransitionInstance,
326+
types: Array<string>,
327+
)=>void,
328+
onGestureUpdate?: (
329+
timeline: GestureProvider,
330+
options: GestureOptionsRequired,
331+
instance: ViewTransitionInstance,
332+
types: Array<string>,
333+
)=>void,
305334
};
306335

307336
export type ActivityProps = {

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 4bcf67e

Browse files
authored
Support onGestureEnter/Exit/Share/Update events (#35556)
This is like the onEnter/Exit/Share/Update events but for gestures. It allows manually controlling the animation using the passed timeline.
1 parent 41b3e9a commit 4bcf67e

5 files changed

Lines changed: 154 additions & 6 deletions

File tree

‎fixtures/view-transition/src/components/Page.js‎

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,48 @@ export default function Page({url, navigate}) {
8686
viewTransition.new.animate(keyframes,250);
8787
}
8888

89+
functiononGestureTransition(
90+
timeline,
91+
{rangeStart, rangeEnd},
92+
viewTransition,
93+
types
94+
){
95+
constkeyframes=[
96+
{rotate: '0deg',transformOrigin: '30px 8px'},
97+
{rotate: '360deg',transformOrigin: '30px 8px'},
98+
];
99+
constreverse=rangeStart>rangeEnd;
100+
if(timelineinstanceofAnimationTimeline){
101+
// Native Timeline
102+
constoptions={
103+
timeline: timeline,
104+
direction: reverse ? 'normal' : 'reverse',
105+
rangeStart: (reverse ? rangeEnd : rangeStart)+'%',
106+
rangeEnd: (reverse ? rangeStart : rangeEnd)+'%',
107+
};
108+
viewTransition.old.animate(keyframes,options);
109+
viewTransition.new.animate(keyframes,options);
110+
}else{
111+
// Custom Timeline
112+
constoptions={
113+
direction: reverse ? 'normal' : 'reverse',
114+
// We set the delay and duration to represent the span of the range.
115+
delay: reverse ? rangeEnd : rangeStart,
116+
duration: reverse ? rangeStart-rangeEnd : rangeEnd-rangeStart,
117+
};
118+
constanimation1=viewTransition.old.animate(keyframes,options);
119+
constanimation2=viewTransition.new.animate(keyframes,options);
120+
// Let the custom timeline take control of driving the animations.
121+
constcleanup1=timeline.animate(animation1);
122+
constcleanup2=timeline.animate(animation2);
123+
// TODO: Support returning a clean up function from ViewTransition events.
124+
// return () => {
125+
// cleanup1();
126+
// cleanup2();
127+
// };
128+
}
129+
}
130+
89131
functionswipeAction(){
90132
navigate(show ? '/?a' : '/?b');
91133
}
@@ -131,7 +173,10 @@ export default function Page({url, navigate}) {
131173
);
132174

133175
constexclamation=(
134-
<ViewTransitionname="exclamation"onShare={onTransition}>
176+
<ViewTransition
177+
name="exclamation"
178+
onShare={onTransition}
179+
onGestureShare={onGestureTransition}>
135180
<span>
136181
<div>!</div>
137182
</span>

‎packages/react-reconciler/src/ReactFiberApplyGesture.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import {
8282
enableComponentPerformanceTrack,
8383
}from'shared/ReactFeatureFlags';
8484
import{trackAnimatingTask}from'./ReactProfilerTimer';
85+
import{scheduleGestureTransitionEvent}from'./ReactFiberWorkLoop';
8586

8687
letdidWarnForRootClone=false;
8788

@@ -280,6 +281,7 @@ function applyAppearingPairViewTransition(child: Fiber): void {
280281
if(clones!==null){
281282
applyViewTransitionToClones(name,className,clones,child);
282283
}
284+
scheduleGestureTransitionEvent(child,props.onGestureShare);
283285
}
284286
}
285287
}
@@ -310,6 +312,11 @@ function applyExitViewTransition(placement: Fiber): void {
310312
if(clones!==null){
311313
applyViewTransitionToClones(name,className,clones,placement);
312314
}
315+
if(state.paired){
316+
scheduleGestureTransitionEvent(placement,props.onGestureShare);
317+
}else{
318+
scheduleGestureTransitionEvent(placement,props.onGestureExit);
319+
}
313320
}
314321
}
315322

@@ -1123,7 +1130,8 @@ function applyViewTransitionsOnFiber(finishedWork: Fiber) {
11231130
// TODO: If this doesn't end up canceled, because a parent animates,
11241131
// then we should probably issue an event since this instance is part of it.
11251132
}else{
1126-
// TODO: Schedule gesture events.
1133+
constprops: ViewTransitionProps=finishedWork.memoizedProps;
1134+
scheduleGestureTransitionEvent(finishedWork,props.onGestureUpdate);
11271135
// If this boundary did update, we cannot cancel its children so those are dropped.
11281136
popViewTransitionCancelableScope(prevCancelableChildren);
11291137
}

‎packages/react-reconciler/src/ReactFiberCommitViewTransitions.js‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ import {
3434
hasInstanceAffectedParent,
3535
wasInstanceInViewport,
3636
}from'./ReactFiberConfig';
37-
import{scheduleViewTransitionEvent}from'./ReactFiberWorkLoop';
37+
import{
38+
scheduleViewTransitionEvent,
39+
scheduleGestureTransitionEvent,
40+
}from'./ReactFiberWorkLoop';
3841
import{
3942
getViewTransitionName,
4043
getViewTransitionClassName,
@@ -312,7 +315,7 @@ export function commitEnterViewTransitions(
312315

313316
if(!state.paired){
314317
if(gesture){
315-
// TODO: Schedule gesture events.
318+
scheduleGestureTransitionEvent(placement,props.onGestureEnter);
316319
}else{
317320
scheduleViewTransitionEvent(placement,props.onEnter);
318321
}
@@ -848,7 +851,7 @@ export function measureNestedViewTransitions(
848851
// Nothing changed.
849852
}else{
850853
if(gesture){
851-
// TODO: Schedule gesture events.
854+
scheduleGestureTransitionEvent(child,props.onGestureUpdate);
852855
}else{
853856
scheduleViewTransitionEvent(child,props.onUpdate);
854857
}

‎packages/react-reconciler/src/ReactFiberWorkLoop.js‎

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99

1010
import{REACT_STRICT_MODE_TYPE}from'shared/ReactSymbols';
1111

12-
importtype{Wakeable,Thenable}from'shared/ReactTypes';
12+
importtype{
13+
Wakeable,
14+
Thenable,
15+
GestureOptionsRequired,
16+
}from'shared/ReactTypes';
1317
importtype{Fiber,FiberRoot}from'./ReactInternalTypes';
1418
importtype{Lanes,Lane}from'./ReactFiberLane';
1519
importtype{ActivityState}from'./ReactFiberActivityComponent';
@@ -26,6 +30,7 @@ import type {
2630
Resource,
2731
ViewTransitionInstance,
2832
RunningViewTransition,
33+
GestureTimeline,
2934
SuspendedState,
3035
}from'./ReactFiberConfig';
3136
importtype{RootState}from'./ReactFiberRoot';
@@ -913,6 +918,42 @@ export function scheduleViewTransitionEvent(
913918
}
914919
}
915920

921+
exportfunctionscheduleGestureTransitionEvent(
922+
fiber: Fiber,
923+
callback: ?(
924+
timeline: GestureTimeline,
925+
options: GestureOptionsRequired,
926+
instance: ViewTransitionInstance,
927+
types: Array<string>,
928+
)=>void,
929+
): void{
930+
if(enableGestureTransition){
931+
if(callback!=null){
932+
constapplyingGesture=pendingEffectsRoot.pendingGestures;
933+
if(applyingGesture!==null){
934+
conststate: ViewTransitionState=fiber.stateNode;
935+
letinstance=state.ref;
936+
if(instance===null){
937+
instance=state.ref=createViewTransitionInstance(
938+
getViewTransitionName(fiber.memoizedProps,state),
939+
);
940+
}
941+
consttimeline=applyingGesture.provider;
942+
constoptions={
943+
rangeStart: applyingGesture.rangeStart,
944+
rangeEnd: applyingGesture.rangeEnd,
945+
};
946+
if(pendingViewTransitionEvents===null){
947+
pendingViewTransitionEvents=[];
948+
}
949+
pendingViewTransitionEvents.push(
950+
callback.bind(null,timeline,options,instance),
951+
);
952+
}
953+
}
954+
}
955+
}
956+
916957
exportfunctionpeekDeferredLane(): Lane{
917958
returnworkInProgressDeferredLane;
918959
}
@@ -4352,6 +4393,8 @@ function applyGestureOnRoot(
43524393
startAnimating(pendingEffectsLanes);
43534394
}
43544395

4396+
pendingViewTransitionEvents=null;
4397+
43554398
constprevTransition=ReactSharedInternals.T;
43564399
ReactSharedInternals.T=null;
43574400
constpreviousPriority=getCurrentUpdatePriority();
@@ -4476,6 +4519,26 @@ function flushGestureAnimations(): void {
44764519
ReactSharedInternals.T=prevTransition;
44774520
}
44784521

4522+
if(enableViewTransition){
4523+
// We should now be after the startGestureTransition's .ready call which is late enough
4524+
// to start animating any pseudo-elements. We have also already applied any adjustments
4525+
// we do to the built-in animations which can now be read by the refs.
4526+
constpendingEvents=pendingViewTransitionEvents;
4527+
letpendingTypes=pendingTransitionTypes;
4528+
pendingTransitionTypes=null;
4529+
if(pendingEvents!==null){
4530+
pendingViewTransitionEvents=null;
4531+
if(pendingTypes===null){
4532+
// Normalize the type. This is lazily created only for events.
4533+
pendingTypes=[];
4534+
}
4535+
for(leti=0;i<pendingEvents.length;i++){
4536+
const viewTransitionEvent =pendingEvents[i];
4537+
viewTransitionEvent(pendingTypes);
4538+
}
4539+
}
4540+
}
4541+
44794542
if(enableProfilerTimer&&enableComponentPerformanceTrack){
44804543
finalizeRender(lanes,commitEndTime);
44814544
}

‎packages/shared/ReactTypes.js‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@ export type ViewTransitionClass =
290290
| string
291291
| ViewTransitionClassPerType;
292292

293+
export type GestureOptionsRequired = {
294+
rangeStart: number,
295+
rangeEnd: number,
296+
};
297+
293298
export type ViewTransitionProps = {
294299
name?: string,
295300
children?: ReactNodeList,
@@ -302,6 +307,30 @@ export type ViewTransitionProps = {
302307
onExit?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
303308
onShare?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
304309
onUpdate?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
310+
onGestureEnter?: (
311+
timeline: GestureProvider,
312+
options: GestureOptionsRequired,
313+
instance: ViewTransitionInstance,
314+
types: Array<string>,
315+
)=>void,
316+
onGestureExit?: (
317+
timeline: GestureProvider,
318+
options: GestureOptionsRequired,
319+
instance: ViewTransitionInstance,
320+
types: Array<string>,
321+
)=>void,
322+
onGestureShare?: (
323+
timeline: GestureProvider,
324+
options: GestureOptionsRequired,
325+
instance: ViewTransitionInstance,
326+
types: Array<string>,
327+
)=>void,
328+
onGestureUpdate?: (
329+
timeline: GestureProvider,
330+
options: GestureOptionsRequired,
331+
instance: ViewTransitionInstance,
332+
types: Array<string>,
333+
)=>void,
305334
};
306335

307336
export type ActivityProps = {

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 4bcf67e

Browse files
authored
Support onGestureEnter/Exit/Share/Update events (#35556)
This is like the onEnter/Exit/Share/Update events but for gestures. It allows manually controlling the animation using the passed timeline.
1 parent 41b3e9a commit 4bcf67e

5 files changed

Lines changed: 154 additions & 6 deletions

File tree

‎fixtures/view-transition/src/components/Page.js‎

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,48 @@ export default function Page({url, navigate}) {
8686
viewTransition.new.animate(keyframes,250);
8787
}
8888

89+
functiononGestureTransition(
90+
timeline,
91+
{rangeStart, rangeEnd},
92+
viewTransition,
93+
types
94+
){
95+
constkeyframes=[
96+
{rotate: '0deg',transformOrigin: '30px 8px'},
97+
{rotate: '360deg',transformOrigin: '30px 8px'},
98+
];
99+
constreverse=rangeStart>rangeEnd;
100+
if(timelineinstanceofAnimationTimeline){
101+
// Native Timeline
102+
constoptions={
103+
timeline: timeline,
104+
direction: reverse ? 'normal' : 'reverse',
105+
rangeStart: (reverse ? rangeEnd : rangeStart)+'%',
106+
rangeEnd: (reverse ? rangeStart : rangeEnd)+'%',
107+
};
108+
viewTransition.old.animate(keyframes,options);
109+
viewTransition.new.animate(keyframes,options);
110+
}else{
111+
// Custom Timeline
112+
constoptions={
113+
direction: reverse ? 'normal' : 'reverse',
114+
// We set the delay and duration to represent the span of the range.
115+
delay: reverse ? rangeEnd : rangeStart,
116+
duration: reverse ? rangeStart-rangeEnd : rangeEnd-rangeStart,
117+
};
118+
constanimation1=viewTransition.old.animate(keyframes,options);
119+
constanimation2=viewTransition.new.animate(keyframes,options);
120+
// Let the custom timeline take control of driving the animations.
121+
constcleanup1=timeline.animate(animation1);
122+
constcleanup2=timeline.animate(animation2);
123+
// TODO: Support returning a clean up function from ViewTransition events.
124+
// return () => {
125+
// cleanup1();
126+
// cleanup2();
127+
// };
128+
}
129+
}
130+
89131
functionswipeAction(){
90132
navigate(show ? '/?a' : '/?b');
91133
}
@@ -131,7 +173,10 @@ export default function Page({url, navigate}) {
131173
);
132174

133175
constexclamation=(
134-
<ViewTransitionname="exclamation"onShare={onTransition}>
176+
<ViewTransition
177+
name="exclamation"
178+
onShare={onTransition}
179+
onGestureShare={onGestureTransition}>
135180
<span>
136181
<div>!</div>
137182
</span>

‎packages/react-reconciler/src/ReactFiberApplyGesture.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import {
8282
enableComponentPerformanceTrack,
8383
}from'shared/ReactFeatureFlags';
8484
import{trackAnimatingTask}from'./ReactProfilerTimer';
85+
import{scheduleGestureTransitionEvent}from'./ReactFiberWorkLoop';
8586

8687
letdidWarnForRootClone=false;
8788

@@ -280,6 +281,7 @@ function applyAppearingPairViewTransition(child: Fiber): void {
280281
if(clones!==null){
281282
applyViewTransitionToClones(name,className,clones,child);
282283
}
284+
scheduleGestureTransitionEvent(child,props.onGestureShare);
283285
}
284286
}
285287
}
@@ -310,6 +312,11 @@ function applyExitViewTransition(placement: Fiber): void {
310312
if(clones!==null){
311313
applyViewTransitionToClones(name,className,clones,placement);
312314
}
315+
if(state.paired){
316+
scheduleGestureTransitionEvent(placement,props.onGestureShare);
317+
}else{
318+
scheduleGestureTransitionEvent(placement,props.onGestureExit);
319+
}
313320
}
314321
}
315322

@@ -1123,7 +1130,8 @@ function applyViewTransitionsOnFiber(finishedWork: Fiber) {
11231130
// TODO: If this doesn't end up canceled, because a parent animates,
11241131
// then we should probably issue an event since this instance is part of it.
11251132
}else{
1126-
// TODO: Schedule gesture events.
1133+
constprops: ViewTransitionProps=finishedWork.memoizedProps;
1134+
scheduleGestureTransitionEvent(finishedWork,props.onGestureUpdate);
11271135
// If this boundary did update, we cannot cancel its children so those are dropped.
11281136
popViewTransitionCancelableScope(prevCancelableChildren);
11291137
}

‎packages/react-reconciler/src/ReactFiberCommitViewTransitions.js‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ import {
3434
hasInstanceAffectedParent,
3535
wasInstanceInViewport,
3636
}from'./ReactFiberConfig';
37-
import{scheduleViewTransitionEvent}from'./ReactFiberWorkLoop';
37+
import{
38+
scheduleViewTransitionEvent,
39+
scheduleGestureTransitionEvent,
40+
}from'./ReactFiberWorkLoop';
3841
import{
3942
getViewTransitionName,
4043
getViewTransitionClassName,
@@ -312,7 +315,7 @@ export function commitEnterViewTransitions(
312315

313316
if(!state.paired){
314317
if(gesture){
315-
// TODO: Schedule gesture events.
318+
scheduleGestureTransitionEvent(placement,props.onGestureEnter);
316319
}else{
317320
scheduleViewTransitionEvent(placement,props.onEnter);
318321
}
@@ -848,7 +851,7 @@ export function measureNestedViewTransitions(
848851
// Nothing changed.
849852
}else{
850853
if(gesture){
851-
// TODO: Schedule gesture events.
854+
scheduleGestureTransitionEvent(child,props.onGestureUpdate);
852855
}else{
853856
scheduleViewTransitionEvent(child,props.onUpdate);
854857
}

‎packages/react-reconciler/src/ReactFiberWorkLoop.js‎

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99

1010
import{REACT_STRICT_MODE_TYPE}from'shared/ReactSymbols';
1111

12-
importtype{Wakeable,Thenable}from'shared/ReactTypes';
12+
importtype{
13+
Wakeable,
14+
Thenable,
15+
GestureOptionsRequired,
16+
}from'shared/ReactTypes';
1317
importtype{Fiber,FiberRoot}from'./ReactInternalTypes';
1418
importtype{Lanes,Lane}from'./ReactFiberLane';
1519
importtype{ActivityState}from'./ReactFiberActivityComponent';
@@ -26,6 +30,7 @@ import type {
2630
Resource,
2731
ViewTransitionInstance,
2832
RunningViewTransition,
33+
GestureTimeline,
2934
SuspendedState,
3035
}from'./ReactFiberConfig';
3136
importtype{RootState}from'./ReactFiberRoot';
@@ -913,6 +918,42 @@ export function scheduleViewTransitionEvent(
913918
}
914919
}
915920

921+
exportfunctionscheduleGestureTransitionEvent(
922+
fiber: Fiber,
923+
callback: ?(
924+
timeline: GestureTimeline,
925+
options: GestureOptionsRequired,
926+
instance: ViewTransitionInstance,
927+
types: Array<string>,
928+
)=>void,
929+
): void{
930+
if(enableGestureTransition){
931+
if(callback!=null){
932+
constapplyingGesture=pendingEffectsRoot.pendingGestures;
933+
if(applyingGesture!==null){
934+
conststate: ViewTransitionState=fiber.stateNode;
935+
letinstance=state.ref;
936+
if(instance===null){
937+
instance=state.ref=createViewTransitionInstance(
938+
getViewTransitionName(fiber.memoizedProps,state),
939+
);
940+
}
941+
consttimeline=applyingGesture.provider;
942+
constoptions={
943+
rangeStart: applyingGesture.rangeStart,
944+
rangeEnd: applyingGesture.rangeEnd,
945+
};
946+
if(pendingViewTransitionEvents===null){
947+
pendingViewTransitionEvents=[];
948+
}
949+
pendingViewTransitionEvents.push(
950+
callback.bind(null,timeline,options,instance),
951+
);
952+
}
953+
}
954+
}
955+
}
956+
916957
exportfunctionpeekDeferredLane(): Lane{
917958
returnworkInProgressDeferredLane;
918959
}
@@ -4352,6 +4393,8 @@ function applyGestureOnRoot(
43524393
startAnimating(pendingEffectsLanes);
43534394
}
43544395

4396+
pendingViewTransitionEvents=null;
4397+
43554398
constprevTransition=ReactSharedInternals.T;
43564399
ReactSharedInternals.T=null;
43574400
constpreviousPriority=getCurrentUpdatePriority();
@@ -4476,6 +4519,26 @@ function flushGestureAnimations(): void {
44764519
ReactSharedInternals.T=prevTransition;
44774520
}
44784521

4522+
if(enableViewTransition){
4523+
// We should now be after the startGestureTransition's .ready call which is late enough
4524+
// to start animating any pseudo-elements. We have also already applied any adjustments
4525+
// we do to the built-in animations which can now be read by the refs.
4526+
constpendingEvents=pendingViewTransitionEvents;
4527+
letpendingTypes=pendingTransitionTypes;
4528+
pendingTransitionTypes=null;
4529+
if(pendingEvents!==null){
4530+
pendingViewTransitionEvents=null;
4531+
if(pendingTypes===null){
4532+
// Normalize the type. This is lazily created only for events.
4533+
pendingTypes=[];
4534+
}
4535+
for(leti=0;i<pendingEvents.length;i++){
4536+
const viewTransitionEvent =pendingEvents[i];
4537+
viewTransitionEvent(pendingTypes);
4538+
}
4539+
}
4540+
}
4541+
44794542
if(enableProfilerTimer&&enableComponentPerformanceTrack){
44804543
finalizeRender(lanes,commitEndTime);
44814544
}

‎packages/shared/ReactTypes.js‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@ export type ViewTransitionClass =
290290
| string
291291
| ViewTransitionClassPerType;
292292

293+
export type GestureOptionsRequired = {
294+
rangeStart: number,
295+
rangeEnd: number,
296+
};
297+
293298
export type ViewTransitionProps = {
294299
name?: string,
295300
children?: ReactNodeList,
@@ -302,6 +307,30 @@ export type ViewTransitionProps = {
302307
onExit?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
303308
onShare?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
304309
onUpdate?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
310+
onGestureEnter?: (
311+
timeline: GestureProvider,
312+
options: GestureOptionsRequired,
313+
instance: ViewTransitionInstance,
314+
types: Array<string>,
315+
)=>void,
316+
onGestureExit?: (
317+
timeline: GestureProvider,
318+
options: GestureOptionsRequired,
319+
instance: ViewTransitionInstance,
320+
types: Array<string>,
321+
)=>void,
322+
onGestureShare?: (
323+
timeline: GestureProvider,
324+
options: GestureOptionsRequired,
325+
instance: ViewTransitionInstance,
326+
types: Array<string>,
327+
)=>void,
328+
onGestureUpdate?: (
329+
timeline: GestureProvider,
330+
options: GestureOptionsRequired,
331+
instance: ViewTransitionInstance,
332+
types: Array<string>,
333+
)=>void,
305334
};
306335

307336
export type ActivityProps = {

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit 4bcf67e

Browse files
authored
Support onGestureEnter/Exit/Share/Update events (#35556)
This is like the onEnter/Exit/Share/Update events but for gestures. It allows manually controlling the animation using the passed timeline.
1 parent 41b3e9a commit 4bcf67e

5 files changed

Lines changed: 154 additions & 6 deletions

File tree

‎fixtures/view-transition/src/components/Page.js‎

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,48 @@ export default function Page({url, navigate}) {
8686
viewTransition.new.animate(keyframes,250);
8787
}
8888

89+
functiononGestureTransition(
90+
timeline,
91+
{rangeStart, rangeEnd},
92+
viewTransition,
93+
types
94+
){
95+
constkeyframes=[
96+
{rotate: '0deg',transformOrigin: '30px 8px'},
97+
{rotate: '360deg',transformOrigin: '30px 8px'},
98+
];
99+
constreverse=rangeStart>rangeEnd;
100+
if(timelineinstanceofAnimationTimeline){
101+
// Native Timeline
102+
constoptions={
103+
timeline: timeline,
104+
direction: reverse ? 'normal' : 'reverse',
105+
rangeStart: (reverse ? rangeEnd : rangeStart)+'%',
106+
rangeEnd: (reverse ? rangeStart : rangeEnd)+'%',
107+
};
108+
viewTransition.old.animate(keyframes,options);
109+
viewTransition.new.animate(keyframes,options);
110+
}else{
111+
// Custom Timeline
112+
constoptions={
113+
direction: reverse ? 'normal' : 'reverse',
114+
// We set the delay and duration to represent the span of the range.
115+
delay: reverse ? rangeEnd : rangeStart,
116+
duration: reverse ? rangeStart-rangeEnd : rangeEnd-rangeStart,
117+
};
118+
constanimation1=viewTransition.old.animate(keyframes,options);
119+
constanimation2=viewTransition.new.animate(keyframes,options);
120+
// Let the custom timeline take control of driving the animations.
121+
constcleanup1=timeline.animate(animation1);
122+
constcleanup2=timeline.animate(animation2);
123+
// TODO: Support returning a clean up function from ViewTransition events.
124+
// return () => {
125+
// cleanup1();
126+
// cleanup2();
127+
// };
128+
}
129+
}
130+
89131
functionswipeAction(){
90132
navigate(show ? '/?a' : '/?b');
91133
}
@@ -131,7 +173,10 @@ export default function Page({url, navigate}) {
131173
);
132174

133175
constexclamation=(
134-
<ViewTransitionname="exclamation"onShare={onTransition}>
176+
<ViewTransition
177+
name="exclamation"
178+
onShare={onTransition}
179+
onGestureShare={onGestureTransition}>
135180
<span>
136181
<div>!</div>
137182
</span>

‎packages/react-reconciler/src/ReactFiberApplyGesture.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import {
8282
enableComponentPerformanceTrack,
8383
}from'shared/ReactFeatureFlags';
8484
import{trackAnimatingTask}from'./ReactProfilerTimer';
85+
import{scheduleGestureTransitionEvent}from'./ReactFiberWorkLoop';
8586

8687
letdidWarnForRootClone=false;
8788

@@ -280,6 +281,7 @@ function applyAppearingPairViewTransition(child: Fiber): void {
280281
if(clones!==null){
281282
applyViewTransitionToClones(name,className,clones,child);
282283
}
284+
scheduleGestureTransitionEvent(child,props.onGestureShare);
283285
}
284286
}
285287
}
@@ -310,6 +312,11 @@ function applyExitViewTransition(placement: Fiber): void {
310312
if(clones!==null){
311313
applyViewTransitionToClones(name,className,clones,placement);
312314
}
315+
if(state.paired){
316+
scheduleGestureTransitionEvent(placement,props.onGestureShare);
317+
}else{
318+
scheduleGestureTransitionEvent(placement,props.onGestureExit);
319+
}
313320
}
314321
}
315322

@@ -1123,7 +1130,8 @@ function applyViewTransitionsOnFiber(finishedWork: Fiber) {
11231130
// TODO: If this doesn't end up canceled, because a parent animates,
11241131
// then we should probably issue an event since this instance is part of it.
11251132
}else{
1126-
// TODO: Schedule gesture events.
1133+
constprops: ViewTransitionProps=finishedWork.memoizedProps;
1134+
scheduleGestureTransitionEvent(finishedWork,props.onGestureUpdate);
11271135
// If this boundary did update, we cannot cancel its children so those are dropped.
11281136
popViewTransitionCancelableScope(prevCancelableChildren);
11291137
}

‎packages/react-reconciler/src/ReactFiberCommitViewTransitions.js‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ import {
3434
hasInstanceAffectedParent,
3535
wasInstanceInViewport,
3636
}from'./ReactFiberConfig';
37-
import{scheduleViewTransitionEvent}from'./ReactFiberWorkLoop';
37+
import{
38+
scheduleViewTransitionEvent,
39+
scheduleGestureTransitionEvent,
40+
}from'./ReactFiberWorkLoop';
3841
import{
3942
getViewTransitionName,
4043
getViewTransitionClassName,
@@ -312,7 +315,7 @@ export function commitEnterViewTransitions(
312315

313316
if(!state.paired){
314317
if(gesture){
315-
// TODO: Schedule gesture events.
318+
scheduleGestureTransitionEvent(placement,props.onGestureEnter);
316319
}else{
317320
scheduleViewTransitionEvent(placement,props.onEnter);
318321
}
@@ -848,7 +851,7 @@ export function measureNestedViewTransitions(
848851
// Nothing changed.
849852
}else{
850853
if(gesture){
851-
// TODO: Schedule gesture events.
854+
scheduleGestureTransitionEvent(child,props.onGestureUpdate);
852855
}else{
853856
scheduleViewTransitionEvent(child,props.onUpdate);
854857
}

‎packages/react-reconciler/src/ReactFiberWorkLoop.js‎

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99

1010
import{REACT_STRICT_MODE_TYPE}from'shared/ReactSymbols';
1111

12-
importtype{Wakeable,Thenable}from'shared/ReactTypes';
12+
importtype{
13+
Wakeable,
14+
Thenable,
15+
GestureOptionsRequired,
16+
}from'shared/ReactTypes';
1317
importtype{Fiber,FiberRoot}from'./ReactInternalTypes';
1418
importtype{Lanes,Lane}from'./ReactFiberLane';
1519
importtype{ActivityState}from'./ReactFiberActivityComponent';
@@ -26,6 +30,7 @@ import type {
2630
Resource,
2731
ViewTransitionInstance,
2832
RunningViewTransition,
33+
GestureTimeline,
2934
SuspendedState,
3035
}from'./ReactFiberConfig';
3136
importtype{RootState}from'./ReactFiberRoot';
@@ -913,6 +918,42 @@ export function scheduleViewTransitionEvent(
913918
}
914919
}
915920

921+
exportfunctionscheduleGestureTransitionEvent(
922+
fiber: Fiber,
923+
callback: ?(
924+
timeline: GestureTimeline,
925+
options: GestureOptionsRequired,
926+
instance: ViewTransitionInstance,
927+
types: Array<string>,
928+
)=>void,
929+
): void{
930+
if(enableGestureTransition){
931+
if(callback!=null){
932+
constapplyingGesture=pendingEffectsRoot.pendingGestures;
933+
if(applyingGesture!==null){
934+
conststate: ViewTransitionState=fiber.stateNode;
935+
letinstance=state.ref;
936+
if(instance===null){
937+
instance=state.ref=createViewTransitionInstance(
938+
getViewTransitionName(fiber.memoizedProps,state),
939+
);
940+
}
941+
consttimeline=applyingGesture.provider;
942+
constoptions={
943+
rangeStart: applyingGesture.rangeStart,
944+
rangeEnd: applyingGesture.rangeEnd,
945+
};
946+
if(pendingViewTransitionEvents===null){
947+
pendingViewTransitionEvents=[];
948+
}
949+
pendingViewTransitionEvents.push(
950+
callback.bind(null,timeline,options,instance),
951+
);
952+
}
953+
}
954+
}
955+
}
956+
916957
exportfunctionpeekDeferredLane(): Lane{
917958
returnworkInProgressDeferredLane;
918959
}
@@ -4352,6 +4393,8 @@ function applyGestureOnRoot(
43524393
startAnimating(pendingEffectsLanes);
43534394
}
43544395

4396+
pendingViewTransitionEvents=null;
4397+
43554398
constprevTransition=ReactSharedInternals.T;
43564399
ReactSharedInternals.T=null;
43574400
constpreviousPriority=getCurrentUpdatePriority();
@@ -4476,6 +4519,26 @@ function flushGestureAnimations(): void {
44764519
ReactSharedInternals.T=prevTransition;
44774520
}
44784521

4522+
if(enableViewTransition){
4523+
// We should now be after the startGestureTransition's .ready call which is late enough
4524+
// to start animating any pseudo-elements. We have also already applied any adjustments
4525+
// we do to the built-in animations which can now be read by the refs.
4526+
constpendingEvents=pendingViewTransitionEvents;
4527+
letpendingTypes=pendingTransitionTypes;
4528+
pendingTransitionTypes=null;
4529+
if(pendingEvents!==null){
4530+
pendingViewTransitionEvents=null;
4531+
if(pendingTypes===null){
4532+
// Normalize the type. This is lazily created only for events.
4533+
pendingTypes=[];
4534+
}
4535+
for(leti=0;i<pendingEvents.length;i++){
4536+
const viewTransitionEvent =pendingEvents[i];
4537+
viewTransitionEvent(pendingTypes);
4538+
}
4539+
}
4540+
}
4541+
44794542
if(enableProfilerTimer&&enableComponentPerformanceTrack){
44804543
finalizeRender(lanes,commitEndTime);
44814544
}

‎packages/shared/ReactTypes.js‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@ export type ViewTransitionClass =
290290
| string
291291
| ViewTransitionClassPerType;
292292

293+
export type GestureOptionsRequired = {
294+
rangeStart: number,
295+
rangeEnd: number,
296+
};
297+
293298
export type ViewTransitionProps = {
294299
name?: string,
295300
children?: ReactNodeList,
@@ -302,6 +307,30 @@ export type ViewTransitionProps = {
302307
onExit?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
303308
onShare?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
304309
onUpdate?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
310+
onGestureEnter?: (
311+
timeline: GestureProvider,
312+
options: GestureOptionsRequired,
313+
instance: ViewTransitionInstance,
314+
types: Array<string>,
315+
)=>void,
316+
onGestureExit?: (
317+
timeline: GestureProvider,
318+
options: GestureOptionsRequired,
319+
instance: ViewTransitionInstance,
320+
types: Array<string>,
321+
)=>void,
322+
onGestureShare?: (
323+
timeline: GestureProvider,
324+
options: GestureOptionsRequired,
325+
instance: ViewTransitionInstance,
326+
types: Array<string>,
327+
)=>void,
328+
onGestureUpdate?: (
329+
timeline: GestureProvider,
330+
options: GestureOptionsRequired,
331+
instance: ViewTransitionInstance,
332+
types: Array<string>,
333+
)=>void,
305334
};
306335

307336
export type ActivityProps = {

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 4bcf67e

Browse files
authored
Support onGestureEnter/Exit/Share/Update events (#35556)
This is like the onEnter/Exit/Share/Update events but for gestures. It allows manually controlling the animation using the passed timeline.
1 parent 41b3e9a commit 4bcf67e

5 files changed

Lines changed: 154 additions & 6 deletions

File tree

‎fixtures/view-transition/src/components/Page.js‎

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,48 @@ export default function Page({url, navigate}) {
8686
viewTransition.new.animate(keyframes,250);
8787
}
8888

89+
functiononGestureTransition(
90+
timeline,
91+
{rangeStart, rangeEnd},
92+
viewTransition,
93+
types
94+
){
95+
constkeyframes=[
96+
{rotate: '0deg',transformOrigin: '30px 8px'},
97+
{rotate: '360deg',transformOrigin: '30px 8px'},
98+
];
99+
constreverse=rangeStart>rangeEnd;
100+
if(timelineinstanceofAnimationTimeline){
101+
// Native Timeline
102+
constoptions={
103+
timeline: timeline,
104+
direction: reverse ? 'normal' : 'reverse',
105+
rangeStart: (reverse ? rangeEnd : rangeStart)+'%',
106+
rangeEnd: (reverse ? rangeStart : rangeEnd)+'%',
107+
};
108+
viewTransition.old.animate(keyframes,options);
109+
viewTransition.new.animate(keyframes,options);
110+
}else{
111+
// Custom Timeline
112+
constoptions={
113+
direction: reverse ? 'normal' : 'reverse',
114+
// We set the delay and duration to represent the span of the range.
115+
delay: reverse ? rangeEnd : rangeStart,
116+
duration: reverse ? rangeStart-rangeEnd : rangeEnd-rangeStart,
117+
};
118+
constanimation1=viewTransition.old.animate(keyframes,options);
119+
constanimation2=viewTransition.new.animate(keyframes,options);
120+
// Let the custom timeline take control of driving the animations.
121+
constcleanup1=timeline.animate(animation1);
122+
constcleanup2=timeline.animate(animation2);
123+
// TODO: Support returning a clean up function from ViewTransition events.
124+
// return () => {
125+
// cleanup1();
126+
// cleanup2();
127+
// };
128+
}
129+
}
130+
89131
functionswipeAction(){
90132
navigate(show ? '/?a' : '/?b');
91133
}
@@ -131,7 +173,10 @@ export default function Page({url, navigate}) {
131173
);
132174

133175
constexclamation=(
134-
<ViewTransitionname="exclamation"onShare={onTransition}>
176+
<ViewTransition
177+
name="exclamation"
178+
onShare={onTransition}
179+
onGestureShare={onGestureTransition}>
135180
<span>
136181
<div>!</div>
137182
</span>

‎packages/react-reconciler/src/ReactFiberApplyGesture.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import {
8282
enableComponentPerformanceTrack,
8383
}from'shared/ReactFeatureFlags';
8484
import{trackAnimatingTask}from'./ReactProfilerTimer';
85+
import{scheduleGestureTransitionEvent}from'./ReactFiberWorkLoop';
8586

8687
letdidWarnForRootClone=false;
8788

@@ -280,6 +281,7 @@ function applyAppearingPairViewTransition(child: Fiber): void {
280281
if(clones!==null){
281282
applyViewTransitionToClones(name,className,clones,child);
282283
}
284+
scheduleGestureTransitionEvent(child,props.onGestureShare);
283285
}
284286
}
285287
}
@@ -310,6 +312,11 @@ function applyExitViewTransition(placement: Fiber): void {
310312
if(clones!==null){
311313
applyViewTransitionToClones(name,className,clones,placement);
312314
}
315+
if(state.paired){
316+
scheduleGestureTransitionEvent(placement,props.onGestureShare);
317+
}else{
318+
scheduleGestureTransitionEvent(placement,props.onGestureExit);
319+
}
313320
}
314321
}
315322

@@ -1123,7 +1130,8 @@ function applyViewTransitionsOnFiber(finishedWork: Fiber) {
11231130
// TODO: If this doesn't end up canceled, because a parent animates,
11241131
// then we should probably issue an event since this instance is part of it.
11251132
}else{
1126-
// TODO: Schedule gesture events.
1133+
constprops: ViewTransitionProps=finishedWork.memoizedProps;
1134+
scheduleGestureTransitionEvent(finishedWork,props.onGestureUpdate);
11271135
// If this boundary did update, we cannot cancel its children so those are dropped.
11281136
popViewTransitionCancelableScope(prevCancelableChildren);
11291137
}

‎packages/react-reconciler/src/ReactFiberCommitViewTransitions.js‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ import {
3434
hasInstanceAffectedParent,
3535
wasInstanceInViewport,
3636
}from'./ReactFiberConfig';
37-
import{scheduleViewTransitionEvent}from'./ReactFiberWorkLoop';
37+
import{
38+
scheduleViewTransitionEvent,
39+
scheduleGestureTransitionEvent,
40+
}from'./ReactFiberWorkLoop';
3841
import{
3942
getViewTransitionName,
4043
getViewTransitionClassName,
@@ -312,7 +315,7 @@ export function commitEnterViewTransitions(
312315

313316
if(!state.paired){
314317
if(gesture){
315-
// TODO: Schedule gesture events.
318+
scheduleGestureTransitionEvent(placement,props.onGestureEnter);
316319
}else{
317320
scheduleViewTransitionEvent(placement,props.onEnter);
318321
}
@@ -848,7 +851,7 @@ export function measureNestedViewTransitions(
848851
// Nothing changed.
849852
}else{
850853
if(gesture){
851-
// TODO: Schedule gesture events.
854+
scheduleGestureTransitionEvent(child,props.onGestureUpdate);
852855
}else{
853856
scheduleViewTransitionEvent(child,props.onUpdate);
854857
}

‎packages/react-reconciler/src/ReactFiberWorkLoop.js‎

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99

1010
import{REACT_STRICT_MODE_TYPE}from'shared/ReactSymbols';
1111

12-
importtype{Wakeable,Thenable}from'shared/ReactTypes';
12+
importtype{
13+
Wakeable,
14+
Thenable,
15+
GestureOptionsRequired,
16+
}from'shared/ReactTypes';
1317
importtype{Fiber,FiberRoot}from'./ReactInternalTypes';
1418
importtype{Lanes,Lane}from'./ReactFiberLane';
1519
importtype{ActivityState}from'./ReactFiberActivityComponent';
@@ -26,6 +30,7 @@ import type {
2630
Resource,
2731
ViewTransitionInstance,
2832
RunningViewTransition,
33+
GestureTimeline,
2934
SuspendedState,
3035
}from'./ReactFiberConfig';
3136
importtype{RootState}from'./ReactFiberRoot';
@@ -913,6 +918,42 @@ export function scheduleViewTransitionEvent(
913918
}
914919
}
915920

921+
exportfunctionscheduleGestureTransitionEvent(
922+
fiber: Fiber,
923+
callback: ?(
924+
timeline: GestureTimeline,
925+
options: GestureOptionsRequired,
926+
instance: ViewTransitionInstance,
927+
types: Array<string>,
928+
)=>void,
929+
): void{
930+
if(enableGestureTransition){
931+
if(callback!=null){
932+
constapplyingGesture=pendingEffectsRoot.pendingGestures;
933+
if(applyingGesture!==null){
934+
conststate: ViewTransitionState=fiber.stateNode;
935+
letinstance=state.ref;
936+
if(instance===null){
937+
instance=state.ref=createViewTransitionInstance(
938+
getViewTransitionName(fiber.memoizedProps,state),
939+
);
940+
}
941+
consttimeline=applyingGesture.provider;
942+
constoptions={
943+
rangeStart: applyingGesture.rangeStart,
944+
rangeEnd: applyingGesture.rangeEnd,
945+
};
946+
if(pendingViewTransitionEvents===null){
947+
pendingViewTransitionEvents=[];
948+
}
949+
pendingViewTransitionEvents.push(
950+
callback.bind(null,timeline,options,instance),
951+
);
952+
}
953+
}
954+
}
955+
}
956+
916957
exportfunctionpeekDeferredLane(): Lane{
917958
returnworkInProgressDeferredLane;
918959
}
@@ -4352,6 +4393,8 @@ function applyGestureOnRoot(
43524393
startAnimating(pendingEffectsLanes);
43534394
}
43544395

4396+
pendingViewTransitionEvents=null;
4397+
43554398
constprevTransition=ReactSharedInternals.T;
43564399
ReactSharedInternals.T=null;
43574400
constpreviousPriority=getCurrentUpdatePriority();
@@ -4476,6 +4519,26 @@ function flushGestureAnimations(): void {
44764519
ReactSharedInternals.T=prevTransition;
44774520
}
44784521

4522+
if(enableViewTransition){
4523+
// We should now be after the startGestureTransition's .ready call which is late enough
4524+
// to start animating any pseudo-elements. We have also already applied any adjustments
4525+
// we do to the built-in animations which can now be read by the refs.
4526+
constpendingEvents=pendingViewTransitionEvents;
4527+
letpendingTypes=pendingTransitionTypes;
4528+
pendingTransitionTypes=null;
4529+
if(pendingEvents!==null){
4530+
pendingViewTransitionEvents=null;
4531+
if(pendingTypes===null){
4532+
// Normalize the type. This is lazily created only for events.
4533+
pendingTypes=[];
4534+
}
4535+
for(leti=0;i<pendingEvents.length;i++){
4536+
const viewTransitionEvent =pendingEvents[i];
4537+
viewTransitionEvent(pendingTypes);
4538+
}
4539+
}
4540+
}
4541+
44794542
if(enableProfilerTimer&&enableComponentPerformanceTrack){
44804543
finalizeRender(lanes,commitEndTime);
44814544
}

‎packages/shared/ReactTypes.js‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@ export type ViewTransitionClass =
290290
| string
291291
| ViewTransitionClassPerType;
292292

293+
export type GestureOptionsRequired = {
294+
rangeStart: number,
295+
rangeEnd: number,
296+
};
297+
293298
export type ViewTransitionProps = {
294299
name?: string,
295300
children?: ReactNodeList,
@@ -302,6 +307,30 @@ export type ViewTransitionProps = {
302307
onExit?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
303308
onShare?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
304309
onUpdate?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
310+
onGestureEnter?: (
311+
timeline: GestureProvider,
312+
options: GestureOptionsRequired,
313+
instance: ViewTransitionInstance,
314+
types: Array<string>,
315+
)=>void,
316+
onGestureExit?: (
317+
timeline: GestureProvider,
318+
options: GestureOptionsRequired,
319+
instance: ViewTransitionInstance,
320+
types: Array<string>,
321+
)=>void,
322+
onGestureShare?: (
323+
timeline: GestureProvider,
324+
options: GestureOptionsRequired,
325+
instance: ViewTransitionInstance,
326+
types: Array<string>,
327+
)=>void,
328+
onGestureUpdate?: (
329+
timeline: GestureProvider,
330+
options: GestureOptionsRequired,
331+
instance: ViewTransitionInstance,
332+
types: Array<string>,
333+
)=>void,
305334
};
306335

307336
export type ActivityProps = {

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 4bcf67e

Browse files
authored
Support onGestureEnter/Exit/Share/Update events (#35556)
This is like the onEnter/Exit/Share/Update events but for gestures. It allows manually controlling the animation using the passed timeline.
1 parent 41b3e9a commit 4bcf67e

5 files changed

Lines changed: 154 additions & 6 deletions

File tree

‎fixtures/view-transition/src/components/Page.js‎

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,48 @@ export default function Page({url, navigate}) {
8686
viewTransition.new.animate(keyframes,250);
8787
}
8888

89+
functiononGestureTransition(
90+
timeline,
91+
{rangeStart, rangeEnd},
92+
viewTransition,
93+
types
94+
){
95+
constkeyframes=[
96+
{rotate: '0deg',transformOrigin: '30px 8px'},
97+
{rotate: '360deg',transformOrigin: '30px 8px'},
98+
];
99+
constreverse=rangeStart>rangeEnd;
100+
if(timelineinstanceofAnimationTimeline){
101+
// Native Timeline
102+
constoptions={
103+
timeline: timeline,
104+
direction: reverse ? 'normal' : 'reverse',
105+
rangeStart: (reverse ? rangeEnd : rangeStart)+'%',
106+
rangeEnd: (reverse ? rangeStart : rangeEnd)+'%',
107+
};
108+
viewTransition.old.animate(keyframes,options);
109+
viewTransition.new.animate(keyframes,options);
110+
}else{
111+
// Custom Timeline
112+
constoptions={
113+
direction: reverse ? 'normal' : 'reverse',
114+
// We set the delay and duration to represent the span of the range.
115+
delay: reverse ? rangeEnd : rangeStart,
116+
duration: reverse ? rangeStart-rangeEnd : rangeEnd-rangeStart,
117+
};
118+
constanimation1=viewTransition.old.animate(keyframes,options);
119+
constanimation2=viewTransition.new.animate(keyframes,options);
120+
// Let the custom timeline take control of driving the animations.
121+
constcleanup1=timeline.animate(animation1);
122+
constcleanup2=timeline.animate(animation2);
123+
// TODO: Support returning a clean up function from ViewTransition events.
124+
// return () => {
125+
// cleanup1();
126+
// cleanup2();
127+
// };
128+
}
129+
}
130+
89131
functionswipeAction(){
90132
navigate(show ? '/?a' : '/?b');
91133
}
@@ -131,7 +173,10 @@ export default function Page({url, navigate}) {
131173
);
132174

133175
constexclamation=(
134-
<ViewTransitionname="exclamation"onShare={onTransition}>
176+
<ViewTransition
177+
name="exclamation"
178+
onShare={onTransition}
179+
onGestureShare={onGestureTransition}>
135180
<span>
136181
<div>!</div>
137182
</span>

‎packages/react-reconciler/src/ReactFiberApplyGesture.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import {
8282
enableComponentPerformanceTrack,
8383
}from'shared/ReactFeatureFlags';
8484
import{trackAnimatingTask}from'./ReactProfilerTimer';
85+
import{scheduleGestureTransitionEvent}from'./ReactFiberWorkLoop';
8586

8687
letdidWarnForRootClone=false;
8788

@@ -280,6 +281,7 @@ function applyAppearingPairViewTransition(child: Fiber): void {
280281
if(clones!==null){
281282
applyViewTransitionToClones(name,className,clones,child);
282283
}
284+
scheduleGestureTransitionEvent(child,props.onGestureShare);
283285
}
284286
}
285287
}
@@ -310,6 +312,11 @@ function applyExitViewTransition(placement: Fiber): void {
310312
if(clones!==null){
311313
applyViewTransitionToClones(name,className,clones,placement);
312314
}
315+
if(state.paired){
316+
scheduleGestureTransitionEvent(placement,props.onGestureShare);
317+
}else{
318+
scheduleGestureTransitionEvent(placement,props.onGestureExit);
319+
}
313320
}
314321
}
315322

@@ -1123,7 +1130,8 @@ function applyViewTransitionsOnFiber(finishedWork: Fiber) {
11231130
// TODO: If this doesn't end up canceled, because a parent animates,
11241131
// then we should probably issue an event since this instance is part of it.
11251132
}else{
1126-
// TODO: Schedule gesture events.
1133+
constprops: ViewTransitionProps=finishedWork.memoizedProps;
1134+
scheduleGestureTransitionEvent(finishedWork,props.onGestureUpdate);
11271135
// If this boundary did update, we cannot cancel its children so those are dropped.
11281136
popViewTransitionCancelableScope(prevCancelableChildren);
11291137
}

‎packages/react-reconciler/src/ReactFiberCommitViewTransitions.js‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ import {
3434
hasInstanceAffectedParent,
3535
wasInstanceInViewport,
3636
}from'./ReactFiberConfig';
37-
import{scheduleViewTransitionEvent}from'./ReactFiberWorkLoop';
37+
import{
38+
scheduleViewTransitionEvent,
39+
scheduleGestureTransitionEvent,
40+
}from'./ReactFiberWorkLoop';
3841
import{
3942
getViewTransitionName,
4043
getViewTransitionClassName,
@@ -312,7 +315,7 @@ export function commitEnterViewTransitions(
312315

313316
if(!state.paired){
314317
if(gesture){
315-
// TODO: Schedule gesture events.
318+
scheduleGestureTransitionEvent(placement,props.onGestureEnter);
316319
}else{
317320
scheduleViewTransitionEvent(placement,props.onEnter);
318321
}
@@ -848,7 +851,7 @@ export function measureNestedViewTransitions(
848851
// Nothing changed.
849852
}else{
850853
if(gesture){
851-
// TODO: Schedule gesture events.
854+
scheduleGestureTransitionEvent(child,props.onGestureUpdate);
852855
}else{
853856
scheduleViewTransitionEvent(child,props.onUpdate);
854857
}

‎packages/react-reconciler/src/ReactFiberWorkLoop.js‎

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99

1010
import{REACT_STRICT_MODE_TYPE}from'shared/ReactSymbols';
1111

12-
importtype{Wakeable,Thenable}from'shared/ReactTypes';
12+
importtype{
13+
Wakeable,
14+
Thenable,
15+
GestureOptionsRequired,
16+
}from'shared/ReactTypes';
1317
importtype{Fiber,FiberRoot}from'./ReactInternalTypes';
1418
importtype{Lanes,Lane}from'./ReactFiberLane';
1519
importtype{ActivityState}from'./ReactFiberActivityComponent';
@@ -26,6 +30,7 @@ import type {
2630
Resource,
2731
ViewTransitionInstance,
2832
RunningViewTransition,
33+
GestureTimeline,
2934
SuspendedState,
3035
}from'./ReactFiberConfig';
3136
importtype{RootState}from'./ReactFiberRoot';
@@ -913,6 +918,42 @@ export function scheduleViewTransitionEvent(
913918
}
914919
}
915920

921+
exportfunctionscheduleGestureTransitionEvent(
922+
fiber: Fiber,
923+
callback: ?(
924+
timeline: GestureTimeline,
925+
options: GestureOptionsRequired,
926+
instance: ViewTransitionInstance,
927+
types: Array<string>,
928+
)=>void,
929+
): void{
930+
if(enableGestureTransition){
931+
if(callback!=null){
932+
constapplyingGesture=pendingEffectsRoot.pendingGestures;
933+
if(applyingGesture!==null){
934+
conststate: ViewTransitionState=fiber.stateNode;
935+
letinstance=state.ref;
936+
if(instance===null){
937+
instance=state.ref=createViewTransitionInstance(
938+
getViewTransitionName(fiber.memoizedProps,state),
939+
);
940+
}
941+
consttimeline=applyingGesture.provider;
942+
constoptions={
943+
rangeStart: applyingGesture.rangeStart,
944+
rangeEnd: applyingGesture.rangeEnd,
945+
};
946+
if(pendingViewTransitionEvents===null){
947+
pendingViewTransitionEvents=[];
948+
}
949+
pendingViewTransitionEvents.push(
950+
callback.bind(null,timeline,options,instance),
951+
);
952+
}
953+
}
954+
}
955+
}
956+
916957
exportfunctionpeekDeferredLane(): Lane{
917958
returnworkInProgressDeferredLane;
918959
}
@@ -4352,6 +4393,8 @@ function applyGestureOnRoot(
43524393
startAnimating(pendingEffectsLanes);
43534394
}
43544395

4396+
pendingViewTransitionEvents=null;
4397+
43554398
constprevTransition=ReactSharedInternals.T;
43564399
ReactSharedInternals.T=null;
43574400
constpreviousPriority=getCurrentUpdatePriority();
@@ -4476,6 +4519,26 @@ function flushGestureAnimations(): void {
44764519
ReactSharedInternals.T=prevTransition;
44774520
}
44784521

4522+
if(enableViewTransition){
4523+
// We should now be after the startGestureTransition's .ready call which is late enough
4524+
// to start animating any pseudo-elements. We have also already applied any adjustments
4525+
// we do to the built-in animations which can now be read by the refs.
4526+
constpendingEvents=pendingViewTransitionEvents;
4527+
letpendingTypes=pendingTransitionTypes;
4528+
pendingTransitionTypes=null;
4529+
if(pendingEvents!==null){
4530+
pendingViewTransitionEvents=null;
4531+
if(pendingTypes===null){
4532+
// Normalize the type. This is lazily created only for events.
4533+
pendingTypes=[];
4534+
}
4535+
for(leti=0;i<pendingEvents.length;i++){
4536+
const viewTransitionEvent =pendingEvents[i];
4537+
viewTransitionEvent(pendingTypes);
4538+
}
4539+
}
4540+
}
4541+
44794542
if(enableProfilerTimer&&enableComponentPerformanceTrack){
44804543
finalizeRender(lanes,commitEndTime);
44814544
}

‎packages/shared/ReactTypes.js‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@ export type ViewTransitionClass =
290290
| string
291291
| ViewTransitionClassPerType;
292292

293+
export type GestureOptionsRequired = {
294+
rangeStart: number,
295+
rangeEnd: number,
296+
};
297+
293298
export type ViewTransitionProps = {
294299
name?: string,
295300
children?: ReactNodeList,
@@ -302,6 +307,30 @@ export type ViewTransitionProps = {
302307
onExit?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
303308
onShare?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
304309
onUpdate?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
310+
onGestureEnter?: (
311+
timeline: GestureProvider,
312+
options: GestureOptionsRequired,
313+
instance: ViewTransitionInstance,
314+
types: Array<string>,
315+
)=>void,
316+
onGestureExit?: (
317+
timeline: GestureProvider,
318+
options: GestureOptionsRequired,
319+
instance: ViewTransitionInstance,
320+
types: Array<string>,
321+
)=>void,
322+
onGestureShare?: (
323+
timeline: GestureProvider,
324+
options: GestureOptionsRequired,
325+
instance: ViewTransitionInstance,
326+
types: Array<string>,
327+
)=>void,
328+
onGestureUpdate?: (
329+
timeline: GestureProvider,
330+
options: GestureOptionsRequired,
331+
instance: ViewTransitionInstance,
332+
types: Array<string>,
333+
)=>void,
305334
};
306335

307336
export type ActivityProps = {

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit 4bcf67e

Browse files
authored
Support onGestureEnter/Exit/Share/Update events (#35556)
This is like the onEnter/Exit/Share/Update events but for gestures. It allows manually controlling the animation using the passed timeline.
1 parent 41b3e9a commit 4bcf67e

5 files changed

Lines changed: 154 additions & 6 deletions

File tree

‎fixtures/view-transition/src/components/Page.js‎

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,48 @@ export default function Page({url, navigate}) {
8686
viewTransition.new.animate(keyframes,250);
8787
}
8888

89+
functiononGestureTransition(
90+
timeline,
91+
{rangeStart, rangeEnd},
92+
viewTransition,
93+
types
94+
){
95+
constkeyframes=[
96+
{rotate: '0deg',transformOrigin: '30px 8px'},
97+
{rotate: '360deg',transformOrigin: '30px 8px'},
98+
];
99+
constreverse=rangeStart>rangeEnd;
100+
if(timelineinstanceofAnimationTimeline){
101+
// Native Timeline
102+
constoptions={
103+
timeline: timeline,
104+
direction: reverse ? 'normal' : 'reverse',
105+
rangeStart: (reverse ? rangeEnd : rangeStart)+'%',
106+
rangeEnd: (reverse ? rangeStart : rangeEnd)+'%',
107+
};
108+
viewTransition.old.animate(keyframes,options);
109+
viewTransition.new.animate(keyframes,options);
110+
}else{
111+
// Custom Timeline
112+
constoptions={
113+
direction: reverse ? 'normal' : 'reverse',
114+
// We set the delay and duration to represent the span of the range.
115+
delay: reverse ? rangeEnd : rangeStart,
116+
duration: reverse ? rangeStart-rangeEnd : rangeEnd-rangeStart,
117+
};
118+
constanimation1=viewTransition.old.animate(keyframes,options);
119+
constanimation2=viewTransition.new.animate(keyframes,options);
120+
// Let the custom timeline take control of driving the animations.
121+
constcleanup1=timeline.animate(animation1);
122+
constcleanup2=timeline.animate(animation2);
123+
// TODO: Support returning a clean up function from ViewTransition events.
124+
// return () => {
125+
// cleanup1();
126+
// cleanup2();
127+
// };
128+
}
129+
}
130+
89131
functionswipeAction(){
90132
navigate(show ? '/?a' : '/?b');
91133
}
@@ -131,7 +173,10 @@ export default function Page({url, navigate}) {
131173
);
132174

133175
constexclamation=(
134-
<ViewTransitionname="exclamation"onShare={onTransition}>
176+
<ViewTransition
177+
name="exclamation"
178+
onShare={onTransition}
179+
onGestureShare={onGestureTransition}>
135180
<span>
136181
<div>!</div>
137182
</span>

‎packages/react-reconciler/src/ReactFiberApplyGesture.js‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import {
8282
enableComponentPerformanceTrack,
8383
}from'shared/ReactFeatureFlags';
8484
import{trackAnimatingTask}from'./ReactProfilerTimer';
85+
import{scheduleGestureTransitionEvent}from'./ReactFiberWorkLoop';
8586

8687
letdidWarnForRootClone=false;
8788

@@ -280,6 +281,7 @@ function applyAppearingPairViewTransition(child: Fiber): void {
280281
if(clones!==null){
281282
applyViewTransitionToClones(name,className,clones,child);
282283
}
284+
scheduleGestureTransitionEvent(child,props.onGestureShare);
283285
}
284286
}
285287
}
@@ -310,6 +312,11 @@ function applyExitViewTransition(placement: Fiber): void {
310312
if(clones!==null){
311313
applyViewTransitionToClones(name,className,clones,placement);
312314
}
315+
if(state.paired){
316+
scheduleGestureTransitionEvent(placement,props.onGestureShare);
317+
}else{
318+
scheduleGestureTransitionEvent(placement,props.onGestureExit);
319+
}
313320
}
314321
}
315322

@@ -1123,7 +1130,8 @@ function applyViewTransitionsOnFiber(finishedWork: Fiber) {
11231130
// TODO: If this doesn't end up canceled, because a parent animates,
11241131
// then we should probably issue an event since this instance is part of it.
11251132
}else{
1126-
// TODO: Schedule gesture events.
1133+
constprops: ViewTransitionProps=finishedWork.memoizedProps;
1134+
scheduleGestureTransitionEvent(finishedWork,props.onGestureUpdate);
11271135
// If this boundary did update, we cannot cancel its children so those are dropped.
11281136
popViewTransitionCancelableScope(prevCancelableChildren);
11291137
}

‎packages/react-reconciler/src/ReactFiberCommitViewTransitions.js‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ import {
3434
hasInstanceAffectedParent,
3535
wasInstanceInViewport,
3636
}from'./ReactFiberConfig';
37-
import{scheduleViewTransitionEvent}from'./ReactFiberWorkLoop';
37+
import{
38+
scheduleViewTransitionEvent,
39+
scheduleGestureTransitionEvent,
40+
}from'./ReactFiberWorkLoop';
3841
import{
3942
getViewTransitionName,
4043
getViewTransitionClassName,
@@ -312,7 +315,7 @@ export function commitEnterViewTransitions(
312315

313316
if(!state.paired){
314317
if(gesture){
315-
// TODO: Schedule gesture events.
318+
scheduleGestureTransitionEvent(placement,props.onGestureEnter);
316319
}else{
317320
scheduleViewTransitionEvent(placement,props.onEnter);
318321
}
@@ -848,7 +851,7 @@ export function measureNestedViewTransitions(
848851
// Nothing changed.
849852
}else{
850853
if(gesture){
851-
// TODO: Schedule gesture events.
854+
scheduleGestureTransitionEvent(child,props.onGestureUpdate);
852855
}else{
853856
scheduleViewTransitionEvent(child,props.onUpdate);
854857
}

‎packages/react-reconciler/src/ReactFiberWorkLoop.js‎

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99

1010
import{REACT_STRICT_MODE_TYPE}from'shared/ReactSymbols';
1111

12-
importtype{Wakeable,Thenable}from'shared/ReactTypes';
12+
importtype{
13+
Wakeable,
14+
Thenable,
15+
GestureOptionsRequired,
16+
}from'shared/ReactTypes';
1317
importtype{Fiber,FiberRoot}from'./ReactInternalTypes';
1418
importtype{Lanes,Lane}from'./ReactFiberLane';
1519
importtype{ActivityState}from'./ReactFiberActivityComponent';
@@ -26,6 +30,7 @@ import type {
2630
Resource,
2731
ViewTransitionInstance,
2832
RunningViewTransition,
33+
GestureTimeline,
2934
SuspendedState,
3035
}from'./ReactFiberConfig';
3136
importtype{RootState}from'./ReactFiberRoot';
@@ -913,6 +918,42 @@ export function scheduleViewTransitionEvent(
913918
}
914919
}
915920

921+
exportfunctionscheduleGestureTransitionEvent(
922+
fiber: Fiber,
923+
callback: ?(
924+
timeline: GestureTimeline,
925+
options: GestureOptionsRequired,
926+
instance: ViewTransitionInstance,
927+
types: Array<string>,
928+
)=>void,
929+
): void{
930+
if(enableGestureTransition){
931+
if(callback!=null){
932+
constapplyingGesture=pendingEffectsRoot.pendingGestures;
933+
if(applyingGesture!==null){
934+
conststate: ViewTransitionState=fiber.stateNode;
935+
letinstance=state.ref;
936+
if(instance===null){
937+
instance=state.ref=createViewTransitionInstance(
938+
getViewTransitionName(fiber.memoizedProps,state),
939+
);
940+
}
941+
consttimeline=applyingGesture.provider;
942+
constoptions={
943+
rangeStart: applyingGesture.rangeStart,
944+
rangeEnd: applyingGesture.rangeEnd,
945+
};
946+
if(pendingViewTransitionEvents===null){
947+
pendingViewTransitionEvents=[];
948+
}
949+
pendingViewTransitionEvents.push(
950+
callback.bind(null,timeline,options,instance),
951+
);
952+
}
953+
}
954+
}
955+
}
956+
916957
exportfunctionpeekDeferredLane(): Lane{
917958
returnworkInProgressDeferredLane;
918959
}
@@ -4352,6 +4393,8 @@ function applyGestureOnRoot(
43524393
startAnimating(pendingEffectsLanes);
43534394
}
43544395

4396+
pendingViewTransitionEvents=null;
4397+
43554398
constprevTransition=ReactSharedInternals.T;
43564399
ReactSharedInternals.T=null;
43574400
constpreviousPriority=getCurrentUpdatePriority();
@@ -4476,6 +4519,26 @@ function flushGestureAnimations(): void {
44764519
ReactSharedInternals.T=prevTransition;
44774520
}
44784521

4522+
if(enableViewTransition){
4523+
// We should now be after the startGestureTransition's .ready call which is late enough
4524+
// to start animating any pseudo-elements. We have also already applied any adjustments
4525+
// we do to the built-in animations which can now be read by the refs.
4526+
constpendingEvents=pendingViewTransitionEvents;
4527+
letpendingTypes=pendingTransitionTypes;
4528+
pendingTransitionTypes=null;
4529+
if(pendingEvents!==null){
4530+
pendingViewTransitionEvents=null;
4531+
if(pendingTypes===null){
4532+
// Normalize the type. This is lazily created only for events.
4533+
pendingTypes=[];
4534+
}
4535+
for(leti=0;i<pendingEvents.length;i++){
4536+
const viewTransitionEvent =pendingEvents[i];
4537+
viewTransitionEvent(pendingTypes);
4538+
}
4539+
}
4540+
}
4541+
44794542
if(enableProfilerTimer&&enableComponentPerformanceTrack){
44804543
finalizeRender(lanes,commitEndTime);
44814544
}

‎packages/shared/ReactTypes.js‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@ export type ViewTransitionClass =
290290
| string
291291
| ViewTransitionClassPerType;
292292

293+
export type GestureOptionsRequired = {
294+
rangeStart: number,
295+
rangeEnd: number,
296+
};
297+
293298
export type ViewTransitionProps = {
294299
name?: string,
295300
children?: ReactNodeList,
@@ -302,6 +307,30 @@ export type ViewTransitionProps = {
302307
onExit?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
303308
onShare?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
304309
onUpdate?: (instance: ViewTransitionInstance,types: Array<string>)=>void,
310+
onGestureEnter?: (
311+
timeline: GestureProvider,
312+
options: GestureOptionsRequired,
313+
instance: ViewTransitionInstance,
314+
types: Array<string>,
315+
)=>void,
316+
onGestureExit?: (
317+
timeline: GestureProvider,
318+
options: GestureOptionsRequired,
319+
instance: ViewTransitionInstance,
320+
types: Array<string>,
321+
)=>void,
322+
onGestureShare?: (
323+
timeline: GestureProvider,
324+
options: GestureOptionsRequired,
325+
instance: ViewTransitionInstance,
326+
types: Array<string>,
327+
)=>void,
328+
onGestureUpdate?: (
329+
timeline: GestureProvider,
330+
options: GestureOptionsRequired,
331+
instance: ViewTransitionInstance,
332+
types: Array<string>,
333+
)=>void,
305334
};
306335

307336
export type ActivityProps = {

0 commit comments

Comments
 (0)