@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.
@@ -35,27 +35,47 @@ export async function setupSubdivisionSelection(apiKey, backendUrl, state, confi
35
35
  select.innerHTML += `<option value="${defaultValue}" disabled>Error: No country select present</option>`;
36
36
  }
37
37
  }
38
+ // Remove old event handlers if they exist (for re-initialization)
39
+ const oldHandlers = state.eventHandlers.get(select);
40
+ if (oldHandlers) {
41
+ if (oldHandlers.update) {
42
+ select.removeEventListener('change', oldHandlers.update);
43
+ }
44
+ if (oldHandlers.followRelated) {
45
+ select.removeEventListener('change', oldHandlers.followRelated);
46
+ }
47
+ if (oldHandlers.followUpward) {
48
+ select.removeEventListener('change', oldHandlers.followUpward);
49
+ }
50
+ }
51
+ // Create new event handlers
52
+ const handlers = {};
38
53
  // Always dispatch an update event for user-initiated subdivision changes
39
- select.addEventListener('change', (event) => {
54
+ handlers.update = (event) => {
40
55
  if (isWidgetInitiatedEvent(event)) {
41
56
  return;
42
57
  }
43
58
  dispatchUpdateEvent(select, { type: 'subdivision', reason: 'regular' });
44
- });
59
+ };
60
+ select.addEventListener('change', handlers.update);
45
61
  // --- follow_related (forward direction) ---
46
- select.addEventListener('change', async (event) => {
62
+ handlers.followRelated = async (event) => {
47
63
  if (isWidgetInitiatedEvent(event)) {
48
64
  return;
49
65
  }
50
66
  await handleFollowRelatedFromSubdivision(select, apiKey, backendUrl, state, config.followRelated, (s, key, code) => updateSubdivisionSelect(s, key, backendUrl, state, config, code));
51
- });
67
+ };
68
+ select.addEventListener('change', handlers.followRelated);
52
69
  // --- follow_upward (reverse direction) ---
53
- select.addEventListener('change', async (event) => {
70
+ handlers.followUpward = async (event) => {
54
71
  if (isWidgetInitiatedEvent(event)) {
55
72
  return;
56
73
  }
57
74
  await handleFollowUpwardFromSubdivision(select, apiKey, backendUrl, state, config.followUpward, (s, key, code) => updateSubdivisionSelect(s, key, backendUrl, state, config, code));
58
- });
75
+ };
76
+ select.addEventListener('change', handlers.followUpward);
77
+ // Store handlers for future cleanup
78
+ state.eventHandlers.set(select, handlers);
59
79
  }
60
80
  }
