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
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { onMounted, watch } from 'vue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared behaviour of checkbox / radio / switch style fields:
|
|
5
|
+
* - the `checked` prop initialises the model when it is still empty
|
|
6
|
+
* - the `indeterminate` DOM property is kept in sync
|
|
7
|
+
*/
|
|
8
|
+
export function useChoice(props, field, elRef, { type = () => props.type } = {}) {
|
|
9
|
+
function applyInitialChecked() {
|
|
10
|
+
if (!props.checked) return;
|
|
11
|
+
const current = field.model.value;
|
|
12
|
+
|
|
13
|
+
if (type() === 'radio') {
|
|
14
|
+
if (current === null || current === undefined || current === '') field.model.value = props.value;
|
|
15
|
+
} else if (Array.isArray(current)) {
|
|
16
|
+
if (!current.includes(props.value)) field.model.value = [...current, props.value];
|
|
17
|
+
} else if (!current) {
|
|
18
|
+
field.model.value = props.trueValue !== undefined ? props.trueValue : true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function syncIndeterminate() {
|
|
23
|
+
if (elRef.value) elRef.value.indeterminate = !!props.indeterminate;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
onMounted(() => {
|
|
27
|
+
applyInitialChecked();
|
|
28
|
+
syncIndeterminate();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
watch(() => props.indeterminate, syncIndeterminate);
|
|
32
|
+
|
|
33
|
+
return { applyInitialChecked };
|
|
34
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { onBeforeUnmount } from 'vue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tracks every resource a component acquires after mount (DOM listeners,
|
|
5
|
+
* timers, abort controllers, third-party instances) and releases all of them
|
|
6
|
+
* in reverse order when the component unmounts.
|
|
7
|
+
*/
|
|
8
|
+
export function useDisposables() {
|
|
9
|
+
const tasks = [];
|
|
10
|
+
let disposed = false;
|
|
11
|
+
|
|
12
|
+
const add = (fn) => {
|
|
13
|
+
if (disposed) {
|
|
14
|
+
try { fn(); } catch (e) { /* noop */ }
|
|
15
|
+
return () => {};
|
|
16
|
+
}
|
|
17
|
+
tasks.push(fn);
|
|
18
|
+
return () => {
|
|
19
|
+
const i = tasks.indexOf(fn);
|
|
20
|
+
if (i !== -1) tasks.splice(i, 1);
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const listen = (target, type, handler, options) => {
|
|
25
|
+
if (!target) return () => {};
|
|
26
|
+
target.addEventListener(type, handler, options);
|
|
27
|
+
return add(() => target.removeEventListener(type, handler, options));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const timeout = (fn, ms) => {
|
|
31
|
+
const id = setTimeout(fn, ms);
|
|
32
|
+
add(() => clearTimeout(id));
|
|
33
|
+
return id;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const abortable = () => {
|
|
37
|
+
const controller = new AbortController();
|
|
38
|
+
add(() => controller.abort());
|
|
39
|
+
return controller;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const dispose = () => {
|
|
43
|
+
disposed = true;
|
|
44
|
+
while (tasks.length) {
|
|
45
|
+
try { tasks.pop()(); } catch (e) { /* keep releasing the rest */ }
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
onBeforeUnmount(dispose);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
add,
|
|
53
|
+
listen,
|
|
54
|
+
timeout,
|
|
55
|
+
abortable,
|
|
56
|
+
dispose,
|
|
57
|
+
isDisposed: () => disposed,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { computed, inject, ref, watch } from 'vue';
|
|
2
|
+
import * as Vue from 'vue';
|
|
3
|
+
|
|
4
|
+
let fallbackUid = 0;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Props shared by every field component.
|
|
8
|
+
* Spread them into a component: `props: { ...fieldProps, ... }`.
|
|
9
|
+
*/
|
|
10
|
+
export const fieldProps = {
|
|
11
|
+
/** Key of the field inside the FormContainer's v-model object. */
|
|
12
|
+
name: { type: String, default: null },
|
|
13
|
+
/** Standalone v-model (only used when the field is NOT inside a FormContainer). */
|
|
14
|
+
modelValue: { type: null, default: undefined },
|
|
15
|
+
id: { type: String, default: null },
|
|
16
|
+
disabled: { type: Boolean, default: false },
|
|
17
|
+
readonly: { type: Boolean, default: false },
|
|
18
|
+
required: { type: Boolean, default: false },
|
|
19
|
+
/** Force the invalid state. By default it follows the server-side errors of the form. */
|
|
20
|
+
invalid: { type: Boolean, default: null },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Connects a field to the parent <FormContainer>.
|
|
25
|
+
*
|
|
26
|
+
* The value of every field lives in the form object that FormContainer exposes
|
|
27
|
+
* through its own `v-model`. Fields never need a v-model of their own; when a
|
|
28
|
+
* field is used outside a FormContainer it falls back to a regular
|
|
29
|
+
* `v-model` / `update:modelValue` pair.
|
|
30
|
+
*/
|
|
31
|
+
export function useField(props, emit, options = {}) {
|
|
32
|
+
const formRef = inject('form', null);
|
|
33
|
+
const groupRef = inject('group', null);
|
|
34
|
+
const fieldCtx = inject('formField', null);
|
|
35
|
+
const formApi = inject('formApi', null);
|
|
36
|
+
|
|
37
|
+
const form = computed(() => formRef?.value ?? null);
|
|
38
|
+
const group = computed(() => (groupRef?.value?.name ? groupRef.value : null));
|
|
39
|
+
const standalone = computed(() => !form.value);
|
|
40
|
+
|
|
41
|
+
const local = ref(
|
|
42
|
+
props.modelValue === undefined ? (options.defaultValue ?? null) : props.modelValue
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
watch(
|
|
46
|
+
() => props.modelValue,
|
|
47
|
+
(v) => {
|
|
48
|
+
if (standalone.value && v !== undefined) local.value = v;
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const value = computed({
|
|
53
|
+
get() {
|
|
54
|
+
if (standalone.value) return local.value;
|
|
55
|
+
if (group.value) return group.value.getData(props.name);
|
|
56
|
+
return form.value[props.name];
|
|
57
|
+
},
|
|
58
|
+
set(next) {
|
|
59
|
+
if (standalone.value) local.value = next;
|
|
60
|
+
else if (group.value) group.value.setData(props.name, next);
|
|
61
|
+
else form.value[props.name] = next;
|
|
62
|
+
|
|
63
|
+
emit('update:modelValue', next);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const errorKeys = computed(() => {
|
|
68
|
+
const keys = [];
|
|
69
|
+
if (group.value) {
|
|
70
|
+
keys.push(`${group.value.name}.${group.value.index}.${props.name}`);
|
|
71
|
+
keys.push(`${group.value.name}.${props.name}`);
|
|
72
|
+
}
|
|
73
|
+
keys.push(props.name);
|
|
74
|
+
return keys;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const error = computed(() => {
|
|
78
|
+
const errors = form.value?.errors;
|
|
79
|
+
if (!errors) return null;
|
|
80
|
+
for (const key of errorKeys.value) {
|
|
81
|
+
if (key && errors[key] !== undefined) return errors[key];
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const isInvalid = computed(() => props.invalid === true || (props.invalid === null && error.value !== null));
|
|
87
|
+
const isDisabled = computed(() => props.disabled || !!form.value?.processing);
|
|
88
|
+
|
|
89
|
+
const uid = Vue.useId ? Vue.useId() : `ibf-${++fallbackUid}`;
|
|
90
|
+
|
|
91
|
+
const inputId = computed(() => {
|
|
92
|
+
if (props.id) return props.id;
|
|
93
|
+
if (fieldCtx?.id && !options.valued) return fieldCtx.id;
|
|
94
|
+
|
|
95
|
+
if (form.value?.getID && props.name) {
|
|
96
|
+
return form.value.getID({
|
|
97
|
+
name: props.name,
|
|
98
|
+
value: options.valued ? options.valued() : undefined,
|
|
99
|
+
group: group.value,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return props.name ? `${props.name}-${uid}` : uid;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const emitChange = (next, event) => emit('change', next, event);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
form,
|
|
110
|
+
group,
|
|
111
|
+
standalone,
|
|
112
|
+
model: value,
|
|
113
|
+
error,
|
|
114
|
+
isInvalid,
|
|
115
|
+
isDisabled,
|
|
116
|
+
inputId,
|
|
117
|
+
emitChange,
|
|
118
|
+
formApi,
|
|
119
|
+
fieldCtx,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { inject } from 'vue';
|
|
2
|
+
|
|
3
|
+
/** Programmatic access to the parent FormContainer: submit(), reset(), form. */
|
|
4
|
+
export function useFormApi() {
|
|
5
|
+
const form = inject('form', null);
|
|
6
|
+
const api = inject('formApi', null);
|
|
7
|
+
return {
|
|
8
|
+
form: form,
|
|
9
|
+
submit: (...args) => api?.submit?.(...args),
|
|
10
|
+
reset: () => api?.reset?.(),
|
|
11
|
+
};
|
|
12
|
+
}
|
package/src/i18n.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { computed, reactive } from 'vue';
|
|
2
|
+
|
|
3
|
+
const state = reactive({
|
|
4
|
+
locale: 'auto',
|
|
5
|
+
messages: {
|
|
6
|
+
en: {
|
|
7
|
+
'select.placeholder': 'Select an option…',
|
|
8
|
+
'select.search': 'Type to search…',
|
|
9
|
+
'select.noResults': 'No results found',
|
|
10
|
+
'select.loading': 'Searching…',
|
|
11
|
+
'select.selectedCount': '{count} selected',
|
|
12
|
+
'select.clear': '— Clear selection —',
|
|
13
|
+
'select.error': 'Search failed',
|
|
14
|
+
'submit.default': 'Submit',
|
|
15
|
+
'chips.placeholder': 'Type and press Enter…',
|
|
16
|
+
'chips.remove': 'Remove',
|
|
17
|
+
'email.placeholder': 'Enter your email',
|
|
18
|
+
'password.placeholder': 'Enter your password',
|
|
19
|
+
'password.show': 'Show password',
|
|
20
|
+
'password.hide': 'Hide password',
|
|
21
|
+
'mobile.placeholder': 'Enter your mobile number',
|
|
22
|
+
'amount.placeholder': 'Enter a number',
|
|
23
|
+
'amount.unit': 'pcs',
|
|
24
|
+
'quantity.unit': 'pcs',
|
|
25
|
+
'quantity.increase': 'Increase',
|
|
26
|
+
'quantity.decrease': 'Decrease',
|
|
27
|
+
'captcha.placeholder': 'Enter the code shown',
|
|
28
|
+
'captcha.refresh': 'Refresh code',
|
|
29
|
+
'captcha.alt': 'captcha',
|
|
30
|
+
'file.retry': 'Retry',
|
|
31
|
+
'file.remove': 'Remove',
|
|
32
|
+
'upload.failed': 'Upload failed',
|
|
33
|
+
'uppy.types': 'Only {types} files',
|
|
34
|
+
'uppy.maxFiles': 'up to {count} files',
|
|
35
|
+
'uppy.minFiles': 'at least {count} files required',
|
|
36
|
+
'uppy.maxSize': 'max {size} per file',
|
|
37
|
+
'uppy.minSize': 'min {size} per file',
|
|
38
|
+
'uppy.maxTotal': 'max {size} in total',
|
|
39
|
+
'uppy.suffix': 'are allowed.',
|
|
40
|
+
},
|
|
41
|
+
fa: {
|
|
42
|
+
'select.placeholder': 'انتخاب کنید…',
|
|
43
|
+
'select.search': 'برای جستجو تایپ کنید…',
|
|
44
|
+
'select.noResults': 'نتیجهای یافت نشد',
|
|
45
|
+
'select.loading': 'در حال جستجو…',
|
|
46
|
+
'select.selectedCount': '{count} مورد انتخاب شده',
|
|
47
|
+
'select.clear': '— پاک کردن انتخاب —',
|
|
48
|
+
'select.error': 'جستجو با خطا مواجه شد',
|
|
49
|
+
'submit.default': 'تایید و ثبت اطلاعات',
|
|
50
|
+
'chips.placeholder': 'تایپ کنید و Enter بزنید…',
|
|
51
|
+
'chips.remove': 'حذف',
|
|
52
|
+
'email.placeholder': 'ایمیل خود را وارد کنید',
|
|
53
|
+
'password.placeholder': 'گذرواژه خود را وارد کنید',
|
|
54
|
+
'password.show': 'نمایش گذرواژه',
|
|
55
|
+
'password.hide': 'مخفی کردن گذرواژه',
|
|
56
|
+
'mobile.placeholder': 'موبایل خود را وارد کنید',
|
|
57
|
+
'amount.placeholder': 'عدد را وارد کنید',
|
|
58
|
+
'amount.unit': 'عدد',
|
|
59
|
+
'quantity.unit': 'عدد',
|
|
60
|
+
'quantity.increase': 'افزایش',
|
|
61
|
+
'quantity.decrease': 'کاهش',
|
|
62
|
+
'captcha.placeholder': 'کد امنیتی روبرو را وارد کنید',
|
|
63
|
+
'captcha.refresh': 'کد جدید',
|
|
64
|
+
'captcha.alt': 'کد امنیتی',
|
|
65
|
+
'file.retry': 'تلاش مجدد',
|
|
66
|
+
'file.remove': 'حذف',
|
|
67
|
+
'upload.failed': 'خطا در آپلود',
|
|
68
|
+
'uppy.types': 'فقط فایلهای {types}',
|
|
69
|
+
'uppy.maxFiles': 'امکان انتخاب حداکثر {count} فایل',
|
|
70
|
+
'uppy.minFiles': 'حداقل {count} فایل نیاز است',
|
|
71
|
+
'uppy.maxSize': 'با حداکثر حجم {size} برای هر فایل',
|
|
72
|
+
'uppy.minSize': 'با حداقل حجم {size} برای هر فایل',
|
|
73
|
+
'uppy.maxTotal': 'و مجموع حجم کل حداکثر {size}',
|
|
74
|
+
'uppy.suffix': 'مجاز است.',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Global i18n configuration.
|
|
81
|
+
* configureI18n({ locale: 'fa' })
|
|
82
|
+
* configureI18n({ messages: { fa: { 'select.noResults': '...' } } })
|
|
83
|
+
*/
|
|
84
|
+
export function configureI18n({ locale, messages } = {}) {
|
|
85
|
+
if (locale) state.locale = locale;
|
|
86
|
+
if (messages) {
|
|
87
|
+
Object.keys(messages).forEach((lang) => {
|
|
88
|
+
state.messages[lang] = { ...(state.messages[lang] || {}), ...messages[lang] };
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function detectLocale() {
|
|
94
|
+
if (typeof document !== 'undefined') {
|
|
95
|
+
const root = document.documentElement;
|
|
96
|
+
if (root?.dir === 'rtl' || root?.getAttribute('lang')?.startsWith('fa')) return 'fa';
|
|
97
|
+
}
|
|
98
|
+
return 'en';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* `localeRef` may be a prop getter. Returns `t(key, vars)`.
|
|
103
|
+
*/
|
|
104
|
+
export function useI18n(getLocale = () => null) {
|
|
105
|
+
const locale = computed(() => {
|
|
106
|
+
const requested = getLocale() || state.locale;
|
|
107
|
+
return requested === 'auto' ? detectLocale() : requested;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const t = (key, vars = {}) => {
|
|
111
|
+
const dict = state.messages[locale.value] || state.messages.en;
|
|
112
|
+
let text = dict[key] ?? state.messages.en[key] ?? key;
|
|
113
|
+
Object.keys(vars).forEach((k) => {
|
|
114
|
+
text = text.replace(new RegExp(`\\{${k}\\}`, 'g'), vars[k]);
|
|
115
|
+
});
|
|
116
|
+
return text;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return { t, locale };
|
|
120
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,61 +1,142 @@
|
|
|
1
|
-
import AmountInput from
|
|
2
|
-
import CaptchaInput from
|
|
3
|
-
import CheckboxButtonInput from
|
|
4
|
-
import CheckboxInput from
|
|
5
|
-
import CheckboxToggle from
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import EmailInput from
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
-
import
|
|
27
|
-
import
|
|
28
|
-
import
|
|
29
|
-
import
|
|
1
|
+
import AmountInput from './AmountInput.vue';
|
|
2
|
+
import CaptchaInput from './CaptchaInput.vue';
|
|
3
|
+
import CheckboxButtonInput from './CheckboxButtonInput.vue';
|
|
4
|
+
import CheckboxInput from './CheckboxInput.vue';
|
|
5
|
+
import CheckboxToggle from './CheckboxToggle.vue';
|
|
6
|
+
import ChipsInput from './ChipsInput.vue';
|
|
7
|
+
import ComboboxInput from './ComboboxInput.vue';
|
|
8
|
+
import DropzoneInput from './DropzoneInput.vue';
|
|
9
|
+
import EditorInput from './EditorInput.vue';
|
|
10
|
+
import EmailInput from './EmailInput.vue';
|
|
11
|
+
import FileInput from './FileInput.vue';
|
|
12
|
+
import FormAdornInput from './FormAdornInput.vue';
|
|
13
|
+
import FormContainer from './FormContainer.vue';
|
|
14
|
+
import FormField from './FormField.vue';
|
|
15
|
+
import FormLabel from './FormLabel.vue';
|
|
16
|
+
import GroupControl from './GroupControl.vue';
|
|
17
|
+
import LocationInput from './LocationInput.vue';
|
|
18
|
+
import MobileInput from './MobileInput.vue';
|
|
19
|
+
import MultiQuantityInput from './MultiQuantityInput.vue';
|
|
20
|
+
import OTPInput from './OTPInput.vue';
|
|
21
|
+
import PasswordInput from './PasswordInput.vue';
|
|
22
|
+
import PersianDatePickerInput from './PersianDatePickerInput.vue';
|
|
23
|
+
import QuantityInput from './QuantityInput.vue';
|
|
24
|
+
import RangeInput from './RangeInput.vue';
|
|
25
|
+
import SimpleUploader from './SimpleUploader.vue';
|
|
26
|
+
import StarRatingInput from './StarRatingInput.vue';
|
|
27
|
+
import SubmitButton from './SubmitButton.vue';
|
|
28
|
+
import SwitchInput from './SwitchInput.vue';
|
|
29
|
+
import TelInput from './TelInput.vue';
|
|
30
|
+
import TextAreaInput from './TextAreaInput.vue';
|
|
31
|
+
import TextInput from './TextInput.vue';
|
|
32
|
+
import UppyInput from './UppyInput.vue';
|
|
30
33
|
|
|
31
|
-
|
|
34
|
+
import { configureBootstrap } from './utils/bootstrap.js';
|
|
35
|
+
import { configureI18n } from './i18n.js';
|
|
36
|
+
|
|
37
|
+
/** Backwards compatible names (v2 → v3). */
|
|
38
|
+
const Select2Input = ComboboxInput;
|
|
39
|
+
const RangeSliderInput = RangeInput;
|
|
40
|
+
const FormAdorn = FormAdornInput;
|
|
41
|
+
|
|
42
|
+
const components = {
|
|
32
43
|
AmountInput,
|
|
33
44
|
CaptchaInput,
|
|
34
45
|
CheckboxButtonInput,
|
|
35
46
|
CheckboxInput,
|
|
36
47
|
CheckboxToggle,
|
|
37
|
-
|
|
38
|
-
|
|
48
|
+
ChipsInput,
|
|
49
|
+
ComboboxInput,
|
|
50
|
+
DropzoneInput,
|
|
51
|
+
EditorInput,
|
|
39
52
|
EmailInput,
|
|
53
|
+
FileInput,
|
|
54
|
+
FormAdornInput,
|
|
55
|
+
FormContainer,
|
|
56
|
+
FormField,
|
|
57
|
+
FormLabel,
|
|
58
|
+
GroupControl,
|
|
59
|
+
LocationInput,
|
|
60
|
+
MobileInput,
|
|
61
|
+
MultiQuantityInput,
|
|
62
|
+
OTPInput,
|
|
63
|
+
PasswordInput,
|
|
40
64
|
PersianDatePickerInput,
|
|
41
|
-
|
|
42
|
-
|
|
65
|
+
QuantityInput,
|
|
66
|
+
RangeInput,
|
|
43
67
|
SimpleUploader,
|
|
44
|
-
|
|
68
|
+
StarRatingInput,
|
|
69
|
+
SubmitButton,
|
|
70
|
+
SwitchInput,
|
|
71
|
+
TelInput,
|
|
72
|
+
TextAreaInput,
|
|
73
|
+
TextInput,
|
|
45
74
|
UppyInput,
|
|
46
|
-
|
|
75
|
+
// aliases
|
|
76
|
+
Select2Input,
|
|
77
|
+
RangeSliderInput,
|
|
78
|
+
FormAdorn,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Optional global registration:
|
|
83
|
+
* app.use(InertiaBootstrapForms, { locale: 'fa', bootstrap: { Combobox, Chips, OtpInput, Range, Menu } })
|
|
84
|
+
*/
|
|
85
|
+
const plugin = {
|
|
86
|
+
install(app, options = {}) {
|
|
87
|
+
if (options.locale || options.messages) configureI18n(options);
|
|
88
|
+
if (options.bootstrap) configureBootstrap(options.bootstrap);
|
|
89
|
+
|
|
90
|
+
Object.entries(components).forEach(([name, component]) => app.component(name, component));
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default plugin;
|
|
95
|
+
|
|
96
|
+
export {
|
|
97
|
+
AmountInput,
|
|
98
|
+
CaptchaInput,
|
|
99
|
+
CheckboxButtonInput,
|
|
100
|
+
CheckboxInput,
|
|
101
|
+
CheckboxToggle,
|
|
102
|
+
ChipsInput,
|
|
103
|
+
ComboboxInput,
|
|
104
|
+
DropzoneInput,
|
|
105
|
+
EditorInput,
|
|
106
|
+
EmailInput,
|
|
107
|
+
FileInput,
|
|
108
|
+
FormAdornInput,
|
|
47
109
|
FormContainer,
|
|
110
|
+
FormField,
|
|
111
|
+
FormLabel,
|
|
112
|
+
GroupControl,
|
|
48
113
|
LocationInput,
|
|
49
114
|
MobileInput,
|
|
115
|
+
MultiQuantityInput,
|
|
116
|
+
OTPInput,
|
|
50
117
|
PasswordInput,
|
|
118
|
+
PersianDatePickerInput,
|
|
51
119
|
QuantityInput,
|
|
52
|
-
|
|
53
|
-
|
|
120
|
+
RangeInput,
|
|
121
|
+
SimpleUploader,
|
|
54
122
|
StarRatingInput,
|
|
55
|
-
|
|
56
|
-
|
|
123
|
+
SubmitButton,
|
|
124
|
+
SwitchInput,
|
|
57
125
|
TelInput,
|
|
58
126
|
TextAreaInput,
|
|
59
127
|
TextInput,
|
|
60
|
-
|
|
61
|
-
|
|
128
|
+
UppyInput,
|
|
129
|
+
// aliases
|
|
130
|
+
Select2Input,
|
|
131
|
+
RangeSliderInput,
|
|
132
|
+
FormAdorn,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export { configureBootstrap } from './utils/bootstrap.js';
|
|
136
|
+
export { configureI18n } from './i18n.js';
|
|
137
|
+
export { fieldProps, useField } from './composables/useField.js';
|
|
138
|
+
export { useFormApi } from './composables/useFormApi.js';
|
|
139
|
+
export { useDisposables } from './composables/useDisposables.js';
|
|
140
|
+
export { useBsInstance } from './composables/useBsInstance.js';
|
|
141
|
+
export { toEnglishDigits } from './utils/digits.js';
|
|
142
|
+
export { default as countryCodes } from './countryCodes.js';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy loader for Bootstrap 6 JavaScript plugins.
|
|
3
|
+
*
|
|
4
|
+
* Plugins are imported on demand (inside `onMounted`), which keeps the package
|
|
5
|
+
* SSR-safe and lets bundlers code-split every plugin. If your application
|
|
6
|
+
* already imports Bootstrap (or you want to pin a specific build) you can hand
|
|
7
|
+
* the classes over with `configureBootstrap({ Combobox, Chips, OtpInput, Range, Menu })`.
|
|
8
|
+
*/
|
|
9
|
+
const overrides = {};
|
|
10
|
+
const cache = new Map();
|
|
11
|
+
|
|
12
|
+
const loaders = {
|
|
13
|
+
Combobox: () => import('bootstrap/js/dist/combobox.js'),
|
|
14
|
+
Menu: () => import('bootstrap/js/dist/menu.js'),
|
|
15
|
+
Strength: () => import('bootstrap/js/dist/strength.js'),
|
|
16
|
+
Chips: () => import('bootstrap/js/dist/chips.js'),
|
|
17
|
+
OtpInput: () => import('bootstrap/js/dist/otp-input.js'),
|
|
18
|
+
Range: () => import('bootstrap/js/dist/range.js'),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function configureBootstrap(modules = {}) {
|
|
22
|
+
Object.assign(overrides, modules);
|
|
23
|
+
cache.clear();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function loadBootstrap(name) {
|
|
27
|
+
if (overrides[name]) {
|
|
28
|
+
return Promise.resolve(overrides[name]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!loaders[name]) {
|
|
32
|
+
return Promise.reject(new Error(`[inertia-bootstrap-forms] Unknown Bootstrap plugin "${name}"`));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!cache.has(name)) {
|
|
36
|
+
cache.set(
|
|
37
|
+
name,
|
|
38
|
+
loaders[name]()
|
|
39
|
+
.then((mod) => mod.default ?? mod[name])
|
|
40
|
+
.catch((error) => {
|
|
41
|
+
cache.delete(name);
|
|
42
|
+
throw error;
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return cache.get(name);
|
|
48
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const PERSIAN = '۰۱۲۳۴۵۶۷۸۹';
|
|
2
|
+
const ARABIC = '٠١٢٣٤٥٦٧٨٩';
|
|
3
|
+
|
|
4
|
+
/** Converts Persian / Arabic-Indic digits to ASCII digits. */
|
|
5
|
+
export function toEnglishDigits(input) {
|
|
6
|
+
return String(input ?? '').replace(/[۰-۹٠-٩]/g, (ch) => {
|
|
7
|
+
const p = PERSIAN.indexOf(ch);
|
|
8
|
+
return p !== -1 ? String(p) : String(ARABIC.indexOf(ch));
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function formatNumber(value) {
|
|
13
|
+
if (value === null || value === undefined || value === '') return '';
|
|
14
|
+
const n = Number(value);
|
|
15
|
+
return Number.isNaN(n) ? '' : n.toLocaleString('en-US');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function parseDigits(text) {
|
|
19
|
+
const digits = toEnglishDigits(text).replace(/\D/g, '');
|
|
20
|
+
return digits === '' ? null : parseInt(digits, 10);
|
|
21
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Default headers for same-origin Laravel endpoints (Accept JSON + XSRF cookie). */
|
|
2
|
+
export function laravelHeaders(extra = {}) {
|
|
3
|
+
const headers = {
|
|
4
|
+
Accept: 'application/json',
|
|
5
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
6
|
+
...extra,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
if (typeof document !== 'undefined') {
|
|
10
|
+
const match = document.cookie.match(/(?:^|;\s*)XSRF-TOKEN=([^;]+)/);
|
|
11
|
+
if (match && !headers['X-XSRF-TOKEN']) headers['X-XSRF-TOKEN'] = decodeURIComponent(match[1]);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return headers;
|
|
15
|
+
}
|