design-system-next 2.14.1 → 2.15.0

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/design-system-next.es.js +9970 -12704
  2. package/dist/design-system-next.es.js.gz +0 -0
  3. package/dist/design-system-next.umd.js +14 -18
  4. package/dist/design-system-next.umd.js.gz +0 -0
  5. package/dist/main.css +1 -1
  6. package/dist/main.css.gz +0 -0
  7. package/dist/package.json.d.ts +4 -3
  8. package/package.json +4 -3
  9. package/src/App.vue +4 -1678
  10. package/src/assets/styles/tailwind.css +6 -19
  11. package/src/components/date-picker/date-picker.ts +0 -1
  12. package/src/components/date-picker/date-picker.vue +2 -2
  13. package/src/components/date-picker/date-range-picker/date-range-picker.vue +59 -36
  14. package/src/components/dropdown/dropdown.ts +8 -1
  15. package/src/components/dropdown/dropdown.vue +3 -1
  16. package/src/components/input/input-contact-number/input-contact-number.ts +6 -8
  17. package/src/components/input/input-contact-number/input-contact-number.vue +6 -5
  18. package/src/components/input/input-contact-number/use-input-contact-number.ts +61 -26
  19. package/src/components/input/input-currency/input-currency.ts +100 -0
  20. package/src/components/input/input-currency/input-currency.vue +60 -0
  21. package/src/components/input/input-currency/use-input-currency.ts +538 -0
  22. package/src/components/input/input.ts +0 -1
  23. package/src/components/input/input.vue +1 -1
  24. package/src/components/select/select-ladderized/select-ladderized.ts +5 -2
  25. package/src/components/select/select-ladderized/select-ladderized.vue +31 -31
  26. package/src/components/select/select-ladderized/use-select-ladderized.ts +1 -13
  27. package/src/components/select/select-multiple/select-multiple.ts +6 -3
  28. package/src/components/select/select-multiple/select-multiple.vue +70 -64
  29. package/src/components/select/select.ts +4 -1
  30. package/src/components/select/select.vue +1 -1
  31. package/src/components/table/table.ts +12 -12
  32. package/src/components/table/table.vue +128 -147
  33. package/src/components/table/use-draggable-table-rows.ts +57 -0
  34. package/src/components/table/use-table.ts +103 -23
  35. package/src/components/textarea/textarea.vue +7 -1
@@ -68,7 +68,7 @@
68
68
 
69
69
  .v-popper__wrapper {
70
70
  .v-popper__inner {
71
- @apply spr-overflow-hidden spr-rounded-none spr-border-none spr-bg-transparent spr-shadow-none;
71
+ @apply spr-overflow-hidden spr-rounded-none spr-border-none spr-bg-transparent spr-shadow-drop;
72
72
 
73
73
  .slide-fade-enter-active {
74
74
  @apply spr-duration-300 spr-ease-out;
@@ -89,7 +89,6 @@
89
89
  }
90
90
  }
91
91
  }
92
-
93
92
  /* #endregion - Sidenav */
94
93
 
95
94
  /* #region - Tooltips */
@@ -97,7 +96,7 @@
97
96
  #tooltip-full-width-wrapper {
98
97
  .v-popper__wrapper {
99
98
  .v-popper__inner {
100
- @apply spr-background-color-inverted spr-text-color-inverted-strong spr-body-xs-regular spr-break-all spr-rounded-border-radius-md spr-px-2 spr-py-1.5 spr-font-main spr-opacity-100;
99
+ @apply spr-background-color-inverted spr-text-color-inverted-strong spr-body-xs-regular spr-break-all spr-rounded-border-radius-md spr-px-2 spr-py-1.5 spr-font-main spr-opacity-100 spr-shadow-drop;
101
100
  }
102
101
 
103
102
  .v-popper__arrow-outer {
@@ -109,7 +108,7 @@
109
108
  #tooltip-wrapper {
110
109
  .v-popper__wrapper {
111
110
  .v-popper__inner {
112
- @apply spr-max-w-full;
111
+ @apply spr-max-w-full spr-shadow-drop;
113
112
  }
114
113
  }
115
114
  }
@@ -117,7 +116,7 @@
117
116
  #tooltip-full-width-wrapper {
118
117
  .v-popper__wrapper {
119
118
  .v-popper__inner {
120
- @apply spr-max-w-[280px];
119
+ @apply spr-max-w-[280px] spr-shadow-drop;
121
120
  }
122
121
  }
123
122
  }
