1/** 2 * src/public/js/country-chip-picker.js 3 * 4 * Shared 10-chip country multi-select. Used by BOTH: 5 * - setup.html step 4 (the SC1-deferred Targeting wizard step, landed SC5) 6 * - discover.html (the legacy-customer country prompt at top of page) 7 * 8 * One source of truth so the chip UX doesn't drift between the two sites. 9 * 10 * Usage: 11 * const picker = createCountryChipPicker({ 12 * mountEl: document.querySelector('#mountId'), 13 * defaults: ['us','gb'], // pre-selected (optional) 14 * onSubmit: async (countries) => { ... }, // called with array of ISO-2 codes 15 * submitLabel: 'Continue', // CTA button text 16 * }); 17 * // picker exposes: getSelected(), submit() (programmatic), destroy() 18 * 19 * No framework â vanilla JS + inline styles. Loaded via plain <script src=...>. 20 * Per CODING-STANDARDS: no build step, no bundler. 21 * 22 * Spec: docs/migration/phase-DISCOVERY-onboarding.md §4 + §8.2. 23 */ 24(function () { 25 // Top-10 default chips per spec §4.1. Order = popularity from the 26 // Replicants outreach data. ISO-3166-1 alpha-2 (lowercase by 27 // convention; the /set-countries endpoint lowercases anyway). 28 var DEFAULT_CHIPS = [ 29 { code: 'us', label: 'United States' }, 30 { code: 'gb', label: 'United Kingdom' }, 31 { code: 'au', label: 'Australia' }, 32 { code: 'ca', label: 'Canada' }, 33 { code: 'ie', label: 'Ireland' }, 34 { code: 'nz', label: 'New Zealand' }, 35 { code: 'in', label: 'India' }, 36 { code: 'de', label: 'Germany' }, 37 { code: 'nl', label: 'Netherlands' }, 38 { code: 'fr', label: 'France' }, 39 ]; 40 41 // SETUP-polish SC1 U2 â typeahead lookup. ISO-3166-1 alpha-2 (lowercase). 42 // ~250 entries inline; no external API. Used by the "Add another" 43 // typeahead to map typed country names â ISO codes. 44 var COUNTRY_LIST = [ 45 { code: 'af', name: 'Afghanistan' }, { code: 'al', name: 'Albania' }, { code: 'dz', name: 'Algeria' }, 46 { code: 'as', name: 'American Samoa' }, { code: 'ad', name: 'Andorra' }, { code: 'ao', name: 'Angola' }, 47 { code: 'ai', name: 'Anguilla' }, { code: 'aq', name: 'Antarctica' }, { code: 'ag', name: 'Antigua and Barbuda' }, 48 { code: 'ar', name: 'Argentina' }, { code: 'am', name: 'Armenia' }, { code: 'aw', name: 'Aruba' }, 49 { code: 'au', name: 'Australia' }, { code: 'at', name: 'Austria' }, { code: 'az', name: 'Azerbaijan' }, 50 { code: 'bs', name: 'Bahamas' }, { code: 'bh', name: 'Bahrain' }, { code: 'bd', name: 'Bangladesh' }, 51 { code: 'bb', name: 'Barbados' }, { code: 'by', name: 'Belarus' }, { code: 'be', name: 'Belgium' }, 52 { code: 'bz', name: 'Belize' }, { code: 'bj', name: 'Benin' }, { code: 'bm', name: 'Bermuda' }, 53 { code: 'bt', name: 'Bhutan' }, { code: 'bo', name: 'Bolivia' }, { code: 'ba', name: 'Bosnia and Herzegovina' }, 54 { code: 'bw', name: 'Botswana' }, { code: 'br', name: 'Brazil' }, { code: 'io', name: 'British Indian Ocean Territory' }, 55 { code: 'vg', name: 'British Virgin Islands' }, { code: 'bn', name: 'Brunei' }, { code: 'bg', name: 'Bulgaria' }, 56 { code: 'bf', name: 'Burkina Faso' }, { code: 'bi', name: 'Burundi' }, { code: 'kh', name: 'Cambodia' }, 57 { code: 'cm', name: 'Cameroon' }, { code: 'ca', name: 'Canada' }, { code: 'cv', name: 'Cape Verde' }, 58 { code: 'ky', name: 'Cayman Islands' }, { code: 'cf', name: 'Central African Republic' }, { code: 'td', name: 'Chad' }, 59 { code: 'cl', name: 'Chile' }, { code: 'cn', name: 'China' }, { code: 'cx', name: 'Christmas Island' }, 60 { code: 'cc', name: 'Cocos Islands' }, { code: 'co', name: 'Colombia' }, { code: 'km', name: 'Comoros' }, 61 { code: 'ck', name: 'Cook Islands' }, { code: 'cr', name: 'Costa Rica' }, { code: 'hr', name: 'Croatia' }, 62 { code: 'cu', name: 'Cuba' }, { code: 'cw', name: 'Curaçao' }, { code: 'cy', name: 'Cyprus' }, 63 { code: 'cz', name: 'Czech Republic' }, { code: 'cd', name: 'Democratic Republic of the Congo' }, 64 { code: 'dk', name: 'Denmark' }, { code: 'dj', name: 'Djibouti' }, { code: 'dm', name: 'Dominica' }, 65 { code: 'do', name: 'Dominican Republic' }, { code: 'tl', name: 'East Timor' }, { code: 'ec', name: 'Ecuador' }, 66 { code: 'eg', name: 'Egypt' }, { code: 'sv', name: 'El Salvador' }, { code: 'gq', name: 'Equatorial Guinea' }, 67 { code: 'er', name: 'Eritrea' }, { code: 'ee', name: 'Estonia' }, { code: 'et', name: 'Ethiopia' }, 68 { code: 'fk', name: 'Falkland Islands' }, { code: 'fo', name: 'Faroe Islands' }, { code: 'fj', name: 'Fiji' }, 69 { code: 'fi', name: 'Finland' }, { code: 'fr', name: 'France' }, { code: 'pf', name: 'French Polynesia' }, 70 { code: 'ga', name: 'Gabon' }, { code: 'gm', name: 'Gambia' }, { code: 'ge', name: 'Georgia' }, 71 { code: 'de', name: 'Germany' }, { code: 'gh', name: 'Ghana' }, { code: 'gi', name: 'Gibraltar' }, 72 { code: 'gr', name: 'Greece' }, { code: 'gl', name: 'Greenland' }, { code: 'gd', name: 'Grenada' }, 73 { code: 'gu', name: 'Guam' }, { code: 'gt', name: 'Guatemala' }, { code: 'gg', name: 'Guernsey' }, 74 { code: 'gn', name: 'Guinea' }, { code: 'gw', name: 'Guinea-Bissau' }, { code: 'gy', name: 'Guyana' }, 75 { code: 'ht', name: 'Haiti' }, { code: 'hn', name: 'Honduras' }, { code: 'hk', name: 'Hong Kong' }, 76 { code: 'hu', name: 'Hungary' }, { code: 'is', name: 'Iceland' }, { code: 'in', name: 'India' }, 77 { code: 'id', name: 'Indonesia' }, { code: 'ir', name: 'Iran' }, { code: 'iq', name: 'Iraq' }, 78 { code: 'ie', name: 'Ireland' }, { code: 'im', name: 'Isle of Man' }, { code: 'il', name: 'Israel' }, 79 { code: 'it', name: 'Italy' }, { code: 'ci', name: 'Ivory Coast' }, { code: 'jm', name: 'Jamaica' }, 80 { code: 'jp', name: 'Japan' }, { code: 'je', name: 'Jersey' }, { code: 'jo', name: 'Jordan' }, 81 { code: 'kz', name: 'Kazakhstan' }, { code: 'ke', name: 'Kenya' }, { code: 'ki', name: 'Kiribati' }, 82 { code: 'xk', name: 'Kosovo' }, { code: 'kw', name: 'Kuwait' }, { code: 'kg', name: 'Kyrgyzstan' }, 83 { code: 'la', name: 'Laos' }, { code: 'lv', name: 'Latvia' }, { code: 'lb', name: 'Lebanon' }, 84 { code: 'ls', name: 'Lesotho' }, { code: 'lr', name: 'Liberia' }, { code: 'ly', name: 'Libya' }, 85 { code: 'li', name: 'Liechtenstein' }, { code: 'lt', name: 'Lithuania' }, { code: 'lu', name: 'Luxembourg' }, 86 { code: 'mo', name: 'Macao' }, { code: 'mk', name: 'North Macedonia' }, { code: 'mg', name: 'Madagascar' }, 87 { code: 'mw', name: 'Malawi' }, { code: 'my', name: 'Malaysia' }, { code: 'mv', name: 'Maldives' }, 88 { code: 'ml', name: 'Mali' }, { code: 'mt', name: 'Malta' }, { code: 'mh', name: 'Marshall Islands' }, 89 { code: 'mr', name: 'Mauritania' }, { code: 'mu', name: 'Mauritius' }, { code: 'yt', name: 'Mayotte' }, 90 { code: 'mx', name: 'Mexico' }, { code: 'fm', name: 'Micronesia' }, { code: 'md', name: 'Moldova' }, 91 { code: 'mc', name: 'Monaco' }, { code: 'mn', name: 'Mongolia' }, { code: 'me', name: 'Montenegro' }, 92 { code: 'ms', name: 'Montserrat' }, { code: 'ma', name: 'Morocco' }, { code: 'mz', name: 'Mozambique' }, 93 { code: 'mm', name: 'Myanmar' }, { code: 'na', name: 'Namibia' }, { code: 'nr', name: 'Nauru' }, 94 { code: 'np', name: 'Nepal' }, { code: 'nl', name: 'Netherlands' }, { code: 'nc', name: 'New Caledonia' }, 95 { code: 'nz', name: 'New Zealand' }, { code: 'ni', name: 'Nicaragua' }, { code: 'ne', name: 'Niger' }, 96 { code: 'ng', name: 'Nigeria' }, { code: 'nu', name: 'Niue' }, { code: 'kp', name: 'North Korea' }, 97 { code: 'no', name: 'Norway' }, { code: 'om', name: 'Oman' }, { code: 'pk', name: 'Pakistan' }, 98 { code: 'pw', name: 'Palau' }, { code: 'ps', name: 'Palestine' }, { code: 'pa', name: 'Panama' }, 99 { code: 'pg', name: 'Papua New Guinea' }, { code: 'py', name: 'Paraguay' }, { code: 'pe', name: 'Peru' }, 100 { code: 'ph', name: 'Philippines' }, { code: 'pl', name: 'Poland' }, { code: 'pt', name: 'Portugal' }, 101 { code: 'pr', name: 'Puerto Rico' }, { code: 'qa', name: 'Qatar' }, { code: 'cg', name: 'Republic of the Congo' }, 102 { code: 're', name: 'Réunion' }, { code: 'ro', name: 'Romania' }, { code: 'ru', name: 'Russia' }, 103 { code: 'rw', name: 'Rwanda' }, { code: 'bl', name: 'Saint Barthélemy' }, { code: 'sh', name: 'Saint Helena' }, 104 { code: 'kn', name: 'Saint Kitts and Nevis' }, { code: 'lc', name: 'Saint Lucia' }, { code: 'mf', name: 'Saint Martin' }, 105 { code: 'pm', name: 'Saint Pierre and Miquelon' }, { code: 'vc', name: 'Saint Vincent and the Grenadines' }, 106 { code: 'ws', name: 'Samoa' }, { code: 'sm', name: 'San Marino' }, { code: 'st', name: 'São Tomé and PrÃncipe' }, 107 { code: 'sa', name: 'Saudi Arabia' }, { code: 'sn', name: 'Senegal' }, { code: 'rs', name: 'Serbia' }, 108 { code: 'sc', name: 'Seychelles' }, { code: 'sl', name: 'Sierra Leone' }, { code: 'sg', name: 'Singapore' }, 109 { code: 'sx', name: 'Sint Maarten' }, { code: 'sk', name: 'Slovakia' }, { code: 'si', name: 'Slovenia' }, 110 { code: 'sb', name: 'Solomon Islands' }, { code: 'so', name: 'Somalia' }, { code: 'za', name: 'South Africa' }, 111 { code: 'kr', name: 'South Korea' }, { code: 'ss', name: 'South Sudan' }, { code: 'es', name: 'Spain' }, 112 { code: 'lk', name: 'Sri Lanka' }, { code: 'sd', name: 'Sudan' }, { code: 'sr', name: 'Suriname' }, 113 { code: 'sz', name: 'Eswatini' }, { code: 'se', name: 'Sweden' }, { code: 'ch', name: 'Switzerland' }, 114 { code: 'sy', name: 'Syria' }, { code: 'tw', name: 'Taiwan' }, { code: 'tj', name: 'Tajikistan' }, 115 { code: 'tz', name: 'Tanzania' }, { code: 'th', name: 'Thailand' }, { code: 'tg', name: 'Togo' }, 116 { code: 'tk', name: 'Tokelau' }, { code: 'to', name: 'Tonga' }, { code: 'tt', name: 'Trinidad and Tobago' }, 117 { code: 'tn', name: 'Tunisia' }, { code: 'tr', name: 'Turkey' }, { code: 'tm', name: 'Turkmenistan' }, 118 { code: 'tc', name: 'Turks and Caicos Islands' }, { code: 'tv', name: 'Tuvalu' }, { code: 'ug', name: 'Uganda' }, 119 { code: 'ua', name: 'Ukraine' }, { code: 'ae', name: 'United Arab Emirates' }, { code: 'gb', name: 'United Kingdom' }, 120 { code: 'us', name: 'United States' }, { code: 'uy', name: 'Uruguay' }, { code: 'uz', name: 'Uzbekistan' }, 121 { code: 'vu', name: 'Vanuatu' }, { code: 'va', name: 'Vatican City' }, { code: 've', name: 'Venezuela' }, 122 { code: 'vn', name: 'Vietnam' }, { code: 'wf', name: 'Wallis and Futuna' }, { code: 'eh', name: 'Western Sahara' }, 123 { code: 'ye', name: 'Yemen' }, { code: 'zm', name: 'Zambia' }, { code: 'zw', name: 'Zimbabwe' }, 124 ]; 125 126 function createCountryChipPicker(opts) { 127 opts = opts || {};
128 if (!opts.mountEl) throw new Error('country-chip-picker: mountEl required'); 129 // hideSubmit: render chips only, no submit button + no onSubmit needed. 130 // Caller reads selection via getSelected() and submits on its own 131 // schedule (used by community-criteria-form.js embedded mode). 132 if (!opts.hideSubmit && typeof opts.onSubmit !== 'function') { 133 throw new Error('country-chip-picker: onSubmit required'); 134 } 135 136 var defaults = Array.isArray(opts.defaults) ? opts.defaults.map(function (s) { return String(s).toLowerCase(); }) : []; 137 var submitLabel = opts.submitLabel || 'Continue'; 138 var selected = new Set(defaults); 139 var customCodes = []; // user-added codes outside the default 10 140 141 // Pre-populate any default codes that aren't in DEFAULT_CHIPS â they 142 // become custom chips so the widget shows them as selected. 143 var defaultCodes = DEFAULT_CHIPS.map(function (c) { return c.code; }); 144 defaults.forEach(function (c) { 145 if (defaultCodes.indexOf(c) === -1 && customCodes.indexOf(c) === -1) { 146 customCodes.push(c); 147 } 148 }); 149 150 // Root container with scoped class for styling. Caller's mountEl 151 // typically has its own outer styling; the widget injects this 152 // inside. 153 var root = document.createElement('div'); 154 root.className = 'ccp-root'; 155 root.style.cssText = 'display:flex;flex-direction:column;gap:16px;'; 156 157 // Chips 158 var chipsRow = document.createElement('div'); 159 chipsRow.className = 'ccp-chips'; 160 chipsRow.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;'; 161 162 function makeChip(code, label) { 163 var btn = document.createElement('button'); 164 btn.type = 'button'; 165 btn.className = 'ccp-chip'; 166 btn.dataset.code = code; 167 btn.textContent = label; 168 btn.style.cssText = [ 169 'padding:8px 14px', 170 'border-radius:999px', 171 'border:1.5px solid #d4d4d8', 172 'background:#fff', 173 'color:#27272a', 174 'font-size:14px', 175 'font-weight:500', 176 'cursor:pointer', 177 'transition:all 0.15s ease', 178 ].join(';'); 179 function updateVisual() { 180 var on = selected.has(code); 181 btn.style.background = on ? '#6a4cf1' : '#fff'; 182 btn.style.borderColor = on ? '#6a4cf1' : '#d4d4d8'; 183 btn.style.color = on ? '#fff' : '#27272a'; 184 btn.setAttribute('aria-pressed', String(on)); 185 } 186 btn.addEventListener('click', function () { 187 if (selected.has(code)) selected.delete(code); 188 else selected.add(code); 189 updateVisual(); 190 _refreshSubmitButton(); 191 }); 192 updateVisual(); 193 return btn; 194 } 195 196 DEFAULT_CHIPS.forEach(function (c) { 197 chipsRow.appendChild(makeChip(c.code, c.label)); 198 }); 199 customCodes.forEach(function (c) { 200 chipsRow.appendChild(makeChip(c, c.toUpperCase())); 201 }); 202 203 // "+ Add another" row â SETUP-polish SC1 U2: typeahead. User types a 204 // country name (or partial); a dropdown shows matches from COUNTRY_LIST; 205 // clicking a suggestion adds the chip selected. Direct ISO-2 paste also 206 // still works (backward compat). 207 var addRow = document.createElement('div'); 208 addRow.style.cssText = 'display:flex;gap:8px;align-items:center;position:relative;'; 209 var addInput = document.createElement('input'); 210 addInput.type = 'text'; 211 addInput.placeholder = 'Add another country: type a nameâ¦'; 212 addInput.style.cssText = [ 213 'flex:1', 214 'padding:8px 12px', 215 'border:1.5px solid #d4d4d8', 216 'border-radius:8px', 217 'font-size:14px', 218 ].join(';'); 219 var addBtn = document.createElement('button'); 220 addBtn.type = 'button'; 221 addBtn.textContent = '+ Add'; 222 addBtn.style.cssText = [ 223 'padding:8px 14px', 224 'border-radius:8px', 225 'border:1.5px solid #6a4cf1', 226 'background:#fff', 227 'color:#6a4cf1', 228 'font-size:14px', 229 'font-weight:600', 230 'cursor:pointer', 231 ].join(';'); 232 233 // Typeahead dropdown (positioned below the input). 234 var dropdown = document.createElement('div'); 235 dropdown.style.cssText = [ 236 'position:absolute', 237 'top:100%', 238 'left:0', 239 'right:60px', // leave room for the + Add button 240 'margin-top:4px', 241 'max-height:220px', 242 'overflow-y:auto', 243 'background:#fff', 244 'border:1.5px solid #d4d4d8', 245 'border-radius:8px', 246 'box-shadow:0 6px 20px -8px rgba(0,0,0,0.15)', 247 'z-index:50', 248 'display:none', 249 ].join(';'); 250 251 function _addCode(v) { 252 // Add the lowercased ISO code v as a selected chip. Idempotent. 253 if (defaultCodes.indexOf(v) !== -1 || customCodes.indexOf(v) !== -1) { 254 selected.add(v); 255 chipsRow.querySelectorAll('.ccp-chip').forEach(function (c) { 256 if (c.dataset.code === v) { 257 c.style.background = '#6a4cf1'; 258 c.style.borderColor = '#6a4cf1'; 259 c.style.color = '#fff'; 260 c.setAttribute('aria-pressed', 'true'); 261 } 262 }); 263 } else { 264 customCodes.push(v); 265 selected.add(v); 266 var chip = makeChip(v, v.toUpperCase()); 267 chipsRow.appendChild(chip); 268 } 269 addInput.value = ''; 270 dropdown.style.display = 'none'; 271 _refreshSubmitButton(); 272 } 273 274 function _renderDropdown(query) { 275 var q = (query || '').trim().toLowerCase(); 276 dropdown.innerHTML = ''; 277 if (q.length < 1) { dropdown.style.display = 'none'; return; } 278 // Direct ISO-2 shortcut: typed exactly 2 letters that match a country code. 279 if (/^[a-z]{2}$/.test(q)) { 280 var direct = COUNTRY_LIST.filter(function (c) { return c.code === q; }); 281 if (direct.length === 1) { 282 _renderRow(dropdown, direct[0]); 283 dropdown.style.display = 'block'; 284 return; 285 } 286 } 287 var matches = COUNTRY_LIST.filter(function (c) { 288 return c.name.toLowerCase().indexOf(q) !== -1 || c.code.indexOf(q) === 0; 289 }).slice(0, 8); 290 if (matches.length === 0) { dropdown.style.display = 'none'; return; }
291 matches.forEach(function (m) { _renderRow(dropdown, m); }); 292 dropdown.style.display = 'block'; 293 } 294 295 function _renderRow(parent, m) { 296 var row = document.createElement('div'); 297 row.style.cssText = 'padding:8px 12px;cursor:pointer;font-size:14px;color:#27272a;display:flex;justify-content:space-between;align-items:center;'; 298 row.addEventListener('mouseenter', function () { row.style.background = '#f4f4f5'; }); 299 row.addEventListener('mouseleave', function () { row.style.background = '#fff'; }); 300 var name = document.createElement('span'); 301 name.textContent = m.name; 302 var code = document.createElement('span'); 303 code.textContent = m.code.toUpperCase(); 304 code.style.cssText = 'font-family:"JetBrains Mono",monospace;font-size:12px;color:#a1a1aa;'; 305 row.appendChild(name); 306 row.appendChild(code); 307 row.addEventListener('click', function () { _addCode(m.code); }); 308 parent.appendChild(row); 309 } 310 311 function tryAdd() { 312 var v = (addInput.value || '').trim().toLowerCase(); 313 // Try direct ISO-2 code first (backward compat). 314 if (/^[a-z]{2}$/.test(v) && COUNTRY_LIST.some(function (c) { return c.code === v; })) { 315 _addCode(v); 316 return; 317 } 318 // Otherwise pick the first dropdown match. 319 var firstMatch = COUNTRY_LIST.filter(function (c) { 320 return c.name.toLowerCase().indexOf(v) !== -1; 321 })[0]; 322 if (firstMatch) { _addCode(firstMatch.code); return; } 323 addInput.style.borderColor = '#dc2626'; 324 setTimeout(function () { addInput.style.borderColor = '#d4d4d8'; }, 1500); 325 } 326 addInput.addEventListener('input', function () { _renderDropdown(addInput.value); }); 327 addInput.addEventListener('focus', function () { _renderDropdown(addInput.value); }); 328 addInput.addEventListener('blur', function () { 329 // Delay so a click on a dropdown row registers before hide. 330 setTimeout(function () { dropdown.style.display = 'none'; }, 200); 331 }); 332 addBtn.addEventListener('click', tryAdd); 333 addInput.addEventListener('keydown', function (e) { 334 if (e.key === 'Enter') { e.preventDefault(); tryAdd(); } 335 }); 336 addRow.appendChild(addInput); 337 addRow.appendChild(addBtn); 338 addRow.appendChild(dropdown); 339 340 // Submit button (omitted in hideSubmit mode) 341 var submitBtn = null; 342 function _refreshSubmitButton() { 343 if (!submitBtn) return; 344 var n = selected.size; 345 submitBtn.disabled = n < 1; 346 submitBtn.style.opacity = n < 1 ? '0.5' : '1'; 347 submitBtn.style.cursor = n < 1 ? 'not-allowed' : 'pointer'; 348 } 349 if (!opts.hideSubmit) { 350 submitBtn = document.createElement('button'); 351 submitBtn.type = 'button'; 352 submitBtn.textContent = submitLabel; 353 submitBtn.style.cssText = [ 354 'align-self:flex-start', 355 'padding:10px 20px', 356 'border-radius:10px', 357 'border:none', 358 'background:#6a4cf1', 359 'color:#fff', 360 'font-size:15px', 361 'font-weight:600', 362 'cursor:pointer', 363 'transition:opacity 0.15s ease', 364 ].join(';'); 365 _refreshSubmitButton(); 366 var submitting = false; 367 submitBtn.addEventListener('click', function () { 368 if (submitting || selected.size < 1) return; 369 submitting = true; 370 submitBtn.disabled = true; 371 submitBtn.style.opacity = '0.6'; 372 var prevLabel = submitBtn.textContent; 373 submitBtn.textContent = 'Savingâ¦'; 374 Promise.resolve(opts.onSubmit(Array.from(selected))) 375 .catch(function (err) { 376 console.warn('[country-chip-picker] onSubmit threw â', err && err.message); 377 submitting = false; 378 submitBtn.disabled = false; 379 submitBtn.style.opacity = '1'; 380 submitBtn.textContent = prevLabel; 381 }); 382 // On success the caller is responsible for any post-submit UX (e.g. 383 // advancing the wizard or replacing the prompt with a banner). 384 }); 385 } 386 387 root.appendChild(chipsRow); 388 root.appendChild(addRow); 389 if (submitBtn) root.appendChild(submitBtn); 390 opts.mountEl.appendChild(root); 391 392 return { 393 getSelected: function () { return Array.from(selected); }, 394 submit: function () { if (submitBtn) submitBtn.click(); }, 395 destroy: function () { if (root.parentNode) root.parentNode.removeChild(root); }, 396 }; 397 } 398 399 // Expose on window for both setup.html and discover.html to use. 400 window.createCountryChipPicker = createCountryChipPicker; 401 402 // ââ Name â ISO-2 helpers (CI onboarding prefill) ââââââââââââââââââââââ 403 // CI personas store their target countries as free text in a single
404 // field (migrated from Bubble). To pre-select the chip picker we need to 405 // map those names to ISO-2 codes. COUNTRY_LIST covers canonical names; 406 // this alias map covers the common shorthands / spellings seen in the 407 // live CI data (UAE, KSA, Bahrein, Türkiye, â¦). Region words and city 408 // names that don't resolve to a single country are intentionally skipped. 409 var COUNTRY_ALIASES = { 410 'uae': 'ae', 'u.a.e': 'ae', 'u.a.e.': 'ae', 'emirates': 'ae', 411 'ksa': 'sa', 'kingdom of saudi arabia': 'sa', 'saudi': 'sa', 'saudi arabia ': 'sa', 412 'bahrein': 'bh', 413 'türkiye': 'tr', 'turkiye': 'tr', 414 'uk': 'gb', 'u.k': 'gb', 'u.k.': 'gb', 'great britain': 'gb', 'england': 'gb', 415 'usa': 'us', 'u.s.a': 'us', 'u.s.a.': 'us', 'u.s': 'us', 'us': 'us', 'america': 'us', 416 'south korea ': 'kr', 'korea': 'kr', 417 'czechia': 'cz', 418 }; 419 420 function countryNameToCode(raw) { 421 var n = String(raw || '').trim().toLowerCase().replace(/\.$/, ''); 422 if (!n) return null; 423 if (/^[a-z]{2}$/.test(n) && COUNTRY_LIST.some(function (c) { return c.code === n; })) return n; 424 if (COUNTRY_ALIASES[n]) return COUNTRY_ALIASES[n]; 425 var exact = COUNTRY_LIST.filter(function (c) { return c.name.toLowerCase() === n; })[0]; 426 if (exact) return exact.code; 427 var starts = COUNTRY_LIST.filter(function (c) { return c.name.toLowerCase().indexOf(n) === 0 && n.length >= 4; })[0]; 428 if (starts) return starts.code; 429 return null; 430 } 431 432 // Parse a free-text list of countries ("Saudi Arabia, Australia, UAE, 433 // Bahrein,") into a de-duped array of ISO-2 codes. Splits on commas, 434 // slashes and semicolons. Unresolvable tokens (regions like "Middle 435 // East", cities like "Copenhagen") are dropped silently. 436 function parseCountriesToCodes(text) { 437 var out = []; 438 String(text || '').split(/[,/;]+/).forEach(function (tok) { 439 var code = countryNameToCode(tok); 440 if (code && out.indexOf(code) === -1) out.push(code); 441 }); 442 return out; 443 } 444 445 window.countryNameToCode = countryNameToCode; 446 window.parseCountriesToCodes = parseCountriesToCodes; 447})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.