inertia-bootstrap-forms 1.0.0 → 1.0.4

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 (37) hide show
  1. package/README.md +46 -0
  2. package/dist/css/from-select.scss +374 -0
  3. package/index.js +49 -0
  4. package/package.json +15 -1
  5. package/{Inputs → src}/AmountInput.vue +1 -2
  6. package/{Inputs → src}/CaptchaInput.vue +11 -6
  7. package/{Inputs/CheckboxInput.vue → src/CheckboxButtonInput.vue} +21 -8
  8. package/src/CheckboxInput.vue +79 -0
  9. package/src/CheckboxToggle.vue +109 -0
  10. package/{Inputs → src}/EditorInput.vue +1 -0
  11. package/{Inputs → src}/EmailInput.vue +1 -1
  12. package/{Inputs → src}/FormContainer.vue +22 -9
  13. package/src/GroupControl.vue +67 -0
  14. package/src/MultiQuantityInput.vue +68 -0
  15. package/src/PersianDatePickerInput.vue +176 -0
  16. package/src/QuantityInput.vue +106 -0
  17. package/src/SecondarySubmitButton.vue +22 -0
  18. package/src/Select2Input.vue +190 -0
  19. package/src/StarRatingInput.vue +158 -0
  20. package/{Inputs → src}/SubmitButton.vue +4 -2
  21. package/src/TextAreaInput.vue +44 -0
  22. package/src/TextInput.vue +51 -0
  23. package/src/locationInput.vue +124 -0
  24. package/Bootstrap/InputGroup.vue +0 -9
  25. package/Bootstrap/InputGroupText.vue +0 -9
  26. package/Inputs/DatePickerInput.vue +0 -137
  27. package/Inputs/Select2Input.vue +0 -193
  28. package/Inputs/TextAreaInput.vue +0 -33
  29. package/Inputs/TextInput.vue +0 -34
  30. package/Inputs/locationInput.vue +0 -122
  31. package/vue-select.css +0 -387
  32. /package/{Inputs → src}/FileInput.vue +0 -0
  33. /package/{Inputs → src}/FormLabel.vue +0 -0
  34. /package/{Inputs → src}/MobileInput.vue +0 -0
  35. /package/{Inputs → src}/PasswordInput.vue +0 -0
  36. /package/{Inputs → src}/TelInput.vue +0 -0
  37. /package/{Inputs → src}/countryCodes.js +0 -0
