@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.
@@ -1,2 +1,10 @@
1
- export declare function ipapi(): Promise<any>;
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":"AAIA,wBAAsB,KAAK,iBAmB1B"}
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/vue",
3
3
  "type": "module",
4
- "version": "1.15.162",
4
+ "version": "1.15.166",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -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?.params.itemID" />
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 px-075 radius-2"
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
- selectCountry(countries.value.find(c => c.iso2 === country_code) ?? countries.value[0])
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
- await getIp()
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) {
@@ -4115,6 +4115,10 @@
4115
4115
  flex-wrap: wrap;
4116
4116
  }
4117
4117
 
4118
+ .flex-wrap-nowrap {
4119
+ flex-wrap: nowrap;
4120
+ }
4121
+
4118
4122
  .flex-reverse {
4119
4123
  flex-direction: row-reverse;
4120
4124
  }
@@ -4146,6 +4146,10 @@
4146
4146
  flex-wrap: wrap;
4147
4147
  }
4148
4148
 
4149
+ .m_flex-wrap-nowrap {
4150
+ flex-wrap: nowrap;
4151
+ }
4152
+
4149
4153
  .m_flex-reverse {
4150
4154
  flex-direction: row-reverse;
4151
4155
  }
@@ -1,24 +1,92 @@
1
1
  import axios from 'axios'
2
2
 
3
- let pendingRequest: Promise<any> | null = null
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
- export async function ipapi() {
6
- const apiData = sessionStorage.getItem('ipapi')
7
- if (apiData) {
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 = axios.get('https://ipapi.co/json/').then(res => {
14
- const data = JSON.stringify(res.data)
15
- sessionStorage.setItem('ipapi', data)
16
- pendingRequest = null
17
- return res.data
18
- }).catch(err => {
19
- pendingRequest = null
20
- throw err
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
  }