Skip to content

Commit

Permalink
feat: add useV7dObject composable
Browse files Browse the repository at this point in the history
  • Loading branch information
nozomuikuta committed Jan 8, 2024
1 parent f7cd12c commit 0ee61a8
Show file tree
Hide file tree
Showing 10 changed files with 378 additions and 58 deletions.
173 changes: 173 additions & 0 deletions packages/vue-v8n/src/composables/useV7dObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { computed, reactive } from 'vue'
import type { MaybeRefOrGetter, UnwrapRef } from 'vue'
import type { RuleDefinition, UseV7dOptions } from '../types'
import { useV7d } from './useV7d'

export function useV7dObject<T extends Record<string, unknown>>(
values: T,
rules: Partial<Record<keyof T, MaybeRefOrGetter<RuleDefinition[]>>>,
options?: Partial<Record<keyof T, UseV7dOptions>>
) {
type InitialValues = Record<keyof T, T[keyof T]>
type Values = Record<keyof T, ReturnType<typeof useV7d<T[keyof T]>>['value']['value']>

const _initialValues = {} as InitialValues
const _values = reactive({} as Values)
const $els = {} as Record<keyof T, ReturnType<typeof useV7d<T[keyof T]>>['$el']>
const _touched = {} as Record<keyof T, ReturnType<typeof useV7d<T[keyof T]>>['$touched']>
const _error = {} as Record<keyof T, ReturnType<typeof useV7d<T[keyof T]>>['$error']>
const _errors = {} as Record<keyof T, ReturnType<typeof useV7d<T[keyof T]>>['$errors']>
const _touchFns = {} as Record<keyof T, ReturnType<typeof useV7d<T[keyof T]>>['$touch']>
const _validateFns = {} as Record<keyof T, ReturnType<typeof useV7d<T[keyof T]>>['$validate']>
const _resetFns = {} as Record<keyof T, ReturnType<typeof useV7d<T[keyof T]>>['$reset']>
const _resetWithInitialValueFns = {} as Record<keyof T, ReturnType<typeof useV7d<T[keyof T]>>['$resetWithInitialValue']>
const _resetWithFn = {} as Record<keyof T, ReturnType<typeof useV7d<T[keyof T]>>['$resetWith']>

const _keys = Object.keys(values)

for (const key of _keys) {
const _key = key as keyof T
const {
value,
$el,
$touched,
$error,
$errors,
$touch,
$validate,
$reset,
$resetWithInitialValue,
$resetWith
} = useV7d(values[_key], rules[_key] ?? [], options?.[_key] ?? {})

_initialValues[_key] = values[_key]
;(_values as Values)[_key] = value.value
$els[_key] = $el
_touched[_key] = $touched
_error[_key] = $error
_errors[_key] = $errors
_touchFns[_key] = $touch
_validateFns[_key] = $validate
_resetFns[_key] = $reset
_resetWithInitialValueFns[_key] = $resetWithInitialValue
_resetWithFn[_key] = $resetWith
}

const $touchedAny = computed(() => {
for (const key of _keys) {
const _key = key as keyof T

if (_touched[_key].value) {
return true
}
}

return false
})

const $touched = computed(() => {
const _$touched = {} as Record<keyof T, ReturnType<typeof useV7d<T[keyof T]>>['$touched']['value']>

for (const key of _keys) {
const _key = key as keyof T
_$touched[_key] = _touched[_key].value
}

return _$touched
})

const $error = computed(() => {
const _$error = {} as Record<keyof T, ReturnType<typeof useV7d<T[keyof T]>>['$error']['value']>

for (const key of _keys) {
const _key = key as keyof T
_$error[_key] = _error[_key].value
}

return _$error
})

const $errors = computed(() => {
const _$errors = {} as Record<keyof T, ReturnType<typeof useV7d<T[keyof T]>>['$errors']['value']>

for (const key of _keys) {
const _key = key as keyof T
_$errors[_key] = _errors[_key as keyof T].value
}

return _$errors
})

function $touch(key?: keyof T) {
if (typeof key === 'string') {
_touchFns[key]()
return
}

for (const key of _keys) {
const _key = key as keyof T
_touchFns[_key]()
}
}

function $validate(key?: keyof T) {
if (typeof key === 'string') {
return _validateFns[key]()
}

const results = {} as Record<keyof T, ReturnType<ReturnType<typeof useV7d<T[keyof T]>>['$validate']>>

for (const key of _keys) {
const _key = key as keyof T
results[_key] = _validateFns[_key]()
}

return results
}

function $reset(key?: keyof T) {
if (typeof key === 'string') {
_resetFns[key]()
return
}

for (const key of _keys) {
const _key = key as keyof T
_resetFns[_key]()
}
}

function $resetWithInitialValue(key?: keyof T) {
if (typeof key === 'string') {
$reset(key)
;(_values as any)[key] = _initialValues[key]
return
}

$reset()

for (const key of _keys) {
const _key = key as keyof T
;(_values as any)[_key] = _initialValues[_key]
}
}

function $resetWith(key: keyof T, newValue: UnwrapRef<T[keyof T]>) {
$reset(key)
;(_values as Values)[key] = newValue
}

return {
values: _values,
$els,
$touchedAny,
$touched,
$error,
$errors,
$touch,
$validate,
$reset,
$resetWithInitialValue,
$resetWith
}
}
1 change: 1 addition & 0 deletions packages/vue-v8n/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { useV7d } from './composables/useV7d'
export { useV7dObject } from './composables/useV7dObject'
export { max } from './rules/max'
export { min } from './rules/min'
export { required } from './rules/required'
Expand Down
1 change: 1 addition & 0 deletions playground/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { RouterLink } from 'vue-router'
const menuItems = computed(() => [
{ label: 'Number', to: '/number' },
{ label: 'Object', to: '/object' },
{ label: 'Password', to: '/password' },
{ label: 'String', to: '/string' }
])
Expand Down
40 changes: 27 additions & 13 deletions playground/src/components/DemoView.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
<script setup lang="ts">
import type { RuleDefinition } from 'vue-v8n'
export interface ControlItemGroup {
label: string
items: ControlItem[]
}
export interface ControlItem {
label: string
fn: (...args: unknown[]) => unknown
}
export interface RuleOptionGroup {
label: string
rules: RuleOption[]
}
export interface RuleOption {
label: string
selected: boolean
rule: RuleDefinition
}
const props = defineProps<{
controlItems: ControlItem[]
controlItemGroups: ControlItemGroup[]
state: unknown
result: unknown
}>()
const ruleOptions = defineModel<RuleOption[]>({ required: true })
const ruleOptionGroups = defineModel<RuleOptionGroup[]>({ required: true })
</script>