@@ -0,0 +1,190 @@
1
+ <script>
2
+ import '../dist/css/from-select.scss';
3
+ import Choices from 'choices.js';
4
+ import {computed, inject} from "vue";
5
+
6
+ export default {
7
+ emits: ['update:modelValue', 'search', 'change', 'selected'],
8
+ props: {
9
+ name: {
10
+ type: String,
11
+ required: true,
12
+ },
13
+ modelValue: '',
14
+ label: {
15
+ type: String,
16
+ default: 'name',
17
+ },
18
+ placeholder: {
19
+ type: String,
20
+ default: 'Click to choice',
21
+ },
22
+ searchPlaceholder: {
23
+ type: String,
24
+ default: 'Type for search...',
25
+ },
26
+ multiple: {
27
+ type: Boolean,
28
+ default: false,
29
+ },
30
+ required: {
31
+ type: Boolean,
32
+ default: false,
33
+ },
34
+ config: {
35
+ type: Object,
36
+ default: {},
37
+ },
38
+ locale: {
39
+ type: String,
40
+ default: 'en',
41
+ },
42
+ options: Array,
43
+ searchEnabled: {
44
+ type: Boolean,
45
+ default: false
46
+ },
47
+ search: {
48
+ type: Object,
49
+ default: {
50
+ url: null,
51
+ }
52
+ },
53
+ },
54
+ setup(props) {
55
+ let form = inject('form', {
56
+ errors: {},
57
+ getID(name) {
58
+ return name;
59
+ }
60
+ });
61
+ let group = inject('group', {});
62
+
63
+ const modelValue = computed({
64
+ get() {
65
+ return (group.value && form.value[group.value.name]) ? group.value?.getData(props.name) : form.value[props.name];
66
+ },
67
+ set(value) {
68
+ if (group?.value?.name) {
69
+ group.value.setData(props.name, value);
70
+ } else {
71
+ form.value[props.name] = value;
72
+ }
73
+ }
74
+ });
75
+
76
+ return {modelValue, form, group};
77
+ },
78
+ methods: {
79
+ init() {
80
+ this.choices = new Choices(this.$refs.input, {
81
+ searchEnabled: (this.searchEnabled || !!this.search?.url),
82
+ removeItemButton: true,
83
+ placeholderValue: this.placeholder,
84
+ searchPlaceholderValue: this.placeholder,
85
+ itemSelectText: null,
86
+ ...this.localeTranslates[this.currentLocale],
87
+ ...this.config
88
+ });
89
+
90
+ this.$refs.input.addEventListener('search', this.searchHandle);
91
+ },
92
+ destroy() {
93
+ this.$refs.input.removeEventListener('search', this.searchHandle);
94
+ this.choices.destroy();
95
+ },
96
+ searchHandle(event){
97
+ this.doSearch(event.detail.value);
98
+ },
99
+ async setLoading(){
100
+ this.loading = true;
101
+ this.choices.clearChoices();
102
+ await this.choices.setChoices(
103
+ [
104
+ {
105
+ value: '',
106
+ label: this.localeTranslates[this.currentLocale]['searchingPlaceholder'] || 'Searching...',
107
+ }
108
+ ],
109
+ 'value',
110
+ 'label',
111
+ true
112
+ );
113
+ },
114
+ async doSearch(searchTerm){
115
+ await this.setLoading();
116
+
117
+ if (this.searchController) {
118
+ this.searchController.abort();
119
+ }
120
+ this.searchController = new AbortController();
121
+
122
+ try {
123
+ const res = await fetch(this.search?.url + '?query=' + encodeURIComponent(searchTerm), {
124
+ method: 'POST',
125
+ signal: this.searchController.signal,
126
+ });
127
+ const data = await res.json();
128
+
129
+ this.choices.clearChoices();
130
+ await this.choices.setChoices(
131
+ data.map(item => ({
132
+ id: item.id,
133
+ name: item.name,
134
+ })),
135
+ 'id',
136
+ 'name',
137
+ true
138
+ );
139
+ this.loading = false;
140
+ } catch (err) {
141
+ this.loading = false;
142
+ this.choices.clearChoices();
143
+ if (err.name !== 'AbortError') {
144
+ console.error(err);
145
+ }
146
+ }
147
+
148
+ }
149
+ },
150
+ mounted() {
151
+ if(this.locale === 'en' && document.dir === 'rtl'){
152
+ this.currentLocale = 'fa';
153
+ }
154
+
155
+ this.init()
156
+ },
157
+ beforeUnmount() {
158
+ this.destroy()
159
+ },
160
+ data() {
161
+ return {
162
+ choices: null,
163
+ loading: false,
164
+ searchController: null,
165
+ currentLocale: this.locale,
166
+ localeTranslates: {
167
+ 'fa' : {
168
+ placeholderValue: 'انتخاب کنید...',
169
+ searchPlaceholderValue: 'برای جستجو تایپ کنید...',
170
+ searchingPlaceholder: 'در حال جستجو...',
171
+ loadingText: 'در حال جستجو...',
172
+ noResultsText: 'نتیجه ای یافت نشد',
173
+ noChoicesText: 'گزینه ای وجود ندارد',
174
+ }
175
+ },
176
+ }
177
+ }
178
+ }
179
+ </script>
180
+ <template>
181
+ <select
182
+ :name="name"
183
+ v-model="modelValue"
184
+ class="form-control-select"
185
+ :class="{'form-control-select--loading': loading}"
186
+ :placeholder="placeholder"
187
+ ref="input">
188
+ <option :value="item.id" v-for="(item, key) in options">{{item.name}}</option>
189
+ </select>
190
+ </template>
@@ -0,0 +1,158 @@
1
+ <script>
2
+ import {inject} from 'vue';
3
+
4
+ export default {
5
+ emits: ['update:modelValue'],
6
+ props: {
7
+ name: {
8
+ type: String,
9
+ default: null,
10
+ },
11
+ modelValue: {
12
+ type: Number,
13
+ default: 0
14
+ },
15
+ value: {
16
+ type: Number,
17
+ default: 0
18
+ },
19
+ maxStars: {
20
+ type: Number,
21
+ default: 5
22
+ },
23
+ precision: {
24
+ type: Number,
25
+ default: 2
26
+ },
27
+ showValue: {
28
+ type: Boolean,
29
+ default: true
30
+ },
31
+ readonly: {
32
+ type: Boolean,
33
+ default: false
34
+ },
35
+ },
36
+ setup(props) {
37
+ let form = inject('form');
38
+ if (form === undefined) {
39
+ form = {
40
+ errors: {},
41
+ getID(name) {
42
+ }
43
+ };
44
+ }
45
+ return {form};
46
+ },
47
+ computed: {
48
+ displayRating() {
49
+ return (this.hoveredRating ?? (this.modelValue ?? (this.name ? this.form[this.name] : null)) ?? this.value).toFixed(1);
50
+ }
51
+ },
52
+ methods: {
53
+ getStarFill(star) {
54
+ const rating = this.hoveredRating ?? (this.modelValue || this.form[this.name]);
55
+ const fullStars = Math.floor(rating);
56
+ const remainder = rating - fullStars;
57
+
58
+ if (star <= fullStars) return 100;
59
+ if (star === fullStars + 1) return remainder * 100;
60
+ return 0;
61
+ },
62
+ setHover(event) {
63
+ if (this.readonly) return;
64
+
65
+ const rect = this.$refs.ratingContainer.getBoundingClientRect();
66
+ let offsetX = event.clientX - rect.left;
67
+
68
+ if (this.isRTL) {
69
+ offsetX = rect.width - (event.clientX - rect.left); // اصلاح برای حالت راست‌چین
70
+ }
71
+
72
+ const starWidth = rect.width / this.maxStars;
73
+ let newRating = Math.round((offsetX / starWidth) * this.precision) / this.precision;
74
+ newRating = Math.min(newRating, this.maxStars);
75
+
76
+ this.hoveredRating = newRating;
77
+ },
78
+ updateRating() {
79
+ if (this.readonly) return;
80
+
81
+ if (this.name) this.form[this.name] = this.hoveredRating.toFixed(1);
82
+ this.$emit('update:modelValue', this.hoveredRating.toFixed(1));
83
+ },
84
+ resetHover() {
85
+ if (this.readonly) return;
86
+
87
+ this.hoveredRating = null;
88
+ }
89
+ },
90
+ mounted() {
91
+ if (this.$refs.ratingContainer) {
92
+ this.isRTL = getComputedStyle(this.$refs.ratingContainer).direction === 'rtl';
93
+ }
94
+ },
95
+ data() {
96
+ return {
97
+ hoveredRating: parseFloat(this.modelValue || ((this.name ? this.form[this.name] : null) ?? this.value)),
98
+ isRTL: false,
99
+ }
100
+ }
101
+ }
102
+ </script>
103
+ <template>
104
+ <div class="star-rating">
105
+ <div class="star-rating-container" ref="ratingContainer" @mouseleave="resetHover" @mousemove="setHover" @click="updateRating">
106
+ <div v-for="star in maxStars" :key="star" class="star-wrapper">
107
+ <div class="star-bg">★</div>
108
+ <span class="star-overlay" :style="{ width: getStarFill(star) + '%' }">★</span>
109
+ </div>
110
+ </div>
111
+ <div class="rating-value fanum" v-if="showValue && displayRating> 0">{{ displayRating }}</div>
112
+ </div>
113
+ </template>
114
+
115
+ <style scoped>
116
+ .star-rating,
117
+ .star-rating-container {
118
+ display: flex;
119
+ align-items: center;
120
+ }
121
+
122
+ .star-rating {
123
+ gap: 8px;
124
+ }
125
+
126
+ .star-rating .star-rating-container {
127
+ cursor: pointer;
128
+ position: relative;
129
+ }
130
+
131
+ .star-rating .star-wrapper {
132
+ position: relative;
133
+ font-size: 28px;
134
+ line-height: 1;
135
+ display: inline-block;
136
+ }
137
+
138
+ .star-rating .star-bg {
139
+ color: #ccc;
140
+ }
141
+
142
+ .star-rating .star-overlay {
143
+ position: absolute;
144
+ top: 0;
145
+ left: 0; /* 👈 اصلاح برای RTL */
146
+ overflow: hidden;
147
+ color: gold;
148
+ white-space: nowrap;
149
+ width: 0;
150
+ transition: width 0.2s ease-in-out;
151
+ }
152
+
153
+ .star-rating .rating-value {
154
+ font-size: 18px;
155
+ color: #444;
156
+ margin-right: 8px;
157
+ }
158
+ </style>
@@ -1,5 +1,5 @@
1
1
  <script>
