@countriesdb/widget 0.1.22 → 0.1.24

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' });
@@ -1101,29 +1141,23 @@
1101
1141
  * Get configuration from options or script URL parameters
1102
1142
  */
1103
1143
  function getConfigFromOptionsOrScript(options) {
1104
- // Check for global config first (for bundled widgets that need config before auto-init)
1105
- const globalConfig = typeof window !== 'undefined' && window.CountriesDBConfig
1106
- ? window.CountriesDBConfig
1107
- : null;
1108
1144
  // Try to get config from script URL (for backward compatibility with widget.blade.php)
1109
1145
  let scriptUrl = null;
1110
1146
  try {
1111
1147
  let loaderScript = null;
1112
1148
  // First try document.currentScript (works during script execution)
1113
- // But only if it matches the widget pattern (not a bundled file)
1114
1149
  if (document.currentScript && document.currentScript instanceof HTMLScriptElement) {
1115
- const src = document.currentScript.src;
1116
- if (src && (src.includes('@countriesdb/widget') ||
1117
- src.includes('widget/dist/index.js'))) {
1118
- loaderScript = document.currentScript;
1119
- }
1150
+ loaderScript = document.currentScript;
1120
1151
  }
1121
- // If currentScript didn't match, search for widget script
1122
- if (!loaderScript) {
1123
- // Only consider script tags that loaded the widget bundle
1152
+ else {
1153
+ // Fallback: find script tag with @countriesdb/widget in src
1124
1154
  const scripts = Array.from(document.getElementsByTagName('script'));
1125
1155
  loaderScript = scripts.find((s) => s.src && (s.src.includes('@countriesdb/widget') ||
1126
1156
  s.src.includes('widget/dist/index.js'))) || null;
1157
+ // If still not found, try the last script with src
1158
+ if (!loaderScript) {
1159
+ loaderScript = scripts.filter((s) => s.src).pop() || null;
1160
+ }
1127
1161
  }
1128
1162
  if (loaderScript && loaderScript.src) {
1129
1163
  scriptUrl = new URL(loaderScript.src);
@@ -1133,57 +1167,39 @@
1133
1167
  // Ignore errors
1134
1168
  }
1135
1169
  const config = {
1136
- // Priority: options > globalConfig > scriptUrl params > defaults
1137
- publicKey: options.publicKey ?? globalConfig?.publicKey ?? scriptUrl?.searchParams.get('public_key') ?? '',
1138
- backendUrl: options.backendUrl ?? globalConfig?.backendUrl ?? scriptUrl?.searchParams.get('backend_url') ?? getBackendUrlFromScript(),
1139
- defaultLanguage: options.defaultLanguage ?? globalConfig?.defaultLanguage ?? scriptUrl?.searchParams.get('default_language') ?? undefined,
1140
- forcedLanguage: options.forcedLanguage ?? globalConfig?.forcedLanguage ?? scriptUrl?.searchParams.get('forced_language') ?? undefined,
1170
+ publicKey: options.publicKey || scriptUrl?.searchParams.get('public_key') || '',
1171
+ backendUrl: options.backendUrl || scriptUrl?.searchParams.get('backend_url') || getBackendUrlFromScript(),
1172
+ defaultLanguage: options.defaultLanguage || scriptUrl?.searchParams.get('default_language') || undefined,
1173
+ forcedLanguage: options.forcedLanguage || scriptUrl?.searchParams.get('forced_language') || undefined,
1141
1174
  showSubdivisionType: options.showSubdivisionType !== undefined
1142
1175
  ? options.showSubdivisionType
1143
- : globalConfig?.showSubdivisionType !== undefined
1144
- ? globalConfig.showSubdivisionType
1145
- : parseBoolean(scriptUrl?.searchParams.get('show_subdivision_type') ?? '1'),
1176
+ : parseBoolean(scriptUrl?.searchParams.get('show_subdivision_type') ?? '1'),
1146
1177
  followRelated: options.followRelated !== undefined
1147
1178
  ? options.followRelated
1148
- : globalConfig?.followRelated !== undefined
1149
- ? globalConfig.followRelated
1150
- : parseBoolean(scriptUrl?.searchParams.get('follow_related') ?? 'false'),
1179
+ : parseBoolean(scriptUrl?.searchParams.get('follow_related') ?? 'false'),
1151
1180
  followUpward: options.followUpward !== undefined
1152
1181
  ? options.followUpward
1153
- : globalConfig?.followUpward !== undefined
1154
- ? globalConfig.followUpward
1155
- : parseBoolean(scriptUrl?.searchParams.get('follow_upward') ?? 'false'),
1182
+ : parseBoolean(scriptUrl?.searchParams.get('follow_upward') ?? 'false'),
1156
1183
  allowParentSelection: options.allowParentSelection !== undefined
1157
1184
  ? options.allowParentSelection
1158
- : globalConfig?.allowParentSelection !== undefined
1159
- ? globalConfig.allowParentSelection
1160
- : parseBoolean(scriptUrl?.searchParams.get('allow_parent_selection') ?? 'false'),
1185
+ : parseBoolean(scriptUrl?.searchParams.get('allow_parent_selection') ?? 'false'),
1161
1186
  isoCountryNames: options.isoCountryNames !== undefined
1162
1187
  ? options.isoCountryNames
1163
- : globalConfig?.isoCountryNames !== undefined
1164
- ? globalConfig.isoCountryNames
1165
- : 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'),
1188
+ : parseBoolean(scriptUrl?.searchParams.get('iso_country_names') ?? 'false'),
1171
1189
  subdivisionRomanizationPreference: options.subdivisionRomanizationPreference ||
1172
- globalConfig?.subdivisionRomanizationPreference ||
1173
1190
  scriptUrl?.searchParams.get('subdivision_romanization_preference') ||
1174
1191
  undefined,
1175
1192
  preferLocalVariant: options.preferLocalVariant !== undefined
1176
1193
  ? options.preferLocalVariant
1177
- : globalConfig?.preferLocalVariant !== undefined
1178
- ? globalConfig.preferLocalVariant
1179
- : parseBoolean(scriptUrl?.searchParams.get('prefer_local_variant') ?? 'false'),
1180
- countryNameFilter: options.countryNameFilter ?? globalConfig?.countryNameFilter,
1181
- subdivisionNameFilter: options.subdivisionNameFilter ?? globalConfig?.subdivisionNameFilter,
1194
+ : parseBoolean(scriptUrl?.searchParams.get('prefer_local_variant') ?? 'false'),
1195
+ preferOfficialSubdivisions: options.preferOfficialSubdivisions !== undefined
1196
+ ? options.preferOfficialSubdivisions
1197
+ : parseBoolean(scriptUrl?.searchParams.get('prefer_official') ?? 'false'),
1198
+ countryNameFilter: options.countryNameFilter,
1199
+ subdivisionNameFilter: options.subdivisionNameFilter,
1182
1200
  autoInit: options.autoInit !== undefined
1183
1201
  ? options.autoInit
1184
- : globalConfig?.autoInit !== undefined
1185
- ? globalConfig.autoInit
1186
- : parseBoolean(scriptUrl?.searchParams.get('auto_init') ?? 'true'),
1202
+ : parseBoolean(scriptUrl?.searchParams.get('auto_init') ?? 'true'),
1187
1203
  };
1188
1204
  // Resolve filter functions from global scope if specified by name
1189
1205
  if (scriptUrl) {
@@ -1211,20 +1227,18 @@
1211
1227
  try {
1212
1228
  let loaderScript = null;
1213
1229
  // First try document.currentScript (works during script execution)
1214
- // But only if it matches the widget pattern (not a bundled file)
1215
1230
  if (document.currentScript && document.currentScript instanceof HTMLScriptElement) {
1216
- const src = document.currentScript.src;
1217
- if (src && (src.includes('@countriesdb/widget') ||
1218
- src.includes('widget/dist/index.js'))) {
1219
- loaderScript = document.currentScript;
1220
- }
1231
+ loaderScript = document.currentScript;
1221
1232
  }
1222
- // If currentScript didn't match, search for widget script
1223
- if (!loaderScript) {
1224
- // Only consider script tags that loaded the widget bundle
1233
+ else {
1234
+ // Fallback: find script tag with @countriesdb/widget in src
1225
1235
  const scripts = Array.from(document.getElementsByTagName('script'));
1226
1236
  loaderScript = scripts.find((s) => s.src && (s.src.includes('@countriesdb/widget') ||
1227
1237
  s.src.includes('widget/dist/index.js'))) || null;
1238
+ // If still not found, try the last script with src
1239
+ if (!loaderScript) {
1240
+ loaderScript = scripts.filter((s) => s.src).pop() || null;
1241
+ }
1228
1242
  }
1229
1243
  if (loaderScript && loaderScript.src) {
1230
1244
  const scriptUrl = new URL(loaderScript.src);
@@ -1283,14 +1297,14 @@
1283
1297
  * Show error when both follow_related and follow_upward are enabled
1284
1298
  */
1285
1299
  function showParamConflictError() {
1286
- const errorMessage = 'Cannot enable both follow_related and follow_upward';
1300
+ const errorMessage = 'Error: Cannot enable both follow_related and follow_upward';
1287
1301
  const countrySelects = Array.from(document.querySelectorAll('.country-selection'));
1288
1302
  for (const select of countrySelects) {
1289
- handleApiError(select, errorMessage, true);
1303
+ select.innerHTML = `<option value="${select.dataset.defaultValue ?? ''}" disabled>${errorMessage}</option>`;
1290
1304
  }
1291
1305
  const subdivisionSelects = Array.from(document.querySelectorAll('.subdivision-selection'));
1292
1306
  for (const select of subdivisionSelects) {
1293
- handleApiError(select, errorMessage, true);
1307
+ select.innerHTML = `<option value="${select.dataset.defaultValue ?? ''}" disabled>${errorMessage}</option>`;
1294
1308
  }
1295
1309
  }
1296
1310
  // Expose public loader
@@ -1307,16 +1321,10 @@
1307
1321
  // Find the script tag that loaded this widget
1308
1322
  let loaderScript = null;
1309
1323
  // First try document.currentScript (works during script execution)
1310
- // But only if it matches the widget pattern (not a bundled file)
1311
1324
  if (document.currentScript && document.currentScript instanceof HTMLScriptElement) {
1312
- const src = document.currentScript.src;
1313
- if (src && (src.includes('@countriesdb/widget') ||
1314
- src.includes('widget/dist/index.js'))) {
1315
- loaderScript = document.currentScript;
1316
- }
1325
+ loaderScript = document.currentScript;
1317
1326
  }
1318
- // If currentScript didn't match, search for widget script
1319
- if (!loaderScript) {
1327
+ else {
1320
1328
  // Fallback: find script tag with @countriesdb/widget in src
1321
1329
  const scripts = Array.from(document.getElementsByTagName('script'));
1322
1330
  loaderScript = scripts.find((s) => s.src && (s.src.includes('@countriesdb/widget') ||
@@ -1324,13 +1332,7 @@
1324
1332
  }
1325
1333
  // Default to auto-init = true (only disable if explicitly set to false)
1326
1334
  let shouldAutoInit = true;
1327
- const globalConfig = typeof window !== 'undefined'
1328
- ? window.CountriesDBConfig || null
1329
- : null;
1330
- if (globalConfig && typeof globalConfig.autoInit !== 'undefined') {
1331
- shouldAutoInit = !!globalConfig.autoInit;
1332
- }
1333
- else if (loaderScript && loaderScript.src) {
1335
+ if (loaderScript && loaderScript.src) {
1334
1336
  try {
1335
1337
  const scriptUrl = new URL(loaderScript.src);
1336
1338
  const autoInit = scriptUrl.searchParams.get('auto_init');