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,108 +1,130 @@
1
- <script setup>
2
- import { computed, inject, ref, onMounted } from "vue";
3
- import Dropzone from "dropzone";
4
- import "dropzone/dist/basic.css";
5
- import "dropzone/dist/dropzone.css";
6
-
7
- const props = defineProps({
8
- name: {
9
- type: String,
10
- required: true,
11
- },
12
- multiple: {
13
- type: Boolean,
14
- default: false,
15
- },
16
- modelValue: String,
17
- url: { type: String, default: "/upload" },
18
- options: { type: Object, default: () => ({}) },
19
- });
20
- const emit = defineEmits(["update:modelValue"]);
21
-
22
- const form = inject("form", {
23
- value: {},
24
- errors: {},
25
- getID: n => n,
26
- });
27
-
28
- const group = inject("group", null);
29
-
30
- const dz = ref(null);
31
- const dropzone = ref(null);
32
-
33
- const modelValue = computed({
34
- get() {
35
- return group?.value?.name
36
- ? group.value.getData(props.name)
37
- : form.value[props.name];
38
- },
39
- set(val) {
40
- if (group?.value?.name) {
41
- group.value.setData(props.name, val);
42
- } else {
43
- form.value[props.name] = val;
44
- }
45
- emit("update:modelValue", val);
46
- }
47
- });
48
-
49
- onMounted(() => {
50
- dropzone.value = new Dropzone(dz.value, {
51
- url: props.url,
52
- addRemoveLinks: true,
53
- maxFiles: props.multiple ? null : 1,
54
- ...props.options,
55
- });
56
-
57
- dropzone.value.on("error", (file, response) => {
58
- let message = "خطا در آپلود";
59
-
60
- if (typeof response === "string") {
61
- message = response;
62
- }
63
-
64
- if (response?.message) {
65
- message = response.message;
66
- }
67
-
68
- file.previewElement.classList.add("dz-error");
69
- const err = file.previewElement.querySelector("[data-dz-errormessage]");
70
- if (err) err.textContent = message;
71
- });
72
-
73
- dropzone.value.on("success", (file, response) => {
74
- const result = response.data ?? response;
75
-
76
- if(props.multiple){
77
- modelValue.value = [...(modelValue.value || []), result];
78
- }else{
79
- modelValue.value = result;
80
- }
81
- });
82
-
83
- dropzone.value.on("removedfile", async (file) => {
84
- let data = file.xhr.response;
85
- if (!data) return;
86
-
87
- try {
88
- data = JSON.parse(data);
89
- modelValue.value = (modelValue.value || []).filter(f => f.path !== data.path);
90
-
91
- await fetch(props.url, {
92
- method: "DELETE",
93
- headers: {
94
- "Content-Type": "application/json",
95
- 'Accept': 'application/json',
96
- },
97
- body: data.path
98
- });
99
- } catch (e) {
100
- console.error("حذف فایل در سرور با خطا مواجه شد", e);
101
- }
102
- });
1
+ <script>
2
+ import { defineComponent, onMounted, ref, watch } from 'vue';
3
+ import { fieldProps, useField } from './composables/useField.js';
4
+ import { useDisposables } from './composables/useDisposables.js';
5
+ import { useI18n } from './i18n.js';
6
+ import { laravelHeaders } from './utils/http.js';
7
+
8
+ /**
9
+ * Dropzone.js uploader. Import `dropzone/dist/dropzone.css` (and `basic.css`) in your application.
10
+ * Stored value: the JSON object returned by the endpoint (single) or an array of them (multiple).
11
+ */
12
+ export default defineComponent({
13
+ name: 'DropzoneInput',
14
+ emits: ['update:modelValue', 'change', 'ready', 'added', 'success', 'error', 'removed', 'complete'],
15
+ props: {
16
+ ...fieldProps,
17
+ multiple: { type: Boolean, default: false },
18
+ url: { type: String, default: '/upload' },
19
+ /** Dropzone options (merged over the defaults). */
20
+ options: { type: Object, default: () => ({}) },
21
+ headers: { type: Object, default: () => ({}) },
22
+ locale: { type: String, default: null },
23
+ },
24
+ setup(props, { emit, expose }) {
25
+ const field = useField(props, emit);
26
+ const { t } = useI18n(() => props.locale);
27
+ const d = useDisposables();
28
+
29
+ const el = ref(null);
30
+ let dz = null;
31
+ let resetting = false;
32
+
33
+ const asArray = () => (Array.isArray(field.model.value) ? field.model.value : []);
34
+
35
+ onMounted(async () => {
36
+ const { default: Dropzone } = await import('dropzone');
37
+ if (d.isDisposed() || !el.value) return;
38
+
39
+ Dropzone.autoDiscover = false;
40
+
41
+ dz = new Dropzone(el.value, {
42
+ url: props.url,
43
+ addRemoveLinks: true,
44
+ maxFiles: props.multiple ? null : 1,
45
+ headers: laravelHeaders(props.headers),
46
+ ...props.options,
47
+ });
48
+
49
+ dz.on('addedfile', (file) => emit('added', file));
50
+ dz.on('complete', (file) => emit('complete', file));
51
+
52
+ dz.on('error', (file, response) => {
53
+ let message = t('upload.failed');
54
+ if (typeof response === 'string') message = response;
55
+ if (response?.message) message = response.message;
56
+
57
+ file.previewElement?.classList.add('dz-error');
58
+ const target = file.previewElement?.querySelector('[data-dz-errormessage]');
59
+ if (target) target.textContent = message;
60
+ emit('error', file, message);
61
+ });
62
+
63
+ dz.on('success', (file, response) => {
64
+ const result = response?.data ?? response;
65
+ field.model.value = props.multiple ? [...asArray(), result] : result;
66
+ field.emitChange(field.model.value);
67
+ emit('success', file, result);
68
+ });
69
+
70
+ dz.on('removedfile', async (file) => {
71
+ emit('removed', file);
72
+ if (resetting) return;
73
+
74
+ let data = file.xhr?.response;
75
+ if (!data) return;
76
+
77
+ try {
78
+ data = JSON.parse(data);
79
+ const info = data.data ?? data;
80
+
81
+ field.model.value = props.multiple
82
+ ? asArray().filter((f) => (f?.path ?? f) !== info.path)
83
+ : null;
84
+ field.emitChange(field.model.value);
85
+
86
+ await fetch(props.url, {
87
+ method: 'DELETE',
88
+ headers: laravelHeaders({ 'Content-Type': 'application/json', ...props.headers }),
89
+ credentials: 'same-origin',
90
+ body: info.path,
91
+ });
92
+ } catch (e) {
93
+ // eslint-disable-next-line no-console
94
+ console.error('[inertia-bootstrap-forms] Could not delete the uploaded file on the server', e);
95
+ }
96
+ });
97
+
98
+ // Release the dropzone (listeners, hidden input, previews) when the component unmounts
99
+ d.add(() => {
100
+ resetting = true;
101
+ try { dz?.destroy(); } catch (e) { /* noop */ }
102
+ dz = null;
103
+ });
104
+
105
+ emit('ready', dz);
106
+ });
107
+
108
+ // The form was reset: clear the previews without calling the DELETE endpoint
109
+ watch(
110
+ () => field.model.value,
111
+ (v) => {
112
+ const empty = v === null || v === undefined || v === '' || (Array.isArray(v) && !v.length);
113
+ if (empty && dz?.files?.length) {
114
+ resetting = true;
115
+ dz.removeAllFiles(true);
116
+ resetting = false;
117
+ }
118
+ }
119
+ );
120
+
121
+ expose({ dropzone: () => dz });
122
+
123
+ return { ...field, el };
124
+ },
103
125
  });
104
126
  </script>
105
127
 
106
128
  <template>
107
- <div class="inertia-dropzone-input dropzone" ref="dz"></div>
129
+ <div ref="el" class="inertia-dropzone-input dropzone" :class="{ 'is-invalid': isInvalid }" :id="inputId"></div>
108
130
  </template>
@@ -1,117 +1,58 @@
1
1
  <script>
2
- import Editor from '@tinymce/tinymce-vue';
3
- import {computed, defineComponent, inject, ref} from "vue";
2
+ import { defineAsyncComponent, defineComponent, ref, watch } from 'vue';
3
+ import { fieldProps, useField } from './composables/useField.js';
4
4
 
5
+ const Editor = defineAsyncComponent(() => import('@tinymce/tinymce-vue'));
6
+
7
+ /** TinyMCE rich text editor. The HTML is stored in the FormContainer's v-model. */
5
8
  export default defineComponent({
6
- emits: ['update:modelValue', 'setContent'],
7
- components: {Editor},
8
- props: {
9
- name: {
10
- type: String,
11
- required: true,
12
- },
13
- placeholder: {
14
- type: String,
15
- default: '',
16
- },
17
- disabled: {
18
- type: Boolean,
19
- default: false,
20
- },
21
- modelValue: String,
22
- options: {
23
- type: Object,
24
- default: {},
25
- required: false,
9
+ name: 'EditorInput',
10
+ components: { Editor },
11
+ emits: ['update:modelValue', 'change', 'init', 'focus', 'blur'],
12
+ props: {
13
+ ...fieldProps,
14
+ placeholder: { type: String, default: '' },
15
+ /** TinyMCE `init` options. */
16
+ options: { type: Object, default: () => ({}) },
17
+ height: { type: [Number, String], default: null },
18
+ /** Self-hosted TinyMCE script (`tinymceScriptSrc`) or Tiny Cloud api key. */
19
+ tinymceScriptSrc: { type: String, default: null },
20
+ apiKey: { type: String, default: null },
26
21
  },
27
- },
28
- setup(props, {expose, emit}) {
29
- const editorRef = ref(null)
30
- const tinyInstance = ref(null)
31
-
32
- const handleInit = (evt, editor) => {
33
- tinyInstance.value = editor
34
- }
35
-
36
- expose({
37
- editor: () => tinyInstance.value
38
- })
22
+ setup(props, { emit, expose }) {
23
+ const field = useField(props, emit);
24
+ const tiny = ref(null);
25
+
26
+ function onInit(event, editor) {
27
+ tiny.value = editor;
28
+ editor.mode?.set(field.isDisabled.value || props.readonly ? 'readonly' : 'design');
29
+ emit('init', editor);
30
+ }
39
31
 
40
- let form = inject('form', {
41
- errors: {},
42
- getID(name) {
43
- return name;
44
- }
45
- });
46
- let group = inject('group', {});
32
+ watch(
33
+ () => field.isDisabled.value || props.readonly,
34
+ (locked) => tiny.value?.mode?.set(locked ? 'readonly' : 'design')
35
+ );
47
36
 
48
- const modelValue = computed({
49
- get() {
50
- return (group.value && form.value[group.value.name]) ? group.value?.getData(props.name) : form.value[props.name];
51
- },
52
- set(value) {
53
- if (group?.value?.name) {
54
- group.value.setData(props.name, value);
55
- } else {
56
- form.value[props.name] = value;
57
- }
58
- }
59
- });
37
+ expose({ editor: () => tiny.value });
60
38
 
61
- return {modelValue, form, group, editorRef, handleInit};
62
- },
63
- })
39
+ return { ...field, onInit };
40
+ },
41
+ });
64
42
  </script>
65
43
 
66
44
  <template>
67
- <Editor
68
- ref="editorRef"
69
- @init="handleInit"
70
- v-model="modelValue"
71
- class="tiny-editor-input-el"
72
- :init="{
73
- disabled: this.disabled,
74
- placeholder: this.placeholder,
75
- ...options
76
- }"
77
- />
45
+ <Editor
46
+ v-model="model"
47
+ :id="inputId"
48
+ :disabled="isDisabled || readonly"
49
+ :tinymce-script-src="tinymceScriptSrc || undefined"
50
+ :api-key="apiKey || undefined"
51
+ :init="{ placeholder, ...(height ? { height } : {}), ...options }"
52
+ class="tiny-editor-input-el"
53
+ @init="onInit"
54
+ @change="emitChange(model, $event)"
55
+ @focus="$emit('focus', $event)"
56
+ @blur="$emit('blur', $event)"
57
+ />
78
58
  </template>
