Skip to content

Commit

Permalink
feat(useAsyncValidator): add immediate and execute (#2899)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
jaw52 and antfu committed Mar 23, 2023
1 parent 7762df4 commit 7917665
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 40 deletions.
10 changes: 5 additions & 5 deletions packages/.vitepress/theme/components/FunctionsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,12 @@ function toggleSort(method: string) {
</label>
</div>
</div>
<div h="1px" bg="$vp-c-divider-light" m="t-4" />
<div h="1px" bg="$vp-c-divider" m="t-4" />
<div flex="~" class="children:my-auto" p="2">
<i i-carbon-search m="r-2" opacity="50" />
<input v-model="search" class="w-full" type="text" role="search" placeholder="Search...">
</div>
<div h="1px" bg="$vp-c-divider-light" m="b-4" />
<div h="1px" bg="$vp-c-divider" m="b-4" />
<div flex="~ col" gap="2" class="relative" p="t-5">
<div v-if="hasFilters" class="transition mb-2 opacity-60 absolute -top-3 right-0 z-10">
<button class="select-button flex gap-1 items-center !px-2 !py-1" @click="resetFilters()">
Expand Down Expand Up @@ -173,9 +173,9 @@ function toggleSort(method: string) {
<style scoped lang="postcss">
input {
--tw-ring-offset-width: 1px !important;
--tw-ring-color: #8885 !important;
--tw-ring-offset-color: transparent !important;
--un-ring-offset-width: 1px !important;
--un-ring-color: #8885 !important;
--un-ring-offset-color: transparent !important;
}
.checkbox {
Expand Down
8 changes: 4 additions & 4 deletions packages/.vitepress/theme/styles/demo.css
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
display: block;
font-size: 0.9rem;
padding: 0.5em 1em 0.4em 1em;
border: 1px solid var(--vp-c-divider-light);
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
outline: none;
background: var(--vp-c-bg);
Expand All @@ -132,7 +132,7 @@
}

input:focus {
border: 1px solid var(--vp-c-divider-dark-1);
border: 1px solid var(--vp-c-border);
}

pre,
Expand All @@ -157,7 +157,7 @@
padding: 1rem;
width: 200px;
height: 200px;
border: 1px solid var(--vp-c-divider-light);
border: 1px solid var(--vp-c-divider);
border-radius: 4px;
background: white;
outline: none;
Expand All @@ -171,7 +171,7 @@
bottom: 0;
right: 0;
padding: 1rem 2rem;
border: 1px solid var(--vp-c-divider-light);
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
z-index: 100;
min-width: 100px;
Expand Down
4 changes: 2 additions & 2 deletions packages/core/onClickOutside/demo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const dropdownHandler: OnClickOutsideHandler = (event) => {
background-color: var(--vp-c-bg);
padding: 0.4em 2em;
border-radius: 5px;
border: 1px solid var(--vp-c-divider-light);
border: 1px solid var(--vp-c-divider);
box-shadow: 2px 2px 10px rgba(10, 10, 10, 0.1);
}
.dropdown-inner {
Expand All @@ -75,7 +75,7 @@ const dropdownHandler: OnClickOutsideHandler = (event) => {
left: 0;
z-index: 10;
border-radius: 5px;
border: 1px solid var(--vp-c-divider-light);
border: 1px solid var(--vp-c-divider);
box-shadow: 2px 2px 5px rgba(10, 10, 10, 0.1);
}
.heading {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/useConfirmDialog/demo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ dialog2.onCancel(() => {
height: 100%;
}
.inner {
background-color: var(--vp-c-bg);
background-color: var(--vp-c-bg);
padding: 0.4em 2em;
border-radius: 5px;
border: 1px solid var(--vp-c-divider-light);
border: 1px solid var(--vp-c-divider);
box-shadow: 2px 2px 10px rgba(10, 10, 10, 0.1);
}
.small {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/useSpeechSynthesis/demo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const stop = () => {

<br>
<label class="font-bold mr-2">Language</label>
<div bg="$vp-c-bg" border="$vp-c-divider-light 1" inline-flex items-center relative rounded>
<div bg="$vp-c-bg" border="$vp-c-divider 1" inline-flex items-center relative rounded>
<i i-carbon-language absolute left-2 opacity-80 pointer-events-none />
<select v-model="voice" px-8 border-0 bg-transparent h-9 rounded appearance-none>
<option bg="$vp-c-bg" disabled>
Expand Down
28 changes: 16 additions & 12 deletions packages/integrations/useAsyncValidator/demo.client.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,53 +28,57 @@ const { pass, isFinished, errorFields } = useAsyncValidator(form, rules)

<template>
<div>
pass:
<code>pass:</code>
<BooleanDisplay :value="pass" />
</div>
<div>
isFinished:
<code>isFinished:</code>
<BooleanDisplay :value="isFinished" />
</div>

<div class="bg-base border-main rounded shadow max-w-96 p-8">
<hr>

<div flex="~ col gap-2">
<div>
email:
Email
<input
v-model="form.email"
:class="{ '!border-red': errorFields?.email?.length }"
type="text"
placeholder="email"
placeholder="Email"
>
<div v-if="errorFields?.email?.length" text-red>
{{ errorFields.email[0].message }}
</div>
</div>
<div>
name:
Name
<input
v-model="form.name"
:class="{ '!border-red': errorFields?.name?.length }"
type="text"
placeholder="name"
placeholder="Name"
>
<div v-if="errorFields?.name?.length" text-red>
{{ errorFields.name[0].message }}
</div>
</div>
<div>
age:
Age
<input
v-model="form.age"
:class="{ '!border-red': errorFields?.age?.length }"
type="number"
placeholder="age"
placeholder="Age"
>
<div v-if="errorFields?.age?.length" text-red>
{{ errorFields.age[0].message }}
</div>
</div>
<button :disabled="!pass">
submit
</button>
<div>
<button :disabled="!pass">
Submit
</button>
</div>
</div>
</template>
72 changes: 72 additions & 0 deletions packages/integrations/useAsyncValidator/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,44 @@ describe('useAsyncValidator', () => {
})
})

it('immediate should can be work', async () => {
const rules: Rules = {
name: {
type: 'string',
},
age: {
type: 'number',
},
}
const { pass, errors, isFinished, then } = useAsyncValidator(form, rules, { immediate: false })
expect(isFinished.value).toBe(false)
expect(pass.value).toBe(true)
expect(errors.value).toMatchObject([])

then(() => {
expect(isFinished.value).toBe(false)
expect(pass.value).toBe(true)
expect(errors.value).toMatchObject([])
})
})

it('execute should can be work', async () => {
const rules: Rules = {
name: {
type: 'string',
},
age: {
type: 'number',
},
}
const { isFinished, execute } = useAsyncValidator(form, rules, { immediate: false })
const { pass, errors } = await execute()

expect(isFinished.value).toBe(true)
expect(pass).toBe(true)
expect(errors).toMatchObject([])
})

it('should can be await', async () => {
const rules: Rules = {
name: {
Expand Down Expand Up @@ -103,6 +141,40 @@ describe('useAsyncValidator', () => {
`)
})

it('should fail to validate when use execute', async () => {
const rules: Rules = {
name: {
type: 'string',
min: 5,
max: 20,
message: 'name length must be 5-20',
},
age: {
type: 'number',
},
}

const { execute } = useAsyncValidator(form, rules, {
validateOption: {
suppressWarning: true,
},
immediate: false,
})

const { pass, errors } = await execute()

expect(pass).toBe(false)
expect(errors).toMatchInlineSnapshot(`
[
{
"field": "name",
"fieldValue": "jelf",
"message": "name length must be 5-20",
},
]
`)
})

it('should reactive', async () => {
const form = ref({
name: 'jelf',
Expand Down
56 changes: 43 additions & 13 deletions packages/integrations/useAsyncValidator/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { MaybeComputedRef } from '@vueuse/shared'
import { resolveUnref, until } from '@vueuse/shared'
import { resolveRef, resolveUnref, until } from '@vueuse/shared'
import Schema from 'async-validator'
import type { Rules, ValidateError, ValidateOption } from 'async-validator'
import type { Ref } from 'vue-demi'
import { computed, ref, watchEffect } from 'vue-demi'
import { computed, ref, shallowRef, watch } from 'vue-demi'

// @ts-expect-error Schema.default is exist in ssr mode
const AsyncValidatorSchema = Schema.default || Schema
Expand All @@ -13,19 +13,28 @@ export type AsyncValidatorError = Error & {
fields: Record<string, ValidateError[]>
}

export interface UseAsyncValidatorExecuteReturn {
pass: boolean
errors: AsyncValidatorError['errors'] | undefined
errorInfo: AsyncValidatorError | null
errorFields: AsyncValidatorError['fields'] | undefined
}

export interface UseAsyncValidatorReturn {
pass: Ref<boolean>
errorInfo: Ref<AsyncValidatorError | null>
isFinished: Ref<boolean>
errors: Ref<AsyncValidatorError['errors'] | undefined>
errorInfo: Ref<AsyncValidatorError | null>
errorFields: Ref<AsyncValidatorError['fields'] | undefined>
execute: () => Promise<UseAsyncValidatorExecuteReturn>
}

export interface UseAsyncValidatorOptions {
/**
* @see https://github.com/yiminghe/async-validator#options
*/
validateOption?: ValidateOption
immediate?: boolean
}

/**
Expand All @@ -39,20 +48,27 @@ export function useAsyncValidator(
rules: MaybeComputedRef<Rules>,
options: UseAsyncValidatorOptions = {},
): UseAsyncValidatorReturn & PromiseLike<UseAsyncValidatorReturn> {
const errorInfo = ref<AsyncValidatorError | null>()
const isFinished = ref(false)
const pass = ref(false)
const {
validateOption = {},
immediate = true,
} = options

const valueRef = resolveRef(value)

const errorInfo = shallowRef<AsyncValidatorError | null>(null)
const isFinished = ref(true)
const pass = ref(!immediate)
const errors = computed(() => errorInfo.value?.errors || [])
const errorFields = computed(() => errorInfo.value?.fields || {})

const { validateOption = {} } = options
const validator = computed(() => new AsyncValidatorSchema(resolveUnref(rules)))

watchEffect(async () => {
const execute = async (): Promise<UseAsyncValidatorExecuteReturn> => {
isFinished.value = false
pass.value = false
const validator = new AsyncValidatorSchema(resolveUnref(rules))

try {
await validator.validate(resolveUnref(value), validateOption)
await validator.value.validate(valueRef.value, validateOption)
pass.value = true
errorInfo.value = null
}
Expand All @@ -62,14 +78,28 @@ export function useAsyncValidator(
finally {
isFinished.value = true
}
})

return {
pass: pass.value,
errorInfo: errorInfo.value,
errors: errors.value,
errorFields: errorFields.value,
}
}

watch(
[valueRef, validator],
() => execute(),
{ immediate, deep: true },
)

const shell = {
pass,
isFinished,
errorInfo,
pass,
errors,
errorInfo,
errorFields,
execute,
} as UseAsyncValidatorReturn

function waitUntilFinished() {
Expand Down
2 changes: 1 addition & 1 deletion unocss.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {

export default defineConfig({
shortcuts: {
'border-main': 'border-gray-400 border-opacity-30',
'border-main': 'border-$vp-c-divider',
'bg-main': 'bg-gray-400',
'bg-base': 'bg-white dark:bg-hex-1a1a1a',
},
Expand Down

0 comments on commit 7917665

Please sign in to comment.