yunohost-admin/app/src/components/CardDeckFeed.vue

97 lines
2.2 KiB
Vue

<script setup lang="ts">
import { BCardGroup } from 'bootstrap-vue-next'
import {
getCurrentInstance,
h,
nextTick,
onBeforeUnmount,
onBeforeUpdate,
onMounted,
ref,
} from 'vue'
// Implementation of the feed pattern
// https://www.w3.org/WAI/ARIA/apg/patterns/feed/
const props = withDefaults(defineProps<{ stacks?: number }>(), { stacks: 21 })
const slots = defineSlots<{
default: any
}>()
const busy = ref(false)
const range = ref(props.stacks)
const childrenCount = ref(slots.default()[0].children.length)
const feedElem = ref<InstanceType<typeof BCardGroup> | null>(null)
function getTopParent(prev: HTMLElement): HTMLElement {
return prev.parentElement === feedElem.value?.$el
? prev
: getTopParent(prev.parentElement!)
}
const i = getCurrentInstance()
function onScroll() {
const elem = feedElem.value?.$el
if (
window.innerHeight >
elem.clientHeight + elem.getBoundingClientRect().top - 200
) {
busy.value = true
range.value = Math.min(range.value + props.stacks, childrenCount.value)
nextTick().then(() => {
busy.value = false
})
}
}
function onKeydown(e: KeyboardEvent) {
if (['PageUp', 'PageDown'].includes(e.code)) {
e.preventDefault()
const key = e.code === 'PageUp' ? 'previous' : 'next'
const sibling = getTopParent(e.target as HTMLElement)[
`${key}ElementSibling`
] as HTMLElement | null
sibling?.focus()
sibling?.scrollIntoView({ block: 'center' })
}
// FIXME Add `Home` and `End` shorcuts
}
onMounted(() => {
window.addEventListener('scroll', onScroll)
feedElem.value?.$el.addEventListener('keydown', onKeydown)
onScroll()
})
onBeforeUpdate(() => {
const children = slots.default()[0].children
if (childrenCount.value !== children.length) {
range.value = props.stacks
childrenCount.value = children.length
}
})
onBeforeUnmount(() => {
window.removeEventListener('scroll', onScroll)
feedElem.value?.$el.removeEventListener('keydown', onKeydown)
})
const root = () =>
h(
BCardGroup,
{
deck: true,
role: 'feed',
'aria-busy': busy.value,
ref: feedElem,
},
{
default: () => slots.default()[0].children.slice(0, range.value),
},
)
</script>
<template>
{{ busy }}
<root />
</template>