Skip to content

Commit 8611c62

Browse files
authored
Merge pull request #27 from kevinkosterr/master
New release
2 parents fc2e317 + 69cb6ae commit 8611c62

File tree

9 files changed

+128
-20
lines changed

9 files changed

+128
-20
lines changed

__tests__/components/fields/FieldColor.spec.js

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ const form = generateSchemaSingleField(
1010
'input',
1111
'color',
1212
'Pick a color',
13-
''
13+
'',
14+
{}
1415
)
1516

1617
const props = generatePropsSingleField(form)
@@ -30,9 +31,21 @@ describe('FieldColor', () => {
3031
expect(formWrapper.find('input[type=color]').exists()).toBe(true)
3132
})
3233

34+
it('Should render correctly, with input', async () => {
35+
config.global.components = { FieldColor }
36+
37+
const schema = { ...form.schema }
38+
schema.fields[0].withInput = true
39+
const formWrapper = mountFormGenerator(schema, form.model)
40+
41+
expect(formWrapper.findComponent(FieldColor).exists()).toBe(true)
42+
expect(formWrapper.find('input[type=color]').exists()).toBe(true)
43+
expect(formWrapper.find('input[type=text]').exists()).toBe(true)
44+
})
45+
3346
it('Should emit onInput event', async () => {
3447
const wrapper = mount(FieldColor, { props })
35-
await wrapper.find('input[type=color]').trigger('change')
48+
await wrapper.find('input[type=color]').trigger('input')
3649
expect(wrapper.emitted()).toHaveProperty('onInput')
3750
})
3851

@@ -47,4 +60,30 @@ describe('FieldColor', () => {
4760
expect(formWrapper.vm.model.colorModel).toBe('#efefef')
4861
})
4962

63+
it ('Should update model value and sync values', async () => {
64+
config.global.components = { FieldColor }
65+
66+
const schema = { ...form.schema }
67+
schema.fields[0].withInput = true
68+
const formWrapper = mountFormGenerator(schema, form.model)
69+
70+
const wrapper = formWrapper.findComponent(FieldColor)
71+
wrapper.find('input[type=text]').setValue('#f00000')
72+
expect(wrapper.emitted()).toHaveProperty('onInput', [ [ '#f00000' ] ])
73+
expect(formWrapper.vm.model.colorModel).toBe('#f00000')
74+
// Wait for the DOM to update.
75+
await wrapper.vm.$nextTick()
76+
expect(wrapper.find('input[type=color').attributes().value).toBe('#f00000')
77+
78+
// Clear emitted events for next interaction test.
79+
wrapper.emitted().onInput = []
80+
81+
wrapper.find('input[type=color]').setValue('#ff0000')
82+
expect(wrapper.emitted()).toHaveProperty('onInput', [ [ '#ff0000' ] ])
83+
expect(formWrapper.vm.model.colorModel).toBe('#ff0000')
84+
// Wait for the DOM to update.
85+
await wrapper.vm.$nextTick()
86+
expect(wrapper.find('input[type=text]').attributes().value).toBe('#ff0000')
87+
})
88+
5089
})

src/fields/core/FieldColor.vue

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,52 @@
11
<template>
2-
<input
3-
:id="props.id"
4-
class="field-color"
5-
type="color"
6-
:name="props.field.name"
7-
:value="currentModelValue"
8-
:required="isRequired"
9-
@change="onFieldValueChanged"
10-
@blur="onBlur"
11-
>
2+
<div class="field-color-wrapper">
3+
<input
4+
v-if="props.field.withInput"
5+
v-maska="maskOptions"
6+
class="field-color-input"
7+
type="text"
8+
:value="currentModelValue"
9+
placeholder="#ffffff"
10+
@input="onFieldValueChanged"
11+
@blur="onBlur"
12+
>
13+
<input
14+
:id="props.id"
15+
class="field-color"
16+
type="color"
17+
:name="props.field.name"
18+
:value="currentModelValue"
19+
:required="isRequired"
20+
@input="onFieldValueChanged"
21+
@blur="onBlur"
22+
>
23+
</div>
1224
</template>
1325