@@ -129,7 +128,7 @@
129
128
 
130
129
  .v-popper__wrapper {
131
130
  .v-popper__inner {
132
- @apply spr-border-color-weak spr-w-full spr-rounded-xl spr-border spr-border-solid spr-font-main spr-shadow-none;
131
+ @apply spr-border-color-weak spr-w-full spr-rounded-xl spr-border spr-border-solid spr-font-main spr-shadow-drop;
133
132
 
134
133
  &::-webkit-scrollbar-track {
135
134
  @apply spr-rounded-br-xl spr-rounded-tr-xl;
@@ -141,18 +140,6 @@
141
140
  }
142
141
  }
143
142
  }
144
-
145
- #contact-number-country-dropdown {
146
- #dropdown-wrapper[data-popper-placement='top-start'] {
147
- margin-top: -4px;
148
- margin-left: -8px;
149
- }
150
-
151
- #dropdown-wrapper[data-popper-placement='bottom-start'] {
152
- margin-top: 4px;
153
- margin-left: -8px;
154
- }
155
- }
156
143
  /* #endregion - Dropdown */
157
144
 
158
145
  /* #region - Select */
@@ -198,7 +185,7 @@
198
185
  }
199
186
  tbody::-webkit-scrollbar {
200
187
  @apply spr-h-0 spr-w-0; /* Hides the scrollbar in WebKit browsers */
201
- }
188
+ }
202
189
 
