Skip to content

Commit ec8bb81

Browse files
committed
refactor: all fields to TypeScript
1 parent 80194eb commit ec8bb81

30 files changed

+406
-269
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,4 @@
7979
"@vueuse/core": "^13.1.0",
8080
"maska": "^3.1.1"
8181
}
82-
}
82+
}

src/FormGenerator.vue

Lines changed: 23 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,31 @@
1-
<script setup>
1+
<script setup lang="ts">
2+
import type { FieldValidation, FormGeneratorProps, FormOptions } from '@/resources/types/generic'
3+
import type { Field } from '@/resources/types/field/fields'
4+
import type { ComponentPublicInstance, ComputedRef, Ref } from 'vue'
25
import { computed, ref } from 'vue'
36
import { resetObjectProperties, toUniqueArray } from '@/helpers'
47
import FormGroup from './FormGroup.vue'
58
69
const emits = defineEmits([ 'submit', 'field-validated' ])
710
8-
const props = defineProps({
9-
id: {
10-
type: String,
11-
required: false,
12-
default: ''
13-
},
14-
idPrefix: {
15-
type: String,
16-
required: false,
17-
default: ''
18-
},
19-
options: {
20-
type: Object,
21-
default: () => ({})
22-
},
23-
schema: {
24-
type: Object,
25-
required: true
26-
},
27-
model: {
28-
type: Object,
29-
required: true
30-
},
31-
enctype: {
32-
type: String,
33-
default: 'application/x-www-form-urlencoded'
34-
}
11+
const props = withDefaults(defineProps<FormGeneratorProps>(), {
12+
enctype: 'application/x-www-form-urlencoded',
13+
id: '',
14+
idPrefix: ''
3515
})
3616
17+
type FormGroupInstance = ComponentPublicInstance<InstanceType<typeof FormGroup>>
3718
/** Data / Refs */
38-
const fieldElements = ref([])
39-
const formErrors = ref({})
40-
const formOptions = computed(() => ({ ...props.options, idPrefix: props.idPrefix }))
19+
const fieldElements: Ref<FormGroupInstance[]> = ref([])
20+
const formErrors: Ref<Record<string, any>> = ref({})
21+
const formOptions: ComputedRef<FormOptions> = computed(() => ({ ...props.options, idPrefix: props.idPrefix }))
4122
4223
/**
4324
* Update form model key with its new value.
4425
* @param {String} model model key to update.
4526
* @param {any} value value to set.
4627
*/
47-
const updateGeneratorModel = ({ model, value }) => {
28+
const updateGeneratorModel = ({ model, value }: { model: string, value: any }) => {
4829
// eslint-disable-next-line vue/no-mutating-props
4930
props.model[model] = value
5031
}
@@ -54,7 +35,7 @@ const updateGeneratorModel = ({ model, value }) => {
5435
* @param fieldErrors errors discovered during validation of the field.
5536
* @param field field schema object that has been validated.
5637
*/
57-
const onFieldValidated = ({ fieldErrors, field }) => {
38+
const onFieldValidated = ({ fieldErrors, field }: FieldValidation) => {
5839
emits('field-validated', { fieldErrors, field })
5940
if (!fieldErrors.length) {
6041
if (!(field.model in formErrors.value)) return
@@ -68,19 +49,20 @@ const onFieldValidated = ({ fieldErrors, field }) => {
6849
6950
/** Compute if the form has errors */
7051
const hasErrors = computed(() => {
71-
return Boolean(Object.values(formErrors.value).map(e => Boolean(e.length)).filter(e => e === true).length)
52+
return Boolean(Object.values(formErrors.value).map(e => Boolean(e.length)).filter(e => e).length)
7253
})
7354
7455
/**
7556
* Handle the submit event from the form element.
7657
*/
7758
const onSubmit = () => {
78-
if (hasErrors.value === false) emits('submit')
59+
if (!hasErrors.value) emits('submit')
7960
}
8061
8162
const onReset = () => {
82-
// eslint-disable-next-line vue/no-mutating-props
83-
props.model = resetObjectProperties(props.model)
63+
// Hideous hack to update the model, without TypeScript crying about mutation of props. And yes, I know it isn't
64+
// recommended to update the props.
65+
(props as any).model = resetObjectProperties(props.model)
8466
}
8567
8668
defineExpose({ hasErrors, formErrors })
@@ -96,8 +78,8 @@ defineExpose({ hasErrors, formErrors })
9678
>
9779
<fieldset v-if="props.schema.fields">
9880
<template v-for="field in props.schema.fields" :key="field">
99-
<form-group
100-
:ref="el => fieldElements.push(el)"
81+
<FormGroup
82+
:ref="el => (el && '$el' in el) ? fieldElements.push((el as FormGroupInstance)) : null"
10183
:form-options="formOptions"
10284
:field="field"
10385
:model="props.model"
@@ -111,8 +93,8 @@ defineExpose({ hasErrors, formErrors })
11193
{{ group.legend }}
11294
</legend>
11395
<template v-for="field in group.fields" :key="field">
114-
<form-group
115-
:ref="el => fieldElements.push(el)"
96+
<FormGroup
97+
:ref="el => (el && '$el' in el) ? fieldElements.push((el as FormGroupInstance)) : null"
11698
:form-options="formOptions"
11799
:field="field"
118100
:model="props.model"

src/composables/useFieldAttributes.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { computed, ComputedRef } from 'vue'
2-
import { type Field } from '@/resources/types/fields'
2+
import type { Field } from '@/resources/types/field/fields'
33
import { TDynamicAttributeBooleanFunction, TDynamicAttributeStringFunction } from '@/resources/types/functions'
4+
import type { FormModel } from '@/resources/types/fieldAttributes'
45

56
export function useFieldAttributes (
6-
model: Record<string, any>,
7+
model: FormModel,
78
field: Field
89
) {
910

src/composables/useFieldValidate.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { getMessage } from '@/validators/messages'
33
import { isFunction, isString, toUniqueArray } from '@/helpers'
44
import { TValidatorFunction } from '@/resources/types/functions'
55
import { ValidatorMap } from '@/resources/types/generic'
6-
import { Field } from '@/resources/types/fields'
6+
import type { FormModel } from '@/resources/types/fieldAttributes'
7+
import type { Field } from '@/resources/types/field/fields'
78
import validators from '@/validators'
89

910
/**
@@ -27,7 +28,7 @@ function getValidator (validator: string | TValidatorFunction | undefined): TVal
2728
}
2829

2930
export function useFieldValidate (
30-
model: Record<string, any>,
31+
model: FormModel,
3132
field: Field,
3233
isDisabled: boolean = false,
3334
isRequired: boolean = false,

src/composables/useFormModel.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { computed, ComputedRef } from 'vue'
2-
import { Field } from '@/resources/types/fields'
2+
import type { Field } from '@/resources/types/field/fields'
3+
import type { FormModel } from '@/resources/types/fieldAttributes'
34

45
/**
56
* Composable for current model state of current field
67
* @param model - form schema model
78
* @param field - form field
89
*/
910
export function useFormModel(
10-
model: Record<string, any>,
11+
model: FormModel,
1112
field: Field
1213
) {
1314

src/directives/onClickOutside.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Directive, DirectiveBinding } from 'vue'
22

3-
const onClickOutside: Directive<HTMLElement, string> = {
3+
const onClickOutside: Directive<HTMLElement, never> = {
44
beforeMount(el: HTMLElement, binding: DirectiveBinding): void {
55
el.clickOutsideEvent = (event: Event) => {
66
if (!(el === event.target || el.contains(<Node>event.target))) {

src/fields/core/FieldButton.vue

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
<template>
2-
<button type="button" :class="field.buttonClasses" @click.prevent="field.onClick(model, field)">
2+
<button type="button" :class="field.buttonClasses" @click.prevent="onClick">
33
{{ field.buttonText }}
44
</button>
55
</template>
66

7-
<script setup>
7+
<script setup lang="ts">
88
import { toRefs } from 'vue'
9-
import { useFieldProps } from '@/composables/index.ts'
9+
import type { ButtonField, FieldPropRefs, FieldProps } from '@/resources/types/field/fields'
1010
11-
const props = defineProps(useFieldProps())
11+
const props = defineProps<FieldProps<ButtonField>>()
1212
13-
const { model, field } = toRefs(props)
13+
const { model, field }: FieldPropRefs<ButtonField> = toRefs(props)
14+
15+
const onClick = () => {
16+
return field.value.onClick !== undefined ? field.value.onClick(model.value, field.value) : undefined
17+
}
1418
1519
defineExpose({ noLabel: true })
1620
</script>

src/fields/core/FieldCheckbox.vue

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@
1212
<label v-if="field.label" style="margin-left: .4em" :for="props.id"> {{ field.label }}</label>
1313
</template>
1414

15-
<script setup>
15+
<script setup lang="ts">
1616
import { toRefs } from 'vue'
17+
import type { CheckboxField, FieldPropRefs, FieldProps } from '@/resources/types/field/fields'
1718
import {
1819
useFormModel,
1920
useFieldAttributes,
2021
useFieldValidate,
21-
useFieldProps,
2222
useFieldEmits
23-
} from '@/composables/index.ts'
23+
} from '@/composables'
2424
2525
const emits = defineEmits(useFieldEmits())
26-
const props = defineProps(useFieldProps())
26+
const props = defineProps<FieldProps<CheckboxField>>()
2727
28-
const { field, model } = toRefs(props)
28+
const { field, model }: FieldPropRefs<CheckboxField> = toRefs(props)
2929
3030
const { currentModelValue } = useFormModel(model.value, field.value)
3131
const { isRequired, isDisabled, hint } = useFieldAttributes(model.value, field.value)
@@ -37,7 +37,8 @@ const { errors, validate } = useFieldValidate(
3737
false
3838
)
3939
40-
const onFieldValueChanged = ({ target }) => {
40+
const onFieldValueChanged = (event: Event) => {
41+
const target = event.target as HTMLInputElement
4142
errors.value = []
4243
validate(currentModelValue.value).then((validationErrors) => {
4344
emits('validated',

src/fields/core/FieldChecklist.vue

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,27 @@
1919
</div>
2020
</template>
2121

22-
<script setup>
22+
<script setup lang="ts">
2323
// This component is heavily inspired by https://github.com/shwld/vfg-field-checkboxlist/
2424
import {
25-
useFieldProps,
2625
useFieldEmits,
2726
useFieldValidate,
28-
useFormModel, useFieldAttributes
27+
useFormModel,
28+
useFieldAttributes
2929
} from '@/composables'
30-
import { toRefs } from 'vue'
30+
import type { FieldProps, FieldPropRefs, ChecklistField } from '@/resources/types/field/fields'
31+
import { type Ref, toRefs } from 'vue'
3132
3233
const emits = defineEmits(useFieldEmits())
33-
const props = defineProps(useFieldProps())
34+
const props = defineProps<FieldProps<ChecklistField>>()
3435
35-
const { field, model } = toRefs(props)
36+
const { field, model }: FieldPropRefs<ChecklistField> = toRefs(props)
3637
const { hint } = useFieldAttributes(model.value, field.value)
37-
const { currentModelValue } = useFormModel(model.value, field.value)
38+
const { currentModelValue }: { currentModelValue: Ref<any[]> } = useFormModel(model.value, field.value)
3839
const { validate, errors } = useFieldValidate(model.value, field.value)
3940
40-
const onFieldValueChanged = ({ target }) => {
41+
const onFieldValueChanged = (event: Event) => {
42+
const target = event.target as HTMLInputElement
4143
errors.value = []
4244
let newValue
4345
const valueAlreadyChecked = currentModelValue.value.includes(target.value)

src/fields/core/FieldColor.vue

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,23 @@
2323
</div>
2424
</template>
2525

26-
<script setup>
26+
<script setup lang="ts">
2727
import validators from '@/validators'
2828
import { toRefs, onBeforeMount } from 'vue'
2929
import {
3030
useFormModel,
3131
useFieldAttributes,
3232
useFieldValidate,
33-
useFieldProps,
3433
useFieldEmits
35-
} from '@/composables/index.ts'
34+
} from '@/composables'
3635
import { vMaska } from 'maska/vue'
36+
import type { ColorField, FieldPropRefs, FieldProps } from '@/resources/types/field/fields'
37+
import type { MaskOptions } from 'maska'
3738
3839
const emits = defineEmits(useFieldEmits())
39-
const props = defineProps(useFieldProps())
40+
const props = defineProps<FieldProps<ColorField>>()
4041
41-
const maskOptions = {
42+
const maskOptions: Readonly<MaskOptions> = {
4243
mask: '!#HHHHHH',
4344
tokens: {
4445
H: {
@@ -47,7 +48,7 @@ const maskOptions = {
4748
}
4849
}
4950
50-
const { field, model } = toRefs(props)
51+
const { field, model }: FieldPropRefs<ColorField> = toRefs(props)
5152
5253
const { currentModelValue } = useFormModel(model.value, field.value)
5354
const { isRequired, isVisible, hint } = useFieldAttributes(model.value, field.value)
@@ -56,8 +57,7 @@ const { errors, validate } = useFieldValidate(
5657
field.value,
5758
false,
5859
isRequired.value,
59-
false,
60-
currentModelValue.value
60+
false
6161
)
6262
6363
const onBlur = () => {
@@ -70,15 +70,17 @@ const onBlur = () => {
7070
})
7171
}
7272
73-
const onFieldValueChanged = ({ target }) => {
73+
const onFieldValueChanged = (event: Event) => {
74+
const target = event.target as HTMLInputElement
7475
errors.value = []
75-
// Ensure a change doesn't emit twice, we need this because both inputs might trigger this function at once.
76+
// Ensure a change doesn't emit twice; we need this because both inputs might trigger this function at once.
7677
if (target.value !== currentModelValue.value) {
7778
emits('onInput', target.value)
7879
}
7980
}
8081
8182
onBeforeMount(() => {
83+
// Only add validators when the extra text input is enabled.
8284
if (field.value.withInput) {
8385
const fieldValidators = []
8486
if (Array.isArray(field.value.validator)) {

src/fields/core/FieldMask.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313

1414
<script setup lang="ts">
1515
import { toRefs, computed, ComputedRef, ref, onBeforeMount } from 'vue'
16-
import { FieldProps, IMaskField } from '@/resources/types/fields'
16+
import type { FieldPropRefs, FieldProps, MaskField } from '@/resources/types/field/fields'
1717
import type { MaskInputOptions } from 'maska'
1818
import { Mask } from 'maska'
1919
import { vMaska } from 'maska/vue'
2020
import { useFormModel, useFieldAttributes, useFieldEmits, useFieldValidate } from '@/composables'
2121
2222
const emits = defineEmits(useFieldEmits())
23-
const props = defineProps<FieldProps<IMaskField>>()
24-
const { field, model } = toRefs(props)
23+
const props = defineProps<FieldProps<MaskField>>()
24+
const { field, model }: FieldPropRefs<MaskField> = toRefs(props)
2525
2626
const unmaskedValue = ref('')
2727
const inputDefaultValue = ref('')

0 commit comments

Comments
 (0)