export interface TypewriterSentence { text: string style?: 'title' | 'citation' | 'text' stays?: boolean separator?: boolean } interface SequenceOptions { holdMs?: number gapMs?: number } export function useTypewriter(sentences: TypewriterSentence[], options: SequenceOptions = {}) { const { holdMs = 2400, gapMs = 300, } = options const currentIndex = ref(-1) const showActive = ref(false) const lockedSentences = ref([]) const isComplete = ref(false) let holdTimer: ReturnType | null = null const currentSentence = computed(() => currentIndex.value >= 0 && currentIndex.value < sentences.length ? sentences[currentIndex.value] : null, ) function clearTimer() { if (holdTimer) { clearTimeout(holdTimer) holdTimer = null } } function showNext() { const nextIdx = currentIndex.value + 1 if (nextIdx >= sentences.length) { isComplete.value = true return } currentIndex.value = nextIdx const sentence = sentences[nextIdx] if (sentence.separator) { lockedSentences.value = [...lockedSentences.value, { text: '', separator: true }] } showActive.value = true } /** Called by component via @after-enter on Transition */ function onEntered() { holdTimer = setTimeout(() => { showActive.value = false }, holdMs) } /** Called by component via @after-leave on Transition */ function onLeft() { const sentence = sentences[currentIndex.value] if (sentence?.stays) { lockedSentences.value = [...lockedSentences.value, { ...sentence }] } setTimeout(showNext, gapMs) } function start() { showNext() } function skipToEnd() { clearTimer() showActive.value = false const locked: TypewriterSentence[] = [] for (const sentence of sentences) { if (sentence.separator) { locked.push({ text: '', separator: true }) } if (sentence.stays) { locked.push({ ...sentence }) } } lockedSentences.value = locked currentIndex.value = sentences.length - 1 isComplete.value = true } onUnmounted(clearTimer) return { currentIndex: readonly(currentIndex), currentSentence, showActive, lockedSentences: readonly(lockedSentences), isComplete: readonly(isComplete), onEntered, onLeft, start, skipToEnd, } }