@countriesdb/widget 0.1.25 → 0.1.27

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.esm.js CHANGED
@@ -506,27 +506,47 @@ async function setupSubdivisionSelection(apiKey, backendUrl, state, config) {
506
506
  select.innerHTML += `<option value="${defaultValue}" disabled>Error: No country select present</option>`;
507
507
  }
508
508
  }
509
+ // Remove old event handlers if they exist (for re-initialization)
510
+ const oldHandlers = state.eventHandlers.get(select);
511
+ if (oldHandlers) {
512
+ if (oldHandlers.update) {
513
+ select.removeEventListener('change', oldHandlers.update);
514
+ }
515
+ if (oldHandlers.followRelated) {
516
+ select.removeEventListener('change', oldHandlers.followRelated);
517
+ }
518
+ if (oldHandlers.followUpward) {
519
+ select.removeEventListener('change', oldHandlers.followUpward);
520
+ }
521
+ }
522
+ // Create new event handlers
523
+ const handlers = {};
509
524
  // Always dispatch an update event for user-initiated subdivision changes
510
- select.addEventListener('change', (event) => {
525
+ handlers.update = (event) => {
511
526
  if (isWidgetInitiatedEvent(event)) {
512
527
  return;
513
528
  }
514
529
  dispatchUpdateEvent(select, { type: 'subdivision', reason: 'regular' });
515
- });
530
+ };
531
+ select.addEventListener('change', handlers.update);
516
532
  // --- follow_related (forward direction) ---
517
- select.addEventListener('change', async (event) => {
533
+ handlers.followRelated = async (event) => {
518
534
  if (isWidgetInitiatedEvent(event)) {
519
535
  return;
520
536
  }
521
537
  await handleFollowRelatedFromSubdivision(select, apiKey, backendUrl, state, config.followRelated, (s, key, code) => updateSubdivisionSelect(s, key, backendUrl, state, config, code));
522
- });
538
+ };
539
+ select.addEventListener('change', handlers.followRelated);
523
540
  // --- follow_upward (reverse direction) ---
524
- select.addEventListener('change', async (event) => {
541
+ handlers.followUpward = async (event) => {
525
542
  if (isWidgetInitiatedEvent(event)) {
526
543
  return;
527
544
  }
528
545
  await handleFollowUpwardFromSubdivision(select, apiKey, backendUrl, state, config.followUpward, (s, key, code) => updateSubdivisionSelect(s, key, backendUrl, state, config, code));
529
- });
546
+ };
547
+ select.addEventListener('change', handlers.followUpward);
548
+ // Store handlers for future cleanup
549
+ state.eventHandlers.set(select, handlers);
530
550
  }
531
551
  }
