98 lines
		
	
	
		
			2.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			98 lines
		
	
	
		
			2.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
| import type { Ref } from 'vue';
 | |
| import { onBeforeUnmount, ref, watch, onMounted } from 'vue';
 | |
| import raf from '../../_util/raf';
 | |
| 
 | |
| /**
 | |
|  * Popup should follow the steps for each component work correctly:
 | |
|  * measure - check for the value stretch size
 | |
|  * align - let component align the position
 | |
|  * aligned - re-align again in case additional className changed the size
 | |
|  * afterAlign - choice next step is trigger motion or finished
 | |
|  * beforeMotion - should reset motion to invisible so that CSSMotion can do normal motion
 | |
|  * motion - play the motion
 | |
|  * stable - everything is done
 | |
|  */
 | |
| type PopupStatus = null | 'measure' | 'align' | 'aligned' | 'motion' | 'stable';
 | |
| 
 | |
| type Func = () => void;
 | |
| 
 | |
| const StatusQueue: PopupStatus[] = ['measure', 'align', null, 'motion'];
 | |
| 
 | |
| export default (
 | |
|   visible: Ref<boolean>,
 | |
|   doMeasure: Func,
 | |
| ): [Ref<PopupStatus>, (callback?: () => void) => void] => {
 | |
|   const status = ref<PopupStatus>(null);
 | |
|   const rafRef = ref<number>();
 | |
|   const destroyRef = ref(false);
 | |
|   function setStatus(nextStatus: PopupStatus) {
 | |
|     if (!destroyRef.value) {
 | |
|       status.value = nextStatus;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   function cancelRaf() {
 | |
|     raf.cancel(rafRef.value);
 | |
|   }
 | |
| 
 | |
|   function goNextStatus(callback?: () => void) {
 | |
|     cancelRaf();
 | |
|     rafRef.value = raf(() => {
 | |
|       // Only align should be manually trigger
 | |
|       let newStatus = status.value;
 | |
|       switch (status.value) {
 | |
|         case 'align':
 | |
|           newStatus = 'motion';
 | |
|           break;
 | |
|         case 'motion':
 | |
|           newStatus = 'stable';
 | |
|           break;
 | |
|         default:
 | |
|       }
 | |
|       setStatus(newStatus);
 | |
| 
 | |
|       callback?.();
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   watch(
 | |
|     visible,
 | |
|     () => {
 | |
|       setStatus('measure');
 | |
|     },
 | |
|     { immediate: true, flush: 'post' },
 | |
|   );
 | |
|   onMounted(() => {
 | |
|     // Go next status
 | |
|     watch(
 | |
|       status,
 | |
|       () => {
 | |
|         switch (status.value) {
 | |
|           case 'measure':
 | |
|             doMeasure();
 | |
|             break;
 | |
|           default:
 | |
|         }
 | |
| 
 | |
|         if (status.value) {
 | |
|           rafRef.value = raf(async () => {
 | |
|             const index = StatusQueue.indexOf(status.value);
 | |
|             const nextStatus = StatusQueue[index + 1];
 | |
|             if (nextStatus && index !== -1) {
 | |
|               setStatus(nextStatus);
 | |
|             }
 | |
|           });
 | |
|         }
 | |
|       },
 | |
|       { immediate: true, flush: 'post' },
 | |
|     );
 | |
|   });
 | |
| 
 | |
|   onBeforeUnmount(() => {
 | |
|     destroyRef.value = true;
 | |
|     cancelRaf();
 | |
|   });
 | |
| 
 | |
|   return [status, goNextStatus];
 | |
| };
 |