iamstevendao / vue-tel-input

International Telephone Input with Vue
https://iamstevendao.com/vue-tel-input/
MIT License
816 stars 342 forks source link

Need help using with vee-validate #75

Closed pravinfullstack closed 5 years ago

pravinfullstack commented 5 years ago

I want help regarding using this vue-tel-input with vee-validate

koraykupe commented 5 years ago

I also need that. Why did you close it?

andreladocruz commented 3 years ago

+1

ITBDarky commented 2 years ago

+1

palfaro91 commented 2 years ago

@power-cut @koraykupe @andreladocruz @ITBDarky did you guys end up figuring out how to hook the two together?

andreladocruz commented 2 years ago

@palfaro91 ... nops...

creage commented 2 years ago

Enjoy.

<template>
    <ValidationProvider
        ref="provider"
        :rules="rules"
        :name="name"
        v-slot="{ errors }"
    >
        <div :class="rootClass" ref="wrapper">
            <div class="v-input__control">
                <div class="v-input__slot">
                    <div class="v-text-field__slot">
                        <label :class="labelClass">
                            {{ name }}
                        </label>
                        <vue-tel-input
                            ref="phoneInput"
                            mode="international"
                            v-bind="attrs"
                            v-on="listeners"
                        ></vue-tel-input>
                    </div>
                </div>
                <div class="v-text-field__details">
                    <div
                        v-if="errors.length"
                        class="v-messages theme--light error--text"
                        role="alert"
                    >
                        <div class="v-messages__wrapper">
                            <div class="v-messages__message">
                                {{ errors[0] }}
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </ValidationProvider>
</template>

<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';

import { ValidationProvider, extend } from 'vee-validate';
import { VueTelInput } from 'vue-tel-input';

const INSTANCES = new Map();

extend('phone', (_, params) => {
    if (Array.isArray(params)) {
        const validator = INSTANCES.get(Number(params[0]));

        if (validator) {
            const isValid = validator();

            if (!isValid) {
                return 'Please enter correct phone number, valid for the country selected';
            }

            return isValid;
        }
    }

    return true;
});

@Component({
    components: {
        VueTelInput,
        ValidationProvider
    }
})
export default class APhoneInput extends Vue {
    _uid!: number;

    $refs!: {
        provider: InstanceType<typeof ValidationProvider>;
        phoneInput: InstanceType<typeof VueTelInput>;
        wrapper: HTMLDivElement;
    };

    innerValue = '';
    isFocused = false;
    isOpen = false;
    isInputValid = true;
    maxWidth = 390;

    get id() {
        return this._uid;
    }

    get name() {
        return this.$attrs.label;
    }

    get isValid() {
        return this.innerValue
            ? this.isInputValid === true
            : !this.$refs.provider?.errors.length;
    }

    get rules() {
        return [this.$attrs.rules, `phone:${this.id}`]
            .filter(Boolean)
            .join('|');
    }

    get rootClass() {
        const classes = [
            'v-input-phone',
            'v-input theme--light',
            'v-text-field',
            'v-text-field--filled',
            'v-text-field--is-booted',
            'v-text-field--enclosed'
        ];

        if (this.isFocused) {
            classes.push('v-input--is-focused', 'primary--text');
        }

        if (this.innerValue) {
            classes.push('v-input--is-dirty', 'v-input--has-state');
        }

        if (this.isValid) {
            classes.push('success--text');
        } else {
            classes.push('error--text');
        }

        return classes.join(' ');
    }

    get labelClass() {
        const classes = new Set(['v-label', 'theme--light']);

        if (this.isFocused || this.isOpen) {
            classes.add('primary--text');
            classes.add('v-label--active');
        }

        if (this.innerValue) {
            classes.add('v-label--active');
        }

        if (this.isValid) {
            classes.add('success--text');
        } else {
            classes.add('error--text');
        }

        return [...classes].join(' ');
    }

    get attrs() {
        return {
            defaultCountry: 'US',
            preferredCountries: ['US', 'GB'],
            dropdownOptions: {
                width: `${this.maxWidth}px`,
                showFlags: true,
                showDialCodeInList: true,
                //showDialCodeInSelection: true,
                showSearchBox: true
            },
            inputOptions: {
                placeholder: ''
            },
            ...this.$attrs
        };
    }

    get listeners() {
        return {
            focus: () => {
                this.isFocused = true;
            },
            blur: () => {
                this.isFocused = false;
            },
            open: () => {
                this.isOpen = true;
                setTimeout(this.focusSearchInput.bind(this), 50);
            },
            close: () => {
                this.isOpen = false;
            },
            input: (value: string) => {
                this.innerValue = value;
                this.$emit('input', value);
            },
            validate: (data: { valid: boolean }) => {
                this.isInputValid = data.valid;
            }
        };
    }

    created() {
        INSTANCES.set(this.id, () => this.isValid);
    }

    beforeDestroy() {
        INSTANCES.delete(this.id);
    }

    mounted() {
        // inherit the width of the control
        this.maxWidth = this.$refs.wrapper.clientWidth;
    }

    focusSearchInput() {
        const list = this.$refs.phoneInput.$refs.list as HTMLDivElement;
        const inputs = list.getElementsByClassName('vti__search_box');

        if (inputs.length) {
            (inputs[0] as HTMLInputElement).focus();
        }
    }
}
</script>

<style src="vue-tel-input/dist/vue-tel-input.css"></style>
<style lang="scss">
.v-input-phone {
    &.v-text-field--filled .v-label {
        top: 13px;
        left: 0px;
        right: auto;
        position: absolute;

        &.v-label--active {
            top: 18px;
        }
    }

    .vue-tel-input {
        border: none;
        width: calc(100% + 11px);
        margin-left: -11px;

        &:focus-within {
            border: none;
            box-shadow: none;
        }

        .vti__dropdown {
            margin-top: 22px;

            &.open,
            &:hover {
                background-color: transparent;
            }
        }

        .vti__dropdown-list {
            padding: 0;

            .vti__search_box {
                width: calc(100% - 20px);
                margin: 10px;
                padding: 3px 5px;
            }

            .vti__dropdown-item {
                padding: 4px 7px;

                &:focus,
                &:focus-visible {
                    outline: none;
                }
            }
        }
    }
}
</style>
vodnicearv commented 1 year ago

how to integrate with vee-validate 4?

palfaro91 commented 1 year ago

@vodnicearv did you ever figure it out?