Coding | December 2023Read in Outline
<template>
<HStack
p="2"
justifyContent="center"
alignItems="center"
:css="{ '--height': '25px' }"
:_before="{
content: ' ',
position: 'absolute',
top: 0,
width: '100%',
height: 'calc(50% - 3.5ex)',
pointerEvents: 'none',
background:
'linear-gradient(hsl(200 20% 10%),65%,hsl(200 20% 10% 0%))'
}"
:_after="{
content: ' ',
position: 'absolute',
top: 0,
width: '100%',
height: 'calc(50% - 3.5ex)',
pointerEvents: 'none',
background:
'linear-gradient(hsl(200 20% 10%),65%,hsl(200 20% 10% 0%))'
}"
>
<Stack height="calc(5 * var(--height))" justifyContent="center">
<Stack
ref="hourScroll"
gap="0"
overflowY="auto"
scrollSnapType="y mandatory"
py="calc(2 * var(--height))"
alignItems="center"
scrollbar="none"
overscrollBehavior="contain"
>
<template v-for="i in 24" :key="i">
<Text scrollSnapAlign="center" py="3" px="8">
{{ i - 1 }}
</Text>
<Divider />
</template>
</Stack>
</Stack>
<Text> : </Text>
<Stack height="calc(5 * var(--height))" justifyContent="center">
<Stack
ref="minuteScroll"
gap="0"
overflowY="auto"
scrollSnapAlign="center"
scrollSnapType="y mandatory"
py="calc(2 * var(--height))"
scrollbar="none"
alignItems="center"
overscrollBehavior="contain"
>
<template v-for="i in 60" :key="i">
<Text scrollSnapAlign="center" py="3" px="8">
{{ (i - 1).toString().padStart(2, '0') }}
</Text>
<Divider />
</template>
</Stack>
</Stack>
</HStack>
</template>
<script lang="ts" setup>
import debounce from 'lodash/debounce'
import type Stack from '../layout/Stack'
const hourScroll = ref<ComponentPublicInstance<typeof Stack> | null>(null)
const minuteScroll = ref<ComponentPublicInstance<typeof Stack> | null>(null)
const lastScrollMinute = ref<number>(0)
const lastScrollHour = ref<number>(0)
const props = defineProps<{ modelValue?: string }>()
const emit = defineEmits(['update:modelValue'])
const getMinutesHours = (time?: string) => {
const [hour, minutes] = time?.includes(':')
? time.split(':')
: [new Date().getHours(), new Date().getMinutes()].map((r) =>
r.toString()
)
return { hour, minutes }
}
function determineSnapped(
container: HTMLElement,
e: Event,
lastScrollPos: number
) {
if (!container) {
return
}
const target = e?.target as HTMLDivElement
if (!e || !target || target.scrollTop === lastScrollPos) {
return undefined
}
const viewportHeight = window.innerHeight
container.style.gap = '100vh'
container.getBoundingClientRect()
const value = Array.from(container.children).find((child) => {
const { top } = child.getBoundingClientRect()
return top > 0 && top < viewportHeight
})?.textContent
container.style.gap = ''
return {
value,
scrollPos: target.scrollTop
}
}
const handleHourScroll = debounce((e: Event) => {
const res = determineSnapped(
hourScroll.value?.$el,
e,
lastScrollHour.value
)
if (!res) {
return
}
const { value, scrollPos } = res
lastScrollHour.value = scrollPos
const { minutes } = getMinutesHours(props.modelValue)
emit('update:modelValue', `${value}:${minutes}`)
}, 100)
const handleMinuteScroll = debounce((e: Event) => {
const res = determineSnapped(
minuteScroll.value?.$el,
e,
lastScrollMinute.value
)
if (!res) {
return
}
const { value, scrollPos } = res
lastScrollMinute.value = scrollPos
const { hour } = getMinutesHours(props.modelValue)
emit('update:modelValue', `${hour}:${value?.padStart(2, '0')}`)
}, 100)
onMounted(() => {
const { hour, minutes } = getMinutesHours(props.modelValue)
const hourContainer = hourScroll.value?.$el as HTMLDivElement
const minuteContainer = minuteScroll.value?.$el as HTMLDivElement
if (!hourContainer || !minuteContainer) {
return
}
console.log('mounted', hour, minutes)
Array.from(hourContainer.children).forEach((child) => {
if (child.textContent === hour) {
child.scrollIntoView({
block: 'center'
})
}
})
Array.from(minuteContainer.children).forEach((child) => {
if (child.textContent === minutes) {
child.scrollIntoView({
block: 'center'
})
}
})
hourContainer?.addEventListener('scroll', handleHourScroll)
minuteContainer?.addEventListener('scroll', handleMinuteScroll)
})
onUnmounted(() => {
hourScroll.value?.$el?.removeEventListener('scroll', handleHourScroll)
minuteScroll.value?.$el?.removeEventListener(
'scroll',
handleMinuteScroll
)
})
</script>
<style></style>
© 2023-2024 HamP, Assets used in the site belongs to respective owner | View Source