Skip to content

Commit 971fd39

Browse files
authored
Merge pull request #37 from kevinkosterr/updates
Updates
2 parents 9737aaf + aaba191 commit 971fd39

26 files changed

+406
-155
lines changed

.github/workflows/pr-run-tests.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Validate tests for pull request
2+
3+
on:
4+
pull_request:
5+
types: [ opened, synchronize, reopened ]
6+
7+
jobs:
8+
test:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- name: Checkout code
13+
uses: actions/checkout@v4
14+
- name: Install pnpm
15+
uses: pnpm/action-setup@v4
16+
with:
17+
version: 10
18+
- uses: actions/setup-node@v4
19+
with:
20+
node-version: 20
21+
cache: 'pnpm'
22+
- name: Get pnpm store directory
23+
shell: bash
24+
run: |
25+
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
26+
- name: Setup pnpm cache
27+
uses: actions/cache@v4
28+
with:
29+
path: ${{ env.STORE_PATH }}
30+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
31+
restore-keys: |
32+
${{ runner.os }}-pnpm-store-
33+
- name: Install dependencies
34+
run: pnpm install
35+
- name: Run tests
36+
run: pnpm run test

apps/docs/.vitepress/config.js renamed to apps/docs/.vitepress/config.mts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ export default defineConfig({
8282
],
8383

8484
socialLinks: [
85-
{ icon: 'github', link: 'https://github.com/kevinkosterr/vue3-form-generator' }
85+
{ icon: 'github', link: 'https://github.com/kevinkosterr/vue3-form-generator' },
86+
{ icon: 'npm', link: 'https://www.npmjs.com/package/@kevinkosterr/vue3-form-generator' }
8687
]
8788
},
8889
vite: {

src/FormGenerator.vue

Lines changed: 44 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,45 @@
1+
<template>
2+
<form
3+
v-if="props.schema !== undefined" :id="props.id ?? ''"
4+
class="vue-form-generator"
5+
:enctype="enctype"
6+
@submit.prevent="onSubmit"
7+
@reset.prevent="onReset"
8+
>
9+
<fieldset v-if="props.schema.fields">
10+
<template v-for="field in props.schema.fields" :key="field">
11+
<FormGroup
12+
:ref="el => (el && '$el' in el) ? fieldElements.push((el as FormGroupInstance)) : null"
13+
:form-options="formOptions"
14+
:field="field"
15+
:model="props.model"
16+
@value-updated="updateGeneratorModel"
17+
@validated="onFieldValidated"
18+
/>
19+
</template>
20+
<template v-for="group in props.schema.groups" :key="group">
21+
<fieldset>
22+
<legend v-if="group.legend">
23+
{{ group.legend }}
24+
</legend>
25+
<template v-for="field in group.fields" :key="field">
26+
<FormGroup
27+
:ref="el => (el && '$el' in el) ? fieldElements.push((el as FormGroupInstance)) : null"
28+
:form-options="formOptions"
29+
:field="field"
30+
:model="props.model"
31+
@value-updated="updateGeneratorModel"
32+
@validated="onFieldValidated"
33+
/>
34+
</template>
35+
</fieldset>
36+
</template>
37+
</fieldset>
38+
</form>
39+
</template>
40+
141
<script setup lang="ts">
242
import type { FieldValidation, FormGeneratorProps, FormOptions } from '@/resources/types/generic'
3-
import type { Field } from '@/resources/types/field/fields'
443
import type { ComponentPublicInstance, ComputedRef, Ref } from 'vue'
544
import { computed, ref } from 'vue'
645
import { resetObjectProperties, toUniqueArray } from '@/helpers'
@@ -11,7 +50,8 @@ const emits = defineEmits([ 'submit', 'field-validated' ])
1150
const props = withDefaults(defineProps<FormGeneratorProps>(), {
1251
enctype: 'application/x-www-form-urlencoded',
1352
id: '',
14-
idPrefix: ''
53+
idPrefix: '', // Kept for compatibility reasons.
54+
options: () => ({})
1555
})
1656
1757
type FormGroupInstance = ComponentPublicInstance<InstanceType<typeof FormGroup>>
@@ -52,9 +92,7 @@ const hasErrors = computed(() => {
5292
return Boolean(Object.values(formErrors.value).map(e => Boolean(e.length)).filter(e => e).length)
5393
})
5494
55-
/**
56-
* Handle the submit event from the form element.
57-
*/
95+
/** Handle the submit event from the form element. */
5896
const onSubmit = () => {
5997
if (!hasErrors.value) emits('submit')
6098
}
@@ -66,44 +104,4 @@ const onReset = () => {
66104
}
67105
68106
defineExpose({ hasErrors, formErrors })
69-
</script>
70-
71-
<template>
72-
<form
73-
v-if="props.schema !== undefined" :id="props.id ?? ''"
74-
class="vue-form-generator"
75-
:enctype="enctype"
76-
@submit.prevent="onSubmit"
77-
@reset.prevent="onReset"
78-
>
79-
<fieldset v-if="props.schema.fields">
80-
<template v-for="field in props.schema.fields" :key="field">
81-
<FormGroup
82-
:ref="el => (el && '$el' in el) ? fieldElements.push((el as FormGroupInstance)) : null"
83-
:form-options="formOptions"
84-
:field="field"
85-
:model="props.model"
86-
@value-updated="updateGeneratorModel"
87-
@validated="onFieldValidated"
88-
/>
89-
</template>
90-
<template v-for="group in props.schema.groups" :key="group">
91-
<fieldset>
92-
<legend v-if="group.legend">
93-
{{ group.legend }}
94-
</legend>
95-
<template v-for="field in group.fields" :key="field">
96-
<FormGroup
97-
:ref="el => (el && '$el' in el) ? fieldElements.push((el as FormGroupInstance)) : null"
98-
:form-options="formOptions"
99-
:field="field"
100-
:model="props.model"
101-
@value-updated="updateGeneratorModel"
102-
@validated="onFieldValidated"
103-
/>
104-
</template>
105-
</fieldset>
106-
</template>
107-
</fieldset>
108-
</form>
109-
</template>
107+
</script>

src/FormGroup.vue

Lines changed: 51 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,5 @@
1-
<script setup>
2-
import { computed, useTemplateRef } from 'vue'
3-
import { getFieldComponentName } from '@/helpers'
4-
5-
const fieldComponent = useTemplateRef('fieldComponent')
6-
7-
const props = defineProps({
8-
formOptions: {
9-
type: Object,
10-
default: () => ({})
11-
},
12-
model: {
13-
type: Object,
14-
required: true
15-
},
16-
field: {
17-
type: Object,
18-
required: true
19-
},
20-
errors: {
21-
type: Array,
22-
default: () => []
23-
}
24-
})
25-
26-
const emit = defineEmits([ 'value-updated', 'validated' ])
27-
28-
function onInput (value) {
29-
emit('value-updated', { model: props.field.model, value })
30-
}
31-
32-
function onValidated (isValid, fieldErrors, field) {
33-
emit('validated', { isValid, fieldErrors, field })
34-
}
35-
36-
/** Computed */
37-
const fieldId = computed(() => {
38-
return `${props.formOptions.idPrefix ? props.formOptions.idPrefix + '_' : ''}${props.field.name}`
39-
})
40-
41-
const shouldHaveLabel = computed(() => {
42-
if (fieldComponent.value?.noLabel || props.field.noLabel === true) {
43-
return false
44-
}
45-
return props.field.label
46-
})
47-
</script>
48-
491
<template>
50-
<div class="form-group">
2+
<div class="form-group" :style="fieldStyle">
513
<label v-if="shouldHaveLabel" :for="fieldId">
524
<span> {{ props.field.label }}</span>
535
</label>
@@ -65,14 +17,61 @@ const shouldHaveLabel = computed(() => {
6517
/>
6618
</div>
6719

68-
<div v-if="fieldComponent && fieldComponent.hint" class="hints">
20+
<div v-if="fieldComponent && fieldHasHint" class="hints">
6921
<span class="hint">{{ fieldComponent.hint }}</span>
7022
</div>
7123

72-
<div v-if="fieldComponent && fieldComponent.errors && fieldComponent.errors.length" class="errors help-block">
24+
<div v-if="fieldComponent && fieldHasErrors" class="errors help-block">
7325
<template v-for="error in fieldComponent.errors" :key="error">
7426
<span class="error">{{ error }}</span> <br>
7527
</template>
7628
</div>
7729
</div>
78-
</template>
30+
</template>
31+
32+
<script setup lang="ts">
33+
import type { ComputedRef, ShallowRef } from 'vue'
34+
import type { FieldComponent, FormGroupProps } from '@/resources/types/generic'
35+
import type { Field } from '@/resources/types/field/fields'
36+
import { computed, useTemplateRef } from 'vue'
37+
import { getFieldComponentName } from '@/helpers'
38+
39+
const fieldComponent = useTemplateRef('fieldComponent') as Readonly<ShallowRef<FieldComponent | undefined>>
40+
41+
const props = withDefaults(defineProps<FormGroupProps>(), {
42+
formOptions: () => ({}),
43+
errors: () => []
44+
})
45+
const emit = defineEmits([ 'value-updated', 'validated' ])
46+
47+
function onInput (value: any) {
48+
emit('value-updated', { model: props.field.model, value })
49+
}
50+
51+
function onValidated (isValid: boolean, fieldErrors: string[], field: Field) {
52+
emit('validated', { isValid, fieldErrors, field })
53+
}
54+
55+
/** Computed */
56+
const fieldId: ComputedRef<string> = computed(() => {
57+
return `${props.formOptions.idPrefix ? props.formOptions.idPrefix + '_' : ''}${props.field.name}`
58+
})
59+
60+
const fieldStyle: ComputedRef<Record<string, string | undefined>> = computed(() => ({
61+
display: fieldComponent.value && fieldComponent.value.isVisible ? undefined : 'none'
62+
}))
63+
64+
const fieldHasErrors: ComputedRef<boolean> = computed(() => {
65+
return Boolean(fieldComponent.value && fieldComponent.value.errors && fieldComponent.value.errors.length)
66+
})
67+
const fieldHasHint: ComputedRef<boolean> = computed(() => {
68+
return Boolean(fieldComponent.value && fieldComponent.value.hint)
69+
})
70+
71+
const shouldHaveLabel: ComputedRef<boolean> = computed(() => {
72+
if (fieldComponent.value?.noLabel || props.field.noLabel === true) {
73+
return false
74+
}
75+
return Boolean(props.field.label)
76+
})
77+
</script>

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, never> = {
3+
const onClickOutside: Directive<HTMLElement, (event: Event) => void> = {
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 & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
<template>
2-
<button type="button" :class="field.buttonClasses" @click.prevent="onClick">
2+
<button
3+
type="button"
4+
:disabled="isDisabled"
5+
:class="field.buttonClasses"
6+
@click.prevent="onClick"
7+
>
38
{{ field.buttonText }}
49
</button>
510
</template>
611

712
<script setup lang="ts">
813
import { toRefs } from 'vue'
14+
import { useFieldAttributes } from '@/composables'
915
import type { ButtonField, FieldPropRefs, FieldProps } from '@/resources/types/field/fields'
1016
1117
const props = defineProps<FieldProps<ButtonField>>()
1218
1319
const { model, field }: FieldPropRefs<ButtonField> = toRefs(props)
20+
const { isVisible, hint, isDisabled } = useFieldAttributes(model.value, field.value)
1421
1522
const onClick = () => {
1623
return field.value.onClick !== undefined ? field.value.onClick(model.value, field.value) : undefined
1724
}
1825
19-
defineExpose({ noLabel: true })
26+
defineExpose({ noLabel: true, isVisible, hint })
2027
</script>

src/fields/core/FieldCheckbox.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const props = defineProps<FieldProps<CheckboxField>>()
2828
const { field, model }: FieldPropRefs<CheckboxField> = toRefs(props)
2929
3030
const { currentModelValue } = useFormModel(model.value, field.value)
31-
const { isRequired, isDisabled, hint } = useFieldAttributes(model.value, field.value)
31+
const { isRequired, isDisabled, isVisible, hint } = useFieldAttributes(model.value, field.value)
3232
const { errors, validate } = useFieldValidate(
3333
model.value,
3434
field.value,
@@ -50,5 +50,5 @@ const onFieldValueChanged = (event: Event) => {
5050
emits('onInput', target.checked)
5151
}
5252
53-
defineExpose({ hint, noLabel: true, errors })
54-
</script>
53+
defineExpose({ hint, noLabel: true, errors, isVisible })
54+
</script>

src/fields/core/FieldChecklist.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const emits = defineEmits(useFieldEmits())
3434
const props = defineProps<FieldProps<ChecklistField>>()
3535
3636
const { field, model }: FieldPropRefs<ChecklistField> = toRefs(props)
37-
const { hint } = useFieldAttributes(model.value, field.value)
37+
const { hint, isVisible } = useFieldAttributes(model.value, field.value)
3838
const { currentModelValue }: { currentModelValue: Ref<any[]> } = useFormModel(model.value, field.value)
3939
const { validate, errors } = useFieldValidate(model.value, field.value)
4040
@@ -63,5 +63,5 @@ const onFieldValueChanged = (event: Event) => {
6363
})
6464
}
6565
66-
defineExpose({ hint, errors })
67-
</script>
66+
defineExpose({ hint, errors, isVisible })
67+
</script>

src/fields/core/FieldMask.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const maskOptions: ComputedRef<MaskInputOptions> = computed(() => {
3838
})
3939
4040
const { currentModelValue } = useFormModel(model.value, field.value)
41-
const { isRequired, isDisabled, hint } = useFieldAttributes(model.value, field.value)
41+
const { isRequired, isDisabled, isVisible, hint } = useFieldAttributes(model.value, field.value)
4242
const { errors, validate } = useFieldValidate(
4343
model.value,
4444
field.value,
@@ -76,5 +76,5 @@ onBeforeMount(() => {
7676
}
7777
})
7878
79-
defineExpose({ unmaskedValue, hint, errors })
80-
</script>
79+
defineExpose({ unmaskedValue, hint, errors, isVisible })
80+
</script>

src/fields/core/FieldNumber.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const emits = defineEmits(useFieldEmits())
3131
3232
const { field, model }: FieldPropRefs<NumberField> = toRefs(props)
3333
34-
const { isDisabled, isRequired, hint } = useFieldAttributes(model.value, field.value)
34+
const { isDisabled, isRequired, isVisible, hint } = useFieldAttributes(model.value, field.value)
3535
const { currentModelValue } = useFormModel(model.value, field.value)
3636
const { errors, validate } = useFieldValidate(
3737
model.value,
@@ -60,5 +60,5 @@ const onFieldValueChanged = (event: Event) => {
6060
emits('onInput', parseFloat(target.value))
6161
}
6262
63-
defineExpose({ hint, errors })
64-
</script>
63+
defineExpose({ hint, errors, isVisible })
64+
</script>

0 commit comments

Comments
 (0)