| 1 |
/** |
| 2 |
* Country selector with flags. |
| 3 |
* |
| 4 |
* Upgrades any `<select data-yatra-country-select>` into the same searchable, |
| 5 |
* flag-bearing dropdown the phone widget uses (assets/js/phone-input.js), so |
| 6 |
* every country field in the plugin looks and behaves alike. |
| 7 |
* |
| 8 |
* Progressive enhancement on purpose: the original <select> stays in the DOM as |
| 9 |
* the value holder. The widget writes the chosen ISO code back to it and fires a |
| 10 |
* `change` event, so form submission, required-field validation and any existing |
| 11 |
* listener keep working exactly as before. If this script fails to load — or the |
| 12 |
* browser is very old — the untouched native select is still fully usable. |
| 13 |
* |
| 14 |
* Reads flag URLs from the field's own `data-flag-base`, matching the phone |
| 15 |
* widget, so there is a single place that knows where the SVGs live. |
| 16 |
*/ |
| 17 |
(function () { |
| 18 |
"use strict"; |
| 19 |
|
| 20 |
var DATA = window.yatraCountrySelectData || {}; |
| 21 |
var I18N = DATA.i18n || {}; |
| 22 |
var SEARCH_PLACEHOLDER = I18N.search || "Search country"; |
| 23 |
var NO_RESULTS = I18N.noResults || "No matches"; |
| 24 |
|
| 25 |
/** Native pickers are better on touch devices, so leave those alone. */ |
| 26 |
function prefersNativePicker() { |
| 27 |
return ( |
| 28 |
window.matchMedia && |
| 29 |
window.matchMedia("(max-width: 640px), (pointer: coarse)").matches |
| 30 |
); |
| 31 |
} |
| 32 |
|
| 33 |
function flagUrl(base, iso) { |
| 34 |
if (!base || !iso) return ""; |
| 35 |
return base + String(iso).toLowerCase() + ".svg"; |
| 36 |
} |
| 37 |
|
| 38 |
function closeAll(except) { |
| 39 |
document |
| 40 |
.querySelectorAll(".yatra-country-field.is-open") |
| 41 |
.forEach(function (f) { |
| 42 |
if (f === except) return; |
| 43 |
f.classList.remove("is-open"); |
| 44 |
var btn = f.querySelector(".yatra-country-trigger"); |
| 45 |
if (btn) btn.setAttribute("aria-expanded", "false"); |
| 46 |
}); |
| 47 |
} |
| 48 |
|
| 49 |
function enhance(select) { |
| 50 |
if (!select || select.dataset.yatraCountryReady === "1") return; |
| 51 |
// A disabled or readonly field gains nothing from the widget. |
| 52 |
if (select.disabled) return; |
| 53 |
|
| 54 |
select.dataset.yatraCountryReady = "1"; |
| 55 |
|
| 56 |
var flagBase = select.getAttribute("data-flag-base") || ""; |
| 57 |
|
| 58 |
// Build the option model straight from the existing <select>, so whatever |
| 59 |
// list, order or filtering the server rendered is preserved verbatim. |
| 60 |
var options = []; |
| 61 |
Array.prototype.forEach.call(select.options, function (opt) { |
| 62 |
options.push({ |
| 63 |
value: opt.value, |
| 64 |
label: opt.textContent.trim(), |
| 65 |
isPlaceholder: opt.value === "", |
| 66 |
}); |
| 67 |
}); |
| 68 |
|
| 69 |
if (options.length === 0) return; |
| 70 |
|
| 71 |
var field = document.createElement("div"); |
| 72 |
field.className = "yatra-country-field"; |
| 73 |
|
| 74 |
var trigger = document.createElement("button"); |
| 75 |
trigger.type = "button"; |
| 76 |
trigger.className = "yatra-country-trigger"; |
| 77 |
trigger.setAttribute("aria-haspopup", "listbox"); |
| 78 |
trigger.setAttribute("aria-expanded", "false"); |
| 79 |
trigger.innerHTML = |
| 80 |
'<img class="yatra-country-flag" width="22" height="16" alt="" src="">' + |
| 81 |
'<span class="yatra-country-label"></span>' + |
| 82 |
'<svg class="yatra-country-caret" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>'; |
| 83 |
|
| 84 |
var flagImg = trigger.querySelector(".yatra-country-flag"); |
| 85 |
var labelEl = trigger.querySelector(".yatra-country-label"); |
| 86 |
|
| 87 |
var dropdown = null; |
| 88 |
var listEl = null; |
| 89 |
var searchEl = null; |
| 90 |
var emptyEl = null; |
| 91 |
|
| 92 |
function syncFromSelect() { |
| 93 |
var opt = select.options[select.selectedIndex]; |
| 94 |
var value = opt ? opt.value : ""; |
| 95 |
var label = opt ? opt.textContent.trim() : ""; |
| 96 |
|
| 97 |
labelEl.textContent = label; |
| 98 |
|
| 99 |
var url = value ? flagUrl(flagBase, value) : ""; |
| 100 |
if (url) { |
| 101 |
flagImg.setAttribute("src", url); |
| 102 |
flagImg.setAttribute("alt", label); |
| 103 |
flagImg.style.visibility = "visible"; |
| 104 |
} else { |
| 105 |
// Placeholder ("Select Country") has no flag to show. |
| 106 |
flagImg.removeAttribute("src"); |
| 107 |
flagImg.setAttribute("alt", ""); |
| 108 |
flagImg.style.visibility = "hidden"; |
| 109 |
} |
| 110 |
|
| 111 |
if (listEl) { |
| 112 |
listEl |
| 113 |
.querySelectorAll(".yatra-country-option") |
| 114 |
.forEach(function (li) { |
| 115 |
li.classList.toggle("is-active", li.getAttribute("data-value") === value); |
| 116 |
li.setAttribute( |
| 117 |
"aria-selected", |
| 118 |
li.getAttribute("data-value") === value ? "true" : "false", |
| 119 |
); |
| 120 |
}); |
| 121 |
} |
| 122 |
} |
| 123 |
|
| 124 |
function choose(value) { |
| 125 |
select.value = value; |
| 126 |
// Let anything already listening (validation, pricing, Pro modules) react |
| 127 |
// exactly as it would to a real user picking from the native control. |
| 128 |
select.dispatchEvent(new Event("change", { bubbles: true })); |
| 129 |
syncFromSelect(); |
| 130 |
} |
| 131 |
|
| 132 |
function filter(term) { |
| 133 |
var needle = String(term || "").trim().toLowerCase(); |
| 134 |
var visible = 0; |
| 135 |
|
| 136 |
listEl.querySelectorAll(".yatra-country-option").forEach(function (li) { |
| 137 |
var hay = li.getAttribute("data-search") || ""; |
| 138 |
var show = needle === "" || hay.indexOf(needle) !== -1; |
| 139 |
li.style.display = show ? "" : "none"; |
| 140 |
if (show) visible++; |
| 141 |
}); |
| 142 |
|
| 143 |
emptyEl.style.display = visible === 0 ? "" : "none"; |
| 144 |
} |
| 145 |
|
| 146 |
function buildDropdown() { |
| 147 |
if (dropdown) return; |
| 148 |
|
| 149 |
dropdown = document.createElement("div"); |
| 150 |
dropdown.className = "yatra-country-dropdown"; |
| 151 |
|
| 152 |
var searchWrap = document.createElement("div"); |
| 153 |
searchWrap.className = "yatra-country-search-wrap"; |
| 154 |
searchEl = document.createElement("input"); |
| 155 |
searchEl.type = "text"; |
| 156 |
searchEl.className = "yatra-country-search"; |
| 157 |
searchEl.setAttribute("placeholder", SEARCH_PLACEHOLDER); |
| 158 |
searchEl.setAttribute("aria-label", SEARCH_PLACEHOLDER); |
| 159 |
searchEl.setAttribute("autocomplete", "off"); |
| 160 |
searchWrap.appendChild(searchEl); |
| 161 |
|
| 162 |
listEl = document.createElement("ul"); |
| 163 |
listEl.className = "yatra-country-list"; |
| 164 |
listEl.setAttribute("role", "listbox"); |
| 165 |
|
| 166 |
options.forEach(function (o) { |
| 167 |
if (o.isPlaceholder) return; |
| 168 |
|
| 169 |
var li = document.createElement("li"); |
| 170 |
li.className = "yatra-country-option"; |
| 171 |
li.setAttribute("role", "option"); |
| 172 |
li.setAttribute("data-value", o.value); |
| 173 |
li.setAttribute( |
| 174 |
"data-search", |
| 175 |
(o.label + " " + o.value).toLowerCase(), |
| 176 |
); |
| 177 |
li.innerHTML = |
| 178 |
'<img class="yatra-country-flag" loading="lazy" width="22" height="16" alt="" src="' + |
| 179 |
flagUrl(flagBase, o.value) + |
| 180 |
'">' + |
| 181 |
'<span class="yatra-country-option-name"></span>'; |
| 182 |
li.querySelector(".yatra-country-option-name").textContent = o.label; |
| 183 |
|
| 184 |
li.addEventListener("click", function () { |
| 185 |
choose(o.value); |
| 186 |
close(); |
| 187 |
trigger.focus(); |
| 188 |
}); |
| 189 |
|
| 190 |
listEl.appendChild(li); |
| 191 |
}); |
| 192 |
|
| 193 |
emptyEl = document.createElement("div"); |
| 194 |
emptyEl.className = "yatra-country-empty"; |
| 195 |
emptyEl.textContent = NO_RESULTS; |
| 196 |
emptyEl.style.display = "none"; |
| 197 |
|
| 198 |
dropdown.appendChild(searchWrap); |
| 199 |
dropdown.appendChild(listEl); |
| 200 |
dropdown.appendChild(emptyEl); |
| 201 |
field.appendChild(dropdown); |
| 202 |
|
| 203 |
searchEl.addEventListener("input", function () { |
| 204 |
filter(searchEl.value); |
| 205 |
}); |
| 206 |
|
| 207 |
searchEl.addEventListener("keydown", function (e) { |
| 208 |
if (e.key === "Escape") { |
| 209 |
close(); |
| 210 |
trigger.focus(); |
| 211 |
} |
| 212 |
}); |
| 213 |
} |
| 214 |
|
| 215 |
function open() { |
| 216 |
buildDropdown(); |
| 217 |
closeAll(field); |
| 218 |
field.classList.add("is-open"); |
| 219 |
trigger.setAttribute("aria-expanded", "true"); |
| 220 |
syncFromSelect(); |
| 221 |
filter(""); |
| 222 |
searchEl.value = ""; |
| 223 |
searchEl.focus(); |
| 224 |
|
| 225 |
var active = listEl.querySelector(".yatra-country-option.is-active"); |
| 226 |
if (active && active.scrollIntoView) { |
| 227 |
active.scrollIntoView({ block: "nearest" }); |
| 228 |
} |
| 229 |
} |
| 230 |
|
| 231 |
function close() { |
| 232 |
field.classList.remove("is-open"); |
| 233 |
trigger.setAttribute("aria-expanded", "false"); |
| 234 |
} |
| 235 |
|
| 236 |
trigger.addEventListener("click", function (e) { |
| 237 |
e.preventDefault(); |
| 238 |
e.stopPropagation(); |
| 239 |
if (field.classList.contains("is-open")) { |
| 240 |
close(); |
| 241 |
} else { |
| 242 |
open(); |
| 243 |
} |
| 244 |
}); |
| 245 |
|
| 246 |
trigger.addEventListener("keydown", function (e) { |
| 247 |
if (e.key === "Escape") close(); |
| 248 |
}); |
| 249 |
|
| 250 |
// Keep the widget in step if other code sets the value programmatically |
| 251 |
// (e.g. a saved profile prefilling the checkout). |
| 252 |
select.addEventListener("change", syncFromSelect); |
| 253 |
|
| 254 |
// Wrap the original control: field takes the select's place, the select |
| 255 |
// moves inside it (hidden, still the value holder) and the trigger renders |
| 256 |
// in front of it. |
| 257 |
select.parentNode.insertBefore(field, select); |
| 258 |
field.appendChild(select); |
| 259 |
select.classList.add("yatra-country-native"); |
| 260 |
field.insertBefore(trigger, select); |
| 261 |
|
| 262 |
syncFromSelect(); |
| 263 |
} |
| 264 |
|
| 265 |
function initAll(root) { |
| 266 |
if (prefersNativePicker()) return; |
| 267 |
|
| 268 |
(root || document) |
| 269 |
.querySelectorAll("select[data-yatra-country-select]") |
| 270 |
.forEach(enhance); |
| 271 |
} |
| 272 |
|
| 273 |
document.addEventListener("click", function () { |
| 274 |
closeAll(null); |
| 275 |
}); |
| 276 |
|
| 277 |
document.addEventListener("keydown", function (e) { |
| 278 |
if (e.key === "Escape") closeAll(null); |
| 279 |
}); |
| 280 |
|
| 281 |
if (document.readyState === "loading") { |
| 282 |
document.addEventListener("DOMContentLoaded", function () { |
| 283 |
initAll(document); |
| 284 |
}); |
| 285 |
} else { |
| 286 |
initAll(document); |
| 287 |
} |
| 288 |
|
| 289 |
// Re-scan when fields arrive later (traveler rows, AJAX-rendered checkout). |
| 290 |
window.yatraInitCountrySelects = initAll; |
| 291 |
document.addEventListener("yatra:content-updated", function (e) { |
| 292 |
initAll((e && e.detail && e.detail.root) || document); |
| 293 |
}); |
| 294 |
})(); |
| 295 |
|