@bagelink/vue 0.0.449 → 0.0.455

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 (35) hide show
  1. package/dist/components/Flag.vue.d.ts +9 -5
  2. package/dist/components/Flag.vue.d.ts.map +1 -1
  3. package/dist/components/MapEmbed.vue.d.ts +20 -0
  4. package/dist/components/MapEmbed.vue.d.ts.map +1 -0
  5. package/dist/components/TableSchema.vue.d.ts +20 -8
  6. package/dist/components/TableSchema.vue.d.ts.map +1 -1
  7. package/dist/components/form/BglField.vue.d.ts.map +1 -1
  8. package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
  9. package/dist/components/form/inputs/TelInput.vue.d.ts +203 -127
  10. package/dist/components/form/inputs/TelInput.vue.d.ts.map +1 -1
  11. package/dist/components/form/inputs/TextInput.vue.d.ts +7 -2
  12. package/dist/components/form/inputs/TextInput.vue.d.ts.map +1 -1
  13. package/dist/components/index.d.ts +1 -0
  14. package/dist/components/index.d.ts.map +1 -1
  15. package/dist/index.cjs +10131 -465
  16. package/dist/index.mjs +10131 -465
  17. package/dist/plugins/modal.d.ts.map +1 -1
  18. package/dist/style.css +801 -1122
  19. package/dist/types/BagelForm.d.ts +1 -0
  20. package/dist/types/BagelForm.d.ts.map +1 -1
  21. package/package.json +4 -2
  22. package/src/components/Flag.vue +45 -1341
  23. package/src/components/MapEmbed.vue +67 -0
  24. package/src/components/form/BglField.vue +1 -0
  25. package/src/components/form/inputs/RichText.vue +2 -2
  26. package/src/components/form/inputs/SelectInput.vue +7 -8
  27. package/src/components/form/inputs/TelInput.vue +164 -108
  28. package/src/components/form/inputs/TextInput.vue +8 -9
  29. package/src/components/index.ts +1 -0
  30. package/src/plugins/modal.ts +13 -12
  31. package/src/styles/appearance.css +54 -19
  32. package/src/styles/layout.css +5 -0
  33. package/src/styles/mobilLayout.css +6 -0
  34. package/src/styles/text.css +32 -3
  35. package/src/types/BagelForm.ts +1 -0
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <div class="map" id="map" :style="{height: `${height || 400}px`}"/>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ import L from 'leaflet';
7
+ import type { LatLngExpression, Map, Marker } from 'leaflet';
8
+ import 'leaflet/dist/leaflet.css';
9
+ import { onMounted, watch } from 'vue';
10
+
11
+ type MapProps = {
12
+ center?: LatLngExpression;
13
+ zoom?: number;
14
+ height?: number;
15
+ address?: string;
16
+ };
17
+
18
+ const props = defineProps<MapProps>();
19
+
20
+ let map = $ref<Map>();
21
+ const markers = $ref<Marker[]>([]);
22
+
23
+ function initializeMap() {
24
+ map = L.map('map', {
25
+ center: props.center || [31.7683, 35.2137],
26
+ zoom: props.zoom || 13,
27
+ });
28
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18 }).addTo(map);
29
+ }
30
+
31
+ onMounted(initializeMap);
32
+
33
+ const markerSVG = '<svg height="32" width="32" xmlns="http://www.w3.org/2000/svg"><g><path d="m12 0c-4.4183 0-8 3.5817-8 8 0 1.421.3816 2.75 1.0312 3.906.1079.192.221.381.3438.563l6.625 11.531 6.625-11.531c.102-.151.19-.311.281-.469l.063-.094c.649-1.156 1.031-2.485 1.031-3.906 0-4.4183-3.582-8-8-8zm0 4c2.209 0 4 1.7909 4 4 0 2.209-1.791 4-4 4-2.2091 0-4-1.791-4-4 0-2.2091 1.7909-4 4-4z" fill="#e74c3c"/><path d="m12 3c-2.7614 0-5 2.2386-5 5 0 2.761 2.2386 5 5 5 2.761 0 5-2.239 5-5 0-2.7614-2.239-5-5-5zm0 2c1.657 0 3 1.3431 3 3s-1.343 3-3 3-3-1.3431-3-3 1.343-3 3-3z" fill="#c0392b"/></g></svg>';
34
+
35
+ function addMarker(latlng: LatLngExpression) {
36
+ const customIcon = L.icon({
37
+ iconUrl: `data:image/svg+xml;utf8,${encodeURIComponent(markerSVG)}`,
38
+ iconSize: [32, 32],
39
+ });
40
+ const marker = L.marker(latlng, { icon: customIcon }).addTo(map);
41
+ markers.push(marker);
42
+ }
43
+
44
+ async function geocodeAddress(address:string) {
45
+ const geocodeUrl = `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(address)}`;
46
+ const res = await fetch(geocodeUrl);
47
+ const data = await res.json() || [];
48
+ data.forEach((element) => addMarker([element.lat, element.lon]));
49
+ }
50
+
51
+ watch(() => props.address, (address) => {
52
+ if (address) geocodeAddress(address).catch(console.error);
53
+ }, { immediate: true });
54
+ </script>
55
+
56
+ <style>
57
+ #map {
58
+ height: 100%;
59
+ }
60
+ .map {
61
+ filter: contrast(0.6) brightness(1.25) hue-rotate(12deg);
62
+ border-radius: var(--input-border-radius);
63
+ }
64
+ .leaflet-control-attribution{
65
+ display: none;
66
+ }
67
+ </style>
@@ -10,6 +10,7 @@
10
10
  :placeholder="field.placeholder || field.label"
