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
package/src/FormLabel.vue CHANGED
@@ -1,34 +1,34 @@
1
1
  <script>
2
- import {defineComponent} from "vue";
2
+ import { computed, defineComponent, inject, onMounted, ref } from 'vue';
3
3
 
4
4
  export default defineComponent({
5
- props: {
6
- default: false,
7
- required: false,
8
- },
9
- emits: ['update:modelValue'],
10
- mounted() {
11
- const sibling = this.$el.nextElementSibling;
12
- if (sibling) {
13
- this.forId = sibling.id || sibling.querySelector('[id]')?.id || null;
14
- }
15
- },
16
- data() {
17
- return {
18
- forId: null,
19
- };
20
- },
21
- })
5
+ name: 'FormLabel',
6
+ props: {
7
+ /** id of the control. Detected automatically inside <FormField> or from the next element. */
8
+ for: { type: String, default: null },
9
+ required: { type: Boolean, default: false },
10
+ },
11
+ setup(props) {
12
+ const ctx = inject('formField', null);
13
+ const el = ref(null);
14
+ const detected = ref(null);
15
+
16
+ onMounted(() => {
17
+ if (props.for || ctx?.id) return;
18
+ const sibling = el.value?.nextElementSibling;
19
+ if (sibling) detected.value = sibling.id || sibling.querySelector('[id]')?.id || null;
20
+ });
21
+
22
+ const target = computed(() => props.for || ctx?.id || detected.value || undefined);
23
+
24
+ return { el, target };
25
+ },
26
+ });
22
27
  </script>
23
28
 
24
29
  <template>
25
- <label class="mb-2 d-block" :for="forId">
26
- <slot/>
27
- <span class="text-danger" v-if="required">*</span>
28
- </label>
30
+ <label ref="el" class="form-label" :for="target">
31
+ <slot />
32
+ <span v-if="required" class="fg-danger" aria-hidden="true"> *</span>
33
+ </label>
29
34
  </template>
30
- <style scoped>
31
- label {
32
- cursor: pointer;
33
- }
34
- </style>
@@ -1,67 +1,69 @@
1
1
  <script>
2
- import {computed, defineComponent, inject} from "vue";
2
+ import { computed, defineComponent, inject, onMounted, provide, ref } from 'vue';
3
3
 
