validation.mjs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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 { getCurrentInstance, 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(props);
  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 errorMessages = computed(() => {
  47. return props.errorMessages?.length ? wrapInArray(props.errorMessages).concat(internalErrorMessages.value).slice(0, Math.max(0, +props.maxErrors)) : internalErrorMessages.value;
  48. });
  49. const validateOn = computed(() => {
  50. let value = (props.validateOn ?? form.validateOn?.value) || 'input';
  51. if (value === 'lazy') value = 'input lazy';
  52. if (value === 'eager') value = 'input eager';
  53. const set = new Set(value?.split(' ') ?? []);
  54. return {
  55. input: set.has('input'),
  56. blur: set.has('blur') || set.has('input') || set.has('invalid-input'),
  57. invalidInput: set.has('invalid-input'),
  58. lazy: set.has('lazy'),
  59. eager: set.has('eager')
  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`]: form.isDisabled.value,
  77. [`${name}--readonly`]: form.isReadonly.value
  78. };
  79. });
  80. const vm = getCurrentInstance('validation');
  81. const uid = computed(() => props.name ?? unref(id));
  82. onBeforeMount(() => {
  83. form.register?.({
  84. id: uid.value,
  85. vm,
  86. validate,
  87. reset,
  88. resetValidation
  89. });
  90. });
  91. onBeforeUnmount(() => {
  92. form.unregister?.(uid.value);
  93. });
  94. onMounted(async () => {
  95. if (!validateOn.value.lazy) {
  96. await validate(!validateOn.value.eager);
  97. }
  98. form.update?.(uid.value, isValid.value, errorMessages.value);
  99. });
  100. useToggleScope(() => validateOn.value.input || validateOn.value.invalidInput && isValid.value === false, () => {
  101. watch(validationModel, () => {
  102. if (validationModel.value != null) {
  103. validate();
  104. } else if (props.focused) {
  105. const unwatch = watch(() => props.focused, val => {
  106. if (!val) validate();
  107. unwatch();
  108. });
  109. }
  110. });
  111. });
  112. useToggleScope(() => validateOn.value.blur, () => {
  113. watch(() => props.focused, val => {
  114. if (!val) validate();
  115. });
  116. });
  117. watch([isValid, errorMessages], () => {
  118. form.update?.(uid.value, isValid.value, errorMessages.value);
  119. });
  120. async function reset() {
  121. model.value = null;
  122. await nextTick();
  123. await resetValidation();
  124. }
  125. async function resetValidation() {
  126. isPristine.value = true;
  127. if (!validateOn.value.lazy) {
  128. await validate(!validateOn.value.eager);
  129. } else {
  130. internalErrorMessages.value = [];
  131. }
  132. }
  133. async function validate() {
  134. let silent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  135. const results = [];
  136. isValidating.value = true;
  137. for (const rule of props.rules) {
  138. if (results.length >= +(props.maxErrors ?? 1)) {
  139. break;
  140. }
  141. const handler = typeof rule === 'function' ? rule : () => rule;
  142. const result = await handler(validationModel.value);
  143. if (result === true) continue;
  144. if (result !== false && typeof result !== 'string') {
  145. // eslint-disable-next-line no-console
  146. console.warn(`${result} is not a valid value. Rule functions must return boolean true or a string.`);
  147. continue;
  148. }
  149. results.push(result || '');
  150. }
  151. internalErrorMessages.value = results;
  152. isValidating.value = false;
  153. isPristine.value = silent;
  154. return internalErrorMessages.value;
  155. }
  156. return {
  157. errorMessages,
  158. isDirty,
  159. isDisabled: form.isDisabled,
  160. isReadonly: form.isReadonly,
  161. isPristine,
  162. isValid,
  163. isValidating,
  164. reset,
  165. resetValidation,
  166. validate,
  167. validationClasses
  168. };
  169. }
  170. //# sourceMappingURL=validation.mjs.map