jackocnr / intl-tel-input

A JavaScript plugin for entering and validating international telephone numbers. Now includes React and Vue components.
https://intl-tel-input.com
MIT License
7.57k stars 1.94k forks source link

Feature Request - Vue 3 component #1746

Closed Lrochefort closed 1 week ago

Lrochefort commented 1 month ago

Not that I am jealous and stuff but... A nice Vue 3 component should be the next thing after the React one, since Vue.js IS the next thing after (the decline of) React...

image

jackocnr commented 1 month ago

Haha, I completely agree with you that we should add a Vue 3 component. I have no experience with Vue, so cannot help here, but I would welcome a pull request to add this. It could be super simple to start with - just a Vue component that imports the plugin and creates an input and initialises the plugin on it.

e.g. see v1 of the react component here - it's literally a simple react component that renders an input, and after the first render (useEffect) it initialises the plugin, and calls an update function when things change.

You could just put it here to start with: src/js/intl-tel-input/vue.ts. And then others could help iterate from there.

Or if you were up for a slightly more complex setup, you could imitate the react setup, with a root vue/ directory, and setup symlinks to the other required plugin files (this is a workaround so that typescript module namespacing works well).

Lrochefort commented 1 month ago

I agree. My problem is the lack of time. I may be able to do a simple PoC next weekend. To be continued…

mdpoulter commented 4 weeks ago

If you're looking for a start, this is what I've used in the past (although it'll need to be converted to TypeScript):

<script setup>
import intlTelInput from 'intl-tel-input/intlTelInputWithUtils';
import 'intl-tel-input/styles';
import { onMounted, onUnmounted, ref, watch } from 'vue';

const model = defineModel({
    type: String,
    default: '',
});

const props = defineProps({
    options: {
        type: Object,
        default: () => ({}),
    },
    value: {
        type: String,
        default: '',
    },
    disabled: {
        type: Boolean,
    },
});

const emit = defineEmits(['changeNumber', 'changeCountry', 'changeValidity', 'changeErrorCode']);

const input = ref();
const instance = ref();
const wasPreviouslyValid = ref(false);

const isValid = () => {
    if (instance.value) {
        return props.options.strictMode ? instance.value.isValidNumberPrecise() : instance.value.isValidNumber();
    }

    return null;
};

const updateValidity = () => {
    let isCurrentlyValid = isValid();

    if (wasPreviouslyValid.value !== isCurrentlyValid) {
        wasPreviouslyValid.value = isCurrentlyValid;

        emit('changeValidity', !!isCurrentlyValid);
        emit('changeErrorCode', isCurrentlyValid ? null : instance.value.getValidationError());
    }
};

const updateValue = () => {
    emit('changeNumber', instance.value?.getNumber() ?? '');
    updateValidity();
};

const updateCountry = () => {
    emit('changeCountry', instance.value?.getSelectedCountryData().iso2 ?? '');
    updateValue();
    updateValidity();
};

onMounted(() => {
    if (input.value) {
        instance.value = intlTelInput(input.value, props.options);

        if (props.value) {
            instance.value.setNumber(props.value);
        }

        if (props.disabled) {
            instance.value.setDisabled(props.disabled);
        }
    }
});

watch(
    () => props.disabled,
    newValue => instance.value?.setDisabled(newValue)
);

onUnmounted(() => instance.value?.destroy());

defineExpose({ instance, input });
</script>

<template>
    <input ref="input" v-model="model" type="tel" @countrychange="updateCountry" @input="updateValue" />
</template>

Edit: With small improvement to expose the instance and input.

Edit 2: One thing I never achieved was exposing the formatted number as the model value, e.g. <TelInput v-model="form.phone" /> where form.phone is the formatted number from getNumber() but the base input's value is the raw value. Similar concept with a currency field: https://dm4t2.github.io/vue-currency-input/playground.html

Lrochefort commented 4 weeks ago

If you're looking for a start, this is what I've used in the past (although it'll need to be converted to TypeScript):

Noice Matt!

Will we be reaching Monday before we have a Vue 3 component?

Let’s share this in Vue communities!

jackocnr commented 3 weeks ago

Thanks for this initial code @mdpoulter! I've literally just copied this into the project here: vue/src/intl-tel-input/IntlTelInput.vue and attempted to hook it up a bit, so you should (untested) now be able to do:

import IntlTelInput from "intl-tel-input/vue"; or import IntlTelInput from "intl-tel-input/vueWithUtils";

That's released in ~v24.1.0~ v24.3.3

Or alternatively, you can fork the project, and run npm run vue:demo to see a simple demo of it up and running. It seems to intialise the plugin without any errors! I don't know enough about Vue to hook up the validation button - is that something you could help with? (I was imagining it would work similarly to the react validation demo here: react/demo/validation/validation.html)

jackocnr commented 3 weeks ago

Oops, I didn't update package.json, so probably those imports didn't work. That should now be fixed in v24.3.3.

Do give it a try and let me know how it goes!

And any thoughts on how to hook up the validation demo app would be appreciated!

jackocnr commented 2 weeks ago

Early stage Vue Component readme here: https://github.com/jackocnr/intl-tel-input/blob/master/vue/README.md

jackocnr commented 2 weeks ago

@Lrochefort @mdpoulter @carlssonemil have you managed to try using the new Vue component at all yet?

carlssonemil commented 2 weeks ago

@jackocnr, not yet! We use our own wrapper Vue component at work but I'm very interested in switching to the provided one if it fits our needs. Just haven't had the time yet but will I'll let you know when I do try it out! 😁

jackocnr commented 2 weeks ago

πŸ‘πŸ»

We use our own wrapper Vue component

I'd be interested to hear if there are any significant differences to our one.

carlssonemil commented 1 week ago

@jackocnr it works like a charm, sweet! The only thing we're missing is the ability to pass attributes to the <input> element, like id, required, disabled, etc. Either as an option (inputAttributes) or as individual props, though there might be a couple that is needed so I'm not entirely sure what the best option is. I'll gladly fix it and open a PR if we decide on the best course of action that works for everyone.

I've temporarily created a method that adds these manually whenever they are needed as a workaround for now.

I'll see if I can find the time to look into fixing the validation demo!

jackocnr commented 1 week ago

it works like a charm, sweet!

Wahoo! Excellent news πŸŽ‰ (this is all thanks to @mdpoulter)

The only thing we're missing is the ability to pass attributes to the element [...] though there might be a couple that is needed

Yes I think easier to just support passing an object called inputProps (like we do for the react component), as there would be so many if we wanted to support all the possible properties individually.

I'll gladly fix it and open a PR

Amazing πŸ™πŸ»

I'll see if I can find the time to look into fixing the validation demo!

That would be really appreciated! It should be fairly straight forward if you know Vue.

jackocnr commented 1 week ago

Thanks again for your work on this @carlssonemil πŸ™πŸ»

I think we can close this issue now πŸŽ‰