# Chat Composer Input

## Usage

Pass ChatComposerInput to ChatComposer's `input` slot when the draft needs trigger menus, inline tokens, history recall, dictation insertion, or file intake.

```ts
import { ChatComposerInput } from '@astryx-vue/core/Chat'
```

### Anatomy

- **Input root**: The themed container that forwards the root ref and BaseProps styling seams.
- **Editable surface**: The labeled contenteditable textbox or combobox that owns text, selection, keyboard, paste, and drop behavior.
- **Placeholder**: Visual guidance shown only while the serialized draft is empty.
- **Trigger menu**: The suggestion popup rendered while a configured trigger is active.
- **Inline token**: A non-editable badge or custom rendering with a stable serialized value.

## Best practices

| Guidance | Practices |
| --- | --- |
| Do | Use the exposed handle for programmatic text or token insertion; inserted text follows the same `change` pipeline as typing. |
| Do | Use `files` for files pasted or dropped onto the editor, and keep file validation and upload progress in the product layer. |
| Don't | Use custom token rendering for ordinary mentions or commands; prefer the structured badge form so tokens remain predictable and themeable. |

## Examples

Common configurations, variations, and states.

### ChatComposerInput — Controlled

Controlled chat input with live value display. Use controlled mode when you need to read or transform the input value outside the composer.

```vue
<script setup lang="ts">
import { ChatComposer, ChatComposerInput } from '@astryx-vue/core/Chat'
import { VStack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
import { ref } from 'vue'

const value = ref('')

function onSubmit(): void {
  value.value = ''
}
</script>

<template>
  <VStack :gap="3">
    <ChatComposer v-model="value" @submit="onSubmit">
      <template #input>
        <ChatComposerInput v-model="value" placeholder="Type a message..." />
      </template>
    </ChatComposer>
    <Text type="supporting" color="secondary">
      Value: {{ JSON.stringify(value) }}
    </Text>
  </VStack>
</template>
```

### ChatComposerInput — Disabled

Composer in a disabled state. Use when the input should be visible but not interactive, such as during streaming or when a prerequisite is unmet.

```vue
<script setup lang="ts">
import { ChatComposer, ChatComposerInput } from '@astryx-vue/core/Chat'
</script>

<template>
  <ChatComposer is-disabled>
    <template #input>
      <ChatComposerInput is-disabled placeholder="Input is disabled" />
    </template>
  </ChatComposer>
</template>
```

### ChatComposerInput — Mentions

Chat input with an @ trigger that opens a typeahead menu for mentioning users. Selected names appear as inline tokens.

```vue
<script setup lang="ts">
import type { ChatComposerTrigger } from '@astryx-vue/core/Chat'
import { createStaticSource } from '@astryx-vue/core'
import { ChatComposer, ChatComposerInput } from '@astryx-vue/core/Chat'
import { VStack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
import { TypeaheadItem } from '@astryx-vue/core/Typeahead'
import { h, ref } from 'vue'

interface UserMeta {
  role: string
}

const users = [
  { id: 'cindy', label: 'Cindy Zhang', auxiliaryData: { role: 'Design Systems' } },
  { id: 'alex', label: 'Alex Johnson', auxiliaryData: { role: 'Frontend' } },
  { id: 'sam', label: 'Sam Rivera', auxiliaryData: { role: 'Backend' } },
  { id: 'jordan', label: 'Jordan Lee', auxiliaryData: { role: 'Product' } },
]

const userSource = createStaticSource(users)

const value = ref('')

const mentionTrigger: ChatComposerTrigger = {
  character: '@',
  searchSource: userSource,
  renderItem: item => h(TypeaheadItem, {
    item,
    description: (item.auxiliaryData as UserMeta | undefined)?.role,
  }),
  onSelect: item => ({
    value: `@${item.id}`,
    label: item.label,
    variant: 'blue',
  }),
}
</script>

<template>
  <VStack :gap="3">
    <ChatComposer @submit="value = ''">
      <template #input>
        <ChatComposerInput
          v-model="value"
          :triggers="[mentionTrigger]"
          placeholder="Type @ to mention someone..."
        />
      </template>
    </ChatComposer>
    <Text type="supporting" color="secondary">
      Value: {{ JSON.stringify(value) }}
    </Text>
  </VStack>
</template>
```

### ChatComposerInput — Multiple Triggers

Chat input with both @ mentions and / commands. Each trigger type renders tokens in a distinct color so users can tell them apart at a glance.