4
4
  export default defineComponent({
5
+ name: 'GroupControl',
5
6
  props: {
6
- name: {
7
- type: String,
8
- required: true,
9
- },
7
+ /** Key of the object / array inside the form that holds the grouped values. */
8
+ name: { type: String, required: true },
9
+ /** Explicit item index (repeaters). When omitted it is detected from the DOM position. */
10
+ index: { type: [Number, String], default: null },
10
11
  },
11
- setup(props, context) {
12
- let form = inject('form', {
13
- errors: {},
14
- getID(name) {
15
- return name;
16
- }
12
+ setup(props) {
13
+ const form = inject('form', null);
14
+ const groupEl = ref(null);
15
+ const detected = ref('0');
16
+
17
+ const index = computed(() => (props.index !== null ? String(props.index) : detected.value));
18
+
19
+ onMounted(() => {
20
+ if (props.index !== null) return;
21
+ const siblings = groupEl.value?.parentNode?.parentNode?.querySelectorAll('.form-control-group');
22
+ siblings?.forEach((item, i) => {
23
+ if (item === groupEl.value) detected.value = String(i);
24
+ });
17
25
  });
18
26
 
19
- return {form};
20
- },
21
- provide() {
22
- return {
23
- group: computed(() => this)
24
- }
25
- },
26
- mounted() {
27
- this.$refs.groupEl?.parentNode?.parentNode.querySelectorAll('.form-control-group').forEach((item, index) => {
28
- if(item === this.$refs.groupEl){
29
- this.groupID = (index).toString();
30
- }
31
- })
32
- },
33
- methods: {
34
- setData(fieldName, value) {
35
- if (!this.form[this.name]) {
36
- this.form[this.name] = {};
37
- }
38
- if (this.groupID && this.form[this.name]?.length >= 0) {
39
- if(!this.form[this.name][this.groupID]){
40
- this.form[this.name][this.groupID] = {};
27
+ const api = {
28
+ get name() {
29
+ return props.name;
30
+ },
31
+ get index() {
32
+ return index.value;
33
+ },
34
+ getData(field) {
35
+ const container = form?.value?.[props.name];
36
+ if (container === null || container === undefined) return null;
37
+ if (Array.isArray(container)) return container[Number(index.value)]?.[field] ?? null;
38
+ return container[field] ?? null;
39
+ },
40
+ setData(field, value) {
41
+ const f = form?.value;
42
+ if (!f) return;
43
+
44
+ if (f[props.name] === null || f[props.name] === undefined) {
45
+ f[props.name] = props.index !== null ? [] : {};
41
46
  }
42
- this.form[this.name][this.groupID][fieldName] = value;
43
- }else{
44
- this.form[this.name][fieldName] = value;
45
- }
46
47
 
47
- },
48
- getData(fieldName) {
49
- if (this.groupID && this.form[this.name]?.length >= 0) {
50
- return this.form[this.name][this.groupID]?.[fieldName] || null;
51
- }else{
52
- return this.form[this.name][fieldName];
53
- }
54
- }
55
- },
56
- data() {
57
- return {
58
- groupID: '0',
48
+ if (Array.isArray(f[props.name])) {
49
+ const i = Number(index.value);
50
+ if (!f[props.name][i]) f[props.name][i] = {};
51
+ f[props.name][i][field] = value;
52
+ } else {
53
+ f[props.name][field] = value;
54
+ }
55
+ },
59
56
  };
60
- }
61
- })
57
+
58
+ provide('group', computed(() => api));
59
+
60
+ return { groupEl, index };
61
+ },
62
+ });
62
63
  </script>
64
+
63
65
  <template>
64
- <div class="form-control-group" :data-name="name" :data-group-id="groupID" ref="groupEl">
65
- <slot/>
66
+ <div ref="groupEl" class="form-control-group" :data-name="name" :data-group-id="index">
67
+ <slot />
66
68
  </div>
67
69
  </template>
@@ -1,84 +1,91 @@
1
1
  <script>
2
- import "leaflet/dist/leaflet.css";
3
- import "./css/location.scss";
4
- import {LMap, LTileLayer, LMarker} from "@vue-leaflet/vue-leaflet";
5
- import {defineComponent, inject} from "vue";
2
+ import { computed, defineAsyncComponent, defineComponent, onMounted, ref, watch } from 'vue';
3
+ import { fieldProps, useField } from './composables/useField.js';
6
4
 
5
+ const LMap = defineAsyncComponent(() => import('@vue-leaflet/vue-leaflet').then((m) => m.LMap));
6
+ const LTileLayer = defineAsyncComponent(() => import('@vue-leaflet/vue-leaflet').then((m) => m.LTileLayer));
7
+ const LMarker = defineAsyncComponent(() => import('@vue-leaflet/vue-leaflet').then((m) => m.LMarker));
8
+
9
+ /**
10
+ * Leaflet map picker. The value is `{ lat, lng }`.
11
+ * Import `leaflet/dist/leaflet.css` in your application.
12
+ */
7
13
  export default defineComponent({
8
- name: 'LocationInput',
9
- components: {
10
- LMap, LTileLayer, LMarker
11
- },
12
- props: {
13
- name: {
14
- type: String,
15
- default: 'location',
16
- required: false,
17
- },
18
- disabled: {
19
- type: [Boolean, String],
20
- default: false,
21
- },
22
- readonly: {
23
- type: [Boolean, String],
24
- default: false,
14
+ name: 'LocationInput',
15
+ components: { LMap, LTileLayer, LMarker },
16
+ emits: ['update:modelValue', 'change', 'ready'],
17
+ props: {
18
+ ...fieldProps,
19
+ name: { type: String, default: 'location' },
20
+ /** Initial map centre when there is no value yet. */
21
+ center: { type: Object, default: () => ({ lat: 35.69293, lng: 51.36372 }) },
22
+ zoom: { type: Number, default: 13 },
23
+ height: { type: [Number, String], default: 300 },
24
+ tileUrl: { type: String, default: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' },
25
+ attribution: { type: String, default: '&copy; OpenStreetMap contributors' },
25
26
  },
26
- modelValue: Object,
27
- },
28
- setup(props) {
29
- let form = inject('form');
27
+ setup(props, { emit }) {
28
+ const field = useField(props, emit);
29
+ const ready = ref(false);
30
+ const zoomLevel = ref(props.zoom);
30
31
 
31
- if (form === undefined) {
32
- form = {
33
- errors: {},
34
- zoom: 8,
35
- center: [47.41322, -1.219482],
36
- getID(name) {
37
- }
38
- };
39
- }
32
+ onMounted(() => {
33
+ ready.value = true; // Leaflet touches `window`, so it is rendered on the client only
34
+ });
35
+
36
+ const marker = computed(() => {
37
+ const v = field.model.value;
38
+ return v?.lat !== undefined && v?.lat !== null && v?.lng !== undefined && v?.lng !== null
39
+ ? { lat: Number(v.lat), lng: Number(v.lng) }
40
+ : null;
41
+ });
40
42
 
41
- return {form};
42
- },
43
- methods: {
44
- setMarker(event) {
45
- if(this.readonly) return;
43
+ const mapCenter = computed(() => marker.value ?? props.center);
44
+ const locked = computed(() => props.readonly || field.isDisabled.value);
46
45
 
47
- this.marker = {
48
- lat: event?.latlng?.lat || event?.target?._latlng?.lat,
49
- lng: event?.latlng?.lng || event?.target?._latlng?.lng,
50
- };
46
+ watch(() => props.zoom, (z) => (zoomLevel.value = z));
51
47
 
52
- this.$emit('update:modelValue', this.marker);
53
- this.form[this.name] = this.marker;
54
- }
55
- },
56
- data() {
57
- return {
58
- zoom: 13,
59
- marker: {
60
- lat: this.modelValue?.lat || this.form[this.name]?.lat || null,
61
- lng: this.modelValue?.lng || this.form[this.name]?.lng || null,
62
- },
63
- center: {
64
- lat: this.modelValue?.lat || this.form[this.name]?.lat || 35.69293,
65
- lng: this.modelValue?.lng || this.form[this.name]?.lng || 51.36372,
66
- },
67
- };
68
- },
48
+ function setMarker(event) {
49
+ if (locked.value) return;
50
+ const latlng = event?.latlng ?? event?.target?._latlng;
51
+ if (!latlng) return;
69
52
 
70
- })
53
+ const next = { lat: latlng.lat, lng: latlng.lng };
54
+ field.model.value = next;
55
+ field.emitChange(next, event);
56
+ }
57
+
58
+ return {
59
+ ...field,
60
+ ready,
61
+ zoomLevel,
62
+ marker,
63
+ mapCenter,
64
+ locked,
65
+ setMarker,
66
+ heightCss: computed(() => (typeof props.height === 'number' ? `${props.height}px` : props.height)),
67
+ };
68
+ },
69
+ });
71
70
  </script>
71
+
72
72
  <template>
73
- <div class="location-map-container"
74
- :class="{disabled: disabled || form.processing, 'is-invalid': form?.errors[name] !== undefined}">
75
- <l-map ref="map" v-model:zoom="zoom" :useGlobalLeaflet="false" :center="this.center" @click="setMarker">
76
- <l-tile-layer
77
- url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
78
- layer-type="base"
79
- name="OpenStreetMap"
80
- ></l-tile-layer>
81
- <LMarker :lat-lng="marker" v-if="marker" :draggable="(!readonly && !disabled)" @dragend="setMarker"/>
82
- </l-map>
83
- </div>
73
+ <div
74
+ class="location-map-container position-relative overflow-hidden"
75
+ :class="{ disabled: locked, 'is-invalid': isInvalid }"
76
+ :id="inputId"
77
+ :style="{ width: '100%', height: heightCss }"
78
+ >
79
+ <l-map
80
+ v-if="ready"
81
+ v-model:zoom="zoomLevel"
82
+ :use-global-leaflet="false"
83
+ :center="mapCenter"
84
+ @click="setMarker"
85
+ @ready="$emit('ready', $event)"
86
+ >
87
+ <l-tile-layer :url="tileUrl" :attribution="attribution" layer-type="base" name="OpenStreetMap" />
88
+ <l-marker v-if="marker" :lat-lng="marker" :draggable="!locked" @dragend="setMarker" />
89
+ </l-map>
90
+ </div>
84
91
  </template>
@@ -1,23 +1,31 @@
1
1
  <script>
2
- import TextInput from "./TextInput.vue";
3
- import {defineComponent} from "vue";
2
+ import { defineComponent } from 'vue';
3
+ import TextInput from './TextInput.vue';
4
+ import { useI18n } from './i18n.js';
4
5
 
6
+ /** Thin wrapper around TextInput (type="tel"). All TextInput props and events are forwarded. */
5
7
  export default defineComponent({
6
- components: {
7
- TextInput
8
- },
8
+ name: 'MobileInput',
9
+ components: { TextInput },
9
10
  props: {
10
- name: {
11
- type: String,
12
- default: 'mobile',
13
- required: true,
14
- },
11
+ name: { type: String, default: 'mobile' },
12
+ placeholder: { type: String, default: null },
13
+ locale: { type: String, default: null },
14
+ },
15
+ setup(props) {
16
+ const { t } = useI18n(() => props.locale);
17
+ return { t };
15
18
  },
16
- })
19
+ });
17
20
  </script>
21
+
18
22
  <template>
19
23
  <TextInput
24
+ type="tel"
25
+ inputmode="tel"
26
+ autocomplete="tel"
27
+ class="form-control-mobile fanum"
20
28
  :name="name"
21
- placeholder="موبایل خود را وارد کنید"
22
- type="tel" class="form-control-mobile fanum"/>
29
+ :placeholder="placeholder ?? t('mobile.placeholder')"
30
+ />
23
31
  </template>