2
- import Spinner from "@/Bootstrap/Spinner.vue";
2
+ import {Spinner} from "vue3-bootstrap-components";
3
3
 
4
4
  export default {
5
5
  components: {
@@ -15,6 +15,8 @@ export default {
15
15
  :disabled="form?.processing"
16
16
  class="btn btn-primary px-3 px-sm-4">
17
17
  <Spinner v-if="form?.processing"/>
18
- تایید و ثبت اطلاعات
18
+ <slot>
19
+ تایید و ثبت اطلاعات
20
+ </slot>
19
21
  </button>
20
22
  </template>
@@ -0,0 +1,44 @@
1
+ <script>
2
+ import {computed, inject} from "vue";
3
+
4
+ export default {
5
+ props: {
6
+ name: {
7
+ type: String,
8
+ required: true,
9
+ },
10
+ },
11
+ setup(props) {
12
+ let form = inject('form', {
13
+ errors: {},
14
+ getID(name) {
15
+ return name;
16
+ }
17
+ });
18
+ let group = inject('group', {});
19
+
20
+ const modelValue = computed({
21
+ get() {
22
+ return (group.value && form.value[group.value.name]) ? group.value?.getData(props.name) : form.value[props.name];
23
+ },
24
+ set(value) {
25
+ if (group?.value?.name) {
26
+ group.value.setData(props.name, value);
27
+ } else {
28
+ form.value[props.name] = value;
29
+ }
30
+ }
31
+ });
32
+
33
+ return {modelValue, form, group};
34
+ },
35
+ }
36
+ </script>
37
+ <template>
38
+ <textarea
39
+ :name="name"
40
+ v-model="modelValue"
41
+ :class="{'is-invalid': form?.errors[name] !== undefined}"
42
+ :disabled="form?.processing"
43
+ class="form-control"></textarea>
44
+ </template>
@@ -0,0 +1,51 @@
1
+ <script>
2
+ import {computed, inject} from "vue";
3
+
4
+ export default {
5
+ props: {
6
+ name: {
7
+ type: String,
8
+ required: true,
9
+ },
10
+ },
11
+ computed: {
12
+ inputID() {
13
+ return this.form.getID(this);
14
+ }
15
+ },
16
+ setup(props) {
17
+ let form = inject('form', {
18
+ errors: {},
19
+ getID(name) {
20
+ return name;
21
+ }
22
+ });
23
+ let group = inject('group', {});
24
+
25
+ const modelValue = computed({
26
+ get() {
27
+ return (group.value && form.value[group.value.name]) ? group.value?.getData(props.name) : form.value[props.name];
28
+ },
29
+ set(value) {
30
+ if (group?.value?.name) {
31
+ group.value.setData(props.name, value);
32
+ } else {
33
+ form.value[props.name] = value;
34
+ }
35
+ }
36
+ });
37
+
38
+ return {modelValue, form, group};
39
+ },
40
+ }
41
+ </script>
42
+ <template>
43
+ <input
44
+ :name="name"
45
+ :id="inputID"
46
+ v-model="modelValue"
47
+ :class="{'is-invalid': form?.errors[name] !== undefined}"
48
+ :disabled="form?.processing"
49
+ type="text"
50
+ class="form-control">
51
+ </template>
@@ -0,0 +1,124 @@
1
+ <script>
2
+ import "leaflet/dist/leaflet.css";
3
+ import {LMap, LTileLayer, LMarker} from "@vue-leaflet/vue-leaflet";
4
+ import {inject} from "vue";
5
+
6
+ export default {
7
+ name: 'locationInput',
8
+ components: {
9
+ LMap, LTileLayer, LMarker
10
+ },
11
+ props: {
12
+ name: {
13
+ type: String,
14
+ default: 'location',
15
+ required: false,
16
+ },
17
+ disabled: {
18
+ type: [Boolean, String],
19
+ default: false,
20
+ },
21
+ modelValue: Object,
22
+ },
23
+ setup(props) {
24
+ let form = inject('form');
25
+
26
+ if (form === undefined) {
27
+ form = {
28
+ errors: {},
29
+ zoom: 8,
30
+ center: [47.41322, -1.219482],
31
+ getID(name) {
32
+ }
33
+ };
34
+ }
35
+
36
+ return {form};
37
+ },
38
+ methods: {
39
+ setMarker(event) {
40
+ this.marker = {
41
+ lat: event?.latlng?.lat || event?.target?._latlng?.lat,
42
+ lng: event?.latlng?.lng || event?.target?._latlng?.lng,
43
+ };
44
+
45
+ this.$emit('update:modelValue', this.marker);
46
+ this.form[this.name] = this.marker;
47
+ }
48
+ },
49
+ data() {
50
+ return {
51
+ zoom: 13,
52
+ marker: {
53
+ lat: this.modelValue?.lat || this.form[this.name]?.lat || null,
54
+ lng: this.modelValue?.lng || this.form[this.name]?.lng || null,
55
+ },
56
+ center: {
57
+ lat: this.modelValue?.lat || this.form[this.name]?.lat || 35.69293,
58
+ lng: this.modelValue?.lng || this.form[this.name]?.lng || 51.36372,
59
+ },
60
+ };
61
+ },
62
+
63
+ }
64
+ </script>
65
+ <template>
66
+ <div class="location-map-container"
67
+ :class="{disabled: disabled || form.processing, 'is-invalid': form?.errors[name] !== undefined}">
68
+ <l-map ref="map" v-model:zoom="zoom" :useGlobalLeaflet="false" :center="this.center" @click="setMarker">
69
+ <l-tile-layer
70
+ url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
71
+ layer-type="base"
72
+ name="OpenStreetMap"
73
+ ></l-tile-layer>
74
+ <LMarker :lat-lng="marker" v-if="marker" :draggable="true" @dragend="setMarker"/>
75
+ </l-map>
76
+ </div>
77
+ </template>
78
+ <style>
79
+ .location-map-container {
80
+ position: relative;
81
+ width: 100%;
82
+ height: 300px;
83
+ overflow: hidden;
84
+ border-radius: var(--bs-border-radius);
85
+ }
86
+
87
+ .location-map-container.is-invalid {
88
+ border: 1px solid var(--bs-form-invalid-border-color);
89
+ }
90
+
91
+ .location-map-container[disabled],
92
+ .location-map-container.disabled:before {
93
+ content: "";
94
+ background-color: rgba(var(--bs-light-rgb), 0.4);
95
+ display: block;
96
+ position: absolute;
97
+ top: 0;
98
+ right: 0;
99
+ left: 0;
100
+ bottom: 0;
101
+ z-index: 9999;
102
+ }
103
+
104
+ .location-map-container {
105
+ .leaflet-pane,
106
+ .leaflet-tile,
107
+ .leaflet-marker-icon,
108
+ .leaflet-marker-shadow,
109
+ .leaflet-tile-container,
110
+ .leaflet-pane > svg,
111
+ .leaflet-pane > canvas,
112
+ .leaflet-zoom-box,
113
+ .leaflet-image-layer,
114
+ .leaflet-layer {
115
+ left: auto;
116
+ right: 0;
117
+ }
118
+
119
+ .leaflet-control-attribution.leaflet-control {
120
+ display: none;
121
+ }
122
+ }
123
+
124
+ </style>
@@ -1,9 +0,0 @@
1
- <script>
2
- export default {}
3
- </script>
4
-
5
- <template>
6
- <div class="input-group">
7
- <slot/>
8
- </div>
9
- </template>
@@ -1,9 +0,0 @@
1
- <script>
2
- export default {}
3
- </script>
4
-
5
- <template>
6
- <div class="input-group-text">
7
- <slot/>
8
- </div>
9
- </template>