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/src/UppyInput.vue
CHANGED
|
@@ -1,324 +1,237 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const niceBytesUnits = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
|
242
|
-
|
|
243
|
-
function niceBytes(x) {
|
|
244
|
-
let l = 0, n = parseInt(x, 10) || 0;
|
|
245
|
-
while (n >= 1024 && ++l) {
|
|
246
|
-
n = n / 1024;
|
|
247
|
-
}
|
|
248
|
-
return (n.toFixed(n < 10 && l > 0 ? 1 : 0) + niceBytesUnits[l]);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function buildRestrictionsCaption(restrictions) {
|
|
252
|
-
if (!restrictions) return '';
|
|
253
|
-
|
|
254
|
-
const {
|
|
255
|
-
allowedFileTypes,
|
|
256
|
-
maxFileSize,
|
|
257
|
-
minFileSize,
|
|
258
|
-
maxNumberOfFiles,
|
|
259
|
-
minNumberOfFiles,
|
|
260
|
-
maxTotalFileSize,
|
|
261
|
-
} = restrictions;
|
|
262
|
-
|
|
263
|
-
const parts = [];
|
|
264
|
-
|
|
265
|
-
if (allowedFileTypes && allowedFileTypes.length) {
|
|
266
|
-
const types = allowedFileTypes
|
|
267
|
-
.map(type => type.replace('.', ''))
|
|
268
|
-
.join('، ');
|
|
269
|
-
parts.push(`فقط فایلهای ${types}`);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (maxNumberOfFiles && maxNumberOfFiles > 1) {
|
|
273
|
-
parts.push(`امکان انتخاب حداکثر ${maxNumberOfFiles} فایل`);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (minNumberOfFiles) {
|
|
277
|
-
parts.push(`حداقل ${minNumberOfFiles} فایل نیاز است`);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (maxFileSize) {
|
|
281
|
-
parts.push(`با حداکثر حجم ${niceBytes(maxFileSize)} برای هر فایل`);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (minFileSize) {
|
|
285
|
-
parts.push(`با حداقل حجم ${niceBytes(minFileSize)} برای هر فایل`);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (maxTotalFileSize) {
|
|
289
|
-
parts.push(`و مجموع حجم کل حداکثر ${niceBytes(maxTotalFileSize)}`);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (!parts.length) return '';
|
|
293
|
-
|
|
294
|
-
return parts.join('، ') + ' مجاز است.';
|
|
295
|
-
}
|
|
296
|
-
</script>
|
|
297
|
-
|
|
298
|
-
<style>
|
|
299
|
-
.uppy-reset p {
|
|
300
|
-
margin-bottom: 0;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
.uppy-file-lists > ul {
|
|
304
|
-
list-style: none;
|
|
305
|
-
padding: 0;
|
|
306
|
-
margin: 0;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
.uppy-input-area .uppy-error {
|
|
310
|
-
display: block;
|
|
311
|
-
padding: 5px;
|
|
312
|
-
background-color: #f8d7da;
|
|
313
|
-
border: 1px solid #f5c2c7;
|
|
314
|
-
color: #842029;
|
|
315
|
-
text-align: center;
|
|
316
|
-
border-radius: 0.5rem;
|
|
317
|
-
margin-bottom: 5px;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
.uppy-input-area .uppy-input-area--caption {
|
|
321
|
-
text-align: center;
|
|
322
|
-
margin: 5px 0;
|
|
323
|
-
}
|
|
324
|
-
</style>
|
|
1
|
+
<script>
|
|
2
|
+
import { computed, defineAsyncComponent, defineComponent, onMounted, ref, shallowRef, watch } from 'vue';
|
|
3
|
+
import { fieldProps, useField } from './composables/useField.js';
|
|
4
|
+
import { useDisposables } from './composables/useDisposables.js';
|
|
5
|
+
import { useI18n } from './i18n.js';
|
|
6
|
+
import { laravelHeaders } from './utils/http.js';
|
|
7
|
+
|
|
8
|
+
const UppyContextProvider = defineAsyncComponent(() => import('@uppy/vue').then((m) => m.UppyContextProvider));
|
|
9
|
+
const FilesList = defineAsyncComponent(() => import('@uppy/vue').then((m) => m.FilesList));
|
|
10
|
+
const UppyDropzone = defineAsyncComponent(() => import('@uppy/vue').then((m) => m.Dropzone));
|
|
11
|
+
|
|
12
|
+
const UNITS = ['bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
13
|
+
function niceBytes(x) {
|
|
14
|
+
let l = 0;
|
|
15
|
+
let n = parseInt(x, 10) || 0;
|
|
16
|
+
while (n >= 1024 && l < UNITS.length - 1) {
|
|
17
|
+
n /= 1024;
|
|
18
|
+
l++;
|
|
19
|
+
}
|
|
20
|
+
return `${n.toFixed(n < 10 && l > 0 ? 1 : 0)}${UNITS[l]}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Uppy based uploader. Import `@uppy/vue/css/style.css` in your application.
|
|
25
|
+
* Uppy is created after mount and destroyed on unmount.
|
|
26
|
+
*/
|
|
27
|
+
export default defineComponent({
|
|
28
|
+
name: 'UppyInput',
|
|
29
|
+
components: { UppyContextProvider, FilesList, UppyDropzone },
|
|
30
|
+
emits: [
|
|
31
|
+
'update:modelValue', 'change', 'ready', 'file-added', 'file-removed', 'beforeUpload', 'progress', 'upload',
|
|
32
|
+
'upload-progress', 'upload-error', 'upload-success', 'upload-pause', 'complete', 'error', 'upload-retry',
|
|
33
|
+
'upload-stalled', 'retry-all', 'cancel-all', 'restriction-failed',
|
|
34
|
+
],
|
|
35
|
+
props: {
|
|
36
|
+
...fieldProps,
|
|
37
|
+
multiple: { type: Boolean, default: false },
|
|
38
|
+
useXHR: { type: Boolean, default: true },
|
|
39
|
+
XHRConfig: { type: Object, default: () => ({}) },
|
|
40
|
+
url: { type: String, default: '/upload' },
|
|
41
|
+
/** Uppy core options. */
|
|
42
|
+
config: { type: Object, default: () => ({}) },
|
|
43
|
+
errorHandler: { type: Function, default: null },
|
|
44
|
+
showRestrictionCaption: { type: Boolean, default: true },
|
|
45
|
+
locale: { type: String, default: null },
|
|
46
|
+
},
|
|
47
|
+
setup(props, { emit, expose }) {
|
|
48
|
+
const field = useField(props, emit);
|
|
49
|
+
const { t } = useI18n(() => props.locale);
|
|
50
|
+
const d = useDisposables();
|
|
51
|
+
|
|
52
|
+
const uppy = shallowRef(null);
|
|
53
|
+
const area = ref(null);
|
|
54
|
+
const caption = ref(null);
|
|
55
|
+
const errorMessage = ref(null);
|
|
56
|
+
const uploadingFiles = ref(0);
|
|
57
|
+
let resetting = false;
|
|
58
|
+
let unmounting = false;
|
|
59
|
+
|
|
60
|
+
const setUploading = (v) => {
|
|
61
|
+
if (field.form.value) field.form.value.uploading = v;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function showError(message) {
|
|
65
|
+
if (props.errorHandler) return props.errorHandler(message);
|
|
66
|
+
if (!message) return;
|
|
67
|
+
errorMessage.value = message;
|
|
68
|
+
d.timeout(() => (errorMessage.value = null), 3000);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildCaption(r) {
|
|
72
|
+
if (!r) return '';
|
|
73
|
+
const parts = [];
|
|
74
|
+
|
|
75
|
+
if (r.allowedFileTypes?.length) {
|
|
76
|
+
parts.push(t('uppy.types', { types: r.allowedFileTypes.map((x) => x.replace('.', '')).join(', ') }));
|
|
77
|
+
}
|
|
78
|
+
if (r.maxNumberOfFiles && r.maxNumberOfFiles > 1) parts.push(t('uppy.maxFiles', { count: r.maxNumberOfFiles }));
|
|
79
|
+
if (r.minNumberOfFiles) parts.push(t('uppy.minFiles', { count: r.minNumberOfFiles }));
|
|
80
|
+
if (r.maxFileSize) parts.push(t('uppy.maxSize', { size: niceBytes(r.maxFileSize) }));
|
|
81
|
+
if (r.minFileSize) parts.push(t('uppy.minSize', { size: niceBytes(r.minFileSize) }));
|
|
82
|
+
if (r.maxTotalFileSize) parts.push(t('uppy.maxTotal', { size: niceBytes(r.maxTotalFileSize) }));
|
|
83
|
+
|
|
84
|
+
return parts.length ? `${parts.join(', ')} ${t('uppy.suffix')}` : '';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
onMounted(async () => {
|
|
88
|
+
const { default: Uppy } = await import('@uppy/core');
|
|
89
|
+
if (d.isDisposed()) return;
|
|
90
|
+
|
|
91
|
+
const instance = new Uppy({
|
|
92
|
+
id: props.name ?? 'uppy',
|
|
93
|
+
autoProceed: true,
|
|
94
|
+
...props.config,
|
|
95
|
+
restrictions: {
|
|
96
|
+
maxNumberOfFiles: props.multiple ? null : 1,
|
|
97
|
+
...(props.config?.restrictions || {}),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
instance.on('upload', () => {
|
|
102
|
+
uploadingFiles.value = instance.getFiles().filter((f) => !f.progress.uploadComplete).length;
|
|
103
|
+
emit('upload');
|
|
104
|
+
});
|
|
105
|
+
instance.on('before-upload', (files) => emit('beforeUpload', files));
|
|
106
|
+
|
|
107
|
+
instance.on('upload-success', (file, response) => {
|
|
108
|
+
uploadingFiles.value = Math.max(0, uploadingFiles.value - 1);
|
|
109
|
+
const result = response.body ?? response;
|
|
110
|
+
|
|
111
|
+
if (props.multiple) {
|
|
112
|
+
const current = Array.isArray(field.model.value) ? field.model.value : [];
|
|
113
|
+
field.model.value = [...current, result];
|
|
114
|
+
} else {
|
|
115
|
+
field.model.value = result;
|
|
116
|
+
}
|
|
117
|
+
field.emitChange(field.model.value);
|
|
118
|
+
emit('upload-success', file, response);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
instance.on('file-added', (file) => emit('file-added', file));
|
|
122
|
+
|
|
123
|
+
instance.on('file-removed', (file) => {
|
|
124
|
+
const serverResponse = file.response?.body ?? file.response;
|
|
125
|
+
|
|
126
|
+
if (props.multiple && Array.isArray(field.model.value)) {
|
|
127
|
+
field.model.value = field.model.value.filter((item) => JSON.stringify(item) !== JSON.stringify(serverResponse));
|
|
128
|
+
} else {
|
|
129
|
+
field.model.value = null;
|
|
130
|
+
}
|
|
131
|
+
field.emitChange(field.model.value);
|
|
132
|
+
|
|
133
|
+
if (serverResponse && !resetting && !unmounting) {
|
|
134
|
+
fetch(props.url, {
|
|
135
|
+
method: 'DELETE',
|
|
136
|
+
headers: laravelHeaders({ 'Content-Type': 'application/json' }),
|
|
137
|
+
credentials: 'same-origin',
|
|
138
|
+
body: JSON.stringify(serverResponse),
|
|
139
|
+
}).catch(() => {});
|
|
140
|
+
}
|
|
141
|
+
emit('file-removed', file);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
instance.on('progress', (progress) => {
|
|
145
|
+
if (progress <= 0) setUploading(null);
|
|
146
|
+
else if (progress >= 100) setUploading(uploadingFiles.value > 0 ? 99 : null); // still waiting for the server
|
|
147
|
+
else setUploading(progress);
|
|
148
|
+
emit('progress', progress);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
instance.on('upload-progress', (file, progress) => emit('upload-progress', file, progress));
|
|
152
|
+
instance.on('upload-pause', (file, progress) => emit('upload-pause', file, progress));
|
|
153
|
+
instance.on('cancel-all', () => emit('cancel-all'));
|
|
154
|
+
instance.on('retry-all', () => emit('retry-all'));
|
|
155
|
+
instance.on('upload-stalled', (error, files) => emit('upload-stalled', error, files));
|
|
156
|
+
instance.on('upload-retry', (file) => emit('upload-retry', file));
|
|
157
|
+
instance.on('complete', (result) => {
|
|
158
|
+
uploadingFiles.value = 0;
|
|
159
|
+
setUploading(null);
|
|
160
|
+
emit('complete', result);
|
|
161
|
+
});
|
|
162
|
+
instance.on('error', (error) => emit('error', error));
|
|
163
|
+
|
|
164
|
+
instance.on('upload-error', (file, error, response) => {
|
|
165
|
+
uploadingFiles.value = Math.max(0, uploadingFiles.value - 1);
|
|
166
|
+
let message = error;
|
|
167
|
+
try {
|
|
168
|
+
message = JSON.parse(response.response)?.message ?? error;
|
|
169
|
+
} catch (e) { /* not json */ }
|
|
170
|
+
showError(message?.message ?? message);
|
|
171
|
+
emit('upload-error', file, error, response, message);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
instance.on('restriction-failed', (file, error) => {
|
|
175
|
+
showError(error.message);
|
|
176
|
+
emit('restriction-failed', file, error);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (props.useXHR) {
|
|
180
|
+
const { default: XHR } = await import('@uppy/xhr-upload');
|
|
181
|
+
if (d.isDisposed()) {
|
|
182
|
+
instance.destroy?.();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
instance.use(XHR, {
|
|
186
|
+
method: 'POST',
|
|
187
|
+
endpoint: props.url,
|
|
188
|
+
headers: laravelHeaders(),
|
|
189
|
+
...props.XHRConfig,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
d.add(() => {
|
|
194
|
+
unmounting = true;
|
|
195
|
+
setUploading(null);
|
|
196
|
+
try { instance.destroy?.(); } catch (e) { /* noop */ }
|
|
197
|
+
uppy.value = null;
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
uppy.value = instance;
|
|
201
|
+
if (props.showRestrictionCaption) caption.value = buildCaption(instance.opts?.restrictions);
|
|
202
|
+
emit('ready', instance);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Form was submitted successfully → forget the files without touching the server
|
|
206
|
+
watch(
|
|
207
|
+
() => field.form.value?.wasSuccessful,
|
|
208
|
+
(now, before) => {
|
|
209
|
+
if (now === true && before === false && uppy.value) {
|
|
210
|
+
resetting = true;
|
|
211
|
+
uppy.value.cancelAll();
|
|
212
|
+
resetting = false;
|
|
213
|
+
field.model.value = props.multiple ? [] : null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
expose({ uppy: () => uppy.value });
|
|
219
|
+
|
|
220
|
+
return { ...field, uppy, area, caption, errorMessage };
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
</script>
|
|
224
|
+
|
|
225
|
+
<template>
|
|
226
|
+
<div v-if="uppy" ref="area" class="uppy-input-area" :id="inputId">
|
|
227
|
+
<div v-if="errorMessage" class="alert theme-danger text-center py-1 mb-2" role="alert">{{ errorMessage }}</div>
|
|
228
|
+
<UppyContextProvider :name="'uu' + (name ?? 'uppy')" :uppy="uppy">
|
|
229
|
+
<slot :uppy="uppy">
|
|
230
|
+
<FilesList class="uppy-file-lists" />
|
|
231
|
+
<UppyDropzone class="uppy-drop-zone" />
|
|
232
|
+
</slot>
|
|
233
|
+
</UppyContextProvider>
|
|
234
|
+
<div v-if="caption" class="uppy-input-area--caption form-text text-center fst-italic">{{ caption }}</div>
|
|
235
|
+
<div v-if="isInvalid" class="invalid-feedback d-block">{{ error }}</div>
|
|
236
|
+
</div>
|
|
237
|
+
</template>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { onBeforeUnmount, onMounted, shallowRef } from 'vue';
|
|
2
|
+
import { loadBootstrap } from '../utils/bootstrap.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a Bootstrap 6 plugin instance after the component is mounted and
|
|
6
|
+
* disposes it when the component unmounts (or when it is re-created).
|
|
7
|
+
*
|
|
8
|
+
* @param {string} name Plugin name: Combobox | Chips | OtpInput | Range
|
|
9
|
+
* @param {Function} getElement () => HTMLElement (usually reads a template ref)
|
|
10
|
+
* @param {Function} getConfig () => object (plugin options)
|
|
11
|
+
* @param {object} hooks { onCreate(instance, el), onDestroy(instance, el) }
|
|
12
|
+
*/
|
|
13
|
+
export function useBsInstance(name, getElement, getConfig, hooks = {}) {
|
|
14
|
+
const instance = shallowRef(null);
|
|
15
|
+
let sequence = 0;
|
|
16
|
+
let unmounted = false;
|
|
17
|
+
let currentEl = null;
|
|
18
|
+
|
|
19
|
+
function destroy() {
|
|
20
|
+
sequence++;
|
|
21
|
+
const inst = instance.value;
|
|
22
|
+
instance.value = null;
|
|
23
|
+
|
|
24
|
+
if (inst) {
|
|
25
|
+
try { hooks.onDestroy?.(inst, currentEl); } catch (e) { /* noop */ }
|
|
26
|
+
try { inst.dispose(); } catch (e) { /* noop */ }
|
|
27
|
+
}
|
|
28
|
+
currentEl = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function create() {
|
|
32
|
+
destroy();
|
|
33
|
+
const ticket = sequence;
|
|
34
|
+
const Plugin = await loadBootstrap(name);
|
|
35
|
+
|
|
36
|
+
// The component may have been unmounted / re-created while the plugin was loading
|
|
37
|
+
if (unmounted || ticket !== sequence) return null;
|
|
38
|
+
|
|
39
|
+
const el = getElement();
|
|
40
|
+
if (!el) return null;
|
|
41
|
+
|
|
42
|
+
// A data-api click can already have created an instance for this element
|
|
43
|
+
Plugin.getInstance?.(el)?.dispose();
|
|
44
|
+
|
|
45
|
+
currentEl = el;
|
|
46
|
+
const inst = new Plugin(el, getConfig());
|
|
47
|
+
instance.value = inst;
|
|
48
|
+
hooks.onCreate?.(inst, el);
|
|
49
|
+
return inst;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
onMounted(() => {
|
|
53
|
+
unmounted = false;
|
|
54
|
+
create().catch((error) => {
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.error(`[inertia-bootstrap-forms] Could not start Bootstrap ${name}.`, error);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
onBeforeUnmount(() => {
|
|
61
|
+
unmounted = true;
|
|
62
|
+
destroy();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return { instance, create, destroy };
|
|
66
|
+
}
|