<template>
  <transition name="expand" @after-enter="afterEnter" @enter="enter" @leave="leave">
    <slot />
  </transition>
</template>

<script setup lang="ts">
import { useReducedMotion } from '@/composables/useReducedMotion'

const prefersReducedMotion = useReducedMotion()

const afterEnter = (element: Element) => {
  const el = element as HTMLElement
  el.style.height = `auto`
}

const enter = (element: Element) => {
  const el = element as HTMLElement

  if (prefersReducedMotion.value) {
    el.style.height = 'auto'
    return
  }

  const { width } = getComputedStyle(el)

  el.style.width = width
  el.style.position = `absolute`
  el.style.visibility = `hidden`
  el.style.height = `auto`

  const { height } = getComputedStyle(el)

  el.style.width = ''
  el.style.position = ''
  el.style.visibility = ''
  el.style.height = '0'

  // Force repaint to make sure the
  // animation is triggered correctly.

  // Intentional expression statement to force layout recalculation before animating.
  void getComputedStyle(el).height

  requestAnimationFrame(() => (el.style.height = height))
}

const leave = (element: Element): void => {
  const el = element as HTMLElement

  if (prefersReducedMotion.value) {
    el.style.height = '0'
    return
  }

  const { height } = getComputedStyle(el)

  el.style.height = height

  // Force repaint to make sure the
  // animation is triggered correctly.
  requestAnimationFrame(() => {
    el.style.height = '0'
  })
}
</script>

<style scoped>
* {
  backface-visibility: hidden;
  perspective: 1000px;
  transform: translateZ(0);
  will-change: height;
}
</style>

<style lang="scss">
.expand-enter-active,
.expand-leave-active {
  overflow: hidden;
  transition-duration: 0.2s;
  transition-property: opacity, height;
  transition-timing-function: ease-out;
}

.expand-enter-from,
.expand-leave-to {
  height: 0;
  opacity: 0;
}
</style>
