validation.mjs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. // Composables
  2. import { makeFocusProps } from "./focus.mjs";
  3. import { useForm } from "./form.mjs";
  4. import { useProxiedModel } from "./proxiedModel.mjs";
  5. import { useToggleScope } from "./toggleScope.mjs"; // Utilities
  6. import { computed, nextTick, onBeforeMount, onBeforeUnmount, onMounted, ref, shallowRef, unref, watch } from 'vue';
  7. import { getCurrentInstanceName, getUid, propsFactory, wrapInArray } from "../util/index.mjs"; // Types
  8. export const makeValidationProps = propsFactory({
  9. disabled: {
  10. type: Boolean,
  11. default: null
  12. },
  13. error: Boolean,
  14. errorMessages: {
  15. type: [Array, String],
  16. default: () => []
  17. },
  18. maxErrors: {
  19. type: [Number, String],
  20. default: 1
  21. },
  22. name: String,
  23. label: String,
  24. readonly: {
  25. type: Boolean,
  26. default: null
  27. },
  28. rules: {
  29. type: Array,
  30. default: () => []
  31. },
  32. modelValue: null,
  33. validateOn: String,
  34. validationValue: null,
  35. ...makeFocusProps()
  36. }, 'validation');
  37. export function useValidation(props) {
  38. let name = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : getCurrentInstanceName();
  39. let id = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : getUid();
  40. const model = useProxiedModel(props, 'modelValue');
  41. const validationModel = computed(() => props.validationValue === undefined ? model.value : props.validationValue);
  42. const form = useForm();
  43. const internalErrorMessages = ref([]);
  44. const isPristine = shallowRef(true);
  45. const isDirty = computed(() => !!(wrapInArray(model.value === '' ? null : model.value).length || wrapInArray(validationModel.value === '' ? null : validationModel.value).length));
  46. const isDisabled = computed(() => !!(props.disabled ?? form?.isDisabled.value));
  47. const isReadonly = computed(() => !!(props.readonly ?? form?.isReadonly.value));
  48. const errorMessages = computed(() => {
  49. return props.errorMessages.length ? wrapInArray(props.errorMessages).slice(0, Math.max(0, +props.maxErrors)) : internalErrorMessages.value;
  50. });
  51. const validateOn = computed(() => {
  52. let value = (props.validateOn ?? form?.validateOn.value) || 'input';
  53. if (value === 'lazy') value = 'input lazy';
  54. const set = new Set(value?.split(' ') ?? []);
  55. return {
  56. blur: set.has('blur') || set.has('input'),
  57. input: set.has('input'),
  58. submit: set.has('submit'),
  59. lazy: set.has('lazy')
  60. };
  61. });
  62. const isValid = computed(() => {
  63. if (props.error || props.errorMessages.length) return false;
  64. if (!props.rules.length) return true;
  65. if (isPristine.value) {
  66. return internalErrorMessages.value.length || validateOn.value.lazy ? null : true;
  67. } else {
  68. return !internalErrorMessages.value.length;
  69. }
  70. });
  71. const isValidating = shallowRef(false);
  72. const validationClasses = computed(() => {
  73. return {
  74. [`${name}--error`]: isValid.value === false,
  75. [`${name}--dirty`]: isDirty.value,
  76. [`${name}--disabled`]: isDisabled.value,
  77. [`${name}--readonly`]: isReadonly.value
  78. };
  79. });
  80. const uid = computed(() => props.name ?? unref(id));
  81. onBeforeMount(() => {
  82. form?.register({
  83. id: uid.value,
  84. validate,
  85. reset,
  86. resetValidation
  87. });
  88. });
  89. onBeforeUnmount(() => {
  90. form?.unregister(uid.value);
  91. });
  92. onMounted(async () => {
  93. if (!validateOn.value.lazy) {
  94. await validate(true);
  95. }
  96. form?.update(uid.value, isValid.value, errorMessages.value);
  97. });
  98. useToggleScope(() => validateOn.value.input, () => {
  99. watch(validationModel, () => {
  100. if (validationModel.value != null) {
  101. validate();
  102. } else if (props.focused) {
  103. const unwatch = watch(() => props.focused, val => {
  104. if (!val) validate();
  105. unwatch();
  106. });
  107. }
  108. });
  109. });
  110. useToggleScope(() => validateOn.value.blur, () => {
  111. watch(() => props.focused, val => {
  112. if (!val) validate();
  113. });
  114. });
  115. watch(isValid, () => {
  116. form?.update(uid.value, isValid.value, errorMessages.value);
  117. });
  118. function reset() {
  119. model.value = null;
  120. nextTick(resetValidation);
  121. }
  122. function resetValidation() {
  123. isPristine.value = true;
  124. if (!validateOn.value.lazy) {
  125. validate(true);
  126. } else {
  127. internalErrorMessages.value = [];
  128. }
  129. }
  130. async function validate() {
  131. let silent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  132. const results = [];
  133. isValidating.value = true;
  134. for (const rule of props.rules) {
  135. if (results.length >= +(props.maxErrors ?? 1)) {
  136. break;
  137. }
  138. const handler = typeof rule === 'function' ? rule : () => rule;
  139. const result = await handler(validationModel.value);
  140. if (result === true) continue;
  141. if (result !== false && typeof result !== 'string') {
  142. // eslint-disable-next-line no-console
  143. console.warn(`${result} is not a valid value. Rule functions must return boolean true or a string.`);
  144. continue;
  145. }
  146. results.push(result || '');
  147. }
  148. internalErrorMessages.value = results;
  149. isValidating.value = false;
  150. isPristine.value = silent;
  151. return internalErrorMessages.value;
  152. }
  153. return {
  154. errorMessages,
  155. isDirty,
  156. isDisabled,
  157. isReadonly,
  158. isPristine,
  159. isValid,
  160. isValidating,
  161. reset,
  162. resetValidation,
  163. validate,
  164. validationClasses
  165. };
  166. }
  167. //# sourceMappingURL=validation.mjs.map