# useStreamingText

## Usage

Smooths bursty streamed text into a steady character-by-character reveal using requestAnimationFrame. Decouples arrival rate from display rate. Advances on grapheme boundaries, so a slice never cuts a surrogate pair, a flag, or a joined emoji in half. Animation timing comes from the `--astryx-duration-fast-min` motion token, with a fallback outside a theme provider. Snaps to full text when `isStreaming` becomes false, and stays on the full text while the reader prefers reduced motion.

Pass the accumulated text, not individual chunks; the hook handles the incremental reveal itself. `displayedText` is a `ComputedRef<string>`, so render it straight away.

```ts
import { useStreamingText } from '@astryx-vue/core'
import { ref } from 'vue'

const targetText = ref('')
const isStreaming = ref(true)
const displayedText = useStreamingText(targetText, isStreaming, { speed: 'natural' })
```

## Best practices

| Guidance | Practices |
| --- | --- |
| Do | Pass the accumulated text (not individual chunks) as `targetText`; the hook handles incremental reveal internally. |
| Do | Set `isStreaming` to false when the stream completes to snap to the final text. |
| Do | Use `speed="instant"` for non-animated contexts like search results or when reduced motion is preferred. |
| Don't | Use for static text that does not change; the hook adds unnecessary overhead for non-streaming content. |

## Examples

Common configurations, variations, and states.

### useStreamingText — Streaming Response

Smooth bursty generated text into a steady reveal with useStreamingText.

```vue
<script setup lang="ts">
import { useStreamingText } from '@astryx-vue/core'
import { Badge } from '@astryx-vue/core/Badge'
import { Button } from '@astryx-vue/core/Button'
import { Card } from '@astryx-vue/core/Card'
import { Heading } from '@astryx-vue/core/Heading'
import { Section } from '@astryx-vue/core/Section'
import { Stack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
import { onBeforeUnmount, ref } from 'vue'

const target = 'Astryx hooks keep behavior reusable while components keep visuals consistent.'

const source = ref('')
const isStreaming = ref(false)
const displayedText = useStreamingText(source, isStreaming)

let timer: ReturnType<typeof setInterval> | undefined

function stop(): void {
  if (timer !== undefined) {
    clearInterval(timer)
    timer = undefined
  }
}

function stream(): void {
  stop()
  source.value = ''
  isStreaming.value = true
  let offset = 0
  // 每 200ms 送一小段，模拟生成中的分块到达
  timer = setInterval(() => {
    offset = Math.min(offset + 8, target.length)
    source.value = target.slice(0, offset)
    if (offset >= target.length) {
      isStreaming.value = false
      stop()
    }
  }, 200)
}

onBeforeUnmount(stop)
stream()
</script>

<template>
  <Section variant="muted" :padding="4" :max-width="520">
    <Card :padding="4" width="100%">
      <Stack direction="vertical" :gap="3">
        <Stack direction="horizontal" :gap="2" align="center">
          <Heading :level="4">
            Assistant response
          </Heading>
          <Badge :variant="isStreaming ? 'info' : 'success'">
            {{ isStreaming ? 'Streaming' : 'Complete' }}
          </Badge>
        </Stack>
        <Text class="streaming-text" type="body">
          {{ displayedText }}
        </Text>
        <Stack direction="horizontal" :gap="2">
          <Button label="Stream again" size="sm" variant="secondary" @click="stream" />
        </Stack>
      </Stack>
    </Card>
  </Section>
</template>

<style scoped>
.streaming-text {
  min-height: 3.5rem;
}
</style>
```

## Parameters

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `targetText` | `MaybeRefOrGetter<string>` | — | The full target text to reveal. As new chunks arrive, update this value with the accumulated text. |
| `isStreaming` | `MaybeRefOrGetter<boolean>` | `false` | Whether text is currently being streamed. When false, the hook returns the full `targetText` immediately. |
| `options` | `MaybeRefOrGetter` | — | Optional configuration for streaming behavior. |
| `options.speed` | `'natural' \| 'fast' \| 'instant'` | `'natural'` | Speed preset for text reveal. `natural` reveals a short run at each motion tick, `fast` ticks twice as often so it drains the backlog sooner, and `instant` returns the full text with no animation. |

## Returns

| Name | Type | Description |
| --- | --- | --- |
| `displayedText` | `ComputedRef<string>` | The portion of `targetText` to render. Grows steadily toward the full `targetText` during streaming, or equals `targetText` when not streaming. |