<template>
Expand All @@ -27,17 +37,21 @@ const ruleOptions = defineModel<RuleOption[]>({ required: true })
<slot />
</div>
<div class="pane">
<p>Rules:</p>
<div class="rule-options">
<label v-for="ruleOption in ruleOptions">
<input type="checkbox" v-model="ruleOption.selected">
{{ ruleOption.label }}
</label>
</div>
<p>Functions:</p>
<div class="control-items">
<button v-for="{ label, fn } in props.controlItems" @click="fn">{{ label }}</button>
</div>
<template v-for="{ label, rules } in ruleOptionGroups">
<p>Rules for "{{ label }}":</p>
<div class="rule-options">
<label v-for="rule in rules" :key="label">
<input type="checkbox" v-model="rule.selected">
{{ rule.label }}
</label>
</div>
</template>
<template v-for="controlItemGroup in props.controlItemGroups">
<p>Functions for "{{ controlItemGroup.label }}":</p>
<div class="control-items">
<button v-for="{ label, fn } in controlItemGroup.items" @click="fn">{{ label }}</button>
</div>
</template>
<p>State:</p>
<div class="content-box">
<pre class="content">{{ props.state }}</pre>
Expand Down
34 changes: 27 additions & 7 deletions playground/src/pages/NumberDemo.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,44 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useV7d } from 'vue-v8n'
import { max, min, required, useV7d } from 'vue-v8n'
import DemoView from '../components/DemoView.vue'
import { getControlItemsRef, getRuleOptionsRef } from '../utils'
import type { ControlItemGroup, RuleOptionGroup } from '../components/DemoView.vue'
const ruleOptions = getRuleOptionsRef()
const ruleOptionGroups = ref<RuleOptionGroup[]>([
{
label: 'Count',
rules: [
{ label: 'required', rule: required, selected: false },
{ label: 'min(5)', rule: min(5), selected: false },
{ label: 'max(10)', rule: max(10), selected: false }
]
}
])
const rules = computed(() => ruleOptions.value.flatMap(({ selected, rule }) => selected ? [rule] : []))
const rules = computed(() => ruleOptionGroups.value[0].rules.flatMap(({ selected, rule }) => selected ? [rule] : []))
const state = useV7d(0, rules)
const result = ref<typeof state.value.value | Error>(state.value.value)
const controlItems = getControlItemsRef(state, result, 0)
const controlItemGroups = ref<ControlItemGroup[]>([
{
label: 'Name',
items: [
{ label: 'Touch', fn: state.$touch },
{ label: 'Validate', fn: () => result.value = state.$validate() },
{ label: 'Reset', fn: state.$reset },
{ label: 'Reset with initial value', fn: state.$resetWithInitialValue },
{ label: 'Reset with 0', fn: () => state.$resetWith(0) }
]
}
])
</script>

<template>
<DemoView
v-model="ruleOptions"
:control-items="controlItems"
v-model="ruleOptionGroups"
:control-item-groups="controlItemGroups"
:state="state"
:result="result"
>
Expand Down
Loading

0 comments on commit 0ee61a8

Please sign in to comment.