UNPKG

maz-ui

Version:

A standalone components library for Vue.Js 3 & Nuxt.Js 3

413 lines (412 loc) 18.3 kB
import { ref, defineComponent, defineAsyncComponent, computed, onBeforeMount, onMounted, watch, provide, createElementBlock, openBlock, normalizeStyle, normalizeClass, unref, createBlock, createCommentVNode, createVNode, mergeProps, withCtx, createElementVNode, toDisplayString, renderSlot, nextTick } from "vue"; import { useTranslations } from "@maz-ui/translations"; import { useInstanceUniqId } from "../composables/useInstanceUniqId.js"; import { getCountryCallingCode, getCountries, isSupportedCountry, getExampleNumber, parsePhoneNumberFromString, AsYouType } from "libphonenumber-js"; import { _ as _export_sfc } from "./_plugin-vue_export-helper.B--vMWp3.js"; import '../assets/MazInputPhoneNumber.CKOOaZiA.css';const examples = ref(); function isCountryAvailable(locale) { try { const response = isSupportedCountry(locale); return response || (console.error(`[maz-ui](MazInputPhoneNumber) The code country "${locale}" is not available`), !1); } catch (error) { return console.error(`[maz-ui](MazInputPhoneNumber) ${error}`), !1; } } function getPhoneNumberResults({ phoneNumber, countryCode, checkCountryCode = !1 }) { try { if (!phoneNumber) return { isValid: !1, countryCode }; const parsedNumber = parsePhoneNumberFromString(phoneNumber, countryCode ?? void 0), isValid = parsedNumber?.isValid() ?? !1, isSameCountryCode = countryCode && checkCountryCode ? parsedNumber?.country && countryCode === parsedNumber.country : !0; return { isValid: isValid && !!isSameCountryCode, countryCode, parsedCountryCode: parsedNumber?.country, isPossible: parsedNumber?.isPossible(), countryCallingCode: parsedNumber?.countryCallingCode, nationalNumber: parsedNumber?.nationalNumber, type: parsedNumber?.getType(), formatInternational: parsedNumber?.formatInternational(), formatNational: parsedNumber?.formatNational(), uri: parsedNumber?.getURI(), e164: parsedNumber?.format("E.164"), rfc3966: parsedNumber?.format("RFC3966"), possibleCountries: parsedNumber?.getPossibleCountries(), phoneNumber }; } catch (error) { throw new Error(`[MazInputPhoneNumber](getResultsFromPhoneNumber) ${error}`); } } async function getPhoneNumberExamplesFile() { const { default: data } = await import("./examples.mobile.json.CZXHnLzv.js"); return data; } function getPhoneNumberExample(countryCode) { try { return examples.value && countryCode ? getExampleNumber(countryCode, examples.value)?.formatNational() : void 0; } catch (error) { console.error(`[maz-ui](MazInputPhoneNumber) ${error}`); } } function getAsYouTypeFormat(countryCode, phoneNumber) { try { return !phoneNumber || !countryCode || typeof countryCode != "string" || countryCode.length !== 2 ? phoneNumber : new AsYouType(countryCode).input(phoneNumber); } catch (error) { return console.error(`[MazInputPhoneNumber](getAsYouTypeFormat) Error with countryCode: "${countryCode}", phoneNumber: "${phoneNumber}"`, error), phoneNumber; } } function isSameCountryCallingCode(countryCode, countryCode2) { return getCountryCallingCode(countryCode) === getCountryCallingCode(countryCode2); } async function loadExamples() { try { if (examples.value) return; examples.value = await getPhoneNumberExamplesFile(); } catch (error) { console.error("[maz-ui](MazInputPhoneNumber) while loading phone number examples file", error); } } function useLibphonenumber() { return { examples, getAsYouTypeFormat, getPhoneNumberResults, getPhoneNumberExamplesFile, getPhoneNumberExample, isSameCountryCallingCode, isCountryAvailable, getCountries, getCountryCallingCode, loadExamples }; } async function fetchLocaleIp() { try { const reponse = await fetch("https://ipwho.is"), { country_code } = await reponse.json(); return country_code; } catch (error) { console.error(`[maz-ui](fetchLocaleIp) ${error}`); return; } } function getBrowserLocale() { if (typeof globalThis.window > "u") return; const browserLocale = globalThis.navigator.language; if (!browserLocale) return; const parts = browserLocale.split("-"); let locale = parts.length > 1 ? parts[1].slice(0, 2).toUpperCase() : parts[0].slice(0, 2).toUpperCase(); return locale === "EN" && (locale = "US"), locale === "JA" && (locale = "JP"), { locale, browserLocale }; } let displayNamesInstance, displayNamesLocale; function getCountryName(locale, code, customCountriesNameListByIsoCode) { return customCountriesNameListByIsoCode?.[code] ? customCountriesNameListByIsoCode[code] : ((displayNamesLocale !== locale || !displayNamesInstance) && (displayNamesLocale = locale, displayNamesInstance = new Intl.DisplayNames([locale], { type: "region" })), displayNamesInstance.of(code)); } function getCountryList(locale, customCountriesNameListByIsoCode) { const countriesList = [], isoList = getCountries(); locale = locale ?? getBrowserLocale()?.browserLocale ?? "en-US"; for (const code of isoList) { const name = getCountryName(locale, code, customCountriesNameListByIsoCode); if (name) try { const dialCode = getCountryCallingCode(code); countriesList.push({ code, dialCode, name }); } catch (error) { console.error(`[MazInputPhoneNumber](getCountryCallingCode) ${error}`); } } return countriesList; } async function fetchCountryCode() { try { const countryCode = await fetchLocaleIp(); return countryCode ? { data: countryCode, error: void 0 } : { data: void 0, error: new Error("[MazInputPhoneNumber](fetchCountryCode) No country code found") }; } catch (error) { return { data: void 0, error: new Error(`[MazInputPhoneNumber](fetchCountryCode) ${error}`) }; } } function sanitizePhoneNumber(input) { if (!input) return ""; const regex = new RegExp(/[^\d ()+-]/g); return input.replaceAll(regex, "").trim(); } function useMazInputPhoneNumber() { return { fetchCountryCode, getBrowserLocale, getCountryList, sanitizePhoneNumber }; } const _hoisted_1 = ["id"], _hoisted_2 = { class: "m-input-phone-number__country-list-code" }, _sfc_main = /* @__PURE__ */ defineComponent({ name: "MazInputPhoneNumber", inheritAttrs: !1, __name: "MazInputPhoneNumber", props: { style: {}, class: {}, modelValue: {}, countryCode: {}, id: {}, placeholder: {}, label: {}, preferredCountries: {}, ignoredCountries: {}, onlyCountries: {}, translations: {}, listPosition: { default: "bottom-start" }, color: { default: "primary" }, size: { default: "md" }, hideFlags: { type: Boolean, default: !1 }, disabled: { type: Boolean, default: !1 }, example: { type: Boolean, default: !0 }, search: { type: Boolean, default: !0 }, searchThreshold: { default: 0.75 }, useBrowserLocale: { type: Boolean, default: !0 }, fetchCountry: { type: Boolean }, hideCountrySelect: { type: Boolean, default: !1 }, showCodeInList: { type: Boolean, default: !1 }, customCountriesList: { default: void 0 }, autoFormat: { type: [String, Boolean], default: "blur" }, countryLocale: { default: void 0 }, validationError: { type: Boolean, default: !0 }, validationSuccess: { type: Boolean, default: !0 }, success: { type: Boolean }, error: { type: Boolean }, displayCountryName: { type: Boolean, default: !1 }, block: { type: Boolean }, orientation: { default: "responsive" }, countrySelectAttributes: { default: () => ({ name: "country", autocomplete: "off", style: { width: "14rem" } }) }, phoneInputAttributes: { default: () => ({ name: "phone", autocomplete: "tel", inputmode: "tel" }) } }, emits: ["update:model-value", "country-code", "update:country-code", "data"], setup(__props, { emit: __emit }) { const props = __props, emits = __emit, MazSelectCountry = defineAsyncComponent(() => import("../components/MazSelectCountry.js")), PhoneInput = defineAsyncComponent(() => import("./PhoneInput.BK3DUfxF.js")), { fetchCountryCode: fetchCountryCode2, getBrowserLocale: getBrowserLocale2, getCountryList: getCountryList2 } = useMazInputPhoneNumber(), { isCountryAvailable: isCountryAvailable2, getPhoneNumberResults: getPhoneNumberResults2 } = useLibphonenumber(), instanceId = useInstanceUniqId({ componentName: "MazInputPhoneNumber", providedId: props.id }), phoneNumber = ref(), selectedCountry = ref(), { t } = useTranslations(), messages = computed(() => ({ countrySelect: { error: props.translations?.countrySelect?.error || t("inputPhoneNumber.countrySelect.error"), placeholder: props.translations?.countrySelect?.placeholder || t("inputPhoneNumber.countrySelect.placeholder"), searchPlaceholder: props.translations?.countrySelect?.searchPlaceholder || t("inputPhoneNumber.countrySelect.searchPlaceholder") }, phoneInput: { placeholder: props.translations?.phoneInput?.placeholder || t("inputPhoneNumber.phoneInput.placeholder"), example: props.translations?.phoneInput?.example } })), isPhoneNumberInternalUpdate = ref(!1), isCountryInternalUpdate = ref(!1), results = ref({ isValid: !1, countryCode: props.countryCode, phoneNumber: props.modelValue }), PhoneInputRef = ref(); onBeforeMount(async () => { if (props.countryCode && !selectedCountry.value && onCountryChanged({ countryCode: props.countryCode }), props.fetchCountry && !selectedCountry.value) { const { data: countryCode, error } = await fetchCountryCode2(); if (error) { console.error(`[MazInputPhoneNumber](onBeforeMount) Error while fetching country code - ${error.message}`); return; } onCountryChanged({ countryCode }); } }), onMounted(() => { if (!selectedCountry.value && props.useBrowserLocale) { const countryCode = getBrowserLocale2()?.locale; countryCode && isCountryAvailable2(countryCode) && onCountryChanged({ countryCode }); } }); function updateTheResults({ phone = phoneNumber.value || props.modelValue, countryCode = selectedCountry.value, checkCountryCode = !1 }) { results.value = getPhoneNumberResults2({ phoneNumber: phone, countryCode, checkCountryCode }); } async function selectPhoneNumberInput() { await nextTick(), setTimeout(() => { PhoneInputRef.value?.focus(); }, 100); } function setSelectedCountry(countryCode) { if (countryCode) { if (!isCountryAvailable2(countryCode)) { console.warn(`[MazInputPhoneNumber] Country code not available: "${countryCode}"`), selectedCountry.value = void 0; return; } selectedCountry.value = countryCode; } } function onPhoneNumberChanged({ newPhoneNumber }) { updateTheResults({ phone: newPhoneNumber }), results.value.parsedCountryCode && results.value.parsedCountryCode !== selectedCountry.value && onCountryChanged({ countryCode: results.value.parsedCountryCode, updateResults: !1 }), results.value.isValid && (props.autoFormat === "blur" || props.autoFormat === "typing") ? phoneNumber.value = results.value.formatNational?.trim().replace(new RegExp(/\D/g), "") : phoneNumber.value = newPhoneNumber, isPhoneNumberInternalUpdate.value = !0, results.value.e164 ? emits("update:model-value", results.value.e164) : emits("update:model-value", results.value.phoneNumber), setTimeout(() => { isPhoneNumberInternalUpdate.value = !1; }, 0); } function onCountryChanged({ countryCode, updateResults = !0, selectPhoneNumber = !1 }) { if (!countryCode) { selectedCountry.value = void 0; return; } isCountryInternalUpdate.value = !0, countryCode !== selectedCountry.value && setSelectedCountry(countryCode), updateResults && updateTheResults({ countryCode: selectedCountry.value, checkCountryCode: !0 }); const code = selectedCountry.value; emits("country-code", code), emits("update:country-code", code), selectPhoneNumber && !results.value.isValid && selectPhoneNumberInput(), setTimeout(() => { isCountryInternalUpdate.value = !1; }, 0); } const countriesList = computed(() => { let list = getCountryList2(props.countryLocale, props.customCountriesList); return props.onlyCountries && (list = list?.filter((country) => props.onlyCountries?.includes(country.code))), props.ignoredCountries && (list = list?.filter((country) => !props.ignoredCountries?.includes(country.code))), props.preferredCountries && (list = list?.sort((a, b) => { const indexA = props.preferredCountries?.indexOf(a.code) ?? -1, indexB = props.preferredCountries?.indexOf(b.code) ?? -1; return indexA >= 0 && indexB >= 0 ? indexA - indexB : indexA >= 0 ? -1 : indexB >= 0 ? 1 : 0; })), list ?? []; }); return watch( () => props.modelValue, (value, oldValue) => { !isPhoneNumberInternalUpdate.value && value !== oldValue && value !== phoneNumber.value && onPhoneNumberChanged({ newPhoneNumber: value }); }, { immediate: !0 } ), watch( () => props.countryCode, (value, oldValue) => { !isCountryInternalUpdate.value && value && value !== oldValue && value !== selectedCountry.value && onCountryChanged({ countryCode: value }); }, { immediate: !0 } ), watch( results, (value) => emits("data", value), { immediate: !0 } ), provide("mazInputPhoneNumberData", { selectedCountry, phoneNumber, results }), (_ctx, _cache) => (openBlock(), createElementBlock("div", { id: unref(instanceId), class: normalizeClass(["m-input-phone-number m-reset-css", [props.class, { "--block": __props.block }, __props.orientation ? `--${__props.orientation}` : void 0]]), style: normalizeStyle(__props.style) }, [ __props.hideCountrySelect ? createCommentVNode("", !0) : (openBlock(), createBlock(unref(MazSelectCountry), mergeProps({ key: 0, id: `${unref(instanceId)}-country`, class: "m-input-phone-number__country-select" }, __props.countrySelectAttributes, { "model-value": selectedCountry.value, "option-input-value-key": __props.displayCountryName ? "name" : "dialCode", color: __props.color, size: __props.size, locale: __props.countryLocale, "countries-list": __props.customCountriesList, "list-position": __props.listPosition, "hide-flags": __props.hideFlags, search: __props.search, block: __props.block, error: __props.error || (__props.validationError ? !!phoneNumber.value && !selectedCountry.value : !1), success: __props.success || (__props.validationSuccess ? results.value?.isValid : !1), translations: messages.value.countrySelect, hint: phoneNumber.value && !selectedCountry.value ? messages.value.countrySelect.error : void 0, options: countriesList.value, disabled: __props.disabled, "search-threshold": __props.searchThreshold, "format-input-value": __props.displayCountryName || !selectedCountry.value ? void 0 : (value) => `+${value}`, "show-code-in-list": __props.showCodeInList, label: messages.value.countrySelect.placeholder, "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => onCountryChanged({ countryCode: $event, selectPhoneNumber: !0 })) }), { "no-results": withCtx(() => [ renderSlot(_ctx.$slots, "no-results", {}, void 0, !0) ]), "selector-flag": withCtx(({ countryCode: codeCountry }) => [ renderSlot(_ctx.$slots, "selector-flag", { countryCode: codeCountry }, void 0, !0) ]), "country-list-flag": withCtx(({ isSelected, option }) => [ renderSlot(_ctx.$slots, "country-list-flag", { countryCode: option.code, option, isSelected }, void 0, !0) ]), "country-list-code": withCtx(({ option }) => [ createElementVNode("span", _hoisted_2, " +" + toDisplayString(option.dialCode), 1) ]), _: 3 }, 16, ["id", "model-value", "option-input-value-key", "color", "size", "locale", "countries-list", "list-position", "hide-flags", "search", "block", "error", "success", "translations", "hint", "options", "disabled", "search-threshold", "format-input-value", "show-code-in-list", "label"])), createVNode(unref(PhoneInput), mergeProps({ id: `${unref(instanceId)}-phone`, ref_key: "PhoneInputRef", ref: PhoneInputRef, class: "m-input-phone-number__phone-input", "model-value": phoneNumber.value }, { ..._ctx.$attrs, ...__props.phoneInputAttributes }, { color: __props.color, size: __props.size, "auto-format": __props.autoFormat, example: __props.example, block: "", disabled: __props.disabled, name: __props.phoneInputAttributes.name, "has-radius": !__props.hideCountrySelect, success: __props.success || (__props.validationSuccess ? results.value.isValid : !1), error: __props.error || (__props.validationError ? !!phoneNumber.value && !results.value.isValid : !1), locales: messages.value.phoneInput, label: __props.label, placeholder: __props.placeholder, "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => onPhoneNumberChanged({ newPhoneNumber: $event })) }), null, 16, ["id", "model-value", "color", "size", "auto-format", "example", "disabled", "name", "has-radius", "success", "error", "locales", "label", "placeholder"]) ], 14, _hoisted_1)); } }), MazInputPhoneNumber = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-2b9b89e5"]]); export { MazInputPhoneNumber as M, useMazInputPhoneNumber as a, useLibphonenumber as u };