@bagelink/vue 0.0.439 → 0.0.441

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.
Files changed (55) hide show
  1. package/dist/components/AccordionItem.vue.d.ts.map +1 -1
  2. package/dist/components/DataPreview.vue.d.ts +6 -0
  3. package/dist/components/DataPreview.vue.d.ts.map +1 -1
  4. package/dist/components/Flag.vue.d.ts +16 -0
  5. package/dist/components/Flag.vue.d.ts.map +1 -0
  6. package/dist/components/Modal.vue.d.ts.map +1 -1
  7. package/dist/components/TableSchema.vue.d.ts +6 -5
  8. package/dist/components/TableSchema.vue.d.ts.map +1 -1
  9. package/dist/components/form/inputs/FileUpload.vue.d.ts +164 -2
  10. package/dist/components/form/inputs/FileUpload.vue.d.ts.map +1 -1
  11. package/dist/components/form/inputs/TelInput.vue.d.ts +165 -0
  12. package/dist/components/form/inputs/TelInput.vue.d.ts.map +1 -0
  13. package/dist/components/form/inputs/TextInput.vue.d.ts +2 -5
  14. package/dist/components/form/inputs/TextInput.vue.d.ts.map +1 -1
  15. package/dist/components/form/inputs/index.d.ts +1 -0
  16. package/dist/components/form/inputs/index.d.ts.map +1 -1
  17. package/dist/components/index.d.ts +1 -0
  18. package/dist/components/index.d.ts.map +1 -1
  19. package/dist/components/layout/Tabs.vue.d.ts.map +1 -1
  20. package/dist/index.cjs +6502 -1540
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.mjs +6502 -1540
  24. package/dist/style.css +1877 -93
  25. package/dist/utils/BagelFormUtils.d.ts.map +1 -1
  26. package/dist/utils/allCountries.d.ts +11 -0
  27. package/dist/utils/allCountries.d.ts.map +1 -0
  28. package/package.json +3 -4
  29. package/src/components/AccordionItem.vue +8 -5
  30. package/src/components/DataPreview.vue +14 -4
  31. package/src/components/Flag.vue +1349 -0
  32. package/src/components/Modal.vue +26 -9
  33. package/src/components/ModalForm.vue +1 -1
  34. package/src/components/PageTitle.vue +8 -1
  35. package/src/components/TableSchema.vue +34 -2
  36. package/src/components/form/inputs/FileUpload.vue +28 -11
  37. package/src/components/form/inputs/SelectInput.vue +6 -0
  38. package/src/components/form/inputs/TelInput.vue +401 -0
  39. package/src/components/form/inputs/TextInput.vue +15 -53
  40. package/src/components/form/inputs/index.ts +1 -0
  41. package/src/components/index.ts +1 -0
  42. package/src/components/layout/Tabs.vue +1 -0
  43. package/src/index.ts +1 -0
  44. package/src/styles/appearance.css +610 -0
  45. package/src/styles/buttons.css +8 -0
  46. package/src/styles/layout.css +46 -1
  47. package/src/styles/mobilLayout.css +37 -0
  48. package/src/styles/modal.css +5 -2
  49. package/src/styles/text.css +39 -0
  50. package/src/utils/BagelFormUtils.ts +2 -0
  51. package/src/utils/allCountries.ts +337 -0
  52. package/src/components/whatsapp/form/MsgTemplate.vue +0 -224
  53. package/src/components/whatsapp/form/TextVariableExamples.vue +0 -79
  54. package/src/components/whatsapp/index.ts +0 -2
  55. package/src/components/whatsapp/interfaces.ts +0 -58
@@ -3,17 +3,30 @@
3
3
  class="bg-dark"
4
4
  :class="{ 'is-side': side, 'is-active': isVisible, 'bg-lignt': false }"
5
5
  @click="() => (dismissable ? closeModal() : '')"
6
- @keydown.esc="closeModal">
6
+ @keydown.esc="closeModal"
7
+ >
7
8
  <Card class="modal" @click.stop :style="{ ...maxWidth }">
8
- <header class="tool-bar">
9
+ <header v-if="slots['toolbar'] || title" class="tool-bar">
9
10
  <slot name="toolbar" />
