@bagelink/vue 1.15.162 → 1.15.166
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/form/inputs/SelectBtn.vue.d.ts +1 -0
- package/dist/components/form/inputs/SelectBtn.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/TelInput.vue.d.ts +2 -0
- package/dist/components/form/inputs/TelInput.vue.d.ts.map +1 -1
- package/dist/index.cjs +91 -91
- package/dist/index.mjs +7932 -7864
- package/dist/style.css +1 -1
- package/dist/utils/ipapi.d.ts +9 -1
- package/dist/utils/ipapi.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/RouterWrapper.vue +1 -1
- package/src/components/form/inputs/SelectBtn.vue +6 -1
- package/src/components/form/inputs/TelInput.vue +34 -2
- package/src/styles/layout.css +4 -0
- package/src/styles/mobilLayout.css +4 -0
- package/src/utils/ipapi.ts +83 -15
package/dist/utils/ipapi.d.ts
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface IpApiResult {
|
|
2
|
+
country_code: string;
|
|
3
|
+
[key: string]: unknown;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the visitor's country from their IP.
|
|
7
|
+
* Result is cached in sessionStorage so multiple TelInputs share one lookup.
|
|
8
|
+
*/
|
|
9
|
+
export declare function ipapi(): Promise<IpApiResult>;
|
|
2
10
|
//# sourceMappingURL=ipapi.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ipapi.d.ts","sourceRoot":"","sources":["../../src/utils/ipapi.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ipapi.d.ts","sourceRoot":"","sources":["../../src/utils/ipapi.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACtB;AA+DD;;;GAGG;AACH,wBAAsB,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC,CAmBlD"}
|
package/package.json
CHANGED
|
@@ -32,7 +32,7 @@ watch(
|
|
|
32
32
|
</div>
|
|
33
33
|
</slot>
|
|
34
34
|
<transition v-else :duration="300" name="slide-fade" mode="out-in" appear>
|
|
35
|
-
<component :is="Component" :key="route
|
|
35
|
+
<component :is="Component" :key="route.fullPath" />
|
|
36
36
|
</transition>
|
|
37
37
|
</RouterView>
|
|
38
38
|
</template>
|
|
@@ -13,6 +13,7 @@ const props = withDefaults(
|
|
|
13
13
|
required?: boolean
|
|
14
14
|
error?: string
|
|
15
15
|
thin?: boolean
|
|
16
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
|
16
17
|
multiselect?: boolean
|
|
17
18
|
} & BagelInputShellProps>(),
|
|
18
19
|
{ multiselect: false },
|
|
@@ -60,6 +61,7 @@ function isSelected(val: OptionValue) {
|
|
|
60
61
|
<div class="flex flex-wrap gap-05">
|
|
61
62
|
<Btn
|
|
62
63
|
v-for="option in options" :key="String(getOptionValue(option))" :thin="thin"
|
|
64
|
+
:size="size"
|
|
63
65
|
type="button"
|
|
64
66
|
:class="isSelected(getOptionValue(option)!) ? 'color-white' : 'bg-transparent'"
|
|
65
67
|
:style="isSelected(getOptionValue(option)!)
|
|
@@ -72,7 +74,7 @@ function isSelected(val: OptionValue) {
|
|
|
72
74
|
color: 'var(--bgl-input-label-active-color, var(--bgl-border-color))',
|
|
73
75
|
borderColor: 'var(--bgl-input-label-active-color, var(--bgl-border-color))',
|
|
74
76
|
}"
|
|
75
|
-
class="border
|
|
77
|
+
class="border radius-2 SelectBtn-padding"
|
|
76
78
|
@click="toggleOption(getOptionValue(option)!)"
|
|
77
79
|
>
|
|
78
80
|
<Icon v-if="getOptionIcon(option)" :icon="getOptionIcon(option)!" weight="300" />
|
|
@@ -90,5 +92,8 @@ function isSelected(val: OptionValue) {
|
|
|
90
92
|
.SelectBtn {
|
|
91
93
|
--bgl-btn-height: 30px;
|
|
92
94
|
}
|
|
95
|
+
.SelectBtn-padding{
|
|
96
|
+
padding: var(--bgl-select-btn-padding, 0.75rem);
|
|
97
|
+
}
|
|
93
98
|
|
|
94
99
|
</style>
|
|
@@ -14,6 +14,8 @@ const props = defineProps<{
|
|
|
14
14
|
placeholder?: string
|
|
15
15
|
excludeCountries?: string[]
|
|
16
16
|
onlyCountries?: string[]
|
|
17
|
+
/** Preferred ISO-2 country when geo-IP is unavailable (e.g. "NL"). */
|
|
18
|
+
defaultCountry?: string
|
|
17
19
|
required?: boolean
|
|
18
20
|
disabled?: boolean
|
|
19
21
|
underlined?: boolean
|
|
@@ -100,9 +102,35 @@ function selectCountry(country: Country) {
|
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
function findCountry(iso2?: string | null) {
|
|
106
|
+
if (!iso2) return undefined
|
|
107
|
+
const code = iso2.toLowerCase()
|
|
108
|
+
return countries.value.find(c => c.iso2.toLowerCase() === code)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Locale / language fallback when geo-IP is blocked (CORS, rate limit, offline). */
|
|
112
|
+
function countryFromBrowser(): Country | undefined {
|
|
113
|
+
const candidates = [
|
|
114
|
+
props.defaultCountry,
|
|
115
|
+
typeof navigator !== 'undefined' ? navigator.language?.split('-')[1] : undefined,
|
|
116
|
+
typeof navigator !== 'undefined' ? navigator.languages?.[0]?.split('-')[1] : undefined,
|
|
117
|
+
]
|
|
118
|
+
for (const code of candidates) {
|
|
119
|
+
const match = findCountry(code)
|
|
120
|
+
if (match) return match
|
|
121
|
+
}
|
|
122
|
+
return undefined
|
|
123
|
+
}
|
|
124
|
+
|
|
103
125
|
async function getIp() {
|
|
104
126
|
const { country_code } = await ipapi()
|
|
105
|
-
|
|
127
|
+
const match = findCountry(country_code) ?? countryFromBrowser()
|
|
128
|
+
if (match) selectCountry(match)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function selectFallbackCountry() {
|
|
132
|
+
const match = countryFromBrowser()
|
|
133
|
+
if (match) selectCountry(match)
|
|
106
134
|
}
|
|
107
135
|
|
|
108
136
|
// Get the country code for use with libphonenumber-js
|
|
@@ -175,7 +203,11 @@ async function initializeCountry() {
|
|
|
175
203
|
if (phoneNumber.value) {
|
|
176
204
|
detectCountryFromNumber(phoneNumber.value)
|
|
177
205
|
} else {
|
|
178
|
-
|
|
206
|
+
try {
|
|
207
|
+
await getIp()
|
|
208
|
+
} catch {
|
|
209
|
+
selectFallbackCountry()
|
|
210
|
+
}
|
|
179
211
|
}
|
|
180
212
|
const formatted = parseAndFormatPhoneNumber(phoneNumber.value)
|
|
181
213
|
if (formatted !== phoneNumber.value) {
|
package/src/styles/layout.css
CHANGED
package/src/utils/ipapi.ts
CHANGED
|
@@ -1,24 +1,92 @@
|
|
|
1
1
|
import axios from 'axios'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export interface IpApiResult {
|
|
4
|
+
country_code: string
|
|
5
|
+
[key: string]: unknown
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const CACHE_KEY = 'ipapi'
|
|
9
|
+
let pendingRequest: Promise<IpApiResult> | null = null
|
|
10
|
+
|
|
11
|
+
function normalizeCountryCode(code: unknown): string | null {
|
|
12
|
+
if (typeof code !== 'string') return null
|
|
13
|
+
const normalized = code.trim().toUpperCase()
|
|
14
|
+
return /^[A-Z]{2}$/.test(normalized) ? normalized : null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function fromCache(): IpApiResult | null {
|
|
18
|
+
try {
|
|
19
|
+
const raw = sessionStorage.getItem(CACHE_KEY)
|
|
20
|
+
if (!raw) return null
|
|
21
|
+
const data = JSON.parse(raw) as IpApiResult
|
|
22
|
+
const country_code = normalizeCountryCode(data?.country_code)
|
|
23
|
+
if (!country_code) {
|
|
24
|
+
sessionStorage.removeItem(CACHE_KEY)
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
return { ...data, country_code }
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
sessionStorage.removeItem(CACHE_KEY)
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
}
|
|
4
34
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return JSON.parse(apiData)
|
|
35
|
+
function toCache(data: IpApiResult) {
|
|
36
|
+
try {
|
|
37
|
+
sessionStorage.setItem(CACHE_KEY, JSON.stringify(data))
|
|
9
38
|
}
|
|
39
|
+
catch {
|
|
40
|
+
/* ignore quota / private mode */
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Providers that support browser CORS and return a country ISO-2 code. */
|
|
45
|
+
const providers: Array<() => Promise<string | null>> = [
|
|
46
|
+
async () => {
|
|
47
|
+
const { data } = await axios.get('https://api.country.is/', { timeout: 4000 })
|
|
48
|
+
return normalizeCountryCode(data?.country)
|
|
49
|
+
},
|
|
50
|
+
async () => {
|
|
51
|
+
const { data } = await axios.get('https://ipaddress.to/api/lookup/my', { timeout: 4000 })
|
|
52
|
+
return normalizeCountryCode(data?.location?.country_code)
|
|
53
|
+
},
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
async function resolveCountryCode(): Promise<string> {
|
|
57
|
+
for (const provider of providers) {
|
|
58
|
+
try {
|
|
59
|
+
const code = await provider()
|
|
60
|
+
if (code) return code
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
/* try next provider */
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
throw new Error('Could not resolve country from IP')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the visitor's country from their IP.
|
|
71
|
+
* Result is cached in sessionStorage so multiple TelInputs share one lookup.
|
|
72
|
+
*/
|
|
73
|
+
export async function ipapi(): Promise<IpApiResult> {
|
|
74
|
+
const cached = fromCache()
|
|
75
|
+
if (cached) return cached
|
|
10
76
|
|
|
11
|
-
// Deduplicate concurrent calls — share a single in-flight request
|
|
12
77
|
if (!pendingRequest) {
|
|
13
|
-
pendingRequest =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
78
|
+
pendingRequest = resolveCountryCode()
|
|
79
|
+
.then((country_code) => {
|
|
80
|
+
const result = { country_code }
|
|
81
|
+
toCache(result)
|
|
82
|
+
pendingRequest = null
|
|
83
|
+
return result
|
|
84
|
+
})
|
|
85
|
+
.catch((err) => {
|
|
86
|
+
pendingRequest = null
|
|
87
|
+
throw err
|
|
88
|
+
})
|
|
22
89
|
}
|
|
90
|
+
|
|
23
91
|
return pendingRequest
|
|
24
92
|
}
|