inertia-bootstrap-forms 2.1.21 → 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
@@ -1,79 +1,60 @@
1
- <script>
2
- import {computed, defineComponent, inject} from "vue";
3
-
4
- export default defineComponent({
5
- props: {
6
- name: {
7
- type: String,
8
- required: true,
9
- },
10
- id: {
11
- type: String,
12
- default: '',
13
- required: false,
14
- },
15
- value: {
16
- type: [String, Number],
17
- default: 'yes',
18
- required: false,
19
- },
20
- type: {
21
- type: String,
22
- default: 'checkbox',
23
- required: false,
24
- },
25
- },
26
- computed: {
27
- inputID() {
28
- return this.form.getID(this);
29
- }
30
- },
31
- setup(props) {
32
- let form = inject('form', {
33
- errors: {},
34
- getID(name) {
35
- return name;
36
- }
37
- });
38
- let group = inject('group', {});
39
-
40
- const modelValue = computed({
41
- get() {
42
- return (group.value && form.value[group.value.name]) ? group.value?.getData(props.name) : form.value[props.name];
43
- },
44
- set(value) {
45
- if (group?.value?.name) {
46
- group.value.setData(props.name, value);
47
- } else {
48
- form.value[props.name] = value;
49
- }
50
- }
51
- });
52
-
53
- return {modelValue, form, group};
54
- },
55
- })
56
- </script>
57
- <template>
58
- <div class="form-check">
59
- <input
60
- :name="name"
61
- :id="inputID"
62
- v-model="modelValue"
63
- :class="{'is-invalid': form?.errors[name]}"
64
- :disabled="form?.processing"
65
- :type="type"
66
- :value="value"
67
- @change="e => $emit('change', e)"
68
- class="form-check-input">
69
- <label class="form-check-label" :for="inputID">
70
- <slot/>
71
- </label>
72
- </div>
73
- </template>
74
- <style>
75
- .form-check,
76
- .form-check label {
77
- cursor: pointer;
78
- }
79
- </style>
1
+ <script>
2
+ import { defineComponent, ref } from 'vue';
3
+ import { fieldProps, useField } from './composables/useField.js';
4
+ import { useChoice } from './composables/useChoice.js';
5
+
6
+ /** Bootstrap 6 checkbox / radio (`.check` / `.radio` directly on the input, wrapped by `.form-field`). */
7
+ export default defineComponent({
8
+ name: 'CheckboxInput',
9
+ emits: ['update:modelValue', 'change', 'focus', 'blur'],
10
+ props: {
11
+ ...fieldProps,
12
+ /** checkbox | radio */
13
+ type: { type: String, default: 'checkbox', validator: (v) => ['checkbox', 'radio'].includes(v) },
14
+ /** Value added to the model (arrays / radios) when this option is checked. */
15
+ value: { type: [String, Number, Boolean, Object], default: 'yes' },
16
+ trueValue: { type: null, default: true },
17
+ falseValue: { type: null, default: false },
18
+ /** Initial checked state (applied only when the model is still empty). */
19
+ checked: { type: Boolean, default: false },
20
+ indeterminate: { type: Boolean, default: false },
21
+ label: { type: String, default: null },
22
+ },
23
+ setup(props, { emit }) {
24
+ const input = ref(null);
25
+ const field = useField(props, emit, { valued: () => props.value });
26
+ useChoice(props, field, input);
27
+
28
+ function onChange(event) {
29
+ field.emitChange(field.model.value, event);
30
+ }
31
+
32
+ return { ...field, input, onChange };
33
+ },
34
+ expose: ['input'],
35
+ });
36
+ </script>
37
+
38
+ <template>
39
+ <div class="form-field">
40
+ <input
41
+ ref="input"
42
+ v-model="model"
43
+ :name="name"
44
+ :id="inputId"
45
+ :type="type"
46
+ :value="value"
47
+ :true-value="trueValue"
48
+ :false-value="falseValue"
49
+ :required="required"
50
+ :disabled="isDisabled"
51
+ :aria-invalid="isInvalid || undefined"
52
+ :class="[type === 'radio' ? 'radio' : 'check', { 'is-invalid': isInvalid }]"
53
+ @click="readonly && $event.preventDefault()"
54
+ @change="onChange"
55
+ @focus="$emit('focus', $event)"
56
+ @blur="$emit('blur', $event)"
57
+ />
58
+ <label :for="inputId"><slot>{{ label }}</slot></label>
59
+ </div>
60
+ </template>
@@ -1,136 +1,53 @@
1
- <script>
2
- import {computed, defineComponent, inject} from "vue";
3
-
4
- export default defineComponent({
5
- props: {
6
- name: {
7
- type: String,
8
- required: true,
9
- },
10
- id: {
11
- type: String,
12
- default: '',
13
- required: false,
14
- },
15
- value: {
16
- type: [String, Number],
17
- default: 'yes',
18
- required: false,
19
- },
20
- type: {
21
- type: String,
22
- default: 'checkbox',
23
- required: false,
24
- },
25
- disabled: {
26
- type: Boolean,
27
- default: false,
28
- required: false,
29
- },
30
- readonly: {
31
- type: Boolean,
32
- default: false,
33
- },
34
- hideInput: {
35
- type: Boolean,
36
- default: false,
37
- },
38
- },
39
- computed: {
40
- inputID() {
41
- return this.form.getID(this) + (this.value || '');
42
- },
43
- selectedValue() {
44
- return (this.modelValue && typeof this.modelValue == 'object') ? Object.values(this.modelValue) : (this.modelValue ? [this.modelValue] : null)
45
- }
46
- },
47
- setup(props) {
48
- let form = inject('form', {
49
- errors: {},
50
- getID(name) {
51
- return name;
52
- }
53
- });
54
- let group = inject('group', {});
55
-
56
- const modelValue = computed({
57
- get() {
58
- return (group.value && form.value[group.value.name]) ? group.value?.getData(props.name) : form.value[props.name];
59
- },
60
- set(value) {
61
- if (group?.value?.name) {
62
- group.value.setData(props.name, value);
63
- } else {
64
- form.value[props.name] = value;
65
- }
66
- }
67
- });
68
-
69
- return {modelValue, form, group};
70
- },
71
- })
72
- </script>
73
- <template>
74
- <label class="form-check-toggle"
75
- :class="{
76
- 'form-check-toggle--active': (selectedValue || []).includes(value),
77
- 'disabled': (disabled || readonly),
78
- 'is-invalid': form?.errors[name]
79
- }" :for="inputID">
80
- <input
81
- :name="name"
82
- :id="inputID"
83
- v-model="modelValue"
84
- :class="{'is-invalid': form?.errors[name], 'form-check-toggle--input-hide': hideInput}"
85
- :disabled="form?.processing || disabled"
86
- :type="type"
87
- :value="value"
88
- :readonly="readonly"
89
- @change="e => $emit('change', e)"
90
- class="form-check-toggle--input form-check-input">
91
- <slot/>
92
- </label>
93
- </template>
94
- <style>
95
- .form-check-toggle {
96
- position: relative;
97
- cursor: pointer;
98
- display: block;
99
- background-color: var(--bs-body-bg, #ffffff);
100
- border-radius: var(--bs-border-radius, 1rem);
101
- min-width: 100px;
102
- width: 100%;
103
- padding: 0.5rem 0.75rem;
104
- font-size: 0.97rem;
105
- font-weight: 400;
106
- line-height: 1.7;
107
- color: var(--bs-body-color, #000000);
108
- -webkit-appearance: none;
109
- -moz-appearance: none;
110
- appearance: none;
111
- background-clip: padding-box;
112
- border: var(--bs-border-width, 1px) solid var(--bs-border-color, #eeeeee);
113
- transition: background-color ease 0.3s, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
114
- }
115
-
116
- .form-check-toggle.is-invalid {
117
- border-color: var(--bs-form-invalid-border-color);
118
- }
119
-
120
- .form-check-toggle.form-check-toggle--active,
121
- .form-check-toggle:has(input:checked) {
122
- background-color: var(--bs-warning-bg-subtle, #ffda6a);
123
- border-color: var(--bs-warning-border-subtle, var(--bs-alert-border-color, #fff0c3));
124
- }
125
-
126
- .form-check-toggle .form-check-toggle--input-hide {
127
- visibility: hidden;
128
- position: absolute;
129
- top: 0;
130
- right: 0;
131
- }
132
-
133
- .form-check-toggle .form-check-input {
134
- margin-right: 5px;
135
- }
136
- </style>
1
+ <script>
2
+ import { defineComponent, ref } from 'vue';
3
+ import { fieldProps, useField } from './composables/useField.js';
4
+ import { useChoice } from './composables/useChoice.js';
5
+
6
+ /** Selectable card (Bootstrap 6 `.form-field-card`): the whole card toggles the checkbox / radio. */
7
+ export default defineComponent({
8
+ name: 'CheckboxToggle',
9
+ emits: ['update:modelValue', 'change', 'focus', 'blur'],
10
+ props: {
11
+ ...fieldProps,
12
+ type: { type: String, default: 'checkbox', validator: (v) => ['checkbox', 'radio'].includes(v) },
13
+ value: { type: [String, Number, Boolean, Object], default: 'yes' },
14
+ trueValue: { type: null, default: true },
15
+ falseValue: { type: null, default: false },
16
+ checked: { type: Boolean, default: false },
17
+ /** Visually hide the check mark (the card highlight is the only indicator). */
18
+ hideInput: { type: Boolean, default: false },
19
+ },
20
+ setup(props, { emit }) {
21
+ const input = ref(null);
22
+ const field = useField(props, emit, { valued: () => props.value });
23
+ useChoice(props, field, input);
24
+
25
+ return { ...field, input, onChange: (event) => field.emitChange(field.model.value, event) };
26
+ },
27
+ expose: ['input'],
28
+ });
29
+ </script>
30
+
31
+ <template>
32
+ <div class="form-field form-field-card" :class="{ 'is-invalid': isInvalid, disabled: isDisabled || readonly }">
33
+ <input
34
+ ref="input"
35
+ v-model="model"
36
+ :name="name"
37
+ :id="inputId"
38
+ :type="type"
39
+ :value="value"
40
+ :true-value="trueValue"
41
+ :false-value="falseValue"
42
+ :required="required"
43
+ :disabled="isDisabled"
44
+ :aria-invalid="isInvalid || undefined"
45
+ :class="[type === 'radio' ? 'radio' : 'check', { 'is-invalid': isInvalid, 'visually-hidden': hideInput }]"
46
+ @click="readonly && $event.preventDefault()"
47
+ @change="onChange"
48
+ @focus="$emit('focus', $event)"
49
+ @blur="$emit('blur', $event)"
50
+ />
51
+ <label :for="inputId"><slot /></label>
52
+ </div>
53
+ </template>
@@ -0,0 +1,157 @@
1
+ <script>
2
+ import { defineComponent, nextTick, ref, watch } from 'vue';
3
+ import { fieldProps, useField } from './composables/useField.js';
4
+ import { useBsInstance } from './composables/useBsInstance.js';
5
+ import { useI18n } from './i18n.js';
6
+
7
+ const sameList = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
8
+
9
+ /**
10
+ * Bootstrap 6 Chip input (tags). The value is an array of strings stored in the FormContainer's v-model.
11
+ */
12
+ export default defineComponent({
13
+ name: 'ChipsInput',
14
+ emits: ['update:modelValue', 'change', 'add', 'remove', 'select', 'rejected', 'focus', 'blur'],
15
+ props: {
16
+ ...fieldProps,
17
+ placeholder: { type: String, default: null },
18
+ /** Character that splits typed / pasted text into chips. Use null to disable. */
19
+ separator: { type: String, default: ',' },
20
+ allowDuplicates: { type: Boolean, default: false },
21
+ maxChips: { type: Number, default: null },
22
+ dismissible: { type: Boolean, default: true },
23
+ /** HTML / SVG of the remove icon (sanitised by Bootstrap). */
24
+ dismissIcon: { type: String, default: null },
25
+ /** Turn the pending text into a chip when the input loses focus. */
26
+ createOnBlur: { type: Boolean, default: true },
27
+ /** (value: string) => boolean — return false to reject a chip. */
28
+ validator: { type: Function, default: null },
29
+ locale: { type: String, default: null },
30
+ },
31
+ setup(props, { emit, expose }) {
32
+ const field = useField(props, emit);
33
+ const { t } = useI18n(() => props.locale);
34
+ const el = ref(null);
35
+ const ghost = ref(null);
36
+ let syncing = false;
37
+ let offs = [];
38
+
39
+ const toValues = (model) => {
40
+ if (Array.isArray(model)) return model.map(String);
41
+ if (typeof model === 'string' && model !== '') {
42
+ return props.separator ? model.split(props.separator).map((s) => s.trim()).filter(Boolean) : [model];
43
+ }
44
+ return [];
45
+ };
46
+
47
+ function syncFromModel() {
48
+ const inst = instance.value;
49
+ if (!inst) return;
50
+
51
+ const target = toValues(field.model.value);
52
+ if (sameList(inst.getValues(), target)) return;
53
+
54
+ syncing = true;
55
+ try {
56
+ inst.clear();
57
+ target.forEach((v) => inst.add(v));
58
+ } finally {
59
+ syncing = false;
60
+ }
61
+
62
+ // Some values may have been rejected (duplicates / maxChips): reflect the truth
63
+ const actual = inst.getValues();
64
+ if (!sameList(actual, target) && Array.isArray(field.model.value)) field.model.value = actual;
65
+ }
66
+
67
+ const locked = () => props.readonly || field.isDisabled.value;
68
+
69
+ const { instance, create } = useBsInstance(
70
+ 'Chips',
71
+ () => el.value,
72
+ () => {
73
+ const config = {
74
+ separator: props.separator,
75
+ allowDuplicates: props.allowDuplicates,
76
+ maxChips: props.maxChips,
77
+ dismissible: props.dismissible && !locked(),
78
+ createOnBlur: props.createOnBlur,
79
+ placeholder: props.placeholder ?? t('chips.placeholder'),
80
+ };
81
+ if (props.dismissIcon) config.dismissIcon = props.dismissIcon;
82
+ return config;
83
+ },
84
+ {
85
+ onCreate(inst, element) {
86
+ const on = (type, fn) => {
87
+ element.addEventListener(type, fn);
88
+ offs.push(() => element.removeEventListener(type, fn));
89
+ };
90
+
91
+ on('add.bs.chips', (e) => {
92
+ if (syncing) return;
93
+ if (props.validator && !props.validator(e.value)) {
94
+ e.preventDefault();
95
+ emit('rejected', e.value);
96
+ return;
97
+ }
98
+ emit('add', e.value);
99
+ });
100
+ on('remove.bs.chips', (e) => !syncing && emit('remove', e.value));
101
+ on('select.bs.chips', (e) => emit('select', e.selected));
102
+ on('change.bs.chips', (e) => {
103
+ if (syncing) return;
104
+ field.model.value = e.values;
105
+ field.emitChange(e.values, e);
106
+ });
107
+
108
+ syncFromModel();
109
+ },
110
+ onDestroy() {
111
+ offs.forEach((off) => off());
112
+ offs = [];
113
+ },
114
+ }
115
+ );
116
+
117
+ watch(() => field.model.value, syncFromModel, { deep: true });
118
+
119
+ // Options that are only read when the plugin is constructed
120
+ watch(
121
+ () => [props.separator, props.allowDuplicates, props.maxChips, props.dismissible, props.createOnBlur, props.dismissIcon, props.readonly, field.isDisabled.value],
122
+ async () => {
123
+ await nextTick();
124
+ create();
125
+ }
126
+ );
127
+
128
+ expose({
129
+ add: (value) => instance.value?.add(value),
130
+ remove: (value) => instance.value?.remove(value),
131
+ clear: () => instance.value?.clear(),
132
+ focus: () => ghost.value?.focus(),
133
+ });
134
+
135
+ return { ...field, t, el, ghost, locked };
136
+ },
137
+ });
138
+ </script>
139
+
140
+ <template>
141
+ <div ref="el" class="chip-input" :aria-disabled="isDisabled || undefined">
142
+ <input
143
+ ref="ghost"
144
+ type="text"
145
+ class="form-ghost"
146
+ :id="inputId"
147
+ :class="{ 'is-invalid': isInvalid }"
148
+ :placeholder="placeholder ?? t('chips.placeholder')"
149
+ :disabled="isDisabled"
150
+ :readonly="readonly"
151
+ :aria-invalid="isInvalid || undefined"
152
+ autocomplete="off"
153
+ @focus="$emit('focus', $event)"
154
+ @blur="$emit('blur', $event)"
155
+ />
156
+ </div>
157
+ </template>