203
190
  .empty-table-dropzone-dragged-class {
204
191
  @apply spr-hidden;
@@ -27,7 +27,6 @@ export const datePickerPropTypes = {
27
27
  id: {
28
28
  type: String,
29
29
  required: true,
30
- default: 'date-picker',
31
30
  },
32
31
  modelValue: {
33
32
  type: String,
@@ -266,7 +266,7 @@
266
266
  </Menu>
267
267
  <div v-if="props.displayHelper" :class="datePickerClasses.datePickerInputHelperClasses">
268
268
  <slot name="helperMessage">
269
- <Icon v-if="props.helperIcon" :icon="props.helperIcon" width="20px" height="20px" />
269
+ <Icon v-if="props.helperIcon" class="spr-h-5 spr-min-h-5 spr-w-5 spr-min-w-5" :icon="props.helperIcon" />
270
270
  <span>{{ props.helperText }}</span>
271
271
  </slot>
272
272
  </div>
@@ -330,7 +330,7 @@ const {
330
330
  handleTabClick,
331
331
  handleBackspace,
332
332
  clearDate,
333
- handleSlotClick
333
+ handleSlotClick,
334
334
  } = useDatePicker(props, emit);
335
335
 
336
336
  defineExpose({
@@ -1,42 +1,49 @@
1
1
  <template>
2
2
  <div>
3
3
  <Menu
4
- v-model:shown="datePopperState"
5
- aria-id="date-range-picker-wrapper"
6
- distance="4"
7
- :placement="finalPlacement"
8
- :triggers="[]"
9
- :popper-hide-triggers="[]"
10
- :auto-hide="false"
11
- :disabled="isDateRangePickerPopperDisabled"
12
- :container="`#${props.id}`"
13
- :reference="activeInputRef"
14
- :strategy="
15
- props.popperStrategy === 'fixed' || props.popperStrategy === 'absolute' ? props.popperStrategy : 'absolute'
16
- "
17
- :delay="0"
18
- :auto-placement="!isUsingCustomSlot"
19
- :style="{
20
- width: props.width,
21
- }"
22
- >
4
+ v-model:shown="datePopperState"
5
+ aria-id="date-range-picker-wrapper"
6
+ distance="4"
7
+ :placement="finalPlacement"
8
+ :triggers="[]"
9
+ :popper-hide-triggers="[]"
10
+ :auto-hide="false"
11
+ :disabled="isDateRangePickerPopperDisabled"
12
+ :container="`#${props.id}`"
13
+ :reference="activeInputRef"
14
+ :strategy="
15
+ props.popperStrategy === 'fixed' || props.popperStrategy === 'absolute' ? props.popperStrategy : 'absolute'
16
+ "
17
+ :delay="0"
18
+ :auto-placement="!isUsingCustomSlot"
19
+ :style="{
20
+ width: props.width,
21
+ }"
22
+ >
23
23
  <div :id="props.id" class="spr-grid spr-gap-size-spacing-4xs">
24
24
  <label v-if="props.label" :for="props.id" :class="dateRangePickerClasses.labelClasses">
25
25
  {{ props.label }}
26
26
  </label>
27
-
27
+
28
28
  <!-- Date Range Input Container -->
29
- <div class="spr-flex spr-items-center spr-gap-2 spr-w-full">
29
+ <div class="spr-flex spr-w-full spr-items-center spr-gap-2">
30
30
  <slot :handle-click="handleCustomComponentClick">
31
31
  <!-- fallback: original input fields -->
32
32
  <!-- Start Date Input -->
33
- <div ref="startDateContainerRef" :class="['spr-flex-1', dateRangePickerClasses.dateRangePickerBaseInputClasses]" @click.stop="handleStartDateClick" >
33
+ <div
34
+ ref="startDateContainerRef"
35
+ :class="['spr-flex-1', dateRangePickerClasses.dateRangePickerBaseInputClasses]"
36
+ @click.stop="handleStartDateClick"
37
+ >
34
38
  <div class="spr-flex spr-h-full spr-items-center spr-gap-1.5">
35
39
  <input
36
40
  :id="`${props.id}-start-month`"
37
41
  ref="startMonthInputRef"
38
42
  v-model="startMonthInput"
39
- :class="['spr-w-[38px] spr-min-w-[38px] spr-uppercase', dateRangePickerClasses.dateRangePickerInputClasses]"
43
+ :class="[
44
+ 'spr-w-[38px] spr-min-w-[38px] spr-uppercase',
45
+ dateRangePickerClasses.dateRangePickerInputClasses,
46
+ ]"
40
47
  type="text"
41
48
  placeholder="MMM"
42
49
  maxlength="3"
@@ -51,7 +58,10 @@
51
58
  :id="`${props.id}-start-date`"
52
59
  ref="startDateInputRef"
53
60
  v-model="startDateInput"
54
- :class="['spr-w-[24px] spr-min-w-[24px] spr-text-center', dateRangePickerClasses.dateRangePickerInputClasses]"
61
+ :class="[
62
+ 'spr-w-[24px] spr-min-w-[24px] spr-text-center',
63
+ dateRangePickerClasses.dateRangePickerInputClasses,
64
+ ]"
55
65
  type="text"
56
66
  placeholder="DD"
57
67
  maxlength="2"
@@ -84,13 +94,20 @@
84
94
  <!-- Separator -->
85
95
  <span class="spr-text-color-strong spr-font-size-200 spr-text-color-weak">{{ props.separator }}</span>
86
96
  <!-- End Date Input -->
87
- <div ref="endDateContainerRef" :class="['spr-flex-1', dateRangePickerClasses.dateRangePickerBaseInputClasses]" @click.stop="handleEndDateClick">
97
+ <div
98
+ ref="endDateContainerRef"
99
+ :class="['spr-flex-1', dateRangePickerClasses.dateRangePickerBaseInputClasses]"
100
+ @click.stop="handleEndDateClick"
101
+ >
88
102
  <div class="spr-flex spr-h-full spr-items-center spr-gap-1.5">
89
103
  <input
90
104
  :id="`${props.id}-end-month`"
91
105
  ref="endMonthInputRef"
92
106
  v-model="endMonthInput"
93
- :class="['spr-w-[38px] spr-min-w-[38px] spr-uppercase', dateRangePickerClasses.dateRangePickerInputClasses]"
107
+ :class="[
108
+ 'spr-w-[38px] spr-min-w-[38px] spr-uppercase',
109
+ dateRangePickerClasses.dateRangePickerInputClasses,
110
+ ]"
94
111
  type="text"
