js-kompo-utils 1.0.34 → 1.0.35

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.
@@ -56,6 +56,7 @@
56
56
  <input
57
57
  ref="phoneInput"
58
58
  class="phone-input"
59
+ :class="{ 'has-extension': showExtension }"
59
60
  :value="formattedValue"
60
61
  v-bind="$_attributes"
61
62
  v-on="$_events"
@@ -64,6 +65,44 @@
64
65
  @focus="onPhoneFocus"
65
66
  @blur="onPhoneBlur"
66
67
  />
68
+
69
+ <!-- Extension ("poste"), opt-in via ->withExtension(). Collapsed until asked for,
70
+ so the common case stays a single field. -->
71
+ <button
72
+ v-if="showExtension && !extensionOpen"
73
+ type="button"
74
+ class="phone-extension-toggle"
75
+ :title="extensionLabel"
76
+ :disabled="$_attributes.disabled"
77
+ @click="openExtension"
78
+ >
79
+ <span class="phone-extension-plus">+</span>
80
+ <span class="phone-extension-toggle-label">{{ extensionLabel }}</span>
81
+ </button>
82
+
83
+ <div v-else-if="showExtension" class="phone-extension-wrap">
84
+ <span class="phone-extension-prefix">{{ extensionLabel }}</span>
85
+ <input
86
+ ref="extensionInput"
87
+ class="phone-extension-input"
88
+ type="text"
89
+ inputmode="numeric"
90
+ autocomplete="off"
91
+ :value="extensionValue"
92
+ :placeholder="extensionPlaceholder"
93
+ :disabled="$_attributes.disabled"
94
+ :readonly="$_attributes.readonly"
95
+ @input="onExtensionInput"
96
+ @keydown.esc="closeExtension"
97
+ />
98
+ <button
99
+ type="button"
100
+ class="phone-extension-clear"
101
+ :title="extensionClearLabel"
102
+ :disabled="$_attributes.disabled"
103
+ @click="closeExtension"
104
+ >×</button>
105
+ </div>
67
106
  </div>
68
107
  </vl-form-field>
69
108
  </template>
@@ -96,6 +135,11 @@ export default {
96
135
 
97
136
  // Geolocation state
98
137
  isDetectingCountry: false,
138
+
139
+ // Optional extension ("poste"), submitted as `<name>_ext`. Starts open only
140
+ // when there is one to show, so an empty field stays a single box.
141
+ extensionValue: this.normalizeExtension(this.$_config('extensionValue')),
142
+ extensionOpen: this.normalizeExtension(this.$_config('extensionValue')) !== '',
99
143
  }
100
144
  },
101
145
 