532
552
  /**
@@ -578,6 +598,11 @@ async function updateSubdivisionSelect(select, apiKey, backendUrl, state, config
578
598
  const dataDefaultValue = !isMultiple
579
599
  ? select.dataset.defaultValue
580
600
  : undefined;
601
+ // Preserve user's current selection before clearing innerHTML
602
+ // This prevents preselected values from overwriting user selections
603
+ const currentValue = select.value;
604
+ const defaultValue = select.dataset.defaultValue || '';
605
+ const hasUserSelection = currentValue && currentValue !== defaultValue && currentValue.trim() !== '';
581
606
  select.innerHTML = buildSubdivisionOptionsHTML(subdivisions, select, subdivisionsLanguage, config.showSubdivisionType, config.allowParentSelection, config.subdivisionNameFilter);
582
607
  // Restore data attributes after setting innerHTML
583
608
  if (!isMultiple) {
@@ -588,41 +613,53 @@ async function updateSubdivisionSelect(select, apiKey, backendUrl, state, config
588
613
  select.dataset.defaultValue = dataDefaultValue;
589
614
  }
590
615
  }
591
- // Manual preselection wins
592
- // Check if preselected value exists (applyPreselectedValue will read it from select element)
593
- const hasPreselectedValue = (select.getAttribute('data-preselected') || select.dataset.preselected || select.dataset._widgetTempPreselect) !== undefined;
594
- if (hasPreselectedValue) {
595
- applyPreselectedValue(select, apiKey);
596
- dispatchUpdateEvent(select, {
597
- type: 'subdivision',
598
- reason: 'preselected',
599
- });
600
- valueSetByWidget = true;
601
- await triggerFollowLogic(select, apiKey, backendUrl, state, config.followRelated, config.followUpward, (s, key, code) => updateSubdivisionSelect(s, key, backendUrl, state, config, code), (countrySelect, key) => updateSubdivisions(countrySelect, key, backendUrl, state, config));
616
+ // Restore user's selection if it exists in the new options (user selection takes priority)
617
+ let userSelectionRestored = false;
618
+ if (hasUserSelection && !isMultiple) {
619
+ const optionExists = Array.from(select.options).some(opt => opt.value === currentValue);
620
+ if (optionExists) {
621
+ select.value = currentValue;
622
+ userSelectionRestored = true;
623
+ // Don't dispatch event here - user already selected it, no need to notify again
624
+ }
602
625
  }
603
- else {
604
- // Try GeoIP preselect
605
- if (shouldUseGeoIP) {
606
- const preselectedSubdivision = subdivisions.find((subdivision) => subdivision.preselected);
607
- if (preselectedSubdivision) {
608
- const isMultiple = select.hasAttribute('multiple');
609
- if (isMultiple) {
610
- // For multi-select, find and select the option
611
- const option = Array.from(select.options).find((opt) => opt.value === preselectedSubdivision.code);
612
- if (option) {
613
- option.selected = true;
626
+ // Manual preselection only if user hasn't selected anything
627
+ if (!userSelectionRestored) {
628
+ // Check if preselected value exists (applyPreselectedValue will read it from select element)
629
+ const hasPreselectedValue = (select.getAttribute('data-preselected') || select.dataset.preselected || select.dataset._widgetTempPreselect) !== undefined;
630
+ if (hasPreselectedValue) {
631
+ applyPreselectedValue(select, apiKey);
632
+ dispatchUpdateEvent(select, {
633
+ type: 'subdivision',
634
+ reason: 'preselected',
635
+ });
636
+ valueSetByWidget = true;
637
+ await triggerFollowLogic(select, apiKey, backendUrl, state, config.followRelated, config.followUpward, (s, key, code) => updateSubdivisionSelect(s, key, backendUrl, state, config, code), (countrySelect, key) => updateSubdivisions(countrySelect, key, backendUrl, state, config));
638
+ }
639
+ else {
640
+ // Try GeoIP preselect (only if user hasn't selected anything)
641
+ if (shouldUseGeoIP) {
642
+ const preselectedSubdivision = subdivisions.find((subdivision) => subdivision.preselected);
643
+ if (preselectedSubdivision) {
644
+ const isMultiple = select.hasAttribute('multiple');
645
+ if (isMultiple) {
646
+ // For multi-select, find and select the option
647
+ const option = Array.from(select.options).find((opt) => opt.value === preselectedSubdivision.code);
648
+ if (option) {
649
+ option.selected = true;
650
+ }
614
651
  }
652
+ else {
653
+ // Single select: set value directly
654
+ select.value = preselectedSubdivision.code;
655
+ }
656
+ dispatchUpdateEvent(select, {
657
+ type: 'subdivision',
658
+ reason: 'geoip',
659
+ });
660
+ valueSetByWidget = true;
661
+ await triggerFollowLogic(select, apiKey, backendUrl, state, config.followRelated, config.followUpward, (s, key, code) => updateSubdivisionSelect(s, key, backendUrl, state, config, code), (countrySelect, key) => updateSubdivisions(countrySelect, key, backendUrl, state, config));
615
662
  }
616
- else {
617
- // Single select: set value directly
618
- select.value = preselectedSubdivision.code;
619
- }
620
- dispatchUpdateEvent(select, {
621
- type: 'subdivision',
622
- reason: 'geoip',
623
- });
624
- valueSetByWidget = true;
625
- await triggerFollowLogic(select, apiKey, backendUrl, state, config.followRelated, config.followUpward, (s, key, code) => updateSubdivisionSelect(s, key, backendUrl, state, config, code), (countrySelect, key) => updateSubdivisions(countrySelect, key, backendUrl, state, config));
626
663
  }
627
664
  }
628
665
  }
@@ -745,8 +782,13 @@ async function setupCountrySelection(apiKey, backendUrl, state, config, subdivis
745
782
  // Subdivisions will be loaded by event handler
746
783
  loadedInitialSubdivisions = true;
747
784
  }
748
- // On change => update subdivisions
749
- select.addEventListener('change', async (event) => {
785
+ // Remove old event handlers if they exist (for re-initialization)
786
+ const oldCountryHandlers = state.eventHandlers.get(select);
787
+ if (oldCountryHandlers?.countryChange) {
788
+ select.removeEventListener('change', oldCountryHandlers.countryChange);
789
+ }
790
+ // Create new country change handler
791
+ const countryChangeHandler = async (event) => {
750
792
  if (isWidgetInitiatedEvent(event)) {
751
793
  return;
752
794
  }
@@ -768,7 +810,12 @@ async function setupCountrySelection(apiKey, backendUrl, state, config, subdivis
768
810
  if (config.followUpward && !select.multiple && picked.is_subdivision_of) {
769
811
  await handleFollowUpwardFromCountry(select, apiKey, backendUrl, state, config.followUpward, (s, key, code) => updateSubdivisionSelect(s, key, backendUrl, state, subdivisionConfig, code));
770
812
  }
771
- });
813
+ };
814
+ // Store and attach the handler
815
+ const countryHandlers = state.eventHandlers.get(select) || {};
816
+ countryHandlers.countryChange = countryChangeHandler;
817
+ state.eventHandlers.set(select, countryHandlers);
818
+ select.addEventListener('change', countryChangeHandler);
772
819
  }
773
820
  catch (error) {
774
821
  console.error('Failed to fetch countries:', error);
@@ -843,6 +890,7 @@ async function CountriesWidgetLoad(options = {}) {
843
890
  subdivisionsMap: new WeakMap(),
844
891
  subdivisionsLanguageMap: new WeakMap(),
845
892
  isInitializing: new Set(),
893
+ eventHandlers: new WeakMap(),
846
894
  };
847
895
  // Use empty string if publicKey is missing (will show error when API calls fail)
848
896
  const apiKey = config.publicKey || '';