@countriesdb/widget 0.1.23 → 0.1.25

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.
package/dist/index.js CHANGED
@@ -400,8 +400,6 @@
400
400
  else {
401
401
  select.innerHTML += `<option value="${defaultValue}" disabled>${formattedMessage}</option>`;
402
402
  }
403
- // Ensure select is enabled so users can see the error
404
- select.disabled = false;
405
403
  }
406
404
 
407
405
  /**
@@ -435,6 +433,34 @@
435
433
  });
436
434
  select.dispatchEvent(evt);
437
435
  }
436
+ /**
437
+ * Dispatch a custom ready event once a select has been populated.
438
+ */
439
+ function dispatchReadyEvent(select, detail = {}) {
440
+ const selectedValues = select.multiple
441
+ ? Array.from(select.selectedOptions || [])
442
+ .map((opt) => opt.value)
443
+ .filter((v) => v !== '')
444
+ : select.value
445
+ ? [select.value]
446
+ : [];
447
+ const evt = new CustomEvent('countriesWidget:ready', {
448
+ bubbles: true,
449
+ detail: {
450
+ value: select.value || '',
451
+ selectedValues,
452
+ name: select.dataset.name || null,
453
+ country: select.dataset.country || null,
454
+ isSubdivision: select.classList.contains('subdivision-selection'),
455
+ type: select.classList.contains('subdivision-selection')
456
+ ? 'subdivision'
457
+ : 'country',
458
+ phase: 'initial',
459
+ ...detail,
460
+ },
461
+ });
462
+ select.dispatchEvent(evt);
463
+ }
438
464
  /**
439
465
  * Check if an event was initiated by the widget (not user)
440
466
  */
@@ -711,7 +737,8 @@
711
737
  : null;
712
738
  // Check if linked country select is multi-select (not allowed)
713
739
  if (linkedCountrySelect && linkedCountrySelect.hasAttribute('multiple')) {
714
- handleApiError(select, 'Cannot link to multi-select country. Use data-country-code instead.', true);
740
+ const defaultValue = select.dataset.defaultValue ?? '';
741
+ select.innerHTML = `<option value="${defaultValue}" disabled>Error: Cannot link to multi-select country. Use data-country-code instead.</option>`;
715
742
  continue;
716
743
  }
717
744
  // No direct link → maybe data-country-code
@@ -721,7 +748,8 @@
721
748
  await updateSubdivisionSelect(select, apiKey, backendUrl, state, config, select.dataset.countryCode);
722
749
  }
723
750
  else {
724
- handleApiError(select, 'No country select present');
751
+ const defaultValue = select.dataset.defaultValue ?? '';
752
+ select.innerHTML += `<option value="${defaultValue}" disabled>Error: No country select present</option>`;
725
753
  }
726
754
  }
727
755
  // Always dispatch an update event for user-initiated subdivision changes
@@ -767,8 +795,10 @@
767
795
  // Use GeoIP only if data-preselected attribute is not set at all
768
796
  const shouldUseGeoIP = preselectedValue === undefined || preselectedValue === null;
769
797
  // Check if this subdivision select prefers official subdivisions
770
- const preferOfficial = select.hasAttribute('data-prefer-official') ||
771
- config.preferOfficialSubdivisions;
798
+ // Use data attribute if present, otherwise use config
799
+ const preferOfficial = select.hasAttribute('data-prefer-official')
800
+ ? true
801
+ : config.preferOfficialSubdivisions;
772
802
  const languageHeaders = CountriesDBClient.getLanguageHeaders(config.forcedLanguage, config.defaultLanguage);