95
112
  placeholder="MMM"
96
113
  maxlength="3"
@@ -105,7 +122,10 @@
105
122
  :id="`${props.id}-end-date`"
106
123
  ref="endDateInputRef"
107
124
  v-model="endDateInput"
108
- :class="['spr-w-[24px] spr-min-w-[24px] spr-text-center', dateRangePickerClasses.dateRangePickerInputClasses]"
125
+ :class="[
126
+ 'spr-w-[24px] spr-min-w-[24px] spr-text-center',
127
+ dateRangePickerClasses.dateRangePickerInputClasses,
128
+ ]"
109
129
  type="text"
110
130
  placeholder="DD"
111
131
  maxlength="2"
@@ -244,11 +264,11 @@
244
264
  'spr-text-color-disabled': calendarTabIsInactiveMonthDates(day),
245
265
 
246
266
  // Selected Date (Start or End) - Use brand color scheme from date picker
247
- 'spr-background-color-brand-base active:spr-background-color-brand-pressed spr-text-color-inverted-strong !spr-text-white-50 active:spr-scale-95 spr-font-medium':
267
+ 'spr-background-color-brand-base active:spr-background-color-brand-pressed spr-text-color-inverted-strong spr-font-medium !spr-text-white-50 active:spr-scale-95':
248
268
  calendarTabIsSelectedDate(day),
249
269
 
250
270
  // In Range (between start and end) - Light green background with brand outline, no border, using spr- prefix
251
- 'spr-bg-green-100 spr-cursor-pointer spr-outline spr-outline-1 spr-outline-offset-[-0.5px] spr-outline-kangkong-700':
271
+ 'spr-cursor-pointer spr-bg-green-100 spr-outline spr-outline-1 spr-outline-offset-[-0.5px] spr-outline-kangkong-700':
252
272
  calendarTabIsInRange(day),
253
273
 
254
274
  // Unselected Date - Gray border, no hover effects
@@ -264,7 +284,7 @@
264
284
  <span>{{ day.date.date() }}</span>
265
285
  <div
266
286
  v-if="calendarTabIsTodayIndicator(day)"
267
- class="spr-bg-green-600 spr-absolute spr-bottom-1 spr-m-auto spr-h-1 spr-w-1 spr-rounded-full"
287
+ class="spr-absolute spr-bottom-1 spr-m-auto spr-h-1 spr-w-1 spr-rounded-full spr-bg-green-600"
268
288
  ></div>
269
289
  </div>
270
290
  <div v-else></div>
@@ -281,9 +301,11 @@
281
301
  {
282
302
  'spr-text-color-brand-base': month.monthValue === currentDate.month(),
283
303
  'spr-border-color-weak hover:spr-background-color-hover active:spr-background-color-pressed':
284
- month.text.toLowerCase() !== startMonthInput.toLowerCase() && month.text.toLowerCase() !== endMonthInput.toLowerCase(),
304
+ month.text.toLowerCase() !== startMonthInput.toLowerCase() &&
305
+ month.text.toLowerCase() !== endMonthInput.toLowerCase(),
285
306
  'spr-border-color-brand-base spr-background-color-single-active':
286
- month.text.toLowerCase() === startMonthInput.toLowerCase() || month.text.toLowerCase() === endMonthInput.toLowerCase(),
307
+ month.text.toLowerCase() === startMonthInput.toLowerCase() ||
308
+ month.text.toLowerCase() === endMonthInput.toLowerCase(),
287
309
  },
