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.
Files changed (59) hide show
  1. package/README.fa.md +468 -0
  2. package/README.md +322 -714
  3. package/dist/inertia-bootstrap-forms.cjs.js +1 -0
  4. package/dist/inertia-bootstrap-forms.es.js +3524 -30
  5. package/index.d.ts +143 -507
  6. package/package.json +89 -70
  7. package/src/AmountInput.vue +81 -76
  8. package/src/CaptchaInput.vue +87 -88
  9. package/src/CheckboxButtonInput.vue +56 -69
  10. package/src/CheckboxInput.vue +60 -79
  11. package/src/CheckboxToggle.vue +53 -136
  12. package/src/ChipsInput.vue +157 -0
  13. package/src/ComboboxInput.vue +556 -0
  14. package/src/DropzoneInput.vue +125 -103
  15. package/src/EditorInput.vue +47 -106
  16. package/src/EmailInput.vue +16 -15
  17. package/src/FileInput.vue +189 -179
  18. package/src/FormAdornInput.vue +75 -0
  19. package/src/FormContainer.vue +288 -251
  20. package/src/FormField.vue +59 -0
  21. package/src/FormLabel.vue +27 -27
  22. package/src/GroupControl.vue +55 -53
  23. package/src/LocationInput.vue +79 -72
  24. package/src/MobileInput.vue +21 -13
  25. package/src/MultiQuantityInput.vue +109 -181
  26. package/src/OTPInput.vue +144 -76
  27. package/src/PasswordInput.vue +151 -17
  28. package/src/PersianDatePickerInput.vue +63 -166
  29. package/src/QuantityInput.vue +100 -119
  30. package/src/RangeInput.vue +96 -0
  31. package/src/SimpleUploader.vue +8 -143
  32. package/src/StarRatingInput.vue +114 -138
  33. package/src/SubmitButton.vue +39 -24
  34. package/src/SwitchInput.vue +58 -83
  35. package/src/TelInput.vue +86 -120
  36. package/src/TextAreaInput.vue +29 -33
  37. package/src/TextInput.vue +42 -51
  38. package/src/UppyInput.vue +237 -324
  39. package/src/composables/useBsInstance.js +66 -0
  40. package/src/composables/useChoice.js +34 -0
  41. package/src/composables/useDisposables.js +59 -0
  42. package/src/composables/useField.js +121 -0
  43. package/src/composables/useFormApi.js +12 -0
  44. package/src/i18n.js +120 -0
  45. package/src/index.js +123 -42
  46. package/src/utils/bootstrap.js +48 -0
  47. package/src/utils/digits.js +21 -0
  48. package/src/utils/http.js +15 -0
  49. package/dist/choices-KiM-I_0P.js +0 -2955
  50. package/dist/dropdown-yh2aQrbw-XuCU1v1F.js +0 -1543
  51. package/dist/index-BRtKc2RA.js +0 -604
  52. package/dist/index-_x4UqcXz.js +0 -4938
  53. package/dist/inertia-bootstrap-forms.umd.js +0 -19
  54. package/dist/range-slider-pips-DTDOg3Q9.js +0 -3506
  55. package/dist/style.css +0 -1
  56. package/src/css/from-select.scss +0 -384
  57. package/src/css/loader.gif +0 -0
  58. package/src/css/location.scss +0 -44
  59. 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 "./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 SwitchInput from "./SwitchInput.vue";
7
- import PersianDatePickerInput from "./PersianDatePickerInput.vue";
8
- import EditorInput from "./EditorInput.vue";
9
- import FileInput from "./FileInput.vue";
10
- import EmailInput from "./EmailInput.vue";
11
- import GroupControl from "./GroupControl.vue";
12
- import FormContainer from "./FormContainer.vue";
13
- import FormLabel from "./FormLabel.vue";
14
- import LocationInput from "./LocationInput.vue";
15
- import MobileInput from "./MobileInput.vue";
16
- import PasswordInput from "./PasswordInput.vue";
17
- import QuantityInput from "./QuantityInput.vue";
18
- import MultiQuantityInput from "./MultiQuantityInput.vue";
19
- import Select2Input from "./Select2Input.vue";
20
- import StarRatingInput from "./StarRatingInput.vue";
21
- import SubmitButton from "./SubmitButton.vue";
22
- import TelInput from "./TelInput.vue";
23
- import TextAreaInput from "./TextAreaInput.vue";
24
- import TextInput from "./TextInput.vue";
25
- import OTPInput from "./OTPInput.vue";
26
- import DropzoneInput from "./DropzoneInput.vue";
27
- import SimpleUploader from "./SimpleUploader.vue";
28
- import RangeSliderInput from "./RangeSliderInput.vue";
29
- import UppyInput from "./UppyInput.vue";
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
- export {
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
- SwitchInput,
38
- FileInput,
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
- EditorInput,
42
- DropzoneInput,
65
+ QuantityInput,
66
+ RangeInput,
43
67
  SimpleUploader,
44
- RangeSliderInput,
68
+ StarRatingInput,
69
+ SubmitButton,
70
+ SwitchInput,
71
+ TelInput,
72
+ TextAreaInput,
73
+ TextInput,
45
74
  UppyInput,
46
- GroupControl,
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
- MultiQuantityInput,
53
- SubmitButton,
120
+ RangeInput,
121
+ SimpleUploader,
54
122
  StarRatingInput,
55
- Select2Input,
56
- FormLabel,
123
+ SubmitButton,
124
+ SwitchInput,
57
125
  TelInput,
58
126
  TextAreaInput,
59
127
  TextInput,
60
- OTPInput,
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
+ }