773
803
  const subdivisionsResult = await CountriesDBClient.fetchSubdivisions({
774
804
  apiKey,
@@ -854,6 +884,10 @@
854
884
  finally {
855
885
  // Mark initialization as complete
856
886
  state.isInitializing.delete(select);
887
+ dispatchReadyEvent(select, {
888
+ type: 'subdivision',
889
+ phase: isReload ? 'reload' : 'initial',
890
+ });
857
891
  // Only fire 'reload' if this is a reload, not initial load
858
892
  if (isReload && !valueSetByWidget) {
859
893
  dispatchUpdateEvent(select, {
@@ -865,7 +899,8 @@
865
899
  }
866
900
  else if (!select.dataset.country ||
867
901
  !document.querySelector(`.country-selection[data-name="${select.dataset.country}"]`)) {
868
- handleApiError(select, 'No country select present');
902
+ const defaultValue = select.dataset.defaultValue ?? '';
903
+ select.innerHTML += `<option value="${defaultValue}" disabled>Error: No country select present</option>`;
869
904
  }
870
905
  }
871
906
  /**
@@ -894,7 +929,8 @@
894
929
  if (name && seenNames[name]) {
895
930
  select.removeAttribute('data-name');
896
931
  initializeSelect(select, '&mdash;');
897
- handleApiError(select, 'Duplicate field');
932
+ const defaultValue = select.dataset.defaultValue ?? '';
933
+ select.innerHTML += `<option value="${defaultValue}" disabled>Error: Duplicate field</option>`;
898
934
  continue;
899
935
  }
900
936
  if (name) {
@@ -995,6 +1031,10 @@
995
1031
  finally {
996
1032
  // Mark initialization as complete
997
1033
  state.isInitializing.delete(select);
1034
+ dispatchReadyEvent(select, {
1035
+ type: 'country',
1036
+ phase: 'initial',
1037
+ });
998
1038
  // If no preselected and no geoip selection happened, emit a regular update
999
1039
  if (!valueSetByWidget) {
1000
1040
  dispatchUpdateEvent(select, { type: 'country', reason: 'regular' });
@@ -1113,14 +1153,12 @@
1113
1153
  // But only if it matches the widget pattern (not a bundled file)
1114
1154
  if (document.currentScript && document.currentScript instanceof HTMLScriptElement) {
1115
1155
  const src = document.currentScript.src;
1116
- if (src && (src.includes('@countriesdb/widget') ||
1117
- src.includes('widget/dist/index.js'))) {
1156
+ if (src && (src.includes('@countriesdb/widget') || src.includes('widget/dist/index.js'))) {
1118
1157
  loaderScript = document.currentScript;
1119
1158
  }
1120
1159
  }
1121
1160
  // If currentScript didn't match, search for widget script
1122
1161
  if (!loaderScript) {
1123
- // Only consider script tags that loaded the widget bundle
1124
1162
  const scripts = Array.from(document.getElementsByTagName('script'));
1125
1163
  loaderScript = scripts.find((s) => s.src && (s.src.includes('@countriesdb/widget') ||
1126
1164
  s.src.includes('widget/dist/index.js'))) || null;
@@ -1163,11 +1201,6 @@
1163
1201
  : globalConfig?.isoCountryNames !== undefined
1164
1202
  ? globalConfig.isoCountryNames
1165
1203
  : parseBoolean(scriptUrl?.searchParams.get('iso_country_names') ?? 'false'),
1166
- preferOfficialSubdivisions: options.preferOfficialSubdivisions !== undefined
1167
- ? options.preferOfficialSubdivisions
1168
- : globalConfig?.preferOfficialSubdivisions !== undefined
1169
- ? globalConfig.preferOfficialSubdivisions
1170
- : parseBoolean(scriptUrl?.searchParams.get('prefer_official') ?? 'false'),
1171
1204
  subdivisionRomanizationPreference: options.subdivisionRomanizationPreference ||
1172
1205
  globalConfig?.subdivisionRomanizationPreference ||
1173
1206
  scriptUrl?.searchParams.get('subdivision_romanization_preference') ||
@@ -1177,6 +1210,11 @@
1177
1210
  : globalConfig?.preferLocalVariant !== undefined
1178
1211
  ? globalConfig.preferLocalVariant
1179
1212
  : parseBoolean(scriptUrl?.searchParams.get('prefer_local_variant') ?? 'false'),
1213
+ preferOfficialSubdivisions: options.preferOfficialSubdivisions !== undefined
1214
+ ? options.preferOfficialSubdivisions
1215
+ : globalConfig?.preferOfficialSubdivisions !== undefined
1216
+ ? globalConfig.preferOfficialSubdivisions
1217
+ : parseBoolean(scriptUrl?.searchParams.get('prefer_official') ?? 'false'),
1180
1218
  countryNameFilter: options.countryNameFilter ?? globalConfig?.countryNameFilter,
1181
1219
  subdivisionNameFilter: options.subdivisionNameFilter ?? globalConfig?.subdivisionNameFilter,
1182
1220
  autoInit: options.autoInit !== undefined
@@ -1214,8 +1252,7 @@
1214
1252
  // But only if it matches the widget pattern (not a bundled file)
1215
1253
  if (document.currentScript && document.currentScript instanceof HTMLScriptElement) {
1216
1254
  const src = document.currentScript.src;
1217
- if (src && (src.includes('@countriesdb/widget') ||
1218
- src.includes('widget/dist/index.js'))) {
1255
+ if (src && (src.includes('@countriesdb/widget') || src.includes('widget/dist/index.js'))) {
1219
1256
  loaderScript = document.currentScript;
1220
1257
  }
1221
1258
  }
@@ -1283,26 +1320,19 @@
1283
1320
  * Show error when both follow_related and follow_upward are enabled
1284
1321
  */
1285
1322
  function showParamConflictError() {
1286
- const errorMessage = 'Cannot enable both follow_related and follow_upward';
1323
+ const errorMessage = 'Error: Cannot enable both follow_related and follow_upward';
1287
1324
  const countrySelects = Array.from(document.querySelectorAll('.country-selection'));
1288
1325
  for (const select of countrySelects) {
1289
- handleApiError(select, errorMessage, true);
1326
+ select.innerHTML = `<option value="${select.dataset.defaultValue ?? ''}" disabled>${errorMessage}</option>`;
1290
1327
  }
1291
1328
  const subdivisionSelects = Array.from(document.querySelectorAll('.subdivision-selection'));
1292
1329
  for (const select of subdivisionSelects) {
1293
- handleApiError(select, errorMessage, true);
1330
+ select.innerHTML = `<option value="${select.dataset.defaultValue ?? ''}" disabled>${errorMessage}</option>`;
1294
1331
  }
1295
1332
  }
1296
1333
  // Expose public loader
1297
1334
  if (typeof window !== 'undefined') {
1298
1335
  window.CountriesWidgetLoad = CountriesWidgetLoad;
1299
- // Dispatch ready event to notify consumers that the widget is available
1300
- // Use setTimeout(0) to ensure it fires after any synchronous code completes
1301
- setTimeout(() => {
1302
- if (typeof window !== 'undefined') {
1303
- window.dispatchEvent(new CustomEvent('countriesWidget:ready'));
1304
- }
1305
- }, 0);
1306
1336
  // Auto-init if script URL has auto_init=true (or not set, default is true)
1307
1337
  // Use a function that waits for DOM to be ready and finds the script tag reliably
1308
1338
  (function checkAutoInit() {
@@ -1317,14 +1347,12 @@
1317
1347
  // But only if it matches the widget pattern (not a bundled file)
1318
1348
  if (document.currentScript && document.currentScript instanceof HTMLScriptElement) {
1319
1349
  const src = document.currentScript.src;
1320
- if (src && (src.includes('@countriesdb/widget') ||
1321
- src.includes('widget/dist/index.js'))) {
1350
+ if (src && (src.includes('@countriesdb/widget') || src.includes('widget/dist/index.js'))) {
1322
1351
  loaderScript = document.currentScript;
1323
1352
  }
1324
1353
  }
1325
1354
  // If currentScript didn't match, search for widget script
1326
1355
  if (!loaderScript) {
1327
- // Fallback: find script tag with @countriesdb/widget in src
1328
1356
  const scripts = Array.from(document.getElementsByTagName('script'));
1329
1357
  loaderScript = scripts.find((s) => s.src && (s.src.includes('@countriesdb/widget') ||
1330
1358
  s.src.includes('widget/dist/index.js'))) || null;