Phone input country restriction (#218)
* Phone input country restriction * Wording --------- Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
parent
a297f2db50
commit
7a4b6dbd79
|
@ -2,51 +2,54 @@
|
|||
<div :class="wrapperClass" :style="inputStyle">
|
||||
<slot name="label">
|
||||
<label v-if="label" :for="id ? id : name"
|
||||
:class="[theme.default.label, { 'uppercase text-xs': uppercaseLabels, 'text-sm': !uppercaseLabels }]">
|
||||
:class="[theme.default.label, { 'uppercase text-xs': uppercaseLabels, 'text-sm': !uppercaseLabels }]"
|
||||
>
|
||||
{{ label }}
|
||||
<span v-if="required" class="text-red-500 required-dot">*</span>
|
||||
</label>
|
||||
</slot>
|
||||
<div v-if="help && helpPosition == 'above_input'" class="flex mb-1">
|
||||
<small :class="theme.default.help" class="grow">
|
||||
<slot name="help"><span class="field-help" v-html="help"/></slot>
|
||||
<slot name="help"><span class="field-help" v-html="help" /></slot>
|
||||
</small>
|
||||
</div>
|
||||
<div :id="id ? id : name" :name="name" :style="inputStyle" class="flex items-center">
|
||||
<v-select class="w-[130px]" dropdown-class="w-[300px]" input-class="rounded-r-none" :data="countries"
|
||||
v-model="selectedCountryCode"
|
||||
<v-select v-model="selectedCountryCode" class="w-[130px]" dropdown-class="w-[300px]" input-class="rounded-r-none"
|
||||
:data="countries"
|
||||
:disabled="disabled || countries.length===1" :searchable="true" :search-keys="['name']" :option-key="'code'" :color="color"
|
||||
:has-error="hasValidation && form.errors.has(name)"
|
||||
:disabled="disabled" :searchable="true" :search-keys="['name']" :option-key="'code'" :color="color"
|
||||
:placeholder="'Select a country'" :uppercase-labels="true" :theme="theme" @input="onChangeCountryCode">
|
||||
:placeholder="'Select a country'" :uppercase-labels="true" :theme="theme" @input="onChangeCountryCode"
|
||||
>
|
||||
<template #option="props">
|
||||
<div class="flex items-center space-x-2 hover:text-white">
|
||||
<country-flag size="normal" class="!-mt-[9px]" :country="props.option.code"/>
|
||||
<country-flag size="normal" class="!-mt-[9px]" :country="props.option.code" />
|
||||
<span class="grow">{{ props.option.name }}</span>
|
||||
<span>{{ props.option.dial_code }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #selected="props">
|
||||
<div class="flex items-center space-x-2 justify-center overflow-hidden">
|
||||
<country-flag size="normal" class="!-mt-[9px]" :country="props.option.code"/>
|
||||
<country-flag size="normal" class="!-mt-[9px]" :country="props.option.code" />
|
||||
<span>{{ props.option.dial_code }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</v-select>
|
||||
<input v-model="inputVal" type="text" class="inline-flex-grow !border-l-0 !rounded-l-none" :disabled="disabled"
|
||||
:class="[theme.default.input, { '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200': disabled }]"
|
||||
:placeholder="placeholder" :style="inputStyle" @input="onInput">
|
||||
:placeholder="placeholder" :style="inputStyle" @input="onInput"
|
||||
>
|
||||
</div>
|
||||
<div v-if="help && helpPosition=='below_input'" class="flex">
|
||||
<small :class="theme.default.help" class="grow">
|
||||
<slot name="help"><span class="field-help" v-html="help"/></slot>
|
||||
<slot name="help"><span class="field-help" v-html="help" /></slot>
|
||||
</small>
|
||||
</div>
|
||||
<has-error v-if="hasValidation" :form="form" :field="name"/>
|
||||
<has-error v-if="hasValidation" :form="form" :field="name" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {directive as onClickaway} from 'vue-clickaway'
|
||||
import { directive as onClickaway } from 'vue-clickaway'
|
||||
import inputMixin from '~/mixins/forms/input.js'
|
||||
import countryCodes from '../../../data/country_codes.json'
|
||||
import CountryFlag from 'vue-country-flag'
|
||||
|
@ -60,13 +63,13 @@ export default {
|
|||
},
|
||||
mixins: [inputMixin],
|
||||
props: {
|
||||
canOnlyCountry: { type: Boolean, default: false }
|
||||
canOnlyCountry: { type: Boolean, default: false },
|
||||
unavailableCountries: { type: Array, default: () => [] },
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
selectedCountryCode: null,
|
||||
countries: countryCodes,
|
||||
inputVal: null
|
||||
}
|
||||
},
|
||||
|
@ -78,7 +81,7 @@ export default {
|
|||
val = val.substring(1)
|
||||
}
|
||||
if (this.canOnlyCountry) {
|
||||
this.compVal = (val) ? this.selectedCountryCode.code + this.selectedCountryCode.dial_code + val : this.selectedCountryCode.code + this.selectedCountryCode.dial_code
|
||||
this.compVal = (val) ? this.selectedCountryCode.code + this.selectedCountryCode.dial_code + val : this.selectedCountryCode.code + this.selectedCountryCode.dial_code
|
||||
} else {
|
||||
this.compVal = (val) ? this.selectedCountryCode.code + this.selectedCountryCode.dial_code + val : null
|
||||
}
|
||||
|
@ -91,9 +94,17 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
countries () {
|
||||
return countryCodes.filter((item) => {
|
||||
return !this.unavailableCountries.includes(item.code)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
if (this.compVal) {
|
||||
if(!this.compVal.startsWith('+')){
|
||||
if (!this.compVal.startsWith('+')) {
|
||||
this.selectedCountryCode = this.getCountryBy(this.compVal.substring(2, 0))
|
||||
}
|
||||
|
||||
|
@ -105,21 +116,28 @@ export default {
|
|||
this.inputVal = phoneObj.nationalNumber
|
||||
}
|
||||
}
|
||||
if(!this.selectedCountryCode){
|
||||
if (!this.selectedCountryCode) {
|
||||
this.selectedCountryCode = this.getCountryBy()
|
||||
}
|
||||
if (!this.selectedCountryCode || this.countries.length === 1) {
|
||||
this.selectedCountryCode = this.countries[0]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getCountryBy (code = 'US', type = 'code') {
|
||||
if(!code) code = 'US' // Default US
|
||||
return countryCodes.find((item) => {
|
||||
if (!code) code = 'US' // Default US
|
||||
return this.countries.find((item) => {
|
||||
return item[type] === code
|
||||
})
|
||||
}) ?? null
|
||||
},
|
||||
onInput (event) {
|
||||
this.inputVal = event.target.value.replace(/[^0-9]/g, '')
|
||||
},
|
||||
onChangeCountryCode () {
|
||||
if (!this.selectedCountryCode && this.countries.length > 0) {
|
||||
this.selectedCountryCode = this.countries[0]
|
||||
}
|
||||
if (this.canOnlyCountry && (this.inputVal === null || this.inputVal === '' || !this.inputVal)) {
|
||||
this.compVal = this.selectedCountryCode.code + this.selectedCountryCode.dial_code
|
||||
}
|
||||
|
|
|
@ -302,8 +302,10 @@ export default {
|
|||
inputProperties.accept = (this.form.is_pro && field.allowed_file_types) ? field.allowed_file_types : ""
|
||||
} else if (field.type === 'number' && field.is_rating) {
|
||||
inputProperties.numberOfStars = parseInt(field.rating_max_value)
|
||||
} else if (['number', 'phone_number'].includes(field.type)) {
|
||||
inputProperties.pattern = "/\d*"
|
||||
} else if (field.type === 'number' || (field.type === 'phone_number' && field.use_simple_text_input)) {
|
||||
inputProperties.pattern = '/\d*'
|
||||
} else if (field.type === 'phone_number' && !field.use_simple_text_input) {
|
||||
inputProperties.unavailableCountries = field.unavailable_countries ?? []
|
||||
}
|
||||
|
||||
return inputProperties
|
||||
|
|
|
@ -161,9 +161,9 @@
|
|||
Advanced options for your select/multiselect fields.
|
||||
</p>
|
||||
<text-area-input v-model="optionsText" :name="field.id+'_options_text'" class="mt-3"
|
||||
@input="onFieldOptionsChange"
|
||||
label="Set selection options"
|
||||
help="Add one option per line"
|
||||
@input="onFieldOptionsChange"
|
||||
label="Set selection options"
|
||||
help="Add one option per line"
|
||||
/>
|
||||
<v-checkbox v-model="field.allow_creation"
|
||||
name="allow_creation" @input="onFieldAllowCreationChange" help=""
|
||||
|
@ -204,6 +204,39 @@
|
|||
>
|
||||
Use simple text input
|
||||
</v-checkbox>
|
||||
<template v-if="field.type === 'phone_number' && !field.use_simple_text_input">
|
||||
<v-select v-model="field.unavailable_countries" class="mt-4"
|
||||
:data="allCountries" :multiple="true"
|
||||
:searchable="true" :search-keys="['name']" :option-key="'code'" :emit-key="'code'"
|
||||
label="Disabled countries" :placeholder="'Select a country'"
|
||||
help="Remove countries from the phone input"
|
||||
>
|
||||
<template #selected="{option, selected}">
|
||||
<div class="flex items-center space-x-2 justify-center overflow-hidden">
|
||||
{{ option.length }} selected
|
||||
</div>
|
||||
</template>
|
||||
<template #option="{option, selected}">
|
||||
<div class="flex items-center space-x-2 hover:text-white">
|
||||
<country-flag size="normal" class="!-mt-[9px]" :country="option.code"/>
|
||||
<span class="grow">{{ option.name }}</span>
|
||||
<span>{{ option.dial_code }}</span>
|
||||
</div>
|
||||
<span v-if="selected" class="absolute inset-y-0 right-0 flex items-center pr-2 dark:text-white">
|
||||
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
</v-select>
|
||||
<small class="flex">
|
||||
<a href="#" class="grow" @click.prevent="selectAllCountries">Select All</a>
|
||||
<a href="#" @click.prevent="field.unavailable_countries=null">Un-select All</a>
|
||||
</small>
|
||||
</template>
|
||||
|
||||
<!-- Pre-fill depends on type -->
|
||||
<v-checkbox v-if="field.type=='checkbox'" v-model="field.prefill" class="mt-3"
|
||||
|
@ -223,14 +256,14 @@
|
|||
label="Pre-filled value"
|
||||
/>
|
||||
<phone-input v-else-if="field.type === 'phone_number' && !field.use_simple_text_input"
|
||||
name="prefill" class="mt-3"
|
||||
:form="field" :can-only-country="true"
|
||||
label="Pre-filled value"
|
||||
name="prefill" class="mt-3"
|
||||
:form="field" :can-only-country="true" :unavailable-countries="field.unavailable_countries ?? []"
|
||||
label="Pre-filled value"
|
||||
/>
|
||||
<text-area-input v-else-if="field.type === 'text' && field.multi_lines"
|
||||
name="prefill" class="mt-3"
|
||||
:form="field"
|
||||
label="Pre-filled value"
|
||||
name="prefill" class="mt-3"
|
||||
:form="field"
|
||||
label="Pre-filled value"
|
||||
/>
|
||||
<text-input v-else-if="field.type!=='files'" name="prefill" class="mt-3"
|
||||
:form="field"
|
||||
|
@ -263,19 +296,19 @@
|
|||
|
||||
<!-- Help -->
|
||||
<rich-text-area-input name="help" class="mt-3"
|
||||
:form="field"
|
||||
:editorToolbar="editorToolbarCustom"
|
||||
label="Field Help"
|
||||
help="Your field help will be shown below/above the field, just like this message."
|
||||
:help-position="field.help_position"
|
||||
:form="field"
|
||||
:editorToolbar="editorToolbarCustom"
|
||||
label="Field Help"
|
||||
help="Your field help will be shown below/above the field, just like this message."
|
||||
:help-position="field.help_position"
|
||||
/>
|
||||
<select-input name="help_position" class="mt-3"
|
||||
:options="[
|
||||
:options="[
|
||||
{name:'Below input',value:'below_input'},
|
||||
{name:'Above input',value:'above_input'},
|
||||
]"
|
||||
:form="field" label="Field Help Position"
|
||||
@input="onFieldHelpPositionChange"
|
||||
:form="field" label="Field Help Position"
|
||||
@input="onFieldHelpPositionChange"
|
||||
/>
|
||||
|
||||
<template v-if="['text','number','url','email'].includes(field.type)">
|
||||
|
@ -305,7 +338,8 @@
|
|||
Generates a unique id
|
||||
</v-checkbox>
|
||||
<p class="text-gray-400 mb-3 text-xs">
|
||||
If you enable this, we will hide this field and fill it with a unique id (UUID format) on each new form submission
|
||||
If you enable this, we will hide this field and fill it with a unique id (UUID format) on each new form
|
||||
submission
|
||||
</p>
|
||||
|
||||
<v-checkbox v-model="field.generates_auto_increment_id"
|
||||
|
@ -327,10 +361,12 @@
|
|||
<script>
|
||||
const FormBlockLogicEditor = () => import('../../components/form-logic-components/FormBlockLogicEditor.vue')
|
||||
import timezones from '../../../../../../data/timezones.json'
|
||||
import countryCodes from '../../../../../../data/country_codes.json'
|
||||
import CountryFlag from 'vue-country-flag'
|
||||
|
||||
export default {
|
||||
name: 'FieldOptions',
|
||||
components: {FormBlockLogicEditor},
|
||||
components: {FormBlockLogicEditor, CountryFlag},
|
||||
props: {
|
||||
field: {
|
||||
type: Object,
|
||||
|
@ -346,7 +382,8 @@ export default {
|
|||
typesWithoutPlaceholder: ['date', 'checkbox', 'files'],
|
||||
editorToolbarCustom: [
|
||||
['bold', 'italic', 'underline', 'link'],
|
||||
]
|
||||
],
|
||||
allCountries: countryCodes,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -388,7 +425,7 @@ export default {
|
|||
|
||||
watch: {
|
||||
'field.width': {
|
||||
handler (val) {
|
||||
handler(val) {
|
||||
if (val === undefined || val === null) {
|
||||
this.$set(this.field, 'width', 'full')
|
||||
}
|
||||
|
@ -396,7 +433,7 @@ export default {
|
|||
immediate: true
|
||||
},
|
||||
'field.align': {
|
||||
handler (val) {
|
||||
handler(val) {
|
||||
if (val === undefined || val === null) {
|
||||
this.$set(this.field, 'align', 'left')
|
||||
}
|
||||
|
@ -405,7 +442,7 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
created() {
|
||||
if (this.field?.width === undefined || this.field?.width === null) {
|
||||
this.$set(this.field, 'width', 'full')
|
||||
}
|
||||
|
@ -418,7 +455,7 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
onFieldDisabledChange (val) {
|
||||
onFieldDisabledChange(val) {
|
||||
this.$set(this.field, 'disabled', val)
|
||||
if (this.field.disabled) {
|
||||
this.$set(this.field, 'hidden', false)
|
||||
|
@ -519,10 +556,15 @@ export default {
|
|||
this.$set(this.field, 'prefill_today', false)
|
||||
}
|
||||
},
|
||||
onFieldHelpPositionChange (val) {
|
||||
onFieldHelpPositionChange(val) {
|
||||
if (!val) {
|
||||
this.$set(this.field, 'help_position', 'below_input')
|
||||
}
|
||||
},
|
||||
selectAllCountries() {
|
||||
this.$set(this.field, 'unavailable_countries', this.allCountries.map(item => {
|
||||
return item.code
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue