From d8e6f73f7e57ed413e1fca4b1a7843254a414274 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 12 May 2021 11:31:32 +0200 Subject: [PATCH] Add `entered` prop to Transition components (#504) * introduce `entered` prop on the Transition components * update Dialog examples to make use of the `entered` prop --- .../@headlessui-react/pages/dialog/dialog.tsx | 10 ++-- .../src/components/transitions/transition.tsx | 8 ++- .../transitions/utils/transition.test.ts | 12 ++--- .../transitions/utils/transition.ts | 5 +- .../examples/src/components/dialog/dialog.vue | 9 ++-- .../src/components/transitions/transition.ts | 51 +++++++++++++------ .../transitions/utils/transition.test.ts | 12 ++--- .../transitions/utils/transition.ts | 5 +- 8 files changed, 70 insertions(+), 42 deletions(-) diff --git a/packages/@headlessui-react/pages/dialog/dialog.tsx b/packages/@headlessui-react/pages/dialog/dialog.tsx index 1717428f6..8e3f0552f 100644 --- a/packages/@headlessui-react/pages/dialog/dialog.tsx +++ b/packages/@headlessui-react/pages/dialog/dialog.tsx @@ -74,16 +74,16 @@ export default function Home() {
- -
-
+
{ + ? transition(node, enterClasses, enterFromClasses, enterToClasses, enteredClasses, reason => { isTransitioning.current = false if (reason === Reason.Finished) events.current.afterEnter() }) - : transition(node, leaveClasses, leaveFromClasses, leaveToClasses, reason => { + : transition(node, leaveClasses, leaveFromClasses, leaveToClasses, enteredClasses, reason => { isTransitioning.current = false if (reason !== Reason.Finished) return diff --git a/packages/@headlessui-react/src/components/transitions/utils/transition.test.ts b/packages/@headlessui-react/src/components/transitions/utils/transition.test.ts index f5b2e0c01..991871617 100644 --- a/packages/@headlessui-react/src/components/transitions/utils/transition.test.ts +++ b/packages/@headlessui-react/src/components/transitions/utils/transition.test.ts @@ -27,7 +27,7 @@ it('should be possible to transition', async () => { ) await new Promise(resolve => { - transition(element, ['enter'], ['enterFrom'], ['enterTo'], resolve) + transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve) }) await new Promise(resolve => d.nextFrame(resolve)) @@ -42,7 +42,7 @@ it('should be possible to transition', async () => { // necessary to put the classes on the element and immediately remove them. // Cleanup phase - expect(snapshots[2].content).toEqual('
') + expect(snapshots[2].content).toEqual('
') d.dispose() }) @@ -71,7 +71,7 @@ it('should wait the correct amount of time to finish a transition', async () => ) let reason = await new Promise(resolve => { - transition(element, ['enter'], ['enterFrom'], ['enterTo'], resolve) + transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve) }) await new Promise(resolve => d.nextFrame(resolve)) @@ -98,7 +98,7 @@ it('should wait the correct amount of time to finish a transition', async () => // Cleanup phase expect(snapshots[3].content).toEqual( - `
` + `
` ) }) @@ -128,7 +128,7 @@ it('should keep the delay time into account', async () => { ) let reason = await new Promise(resolve => { - transition(element, ['enter'], ['enterFrom'], ['enterTo'], resolve) + transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve) }) await new Promise(resolve => d.nextFrame(resolve)) @@ -178,7 +178,7 @@ it('should be possible to cancel a transition at any time', async () => { expect.assertions(2) // Setup the transition - let cancel = transition(element, ['enter'], ['enterFrom'], ['enterTo'], reason => { + let cancel = transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], reason => { expect(reason).toBe(Reason.Cancelled) }) diff --git a/packages/@headlessui-react/src/components/transitions/utils/transition.ts b/packages/@headlessui-react/src/components/transitions/utils/transition.ts index 67f1ffb66..6201eae0c 100644 --- a/packages/@headlessui-react/src/components/transitions/utils/transition.ts +++ b/packages/@headlessui-react/src/components/transitions/utils/transition.ts @@ -60,11 +60,13 @@ export function transition( base: string[], from: string[], to: string[], + entered: string[], done?: (reason: Reason) => void ) { let d = disposables() let _done = done !== undefined ? once(done) : () => {} + removeClasses(node, ...entered) addClasses(node, ...base, ...from) d.nextFrame(() => { @@ -74,6 +76,7 @@ export function transition( d.add( waitForTransition(node, reason => { removeClasses(node, ...to, ...base) + addClasses(node, ...entered) return _done(reason) }) ) @@ -83,7 +86,7 @@ export function transition( // the node itself will be nullified and will be a no-op. In case of a full transition the classes // are already removed which is also a no-op. However if you go from enter -> leave mid-transition // then we have some leftovers that should be cleaned. - d.add(() => removeClasses(node, ...base, ...from, ...to)) + d.add(() => removeClasses(node, ...base, ...from, ...to, ...entered)) // When we get disposed early, than we should also call the done method but switch the reason. d.add(() => _done(Reason.Cancelled)) diff --git a/packages/@headlessui-vue/examples/src/components/dialog/dialog.vue b/packages/@headlessui-vue/examples/src/components/dialog/dialog.vue index 4b744c553..161e97eb2 100644 --- a/packages/@headlessui-vue/examples/src/components/dialog/dialog.vue +++ b/packages/@headlessui-vue/examples/src/components/dialog/dialog.vue @@ -17,14 +17,13 @@ as="template" enter="ease-out duration-300" enterFrom="opacity-0" - enterTo="opacity-100" + enterTo="opacity-75" leave="ease-in duration-200" - leaveFrom="opacity-100" + leaveFrom="opacity-75" leaveTo="opacity-0" + entered="opacity-75" > - -
-
+ { - isTransitioning.value = false - if (reason === Reason.Finished) emit('afterEnter') - }) - : transition(node, leaveClasses, leaveFromClasses, leaveToClasses, reason => { - isTransitioning.value = false - - if (reason !== Reason.Finished) return - - // When we don't have children anymore we can safely unregister from the parent and hide - // ourselves. - if (!hasChildren(nesting)) { - state.value = TreeStates.Hidden - unregister(id) - emit('afterLeave') + ? transition( + node, + enterClasses, + enterFromClasses, + enterToClasses, + enteredClasses, + reason => { + isTransitioning.value = false + if (reason === Reason.Finished) emit('afterEnter') + } + ) + : transition( + node, + leaveClasses, + leaveFromClasses, + leaveToClasses, + enteredClasses, + reason => { + isTransitioning.value = false + + if (reason !== Reason.Finished) return + + // When we don't have children anymore we can safely unregister from the parent and hide + // ourselves. + if (!hasChildren(nesting)) { + state.value = TreeStates.Hidden + unregister(id) + emit('afterLeave') + } } - }) + ) ) } @@ -334,6 +352,7 @@ export let TransitionRoot = defineComponent({ enter: { type: [String], default: '' }, enterFrom: { type: [String], default: '' }, enterTo: { type: [String], default: '' }, + entered: { type: [String], default: '' }, leave: { type: [String], default: '' }, leaveFrom: { type: [String], default: '' }, leaveTo: { type: [String], default: '' }, diff --git a/packages/@headlessui-vue/src/components/transitions/utils/transition.test.ts b/packages/@headlessui-vue/src/components/transitions/utils/transition.test.ts index f5b2e0c01..991871617 100644 --- a/packages/@headlessui-vue/src/components/transitions/utils/transition.test.ts +++ b/packages/@headlessui-vue/src/components/transitions/utils/transition.test.ts @@ -27,7 +27,7 @@ it('should be possible to transition', async () => { ) await new Promise(resolve => { - transition(element, ['enter'], ['enterFrom'], ['enterTo'], resolve) + transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve) }) await new Promise(resolve => d.nextFrame(resolve)) @@ -42,7 +42,7 @@ it('should be possible to transition', async () => { // necessary to put the classes on the element and immediately remove them. // Cleanup phase - expect(snapshots[2].content).toEqual('
') + expect(snapshots[2].content).toEqual('
') d.dispose() }) @@ -71,7 +71,7 @@ it('should wait the correct amount of time to finish a transition', async () => ) let reason = await new Promise(resolve => { - transition(element, ['enter'], ['enterFrom'], ['enterTo'], resolve) + transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve) }) await new Promise(resolve => d.nextFrame(resolve)) @@ -98,7 +98,7 @@ it('should wait the correct amount of time to finish a transition', async () => // Cleanup phase expect(snapshots[3].content).toEqual( - `
` + `
` ) }) @@ -128,7 +128,7 @@ it('should keep the delay time into account', async () => { ) let reason = await new Promise(resolve => { - transition(element, ['enter'], ['enterFrom'], ['enterTo'], resolve) + transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], resolve) }) await new Promise(resolve => d.nextFrame(resolve)) @@ -178,7 +178,7 @@ it('should be possible to cancel a transition at any time', async () => { expect.assertions(2) // Setup the transition - let cancel = transition(element, ['enter'], ['enterFrom'], ['enterTo'], reason => { + let cancel = transition(element, ['enter'], ['enterFrom'], ['enterTo'], ['entered'], reason => { expect(reason).toBe(Reason.Cancelled) }) diff --git a/packages/@headlessui-vue/src/components/transitions/utils/transition.ts b/packages/@headlessui-vue/src/components/transitions/utils/transition.ts index 24baf2167..8382141da 100644 --- a/packages/@headlessui-vue/src/components/transitions/utils/transition.ts +++ b/packages/@headlessui-vue/src/components/transitions/utils/transition.ts @@ -58,11 +58,13 @@ export function transition( base: string[], from: string[], to: string[], + entered: string[], done?: (reason: Reason) => void ) { let d = disposables() let _done = done !== undefined ? once(done) : () => {} + removeClasses(node, ...entered) addClasses(node, ...base, ...from) d.nextFrame(() => { @@ -72,6 +74,7 @@ export function transition( d.add( waitForTransition(node, reason => { removeClasses(node, ...to, ...base) + addClasses(node, ...entered) return _done(reason) }) ) @@ -81,7 +84,7 @@ export function transition( // the node itself will be nullified and will be a no-op. In case of a full transition the classes // are already removed which is also a no-op. However if you go from enter -> leave mid-transition // then we have some leftovers that should be cleaned. - d.add(() => removeClasses(node, ...base, ...from, ...to)) + d.add(() => removeClasses(node, ...base, ...from, ...to, ...entered)) // When we get disposed early, than we should also call the done method but switch the reason. d.add(() => _done(Reason.Cancelled))