feat: animate completed items out
This commit is contained in:
@@ -3,6 +3,25 @@ type CompletionId = string
|
||||
type CompletionTimer = number
|
||||
type CompletionFrame = number
|
||||
|
||||
export const COMPLETION_EXIT_DURATION = 320
|
||||
|
||||
export function shouldAnimateCompletionExit({ completing, showCompleted }: { completing: boolean; showCompleted: boolean }) {
|
||||
return completing && !showCompleted
|
||||
}
|
||||
|
||||
export function completionExitDelay(reducedMotion: boolean) {
|
||||
return reducedMotion ? 0 : COMPLETION_EXIT_DURATION
|
||||
}
|
||||
|
||||
export function prefersReducedMotion() {
|
||||
return window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false
|
||||
}
|
||||
|
||||
export function waitForCompletionExit(reducedMotion = prefersReducedMotion()) {
|
||||
const delay = completionExitDelay(reducedMotion)
|
||||
return delay ? new Promise<void>((resolve) => window.setTimeout(resolve, delay)) : Promise.resolve()
|
||||
}
|
||||
|
||||
export function createCompletionPulse(
|
||||
activate: (id: CompletionId) => void,
|
||||
deactivate: (id: CompletionId) => void,
|
||||
|
||||
@@ -227,13 +227,15 @@ export async function runLatestRequest<T>(
|
||||
return committed
|
||||
}
|
||||
|
||||
type MutationSuccessCallback<T> = (value: T) => void | Promise<void>
|
||||
|
||||
export type MutationReconciler<TContext> = {
|
||||
run<T>(
|
||||
mutation: () => Promise<T>,
|
||||
onSuccess: (value: T) => void,
|
||||
onError?: (reason: unknown) => void,
|
||||
onCurrentSuccess?: (value: T) => void,
|
||||
): Promise<void>
|
||||
onCurrentSuccess?: MutationSuccessCallback<T>,
|
||||
): Promise<boolean>
|
||||
}
|
||||
|
||||
export function createMutationReconciler<TContext>(
|
||||
@@ -269,12 +271,16 @@ export function createMutationReconciler<TContext>(
|
||||
value = await mutation()
|
||||
} catch (reason) {
|
||||
onError?.(reason)
|
||||
return
|
||||
return false
|
||||
}
|
||||
onSuccess(value)
|
||||
if (sameContext(context, currentContext())) onCurrentSuccess?.(value)
|
||||
if (sameContext(context, currentContext())) {
|
||||
const currentSuccess = onCurrentSuccess?.(value)
|
||||
if (currentSuccess instanceof Promise) await currentSuccess
|
||||
}
|
||||
await reconcile(context)
|
||||
if (sameContext(context, currentContext()) && reconciled < dirty) await reconcile(context)
|
||||
return true
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,31 @@ describe('request generation protection', () => {
|
||||
await Promise.all([first, second])
|
||||
})
|
||||
|
||||
it('waits for current-context success work before refreshing', async () => {
|
||||
const events: string[] = []
|
||||
let releaseExit!: () => void
|
||||
const reconciler = createMutationReconciler(
|
||||
() => ({ view: 'today' }),
|
||||
(left, right) => left.view === right.view,
|
||||
async () => { events.push('refresh') },
|
||||
)
|
||||
const mutation = reconciler.run(
|
||||
async () => 'done',
|
||||
() => events.push('success'),
|
||||
undefined,
|
||||
async () => {
|
||||
events.push('exit:start')
|
||||
await new Promise<void>((resolve) => { releaseExit = resolve })
|
||||
events.push('exit:end')
|
||||
},
|
||||
)
|
||||
await Promise.resolve()
|
||||
expect(events).toEqual(['success', 'exit:start'])
|
||||
releaseExit()
|
||||
await mutation
|
||||
expect(events).toEqual(['success', 'exit:start', 'exit:end', 'refresh'])
|
||||
})
|
||||
|
||||
it('refreshes again when the first refresh finishes before the second mutation commits', async () => {
|
||||
const events: string[] = []
|
||||
const context = { view: 'today' }
|
||||
|
||||
Reference in New Issue
Block a user