1426
<script setup>
15-
import { toRefs } from 'vue'
27+
import validators from '@/validators'
28+
import { toRefs, onBeforeMount } from 'vue'
1629
import {
1730
useFormModel,
1831
useFieldAttributes,
1932
useFieldValidate,
2033
useFieldProps,
2134
useFieldEmits
2235
} from '@/composables/index.ts'
36+
import { vMaska } from 'maska/vue'
2337
2438
const emits = defineEmits(useFieldEmits())
2539
const props = defineProps(useFieldProps())
2640
41+
const maskOptions = {
42+
mask: '!#HHHHHH',
43+
tokens: {
44+
H: {
45+
pattern: /[A-Fa-f0-9]/
46+
}
47+
}
48+
}
49+
2750
const { field, model } = toRefs(props)
2851
2952
const { currentModelValue } = useFormModel(model.value, field.value)
@@ -38,7 +61,7 @@ const { errors, validate } = useFieldValidate(
3861
)
3962
4063
const onBlur = () => {
41-
validate().then((validationErrors) => {
64+
validate(currentModelValue.value).then((validationErrors) => {
4265
emits('validated',
4366
validationErrors.length === 0,
4467
validationErrors,
@@ -49,8 +72,26 @@ const onBlur = () => {
4972
5073
const onFieldValueChanged = ({ target }) => {
5174
errors.value = []
52-
emits('onInput', target.value)
75+
// Ensure a change doesn't emit twice, we need this because both inputs might trigger this function at once.
76+
if (target.value !== currentModelValue.value) {
77+
emits('onInput', target.value)
78+
}
5379
}
5480
55-
defineExpose({ isVisible })
81+
onBeforeMount(() => {
82+
if (field.value.withInput) {
83+
const fieldValidators = []
84+
if (Array.isArray(field.value.validator)) {
85+
fieldValidators.push(...field.value.validator)
86+
} else if (field.value.validator !== undefined) {
87+
fieldValidators.push(field.value.validator)
88+
}
89+
// Keep in mind that the native color picker only supports 6 digit hex codes,
90+
// so even though a value might technically be valid, it won't display the right color on the color picker input.
91+
fieldValidators.push(validators.hexColorValue)
92+
field.value.validator = fieldValidators
93+
}
94+
})
95+
96+
defineExpose({ isVisible, errors })
5697
</script>

src/fields/core/FieldNumber.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<input
3-
:id="id"
3+
:id="props.id"
44
type="number"
55
:name="field.name"
66
:required="isRequired"
@@ -10,6 +10,7 @@
1010
:max="field.max || undefined"
1111
:min="field.min || undefined"
1212
:step="field.step || 1"
13+
inputmode="numeric"
1314
@input="onFieldValueChanged"
1415
@blur="onBlur"
1516
>

src/scss/modules/_base.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.vfg-fi {
22
display: flex;
3+
align-items: center;
34

45
svg {
56
height: 18px;

src/scss/shared/fieldColor.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.field-color-wrapper {
2+
display: flex;
3+
flex-direction: row-reverse;
4+
align-items: stretch;
5+
justify-content: start;
6+
7+
.field-color-input {
8+
max-width: 100px;
9+
}
10+
}

src/scss/themes/basic.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
@import '../modules/base';
44
@import '../modules/colors';
55
@import '../shared/fieldSelect';
6+
@import '../shared/fieldColor';
67

78
$input-padding: .3rem .4rem;
89

@@ -100,6 +101,9 @@ $input-padding: .3rem .4rem;
100101
border: none;
101102
padding: 0;
102103
}
104+
&:hover {
105+
cursor: pointer;
106+
}
103107
}
104108
/** End of color field */
105109

src/scss/themes/legacy.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@import '../modules/colors';
22
@import '../shared/fieldSelect';
3+
@import '../shared/fieldColor';
34

45
$field-switch-width: 120px;
56
$field-switch-height: 30px;

src/validators/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ export default {
7575
// eslint-disable-next-line @typescript-eslint/no-unused-vars
7676
email (value: FieldValue, field: Field, model: Record<string, any>): boolean {
7777
if (typeof value !== 'string') return false
78-
// eslint-disable-next-line max-len
7978
const regex = new RegExp('^([^<>()\\[\\]\\\\.,;:\\s@"]+(?:\\.[^<>()\\[\\]\\\\.,;:\\s@"]+)*|".+")@(\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}]|(?:[a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,})$', 'i')
8079
return Boolean(value.match(regex))
8180
},
@@ -89,7 +88,6 @@ export default {
8988
if (typeof value !== 'string') return false
9089

9190
const regex = new RegExp(
92-
// eslint-disable-next-line max-len
9391
'^\\+\\d{1,3}\\s\\d{2,3}\\s\\d{2,3}\\s\\d{4}|^\\+\\d{1,3}\\s\\d{1,14}(\\s\\d{1,13})?|^\\(\\d{3}\\)\\s\\d{3}\\s\\d{4}?',
9492
'i'
9593
)
@@ -106,6 +104,18 @@ export default {
106104

107105
const regex = new RegExp('(\\+316[0-9]{8})|(06[0-9]{8})', 'i')
108106
return Boolean(value.match(regex))
107+
},
108+
109+
/**
110+
* Check if a value is a valid HEX color value.
111+
* @returns {boolean} - Returns `true` if value matches the format, otherwise false.
112+
*/
113+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
114+
hexColorValue (value: FieldValue, field: Field, model: Record<string, any>): boolean {
115+
if (typeof value !== 'string') return false
116+
117+
const regex = new RegExp('^\#([a-f0-9]{3}|[a-f0-9]{6})$', 'i')
118+
return Boolean(value.match(regex))
109119
}
110120

111121

src/validators/messages.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ let messages: Record<string, string> = {
44
number: 'Value must be a number',
55
email: 'E-mail is invalid',
66
phoneNumberE164andE123: 'Phone number is invalid (must be valid E164 or E123 format, e.g. +31 612345678)',
7-
mobilePhoneNL: 'Phone number is invalid (must be a valid Dutch phone number, e.g. +31612345678)'
7+
mobilePhoneNL: 'Phone number is invalid (must be a valid Dutch phone number, e.g. +31612345678)',
8+
hexColorValue: 'Invalid hex value (e.g. #ff0000 or #ff0)'
89
}
910

1011
/**

0 commit comments

Comments
 (0)