@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 +89 -41
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +89 -41
- package/dist/index.js.map +1 -1
- package/dist/initialization.js +88 -41
- package/dist/types.d.ts +7 -0
- package/package.json +1 -1
package/dist/initialization.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
//
|
|
278
|
-
|
|
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.
|
|
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",
|