288
310
  ]"
289
311
  @click="monthTabHandleSelectedMonth(month)"
@@ -308,7 +330,8 @@
308
330
  'spr-text-color-brand-base': year === currentDate.year(),
309
331
  'spr-border-color-weak hover:spr-background-color-hover active:spr-background-color-pressed':
310
332
  year !== Number(startYearInput) && year !== Number(endYearInput),
311
- 'spr-border-color-brand-base spr-background-color-single-active': year === Number(startYearInput) || year === Number(endYearInput),
333
+ 'spr-border-color-brand-base spr-background-color-single-active':
334
+ year === Number(startYearInput) || year === Number(endYearInput),
312
335
  },
313
336
  ]"
314
337
  @click="yearTabHandleSelectedYear(String(year))"
@@ -324,10 +347,10 @@
324
347
  </div>
325
348
  </template>
326
349
  </Menu>
327
-
350
+
328
351
  <div v-if="props.displayHelper" :class="dateRangePickerClasses.dateRangePickerInputHelperClasses">
329
352
  <slot name="helperMessage">
330
- <Icon v-if="props.helperIcon" :icon="props.helperIcon" width="20px" height="20px" />
353
+ <Icon v-if="props.helperIcon" class="spr-h-5 spr-min-h-5 spr-w-5 spr-min-w-5" :icon="props.helperIcon" />
331
354
  <span>{{ props.helperText }}</span>
332
355
  </slot>
333
356
  </div>
@@ -409,4 +432,4 @@ const {
409
432
  handleEndDateClick,
410
433
  handleCustomComponentClick,
411
434
  } = useDateRangePicker(props, emit);
412
- </script>
435
+ </script>
@@ -42,6 +42,10 @@ export const dropdownPropTypes = {
42
42
  required: true,
43
43
  default: [],
44
44
  },
