@appsbd/vue3-appsbd-ui 1.0.14 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ai/ai_ref_AbChatBox.md +148 -0
- package/.ai/ai_ref_AbChatInput.md +102 -22
- package/.ai/ai_ref_AbDateTimePicker.md +1 -1
- package/.ai/ai_ref_AbScrollbar.md +27 -0
- package/.ai/ai_ref_AbVoiceButton.md +112 -0
- package/.ai/ai_ref_global_config.md +39 -4
- package/.ai/ai_ref_migration_guide.md +91 -0
- package/AI_REFERENCE.md +4 -0
- package/CHANGELOG.md +32 -0
- package/dist/style.css +1 -1
- package/dist/vue3-appsbd-ui.cjs.js +84 -79
- package/dist/vue3-appsbd-ui.es.js +9921 -9377
- package/package.json +2 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# AbChatBox — AI Reference
|
|
2
|
+
|
|
3
|
+
A full chat conversation container built on top of `AbChatInput` — header, scrollable message list (with
|
|
4
|
+
empty/typing states), and the composer. Every part is slot-overridable, so pick this over building a chat UI by
|
|
5
|
+
hand whenever you need a message list around `AbChatInput`, not just the composer itself.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Not globally registered — import it explicitly:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import { AbChatBox } from 'vue3-appsbd-ui';
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
No extra peer deps beyond `AbChatInput`'s own (`@lucide/vue`).
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
`v-model:messages` holds the conversation array, `v-model` holds the composer's text. Submitting auto-appends the
|
|
20
|
+
user's message and fires `send` — handle that to call your API and push the reply back:
|
|
21
|
+
|
|
22
|
+
```vue
|
|
23
|
+
<template>
|
|
24
|
+
<AbChatBox
|
|
25
|
+
v-model="message"
|
|
26
|
+
v-model:messages="messages"
|
|
27
|
+
title="Support Bot"
|
|
28
|
+
subtitle="Usually replies in a few seconds"
|
|
29
|
+
:loading="loading"
|
|
30
|
+
@send="onSend"
|
|
31
|
+
/>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup>
|
|
35
|
+
import { ref } from 'vue';
|
|
36
|
+
import { AbChatBox } from 'vue3-appsbd-ui';
|
|
37
|
+
|
|
38
|
+
const message = ref('');
|
|
39
|
+
const messages = ref([
|
|
40
|
+
{ id: 1, role: 'assistant', name: 'Assistant', text: 'Hi! How can I help you today?' },
|
|
41
|
+
]);
|
|
42
|
+
const loading = ref(false);
|
|
43
|
+
|
|
44
|
+
function onSend({ message, files }) {
|
|
45
|
+
loading.value = true;
|
|
46
|
+
callMyApi(message).then((reply) => {
|
|
47
|
+
loading.value = false;
|
|
48
|
+
messages.value.push({ id: Date.now(), role: 'assistant', text: reply });
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Any `AbChatInput` prop/event/slot works directly on `AbChatBox` — it's forwarded straight to the inner composer:
|
|
55
|
+
|
|
56
|
+
```vue
|
|
57
|
+
<template>
|
|
58
|
+
<AbChatBox
|
|
59
|
+
v-model="message"
|
|
60
|
+
v-model:messages="messages"
|
|
61
|
+
v-model:files="files"
|
|
62
|
+
:max-file-size="2"
|
|
63
|
+
:allowed-extensions="['jpg', 'jpeg', 'png', 'pdf']"
|
|
64
|
+
placeholder="Ask anything"
|
|
65
|
+
>
|
|
66
|
+
<template #single-input="{ addToAttach }">
|
|
67
|
+
<AbChatInputMenuItem :icon="Paperclip" name="Upload file" :action="() => addToAttach()" />
|
|
68
|
+
</template>
|
|
69
|
+
</AbChatBox>
|
|
70
|
+
</template>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Props
|
|
74
|
+
|
|
75
|
+
| Name | Type | Default | Description |
|
|
76
|
+
| --- | --- | --- | --- |
|
|
77
|
+
| `messages` | `Array` | `[]` | v-model:messages. See "Message shape" below. |
|
|
78
|
+
| `modelValue` | `String` | `""` | Two-way-bound composer text — forwarded to the inner `AbChatInput`. |
|
|
79
|
+
| `title` | `String` | `""` | Default header title (translated). |
|
|
80
|
+
| `subtitle` | `String` | `""` | Default header subtitle (translated). |
|
|
81
|
+
| `avatar` | `String` | `""` | Default header avatar image URL. |
|
|
82
|
+
| `loading` | `Boolean` | `false` | Shows the typing indicator at the bottom of the message list. |
|
|
83
|
+
| `emptyText` | `String` | `"Start a conversation"` | Text shown by the default empty state (translated). |
|
|
84
|
+
| `emptyIcon` | `Object \| Function` | `MessageCircle` | Icon shown above `emptyText`. Pass `null` to hide it. |
|
|
85
|
+
| `autoScroll` | `Boolean` | `true` | Auto-scrolls the message list to the bottom on new messages / `loading` changes. |
|
|
86
|
+
| `containerClass` | `String` | `undefined` | Extra class on the root element. |
|
|
87
|
+
|
|
88
|
+
Any other prop not listed above (`placeholder`, `disabled`, `readonly`, `minHeight`, `maxHeight`, `submitOnEnter`,
|
|
89
|
+
`allowNewLine`, `autoFocus`, `autoPreview`, `maxFileSize`, `allowedExtensions`, `borderRadius`,
|
|
90
|
+
`filePreviewWidth`, `filePreviewHeight`, `showFileName`, `showVoiceButton`, ...) falls through untouched to the
|
|
91
|
+
inner `AbChatInput` — see `.ai/ai_ref_AbChatInput.md` for the full list.
|
|
92
|
+
|
|
93
|
+
## Events
|
|
94
|
+
|
|
95
|
+
| Event | Payload | Description |
|
|
96
|
+
| --- | --- | --- |
|
|
97
|
+
| `update:messages` | `Message[]` | v-model:messages sync — fires after the user's message is auto-appended, or after `addMessage()`. |
|
|
98
|
+
| `update:modelValue` | `string` | v-model sync for the composer text. |
|
|
99
|
+
| `send` | `{ message: string, files: FileEntry[] }` | Fired right after the user's message is auto-appended to `messages` — hook here to call an API and push the reply. |
|
|
100
|
+
|
|
101
|
+
Any other `AbChatInput` event (`focus`, `blur`, `keydown`, `file-click`, `file-remove`, `file-reject`,
|
|
102
|
+
`voice-start`, `voice-stop`, `voice-cancel`, `voice-error`, `update:files`) is forwarded straight from the inner
|
|
103
|
+
composer — listen for it on `AbChatBox` exactly as you would on `AbChatInput`. Note: `submit` itself is **not**
|
|
104
|
+
forwarded — `AbChatBox` consumes it internally to auto-append + emit `send`.
|
|
105
|
+
|
|
106
|
+
## Slots
|
|
107
|
+
|
|
108
|
+
| Name | Description |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| `header` | Overrides the entire header. Scoped: `{ title, subtitle, avatar }`. |
|
|
111
|
+
| `header-actions` | Adds content to the right side of the default header, without replacing it. |
|
|
112
|
+
| `empty` | Overrides the empty state (shown when `messages` is empty). |
|
|
113
|
+
| `message` | Overrides one message's whole bubble. Scoped: `{ message, index }`. |
|
|
114
|
+
| `message-avatar` | Overrides just the avatar inside the default bubble. Scoped: `{ message }`. |
|
|
115
|
+
| `typing` | Overrides the typing indicator (shown when `loading` is `true`). |
|
|
116
|
+
| `left` / `right` / `send-button` / `menu-trigger` / `single-input` / `multiple-input` | `AbChatInput`'s own slots — forwarded straight through to the inner composer with the same scoped props (`addToAttach`, `addFiles`, `removeFile`, `clearFiles`, `updateText`, `focus`, `submit`). See `.ai/ai_ref_AbChatInput.md`. |
|
|
117
|
+
|
|
118
|
+
## v-model
|
|
119
|
+
|
|
120
|
+
Yes — two v-models: `modelValue` + `update:modelValue` (string, the composer text) and `messages` +
|
|
121
|
+
`update:messages` (`Message[]`, the conversation). A third, `files` + `update:files`, is available by passthrough
|
|
122
|
+
if you use `v-model:files` (forwarded to the inner `AbChatInput`).
|
|
123
|
+
|
|
124
|
+
### Message shape
|
|
125
|
+
|
|
126
|
+
Any plain object works — only `role` is read internally (to align the bubble left/right); everything else is
|
|
127
|
+
just read by the default bubble template (or your own, via `#message`).
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
{
|
|
131
|
+
id, // stable key; auto-generated if omitted
|
|
132
|
+
role, // "user" | "assistant" | "system" — "user" aligns right, others left
|
|
133
|
+
text, // message body
|
|
134
|
+
name, // optional sender name, shown above the text
|
|
135
|
+
avatar, // optional avatar image URL
|
|
136
|
+
time, // optional string, shown below the text
|
|
137
|
+
files, // optional array — rendered as a small attachment list ({ name } or a plain string)
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Exposed methods
|
|
142
|
+
|
|
143
|
+
| Method | Description |
|
|
144
|
+
| --- | --- |
|
|
145
|
+
| `addMessage(message)` | Appends a message (fills in a default `id`/`role` if omitted), fires `update:messages`, and auto-scrolls. Returns the stored message. |
|
|
146
|
+
| `scrollToBottom()` | Scrolls the message list to the bottom. |
|
|
147
|
+
| `focus()` | Focuses the composer's textarea. |
|
|
148
|
+
| `chatInput` | Raw template ref to the inner `AbChatInput` — call any of its own exposed methods directly (`submit()`, `addToAttach()`, `startRecording()`, etc). See `.ai/ai_ref_AbChatInput.md`. |
|
|
@@ -14,21 +14,17 @@ No extra peer deps beyond the library's own (`@lucide/vue` for the default "+" i
|
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
|
+
A send button (arrow-up icon, `AbButton` with `color="primary"`) renders on the right by default and calls the
|
|
18
|
+
same submit logic as pressing Enter — no manual wiring needed:
|
|
19
|
+
|
|
17
20
|
```vue
|
|
18
21
|
<template>
|
|
19
|
-
<AbChatInput v-model="message" placeholder="Ask anything" @submit="onSubmit"
|
|
20
|
-
<template #right>
|
|
21
|
-
<button type="button" class="btn btn-primary btn-sm rounded-circle">
|
|
22
|
-
<ArrowUp :size="16" />
|
|
23
|
-
</button>
|
|
24
|
-
</template>
|
|
25
|
-
</AbChatInput>
|
|
22
|
+
<AbChatInput v-model="message" placeholder="Ask anything" @submit="onSubmit" />
|
|
26
23
|
</template>
|
|
27
24
|
|
|
28
25
|
<script setup>
|
|
29
26
|
import { ref } from 'vue';
|
|
30
27
|
import { AbChatInput } from 'vue3-appsbd-ui';
|
|
31
|
-
import { ArrowUp } from '@lucide/vue';
|
|
32
28
|
|
|
33
29
|
const message = ref('');
|
|
34
30
|
function onSubmit({ message, files }) {
|
|
@@ -37,6 +33,21 @@ function onSubmit({ message, files }) {
|
|
|
37
33
|
</script>
|
|
38
34
|
```
|
|
39
35
|
|
|
36
|
+
Override the default send button via the scoped `#send-button` slot (e.g. to add a label, swap the icon, or
|
|
37
|
+
change its color) — call the scoped `submit()` to fire the same `submit` event the default button would:
|
|
38
|
+
|
|
39
|
+
```vue
|
|
40
|
+
<template>
|
|
41
|
+
<AbChatInput v-model="message" @submit="onSubmit">
|
|
42
|
+
<template #send-button="{ submit }">
|
|
43
|
+
<AbButton color="success" is-icon-btn is-rounded size="sm" @click="submit">
|
|
44
|
+
<Send :size="18" />
|
|
45
|
+
</AbButton>
|
|
46
|
+
</template>
|
|
47
|
+
</AbChatInput>
|
|
48
|
+
</template>
|
|
49
|
+
```
|
|
50
|
+
|
|
40
51
|
Add/more dropdown (0 items = no trigger, 1 item = direct action with no popover, 2+ items = a real dropdown):
|
|
41
52
|
|
|
42
53
|
```vue
|
|
@@ -76,24 +87,88 @@ a native file picker and routes the selection through `addFiles`) among other me
|
|
|
76
87
|
## Voice input
|
|
77
88
|
|
|
78
89
|
Uses the browser's native Web Speech API (`SpeechRecognition`) — no extra dependency, no API key. A built-in mic
|
|
79
|
-
button renders in the `right` area (
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
button renders in the `right` area (before the `right` slot and the send button) whenever `showVoiceButton` is
|
|
91
|
+
`true` (default).
|
|
92
|
+
|
|
93
|
+
The composer layout never changes while recording, on every platform — no full-width recording bar swap: the
|
|
94
|
+
textarea stays visible **and stays fully editable** (typing/deleting mid-recording works normally) and fills with
|
|
95
|
+
the transcribed text live as it's recognized. The mic button shows a Google Assistant-style listening cue: its
|
|
96
|
+
circle fills with the theme's primary color (icon turns white to read against it), with a few rings pulsing
|
|
97
|
+
outward from behind it on a loop (`.apbd-chat-input__voice-ring`, staggered so they overlap continuously). Clicking
|
|
98
|
+
it (or calling `stopRecording()`) stops and keeps the text — there's no dedicated cancel button in this UI
|
|
99
|
+
(`cancelRecording()` is still available programmatically via a template ref), and the default send button (or
|
|
100
|
+
`submit()`/Enter generally) stops recording first when called mid-listening, then fires `submit` immediately.
|
|
82
101
|
|
|
83
|
-
|
|
84
|
-
-
|
|
85
|
-
- **↑ submit** — stops recording, keeps the text, then fires `submit` immediately.
|
|
102
|
+
On every platform, the textarea is refocused automatically the moment listening ends (stop, cancel, or a
|
|
103
|
+
voice-triggered submit) — the cursor lands right back in it so the user can keep typing without an extra tap.
|
|
86
104
|
|
|
87
|
-
Recognized speech is appended to the existing `modelValue` (not replaced),
|
|
88
|
-
|
|
105
|
+
Recognized speech is appended to the existing `modelValue` (not replaced), converting instantly as the user speaks
|
|
106
|
+
on every platform — interim results drive `modelValue` live so the text is already there before the user taps the
|
|
107
|
+
mic to stop, not just once they do (see below).
|
|
89
108
|
|
|
90
109
|
A small language-code badge (e.g. `EN`, `BN`) sits on the mic button's corner. Clicking it opens an `AbPopover`
|
|
91
110
|
with a searchable list of ~130 BCP-47 locales; picking one sets that recognition session's `SpeechRecognition.lang`
|
|
92
111
|
and persists the choice to `localStorage` (key `apbd-chat-input-voice-lang`) so it's remembered next visit. This
|
|
93
112
|
never touches the browser's own language/locale settings — only this component's own recognizer instance.
|
|
94
113
|
|
|
95
|
-
|
|
96
|
-
|
|
114
|
+
### Voice punctuation
|
|
115
|
+
|
|
116
|
+
Two independent behaviors, each its own boolean prop, **both default `true`**:
|
|
117
|
+
|
|
118
|
+
- **`voicePunctuationCommands`** — saying a punctuation word inserts the actual mark, same idea as Google/Apple
|
|
119
|
+
dictation: English `"period"`/`"full stop"` → `.`, `"comma"` → `,`, `"question mark"` → `?`; Bengali
|
|
120
|
+
`"পূর্ণচ্ছেদ"` → `।`, `"কমা"` → `,`, `"প্রশ্নবোধক"` → `?`. Matched against the recognized transcript text itself
|
|
121
|
+
(not `voiceLang`), so it works regardless of which recognition language is selected. Matching uses
|
|
122
|
+
script-agnostic lookaround (not `\b`, which only understands ASCII letters — a no-op for Bengali) so a command
|
|
123
|
+
word only triggers standalone, never as a substring of a longer word (e.g. saying "দাঁড়িপাল্লা" doesn't trigger
|
|
124
|
+
on the "দাড়ি" inside it). `"দাড়ি"` itself (an earlier alternate trigger, same meaning as `"পূর্ণচ্ছেদ"`) was
|
|
125
|
+
dropped entirely rather than just boundary-guarded — it's also ordinary vocabulary (beard), so a sentence
|
|
126
|
+
actually about a beard would still lose that word every time. `"কমা"` keeps a similar, smaller risk (it's also
|
|
127
|
+
a common verb, "to decrease") — left as-is, since removing every word with *any* plausible double meaning
|
|
128
|
+
would leave nothing left to trigger on.
|
|
129
|
+
- **`voiceAutoPunctuate`** — when a new utterance is about to be appended onto existing text, a sentence-ending
|
|
130
|
+
mark is inserted first if the existing text doesn't already end with one — `.` normally, `।` when `voiceLang`
|
|
131
|
+
starts with `bn`. Applies between pauses (each per-utterance restart, see below), not mid-utterance.
|
|
132
|
+
|
|
133
|
+
Set either to `false` to disable it independently; both keep working with the default Web Speech transcript as-is
|
|
134
|
+
when off.
|
|
135
|
+
|
|
136
|
+
Both also auto-capitalize the sentence start they create: `voiceAutoPunctuate` capitalizes the first letter of a
|
|
137
|
+
new utterance right after it inserts a boundary mark (and always capitalizes the very first letter of the whole
|
|
138
|
+
message, regardless of this prop); `voicePunctuationCommands` capitalizes the letter right after any mark it
|
|
139
|
+
inserts mid-utterance (`"hello period how are you"` → `"Hello. How are you"`). Same script-agnostic approach as the
|
|
140
|
+
punctuation matching above — plain `toUpperCase()` on the matched letter, which is a no-op for scripts without a
|
|
141
|
+
case concept (Bengali, Arabic, CJK, …), so it needs no per-language list.
|
|
142
|
+
|
|
143
|
+
Not supported in Firefox. On iOS, only Safari itself gets it — Chrome/Firefox/Edge/Opera on iOS run on the same
|
|
144
|
+
underlying WebKit engine and expose the same `webkitSpeechRecognition` constructor, but iOS never grants the mic
|
|
145
|
+
permission for it outside of Safari's own app (no permission prompt ever appears; `.start()` would otherwise fail
|
|
146
|
+
with a confusing, prompt-less "permission denied"), so these are detected via user-agent and treated as unsupported
|
|
147
|
+
too. The mic button still renders in all of these cases but is `disabled`, with a tooltip explaining why. Where it
|
|
148
|
+
is supported, requires the browser's mic permission prompt to be accepted.
|
|
149
|
+
|
|
150
|
+
Some browsers/builds hold back `isFinal` results until `.stop()` is called instead of firing them per pause segment
|
|
151
|
+
— waiting for that would mean the transcript only shows up once the user taps stop, not while they're actually
|
|
152
|
+
speaking (and on some builds it never reliably fires at all). `interimResults` is on by default to sidestep that,
|
|
153
|
+
converting speech to text instantly instead of only once recording stops — **except on Safari** (desktop and iOS
|
|
154
|
+
both), where it's kept `false`: Safari's engine has a bug where enabling it can fail to (re)start the recognizer
|
|
155
|
+
entirely, surfacing as a generic "Microphone permission denied" error indistinguishable from an actually-denied
|
|
156
|
+
permission. Safari still converts per finished phrase (not mid-utterance), just not keystroke-by-keystroke live.
|
|
157
|
+
|
|
158
|
+
`continuous`+`interimResults` together is also unreliable across browsers/builds in a way delta-tracking
|
|
159
|
+
`e.results` (via `resultIndex` or position) can't fully work around — it's been observed restating/duplicating
|
|
160
|
+
earlier words in a later result with no restart involved at all. So `recognition.continuous` is always `false` —
|
|
161
|
+
each internal session covers exactly one utterance, and a transparent onend-restart (also covers Safari's own
|
|
162
|
+
quirk of ending a `continuous: true` session early) picks the next utterance back up automatically, so it still
|
|
163
|
+
reads as continuous listening. Within one utterance's `onresult`, only the latest `e.results` entry is read — no
|
|
164
|
+
cross-event bookkeeping needed, since there's nothing earlier in that same array to conflict with.
|
|
165
|
+
|
|
166
|
+
Because the textarea stays editable during recording, `onresult` can't just privately accumulate a running
|
|
167
|
+
transcript either — that would silently clobber whatever the user just typed or deleted. Instead it reads the
|
|
168
|
+
current `modelValue`, strips back off the exact suffix it itself inserted last time (if that suffix is still there
|
|
169
|
+
untouched), and rebuilds from that stable base with the freshly recognized text appended; if the suffix isn't
|
|
170
|
+
there anymore, whatever's currently in the field — including the user's edit — becomes the new base instead of
|
|
171
|
+
being overwritten.
|
|
97
172
|
|
|
98
173
|
## Props
|
|
99
174
|
|
|
@@ -119,6 +194,8 @@ why. Requires the browser's mic permission prompt to be accepted.
|
|
|
119
194
|
| `filePreviewHeight` | `Number` | `24` | Height (px) of each attached file's thumbnail/icon in the chip. |
|
|
120
195
|
| `showFileName` | `Boolean` | `true` | Show/hide the filename text next to the chip thumbnail. |
|
|
121
196
|
| `showVoiceButton` | `Boolean` | `true` | Show/hide the built-in mic button (see "Voice input" below). Set `false` to build custom voice UI via slots instead. |
|
|
197
|
+
| `voicePunctuationCommands` | `Boolean` | `true` | Say a punctuation word (e.g. "period", "পূর্ণচ্ছেদ") to insert the actual mark — see "Voice punctuation" above. |
|
|
198
|
+
| `voiceAutoPunctuate` | `Boolean` | `true` | Auto-insert a sentence-ending mark between voice utterances when one isn't already there — see "Voice punctuation" above. |
|
|
122
199
|
|
|
123
200
|
## Events
|
|
124
201
|
|
|
@@ -133,8 +210,8 @@ why. Requires the browser's mic permission prompt to be accepted.
|
|
|
133
210
|
| `file-remove` | `FileEntry` | A file chip's remove button was clicked. |
|
|
134
211
|
| `file-reject` | `{ file: File, reason: string }` | A file failed `maxFileSize`/`allowedExtensions` validation. A `useToast` error is also shown. |
|
|
135
212
|
| `voice-start` | — | Voice recording started (mic button clicked or `startRecording()` called). |
|
|
136
|
-
| `voice-stop` | — | Recording stopped normally (
|
|
137
|
-
| `voice-cancel` | — | Recording was cancelled
|
|
213
|
+
| `voice-stop` | — | Recording stopped normally (mic button tapped again, `stopRecording()`, or the browser ending it on its own) — transcribed text is kept in `modelValue`. |
|
|
214
|
+
| `voice-cancel` | — | Recording was cancelled via `cancelRecording()` (no dedicated UI button — call it programmatically via a template ref) — `modelValue` is reverted to what it was before recording started. |
|
|
138
215
|
| `voice-error` | `Error \| SpeechRecognitionErrorEvent` | Mic permission was denied, or the recognition engine errored. A `useToast` error is also shown (except benign `no-speech`/`aborted` errors). |
|
|
139
216
|
|
|
140
217
|
## Slots
|
|
@@ -142,12 +219,13 @@ why. Requires the browser's mic permission prompt to be accepted.
|
|
|
142
219
|
| Name | Description |
|
|
143
220
|
| --- | --- |
|
|
144
221
|
| `left` | Left action area (e.g. an attachment button). Scoped — see below. |
|
|
145
|
-
| `right` | Right action area (e.g.
|
|
222
|
+
| `right` | Right action area, rendered before the send button (e.g. extra icon buttons). Scoped — see below. |
|
|
223
|
+
| `send-button` | Overrides the default send button (an `AbButton` with an arrow-up icon, `color="primary"`, calling `submit()`). Always rendered — there's no prop to hide it; override this slot with empty content if you don't want one. Scoped — see below. |
|
|
146
224
|
| `menu-trigger` | Overrides the default "+" icon on the add/more trigger. Only rendered when the trigger itself renders (see below). Scoped — see below. |
|
|
147
225
|
| `single-input` | One static `AbChatInputMenuItem` for the add/more dropdown. Scoped — see below. |
|
|
148
226
|
| `multiple-input` | A `v-for`'d set of `AbChatInputMenuItem`s for the add/more dropdown. Scoped — see below. |
|
|
149
227
|
|
|
150
|
-
**All
|
|
228
|
+
**All six slots are scoped**, receiving the same props so slot content can drive the composer directly:
|
|
151
229
|
|
|
152
230
|
| Scoped prop | Description |
|
|
153
231
|
| --- | --- |
|
|
@@ -157,6 +235,7 @@ why. Requires the browser's mic permission prompt to be accepted.
|
|
|
157
235
|
| `clearFiles()` | Same as the exposed method. |
|
|
158
236
|
| `updateText(text)` | Replaces the message text (fires `update:modelValue`). |
|
|
159
237
|
| `focus()` | Focuses the textarea. |
|
|
238
|
+
| `submit()` | Same as the exposed method — fires `submit` with `{ message, files }`. No-op when `disabled`/`readonly`. |
|
|
160
239
|
|
|
161
240
|
A single-item `#single-input`/`#multiple-input` action can use these directly — `onMenuTriggerClick`'s no-dropdown
|
|
162
241
|
fast path reads the item's `action` prop with these same bindings applied, so `:action="addToAttach"` works there too.
|
|
@@ -202,6 +281,7 @@ any slot — e.g. a toolbar button elsewhere on the page.
|
|
|
202
281
|
| `addToAttach(options?)` | Opens a native file picker and routes the selection through `addFiles`. `options`: `{ accept, multiple }`. |
|
|
203
282
|
| `updateText(text)` | Replaces the message text (fires `update:modelValue`). |
|
|
204
283
|
| `focus()` | Focuses the textarea. |
|
|
284
|
+
| `submit()` | Fires `submit` with `{ message, files }` (same as clicking the default send button). No-op when `disabled`/`readonly`. |
|
|
205
285
|
| `startRecording()` | Starts voice input (same as clicking the mic button). No-op if unsupported/disabled/readonly/already recording. |
|
|
206
286
|
| `stopRecording()` | Stops recording, keeping the transcribed text (same as the ■ button). |
|
|
207
287
|
| `cancelRecording()` | Stops recording and discards the transcribed text (same as the ✕ button). |
|
|
@@ -46,7 +46,7 @@ const datetime = ref(null);
|
|
|
46
46
|
| `monthNames` | `Array` | `undefined` | Custom full month names. |
|
|
47
47
|
| `monthNamesShort` | `Array` | `undefined` | Custom short month names. |
|
|
48
48
|
| `dayNames` | `Array` | `undefined` | Custom day names. |
|
|
49
|
-
| `
|
|
49
|
+
| `digits` | `Array` | `undefined` | 10-character digits array (0-9) for locale digit transliteration (e.g. Bangla digits), overriding `globalConfig.digits`. |
|
|
50
50
|
| `rangeSeparator` | `String` | `undefined` | Display separator between range start/end. |
|
|
51
51
|
| `mode` | `String` | `"date"` | `date`, `time`, or `datetime`. |
|
|
52
52
|
| `is24Hour` | `Boolean` | `undefined` | 24-hour clock. |
|
|
@@ -36,3 +36,30 @@ Each event payload is an object with `scrollTop`, `scrollHeight`, `scrollRatio`,
|
|
|
36
36
|
| Name | Description |
|
|
37
37
|
| --- | --- |
|
|
38
38
|
| `default` | Scrollable content. |
|
|
39
|
+
|
|
40
|
+
## Exposed methods
|
|
41
|
+
|
|
42
|
+
Available via a template ref, for programmatic scroll control (e.g. scrolling a chat/message list to the bottom on new content):
|
|
43
|
+
|
|
44
|
+
| Method | Description |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `scrollToBottom(behavior?)` | Scrolls to the bottom. `behavior`: `"auto"` (default) or `"smooth"`. |
|
|
47
|
+
| `scrollToTop(behavior?)` | Scrolls to the top. `behavior`: `"auto"` (default) or `"smooth"`. |
|
|
48
|
+
| `scrollTo(options)` | Same as the native `Element.scrollTo(options)` (e.g. `{ top, left, behavior }`). |
|
|
49
|
+
| `scrollArea` | Raw ref to the internal scrollable element, for anything not covered above. |
|
|
50
|
+
|
|
51
|
+
```vue
|
|
52
|
+
<template>
|
|
53
|
+
<AbScrollbar ref="listRef" style="height: 300px">
|
|
54
|
+
<div v-for="m in messages" :key="m.id">{{ m.text }}</div>
|
|
55
|
+
</AbScrollbar>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<script setup>
|
|
59
|
+
import { ref, nextTick } from 'vue';
|
|
60
|
+
const listRef = ref(null);
|
|
61
|
+
function onNewMessage() {
|
|
62
|
+
nextTick(() => listRef.value.scrollToBottom());
|
|
63
|
+
}
|
|
64
|
+
</script>
|
|
65
|
+
```
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# AbVoiceButton — AI Reference
|
|
2
|
+
|
|
3
|
+
A standalone voice-to-text mic button — the same recognition engine and behavior as `AbChatInput`'s built-in mic,
|
|
4
|
+
with no composer attached. Use it to bolt voice input onto any existing input, form field, search box, or custom
|
|
5
|
+
UI via `v-model`; pick `AbChatInput` instead when you need the whole chat composer (textarea, file attachments,
|
|
6
|
+
add/more dropdown) with voice input built in.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Not globally registered — import it explicitly:
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
import { AbVoiceButton } from 'vue3-appsbd-ui';
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
No extra peer deps beyond the library's own (`@lucide/vue` for the mic icon). Uses the browser's native Web
|
|
17
|
+
Speech API (`SpeechRecognition`) — no dependency, no API key.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```vue
|
|
22
|
+
<template>
|
|
23
|
+
<AbVoiceButton v-model="message" />
|
|
24
|
+
<p>{{ message }}</p>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup>
|
|
28
|
+
import { ref } from 'vue';
|
|
29
|
+
import { AbVoiceButton } from 'vue3-appsbd-ui';
|
|
30
|
+
|
|
31
|
+
const message = ref('');
|
|
32
|
+
</script>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Pin the recognition language explicitly (there's no built-in language picker here, unlike `AbChatInput`'s mic
|
|
36
|
+
button — pass whichever locale your app already knows the user wants):
|
|
37
|
+
|
|
38
|
+
```vue
|
|
39
|
+
<AbVoiceButton v-model="message" lang="bn-BD" />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Behavior
|
|
43
|
+
|
|
44
|
+
Tapping the button (or calling `startRecording()`) starts listening; the mic icon stays the mic icon, but its
|
|
45
|
+
circle fills with the theme's primary color (`--#{prefix}theme`) and the icon turns white, with a few rings
|
|
46
|
+
pulsing outward from behind it on a loop — a Google Assistant-style listening cue, on every platform (same cue,
|
|
47
|
+
same architecture as `AbChatInput`'s built-in mic — this component just has no composer/textarea attached to it).
|
|
48
|
+
Tap it again (or call `stopRecording()`) to stop and keep the text.
|
|
49
|
+
|
|
50
|
+
Recognized speech merges into `modelValue` live as the user speaks, via one internal recognition session per
|
|
51
|
+
utterance (`continuous: false` + `interimResults: true`, restarted transparently between utterances) — on every
|
|
52
|
+
platform except Safari (desktop and iOS both), where `interimResults` is kept `false`: Safari's engine has a bug
|
|
53
|
+
where enabling it can fail to (re)start the recognizer entirely, surfacing as a generic "Microphone permission
|
|
54
|
+
denied" error indistinguishable from an actually-denied permission. Safari still converts per finished phrase, just
|
|
55
|
+
not keystroke-by-keystroke live. Reads the current `modelValue` on each update and replaces only the exact suffix
|
|
56
|
+
it last inserted itself, so external edits to `modelValue` between voice updates (e.g. the consumer's own code, or
|
|
57
|
+
a bound `<textarea>` the user is also typing into) are preserved rather than clobbered.
|
|
58
|
+
|
|
59
|
+
Not supported in Firefox. On iOS, only Safari itself gets it — Chrome/Firefox/Edge/Opera on iOS run on the same
|
|
60
|
+
underlying WebKit engine and expose the same `webkitSpeechRecognition` constructor, but iOS never grants the mic
|
|
61
|
+
permission for it outside of Safari's own app, so these are detected via user-agent and treated as unsupported too
|
|
62
|
+
(button `disabled`, tooltip explains why). Requires the browser's mic permission prompt to be accepted where
|
|
63
|
+
supported.
|
|
64
|
+
|
|
65
|
+
### Voice punctuation
|
|
66
|
+
|
|
67
|
+
Two independent behaviors, each its own boolean prop, **both default `true`** (identical to `AbChatInput`'s):
|
|
68
|
+
|
|
69
|
+
- **`voicePunctuationCommands`** — saying a punctuation word inserts the actual mark: English `"period"`/
|
|
70
|
+
`"full stop"` → `.`, `"comma"` → `,`, `"question mark"` → `?`; Bengali `"পূর্ণচ্ছেদ"` → `।`, `"কমা"` → `,`,
|
|
71
|
+
`"প্রশ্নবোধক"` → `?`. Matched against the transcript text itself, independent of `lang`. Matching uses
|
|
72
|
+
script-agnostic Unicode lookaround (not `\b`, a no-op for Bengali script), so a command word only triggers
|
|
73
|
+
standalone, never as a substring of a longer word.
|
|
74
|
+
- **`voiceAutoPunctuate`** — a sentence-ending mark is inserted between voice utterances when the existing text
|
|
75
|
+
doesn't already end with one — `.` normally, `।` when `lang` (or the auto-detected language) starts with `bn`.
|
|
76
|
+
|
|
77
|
+
## Props
|
|
78
|
+
|
|
79
|
+
| Name | Type | Default | Description |
|
|
80
|
+
| --- | --- | --- | --- |
|
|
81
|
+
| `modelValue` | `String` | `""` | Two-way-bound transcribed text. |
|
|
82
|
+
| `disabled` | `Boolean` | `false` | Disables the button. |
|
|
83
|
+
| `size` | `Number` | `40` | Button diameter in px. Icon and listening rings scale with it. |
|
|
84
|
+
| `lang` | `String` | `""` | BCP-47 recognition locale (e.g. `"bn-BD"`). Empty = auto-detect from `document.documentElement.lang` / `navigator.language`. |
|
|
85
|
+
| `voicePunctuationCommands` | `Boolean` | `true` | Say a punctuation word (e.g. "period", "পূর্ণচ্ছেদ") to insert the actual mark — see "Voice punctuation" above. |
|
|
86
|
+
| `voiceAutoPunctuate` | `Boolean` | `true` | Auto-insert a sentence-ending mark between voice utterances when one isn't already there — see "Voice punctuation" above. |
|
|
87
|
+
|
|
88
|
+
## Events
|
|
89
|
+
|
|
90
|
+
| Event | Payload | Description |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `update:modelValue` | `string` | v-model sync. |
|
|
93
|
+
| `voice-start` | — | Voice recording started (mic tapped, or `startRecording()` called). |
|
|
94
|
+
| `voice-stop` | — | Recording stopped normally (mic tapped again, `stopRecording()`, or the browser ending it on its own) — transcribed text is kept in `modelValue`. |
|
|
95
|
+
| `voice-cancel` | — | Recording was cancelled via `cancelRecording()` — `modelValue` is reverted to what it was before recording started. |
|
|
96
|
+
| `voice-error` | `Error \| SpeechRecognitionErrorEvent` | Mic permission was denied, or the recognition engine errored. A `useToast` error is also shown (except benign `no-speech`/`aborted` errors). |
|
|
97
|
+
|
|
98
|
+
## Slots
|
|
99
|
+
|
|
100
|
+
None.
|
|
101
|
+
|
|
102
|
+
## v-model
|
|
103
|
+
|
|
104
|
+
Yes — `modelValue` + `update:modelValue` (string).
|
|
105
|
+
|
|
106
|
+
## Exposed methods
|
|
107
|
+
|
|
108
|
+
| Method | Description |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| `startRecording()` | Starts voice input (same as tapping the button). No-op if unsupported/disabled/already recording. |
|
|
111
|
+
| `stopRecording()` | Stops recording, keeping the transcribed text (same as tapping the button again). |
|
|
112
|
+
| `cancelRecording()` | Stops recording and reverts `modelValue` to what it was before recording started. No dedicated UI for this — call it via a template ref. |
|
|
@@ -7,13 +7,11 @@ Call `AppsbdUIConfigure` once during app initialization to set library-wide loca
|
|
|
7
7
|
```javascript
|
|
8
8
|
import { AppsbdUIConfigure } from "@vue3-appsbd-ui";
|
|
9
9
|
|
|
10
|
-
const banglaDigits = ["০", "১", "২", "৩", "৪", "৫", "৬", "৭", "৮", "৯"];
|
|
11
|
-
|
|
12
10
|
AppsbdUIConfigure({
|
|
13
11
|
monthNames: ["জানুয়ারি", "ফেব্রুয়ারি", "মার্চ", "এপ্রিল", "মে", "জুন", "জুলাই", "আগস্ট", "সেপ্টেম্বর", "অক্টোবর", "নভেম্বর", "ডিসেম্বর"],
|
|
14
12
|
monthNamesShort: ["জানু", "ফেব্রু", "মার্চ", "এপ্রি", "মে", "জুন", "জুলাই", "আগস্ট", "সেপ্টে", "অক্টো", "নভে", "ডিসে"],
|
|
15
13
|
dayNames: ["শনি", "রবি", "সোম", "মঙ্গল", "বুধ", "বৃহঃ", "শুক্র"],
|
|
16
|
-
|
|
14
|
+
digits: ["০", "১", "২", "৩", "৪", "৫", "৬", "৭", "৮", "৯"],
|
|
17
15
|
is24Hour: false,
|
|
18
16
|
rangeSeparator: " থেকে ",
|
|
19
17
|
dateDataFormat: "YYYY-MM-DD",
|
|
@@ -32,7 +30,7 @@ AppsbdUIConfigure({
|
|
|
32
30
|
| `monthNames` | `String[]` | English full names | Array of 12 full month names. |
|
|
33
31
|
| `monthNamesShort` | `String[]` | English short names | Array of 12 short month names. |
|
|
34
32
|
| `dayNames` | `String[]` | English 3-letter names | Array of 7 short day names (Sat-first). |
|
|
35
|
-
| `
|
|
33
|
+
| `digits` | `String[]` | `undefined` | Array of 10 digit characters (0-9) for locale digit transliteration (e.g. Bangla digits), applied by `AbDateTimePicker` and `formatCurrency`/`formatAmount`. |
|
|
36
34
|
| `rangeSeparator` | `String` | `' ↔ '` | Separator for date ranges in `AbDateTimePicker`. |
|
|
37
35
|
| `is24Hour` | `Boolean` | `false` | Whether to use 24-hour format in `AbDateTimePicker`. |
|
|
38
36
|
| `dateDataFormat` | `String` | `'YYYY-MM-DD'` | Global format for date data. |
|
|
@@ -56,3 +54,40 @@ AppsbdUIConfigure({
|
|
|
56
54
|
| `button.iconPosition` | `String` | `'left'` | Global default icon position for buttons (`left`, `right`). |
|
|
57
55
|
| `isModalBorder` | `Boolean` | `true` | Global default modal border. |
|
|
58
56
|
| `modalLoader` | `String` | `'AbLoader'` | Global default modal loader component. |
|
|
57
|
+
| `currency` | `String` | `'$'` | Global default currency symbol, used by `formatCurrency`/`formatAmount`. |
|
|
58
|
+
| `currencyPosition` | `String` | `'left'` | Global default currency symbol position: `'left'`, `'right'`, `'left_space'`, `'right_space'`. |
|
|
59
|
+
| `thousandSeparator` | `String` | `','` | Global default thousand separator, used by `formatCurrency`/`formatAmount`. |
|
|
60
|
+
| `decimalSeparator` | `String` | `'.'` | Global default decimal separator, used by `formatCurrency`/`formatAmount`. |
|
|
61
|
+
| `numberOfDecimals` | `Number` | `2` | Global default number of decimal digits, used by `formatCurrency`/`formatAmount`. |
|
|
62
|
+
| `round` | `Boolean` | `false` | Global default: round to `numberOfDecimals` instead of truncating, used by `formatCurrency`/`formatAmount`. |
|
|
63
|
+
|
|
64
|
+
## `formatCurrency` / `formatAmount` utils
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
import { formatCurrency, formatAmount } from "@vue3-appsbd-ui";
|
|
68
|
+
|
|
69
|
+
formatCurrency(1234.5);
|
|
70
|
+
// -> "$1,234.50" (uses globalConfig.currency/currencyPosition/thousandSeparator/decimalSeparator/numberOfDecimals)
|
|
71
|
+
|
|
72
|
+
formatCurrency(1234.5, { currency: "৳", currencyPosition: "right_space", numberOfDecimals: 0 });
|
|
73
|
+
// -> "1,234 ৳"
|
|
74
|
+
|
|
75
|
+
formatAmount(1234.5); // plain number, no currency symbol -> "1,234.50"
|
|
76
|
+
|
|
77
|
+
// round (default false) — extra decimals are truncated, not rounded up:
|
|
78
|
+
formatCurrency(1234.499); // -> "$1,234.49" (truncated)
|
|
79
|
+
formatCurrency(1234.499, { round: true }); // -> "$1,234.50" (rounded)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Any option not passed falls back to `globalConfig`, then to the hardcoded default (prop > global config > built-in default, same resolution order as other components). `digits` (see above, or `options.digits` per-call) is also applied to the formatted output via the internal `formatDigits` util. By default `round` is `false`: `numberOfDecimals` truncates extra digits rather than rounding (`1234.499` at 2 decimals -> `1234.49`, never `1234.50`); pass `round: true` (or `globalConfig.round = true`) for standard rounding.
|
|
83
|
+
|
|
84
|
+
## `formatDigits` util
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
import { formatDigits } from "@vue3-appsbd-ui";
|
|
88
|
+
|
|
89
|
+
formatDigits(123); // no globalConfig.digits set -> "123"
|
|
90
|
+
formatDigits(123, ["০", "১", "২", "৩", "৪", "৫", "৬", "৭", "৮", "৯"]); // -> "১২৩"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Used internally by `AbDateTimePicker` (`digits` prop) and `formatCurrency`/`formatAmount` (`options.digits`) to transliterate digits; falls back to `globalConfig.digits` when no array is passed, and returns the value unchanged (stringified) when neither is set.
|