10
11
  <Btn
11
12
  :style="{ float: side ? 'left' : 'right' }"
12
13
  flat
13
14
  icon="close"
14
- @click="closeModal" />
15
+ @click="closeModal"
16
+ />
15
17
  <Title class="modal-title" tag="h3" v-if="title" :label="title" />
16
18
  </header>
19
+
20
+ <div v-else class="sticky h-10 bg-white z-index-999 -mt-1 -me-075 h-40px pt-025 round">
21
+ <Btn
22
+ class="color-black"
23
+ :style="{ float: side ? 'left' : 'right' }"
24
+ icon="close"
25
+ thin
26
+ color="white"
27
+ @click="closeModal"
28
+ />
29
+ </div>
17
30
  <slot />
18
31
  <footer v-if="slots['footer'] || actions?.length" class="modal-footer mt-3">
19
32
  <Btn
@@ -21,7 +34,8 @@
21
34
  :key="i"
22
35
  @click="closeModal"
23
36
  color="gray"
24
- v-bind="action" />
37
+ v-bind="action"
38
+ />
25
39
  <slot name="footer" />
26
40
  </footer>
27
41
  </Card>
@@ -29,8 +43,12 @@
29
43
  </template>
30
44
 
31
45
  <script lang="ts" setup>
32
- import { onMounted, onUnmounted, watch, useSlots } from 'vue';
33
- import { type BtnOptions, Btn, useEscape, Title, Card } from '@bagelink/vue';
46
+ import {
47
+ onMounted, onUnmounted, watch, useSlots,
48
+ } from 'vue';
49
+ import {
50
+ type BtnOptions, Btn, useEscape, Title, Card,
51
+ } from '@bagelink/vue';
34
52
  import '../styles/modal.css';
35
53
 
36
54
  const props = defineProps<{
@@ -53,7 +71,7 @@ watch(
53
71
  if (val) openModal();
54
72
  else closeModal();
55
73
  },
56
- { immediate: true }
74
+ { immediate: true },
57
75
  );
58
76
 