45
+ searchableMenu: {
46
+ type: Boolean,
47
+ default: false,
48
+ },
45
49
  textField: {
46
50
  type: String,
47
51
  default: 'text',
@@ -65,6 +69,10 @@ export const dropdownPropTypes = {
65
69
  validator: (value: (typeof PLACEMENTS_TYPES)[number]) => PLACEMENTS_TYPES.includes(value),
66
70
  default: 'bottom',
67
71
  },
72
+ distance: {
73
+ type: Number,
74
+ default: 6,
75
+ },
68
76
  groupItemsBy: {
69
77
  type: String as PropType<(typeof GROUPED_ITEMS_BY_TYPES)[number]>,
70
78
  validator: (value: (typeof GROUPED_ITEMS_BY_TYPES)[number] | undefined) => {
@@ -112,7 +120,6 @@ export const dropdownPropTypes = {
112
120
  type: Boolean,
113
121
  default: false,
114
122
  },
115
- // Enable lozenge style for dropdown items
116
123
  lozenge: {
117
124
  type: Boolean,
118
125
  default: false,
@@ -2,7 +2,7 @@
2
2
  <Menu
3
3
  v-model:shown="dropdownPopperState"
4
4
  aria-id="dropdown-wrapper"
5
- distance="4"
5
+ :distance="props.distance"
6
6
  :placement="props.placement"
7
7
  :triggers="props.triggers"
8
8
  :popper-triggers="props.popperTriggers"
@@ -45,6 +45,7 @@
45
45
  v-if="!props.ladderized || isLadderizedSearch"
46
46
  v-model="selectedListItems"
47
47
  :menu-list="dropdownMenuList"
48
+ :searchable-menu="props.searchableMenu"
48
49
  :group-items-by="props.groupItemsBy"
49
50
  :multi-select="props.multiSelect"
50
51
  :pre-selected-items="dropdownValue"
@@ -59,6 +60,7 @@
59
60
  :ladderized="props.ladderized"
60
61
  :dropdown="props.dropdown"
61
62
  :menu-list="dropdownMenuList"
63
+ :searchable-menu="props.searchableMenu"
62
64
  :remove-current-level-in-back-label="removeCurrentLevelInBackLabel"
63
65
  @update:model-value="handleSelectedLadderizedItem"
64
66
  />
@@ -23,9 +23,13 @@ export const COUNTRY_OPTIONS: CountryOption[] = getCountries().map((countryCode)
23
23
  });
24
24
 
25
25
  export const inputContactNumberPropTypes = {
26
+ id: {
27
+ type: String,
28
+ default: '',
29
+ },
26
30
  modelValue: {
27
31
  type: String,
28
- required: true,
32
+ default: '',
29
33
  },
30
34
  placeholder: {
31
35
  type: String,
@@ -69,13 +73,7 @@ export const inputContactNumberEmitTypes = {
69
73
 
70
74
  export interface InputContactNumberEmit {
71
75
  (event: 'update:modelValue', value: string): void;
72
- (
73
- event: 'getSelectedCountryCallingCode',
74
- value: {
75
- countryCode: string[];
76
- countryCallingCode: string[];
77
- },
78
- ): void;
76
+ (event: 'getSelectedCountryCallingCode', value: { countryCode: string; countryCallingCode: string }): void;
79
77
  (event: 'getContactNumberErrors', value: Array<{ title: string; message: string }>): void;
80
78
  }
81
79
 
@@ -12,9 +12,9 @@
12
12
  >
13
13
  <template #prefix>
14
14
  <spr-dropdown
15
- id="contact-number-country-dropdown"
15
+ :id="dropdownId"
16
16
  v-model="selectedCountry.countryCode"
17
- class="[&>#dropdown-wrapper]:spr-my-1"
17
+ :class="inputContactNumberClasses.dropdownBaseClasses"
18
18
  :menu-list="COUNTRY_OPTIONS"
19
19
  placement="bottom-start"
20
20
  :width="!props.disabledCountryCallingCode ? '45px' : '35px'"
@@ -23,10 +23,10 @@
23
23
  @update:model-value="handleSelectedCountryCode"
24
24
  @get-popper-state="handlePopperState"
25
25
  >
26
- <span :class="inputContactNumberClasses.countryCallingCodeClasses">
27
- +{{ selectedCountry.countryCallingCode[0] }}
26
+ <div :class="inputContactNumberClasses.dropdownWrappertClasses">
27
+ <span>+{{ selectedCountry.countryCallingCode }}</span>
28
28
  <icon v-if="!props.disabledCountryCallingCode" icon="ph:caret-down" width="16px" height="16px" />
29
- </span>
29
+ </div>
30
30
  </spr-dropdown>
31
31
  </template>
32
32
  </spr-input>
@@ -48,6 +48,7 @@ const emit = defineEmits(inputContactNumberEmitTypes);
48
48
 
49
49
  const {
50
50
  inputContactNumberClasses,
51
+ dropdownId,
51
52
  formattedValue,
52
53
  selectedCountry,
53
54
  popperState,
@@ -8,17 +8,26 @@ import parsePhoneNumber, { getCountries, getCountryCallingCode, CountryCode } fr
8
8
  import { type InputContactNumberEmitTypes, type InputContactNumberPropTypes } from './input-contact-number';
9
9
 
10
10
  interface InputContactNumberClasses {
11
- countryCallingCodeClasses: string;
11
+ dropdownBaseClasses: string;
12
+ dropdownWrappertClasses: string;
12
13
  }
13
14
 
14
15
  export const useInputContactNumber = (
15
16
  props: InputContactNumberPropTypes,
16
17
  emit: SetupContext<InputContactNumberEmitTypes>['emit'],
17
18
  ) => {
18
- const { preSelectedCountryCode, disabledCountryCallingCode, disabled } = toRefs(props);
19
+ const { id, preSelectedCountryCode, disabledCountryCallingCode, disabled } = toRefs(props);
19
20
 
20
21
  const inputContactNumberClasses: ComputedRef<InputContactNumberClasses> = computed(() => {
21
- const countryCallingCodeClasses = classNames(
22
+ const dropdownBaseClasses = classNames(
23
+ '[&_#dropdown-wrapper]:spr-my-1',
24
+ '[&_#dropdown-wrapper[data-popper-placement="bottom-start"]]:spr-ml-[-10px]',
25
+ '[&_#dropdown-wrapper[data-popper-placement="bottom-start"]]:spr-mt-[6px]',
26
+ '[&_#dropdown-wrapper[data-popper-placement="top-start"]]:spr-ml-[-10px]',
27
+ '[&_#dropdown-wrapper[data-popper-placement="top-start"]]:spr-mt-[-6px]',
28
+ );
29
+
30
+ const dropdownWrappertClasses = classNames(
22
31
  'spr-font-weight-regular spr-font-size-200 spr-line-height-500 spr-letter-spacing-none spr-font-main',
23
32
  'spr-flex spr-items-center spr-gap-size-spacing-5xs',
24
33
  {
@@ -29,20 +38,25 @@ export const useInputContactNumber = (
29
38
  );
30
39
 
31
40
  return {
32
- countryCallingCodeClasses,
41
+ dropdownBaseClasses,
42
+ dropdownWrappertClasses,
33
43
  };
34
44
  });
35
45
 
46
+ // fallback random id if user does not provide one (stable per component instance)
47
+ const fallbackId = ref(`currency-${Math.random().toString(36).slice(2, 8)}-dropdown`);
48
+ const dropdownId = computed(() => (id.value ? `${id.value}-dropdown` : fallbackId.value));
49
+
36
50
  const formattedValue = useVModel(props, 'modelValue', emit);
37
51
 
38
52
  const selectedCountry = ref({
39
- countryCode: ['PH'],
40
- countryCallingCode: ['63'],
53
+ countryCode: 'PH',
54
+ countryCallingCode: '63',
41
55
  });
42
56
 
43
57
  const popperState = ref(false);
44
58
 
45
- const setselectedCountry = (selectedCountryCode: string) => {
59
+ const setSelectedCountry = (selectedCountryCode: string) => {
46
60
  const countryCallingCode = getCountryCallingCode(selectedCountryCode as CountryCode);
47
61
 
48
62
  const countryCode = getCountries().find((country) => {
@@ -51,8 +65,8 @@ export const useInputContactNumber = (
51
65
 
52
66
  if (countryCode && countryCallingCode) {
53
67
  selectedCountry.value = {
54
- countryCode: [countryCode],
55
- countryCallingCode: [countryCallingCode],
68
+ countryCode: countryCode,
69
+ countryCallingCode: countryCallingCode,
56
70
  };
57
71
 
58
72
  formatContactNumber();
@@ -81,10 +95,10 @@ export const useInputContactNumber = (
81
95
  }
82
96
  };
83
97
 
84
- const handleSelectedCountryCode = (countryCode: string[]) => {
98
+ const handleSelectedCountryCode = (countryCode: string) => {
85
99
  selectedCountry.value = {
86
- countryCode: [countryCode[0]],
87
- countryCallingCode: [getCountryCallingCode(countryCode[0] as CountryCode)],
100
+ countryCode: countryCode,
101
+ countryCallingCode: getCountryCallingCode(countryCode as CountryCode),
88
102
  };
89
103
 
90
104
  emit('getContactNumberErrors', []);
@@ -92,27 +106,46 @@ export const useInputContactNumber = (
92
106
  formatContactNumber();
93
107
 
94
108
  emit('getSelectedCountryCallingCode', {
95
- countryCode: selectedCountry.value.countryCode[0],
96
- countryCallingCode: selectedCountry.value.countryCallingCode[0],
109
+ countryCode: selectedCountry.value.countryCode,
110
+ countryCallingCode: selectedCountry.value.countryCallingCode,
97
111
  });
98
112
  };
99
113
 
100
114
  const formatContactNumber = () => {
101
- if (!formattedValue.value) return;
102
-
103
- const normalizedNumber = formattedValue.value.replace(/\D/g, '');
115
+ if (!formattedValue.value) {
116
+ emit('getContactNumberErrors', []);
117
+ return;
118
+ }
104
119
 
105
- const phoneNumber = parsePhoneNumber(normalizedNumber, {
106
- defaultCountry: selectedCountry.value.countryCode[0] as CountryCode,
107
- extract: false,
108
- });
120
+ const original = formattedValue.value.trim();
121
+ const hasPlus = original.startsWith('+');
122
+ const normalizedNumber = hasPlus ? `+${original.replace(/[^0-9]/g, '')}` : original.replace(/\D/g, '');
123
+
124
+ let phoneNumber;
125
+
126
+ try {
127
+ phoneNumber = hasPlus
128
+ ? parsePhoneNumber(normalizedNumber)
129
+ : parsePhoneNumber(normalizedNumber, {
130
+ defaultCountry: selectedCountry.value.countryCode as CountryCode,
131
+ extract: false,
132
+ });
133
+ } catch {
134
+ phoneNumber = undefined;
135
+ }
109
136
 
110
137
  if (phoneNumber && phoneNumber.isValid()) {
111
138
  let formattedNumber = phoneNumber.formatInternational();
112
139
 
113
- formattedNumber = formattedNumber.replace(`+${selectedCountry.value.countryCallingCode[0]} `, '');
140
+ const prefix = `+${selectedCountry.value.countryCallingCode} `;
141
+
142
+ if (formattedNumber.startsWith(prefix)) {
143
+ formattedNumber = formattedNumber.slice(prefix.length);
144
+ }
114
145
 
115
146
  formattedValue.value = formattedNumber;
147
+
148
+ emit('getContactNumberErrors', []);
116
149
  } else {
117
150
  emit('getContactNumberErrors', [
118
151
  {
@@ -133,23 +166,24 @@ export const useInputContactNumber = (
133
166
 
134
167
  watch(preSelectedCountryCode, (newValue) => {
135
168
  if (newValue) {
136
- setselectedCountry(newValue);
169
+ setSelectedCountry(newValue);
137
170
  }
138
171
  });
139
172
 
140
173
  onMounted(() => {
141
174
  emit('getSelectedCountryCallingCode', {
142
- countryCode: selectedCountry.value.countryCode[0],
143
- countryCallingCode: selectedCountry.value.countryCallingCode[0],
175
+ countryCode: selectedCountry.value.countryCode,
176
+ countryCallingCode: selectedCountry.value.countryCallingCode,
144
177
  });
145
178
 
146
179
  if (preSelectedCountryCode.value) {
147
- setselectedCountry(preSelectedCountryCode.value);
180
+ setSelectedCountry(preSelectedCountryCode.value);
148
181
  }
149
182
  });
150
183
 
151
184
  return {
152
185
  inputContactNumberClasses,
186
+ dropdownId,
153
187
  formattedValue,
154
188
  selectedCountry,
155
189
  popperState,
@@ -158,5 +192,6 @@ export const useInputContactNumber = (
158
192
  formatContactNumber,
159
193
  handleUpdateModelValue,
160
194
  handlePopperState,
195
+ setSelectedCountry,
161
196
  };
162
197
  };