134 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Vue
		
	
	
			
		
		
	
	
			134 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Vue
		
	
	
| import type { CSSProperties } from 'vue';
 | |
| import { computed, defineComponent, onUnmounted, reactive, ref, watch } from 'vue';
 | |
| import classNames from '../_util/classNames';
 | |
| 
 | |
| export interface UnitNumberProps {
 | |
|   prefixCls: string;
 | |
|   value: string | number;
 | |
|   offset?: number;
 | |
|   current?: boolean;
 | |
| }
 | |
| 
 | |
| function UnitNumber({ prefixCls, value, current, offset = 0 }: UnitNumberProps) {
 | |
|   let style: CSSProperties | undefined;
 | |
| 
 | |
|   if (offset) {
 | |
|     style = {
 | |
|       position: 'absolute',
 | |
|       top: `${offset}00%`,
 | |
|       left: 0,
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   return (
 | |
|     <p
 | |
|       style={style}
 | |
|       class={classNames(`${prefixCls}-only-unit`, {
 | |
|         current,
 | |
|       })}
 | |
|     >
 | |
|       {value}
 | |
|     </p>
 | |
|   );
 | |
| }
 | |
| 
 | |
| function getOffset(start: number, end: number, unit: -1 | 1) {
 | |
|   let index = start;
 | |
|   let offset = 0;
 | |
| 
 | |
|   while ((index + 10) % 10 !== end) {
 | |
|     index += unit;
 | |
|     offset += unit;
 | |
|   }
 | |
| 
 | |
|   return offset;
 | |
| }
 | |
| 
 | |
| export default defineComponent({
 | |
|   compatConfig: { MODE: 3 },
 | |
|   name: 'SingleNumber',
 | |
|   props: {
 | |
|     prefixCls: String,
 | |
|     value: String,
 | |
|     count: Number,
 | |
|   },
 | |
|   setup(props) {
 | |
|     const originValue = computed(() => Number(props.value));
 | |
|     const originCount = computed(() => Math.abs(props.count));
 | |
|     const state = reactive({
 | |
|       prevValue: originValue.value,
 | |
|       prevCount: originCount.value,
 | |
|     });
 | |
| 
 | |
|     // ============================= Events =============================
 | |
|     const onTransitionEnd = () => {
 | |
|       state.prevValue = originValue.value;
 | |
|       state.prevCount = originCount.value;
 | |
|     };
 | |
|     const timeout = ref();
 | |
|     // Fallback if transition event not support
 | |
|     watch(
 | |
|       originValue,
 | |
|       () => {
 | |
|         clearTimeout(timeout.value);
 | |
|         timeout.value = setTimeout(() => {
 | |
|           onTransitionEnd();
 | |
|         }, 1000);
 | |
|       },
 | |
|       { flush: 'post' },
 | |
|     );
 | |
|     onUnmounted(() => {
 | |
|       clearTimeout(timeout.value);
 | |
|     });
 | |
| 
 | |
|     return () => {
 | |
|       let unitNodes: any[];
 | |
|       let offsetStyle: CSSProperties = {};
 | |
|       const value = originValue.value;
 | |
|       if (state.prevValue === value || Number.isNaN(value) || Number.isNaN(state.prevValue)) {
 | |
|         // Nothing to change
 | |
|         unitNodes = [UnitNumber({ ...props, current: true } as UnitNumberProps)];
 | |
|         offsetStyle = {
 | |
|           transition: 'none',
 | |
|         };
 | |
|       } else {
 | |
|         unitNodes = [];
 | |
| 
 | |
|         // Fill basic number units
 | |
|         const end = value + 10;
 | |
|         const unitNumberList: number[] = [];
 | |
|         for (let index = value; index <= end; index += 1) {
 | |
|           unitNumberList.push(index);
 | |
|         }
 | |
| 
 | |
|         // Fill with number unit nodes
 | |
|         const prevIndex = unitNumberList.findIndex(n => n % 10 === state.prevValue);
 | |
|         unitNodes = unitNumberList.map((n, index) => {
 | |
|           const singleUnit = n % 10;
 | |
|           return UnitNumber({
 | |
|             ...props,
 | |
|             value: singleUnit,
 | |
|             offset: index - prevIndex,
 | |
|             current: index === prevIndex,
 | |
|           } as UnitNumberProps);
 | |
|         });
 | |
| 
 | |
|         // Calculate container offset value
 | |
|         const unit = state.prevCount < originCount.value ? 1 : -1;
 | |
|         offsetStyle = {
 | |
|           transform: `translateY(${-getOffset(state.prevValue, value, unit)}00%)`,
 | |
|         };
 | |
|       }
 | |
|       return (
 | |
|         <span
 | |
|           class={`${props.prefixCls}-only`}
 | |
|           style={offsetStyle}
 | |
|           onTransitionend={() => onTransitionEnd()}
 | |
|         >
 | |
|           {unitNodes}
 | |
|         </span>
 | |
|       );
 | |
|     };
 | |
|   },
 | |
| });
 |