inertia-bootstrap-forms 2.1.20 → 3.0.0
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/README.fa.md +468 -0
- package/README.md +322 -714
- package/dist/inertia-bootstrap-forms.cjs.js +1 -0
- package/dist/inertia-bootstrap-forms.es.js +3524 -30
- package/index.d.ts +143 -507
- package/package.json +89 -70
- package/src/AmountInput.vue +81 -76
- package/src/CaptchaInput.vue +87 -88
- package/src/CheckboxButtonInput.vue +56 -69
- package/src/CheckboxInput.vue +60 -79
- package/src/CheckboxToggle.vue +53 -136
- package/src/ChipsInput.vue +157 -0
- package/src/ComboboxInput.vue +556 -0
- package/src/DropzoneInput.vue +125 -103
- package/src/EditorInput.vue +47 -106
- package/src/EmailInput.vue +16 -15
- package/src/FileInput.vue +189 -179
- package/src/FormAdornInput.vue +75 -0
- package/src/FormContainer.vue +288 -251
- package/src/FormField.vue +59 -0
- package/src/FormLabel.vue +27 -27
- package/src/GroupControl.vue +55 -53
- package/src/LocationInput.vue +79 -72
- package/src/MobileInput.vue +21 -13
- package/src/MultiQuantityInput.vue +109 -181
- package/src/OTPInput.vue +144 -76
- package/src/PasswordInput.vue +151 -17
- package/src/PersianDatePickerInput.vue +63 -166
- package/src/QuantityInput.vue +100 -119
- package/src/RangeInput.vue +96 -0
- package/src/SimpleUploader.vue +8 -143
- package/src/StarRatingInput.vue +114 -138
- package/src/SubmitButton.vue +39 -24
- package/src/SwitchInput.vue +58 -83
- package/src/TelInput.vue +86 -120
- package/src/TextAreaInput.vue +29 -33
- package/src/TextInput.vue +42 -51
- package/src/UppyInput.vue +237 -324
- package/src/composables/useBsInstance.js +66 -0
- package/src/composables/useChoice.js +34 -0
- package/src/composables/useDisposables.js +59 -0
- package/src/composables/useField.js +121 -0
- package/src/composables/useFormApi.js +12 -0
- package/src/i18n.js +120 -0
- package/src/index.js +123 -42
- package/src/utils/bootstrap.js +48 -0
- package/src/utils/digits.js +21 -0
- package/src/utils/http.js +15 -0
- package/dist/choices-KiM-I_0P.js +0 -2955
- package/dist/dropdown-yh2aQrbw-XuCU1v1F.js +0 -1543
- package/dist/index-BRtKc2RA.js +0 -604
- package/dist/index-_x4UqcXz.js +0 -4938
- package/dist/inertia-bootstrap-forms.umd.js +0 -19
- package/dist/range-slider-pips-DTDOg3Q9.js +0 -3506
- package/dist/style.css +0 -1
- package/src/css/from-select.scss +0 -384
- package/src/css/loader.gif +0 -0
- package/src/css/location.scss +0 -44
- package/src/css/simple-upload.scss +0 -54
package/README.md
CHANGED
|
@@ -1,859 +1,467 @@
|
|
|
1
1
|
# inertia-bootstrap-forms
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **English** · [فارسی](./README.fa.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Bootstrap **6** form components for **Vue 3 + Inertia.js** (Laravel). Every field binds itself to one
|
|
6
|
+
[Inertia `useForm`](https://inertiajs.com/forms) instance through a `<FormContainer>`, and the value of **every field
|
|
7
|
+
lives in the container's single `v-model`** — no per-field `v-model`, error classes or ids to wire by hand.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
```vue
|
|
10
|
+
<FormContainer v-model="formData" url="/tickets">
|
|
11
|
+
<FormField name="priority" label="Priority">
|
|
12
|
+
<ComboboxInput name="priority" :options="priorities" />
|
|
13
|
+
</FormField>
|
|
14
|
+
|
|
15
|
+
<SubmitButton>
|
|
16
|
+
Save
|
|
17
|
+
<Icon icon="arrow-left" class="align-middle ms-1" />
|
|
18
|
+
</SubmitButton>
|
|
19
|
+
</FormContainer>
|
|
20
|
+
```
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
Built and maintained by **[Novin Vision](https://novinvision.com)** (نوین ویژن). Free for personal and commercial use (MIT).
|
|
15
23
|
|
|
16
|
-
|
|
24
|
+
> **v3 is a rewrite for Bootstrap 6.** Bootstrap 5 projects should stay on `inertia-bootstrap-forms@2`.
|
|
25
|
+
> See [Migrating from v2](#migrating-from-v2).
|
|
17
26
|
|
|
18
27
|
---
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<Select2Input name="status" :options="[
|
|
35
|
-
{ id: 'active', name: 'Active' },
|
|
36
|
-
{ id: 'disable', name: 'Disabled' },
|
|
37
|
-
{ id: 'draft', name: 'Draft' }
|
|
38
|
-
]" />
|
|
39
|
-
</div>
|
|
40
|
-
<div class="col-12 col-sm-6">
|
|
41
|
-
<FormLabel>New Image</FormLabel>
|
|
42
|
-
<FileInput name="new_thumbnail" />
|
|
43
|
-
</div>
|
|
44
|
-
<div class="col-12">
|
|
45
|
-
<FormLabel>Description</FormLabel>
|
|
46
|
-
<EditorInput name="description" />
|
|
47
|
-
</div>
|
|
48
|
-
<div class="col-12 text-end">
|
|
49
|
-
<SubmitButton />
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
</FormContainer>
|
|
53
|
-
```
|
|
29
|
+
## Table of contents
|
|
30
|
+
|
|
31
|
+
1. [What's new in v3](#whats-new-in-v3)
|
|
32
|
+
2. [Requirements](#requirements)
|
|
33
|
+
3. [Installation](#installation)
|
|
34
|
+
4. [Core concepts](#core-concepts)
|
|
35
|
+
5. [Component reference](#component-reference)
|
|
36
|
+
6. [Upload endpoint contract](#upload-endpoint-contract)
|
|
37
|
+
7. [Internationalisation](#internationalisation)
|
|
38
|
+
8. [Lifecycle, SSR and cleanup](#lifecycle-ssr-and-cleanup)
|
|
39
|
+
9. [Composables](#composables)
|
|
40
|
+
10. [Migrating from v2](#migrating-from-v2)
|
|
41
|
+
11. [Notes and known limitations](#notes-and-known-limitations)
|
|
42
|
+
12. [Development](#development)
|
|
54
43
|
|
|
55
44
|
---
|
|
56
45
|
|
|
57
|
-
##
|
|
58
|
-
|
|
59
|
-
1. [Requirements](#requirements)
|
|
60
|
-
2. [Installation](#installation)
|
|
61
|
-
3. [Global Setup (required helpers)](#global-setup-required-helpers)
|
|
62
|
-
4. [Core Concepts](#core-concepts)
|
|
63
|
-
5. [Component Reference](#component-reference)
|
|
64
|
-
- [FormContainer](#formcontainer)
|
|
65
|
-
- [FormLabel](#formlabel)
|
|
66
|
-
- [GroupControl](#groupcontrol)
|
|
67
|
-
- [TextInput](#textinput)
|
|
68
|
-
- [TextAreaInput](#textareainput)
|
|
69
|
-
- [EmailInput](#emailinput)
|
|
70
|
-
- [PasswordInput](#passwordinput)
|
|
71
|
-
- [MobileInput](#mobileinput)
|
|
72
|
-
- [TelInput](#telinput)
|
|
73
|
-
- [AmountInput](#amountinput)
|
|
74
|
-
- [QuantityInput](#quantityinput)
|
|
75
|
-
- [MultiQuantityInput](#multiquantityinput)
|
|
76
|
-
- [CheckboxInput](#checkboxinput)
|
|
77
|
-
- [CheckboxButtonInput](#checkboxbuttoninput)
|
|
78
|
-
- [CheckboxToggle](#checkboxtoggle)
|
|
79
|
-
- [Select2Input](#select2input)
|
|
80
|
-
- [PersianDatePickerInput](#persiandatepickerinput)
|
|
81
|
-
- [RangeSliderInput](#rangesliderinput)
|
|
82
|
-
- [StarRatingInput](#starratinginput)
|
|
83
|
-
- [CaptchaInput](#captchainput)
|
|
84
|
-
- [EditorInput](#editorinput)
|
|
85
|
-
- [LocationInput](#locationinput)
|
|
86
|
-
- [FileInput](#fileinput)
|
|
87
|
-
- [SimpleUploader](#simpleuploader)
|
|
88
|
-
- [DropzoneInput](#dropzoneinput)
|
|
89
|
-
- [UppyInput](#uppyinput)
|
|
90
|
-
- [SubmitButton](#submitbutton)
|
|
91
|
-
6. [Upload Endpoint Contract (backend)](#upload-endpoint-contract-backend)
|
|
92
|
-
7. [Styling & Theming](#styling--theming)
|
|
93
|
-
8. [TypeScript](#typescript)
|
|
94
|
-
9. [Known Limitations](#known-limitations)
|
|
46
|
+
## What's new in v3
|
|
95
47
|
|
|
96
|
-
|
|
48
|
+
- Rewritten for **Bootstrap 6** markup: `.check` / `.radio` / `.switch`, `.form-field`, `.btn-solid.theme-*`, `.menu`, `.alert.theme-*`, `.form-ghost`, …
|
|
49
|
+
- **New components:** `ComboboxInput` (replaces `Select2Input` / choices.js), `ChipsInput`, `OTPInput` (Bootstrap OTP), `RangeInput`, `FormAdornInput`, `FormField`.
|
|
50
|
+
- **Zero styles shipped.** No CSS file, no `<style>` blocks. Import Bootstrap 6 in your app.
|
|
51
|
+
- Every field emits Vue events (`update:modelValue`, `change`, plus component specific ones) and exposes all the props it needs.
|
|
52
|
+
- Bootstrap JavaScript plugins (Combobox, Chips, OTP, Range, Menu) are loaded lazily **on mount** and **disposed on unmount**. Listeners, timers, XHR requests, Dropzone and Uppy instances are released too.
|
|
53
|
+
- SSR-safe: nothing touches `window` / `document` at import time or during server rendering.
|
|
54
|
+
- No more hidden global requirements (`window.axios`, `maska`, `vue3-bootstrap-components`, FontAwesome).
|
|
97
55
|
|
|
98
56
|
## Requirements
|
|
99
57
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
| Requirement | Why |
|
|
58
|
+
| Requirement | Notes |
|
|
103
59
|
|---|---|
|
|
104
|
-
|
|
|
105
|
-
| `@inertiajs/vue3`
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
| `vue-tel-input` | Only needed if you use `<TelInput>`. |
|
|
111
|
-
| `vue3-persian-datetime-picker` | Only needed if you use `<PersianDatePickerInput>`. |
|
|
112
|
-
| `maska` | Only needed if you use `<AmountInput>` or `<QuantityInput>` (number masking). |
|
|
113
|
-
| `axios` available globally as `window.axios` | Used internally by `<FileInput>`. Laravel's default `resources/js/bootstrap.js` already does this (`window.axios = require('axios')`). |
|
|
114
|
-
|
|
115
|
-
The following libraries are **already bundled inside `dist/`** and do **not** need to be installed separately: `vue3-bootstrap-components`, `choices.js`, `svelte-range-slider-pips`, `@uppy/core`, `@uppy/vue`, `@uppy/xhr-upload`.
|
|
60
|
+
| `vue` ≥ 3.3 | |
|
|
61
|
+
| `@inertiajs/vue3` ≥ 2.0 | `FormContainer` uses `useForm()` |
|
|
62
|
+
| `bootstrap` ^6.0.0-alpha.1 | **CSS** is required. The JS plugins are imported by this package on demand. |
|
|
63
|
+
| `@floating-ui/dom` | Peer dependency of Bootstrap 6 menus (needed by Combobox / MultiQuantityInput) |
|
|
64
|
+
| `dropzone` ^6 | Only for `DropzoneInput` |
|
|
65
|
+
| `leaflet` | Only for `LocationInput` |
|
|
116
66
|
|
|
117
|
-
|
|
67
|
+
Third-party widgets are regular dependencies of this package and are loaded lazily: `vue-tel-input`,
|
|
68
|
+
`vue3-persian-datetime-picker`, `@tinymce/tinymce-vue`, `@vue-leaflet/vue-leaflet`, `@uppy/*`.
|
|
118
69
|
|
|
119
70
|
## Installation
|
|
120
71
|
|
|
121
|
-
### 1. Install the package
|
|
122
|
-
|
|
123
|
-
This package isn't necessarily on the public npm registry, so install it however it was distributed to you:
|
|
124
|
-
|
|
125
72
|
```bash
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
# from a Git repository
|
|
130
|
-
npm install git+https://github.com/novinvision/inertia-bootstrap-forms.git
|
|
131
|
-
|
|
132
|
-
# from a local folder or tarball
|
|
133
|
-
npm install file:../path/to/inertia-bootstrap-forms
|
|
73
|
+
npm install inertia-bootstrap-forms bootstrap@next @floating-ui/dom
|
|
74
|
+
# optional
|
|
75
|
+
npm install dropzone leaflet
|
|
134
76
|
```
|
|
135
77
|
|
|
136
|
-
###
|
|
78
|
+
### Import the styles in your application
|
|
137
79
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
npm install @inertiajs/vue3 dropzone @tinymce/tinymce-vue \
|
|
142
|
-
@vue-leaflet/vue-leaflet leaflet vue-tel-input \
|
|
143
|
-
vue3-persian-datetime-picker maska
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### 3. Import the package styles
|
|
80
|
+
The package does not ship any CSS. Import Bootstrap 6 (compiled CSS or your own Sass build) and — only for the
|
|
81
|
+
widgets you use — the CSS of the third-party libraries:
|
|
147
82
|
|
|
148
83
|
```js
|
|
149
84
|
// resources/js/app.js
|
|
150
|
-
import '
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### 4. Register the components
|
|
85
|
+
import 'bootstrap/dist/css/bootstrap.min.css'; // Bootstrap 6 (required)
|
|
154
86
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
import
|
|
160
|
-
</script>
|
|
87
|
+
import 'vue-tel-input/vue-tel-input.css'; // TelInput
|
|
88
|
+
import 'leaflet/dist/leaflet.css'; // LocationInput
|
|
89
|
+
import 'dropzone/dist/basic.css'; // DropzoneInput
|
|
90
|
+
import 'dropzone/dist/dropzone.css'; // DropzoneInput
|
|
91
|
+
import '@uppy/vue/css/style.css'; // UppyInput
|
|
161
92
|
```
|
|
162
93
|
|
|
163
|
-
|
|
94
|
+
You do **not** need to import Bootstrap's JavaScript: the plugins used by the components are loaded by the package.
|
|
95
|
+
If your application already bundles Bootstrap and you want a single copy, hand the classes over:
|
|
164
96
|
|
|
165
97
|
```js
|
|
166
|
-
import {
|
|
167
|
-
import {
|
|
168
|
-
import * as InertiaBootstrapForms from 'inertia-bootstrap-forms';
|
|
169
|
-
import 'inertia-bootstrap-forms/dist/style.css';
|
|
170
|
-
|
|
171
|
-
createInertiaApp({
|
|
172
|
-
resolve: name => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
|
|
173
|
-
setup({ el, App, props, plugin }) {
|
|
174
|
-
const app = createApp({ render: () => h(App, props) }).use(plugin);
|
|
98
|
+
import { Combobox, Chips, OtpInput, Range, Menu } from 'bootstrap';
|
|
99
|
+
import { configureBootstrap } from 'inertia-bootstrap-forms';
|
|
175
100
|
|
|
176
|
-
|
|
177
|
-
if (name !== 'default') app.component(name, component);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
app.mount(el);
|
|
181
|
-
},
|
|
182
|
-
});
|
|
101
|
+
configureBootstrap({ Combobox, Chips, OtpInput, Range, Menu });
|
|
183
102
|
```
|
|
184
103
|
|
|
185
|
-
|
|
104
|
+
### Registering components
|
|
186
105
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
Two components (`AmountInput`, `QuantityInput`) rely on globals that this package does **not** provide. You need to register them yourself in `app.js` before those components will work.
|
|
190
|
-
|
|
191
|
-
### `v-maska` directive
|
|
192
|
-
|
|
193
|
-
`AmountInput` and `QuantityInput` use `v-maska` (from the [`maska`](https://www.npmjs.com/package/maska) package) to format numbers with thousand separators while typing.
|
|
194
|
-
|
|
195
|
-
```bash
|
|
196
|
-
npm install maska
|
|
197
|
-
```
|
|
106
|
+
Import what you need, or install the plugin to register everything globally:
|
|
198
107
|
|
|
199
108
|
```js
|
|
200
|
-
import
|
|
201
|
-
|
|
202
|
-
app.directive('maska', vMaska);
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
You can use any version/config of `maska` you like — the components only depend on the directive name (`v-maska`) and the `@maska` event payload (`event.detail.unmasked`), so feel free to follow Maska's own documentation for advanced masking options.
|
|
206
|
-
|
|
207
|
-
### `$number` global helper
|
|
208
|
-
|
|
209
|
-
The same two components call `this.$number.toEnglish(...)` to convert localized (Persian/Arabic-Indic) digits typed by the user into plain English digits before storing them in the form. This helper is **not bundled** — you need to provide it as a global property. A minimal implementation that mirrors the digit conversion already used internally by `FormContainer` on submit:
|
|
210
|
-
|
|
211
|
-
```js
|
|
212
|
-
// resources/js/plugins/number.js
|
|
213
|
-
const persianDigits = ['۰','۱','۲','۳','۴','۵','۶','۷','۸','۹'];
|
|
214
|
-
const arabicDigits = ['٠','١','٢','٣','٤','٥','٦','٧','٨','٩'];
|
|
215
|
-
|
|
216
|
-
export default {
|
|
217
|
-
install(app) {
|
|
218
|
-
app.config.globalProperties.$number = {
|
|
219
|
-
toEnglish(value) {
|
|
220
|
-
if (value === null || value === undefined) return value;
|
|
221
|
-
let str = value.toString();
|
|
222
|
-
persianDigits.forEach((d, i) => { str = str.replaceAll(d, i); });
|
|
223
|
-
arabicDigits.forEach((d, i) => { str = str.replaceAll(d, i); });
|
|
224
|
-
return str.replaceAll(',', '');
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
```js
|
|
232
|
-
import NumberPlugin from './plugins/number';
|
|
233
|
-
app.use(NumberPlugin);
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
If you don't use `AmountInput` or `QuantityInput`, you can skip this section entirely.
|
|
237
|
-
|
|
238
|
-
### Optional: Font Awesome
|
|
239
|
-
|
|
240
|
-
`CaptchaInput` renders a refresh icon using a Font Awesome class (`fal fa-sync-alt`, Pro "light" style). If you don't load Font Awesome Pro, the button still works — you just won't see the icon. Swap in your own icon via the component's markup if needed.
|
|
241
|
-
|
|
242
|
-
---
|
|
243
|
-
|
|
244
|
-
## Core Concepts
|
|
245
|
-
|
|
246
|
-
### `FormContainer` and the injected `form`
|
|
247
|
-
|
|
248
|
-
`<FormContainer>` wraps Inertia's `useForm()` and `provide()`s it to every descendant via Vue's `provide`/`inject`. All the field components below `inject` this `form` object and read/write `form[name]` automatically — that's the entire trick behind not having to wire `v-model` on every field yourself.
|
|
249
|
-
|
|
250
|
-
```vue
|
|
251
|
-
<FormContainer v-model="formData" url="/login" method="post" reset-on-success>
|
|
252
|
-
<TextInput name="email" />
|
|
253
|
-
<PasswordInput name="password" />
|
|
254
|
-
<SubmitButton />
|
|
255
|
-
</FormContainer>
|
|
256
|
-
```
|
|
109
|
+
import InertiaBootstrapForms from 'inertia-bootstrap-forms';
|
|
257
110
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
| `options` | Object | `{}` | Extra fields merged into the Inertia form state (e.g. defaults that aren't real inputs). |
|
|
267
|
-
| `resetOnSuccess` | Boolean | `false` | Calls `form.reset()` automatically after a successful submit. |
|
|
268
|
-
| `submitHandler` | Function | `null` | If provided, this function is called instead of the default Inertia submit flow — useful for custom submit logic (e.g. confirmation modals). |
|
|
269
|
-
|
|
270
|
-
Events: `submit`, `reset`, `onStart`, `onFinish`, `onSuccess`, `onError`, `change`, `update:modelValue`.
|
|
271
|
-
|
|
272
|
-
Slots:
|
|
273
|
-
- default — scoped with `{ form, submit }`, the actual form fields.
|
|
274
|
-
- `errors` — override the default error `<Alert>` block.
|
|
275
|
-
- `message` — override the default success `<Alert>` block (shown when the backend response includes a `message` prop).
|
|
276
|
-
|
|
277
|
-
Exposed methods (via template ref): `submit()`, `reset()`.
|
|
278
|
-
|
|
279
|
-
Notable built-in behavior: on submit, `FormContainer` converts any Persian/Arabic-Indic digits present in the form's values to plain English digits before sending the request — handy for numeric fields typed with a Persian keyboard.
|
|
280
|
-
|
|
281
|
-
### Field naming & binding
|
|
282
|
-
|
|
283
|
-
Every field component takes a `name` prop. As long as it lives inside a `<FormContainer>` (or any component that injects a `form`), it automatically reads/writes `form[name]` — no manual `v-model` needed:
|
|
284
|
-
|
|
285
|
-
```vue
|
|
286
|
-
<TextInput name="title" />
|
|
287
|
-
<!-- equivalent to manually doing -->
|
|
288
|
-
<input v-model="form.title" name="title" class="form-control">
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
Validation errors are read from `form.errors[name]` and applied as Bootstrap's `is-invalid` class automatically.
|
|
292
|
-
|
|
293
|
-
### Grouped / repeated fields — `GroupControl`
|
|
294
|
-
|
|
295
|
-
`<GroupControl name="...">` lets a *block* of fields write into a nested/indexed slice of the form data instead of the top level — useful for repeating rows (e.g. multiple bid items, multiple contacts, etc.).
|
|
296
|
-
|
|
297
|
-
Each `<GroupControl>` instance figures out its own index (`groupID`) based on its position among sibling `.form-control-group` blocks, so the typical pattern is to repeat the **whole** `<GroupControl>` with `v-for`:
|
|
298
|
-
|
|
299
|
-
```vue
|
|
300
|
-
<div v-for="(row, index) in rows" :key="index">
|
|
301
|
-
<GroupControl name="bids">
|
|
302
|
-
<TextInput name="title" />
|
|
303
|
-
<AmountInput name="price" />
|
|
304
|
-
</GroupControl>
|
|
305
|
-
</div>
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
This produces `form.bids[0].title`, `form.bids[0].price`, `form.bids[1].title`, etc.
|
|
309
|
-
|
|
310
|
-
`MultiQuantityInput` (documented below) uses a single `GroupControl` to collect a *map* of quantities (`form.tickets.adult`, `form.tickets.child`, …) instead of a repeated list — see its own example for that variant.
|
|
311
|
-
|
|
312
|
-
### Validation & error display
|
|
313
|
-
|
|
314
|
-
`FormContainer` renders a default Bootstrap `Alert` listing every message in `form.errors` (replaceable via the `errors` slot), and every field adds `is-invalid` automatically when `form.errors[name]` exists. You don't need to render error text per-field unless you want to — Bootstrap will show `.invalid-feedback` siblings if you add them yourself.
|
|
315
|
-
|
|
316
|
-
### Element IDs
|
|
317
|
-
|
|
318
|
-
`form.getID(field)` (available on the injected `form`) builds a unique, collision-free `id` attribute for fields, taking the surrounding `<FormContainer>` id and any `GroupControl` into account. Components that render a real `<label for="">`/`<input id="">` pair (e.g. `TextInput`, `CheckboxInput`) use this automatically.
|
|
319
|
-
|
|
320
|
-
---
|
|
321
|
-
|
|
322
|
-
## Component Reference
|
|
323
|
-
|
|
324
|
-
### FormContainer
|
|
325
|
-
|
|
326
|
-
See [Core Concepts](#formcontainer-and-the-injected-form) above for the full prop/event/slot table.
|
|
327
|
-
|
|
328
|
-
### FormLabel
|
|
329
|
-
|
|
330
|
-
Simple `<label>` wrapper that appends a red `*` when `required` is set.
|
|
331
|
-
|
|
332
|
-
```vue
|
|
333
|
-
<FormLabel required>Email address</FormLabel>
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
| Prop | Type | Default |
|
|
337
|
-
|---|---|---|
|
|
338
|
-
| `required` | Boolean | `false` |
|
|
339
|
-
|
|
340
|
-
Slot: default — label text.
|
|
341
|
-
|
|
342
|
-
### GroupControl
|
|
343
|
-
|
|
344
|
-
See [Grouped / repeated fields](#grouped--repeated-fields--groupcontrol) above.
|
|
345
|
-
|
|
346
|
-
| Prop | Type | Required |
|
|
347
|
-
|---|---|---|
|
|
348
|
-
| `name` | String | yes |
|
|
349
|
-
|
|
350
|
-
### TextInput
|
|
351
|
-
|
|
352
|
-
The base text field. Most other "shortcut" inputs (`EmailInput`, `PasswordInput`, `MobileInput`) are thin wrappers around this component.
|
|
353
|
-
|
|
354
|
-
```vue
|
|
355
|
-
<TextInput name="full_name" />
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
| Prop | Type | Required |
|
|
359
|
-
|---|---|---|
|
|
360
|
-
| `name` | String | yes |
|
|
361
|
-
|
|
362
|
-
Renders a plain `<input type="text" class="form-control">` bound to `form[name]`.
|
|
363
|
-
|
|
364
|
-
### TextAreaInput
|
|
365
|
-
|
|
366
|
-
Same binding pattern as `TextInput`, renders a `<textarea class="form-control">`.
|
|
367
|
-
|
|
368
|
-
```vue
|
|
369
|
-
<TextAreaInput name="description" />
|
|
111
|
+
createInertiaApp({
|
|
112
|
+
setup({ el, App, props, plugin }) {
|
|
113
|
+
createApp({ render: () => h(App, props) })
|
|
114
|
+
.use(plugin)
|
|
115
|
+
.use(InertiaBootstrapForms, { locale: 'en' }) // options are optional
|
|
116
|
+
.mount(el);
|
|
117
|
+
},
|
|
118
|
+
});
|
|
370
119
|
```
|
|
371
120
|
|
|
372
|
-
|
|
373
|
-
|---|---|---|
|
|
374
|
-
| `name` | String | yes |
|
|
375
|
-
|
|
376
|
-
### EmailInput
|
|
121
|
+
## Core concepts
|
|
377
122
|
|
|
378
|
-
|
|
123
|
+
### One `v-model` for the whole form
|
|
379
124
|
|
|
380
125
|
```vue
|
|
381
|
-
<
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
> **Note:** see [Known Limitations](#known-limitations) — the `name` prop on this specific wrapper is not declared correctly in the current source, so passing a custom `name` may not bind to the form data as expected. If that affects you, use `<TextInput name="..." type="email" />` directly instead.
|
|
385
|
-
|
|
386
|
-
### PasswordInput
|
|
126
|
+
<script setup>
|
|
127
|
+
import { ref } from 'vue';
|
|
128
|
+
import { FormContainer, TextInput, ComboboxInput, SwitchInput, SubmitButton } from 'inertia-bootstrap-forms';
|
|
387
129
|
|
|
388
|
-
|
|
130
|
+
const formData = ref({ name: '', status: 'draft', published: false });
|
|
131
|
+
</script>
|
|
389
132
|
|
|
390
|
-
|
|
391
|
-
<
|
|
133
|
+
<template>
|
|
134
|
+
<FormContainer v-model="formData" url="/products" @success="onSaved">
|
|
135
|
+
<TextInput name="name" />
|
|
136
|
+
<ComboboxInput name="status" :options="['draft', 'active']" />
|
|
137
|
+
<SwitchInput name="published" label="Published" />
|
|
138
|
+
<SubmitButton />
|
|
139
|
+
</FormContainer>
|
|
140
|
+
</template>
|
|
392
141
|
```
|
|
393
142
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
Thin wrapper around `TextInput` (`type="tel"`, Persian placeholder "موبایل خود را وارد کنید").
|
|
143
|
+
* `name` is the key inside `formData`. The object does **not** have to declare every field up front: keys that are missing are added to `formData` as soon as the field is used, and they are submitted and reset with the rest. Typing into a field updates `formData`; changing `formData` from the outside updates the fields.
|
|
144
|
+
* Server-side validation errors (`form.errors`) are matched by `name` and turn the control `.is-invalid`.
|
|
145
|
+
* Fields are disabled while the form is `processing`.
|
|
146
|
+
* Persian / Arabic digits are converted to English digits before submit (`:normalize-digits="false"` to disable). `File` objects are never touched.
|
|
147
|
+
* Values are still **not limited to the container**: a field used *outside* a `FormContainer` works with its own `v-model`.
|
|
401
148
|
|
|
402
149
|
```vue
|
|
403
|
-
<
|
|
150
|
+
<TextInput v-model="title" /> <!-- standalone -->
|
|
404
151
|
```
|
|
405
152
|
|
|
406
|
-
|
|
407
|
-
|---|---|---|
|
|
408
|
-
| `name` | String | `'mobile'` |
|
|
409
|
-
|
|
410
|
-
### TelInput
|
|
153
|
+
### Events
|
|
411
154
|
|
|
412
|
-
|
|
155
|
+
Every component declares its emits:
|
|
413
156
|
|
|
414
|
-
|
|
415
|
-
<TelInput name="phone" placeholder="912 345 6789" />
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
| Prop | Type | Default |
|
|
157
|
+
| Event | Payload | Emitted by |
|
|
419
158
|
|---|---|---|
|
|
420
|
-
| `
|
|
421
|
-
| `
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
### AmountInput
|
|
159
|
+
| `update:modelValue` | `value` | all fields |
|
|
160
|
+
| `change` | `value, event?` | all fields |
|
|
161
|
+
| `input` | `value, event` | text-like fields, OTP, range |
|
|
162
|
+
| `focus` / `blur` | `event` | text-like fields, checkbox family, range, chips |
|
|
163
|
+
| component specific | see [reference](#component-reference) | combobox, chips, OTP, uploaders … |
|
|
426
164
|
|
|
427
|
-
|
|
165
|
+
Native listeners that are not declared (`@keydown`, `@paste`, …) fall through to the underlying `<input>` on single-root
|
|
166
|
+
components (`TextInput`, `TextAreaInput`, `EmailInput`, `MobileInput`, `PasswordInput`, `FormAdornInput`).
|
|
428
167
|
|
|
429
|
-
|
|
430
|
-
<AmountInput name="price" unit="تومان" />
|
|
431
|
-
```
|
|
168
|
+
### Props shared by every field
|
|
432
169
|
|
|
433
|
-
| Prop | Type |
|
|
170
|
+
| Prop | Type | Description |
|
|
434
171
|
|---|---|---|
|
|
435
|
-
| `name` |
|
|
436
|
-
| `
|
|
437
|
-
| `
|
|
438
|
-
| `
|
|
439
|
-
| `
|
|
440
|
-
| `
|
|
441
|
-
|
|
442
|
-
Slot: `suffix` — extra content appended after the unit label (inside the Bootstrap `InputGroup`).
|
|
443
|
-
|
|
444
|
-
Requires the `v-maska` directive and `$number.toEnglish()` global helper — see [Global Setup](#global-setup-required-helpers).
|
|
172
|
+
| `name` | `string` | Key in the form's v-model (also used for error lookup) |
|
|
173
|
+
| `modelValue` | any | Standalone `v-model` (ignored inside a `FormContainer`) |
|
|
174
|
+
| `id` | `string` | Override the generated id (auto-linked to `FormField` labels) |
|
|
175
|
+
| `disabled` | `boolean` | Disable the control |
|
|
176
|
+
| `readonly` | `boolean` | Read-only (or non-interactive for controls without native readonly) |
|
|
177
|
+
| `required` | `boolean` | Native `required` |
|
|
178
|
+
| `invalid` | `boolean` | Force invalid state; default follows server errors |
|
|
445
179
|
|
|
446
|
-
###
|
|
447
|
-
|
|
448
|
-
Stepper-style numeric input (`+` / `-` buttons) with the same masking as `AmountInput`.
|
|
180
|
+
### Grouped / repeated values
|
|
449
181
|
|
|
450
182
|
```vue
|
|
451
|
-
<
|
|
183
|
+
<GroupControl name="items" :index="0">
|
|
184
|
+
<TextInput name="title" /> <!-- items[0].title -->
|
|
185
|
+
<QuantityInput name="qty" /> <!-- items[0].qty -->
|
|
186
|
+
</GroupControl>
|
|
452
187
|
```
|
|
453
188
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
| `name` | String | required |
|
|
457
|
-
| `required` | Boolean | `false` |
|
|
458
|
-
| `disabled` | Boolean | `false` |
|
|
459
|
-
| `readonly` | Boolean | `false` |
|
|
460
|
-
| `unit` | String | `'عدد'` |
|
|
461
|
-
| `min` | Number | `0` |
|
|
462
|
-
| `max` | Number | `null` |
|
|
189
|
+
`index` is optional — without it the position among sibling `.form-control-group` elements is used. Error keys such as
|
|
190
|
+
`items.0.title` are resolved automatically.
|
|
463
191
|
|
|
464
|
-
|
|
192
|
+
---
|
|
465
193
|
|
|
466
|
-
|
|
194
|
+
## Component reference
|
|
467
195
|
|
|
468
|
-
###
|
|
196
|
+
### Form structure
|
|
469
197
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
```vue
|
|
473
|
-
<MultiQuantityInput
|
|
474
|
-
name="tickets"
|
|
475
|
-
unit="نفر"
|
|
476
|
-
:options="[
|
|
477
|
-
{ name: 'بزرگسال', min: 1, max: 10 }, // key: 0
|
|
478
|
-
{ name: 'کودک', min: 0, max: 10 }, // key: 1
|
|
479
|
-
]"
|
|
480
|
-
/>
|
|
481
|
-
```
|
|
198
|
+
#### `FormContainer`
|
|
482
199
|
|
|
483
200
|
| Prop | Type | Default | Description |
|
|
484
201
|
|---|---|---|---|
|
|
485
|
-
| `
|
|
486
|
-
| `
|
|
487
|
-
| `
|
|
488
|
-
| `
|
|
489
|
-
| `
|
|
490
|
-
| `
|
|
491
|
-
| `
|
|
492
|
-
| `
|
|
493
|
-
| `
|
|
494
|
-
| `
|
|
495
|
-
|
|
496
|
-
|
|
202
|
+
| `modelValue` | `object` | `{}` | The form data (`v-model`) |
|
|
203
|
+
| `url` | `string` | `''` | Submit URL |
|
|
204
|
+
| `method` | `string` | `'post'` | HTTP method |
|
|
205
|
+
| `only` | `string[]` | `[]` | Inertia `only` option |
|
|
206
|
+
| `options` | `object` | `{}` | Extra fields that are not part of `modelValue` |
|
|
207
|
+
| `visitOptions` | `object` | `{}` | Extra Inertia visit options (`preserveScroll`, `headers`, callbacks…) |
|
|
208
|
+
| `autoTab` | `boolean` | `true` | Focus next field when `maxlength` is reached |
|
|
209
|
+
| `resetOnSuccess` | `boolean` | `false` | Reset the form after a successful submit |
|
|
210
|
+
| `submitHandler` | `(event, form) => any` | `null` | Replace the default submit |
|
|
211
|
+
| `normalizeDigits` | `boolean` | `true` | Persian/Arabic → English digits on submit |
|
|
212
|
+
| `validate` | `boolean` | `false` | Enable Bootstrap 6 client-side validation (`data-bs-validate`) |
|
|
213
|
+
| `htmlMessages` | `boolean` | `false` | Render server messages as HTML (trusted content only) |
|
|
214
|
+
| `hideAlerts` | `boolean` | `false` | Hide the built-in error / success alerts |
|
|
215
|
+
|
|
216
|
+
Events: `update:modelValue`, `change`, `submit`, `reset`, `before`, `start`, `progress`, `success`, `error`, `cancel`, `finish`
|
|
217
|
+
(legacy `onStart`, `onFinish`, `onSuccess`, `onError` are still emitted).
|
|
218
|
+
Slots: default `{ form, submit, reset }`, `errors { errors, form }`, `message { message }`.
|
|
219
|
+
Exposed: `submit()`, `reset()`, `form`, `el`.
|
|
220
|
+
|
|
221
|
+
A `message` prop returned by the server in the Inertia page props is shown as a success alert.
|
|
222
|
+
|
|
223
|
+
#### `FormField`
|
|
224
|
+
|
|
225
|
+
Bootstrap 6 `.form-field`: label + control + help + validation feedback. The generated `id` is injected into the child control and the label.
|
|
226
|
+
|
|
227
|
+
| Prop | Description |
|
|
228
|
+
|---|---|
|
|
229
|
+
| `name` | Field key — used to read the server error |
|
|
230
|
+
| `label`, `help` | Texts |
|
|
231
|
+
| `error` | Override the error text |
|
|
232
|
+
| `required` | Adds the required mark to the label |
|
|
233
|
+
| `id` | Override the generated id |
|
|
497
234
|
|
|
498
|
-
|
|
235
|
+
Slots: default, `label`, `help`, `error`.
|
|
499
236
|
|
|
500
237
|
```vue
|
|
501
|
-
<
|
|
238
|
+
<FormField name="email" label="Email" help="We never share it" required>
|
|
239
|
+
<EmailInput name="email" />
|
|
240
|
+
</FormField>
|
|
502
241
|
```
|
|
503
242
|
|
|
504
|
-
|
|
505
|
-
|---|---|---|
|
|
506
|
-
| `name` | String | required |
|
|
507
|
-
| `id` | String | `''` |
|
|
508
|
-
| `value` | String / Number | `'yes'` |
|
|
509
|
-
| `type` | String | `'checkbox'` (can be set to `'radio'`) |
|
|
510
|
-
|
|
511
|
-
Event: `change`. Slot: default — label text.
|
|
243
|
+
#### `FormLabel`, `GroupControl`, `SubmitButton`
|
|
512
244
|
|
|
513
|
-
|
|
245
|
+
* `FormLabel` – `for`, `required`. Detects its control automatically inside `FormField` or from the next element.
|
|
246
|
+
* `GroupControl` – `name` (required), `index`.
|
|
247
|
+
* `SubmitButton` – `variant` (`solid`), `theme` (`primary`), `loading`, `disabled`, `locale`. Renders `class="btn-solid theme-primary"`; pass `:variant="null"` to bring your own classes. Shows a spinner while `form.processing` / `form.uploading`. Emits `click`. Slots: default, `progress`.
|
|
514
248
|
|
|
515
|
-
###
|
|
249
|
+
### Text-like inputs
|
|
516
250
|
|
|
517
|
-
|
|
251
|
+
| Component | Extra props | Notes |
|
|
252
|
+
|---|---|---|
|
|
253
|
+
| `TextInput` | `type`, `placeholder`, `size` (`sm`/`lg`) | `.form-control` |
|
|
254
|
+
| `TextAreaInput` | `rows`, `placeholder`, `size` | |
|
|
255
|
+
| `EmailInput` | `placeholder`, `locale` | `type="email"`; all `TextInput` props forwarded |
|
|
256
|
+
| `MobileInput` | `placeholder`, `locale` | `type="tel"` |
|
|
257
|
+
| `PasswordInput` | `placeholder`, `toggle`, `locale` | `toggle` adds a show / hide button (form-adorn); event `toggle` |
|
|
258
|
+
| `TelInput` | `placeholder`, `defaultCountry` (`ir`), `preferredCountries`, `mode`, `validCharactersOnly`, `validate`, `dropdownOptions`, `inputOptions` | `vue-tel-input`; stores the international number; events `validate`, `country-changed` |
|
|
259
|
+
| `FormAdornInput` | `type`, `placeholder`, `startText`, `endText`, `size` | Bootstrap 6 `.form-adorn`. Slots `start`, `end` |
|
|
260
|
+
| `AmountInput` | `placeholder`, `unit`, `min`, `max`, `size`, `locale` | Integer with thousands separators, stores a `number` |
|
|
261
|
+
| `QuantityInput` | `min` (0), `max`, `step` (1), `unit`, `locale` | Stepper; events `increment`, `decrement` |
|
|
262
|
+
| `MultiQuantityInput` | `options`, `min`, `max`, `totalMin`, `totalMax`, `unit`, `placement`, `locale` | Bootstrap Menu with one stepper per option; value `{ 0: 2, 1: 1 }` |
|
|
518
263
|
|
|
519
264
|
```vue
|
|
520
|
-
<
|
|
521
|
-
<
|
|
265
|
+
<FormAdornInput name="website" start-text="https://">
|
|
266
|
+
<template #end><span class="form-adorn-icon"><svg …/></span></template>
|
|
267
|
+
</FormAdornInput>
|
|
522
268
|
```
|
|
523
269
|
|
|
524
|
-
|
|
525
|
-
|---|---|---|
|
|
526
|
-
| `name` | String | required |
|
|
527
|
-
| `id` | String | `''` |
|
|
528
|
-
| `value` | String | `'yes'` |
|
|
529
|
-
| `type` | String | `'radio'` |
|
|
530
|
-
|
|
531
|
-
Event: `change`. Wrap the rendered `<label>` in your own `.btn-group`/`.btn` classes for the typical Bootstrap button-group look.
|
|
270
|
+
### Choice inputs
|
|
532
271
|
|
|
533
|
-
|
|
272
|
+
| Component | Extra props |
|
|
273
|
+
|---|---|
|
|
274
|
+
| `CheckboxInput` | `type` (`checkbox`/`radio`), `value`, `trueValue`, `falseValue`, `checked`, `indeterminate`, `label` |
|
|
275
|
+
| `SwitchInput` | `value`, `trueValue`, `falseValue`, `checked`, `label` |
|
|
276
|
+
| `CheckboxButtonInput` | `type` (`radio`), `value`, `variant` (`outline`), `theme` (`primary`), `checked` — Bootstrap `.btn-check` |
|
|
277
|
+
| `CheckboxToggle` | `type`, `value`, `checked`, `hideInput` — selectable card (`.form-field-card`) |
|
|
534
278
|
|
|
535
|
-
|
|
279
|
+
Model rules follow Vue's native `v-model`: an **array** model collects the checked `value`s, a **radio** stores the selected `value`, a single checkbox stores `trueValue` / `falseValue` (default `true` / `false`).
|
|
536
280
|
|
|
537
281
|
```vue
|
|
538
|
-
<
|
|
282
|
+
<CheckboxInput name="roles" value="admin" label="Admin" />
|
|
283
|
+
<CheckboxInput name="roles" value="editor" label="Editor" /> <!-- roles: ['admin','editor'] -->
|
|
284
|
+
<CheckboxInput name="plan" type="radio" value="pro" label="Pro" />
|
|
539
285
|
```
|
|
540
286
|
|
|
541
|
-
|
|
542
|
-
|---|---|---|
|
|
543
|
-
| `name` | String | required |
|
|
544
|
-
| `id` | String | `''` |
|
|
545
|
-
| `value` | String / Number | `'yes'` |
|
|
546
|
-
| `type` | String | `'checkbox'` |
|
|
547
|
-
| `hideInput` | Boolean | `false` — visually hides the native input while keeping the toggle clickable. |
|
|
548
|
-
|
|
549
|
-
Event: `change`.
|
|
550
|
-
|
|
551
|
-
### Select2Input
|
|
287
|
+
### `ComboboxInput` (Bootstrap 6 Combobox)
|
|
552
288
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
```vue
|
|
556
|
-
<Select2Input
|
|
557
|
-
name="category_id"
|
|
558
|
-
:options="categories"
|
|
559
|
-
label="title"
|
|
560
|
-
placeholder="Select a category"
|
|
561
|
-
search-enabled
|
|
562
|
-
/>
|
|
563
|
-
|
|
564
|
-
<!-- Remote search -->
|
|
565
|
-
<Select2Input
|
|
566
|
-
name="user_id"
|
|
567
|
-
:search="{ url: '/api/users/search' }"
|
|
568
|
-
placeholder="Search a user..."
|
|
569
|
-
/>
|
|
570
|
-
```
|
|
289
|
+
Replaces `Select2Input` (choices.js). `Select2Input` is still exported as an alias.
|
|
571
290
|
|
|
572
291
|
| Prop | Type | Default | Description |
|
|
573
292
|
|---|---|---|---|
|
|
574
|
-
| `
|
|
575
|
-
| `
|
|
576
|
-
| `
|
|
577
|
-
| `
|
|
578
|
-
| `
|
|
579
|
-
| `
|
|
580
|
-
| `
|
|
581
|
-
| `
|
|
582
|
-
| `
|
|
583
|
-
| `
|
|
584
|
-
| `hideDropdown` |
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
> **Note:** the prop named `key` (meant to pick which object field is used as the option *value*) cannot actually be set from a template, because Vue reserves `:key` for its own list-rendering mechanism. The default value-resolution (`id` → `name` → `label` → `value`) is used instead. See [Known Limitations](#known-limitations).
|
|
590
|
-
|
|
591
|
-
Since this wraps Choices.js, any configuration option supported by that library can be passed through the `config` prop — see [Choices.js documentation](https://github.com/Choices-js/Choices) for the full list (custom templates, callback hooks, RTL, etc.).
|
|
592
|
-
|
|
593
|
-
### PersianDatePickerInput
|
|
594
|
-
|
|
595
|
-
Persian (Jalali) date/time picker powered by the **external package `vue3-persian-datetime-picker`**.
|
|
293
|
+
| `options` | `Array` | `[]` | Strings, numbers or objects `{ id \| value, name \| label, description, disabled }` |
|
|
294
|
+
| `valueKey` / `labelKey` | `string` | auto | Property names of option objects (`label` is a legacy alias of `labelKey`) |
|
|
295
|
+
| `descriptionKey` | `string` | `'description'` | Secondary text of an option |
|
|
296
|
+
| `multiple` | `boolean` | `false` | Multiple selection (array model) |
|
|
297
|
+
| `placeholder` | `string` | i18n | |
|
|
298
|
+
| `searchEnabled` | `boolean` | `false` | Client-side search box |
|
|
299
|
+
| `search` | `object` | `{ url: null }` | **Remote search**: `{ url, method='POST', param='query', debounce=400, minChars=0, headers, resultsKey='data' }` |
|
|
300
|
+
| `clearable` | `boolean` | `false` | Adds an entry that clears a single select |
|
|
301
|
+
| `clearText`, `noResultsText`, `loadingText`, `searchPlaceholder`, `selectedText` | `string` | i18n | `selectedText` supports `{count}` |
|
|
302
|
+
| `placement` | `string` | `'bottom-start'` | Floating UI placement |
|
|
303
|
+
| `size`, `hideDropdown`, `searchNormalize`, `locale` | | | |
|
|
304
|
+
|
|
305
|
+
Events: `update:modelValue`, `change`, `selected` (option(s)), `clear`, `search`, `search-success`, `search-error`, `show`, `shown`, `hide`, `hidden`.
|
|
306
|
+
Exposed: `show()`, `hide()`, `toggle()`, `clear()`, `focus()`.
|
|
596
307
|
|
|
597
308
|
```vue
|
|
598
|
-
<
|
|
599
|
-
<PersianDatePickerInput name="appointment" calendar="datetime" view="datetime" range />
|
|
600
|
-
```
|
|
309
|
+
<ComboboxInput name="priority" :options="[{ id: 1, name: 'Low' }, { id: 2, name: 'High', description: 'Answered first' }]" clearable />
|
|
601
310
|
|
|
602
|
-
|
|
603
|
-
|---|---|---|
|
|
604
|
-
| `name` | String | required |
|
|
605
|
-
| `calendar` | String | `'date'` |
|
|
606
|
-
| `view` | String | `'date'` |
|
|
607
|
-
| `min` | String | — |
|
|
608
|
-
| `max` | String | — |
|
|
609
|
-
| `range` | Boolean | `false` |
|
|
610
|
-
| `locale` | String | `'fa'` |
|
|
611
|
-
| `format` | String | `'jYYYY-jMM-jDD'` |
|
|
612
|
-
| `inputFormat` | String | `null` (falls back to `format`) |
|
|
613
|
-
| `placeholder` | String | `'انتخاب تاریخ'` |
|
|
614
|
-
|
|
615
|
-
Event: `change`. Method exposed: `clear()`.
|
|
616
|
-
|
|
617
|
-
Any option supported by `vue3-persian-datetime-picker` itself (Gregorian mode, time steps, custom themes, etc.) can be used by following [that package's own documentation](https://www.npmjs.com/package/vue3-persian-datetime-picker) and extending the source where needed.
|
|
618
|
-
|
|
619
|
-
### RangeSliderInput
|
|
620
|
-
|
|
621
|
-
Slider input powered by the **external package `svelte-range-slider-pips`**.
|
|
622
|
-
|
|
623
|
-
```vue
|
|
624
|
-
<RangeSliderInput name="budget" :min="0" :max="1000" :step="10" />
|
|
625
|
-
<RangeSliderInput name="price_range" :min="0" :max="1000" range />
|
|
626
|
-
```
|
|
627
|
-
|
|
628
|
-
| Prop | Type | Default |
|
|
629
|
-
|---|---|---|
|
|
630
|
-
| `name` | String | `null` |
|
|
631
|
-
| `modelValue` | Number | `0` |
|
|
632
|
-
| `min` | Number | `1` |
|
|
633
|
-
| `max` | Number | `100` |
|
|
634
|
-
| `step` | Number | `1` |
|
|
635
|
-
| `readonly` | Boolean | `false` |
|
|
636
|
-
| `range` | Boolean | `false` — emits `[min, max]` instead of a single value. |
|
|
637
|
-
| `options` | Object | `{}` | Passed straight through to `svelte-range-slider-pips` — use this for pips formatting, colors, springs, etc. See [that package's docs](https://github.com/simeydotme/svelte-range-slider-pips) for everything it supports. |
|
|
638
|
-
|
|
639
|
-
Events: `update:modelValue`, `change`.
|
|
311
|
+
<ComboboxInput name="customer" :search="{ url: '/api/customers', debounce: 300 }" /> <!-- remote -->
|
|
640
312
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
Hover/click star rating, no external dependency.
|
|
644
|
-
|
|
645
|
-
```vue
|
|
646
|
-
<StarRatingInput name="rating" :max-stars="5" />
|
|
313
|
+
<ComboboxInput name="tags" multiple search-enabled :options="tags" />
|
|
647
314
|
```
|
|
648
315
|
|
|
649
|
-
|
|
650
|
-
|---|---|---|
|
|
651
|
-
| `name` | String | `null` |
|
|
652
|
-
| `modelValue` | Number | `0` |
|
|
653
|
-
| `maxStars` | Number | `5` |
|
|
654
|
-
| `precision` | Number | `2` |
|
|
655
|
-
| `showValue` | Boolean | `true` |
|
|
656
|
-
| `readonly` | Boolean | `false` |
|
|
657
|
-
|
|
658
|
-
Event: `update:modelValue`.
|
|
316
|
+
The remote endpoint receives `?query=…` and must answer with a JSON array (or `{ data: [] }`). The `X-XSRF-TOKEN` cookie is forwarded automatically. Selected options always keep their label even when they are not part of the latest results.
|
|
659
317
|
|
|
660
|
-
###
|
|
318
|
+
### `ChipsInput` (Bootstrap 6 Chips)
|
|
661
319
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
```vue
|
|
665
|
-
<CaptchaInput url="/captcha/default" />
|
|
666
|
-
```
|
|
320
|
+
Value: array of strings.
|
|
667
321
|
|
|
668
|
-
| Prop |
|
|
322
|
+
| Prop | Default | Description |
|
|
669
323
|
|---|---|---|
|
|
670
|
-
| `
|
|
671
|
-
| `
|
|
672
|
-
| `
|
|
673
|
-
| `
|
|
674
|
-
|
|
675
|
-
|
|
324
|
+
| `placeholder` | i18n | |
|
|
325
|
+
| `separator` | `','` | Splits typed / pasted text. `null` disables |
|
|
326
|
+
| `allowDuplicates` | `false` | |
|
|
327
|
+
| `maxChips` | `null` | |
|
|
328
|
+
| `dismissible` | `true` | Remove button on chips |
|
|
329
|
+
| `dismissIcon` | `null` | Custom remove icon markup |
|
|
330
|
+
| `createOnBlur` | `true` | |
|
|
331
|
+
| `validator` | `null` | `(value) => boolean`; rejected values emit `rejected` |
|
|
676
332
|
|
|
677
|
-
|
|
333
|
+
Events: `update:modelValue`, `change`, `add`, `remove`, `select`, `rejected`, `focus`, `blur`. Exposed: `add()`, `remove()`, `clear()`, `focus()`.
|
|
678
334
|
|
|
679
|
-
|
|
335
|
+
### `OTPInput` (Bootstrap 6 OTP)
|
|
680
336
|
|
|
681
|
-
|
|
682
|
-
<EditorInput name="body" :options="{ height: 400 }" />
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
| Prop | Type | Default |
|
|
337
|
+
| Prop | Default | Description |
|
|
686
338
|
|---|---|---|
|
|
687
|
-
| `
|
|
688
|
-
| `
|
|
689
|
-
| `
|
|
690
|
-
| `
|
|
691
|
-
|
|
692
|
-
|
|
339
|
+
| `length` (`maxLength` legacy) | `6` | Number of characters |
|
|
340
|
+
| `type` | `'numeric'` | `numeric` \| `alphanumeric` \| `alpha` |
|
|
341
|
+
| `mask` | `false` | Show `•` |
|
|
342
|
+
| `groups` | `null` | e.g. `[3, 3]` |
|
|
343
|
+
| `separator` | `'·'` | |
|
|
344
|
+
| `autoSubmit` | `false` | Submit the `FormContainer` when the code is complete |
|
|
345
|
+
| `autofocus` | `false` | |
|
|
693
346
|
|
|
694
|
-
|
|
347
|
+
Events: `update:modelValue`, `change`, `input`, `complete`, `focus`, `blur`. Exposed: `focus()`, `clear()`.
|
|
348
|
+
A single real input backs the slots, so SMS / password-manager autofill keeps working.
|
|
695
349
|
|
|
696
|
-
###
|
|
350
|
+
### `RangeInput` (Bootstrap 6 Range)
|
|
697
351
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
```vue
|
|
701
|
-
<LocationInput name="location" />
|
|
702
|
-
```
|
|
703
|
-
|
|
704
|
-
| Prop | Type | Default |
|
|
352
|
+
| Prop | Default | Description |
|
|
705
353
|
|---|---|---|
|
|
706
|
-
| `
|
|
707
|
-
| `
|
|
708
|
-
| `
|
|
709
|
-
| `
|
|
710
|
-
|
|
711
|
-
Event: `update:modelValue`. Stores `{ lat, lng }` into `form[name]`.
|
|
354
|
+
| `min` / `max` / `step` | `0` / `100` / `1` | |
|
|
355
|
+
| `bubble` | `false` | Value bubble above the thumb |
|
|
356
|
+
| `formatter` | `null` | `(value) => string` for bubble and tick labels |
|
|
357
|
+
| `ticks` | `[]` | `[0, 50, 100]` or `[{ value, label }]` |
|
|
712
358
|
|
|
713
|
-
|
|
359
|
+
Value: `number`. Events: `update:modelValue`, `change`, `input`, `focus`, `blur`. `RangeSliderInput` is an alias.
|
|
714
360
|
|
|
715
|
-
###
|
|
361
|
+
### Other inputs
|
|
716
362
|
|
|
717
|
-
|
|
363
|
+
| Component | Highlights |
|
|
364
|
+
|---|---|
|
|
365
|
+
| `StarRatingInput` | `maxStars`, `precision` (steps per star), `showValue`, `clearable`, `value`; pointer + keyboard, RTL aware, `role="slider"` |
|
|
366
|
+
| `CaptchaInput` | `url`, `placeholder`, `refreshOnSubmit`, `imageWidth`, `imageHeight`; event `refresh` |
|
|
367
|
+
| `PersianDatePickerInput` | `calendar`, `view`, `min`, `max`, `range`, `format`, `inputFormat`, `pickerOptions`, `inputClass`; events `open`, `close` |
|
|
368
|
+
| `EditorInput` | TinyMCE: `options`, `height`, `placeholder`, `apiKey`, `tinymceScriptSrc`; event `init` |
|
|
369
|
+
| `LocationInput` | Leaflet: `center`, `zoom`, `height`, `tileUrl`, `attribution`; value `{ lat, lng }`; event `ready` |
|
|
370
|
+
| `FileInput` (`SimpleUploader`) | `multiple`, `endpoint`, `accept`, `headers`, `fieldName`; events `added`, `progress`, `uploaded`, `error`, `removed` |
|
|
371
|
+
| `DropzoneInput` | `multiple`, `url`, `options`, `headers`; events `ready`, `added`, `success`, `error`, `removed`, `complete` |
|
|
372
|
+
| `UppyInput` | `multiple`, `useXHR`, `XHRConfig`, `url`, `config`, `errorHandler`, `showRestrictionCaption`; all Uppy events are re-emitted |
|
|
718
373
|
|
|
719
|
-
|
|
720
|
-
<FileInput name="avatar" endpoint="/upload" />
|
|
721
|
-
<FileInput name="gallery" endpoint="/upload" multiple />
|
|
722
|
-
```
|
|
374
|
+
---
|
|
723
375
|
|
|
724
|
-
|
|
725
|
-
|---|---|---|
|
|
726
|
-
| `name` | String | required |
|
|
727
|
-
| `multiple` | Boolean | `false` |
|
|
728
|
-
| `endpoint` | String | `'/upload'` |
|
|
376
|
+
## Upload endpoint contract
|
|
729
377
|
|
|
730
|
-
|
|
378
|
+
`FileInput`, `DropzoneInput` and `UppyInput` talk to the same endpoint shape:
|
|
731
379
|
|
|
732
|
-
|
|
380
|
+
* `POST {url}` — multipart field `file` → JSON `{ "data": { "path": "tmp/abc.jpg" } }` (or `{ "path": … }`).
|
|
381
|
+
* `DELETE {url}` — body is the stored `path` (`FileInput`, `DropzoneInput`) or the JSON response (`UppyInput`).
|
|
733
382
|
|
|
734
|
-
|
|
383
|
+
`FileInput` stores the `path` string(s); `DropzoneInput` / `UppyInput` store the response object(s). `form.uploading` holds the upload progress, so `SubmitButton` is disabled until uploads finish.
|
|
735
384
|
|
|
736
|
-
|
|
737
|
-
<SimpleUploader name="avatar" endpoint="/upload" />
|
|
738
|
-
```
|
|
385
|
+
## Internationalisation
|
|
739
386
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
| `name` | String | required |
|
|
743
|
-
| `multiple` | Boolean | `false` |
|
|
744
|
-
| `endpoint` | String | `'/upload'` |
|
|
387
|
+
Built-in dictionaries: `en` and `fa`. The default `locale: 'auto'` picks `fa` when the page is RTL (`dir="rtl"` / `lang="fa"`) and `en` otherwise.
|
|
388
|
+
Set the locale globally to avoid any SSR / hydration difference:
|
|
745
389
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
Drag-and-drop uploader powered by the **external package `dropzone`** (Dropzone.js).
|
|
390
|
+
```js
|
|
391
|
+
import { configureI18n } from 'inertia-bootstrap-forms';
|
|
749
392
|
|
|
750
|
-
|
|
751
|
-
|
|
393
|
+
configureI18n({ locale: 'fa' });
|
|
394
|
+
configureI18n({ messages: { fa: { 'select.noResults': 'موردی نیست' } } });
|
|
752
395
|
```
|
|
753
396
|
|
|
754
|
-
|
|
755
|
-
|---|---|---|
|
|
756
|
-
| `name` | String | required |
|
|
757
|
-
| `multiple` | Boolean | `false` |
|
|
758
|
-
| `url` | String | `'/upload'` |
|
|
759
|
-
| `options` | Object | `{}` — merged into Dropzone's own constructor options. |
|
|
397
|
+
Any component with texts also accepts a `locale` prop and explicit text props (`placeholder`, `noResultsText`, …).
|
|
760
398
|
|
|
761
|
-
|
|
399
|
+
## Lifecycle, SSR and cleanup
|
|
762
400
|
|
|
763
|
-
|
|
401
|
+
* Bootstrap plugins are created in `onMounted` through `useBsInstance()` and disposed in `onBeforeUnmount`; re-created automatically when a prop that is only read at construction time changes (`multiple`, `ticks`, `length`, …).
|
|
402
|
+
* Plugins are imported lazily with `import('bootstrap/js/dist/<plugin>.js')` — bundlers code-split them.
|
|
403
|
+
* `useDisposables()` tracks listeners, timers, `AbortController`s, XHRs, Dropzone and Uppy instances and releases them on unmount (also when the component unmounts while a plugin is still loading).
|
|
404
|
+
* Server rendering outputs the static markup; widgets that need the DOM (Combobox, Chips, OTP, Range, Leaflet, date picker, …) start after hydration.
|
|
764
405
|
|
|
765
|
-
|
|
406
|
+
## Composables
|
|
766
407
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
Default usage renders Uppy's built-in `Dropzone` + `FilesList`:
|
|
408
|
+
Build your own fields with the same plumbing:
|
|
770
409
|
|
|
771
410
|
```vue
|
|
772
|
-
<
|
|
773
|
-
|
|
774
|
-
url="/upload"
|
|
775
|
-
multiple
|
|
776
|
-
:config="{ restrictions: { maxFileSize: 5 * 1024 * 1024, allowedFileTypes: ['.jpg', '.png', '.pdf'] } }"
|
|
777
|
-
/>
|
|
778
|
-
```
|
|
779
|
-
|
|
780
|
-
| Prop | Type | Default | Description |
|
|
781
|
-
|---|---|---|---|
|
|
782
|
-
| `name` | String | required | |
|
|
783
|
-
| `multiple` | Boolean | `false` | |
|
|
784
|
-
| `useXHR` | Boolean | `true` | Automatically registers `@uppy/xhr-upload` pointed at `url`. Set `false` if you want to register your own uploader plugin (e.g. AWS S3, Tus) on the exposed `uppy` instance instead. |
|
|
785
|
-
| `XHRConfig` | Object | `{}` | Merged into `@uppy/xhr-upload`'s own config (headers, formData, fieldName, etc.). |
|
|
786
|
-
| `url` | String | `'/upload'` | |
|
|
787
|
-
| `config` | Object | `{}` | Merged into Uppy's core constructor options (`id`, `autoProceed`, `restrictions`, `locale`, etc.). |
|
|
788
|
-
| `errorHandler` | Function | `null` | Custom error display/handling instead of the built-in inline error banner. |
|
|
789
|
-
| `showRestrictionCaption` | Boolean | `true` | Shows an auto-generated Persian caption describing the configured file-size/type/count restrictions. |
|
|
790
|
-
|
|
791
|
-
Events: `update:modelValue`, `file-added`, `file-removed`, `beforeUpload`, `progress`, `upload`, `upload-progress`, `upload-error`, `upload-success`, `upload-pause`, `complete`, `error`, `upload-retry`, `upload-stalled`, `retry-all`, `cancel-all`, `restriction-failed`.
|
|
792
|
-
|
|
793
|
-
Slot: default — scoped with `{ uppy }`, the raw Uppy instance, letting you completely replace the default Dropzone/FilesList UI with your own, or register extra Uppy plugins:
|
|
794
|
-
|
|
795
|
-
```vue
|
|
796
|
-
<UppyInput name="cover" url="/upload" v-slot="{ uppy }">
|
|
797
|
-
<!-- bring your own plugins, e.g. @uppy/dashboard, @uppy/audio, @uppy/drag-drop -->
|
|
798
|
-
</UppyInput>
|
|
799
|
-
```
|
|
800
|
-
|
|
801
|
-
Because the file restriction caption, error banners, and the `Dropzone`/`FilesList` building blocks all come straight from Uppy, refer to [Uppy's documentation](https://uppy.io/docs/uppy/) for every available core/plugin option (restrictions, locales, custom plugins like Dashboard/Audio/Webcam/DragDrop, etc.) — anything supported there can be passed through `config`, `XHRConfig`, or registered manually on the exposed `uppy` instance.
|
|
802
|
-
|
|
803
|
-
### SubmitButton
|
|
411
|
+
<script setup>
|
|
412
|
+
import { fieldProps, useField } from 'inertia-bootstrap-forms';
|
|
804
413
|
|
|
805
|
-
|
|
414
|
+
const props = defineProps({ ...fieldProps, color: String });
|
|
415
|
+
const emit = defineEmits(['update:modelValue', 'change']);
|
|
416
|
+
const { model, inputId, isInvalid, isDisabled, emitChange } = useField(props, emit);
|
|
417
|
+
</script>
|
|
806
418
|
|
|
807
|
-
|
|
808
|
-
<
|
|
809
|
-
|
|
419
|
+
<template>
|
|
420
|
+
<input type="color" v-model="model" :id="inputId" :disabled="isDisabled" :class="{ 'is-invalid': isInvalid }" @change="emitChange(model, $event)" />
|
|
421
|
+
</template>
|
|
810
422
|
```
|
|
811
423
|
|
|
812
|
-
|
|
813
|
-
|---|---|---|
|
|
814
|
-
| `disabled` | Boolean | `false` |
|
|
815
|
-
|
|
816
|
-
Slots:
|
|
817
|
-
- default — scoped with `{ form }`. Defaults to "تایید و ثبت اطلاعات" (Persian for "Confirm and submit").
|
|
818
|
-
- `progress` — scoped, defaults to an upload percentage (`form.uploading`%) while a file upload is in progress.
|
|
819
|
-
|
|
820
|
-
Must be used inside a `<FormContainer>` (injects `form` directly).
|
|
424
|
+
Also exported: `useFormApi()`, `useDisposables()`, `useBsInstance()`, `configureBootstrap()`, `configureI18n()`, `toEnglishDigits()`, `countryCodes`.
|
|
821
425
|
|
|
822
426
|
---
|
|
823
427
|
|
|
824
|
-
##
|
|
428
|
+
## Migrating from v2
|
|
825
429
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
430
|
+
| v2 | v3 |
|
|
431
|
+
|---|---|
|
|
432
|
+
| Bootstrap 5 classes (`.form-check`, `.btn-primary`, …) | Bootstrap 6 (`.check`, `.btn-solid.theme-primary`, …). `.btn-primary` does not exist in Bootstrap 6: `SubmitButton` now renders `btn-solid theme-primary` by itself — use the `variant` / `theme` props instead of `class="btn btn-primary"` |
|
|
433
|
+
| `import 'inertia-bootstrap-forms/dist/style.css'` | **Removed** — no CSS is shipped. Import Bootstrap 6 + third-party CSS yourself |
|
|
434
|
+
| `Select2Input` (choices.js) | `ComboboxInput`; `Select2Input` remains as an alias. The choices.js `config` object is gone — use the documented props. Option field names are set with `valueKey` / `labelKey` (`label` still works as a legacy alias) |
|
|
435
|
+
| Select value always a string | Select value keeps the option's original type (a numeric `id` stays a number) |
|
|
436
|
+
| `RangeSliderInput` (dual thumb, svelte-range-slider-pips) | `RangeInput` (single thumb, Bootstrap 6). Dual range is not supported by Bootstrap 6 |
|
|
437
|
+
| `OTPInput` default length `5` | default `6`; pass `:length="5"` to keep the old size. Fires `complete`; use `auto-submit` instead of `data-auto-submit-length` |
|
|
438
|
+
| `CheckboxInput` `change` event passes the DOM event | `change(value, event)` for every field |
|
|
439
|
+
| `StarRatingInput` stored `"3.5"` strings | stores numbers |
|
|
440
|
+
| `FormContainer` rendered server messages with `v-html` | text by default, `html-messages` to opt in |
|
|
441
|
+
| `FormContainer` emitted extra helper methods inside the data | emits only the form data |
|
|
442
|
+
| `FileInput` needed `window.axios` | uses `XMLHttpRequest` + `fetch`, forwards the `XSRF-TOKEN` cookie |
|
|
443
|
+
| `AmountInput` / `QuantityInput` needed `maska` | built in (Persian digits supported) |
|
|
444
|
+
| `vue3-bootstrap-components`, `choices.js`, `svelte-range-slider-pips` bundled | removed |
|
|
445
|
+
| `name` was `required` | optional — omit it for standalone `v-model` usage |
|
|
446
|
+
| Peer: Bootstrap 5 CSS | Peer: `bootstrap@^6.0.0-alpha.1`, `@floating-ui/dom` |
|
|
447
|
+
|
|
448
|
+
## Notes and known limitations
|
|
449
|
+
|
|
450
|
+
* Bootstrap 6 is currently in **alpha**; class names and plugin APIs may still change before the stable release.
|
|
451
|
+
* Bootstrap 6 Combobox creates a `Menu` on the same toggle element, which makes Bootstrap log *"Bootstrap doesn't allow more than one instance per element"* to the console. This message comes from Bootstrap itself; the component works normally.
|
|
452
|
+
* Bootstrap 6's own Datepicker has no Jalali calendar, so `PersianDatePickerInput` keeps `vue3-persian-datetime-picker`. Theme it with CSS variables in your app.
|
|
453
|
+
* `LocationInput` in RTL pages: Leaflet panes need `left: auto; right: 0` — add that rule in your own CSS if the map renders offset.
|
|
454
|
+
* `AmountInput` / `QuantityInput` handle integers.
|
|
455
|
+
* `data-bs-toggle` attributes on the Combobox / Menu toggles are required for Bootstrap's data-API click handling and are rendered by the components.
|
|
456
|
+
|
|
457
|
+
## Development
|
|
844
458
|
|
|
845
|
-
```
|
|
846
|
-
|
|
459
|
+
```bash
|
|
460
|
+
npm install
|
|
461
|
+
npm test # vitest + jsdom (components, Bootstrap plugin lifecycle, SSR)
|
|
462
|
+
npm run build # vite library build → dist/
|
|
847
463
|
```
|
|
848
464
|
|
|
849
465
|
---
|
|
850
466
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
A few quirks in the current source worth knowing about before you rely on certain props:
|
|
854
|
-
|
|
855
|
-
- **`EmailInput`'s `name` prop:** the component's `props` block doesn't actually declare a `name` prop (it declares an unrelated `FileInput` prop instead), so passing a custom `name="..."` may not bind correctly to the form. If you need a non-default field name for an email field, use `<TextInput name="..." type="email" placeholder="..." />` directly.
|
|
856
|
-
- **`TelInput`'s default `name`:** defaults to `'email'` rather than something like `'mobile'`/`'phone'` — always pass an explicit `name` prop to avoid surprises.
|
|
857
|
-
- **`Select2Input`'s `key` prop:** intended to let you choose which object field is used as an option's value, but it can't actually be set from a template, since Vue reserves `:key` for its own list-rendering/diffing mechanism. In practice, value resolution always falls back to `id` → `name` → `label` → `value` (or the `label` prop, for the display text).
|
|
858
|
-
- **`MultiQuantityInput`'s `totalMax`/`totalMin` props:** declared, but not currently enforced anywhere in the component's logic — treat them as reserved for future use rather than working constraints.
|
|
859
|
-
- Several components are typed `DefineComponent<{}, {}, any>` in `index.d.ts`, i.e. without real prop typing — rely on this README (or the `.vue` source) rather than IDE autocomplete for the full prop list of those components.
|
|
467
|
+
Built and maintained by **[Novin Vision](https://novinvision.com)** · MIT license
|