59
77
  const maxWidth = $computed(() => {
@@ -72,8 +90,7 @@ const closeModal = () => {
72
90
 
73
91
  defineExpose({ closeModal });
74
92
 
75
- const escapeKeyClose = (e: KeyboardEvent) =>
76
- props?.dismissable && useEscape(e, () => closeModal());
93
+ const escapeKeyClose = (e: KeyboardEvent) => props?.dismissable && useEscape(e, () => closeModal());
77
94
 
78
95
  function openModal() {
79
96
  setTimeout(() => (isVisible = true), 1);
@@ -6,7 +6,7 @@
6
6
  :width
7
7
  :visible="visible"
8
8
  :dismissable
9
- :title="title">
9
+ :title >
10
10
  <BagelForm
11
11
  @submit="runSubmit"
12
12
  ref="form"
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="page-top">
3
- <h1 class="top-title">
3
+ <h1 class="top-title m-0">
4
4
  <slot /> {{ value }}
5
5
  </h1>
6
6
  </div>
@@ -19,6 +19,7 @@ defineProps({
19
19
  .top-title {
20
20
  font-weight: 600;
21
21
  font-size: 20px;
22
+ line-height: 1;
22
23
  }
23
24
 
24
25
  .page-top {
@@ -26,4 +27,10 @@ defineProps({
26
27
  align-items: center;
27
28
  gap: 1rem;
28
29
  }
30
+
31
+ @media screen and (max-width: 910px) {
32
+ .top-title {
33
+ font-size: 16px;
34
+ }
35
+ }
29
36
  </style>
@@ -71,11 +71,32 @@ const slots = useSlots();
71
71
  const loading = $ref(true);
72
72
 
73
73
  const props = defineProps<{
74
+ selecteItems?: string[];
74
75
  data: any[];
75
76
  schema?: BglFormSchemaT | (() => BglFormSchemaT);
76
77
  showFields?: string[];
77
78
  }>();
78
79
 
80
+ const emit = defineEmits(['update:selecteItems', 'orderBy', 'select']);
81
+
82
+ let selecteItems = $ref<string[]>(props.selecteItems || []);
83
+ let selected = $computed({
84
+ get: () => selecteItems,
85
+ set: (value: string[]) => {
86
+ selecteItems = value;
87
+ emit('update:selecteItems', value);
88
+ },
89
+ });
90
+
91
+ function selectAll(event: PointerEvent) {
92
+ const value = (event.target as HTMLInputElement).checked;
93
+ if (!value) {
94
+ selected = [];
95
+ return;
96
+ }
97
+ selected.push(...computedData.map((d) => d.id));
98
+ }
99
+
79
100
  const computedSchema = $computed(() => {
80
101
  if (typeof props.schema === 'function') {
81
102
  return props.schema();
@@ -97,8 +118,6 @@ const computedSchema = $computed(() => {
97
118
  : schema;
98
119
  });
99
120
 
100
- const emit = defineEmits(['select', 'orderBy']);
101
-
102
121
  const selectElement = (item: Record<string, any>) => emit('select', item);
103
122
 
104
123
  let sortDirection = $ref('ASC');
@@ -131,6 +150,12 @@ const computedData = $computed(() => {
131
150
  return dta.sort((a, b) => {
132
151
  const aValue = a[sortField];
133
152
  const bValue = b[sortField];
153
+ const numAValue = parseInt(aValue);
154
+ const numBValue = parseInt(bValue);
155
+ if (!isNaN(numAValue) && !isNaN(numBValue)) {
156
+ if (sortDirection === 'ASC') return numAValue - numBValue;
157
+ return numBValue - numAValue;
158
+ }
134
159
  if (typeof aValue === 'string') {
135
160
  if (sortDirection === 'ASC') return aValue.localeCompare(bValue);
136
161
  return bValue.localeCompare(aValue);
@@ -197,6 +222,9 @@ th {
197
222
  height: 50px;
198
223
  vertical-align: bottom;
199
224
  }
225
+ .row.first-row input[type=checkbox]{
226
+ margin-bottom: 0.7rem;
227
+ }
200
228
 
201
229
  .row.first-row::after {
202
230
  content: '';
@@ -267,6 +295,10 @@ th {
267
295
  .row-item:hover {
268
296
  background: var(--bgl-gray-light);
269
297
  }
298
+ .row-item input[type=checkbox]{
299
+ margin-top: 0.45rem !important;
300
+ accent-color: var(--bgl-primary);
301
+ }
270
302
 
271
303
  .infinite-wrapper {
272
304
  overflow-y: auto;
@@ -14,12 +14,16 @@
14
14
  dragover: isDragOver,
15
15
  }"
16
16
  >
17
+ <slot name="files" :files="storageFiles" :fileQueue>
17
18
  <template v-for="file in storageFiles" :key="file.id">
18
- <div class="imagePreviewWrap" v-if="!multiple">
19
- <img class="preview" :src="file.url" alt="" />
19
+ <div class="single-image" v-if="!multiple">
20
+ <img class="single preview" :src="file.url" alt="" />
21
+ <!-- <p class="no-margin">
22
+ {{ file.name }}
23
+ </p> -->
20
24
  </div>
21
- <div class="previewName">
22
- <img v-if="multiple" class="preview" :src="file.url" alt="" />
25
+ <div class="multi-image-item previewName" v-if="multiple">
26
+ <img class="preview" :src="file.url" alt="" />
23
27
  <p class="no-margin">
24
28
  {{ file.name }}
25
29
  </p>
@@ -60,6 +64,10 @@
60
64
  </div>
61
65
  </div>
62
66
  </template>
67
+ </slot>
68
+ <slot v-if="(!storageFiles.length && !fileQueue.length) || multiple" name="placeholder">
69
+ <p>Drop files here or click to upload</p>
70
+ </slot>
63
71
  </div>
64
72
  </div>
65
73
  </template>
@@ -224,8 +232,7 @@ const drop = (e: DragEvent) => {
224
232
  cursor: pointer;
225
233
  transition: var(--bgl-transition);
226
234
  position: relative;
227
- min-height: 132px;
228
- max-height: 500px;
235
+ height: calc(var(--width) / 1.5);
229
236
  font-size: var(--input-font-size);
230
237
  overflow-y: auto;
231
238
  background: var(--input-bg);
@@ -256,11 +263,24 @@ const drop = (e: DragEvent) => {
256
263
  }
257
264
 
258
265
  img.preview {
259
- width: var(--width);
266
+ /* max-height: calc(var(--width) / 2); */
260
267
  /* max-width: calc(var(--width) * 2); */
268
+ width: 40px;
269
+ height: 40px;
261
270
  border-radius: var(--input-border-radius);
262
271
  object-fit: cover;
263
- background: var(--bgl-white);
272
+ background: var(--bgl-gray-light);
273
+ box-shadow: 0 0 10px #00000012;
274
+
275
+ }
276
+
277
+ img.preview.single{
278
+ max-width: var(--width);
279
+ width: auto;
280
+ object-fit: contain;
281
+ height: auto;
282
+ max-height: calc(var(--width) / 2);
283
+ margin: 1rem;
264
284
  }
265
285
 
266
286
  .fileUploadWrap.dragover,
@@ -276,9 +296,6 @@ img.preview {
276
296
  color: var(--bgl-gray);
277
297
  }
278
298
 
279
- .bagel-input .fileUploadWrap.fileDropZone:after {
280
- content: 'Drop files here or click to upload';
281
- }
282
299
 
283
300
  .pie {
284
301
  width: 30px;
@@ -283,6 +283,12 @@ watch(
283
283
  width: 100%;
284
284
  font-family: inherit;
285
285
  font-size: var(--input-font-size);
286
+
287
+ }
288
+ .selectinput-btn p{
289
+ white-space: nowrap;
290
+ overflow: hidden;
291
+ text-overflow: ellipsis;
286
292
  }
287
293
 
288
294
  .selectinput-btn:disabled {
@@ -0,0 +1,401 @@
1
+ <template>
2
+ <div class="bagel-input" :class="{ disabled: disabled }">
3
+ <label>
4
+ {{ label }}
5
+ <div
6
+ dir="ltr"
7
+ class="flex gap-05 tel-input"
8
+ tabindex="-1"
9
+ aria-label="Country Code Selector"
10
+ aria-haspopup="listbox"
11
+ :aria-expanded="open"
12
+ @keydown.esc="reset"
13
+ @keydown.tab="reset"
14
+ >
15
+ <Dropdown :placement="'bottom'" @hide="open = false">
16
+ <span class="flex gap-05" @click="open = true">
17
+ <Icon :icon="open ? 'collapse_all' : 'expand_all'" />
18
+ <Flag
19
+ v-if="dropdownOptions.showFlags && activeCountryCode"
20
+ :country-code="activeCountryCode?.toLowerCase()"
21
+ />
22
+ </span>
23
+ <template #popper="{ hide }">
24
+ <div class="p-075 tel-countryp-dropdown">
25
+ <TextInput
26
+ v-if="searchable"
27
+ aria-label="Search by country name or country code"
28
+ placeholder="Search"
29
+ icon="search"
30
+ v-model="searchQuery"
31
+ />
32
+ <ul
33
+ ref="listEl"
34
+ class="overflow-y p-0"
35
+ :style="{ 'max-height': '400px' }"
36
+ :class="dropdownOpenDirection"
37
+ role="listbox"
38
+ >
39
+ <li
40
+ v-for="(pb, index) in sortedCountries"
41
+ role="option"
42
+ class="flex gap-075"
43
+ :key="pb.iso2 + isPreferred(pb)"
44
+ tabindex="-1"
45
+ @click="
46
+ () => {
47
+ chooseCountry(pb.iso2);
48
+ hide();
49
+ }
50
+ "
51
+ @mousemove="selectedIndex = index"
52
+ :aria-selected="activeCountryCode === pb.iso2 && !isPreferred(pb)"
53
+ >
54
+ <Flag v-if="dropdownOptions.showFlags" :country-code="pb.iso2.toLowerCase()" />
55
+ <p class="tel-country">{{ pb.name }}</p>
56
+ <span v-if="dropdownOptions.showDialCodeInList"> +{{ pb.dialCode }} </span>
57
+ </li>
58
+ </ul>
59
+ </div>
60
+ </template>
61
+ </Dropdown>
62
+ <input
63
+ :required="required"
64
+ :placeholder="placeholder"
65
+ :disabled="disabled"
66
+ v-model="phone"
67
+ type="tel"
68
+ ref="refInput"
69
+ :autocomplete="autocomplete"
70
+ :class="['vti__input']"
71
+ :id="id"
72
+ :maxlength="inputOptions.maxlength"
73
+ :name="inputOptions.name"
74
+ :readonly="inputOptions.readonly"
75
+ :tabindex="inputOptions.tabindex"
76
+ :aria-describedby="inputOptions['aria-describedby']"
77
+ @blur="onBlur"
78
+ @focus="onFocus"
79
+ @keyup.enter="onEnter"
80
+ @keyup.space="onSpace"
81
+ >
82
+ </div>
83
+ </label>
84
+ </div>
85
+ </template>
86
+
87
+ <script lang="ts" setup>
88
+ import {
89
+ Icon, Dropdown, TextInput, Flag,
90
+ allCountries, type Country,
91
+ } from '@bagelink/vue';
92
+ import { onMounted, watch } from 'vue';
93
+ import { parsePhoneNumberFromString, type CountryCode, type PhoneNumber } from 'libphonenumber-js';
94
+ import axios from 'axios';
95
+
96
+ async function getIp() {
97
+ const apiData = sessionStorage?.getItem('ipapi');
98
+ if (apiData) return JSON.parse(apiData);
99
+ const { data } = await axios.get('https://ipapi.co/json/');
100
+ sessionStorage.setItem('ipapi', JSON.stringify(data));
101
+ return data;
102
+ }
103
+
104
+ type DropdownOptions = {
105
+ disabled?: boolean;
106
+ showFlags?: boolean;
107
+ showDialCodeInSelection?: boolean;
108
+ showDialCodeInList?: boolean;
109
+ };
110
+
111
+ type InputOptions = {
112
+ autofocus?: boolean;
113
+ 'aria-describedby'?: string;
114
+ maxlength?: number;
115
+ name?: string;
116
+ placeholder?: string;
117
+ readonly?: boolean;
118
+ tabindex?: number;
119
+ showDialCode?: boolean;
120
+ };
121
+
122
+ type Props = {
123
+ label?: string;
124
+ id?: string;
125
+ autocomplete?: 'on' | 'off';
126
+ placeholder?: string;
127
+ required?: boolean;
128
+ modelValue: string;
129
+ allCountries?: Country[];
130
+ autoFormat?: boolean;
131
+ customValidate?: boolean | RegExp;
132
+ defaultCountry?: string | number;
133
+ disabled?: boolean;
134
+ searchable?: boolean;
135
+ autoDefaultCountry?: boolean;
136
+ dropdownOptions?: DropdownOptions;
137
+ excludeCountries?: string[];
138
+ inputOptions?: InputOptions;
139
+ invalidMsg?: string;
140
+ mode?: 'auto' | 'international' | 'national';
141
+ onlyCountries?: string[];
142
+ preferredCountries?: string[];
143
+ };
144
+
145
+ const props = withDefaults(defineProps<Props>(), {
146
+ modelValue: '',
147
+ autocomplete: 'on',
148
+ allCountries: () => allCountries,
149
+ autoFormat: true,
150
+ customValidate: false,
151
+ defaultCountry: '',
152
+ placeholder: 'Enter a phone number',
153
+ searchable: true,
154
+ dropdownOptions: () => ({
155
+ disabled: false,
156
+ showFlags: true,
157
+ showDialCodeInSelection: true,
158
+ showDialCodeInList: true,
159
+ searchable: true,
160
+ }),
161
+ disabled: false,
162
+ autoDefaultCountry: true,
163
+ excludeCountries: () => [],
164
+ inputOptions: () => ({
165
+ showDialCode: true,
166
+ autofocus: false,
167
+ 'aria-describedby': '',
168
+ id: '',
169
+ maxlength: 25,
170
+ name: '',
171
+ readonly: false,
172
+ tabindex: 0,
173
+ }),
174
+ required: false,
175
+ invalidMsg: 'Invalid phone number',
176
+ mode: 'auto',
177
+ onlyCountries: () => [],
178
+ preferredCountries: () => [],
179
+ });
180
+
181
+ let activeCountryCode = $ref<CountryCode>();
182
+ let open = $ref(false);
183
+ let selectedIndex = $ref<number>();
184
+ const dropdownOpenDirection = $ref('below');
185
+ const searchQuery = $ref('');
186
+
187
+ const refInput = $ref<HTMLInputElement | null>(null);
188
+
189
+ const emit = defineEmits([
190
+ 'update:modelValue',
191
+ 'change',
192
+ 'input',
193
+ 'blur',
194
+ 'focus',
195
+ 'country-changed',
196
+ 'enter',
197
+ 'space',
198
+ ]);
199
+
200
+ function formatPhone(val: string) {
201
+ let result: PhoneNumber | undefined;
202
+ if (val?.[0] === '+') result = parsePhoneNumberFromString(val);
203
+ else result = parsePhoneNumberFromString(val, activeCountryCode);
204
+ const valid = result?.isValid?.();
205
+ refInput?.setCustomValidity?.(valid ? '' : props.invalidMsg);
206
+ const formatted = valid ? result?.format?.(parsedMode.toUpperCase() as any) : val;
207
+ return formatted;
208
+ }
209
+
210
+ const cleanStr = (str?: string) => {
211
+ const phoneNumber = (str?.match(/[()\-+0-9\s]*/g) || []).join('');
212
+ return phoneNumber.trim();
213
+ };
214
+
215
+ let phone = $computed<string>({
216
+ get: () => props.modelValue,
217
+ set: (value) => {
218
+ const clean = cleanStr(value);
219
+ const formatted = formatPhone(clean) || '';
220
+ emit('update:modelValue', formatted);
221
+ },
222
+ });
223
+
224
+ // Computed: activeCountry
225
+ const activeCountry = $computed(() => props.allCountries.find((country) => country.iso2 === activeCountryCode?.toUpperCase()));
226
+
227
+ const isPreferred = (country?: Country) => props.preferredCountries.includes(country?.iso2 as string);
228
+
229
+ // Computed: parsedMode
230
+ const parsedMode = $computed(() => {
231
+ if (props.mode === 'auto') {
232
+ if (!phone || phone[0] !== '+') return 'national';
233
+ return 'international';
234
+ }
235
+ return props.mode || 'international';
236
+ });
237
+
238
+ const filteredCountries = $computed(() => {
239
+ if (props.onlyCountries.length) {
240
+ return props.allCountries.filter(({ iso2 }) => props.onlyCountries.some((c) => c.toUpperCase() === iso2));
241
+ }
242
+ if (props.excludeCountries.length) {
243
+ return props.allCountries.filter(
244
+ ({ iso2 }) => !props.excludeCountries.includes(iso2.toUpperCase()) &&
245
+ !props.excludeCountries.includes(iso2.toLowerCase()),
246
+ );
247
+ }
248
+ return props.allCountries;
249
+ });
250
+
251
+ const sortedCountries = $computed(() => {
252
+ const preferredCountries = getCountries(props.preferredCountries);
253
+
254
+ const countriesList = [...preferredCountries, ...filteredCountries];
255
+ const cleanInput = searchQuery.replace(/[~`!@#$%^&*()+={}\[\];:'"<>.,\/\\\?-_]/g, '');
256
+ return countriesList
257
+ .filter(
258
+ (c) => new RegExp(cleanInput, 'i').test(c?.name || '') ||
259
+ new RegExp(cleanInput, 'i').test(c?.iso2 || '') ||
260
+ new RegExp(cleanInput, 'i').test(c?.dialCode || ''),
261
+ )
262
+ .filter(Boolean);
263
+ });
264
+
265
+ // Watchers
266
+ watch(
267
+ () => activeCountry,
268
+ (value, oldValue) => {
269
+ if (!value && oldValue?.iso2) {
270
+ activeCountryCode = oldValue.iso2;
271
+ return;
272
+ }
273
+ if (value?.iso2) emit('country-changed', value);
274
+ },
275
+ );
276
+
277
+ const initializeCountry = async () => {
278
+ // if (phone?.[0] === '+') return;
279
+ // 2. Use default country if passed from parent
280
+ if (props.defaultCountry) {
281
+ if (typeof props.defaultCountry === 'string') {
282
+ chooseCountry(props.defaultCountry);
283
+ return;
284
+ }
285
+ if (typeof props.defaultCountry === 'number') {
286
+ const country = findCountryByDialCode(props.defaultCountry);
287
+ if (country) {
288
+ chooseCountry(country.iso2);
289
+ return;
290
+ }
291
+ }
292
+ }
293
+
294
+ const fallbackCountry = sortedCountries[0];
295
+
296
+ if (props.autoDefaultCountry) {
297
+ try {
298
+ const res = (await getIp()).country;
299
+ chooseCountry(res || activeCountryCode);
300
+ } catch (error) {
301
+ console.warn(error);
302
+ chooseCountry(fallbackCountry?.iso2);
303
+ }
304
+ } else {
305
+ // 4. Use the first country from preferred list (if available) or all countries list
306
+ chooseCountry(fallbackCountry?.iso2);
307
+ }
308
+ };
309
+
310
+ onMounted(initializeCountry);
311
+
312
+ const findCountry = (iso: string) => filteredCountries.find((country) => country.iso2 === iso?.toUpperCase());
313
+
314
+ const getCountries = (list: string[]): Country[] => {
315
+ const countryList: Country[] = [];
316
+ list.forEach((countryCode) => {
317
+ const country = findCountry(countryCode);
318
+ if (country) countryList.push(country);
319
+ });
320
+ return countryList;
321
+ };
322
+
323
+ const findCountryByDialCode = (dialCode: number) => filteredCountries.find((country) => Number(country.dialCode) === dialCode);
324
+
325
+ const chooseCountry = (country?: string) => {
326
+ if (!country) return;
327
+ const parsedCountry = findCountry(country);
328
+ if (!parsedCountry) return;
329
+ activeCountryCode = parsedCountry.iso2;
330
+
331
+ if (props.inputOptions?.showDialCode && parsedCountry) {
332
+ phone = `+${parsedCountry.dialCode}`;
333
+ activeCountryCode = parsedCountry.iso2 || '';
334
+ return;
335
+ }
336
+
337
+ activeCountryCode = parsedCountry.iso2 || '';
338
+ // emitInput(phone);
339
+ };
340
+
341
+ const onBlur = () => emit('blur');
342
+
343
+ const onFocus = () => emit('focus');
344
+
345
+ const onEnter = () => emit('enter');
346
+
347
+ const onSpace = () => emit('space');
348
+
349
+ // const focus = () => refInput?.focus();
350
+
351
+ // Method: reset
352
+ const reset = () => {
353
+ selectedIndex = sortedCountries.findIndex((c) => c.iso2 === activeCountryCode);
354
+ open = false;
355
+ };
356
+ </script>
357
+
358
+ <style scoped>
359
+ .tel-input {
360
+ direction: ltr;
361
+ text-align: left;
362
+ background: var(--input-bg);
363
+ border: none;
364
+ padding-inline-start: 0.7rem;
365
+ border-radius: var(--input-border-radius);
366
+ color: var(--input-color);
367
+ min-width: calc(var(--input-height) * 3);
368
+ width: 100%;
369
+ }
370
+
371
+ .tel-input:focus-within {
372
+ outline: none;
373
+ box-shadow: inset 0 0 10px #00000012;
374
+ }
375
+ .tel-input input {
376
+ background: transparent;
377
+ }
378
+ .tel-input input::placeholder {
379
+ color: transparent;
380
+ }
381
+ .tel-input input:focus-visible {
382
+ box-shadow: none;
383
+ }
384
+ .input_country-code {
385
+ font-size: var(--input-font-size);
386
+ color: var(--input-color);
387
+ }
388
+ .tel-country {
389
+ font-size: var(--input-font-size);
390
+ max-width: 200px;
391
+ white-space: nowrap;
392
+ text-overflow: ellipsis;
393
+ overflow: hidden;
394
+ margin-top: 0;
395
+ margin-bottom: 0;
396
+ }
397
+ .tel-countryp-dropdown {
398
+ direction: ltr;
399
+ text-align: left;
400
+ }
401
+ </style>