```vue
<script setup lang="ts">
import type { ChatComposerTrigger } from '@astryx-vue/core/Chat'
import { createStaticSource } from '@astryx-vue/core'
import { ChatComposer, ChatComposerInput } from '@astryx-vue/core/Chat'
import { VStack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
import { ref } from 'vue'

const users = [
  { id: 'cindy', label: 'Cindy Zhang' },
  { id: 'alex', label: 'Alex Johnson' },
  { id: 'sam', label: 'Sam Rivera' },
  { id: 'jordan', label: 'Jordan Lee' },
]

const commands = [
  { id: 'summarize', label: 'summarize' },
  { id: 'translate', label: 'translate' },
  { id: 'search', label: 'search' },
  { id: 'code', label: 'code' },
]

const userSource = createStaticSource(users)
const commandSource = createStaticSource(commands)

const value = ref('')

const mentionTrigger: ChatComposerTrigger = {
  character: '@',
  searchSource: userSource,
  onSelect: item => ({
    value: `@${item.id}`,
    label: item.label,
    variant: 'blue',
  }),
}

const commandTrigger: ChatComposerTrigger = {
  character: '/',
  searchSource: commandSource,
  onSelect: item => ({
    value: `/${item.label}`,
    label: `/${item.label}`,
    variant: 'yellow',
  }),
}
</script>

<template>
  <VStack :gap="3">
    <Text type="supporting" color="secondary">
      Type @ for mentions (blue) or / for commands (yellow)
    </Text>
    <ChatComposer @submit="value = ''">
      <template #input>
        <ChatComposerInput
          v-model="value"
          :triggers="[mentionTrigger, commandTrigger]"
          placeholder="Type @ or / ..."
        />
      </template>
    </ChatComposer>
    <Text type="supporting" color="secondary">
      Value: {{ JSON.stringify(value) }}
    </Text>
  </VStack>
</template>
```

### ChatComposerInput — Slash Commands

Chat input with a / trigger for command selection. Use for AI assistants or bots that support structured commands.

```vue
<script setup lang="ts">
import type { ChatComposerTrigger } from '@astryx-vue/core/Chat'
import { createStaticSource } from '@astryx-vue/core'
import { ChatComposer, ChatComposerInput } from '@astryx-vue/core/Chat'
import { TypeaheadItem } from '@astryx-vue/core/Typeahead'
import { h } from 'vue'

interface CommandMeta {
  description: string
}

const commands = [
  { id: 'summarize', label: 'summarize', auxiliaryData: { description: 'Summarize the conversation' } },
  { id: 'translate', label: 'translate', auxiliaryData: { description: 'Translate text to another language' } },
  { id: 'search', label: 'search', auxiliaryData: { description: 'Search the web or documents' } },
  { id: 'code', label: 'code', auxiliaryData: { description: 'Generate or explain code' } },
  { id: 'help', label: 'help', auxiliaryData: { description: 'Show available commands' } },
]

const commandSource = createStaticSource(commands)

const commandTrigger: ChatComposerTrigger = {
  character: '/',
  searchSource: commandSource,
  renderItem: item => h(TypeaheadItem, {
    item,
    description: (item.auxiliaryData as CommandMeta | undefined)?.description,
  }),
  onSelect: item => ({
    value: `/${item.label}`,
    label: `/${item.label}`,
    variant: 'yellow',
  }),
}
</script>

<template>
  <ChatComposer>
    <template #input>
      <ChatComposerInput
        :triggers="[commandTrigger]"
        placeholder="Type / for commands..."
      />
    </template>
  </ChatComposer>
</template>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `v-model` | `string` | — | The draft. Inside a `ChatComposer`, the shell's draft is used when this is omitted. |
| `placeholder` | `string` | Composer's, then localized “Type a message…” | Text shown only while the serialized draft is empty. |
| `maxRows` | `number` | `8` | Lines before the editor scrolls. |
| `triggers` | `ChatComposerTrigger[]` | — | Characters that open a suggestion list. Each trigger carries the character, a search source, and what to insert. |
| `debounceMs` | `number` | `150` | Delay before an async trigger search. Synchronous sources are not delayed. |
| `hasHistory` | `boolean` | `true` | Arrow keys at the ends of the draft recall earlier submissions. |
| `label` | `string` | Localized “Message input” | Accessible name of the editor. |
| `isDisabled` | `boolean` | Composer's value | Stops editing. Falls back to the surrounding composer. |
| `pasteAsToken` | `{ onPaste } \| false` | Built-in threshold | Long pastes become chips. Pass a `useChatPasteAsToken` result to change the threshold, or `false` to insert the text as-is. |
| `pasteAction` | `(event, text) => boolean \| void` | — | First chance at a plain-text paste. Return `true` to keep the input from inserting the text itself. |

## Events

| Event | Payload | Description |
| --- | --- | --- |
| `update:modelValue` | `string` | The serialized draft, including chip values. This is what `v-model` binds. |
| `change` | `string` | The draft changed, by typing, pasting, or a programmatic insert. |
| `submit` | `string` | Enter without Shift. Shift+Enter inserts a newline. |
| `files` | `File[]` | Files from a paste or a drop. A file drop does not navigate away. |
| `keydown` | `KeyboardEvent` | Keydown after an open trigger menu has declined the event. Call `preventDefault()` to skip Enter-to-send and history recall. |

## Exposed

| Name | Type | Description |
| --- | --- | --- |
| `insertToken` | `(token) => string \| undefined` | Inserts a chip at the caret and returns its id. |
| `expandToken` | `(id: string) => void` | Replaces a chip with the text it serializes to. |
| `insertText` | `(text: string) => void` | Inserts plain text at the caret. |
| `focus` | `() => void` | Focuses the editor and keeps a caret the user already had inside it. |
| `getValue` | `() => string` | The serialized draft, including chip values. |