61
81
  /**
@@ -107,6 +127,11 @@ export async function updateSubdivisionSelect(select, apiKey, backendUrl, state,
107
127
  const dataDefaultValue = !isMultiple
108
128
  ? select.dataset.defaultValue
109
129
  : undefined;
130
+ // Preserve user's current selection before clearing innerHTML
131
+ // This prevents preselected values from overwriting user selections
132
+ const currentValue = select.value;
133
+ const defaultValue = select.dataset.defaultValue || '';
134
+ const hasUserSelection = currentValue && currentValue !== defaultValue && currentValue.trim() !== '';
110
135
  select.innerHTML = buildSubdivisionOptionsHTML(subdivisions, select, subdivisionsLanguage, config.showSubdivisionType, config.allowParentSelection, config.subdivisionNameFilter);
111
136
  // Restore data attributes after setting innerHTML
112
137
  if (!isMultiple) {
@@ -117,41 +142,53 @@ export async function updateSubdivisionSelect(select, apiKey, backendUrl, state,
117
142
  select.dataset.defaultValue = dataDefaultValue;
118
143
  }
119
144
  }
120
- // Manual preselection wins
121
- // Check if preselected value exists (applyPreselectedValue will read it from select element)
122
- const hasPreselectedValue = (select.getAttribute('data-preselected') || select.dataset.preselected || select.dataset._widgetTempPreselect) !== undefined;
123
- if (hasPreselectedValue) {
124
- applyPreselectedValue(select, apiKey);
125
- dispatchUpdateEvent(select, {
126
- type: 'subdivision',
127
- reason: 'preselected',
128
- });
129
- valueSetByWidget = true;
130
- 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));
145
+ // Restore user's selection if it exists in the new options (user selection takes priority)
146
+ let userSelectionRestored = false;
147
+ if (hasUserSelection && !isMultiple) {
148
+ const optionExists = Array.from(select.options).some(opt => opt.value === currentValue);
149
+ if (optionExists) {
150
+ select.value = currentValue;
151
+ userSelectionRestored = true;
152
+ // Don't dispatch event here - user already selected it, no need to notify again
153
+ }
131
154
  }
132
- else {
133
- // Try GeoIP preselect
134
- if (shouldUseGeoIP) {
135
- const preselectedSubdivision = subdivisions.find((subdivision) => subdivision.preselected);
136
- if (preselectedSubdivision) {
137
- const isMultiple = select.hasAttribute('multiple');
138
- if (isMultiple) {
139
- // For multi-select, find and select the option
140
- const option = Array.from(select.options).find((opt) => opt.value === preselectedSubdivision.code);
141
- if (option) {
142
- option.selected = true;
155
+ // Manual preselection only if user hasn't selected anything
156
+ if (!userSelectionRestored) {
157
+ // Check if preselected value exists (applyPreselectedValue will read it from select element)
158
+ const hasPreselectedValue = (select.getAttribute('data-preselected') || select.dataset.preselected || select.dataset._widgetTempPreselect) !== undefined;
159
+ if (hasPreselectedValue) {
160
+ applyPreselectedValue(select, apiKey);
161
+ dispatchUpdateEvent(select, {
162
+ type: 'subdivision',
163
+ reason: 'preselected',
164
+ });
165
+ valueSetByWidget = true;
166
+ 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));
167
+ }
168
+ else {
169
+ // Try GeoIP preselect (only if user hasn't selected anything)
170
+ if (shouldUseGeoIP) {
171
+ const preselectedSubdivision = subdivisions.find((subdivision) => subdivision.preselected);
172
+ if (preselectedSubdivision) {
173
+ const isMultiple = select.hasAttribute('multiple');
174
+ if (isMultiple) {
175
+ // For multi-select, find and select the option
176
+ const option = Array.from(select.options).find((opt) => opt.value === preselectedSubdivision.code);
177
+ if (option) {
178
+ option.selected = true;
179
+ }
143
180
  }
181
+ else {
182
+ // Single select: set value directly
183
+ select.value = preselectedSubdivision.code;
184
+ }
185
+ dispatchUpdateEvent(select, {
186
+ type: 'subdivision',
187
+ reason: 'geoip',
188
+ });
189
+ valueSetByWidget = true;
190
+ 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));
144
191
  }
145
- else {
146
- // Single select: set value directly
147
- select.value = preselectedSubdivision.code;
148
- }
149
- dispatchUpdateEvent(select, {
150
- type: 'subdivision',
151
- reason: 'geoip',
152
- });
153
- valueSetByWidget = true;
154
- 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));
155
192
  }
156
193
  }
157
194
  }
@@ -274,8 +311,13 @@ export async function setupCountrySelection(apiKey, backendUrl, state, config, s
274
311
  // Subdivisions will be loaded by event handler
275
312
  loadedInitialSubdivisions = true;
276
313
  }
277
- // On change => update subdivisions
278
- select.addEventListener('change', async (event) => {
314
+ // Remove old event handlers if they exist (for re-initialization)
315
+ const oldCountryHandlers = state.eventHandlers.get(select);
316
+ if (oldCountryHandlers?.countryChange) {
317
+ select.removeEventListener('change', oldCountryHandlers.countryChange);
318
+ }
319
+ // Create new country change handler
320
+ const countryChangeHandler = async (event) => {
279
321
  if (isWidgetInitiatedEvent(event)) {
280
322
  return;
281
323
  }
@@ -297,7 +339,12 @@ export async function setupCountrySelection(apiKey, backendUrl, state, config, s
297
339
  if (config.followUpward && !select.multiple && picked.is_subdivision_of) {
298
340
  await handleFollowUpwardFromCountry(select, apiKey, backendUrl, state, config.followUpward, (s, key, code) => updateSubdivisionSelect(s, key, backendUrl, state, subdivisionConfig, code));
299
341
  }
300
- });
342
+ };
343
+ // Store and attach the handler
344
+ const countryHandlers = state.eventHandlers.get(select) || {};
345
+ countryHandlers.countryChange = countryChangeHandler;
346
+ state.eventHandlers.set(select, countryHandlers);
347
+ select.addEventListener('change', countryChangeHandler);
301
348
  }
302
349
  catch (error) {
303
350
  console.error('Failed to fetch countries:', error);
package/dist/types.d.ts CHANGED
@@ -30,9 +30,16 @@ export interface UpdateEventDetail {
30
30
  export interface ReadyEventDetail extends UpdateEventDetail {
31
31
  phase: 'initial' | 'reload';
32
32
  }
33
+ export interface EventHandlers {
34
+ update?: (event: Event) => void;
35
+ followRelated?: (event: Event) => void;
36
+ followUpward?: (event: Event) => void;
37
+ countryChange?: (event: Event) => void;
38
+ }
33
39
  export interface WidgetState {
34
40
  countriesMap: WeakMap<SelectElement, Country[]>;
35
41
  subdivisionsMap: WeakMap<SelectElement, Subdivision[]>;
36
42
  subdivisionsLanguageMap: WeakMap<SelectElement, string>;
37
43
  isInitializing: Set<SelectElement>;
44
+ eventHandlers: WeakMap<SelectElement, EventHandlers>;
38
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@countriesdb/widget",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "Country and state/province select widget with ISO 3166-1 and ISO 3166-2 codes. Auto-populates dropdowns with up-to-date country and subdivision data in multiple languages. Easy integration for forms, location selection, and address validation.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.esm.js",