Mirror: React hooks for accessible, common web interactions. UI super powers without the UI.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Rename `style` to `to` and add `final` option

+35 -15
+35 -15
src/useStyleTransition.ts
··· 5 5 const animations = new WeakMap<HTMLElement, Animation>(); 6 6 7 7 export interface TransitionOptions { 8 - style?: Style | null; 8 + to?: Style | null; 9 + final?: Style | null; 9 10 duration?: number | string; 10 11 easing?: string | [number, number, number, number]; 11 12 } 12 13 13 - const animate = (element: HTMLElement, options: TransitionOptions) => { 14 - const style = options.style || {}; 14 + const applyKeyframe = ( 15 + element: HTMLElement, 16 + style: Style 17 + ): [Keyframe, Keyframe] => { 15 18 const computed = getComputedStyle(element); 16 19 const from: Keyframe = {}; 17 20 const to: Keyframe = {}; 18 21 19 22 for (const propName in style) { 20 - let value: string = style[propName]; 21 - if (typeof value === 'number' && propName !== 'opacity') { 22 - (value as string) += 'px'; 23 - } 24 - 25 23 let key: string; 24 + let value: string = 25 + style[propName] + 26 + (typeof style[propName] === 'number' && propName !== 'opacity' 27 + ? 'px' 28 + : ''); 26 29 if (/^--/.test(propName)) { 27 30 key = propName; 28 31 from[key] = element.style.getPropertyValue(propName); 29 - element.style.setProperty(key, (to[key] = value)); 32 + element.style.setProperty(key, value); 30 33 } else { 31 34 if (propName === 'transform') { 32 35 key = propName; ··· 37 40 } 38 41 39 42 from[key] = computed[key]; 40 - element.style[key] = to[key] = value; 43 + element.style[key] = value; 41 44 } 45 + 46 + if (from[key] !== value) to[key] = value; 42 47 } 43 48 49 + return [from, to]; 50 + }; 51 + 52 + const animate = (element: HTMLElement, options: TransitionOptions) => { 44 53 const effect: KeyframeEffectOptions = { 45 54 duration: 46 55 typeof options.duration === 'number' ··· 54 63 const prevAnimation = animations.get(element); 55 64 if (prevAnimation) prevAnimation.cancel(); 56 65 57 - const animation = element.animate([from, to], effect); 66 + const keyframes = applyKeyframe(element, options.to || {}); 67 + const animation = element.animate(keyframes, effect); 68 + 58 69 animation.playbackRate = 1.000001; 59 70 animation.currentTime = 0.1; 60 71 61 72 let animating = false; 62 73 const media = matchMedia('(prefers-reduced-motion: reduce)'); 74 + const computed = getComputedStyle(element); 63 75 if (!media.matches) { 64 - for (const propName in from) { 76 + for (const propName in keyframes[1]) { 65 77 const value = /^--/.test(propName) 66 78 ? element.style.getPropertyValue(propName) 67 79 : computed[propName]; 68 - if (value !== from[propName]) { 80 + if (value !== keyframes[0][propName]) { 69 81 animating = true; 70 82 break; 71 83 } ··· 78 90 return; 79 91 } 80 92 81 - return new Promise<unknown>((resolve, reject) => { 93 + const promise = new Promise<unknown>((resolve, reject) => { 82 94 animations.set(element, animation); 83 95 animation.addEventListener('cancel', reject); 84 96 animation.addEventListener('finish', resolve); 85 97 }); 98 + 99 + if (options.final) { 100 + return promise.then(() => { 101 + applyKeyframe(element, options.final!); 102 + }); 103 + } 104 + 105 + return promise; 86 106 }; 87 107 88 108 export function useStyleTransition<T extends HTMLElement>( ··· 91 111 ): [boolean, (options: TransitionOptions) => Promise<void>] { 92 112 if (!options) options = {}; 93 113 94 - const style = options.style || {}; 114 + const style = options.to || {}; 95 115 const [state, setState] = useState<[boolean, Style]>([false, style]); 96 116 if (JSON.stringify(style) !== JSON.stringify(state[1])) { 97 117 setState([true, style]);