11
11
  v-model="fieldData"
12
12
  :defaultValue="field.defaultValue"
13
+ :disabled="field.disabled"
13
14
  :options="
14
15
  bindAttrs({ options: field.options }, fieldData, modelValue).options
15
16
  "
@@ -111,7 +111,7 @@ const config: {
111
111
  txtField('src', 'YouTube URL'),
112
112
  {
113
113
  $el: BglVideo,
114
- attrs: { src: (_, { src }: { src: string }) => src || '' },
114
+ attrs: { src: (_: any, { src }: { src: string }) => src || '' },
115
115
  },
116
116
  ],
117
117
  onSubmit: (videoObj) => editor?.commands.setYoutubeVideo(videoObj),
@@ -198,7 +198,7 @@ const config: {
198
198
  {
199
199
  name: 'Table',
200
200
  command: () => editor
201
- .chain()
201
+ ?.chain()
202
202
  .focus()
203
203
  .insertTable({ rows: 3, cols: 3, withHeaderRow: true })
204
204
  .run(),
@@ -75,7 +75,9 @@
75
75
  import { watch } from 'vue';
76
76
  import { Dropdown } from 'floating-vue';
77
77
  import 'floating-vue/style.css';
78
- import { TextInput, Card, Icon, type MaterialIcons } from '@bagelink/vue';
78
+ import {
79
+ TextInput, Card, Icon, type MaterialIcons,
80
+ } from '@bagelink/vue';
79
81
 
80
82
  type Option =
81
83
  | string
@@ -150,11 +152,9 @@ const getValue = (option?: Option) => {
150
152
  return option.value;
151
153
  };
152
154
 
153
- const isSelected = (option: Option) =>
154
- !!selectedItems.find((item) => getValue(option) === getValue(item));
155
+ const isSelected = (option: Option) => !!selectedItems.find((item) => getValue(option) === getValue(item));
155
156
 
156
- const filteredOptions = $computed(() =>
157
- props.options.filter((option) => {
157
+ const filteredOptions = $computed(() => props.options.filter((option) => {
158
158
  const searchTerm = search
159
159
  .split(/\s+/)
160
160
  .filter(Boolean)
@@ -164,8 +164,7 @@ const filteredOptions = $computed(() =>
164
164
  (searchTerm.every((s) => getLabel(option).match(s)) ||
165
165
  searchTerm.length === 0)
166
166
  );
167
- }),
168
- );
167
+ }));
169
168
 
170
169
  const select = (option: Option) => {
171
170
  const existingIndex = selectedItems.findIndex(
@@ -205,7 +204,7 @@ watch(
205
204
  (newVal: Option | Option[]) => {
206
205
  if (!props.multiselect) {
207
206
  const newOption =
208
- props.options.find((o) => getValue(o) === newVal) || newVal;
207
+ props.options?.find((o) => getValue(o) === newVal) || newVal;
209
208
  if (newOption && !isSelected(newOption)) selectedItems = [newOption];
210
209
  } else {
211
210
  const isSame = compareArrays([newVal].flat(), selectedItems);
@@ -1,5 +1,8 @@
1
1
  <template>
2
- <div class="bagel-input" :class="{ disabled: disabled }">
2
+ <div
3
+ class="bagel-input"
4
+ :class="{ disabled: disabled }"
5
+ >
3
6
  <label>
4
7
  {{ label }}
5
8
  <div
@@ -12,12 +15,20 @@
12
15
  @keydown.esc="reset"
13
16
  @keydown.tab="reset"
14
17
  >
15
- <Dropdown :placement="'bottom'" @hide="open = false">
16
- <span class="flex gap-05" @click="open = true">
18
+ <Dropdown
19
+ :placement="'bottom'"
20
+ @hide="open = false"
21
+ v-if="!computedDropDownOptions.hide"
22
+ :disabled="computedDropDownOptions.disabled"
23
+ >
24
+ <span
25
+ class="flex gap-05"
26
+ @click="open = true"
27
+ >
17
28
  <Icon :icon="open ? 'collapse_all' : 'expand_all'" />
18
29
  <Flag
19
- v-if="dropdownOptions.showFlags && activeCountryCode"
20
- :country-code="activeCountryCode?.toLowerCase()"
30
+ v-if="computedDropDownOptions.showFlags && activeCountryCode"
31
+ :country="activeCountryCode"
21
32
  />
22
33
  </span>
23
34
  <template #popper="{ hide }">
@@ -29,6 +40,7 @@
29
40
  icon="search"
30
41
  v-model="searchQuery"
31
42
  />
43
+
32
44
  <ul
33
45
  ref="listEl"
34
46
  class="overflow-y p-0"
@@ -43,25 +55,30 @@
43
55
  :key="pb.iso2 + isPreferred(pb)"
44
56
  tabindex="-1"
45
57
  @click="
46
- () => {
47
- chooseCountry(pb.iso2);
48
- hide();
49
- }
58
+ chooseCountry(pb.iso2);
59
+ hide();
50
60
  "
51
61
  @mousemove="selectedIndex = index"
52
- :aria-selected="activeCountryCode === pb.iso2 && !isPreferred(pb)"
62
+ :aria-selected="activeCountryCode === pb.iso2 && !isPreferred(pb)
63
+ "
53
64
  >
54
- <Flag v-if="dropdownOptions.showFlags" :country-code="pb.iso2.toLowerCase()" />
65
+ <Flag
66
+ v-if="computedDropDownOptions.showFlags"
67
+ :country="pb.iso2"
68
+ />
55
69
  <p class="tel-country">{{ pb.name }}</p>
56
- <span v-if="dropdownOptions.showDialCodeInList"> +{{ pb.dialCode }} </span>
70
+ <span v-if="computedDropDownOptions.showDialCodeInList">
71
+ +{{ pb.dialCode }}
72
+ </span>
57
73
  </li>
58
74
  </ul>
59
75
  </div>
60
76
  </template>
61
77
  </Dropdown>
78
+
62
79
  <input
63
80
  :required="required"
64
- :placeholder="placeholder"
81
+ :placeholder="computedInputOptions.placeholder"
65
82
  :disabled="disabled"
66
83
  v-model="phone"
67
84
  type="tel"
@@ -69,16 +86,20 @@
69
86
  :autocomplete="autocomplete"
70
87
  :class="['vti__input']"
71
88
  :id="id"
72
- :maxlength="inputOptions.maxlength"
73
- :name="inputOptions.name"
74
- :readonly="inputOptions.readonly"
75
- :tabindex="inputOptions.tabindex"
76
- :aria-describedby="inputOptions['aria-describedby']"
89
+ :pattern="computedInputOptions.pattern"
90
+ :minlength="computedInputOptions.minlength"
91
+ :maxlength="computedInputOptions.maxlength"
92
+ :name="computedInputOptions.name"
93
+ :readonly="computedInputOptions.readonly"
94
+ :tabindex="computedInputOptions.tabindex"
95
+ :aria-describedby="computedInputOptions['aria-describedby']"
77
96
  @blur="onBlur"
78
97
  @focus="onFocus"
79
98
  @keyup.enter="onEnter"
80
99
  @keyup.space="onSpace"
81
- >
100
+ @keydown="handleInput"
101
+ :style="computedInputOptions.style"
102
+ />
82
103
  </div>
83
104
  </label>
84
105
  </div>
@@ -86,11 +107,20 @@
86
107
 
87
108
  <script lang="ts" setup>
88
109
  import {
89
- Icon, Dropdown, TextInput, Flag,
90
- allCountries, type Country,
110
+ Icon,
111
+ Dropdown,
112
+ TextInput,
113
+ Flag,
114
+ allCountries,
115
+ type Country,
116
+ debounce,
91
117
  } from '@bagelink/vue';
92
- import { onMounted, watch } from 'vue';
93
- import { parsePhoneNumberFromString, type CountryCode, type PhoneNumber } from 'libphonenumber-js';
118
+ import { type Raw, type StyleValue, onMounted, watch } from 'vue';
119
+ import {
120
+ parsePhoneNumberFromString,
121
+ type NumberFormat,
122
+ type CountryCode,
123
+ } from 'libphonenumber-js';
94
124
  import axios from 'axios';
95
125
 
96
126
  async function getIp() {
@@ -101,28 +131,34 @@ async function getIp() {
101
131
  return data;
102
132
  }
103
133
 
104
- type DropdownOptions = {
105
- disabled?: boolean;
106
- showFlags?: boolean;
107
- showDialCodeInSelection?: boolean;
108
- showDialCodeInList?: boolean;
134
+ const defaultDropdownOptions = {
135
+ hide: false,
136
+ disabled: false,
137
+ showFlags: true,
138
+ showDialCodeInSelection: true,
139
+ showDialCodeInList: true,
140
+ searchable: true,
109
141
  };
110
142
 
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;
143
+ const defaultInputOptions = {
144
+ showDialCode: true,
145
+ autofocus: false,
146
+ 'aria-describedby': '',
147
+ id: '',
148
+ maxlength: 25,
149
+ minlength: 9,
150
+ pattern: '',
151
+ name: '',
152
+ readonly: false,
153
+ tabindex: 0,
154
+ style: '' as Raw<StyleValue>,
155
+ placeholder: 'Enter a phone number',
120
156
  };
121
157
 
122
158
  type Props = {
123
159
  label?: string;
124
160
  id?: string;
125
- autocomplete?: 'on' | 'off';
161
+ autocomplete?: 'on' | 'off' | 'tel';
126
162
  placeholder?: string;
127
163
  required?: boolean;
128
164
  modelValue: string;
@@ -133,13 +169,14 @@ type Props = {
133
169
  disabled?: boolean;
134
170
  searchable?: boolean;
135
171
  autoDefaultCountry?: boolean;
136
- dropdownOptions?: DropdownOptions;
172
+ inputOptions?: Partial<typeof defaultInputOptions>;
173
+ dropdownOptions?: Partial<typeof defaultDropdownOptions>;
137
174
  excludeCountries?: string[];
138
- inputOptions?: InputOptions;
139
- invalidMsg?: string;
140
- mode?: 'auto' | 'international' | 'national';
175
+ mode?: NumberFormat;
141
176
  onlyCountries?: string[];
142
177
  preferredCountries?: string[];
178
+ parseArg?: { extract?: boolean };
179
+ debounceDelay?: number;
143
180
  };
144
181
 
145
182
  const props = withDefaults(defineProps<Props>(), {
@@ -151,33 +188,27 @@ const props = withDefaults(defineProps<Props>(), {
151
188
  defaultCountry: '',
152
189
  placeholder: 'Enter a phone number',
153
190
  searchable: true,
154
- dropdownOptions: () => ({
155
- disabled: false,
156
- showFlags: true,
157
- showDialCodeInSelection: true,
158
- showDialCodeInList: true,
159
- searchable: true,
160
- }),
161
191
  disabled: false,
162
192
  autoDefaultCountry: true,
163
193
  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
194
  required: false,
175
- invalidMsg: 'Invalid phone number',
176
- mode: 'auto',
195
+ mode: 'INTERNATIONAL',
177
196
  onlyCountries: () => [],
178
197
  preferredCountries: () => [],
198
+ showDropdown: true,
199
+ debounceDelay: 300,
179
200
  });
180
201
 
202
+ const computedDropDownOptions = $computed(() => ({
203
+ ...defaultDropdownOptions,
204
+ ...props.dropdownOptions,
205
+ }));
206
+
207
+ const computedInputOptions = $computed(() => ({
208
+ ...defaultInputOptions,
209
+ ...props.inputOptions,
210
+ }));
211
+
181
212
  let activeCountryCode = $ref<CountryCode>();
182
213
  let open = $ref(false);
183
214
  let selectedIndex = $ref<number>();
@@ -195,53 +226,29 @@ const emit = defineEmits([
195
226
  'country-changed',
196
227
  'enter',
197
228
  'space',
229
+ 'debounce',
198
230
  ]);
199
231
 
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
232
  // Computed: activeCountry
225
- const activeCountry = $computed(() => props.allCountries.find((country) => country.iso2 === activeCountryCode?.toUpperCase()));
233
+ const activeCountry = $computed(() =>
234
+ props.allCountries.find(
235
+ (country) => country.iso2 === activeCountryCode?.toUpperCase(),
236
+ ),
237
+ );
226
238
 
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
- });
239
+ const isPreferred = (country?: Country) =>
240
+ props.preferredCountries.includes(country?.iso2 as CountryCode);
237
241
 
238
242
  const filteredCountries = $computed(() => {
239
243
  if (props.onlyCountries.length) {
240
- return props.allCountries.filter(({ iso2 }) => props.onlyCountries.some((c) => c.toUpperCase() === iso2));
244
+ return props.allCountries.filter(({ iso2 }) =>
245
+ props.onlyCountries.some((c) => c.toUpperCase() === iso2),
246
+ );
241
247
  }
242
248
  if (props.excludeCountries.length) {
243
249
  return props.allCountries.filter(
244
- ({ iso2 }) => !props.excludeCountries.includes(iso2.toUpperCase()) &&
250
+ ({ iso2 }) =>
251
+ !props.excludeCountries.includes(iso2.toUpperCase()) &&
245
252
  !props.excludeCountries.includes(iso2.toLowerCase()),
246
253
  );
247
254
  }
@@ -252,16 +259,48 @@ const sortedCountries = $computed(() => {
252
259
  const preferredCountries = getCountries(props.preferredCountries);
253
260
 
254
261
  const countriesList = [...preferredCountries, ...filteredCountries];
255
- const cleanInput = searchQuery.replace(/[~`!@#$%^&*()+={}\[\];:'"<>.,\/\\\?-_]/g, '');
262
+ const cleanInput = searchQuery.replace(
263
+ /[~`!@#$%^&*()+={}[\];:'"<>.,/\\?-_]/g,
264
+ '',
265
+ );
256
266
  return countriesList
257
267
  .filter(
258
- (c) => new RegExp(cleanInput, 'i').test(c?.name || '') ||
268
+ (c) =>
269
+ new RegExp(cleanInput, 'i').test(c?.name || '') ||
259
270
  new RegExp(cleanInput, 'i').test(c?.iso2 || '') ||
260
271
  new RegExp(cleanInput, 'i').test(c?.dialCode || ''),
261
272
  )
262
273
  .filter(Boolean);
263
274
  });
264
275
 
276
+ const parseArgs = $computed(() => ({
277
+ ...props?.parseArg,
278
+ defaultCountry: activeCountryCode,
279
+ }));
280
+
281
+ const phone = defineModel<string>('modelValue', {
282
+ default: '',
283
+ set: (value) => {
284
+ const formatted = formatPhone(`${value}`);
285
+ if (!formatted) return '';
286
+
287
+ emit('update:modelValue', formatted);
288
+ debounce(() => emit('debounce', formatted), props.debounceDelay);
289
+ return formatted;
290
+ },
291
+ });
292
+
293
+ function formatPhone(val: string): string {
294
+ const phoneNumber = parsePhoneNumberFromString(val, parseArgs);
295
+ if (!phoneNumber) {
296
+ const dialCode =
297
+ sortedCountries.find((c) => c.iso2 === activeCountryCode)?.dialCode || '';
298
+ if (props.mode === 'INTERNATIONAL') return `+${dialCode}`;
299
+ return dialCode;
300
+ }
301
+ return phoneNumber.format(props.mode)?.replaceAll(' ', '') || '';
302
+ }
303
+
265
304
  // Watchers
266
305
  watch(
267
306
  () => activeCountry,
@@ -309,7 +348,8 @@ const initializeCountry = async () => {
309
348
 
310
349
  onMounted(initializeCountry);
311
350
 
312
- const findCountry = (iso: string) => filteredCountries.find((country) => country.iso2 === iso?.toUpperCase());
351
+ const findCountry = (iso: string) =>
352
+ filteredCountries.find((country) => country.iso2 === iso?.toUpperCase());
313
353
 
314
354
  const getCountries = (list: string[]): Country[] => {
315
355
  const countryList: Country[] = [];
@@ -320,7 +360,8 @@ const getCountries = (list: string[]): Country[] => {
320
360
  return countryList;
321
361
  };
322
362
 
323
- const findCountryByDialCode = (dialCode: number) => filteredCountries.find((country) => Number(country.dialCode) === dialCode);
363
+ const findCountryByDialCode = (dialCode: number) =>
364
+ filteredCountries.find((country) => Number(country.dialCode) === dialCode);
324
365
 
325
366
  const chooseCountry = (country?: string) => {
326
367
  if (!country) return;
@@ -329,7 +370,7 @@ const chooseCountry = (country?: string) => {
329
370
  activeCountryCode = parsedCountry.iso2;
330
371
 
331
372
  if (props.inputOptions?.showDialCode && parsedCountry) {
332
- phone = `+${parsedCountry.dialCode}`;
373
+ phone.value = `+ ${parsedCountry.dialCode}`;
333
374
  activeCountryCode = parsedCountry.iso2 || '';
334
375
  return;
335
376
  }
@@ -342,7 +383,7 @@ const onBlur = () => emit('blur');
342
383
 
343
384
  const onFocus = () => emit('focus');
344
385
 
345
- const onEnter = () => emit('enter');
386
+ const onEnter = (e: KeyboardEvent) => emit('enter');
346
387
 
347
388
  const onSpace = () => emit('space');
348
389
 
@@ -350,9 +391,22 @@ const onSpace = () => emit('space');
350
391
 
351
392
  // Method: reset
352
393
  const reset = () => {
353
- selectedIndex = sortedCountries.findIndex((c) => c.iso2 === activeCountryCode);
394
+ selectedIndex = sortedCountries.findIndex(
395
+ (c) => c.iso2 === activeCountryCode,
396
+ );
354
397
  open = false;
355
398
  };
399
+
400
+ function handleInput(e: KeyboardEvent) {
401
+ const keyVal = e.key;
402
+ if (keyVal?.length > 1 || e.metaKey) return;
403
+
404
+ const hasBadChars = RegExp(/[^+\d]/).test(keyVal);
405
+
406
+ if (!hasBadChars) return;
407
+
408
+ e.preventDefault();
409
+ }
356
410
  </script>
357
411
 
358
412
  <style scoped>
@@ -372,19 +426,20 @@ const reset = () => {
372
426
  outline: none;
373
427
  box-shadow: inset 0 0 10px #00000012;
374
428
  }
429
+
375
430
  .tel-input input {
376
431
  background: transparent;
377
432
  }
378
- .tel-input input::placeholder {
379
- color: transparent;
380
- }
433
+
381
434
  .tel-input input:focus-visible {
382
435
  box-shadow: none;
383
436
  }
437
+
384
438
  .input_country-code {
385
439
  font-size: var(--input-font-size);
386
440
  color: var(--input-color);
387
441
  }
442
+
388
443
  .tel-country {
389
444
  font-size: var(--input-font-size);
390
445
  max-width: 200px;
@@ -394,6 +449,7 @@ const reset = () => {
394
449
  margin-top: 0;
395
450
  margin-bottom: 0;
396
451
  }
452
+
397
453
  .tel-countryp-dropdown {
398
454
  direction: ltr;
399
455
  text-align: left;
@@ -10,6 +10,7 @@
10
10
  >
11
11
  <label :for="id">
12
12
  {{ label }}
13
+
13
14
  <input
14
15
  v-if="!multiline && !autoheight && !code"
15
16
  :title="title"
@@ -56,13 +57,10 @@
56
57
  </div>
57
58
  </template>
58
59
 
59
- <script
60
- setup
61
- lang="ts"
62
- >
60
+ <script setup lang="ts">
63
61
  import { onMounted, watch } from 'vue';
64
62
  import {
65
- debounce, Btn, type MaterialIcons, MaterialIcon,
63
+ debounce, type MaterialIcons, MaterialIcon,
66
64
  } from '@bagelink/vue';
67
65
 
68
66
  const emit = defineEmits(['update:modelValue', 'debounce']);
@@ -88,13 +86,14 @@ const props = withDefaults(
88
86
  autoheight?: boolean;
89
87
  code?: boolean;
90
88
  lines?: number;
91
- // eslint-disable-next-line max-len
92
- autocomplete?: string;
89
+ autocomplete?: AutoFillField;
93
90
  autofocus?: boolean;
91
+ debounceDelay?: number;
94
92
  }>(),
95
93
  {
96
94
  type: 'text',
97
95
  modelValue: '',
96
+ debounceDelay: 300,
98
97
  },
99
98
  );
100
99
  let inputVal = $ref<string | number>();
@@ -111,7 +110,7 @@ const rows = $computed(() => {
111
110
  function updateInputVal() {
112
111
  if (props.disabled) return;
113
112
  emit('update:modelValue', inputVal as string);
114
- debounce(() => emit('debounce', inputVal), 300);
113
+ debounce(() => emit('debounce', inputVal), props.debounceDelay);
115
114
  }
116
115
 
117
116
  watch(
@@ -154,7 +153,7 @@ onMounted(() => {
154
153
  background: var(--bgl-black) !important;
155
154
  color: var(--bgl-white) !important;
156
155
  }
157
- .code textarea::placeholder{
156
+ .code textarea::placeholder {
158
157
  color: var(--bgl-white) !important;
159
158
  opacity: 0.3;
160
159
  }
@@ -21,6 +21,7 @@ export { default as Badge } from './Badge.vue';
21
21
  export { default as BglVideo } from './BglVideo.vue';
22
22
  export { default as Carousel } from './Carousel.vue';
23
23
  export { default as ModalConfirm } from './ModalConfirm.vue';
24
+ export { default as MapEmbed } from './MapEmbed.vue';
24
25
  export { default as Flag } from './Flag.vue';
25
26
 
26
27
  export * from './form';