@@ -114,6 +158,22 @@ export default {
114
158
  placeholder() {
115
159
  return this.$_config('placeholder') || 'Phone number'
116
160
  },
161
+
162
+ showExtension() {
163
+ return !!this.$_config('withExtension')
164
+ },
165
+
166
+ extensionLabel() {
167
+ return this.$_config('extensionLabel') || 'ext'
168
+ },
169
+
170
+ extensionPlaceholder() {
171
+ return this.$_config('extensionPlaceholder') || '000'
172
+ },
173
+
174
+ extensionClearLabel() {
175
+ return this.$_config('extensionClearLabel') || 'Remove'
176
+ },
117
177
 
118
178
  inputGroupClass() {
119
179
  const baseClass = 'vlInputGroup rounded-lg flex items-stretch'
@@ -212,6 +272,13 @@ export default {
212
272
  }
213
273
  },
214
274
 
275
+ // Kompo swaps `component` in place on a refresh without re-running data(), which is why
276
+ // the number re-syncs above. The extension has to follow or it keeps the stale prefill.
277
+ 'component.config.extensionValue'(newValue) {
278
+ this.extensionValue = this.normalizeExtension(newValue)
279
+ this.extensionOpen = this.extensionOpen || this.extensionValue !== ''
280
+ },
281
+
215
282
  selectedCountryCode(newCode, oldCode) {
216
283
  if (newCode !== oldCode && this.phoneValue) {
217
284
  this.reformatWithCountry(newCode)
@@ -239,6 +306,42 @@ export default {
239
306
  },
240
307
 
241
308
  methods: {
309
+ // The extension is not part of the number, so it rides alongside it as its own
310
+ // key instead of being folded into the value the way E.164 cannot carry it.
311
+ $_fill(jsonFormData) {
312
+ jsonFormData[this.$_name] = (this.$_value === null || this.$_value === undefined) ? '' : this.$_value
313
+
314
+ if (this.showExtension) {
315
+ jsonFormData[this.$_name + '_ext'] = this.extensionValue || ''
316
+ }
317
+ },
318
+
319
+ // `|| ''` would swallow a "0" extension, and the column is a string.
320
+ normalizeExtension(value) {
321
+ return (value === null || value === undefined) ? '' : String(value)
322
+ },
323
+
324
+ onExtensionInput(event) {
325
+ this.extensionValue = event.target.value.replace(/[^0-9#*]/g, '')
326
+
327
+ if (event.target.value !== this.extensionValue) {
328
+ event.target.value = this.extensionValue
329
+ }
330
+ },
331
+
332
+ openExtension() {
333
+ this.extensionOpen = true
334
+
335
+ this.$nextTick(() => this.$refs.extensionInput?.focus())
336
+ },
337
+
338
+ // Collapsing clears the value: the empty companion still posts, so the stored
339
+ // extension is removed rather than silently kept.
340
+ closeExtension() {
341
+ this.extensionValue = ''
342
+ this.extensionOpen = false
343
+ },
344
+
242
345
  // Country selection methods
243
346
  toggleDropdown() {
244
347
  this.isDropdownOpen = !this.isDropdownOpen
@@ -922,4 +1025,100 @@ export default {
922
1025
  color: #6b7280;
923
1026
  cursor: not-allowed;
924
1027
  }
1028
+
1029
+ .phone-input.has-extension {
1030
+ border-radius: 0;
1031
+ }
1032
+
1033
+ /* Collapsed state: a quiet pill closing the group's right edge. */
1034
+ .phone-extension-toggle {
1035
+ display: flex;
1036
+ align-items: center;
1037
+ gap: 0.2rem;
1038
+ flex: none;
1039
+ padding: 0 0.7rem;
1040
+ margin-left: -1px;
1041
+ border: 1px solid #d1d5db;
1042
+ border-radius: 0 0.5rem 0.5rem 0;
1043
+ background-color: transparent;
1044
+ color: #6b7280;
1045
+ font-size: 0.8125rem;
1046
+ line-height: 1;
1047
+ white-space: nowrap;
1048
+ cursor: pointer;
1049
+ transition: color 0.15s ease, background-color 0.15s ease;
1050
+ }
1051
+
1052
+ .phone-extension-toggle:hover:not(:disabled) {
1053
+ color: #3b82f6;
1054
+ background-color: #f3f4f6;
1055
+ }
1056
+
1057
+ .phone-extension-toggle:disabled {
1058
+ cursor: not-allowed;
1059
+ opacity: 0.6;
1060
+ }
1061
+
1062
+ .phone-extension-plus {
1063
+ font-size: 0.95rem;
1064
+ line-height: 1;
1065
+ }
1066
+
1067
+ /* Expanded state: label + field + dismiss, sharing one bordered cell. */
1068
+ .phone-extension-wrap {
1069
+ display: flex;
1070
+ align-items: center;
1071
+ flex: none;
1072
+ margin-left: -1px;
1073
+ padding-left: 0.6rem;
1074
+ border: 1px solid #d1d5db;
1075
+ border-radius: 0 0.5rem 0.5rem 0;
1076
+ }
1077
+
1078
+ .phone-extension-wrap:focus-within {
1079
+ border-color: #3b82f6;
1080
+ box-shadow: 0 0 0 1px #3b82f6;
1081
+ z-index: 1;
1082
+ }
1083
+
1084
+ .phone-extension-prefix {
1085
+ color: #9ca3af;
1086
+ font-size: 0.8125rem;
1087
+ white-space: nowrap;
1088
+ pointer-events: none;
1089
+ }
1090
+
1091
+ .phone-extension-input {
1092
+ width: 3.75rem;
1093
+ min-width: 0;
1094
+ padding: 0.5rem 0.25rem;
1095
+ border: none;
1096
+ outline: none;
1097
+ background-color: transparent;
1098
+ font-size: 1rem;
1099
+ }
1100
+
1101
+ .phone-extension-input:disabled {
1102
+ color: #6b7280;
1103
+ cursor: not-allowed;
1104
+ }
1105
+
1106
+ .phone-extension-clear {
1107
+ padding: 0 0.55rem 0 0.15rem;
1108
+ border: none;
1109
+ background-color: transparent;
1110
+ color: #9ca3af;
1111
+ font-size: 1.05rem;
1112
+ line-height: 1;
1113
+ cursor: pointer;
1114
+ }
1115
+
1116
+ .phone-extension-clear:hover:not(:disabled) {
1117
+ color: #ef4444;
1118
+ }
1119
+
1120
+ .phone-extension-clear:disabled {
1121
+ cursor: not-allowed;
1122
+ opacity: 0.6;
1123
+ }
925
1124
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-kompo-utils",
3
- "version": "1.0.34",
3
+ "version": "1.0.35",
4
4
  "description": "",
5
5
  "main": "app.js",
6
6
  "scripts": {