79
- <style>
80
- textarea.tiny-editor-input-el {
81
- width: 100%;
82
- min-height: 200px;
83
- }
84
-
85
- textarea.tiny-editor-input-el,
86
- .tox.tox-tinymce {
87
- background-color: transparent;
88
- border: 0;
89
- outline: none;
90
- background-image: url("css/loader.gif");
91
- background-repeat: no-repeat;
92
- background-position: center center;
93
- }
94
-
95
- .tox .tox-edit-area::before {
96
- display: none;
97
- }
98
-
99
- .tox .tox-throbber{
100
- z-index: var(--bs-dropdown-zindex, 900) !important;
101
- }
102
-
103
- .tox:not(.tox-tinymce-inline) .tox-editor-header,
104
- .tox .tox-toolbar-overlord,
105
- .tox .tox-toolbar, .tox .tox-toolbar__overflow, .tox .tox-toolbar__primary,
106
- .tox .tox-statusbar{
107
- background-color: var(--bs-body-bg, #fff) !important;
108
- color: var(--bs-body-color, #000) !important;
109
- }
110
-
111
- .tox .tox-statusbar a,
112
- .tox .tox-statusbar__path-item,
113
- .tox .tox-statusbar__wordcount,
114
- .tox .tox-statusbar{
115
- color: var(--bs-secondary-color, #cccc) !important;
116
- }
117
- </style>
@@ -1,23 +1,24 @@
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="email"). All TextInput props and events are forwarded. */
5
7
  export default defineComponent({
6
- components: {
7
- TextInput
8
- },
8
+ name: 'EmailInput',
9
+ components: { TextInput },
9
10
  props: {
10
- FileInput: {
11
- type: String,
12
- default: 'email',
13
- required: true,
14
- },
11
+ name: { type: String, default: 'email' },
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
- <TextInput
20
- :name="name"
21
- placeholder="ایمیل خود را وارد کنید"
22
- type="email"/>
23
+ <TextInput type="email" autocomplete="email" :name="name" :placeholder="placeholder ?? t('email.placeholder')" />
23
24
  </template>