PageSourceSearch

https://www.bakgoed.nl/wp-content/plugins/postcode-eu-address-validation/assets/js/postcode-eu-autofill.js

js bakgoed.nl collected 2026-10-02 07:10:49 UTC 29,747 bytes, 1,049 lines download raw bytes

1/* global PostcodeEuSettings, PostcodeNlAddressFieldMapping, PostcodeNlStateToValueMapping */
2(function () {
3	'use strict';
4
5	if (typeof PostcodeEuSettings === 'undefined')
6	{
7		return;
8	}
9
10	const $ = jQuery,
11		__ = wp.i18n.__,
12		settings = PostcodeEuSettings,
13		initializedElements = new Set(),
14		addressDetailsCache = new Map(),
15		storedAddresses = {},
16		checkoutElements = {},
17		loadingClassName = 'postcode-eu-address-validation-loading';
18
19	const initialize = function ()
20	{
21		if (typeof PostcodeNlAddressFieldMapping !== 'undefined')
22		{
23			// Provide backward compatibility with the `PostcodeNlAddressFieldMapping.mapping` global.
24			$.extend(settings.addressFieldMapping, PostcodeNlAddressFieldMapping.mapping);
25		}
26
27		$('.postcode-eu-autofill-intl').closest('div').each(function () {
28			if (initializedElements.has(this))
29			{
30				return; // Already initialized.
31			}
32
33			const container = $(this),
34				addressType = this.querySelector('#billing_city') ? 'billing' : 'shipping';
35
36			storedAddresses[addressType] ??= new StoredAddress(addressType);
37
38			if (false === container.is(':visible'))
39			{
40				return; // Skip if the container is not visible. Will try again on update_checkout.
41			}
42
43			initializedElements.add(this);
44
45			const addressFields = getAddressFields(addressType);
46			checkoutElements[addressType] = { container, addressFields };
47
48			if (settings.displayMode === 'default')
49			{
50				addFormattedAddressOutput(container);
51			}
52
53			addAddressAutocompleteNl(container, addressType);
54			addAddressAutocompleteIntl(container, addressType);
55
56			if (settings.displayMode === 'showAll')
57			{
58				return;
59			}
60
61			if (settings.allowAutofillIntlBypass === 'y')
62			{
63				addAutofillIntlBypass(container, addressType);
64			}
65
66			container.find('.country_to_state').on('change.postcode-eu.address-fields', function () {
67				const selectedCountry = this.value;
68
69				window.setTimeout( // Execute after Woocommerce field logic:
70					() => toggleAddressFields(addressFields, !isSupportedCountry(selectedCountry), true)
71				);
72			}).trigger('change.postcode-eu.address-fields');
73		});
74	};
75
76	$(initialize); // On DOMReady
77	$(document.body).on('updated_checkout', initialize);
78
79	const isSupportedCountry = function (countryIso2)
80	{
81		for (let i in settings.enabledCountries)
82		{
83			if (settings.enabledCountries[i].iso2 === countryIso2)
84			{
85				return true;
86			}
87		}
88
89		return false;
90	};
91
92	const getAddressFields = function (addressType)
93	{
94		const fields = {};
95
96		for (const [key, addressPart] of Object.entries(settings.addressFieldMapping))
97		{
98			const field = document.forms.checkout[`${addressType}_${key}`];
99			if (field && addressPart !== null && field.type !== 'hidden')
100			{
101				fields[key] = $(field);
102			}
103		}
104
105		return fields;
106	};
107
108	const getAddressFieldValues = function (addressType)
109	{
110		const fields = checkoutElements[addressType]?.addressFields ?? {};
111		return Object.fromEntries(Object.entries(fields).map(([k, el]) => [k, el.val()]));
112	};
113
114	const resetAddressFields = function (addressFields)
115	{
116		for (let i in addressFields)
117		{
118			addressFields[i]
119				.val('')
120				.trigger('input'); // Clears field validation.
121		}
122	};
123
124	const toggleAddressFields = function (addressFields, state, force)
125	{
126		if (!force)
127		{
128			if (settings.displayMode === 'default')
129			{
130				// Fields remain hidden in default mode. A formatted address is shown instead.
131				state = false;
132			}
133			else if (settings.displayMode === 'showAll')
134			{
135				// Fields always visible in showAll mode.
136				state = true;
137			}
138		}
139
140		for (let i in addressFields)
141		{
142			let field = addressFields[i];
143
144			if (field.length > 0 && field.prop('type') !== 'hidden')
145			{
146				field.closest('.form-row').toggle(state);
147			}
148		}
149	};
150
151	const setAddressWithStorage = function (addressType, apiAddress, mailLines = null)
152	{
153		const mappedValues = {},
154			values = {
155				street: apiAddress.street,
156				streetAndHouseNumber: apiAddress.streetLine,
157				houseNumber: apiAddress.buildingNumber ?? apiAddress.houseNumber,
158				houseNumberAddition: apiAddress.buildingNumberAddition ?? apiAddress.houseNumberAddition,
159				houseNumberAndAddition: apiAddress.building,
160				postcode: apiAddress.postcode,
161				city: apiAddress.locality ?? apiAddress.city,
162				province: apiAddress.province,
163			};
164
165		for (const [key, field] of Object.entries(checkoutElements[addressType].addressFields))
166		{
167			mappedValues[key] = String(values[settings.addressFieldMapping[key]] ?? '');
168			field[0].value = mappedValues[key];
169			field[0].dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
170		}
171
172		storedAddresses[addressType].set(mappedValues, mailLines);
173
174		if (addressType === 'billing' && hasUnusedShippingForm())
175		{
176			// Also set shipping to avoid redundant validation at next pageview.
177			storedAddresses.shipping.set(mappedValues, mailLines);
178		}
179
180		// Force WooCommerce to recalculate shipping costs after address change.
181		$(document.body).trigger('update_checkout');
182	};
183
184	const setFieldValidity = function (field, errorMessage)
185	{
186		errorMessage = errorMessage || '';
187		console.assert($.type(errorMessage) === 'string', 'Error message must be a string.');
188		clearFieldValidity(field);
189
190		if (errorMessage === '')
191		{
192			field.closest('.form-row').addClass('woocommerce-validated');
193		}
194		else
195		{
196			field.closest('.form-row').addClass('woocommerce-invalid');
197			field.after($('<span>', {class: 'postcode-eu-address-validation-warning', text: errorMessage}));
198		}
199	};
200
201	const clearFieldValidity = function (field)
202	{
203		field.closest('.form-row').removeClass('woocommerce-validated woocommerce-invalid');
204		field.siblings('.postcode-eu-address-validation-warning').remove();
205	};
206
207	const getMappedAddressValues = (addressType) => {
208		// Get values according to field mapping. Mapped key must exist in address fields.
209		const values = {};
210		for (const [key, prop] of Object.entries(settings.addressFieldMapping))
211		{
212			if (prop && key in checkoutElements[addressType].addressFields)
213			{
214				values[prop] = checkoutElements[addressType].addressFields[key].val();
215			}
216		}
217
218		// Check for separate street and house number fields, fill combined fields.
219		if ('street' in values)
220		{
221			if ('houseNumber' in values)
222			{
223				values.houseNumberAndAddition = [values.houseNumber, values.houseNumberAddition ?? ''].join(' ').trim();
224			}
225
226			if ('houseNumberAndAddition' in values)
227			{
228				values.streetAndHouseNumber = [values.street, values.houseNumberAndAddition].join(' ');
229			}
230		}
231
232		// Return values if required fields are filled. Otherwise return null.
233		if (values.postcode && values.city && values.streetAndHouseNumber)
234		{
235			return values;
236		}
237
238		return null;
239	};
240
241	const validateAddress = (countryIso3, streetAndBuilding = '', postcode = '', locality = '') => {
242		const url = settings.actions.validate
243			.replace('${country}', encodeURIComponent(countryIso3))
244			.replace('${streetAndBuilding}', encodeURIComponent(streetAndBuilding))
245			.replace('${postcode}', encodeURIComponent(postcode))
246			.replace('${locality}', encodeURIComponent(locality));
247
248		return fetch(url, {headers: {'X-WC-Checkout-Type': 'classic'}}).then((response) => {
249			if (response.ok)
250			{
251				return response.json();
252			}
253
254			throw new Error(response.statusText, {cause: response});
255		});
256	};
257
258	const getValidatedAddress = (...args) => {
259		return validateAddress(...args)
260			.then((response) => {
261				const top = response.matches[0];
262				if (
263					top?.status
264					&& !top.status.isAmbiguous
265					&& top.status.grade < 'C'
266					&& ['Building', 'BuildingPartial'].includes(top.status.validationLevel)
267				)
268				{
269					return top;
270				}
271
272				return null;
273			})
274			.catch((error) => {
275				console.error(error);
276				throw error;
277			});
278	};
279
280	const validatePoBox = function (addressType)
281	{
282		return settings.allowPoBoxShipping === 'y' || !(addressType === 'shipping' || isBillingAsShipping());
283	};
284
285	const destruct = function ()
286	{
287		for (const addressType in checkoutElements)
288		{
289			checkoutElements[addressType].container.find('.country_to_state').off('change.postcode-eu');
290			checkoutElements[addressType].container.find('.form-row.postcode-eu-autofill').remove();
291			toggleAddressFields(checkoutElements[addressType].addressFields, true, true);
292		}
293	};
294
295	const addAddressAutocompleteNl = function (container, addressType)
296	{
297		if (typeof settings.enabledCountries.NL === 'undefined' || settings.netherlandsMode !== 'postcodeOnly')
298		{
299			return;
300		}
301
302		const countryToState = container.find('.country_to_state'),
303			nlFields = container.find('.postcode-eu-autofill-nl'),
304			lookupDelay = 750,
305			postcodeRegex = /[1-9][0-9]{3}\s*[a-z]{2}/i,
306			houseNumberRegex = /[1-9]\d{0,4}(?:\D.*)?$/i,
307			postcodeField = container.find('.postcode-eu-autofill-nl-postcode input'),
308			houseNumberField = container.find('.postcode-eu-autofill-nl-house-number input'),
309			houseNumberSelect = container.find('.postcode-eu-autofill-nl-house-number-select select'),
310			addressFields = checkoutElements[addressType].addressFields = getAddressFields(addressType),
311			storedAddress = storedAddresses[addressType];
312
313		let lookupTimeout,
314			currentAddress = null,
315			currentStatus = null;
316
317		const toggleNlFields = function (state)
318		{
319			nlFields.slice(0, 2).toggle(state);
320			nlFields.slice(-1).toggle(state && houseNumberSelect.children().length > 1);
321		};
322
323		const isNl = function ()
324		{
325			return countryToState.val() === 'NL';
326		};
327
328		const isPostcodeValid = () => postcodeRegex.test(postcodeField.val());
329
330		const isHouseNumberValid = () => houseNumberRegex.test(houseNumberField.val());
331
332		const setHouseNumberAddition = function (value)
333		{
334			currentAddress.houseNumberAddition = value;
335			currentAddress.streetLine = `${currentAddress.street} ${currentAddress.houseNumber} ${currentAddress.houseNumberAddition ?? ''}`.trim();
336		};
337
338		postcodeField.on('input', () => {
339			window.clearTimeout(lookupTimeout);
340			clearFieldValidity(postcodeField);
341			resetHouseNumberSelect();
342			currentStatus = null;
343
344			if (isPostcodeValid() && isHouseNumberValid())
345			{
346				lookupTimeout = window.setTimeout(getAddress, lookupDelay);
347			}
348		});
349
350		houseNumberField.on('input', () => {
351			window.clearTimeout(lookupTimeout);
352			clearFieldValidity(houseNumberField);
353			resetHouseNumberSelect();
354			currentStatus = null;
355
356			if (isHouseNumberValid() && isPostcodeValid())
357			{
358				lookupTimeout = window.setTimeout(getAddress, lookupDelay);
359			}
360		});
361
362		// Replace WooCommerce checkout validation.
363		postcodeField.on('change focusout validate', (e) => {
364			e.stopPropagation();
365
366			setFieldValidity(
367				postcodeField,
368				isPostcodeValid() ? '' : __('Please enter a valid postcode', 'postcode-eu-address-validation')
369			);
370		});
371
372		houseNumberField.on('change focusout validate', (e) => {
373			e.stopPropagation();
374
375			if (currentStatus === null || currentStatus === 'valid')
376			{
377				setFieldValidity(
378					houseNumberField,
379					isHouseNumberValid() ? '' : __('Please enter a valid house number', 'postcode-eu-address-validation')
380				);
381			}
382		});
383
384		countryToState.on('change.postcode-eu', () => {
385			toggleNlFields(isNl());
386
387			if (!isNl())
388			{
389				return;
390			}
391
392			resetAddressFields(addressFields);
393			storedAddress.clear();
394
395			if (isPostcodeValid() && isHouseNumberValid())
396			{
397				getAddress();
398			}
399		});
400
401		postcodeField.on('address-result', function (_, data) {
402			if (data.status === 'valid')
403			{
404				fillAddressFieldsNl(data.address);
405			}
406		});
407
408		houseNumberSelect.on('change validate', function () {
409			if (this.value === '0') // '0' is the select-house-number label.
410			{
411				if (this.children.length > 1)
412				{
413					setHouseNumberAddition(null);
414					postcodeField.trigger('address-result', {address: currentAddress, status: 'houseNumberAdditionIncorrect'});
415					toggleAddressFields(addressFields, false);
416					resetAddressFields(addressFields);
417					setFieldValidity(
418						houseNumberSelect,
419						__('Please select a house number.', 'postcode-eu-address-validation')
420					);
421				}
422
423				return;
424			}
425
426			toggleAddressFields(addressFields, true);
427			setHouseNumberAddition(this.value);
428			postcodeField.trigger('address-result', {address: currentAddress, status: 'valid'});
429			clearFieldValidity(houseNumberSelect);
430		});
431
432		container.on('reset-address', () => {
433			if (isNl())
434			{
435				postcodeField.val('');
436				houseNumberField.val('');
437				resetHouseNumberSelect();
438				resetAddressFields(addressFields);
439				storedAddress.clear();
440				postcodeField.focus();
441			}
442		});
443
444		const getAddress = function ()
445		{
446			const postcode = postcodeRegex.exec(postcodeField.val())[0].replace(/\s/g, ''),
447				houseNumberAndAddition = houseNumberRegex.exec(houseNumberField.val())[0].trim(),
448				url = settings.actions.dutchAddressLookup.replace('${postcode}', encodeURIComponent(postcode)).replace('${houseNumberAndAddition}', encodeURIComponent(houseNumberAndAddition));
449
450			resetHouseNumberSelect();
451			resetAddressFields(addressFields);
452			currentAddress = currentStatus = null;
453			postcodeField.addClass(loadingClassName);
454
455			$.get({
456				url: url,
457				cache: true,
458				dataType: 'json',
459				headers: { 'X-WC-Checkout-Type': 'classic' },
460				success: function (response) {
461					if (response.status === 'notFound')
462					{
463						setFieldValidity(
464							houseNumberField,
465							__('Address not found.', 'postcode-eu-address-validation')
466						);
467						return;
468					}
469
470					if (response.address.addressType === 'PO box' && !validatePoBox(addressType))
471					{
472						setFieldValidity(
473							houseNumberField,
474							__('Sorry, we cannot ship to a PO Box address.', 'postcode-eu-address-validation')
475						);
476						response.status = 'poBoxNotAllowed';
477						return;
478					}
479
480					if (response.status === 'houseNumberAdditionIncorrect')
481					{
482						setHouseNumberOptions(response.address);
483						houseNumberSelect.closest('.postcode-eu-autofill').show();
484					}
485					else
486					{
487						toggleAddressFields(addressFields, true);
488						setFieldValidity(postcodeField);
489						setFieldValidity(houseNumberField);
490					}
491
492					currentAddress = response.address;
493				}
494			}).fail(function (jqXhr) {
495				if (jqXhr.status === 503)
496				{
497					destruct();
498					return;
499				}
500
501				setFieldValidity(
502					houseNumberField,
503					__('An error has occurred. Please try again later or contact us.', 'postcode-eu-address-validation')
504				);
505			}).always(function (response, textStatus) {
506				currentStatus = response.status ?? textStatus;
507
508				postcodeField.removeClass(loadingClassName);
509
510				postcodeField.trigger(
511					'address-result',
512					textStatus === 'success' ? response : {status: textStatus, address: null}
513				);
514			});
515		};
516
517		const setHouseNumberOptions = function (address)
518		{
519			houseNumberSelect.children().eq(0).siblings().remove();
520
521			let options = document.createDocumentFragment();
522
523			address.houseNumberAdditions.forEach((addition) => options.appendChild(createOption(addition)));
524			options.appendChild(createOption(
525				address.unknownHouseNumberAddition,
526				'(' + __('unknown addition', 'postcode-eu-address-validation') + ')'
527			));
528			houseNumberSelect.append(options);
529
530			function createOption(addition, labelSuffix)
531			{
532				const option = document.createElement('option');
533				option.value = addition;
534				option.textContent = (address.houseNumber + ' ' + addition + ' ' + (labelSuffix ?? '')).trim();
535				return option;
536			}
537		};
538
539		const fillAddressFieldsNl = function (apiAddress)
540		{
541			setAddressWithStorage(addressType, apiAddress, getMailLinesNl(apiAddress));
542		};
543
544		const resetHouseNumberSelect = function ()
545		{
546			houseNumberSelect.closest('.postcode-eu-autofill').hide();
547			houseNumberSelect.children().eq(0).siblings().remove();
548		};
549
550		const extractHouseNumber = (streetAndHouseNumber) => {
551			const matches = [...streetAndHouseNumber.matchAll(/[1-9]\d{0,4}\D*/g)];
552
553			if (matches[0]?.index === 0)
554			{
555				matches.shift(); // Discard leading number as a valid house number.
556			}
557
558			if (matches.length === 1) // Single match is most likely the house number.
559			{
560				return matches[0][0].trim();
561			}
562
563			return null; // No match or ambiguous (i.e. multiple numbers found).
564		};
565
566		// Initialize
567		(() => {
568			toggleNlFields(isNl());
569
570			if (!isNl())
571			{
572				return;
573			}
574
575			const prefilledAddressValues = getMappedAddressValues(addressType);
576			if (prefilledAddressValues === null)
577			{
578				return;
579			}
580
581			// Check for stored address to use.
582			if (!storedAddress.isExpired() && storedAddress.isEqual(getAddressFieldValues(addressType)))
583			{
584				const {values, mailLines} = storedAddress.get();
585
586				postcodeField.val(values.postcode);
587				houseNumberField.val(values.houseNumberAndAddition);
588
589				setFieldValidity(postcodeField);
590				setFieldValidity(houseNumberField);
591
592				container.find('.postcode-eu-autofill-intl').trigger('address-result', {mailLines});
593
594				toggleAddressFields(addressFields, true);
595
596				return;
597			}
598
599			// Stored address expired or not found, continue with prefilled values.
600
601			const houseNumberAndAddition = prefilledAddressValues.houseNumberAndAddition ??
602				extractHouseNumber(prefilledAddressValues.streetAndHouseNumber);
603
604			postcodeField.val(prefilledAddressValues.postcode);
605
606			if (houseNumberAndAddition)
607			{
608				houseNumberField.val(houseNumberAndAddition);
609			}
610
611			// Use NL API if the postcode and house number are both valid.
612			if (isPostcodeValid() && isHouseNumberValid())
613			{
614				getAddress();
615				return;
616			}
617
618			// Fall back to Validate API for ambiguous house number cases. Because when a street line contains
619			// multiple numbers, the house number can't easily be determined via pattern matching.
620			if (houseNumberRegex.test(prefilledAddressValues.streetAndHouseNumber))
621			{
622				postcodeField.addClass(loadingClassName);
623
624				const {postcode, city, streetAndHouseNumber} = prefilledAddressValues;
625				getValidatedAddress('NLD', streetAndHouseNumber, postcode, city)
626					.then((result) => {
627						if (result === null)
628						{
629							resetAddressFields(addressFields);
630							setFieldValidity(
631								houseNumberField,
632								__('Please enter a valid address.', 'postcode-eu-address-validation')
633							);
634							currentStatus = 'notFound';
635						}
636						else if (result.isPoBox && !validatePoBox(addressType))
637						{
638							resetAddressFields(addressFields);
639							setFieldValidity(
640								houseNumberField,
641								__('Sorry, we cannot ship to a PO Box address.', 'postcode-eu-address-validation')
642							);
643							currentStatus = 'poBoxNotAllowed';
644						}
645						else
646						{
647							fillAddressFieldsNl(result.address);
648
649							setFieldValidity(postcodeField);
650							setFieldValidity(houseNumberField);
651
652							container.find('.postcode-eu-autofill-intl').trigger('address-result', result);
653
654							toggleAddressFields(addressFields, true);
655						}
656					})
657					.catch((error) => {
658						if (error.cause?.status === 503)
659						{
660							destruct();
661						}
662					})
663					.finally(() => postcodeField.removeClass(loadingClassName));
664			}
665		})();
666	};
667
668	const addAddressAutocompleteIntl = function (container, addressType)
669	{
670		const countryToState = container.find('.country_to_state'),
671			intlFormRow = container.find('.postcode-eu-autofill-intl'),
672			intlField = intlFormRow.find('input'),
673			storedAddress = storedAddresses[addressType];
674
675		let autocompleteInstance = null,
676			addressFields = checkoutElements[addressType].addressFields = getAddressFields(addressType),
677			matches = [];
678
679		const isSupportedCountryIntl = function (countryIso2)
680		{
681			if (settings.netherlandsMode === 'postcodeOnly' && countryIso2 === 'NL')
682			{
683				return false;
684			}
685
686			return isSupportedCountry(countryIso2);
687		};
688
689		const fillAddressFieldsIntl = function (result)
690		{
691			setAddressWithStorage(addressType, result.address, result.mailLines);
692		};
693
694		const reset = function ()
695		{
696			intlField.val('');
697			resetAddressFields(addressFields);
698			storedAddress.clear();
699			autocompleteInstance?.reset();
700		};
701
702		countryToState.on('change.postcode-eu', () => window.setTimeout(() => {
703			const isSupported = isSupportedCountryIntl(countryToState.val());
704			if (isSupported)
705			{
706				reset();
707				autocompleteInstance?.setCountry(settings.enabledCountries[countryToState.val()].iso3);
708			}
709
710			intlFormRow.toggle(isSupported);
711		}));
712
713		container.on('reset-address', () => {
714			if (isSupportedCountryIntl(countryToState.val()))
715			{
716				reset();
717				intlField.focus();
718			}
719		});
720
721		const intlFieldObserver = new IntersectionObserver(function (entries) {
722			entries.forEach((entry) => {
723				if (!entry.isIntersecting || autocompleteInstance !== null)
724				{
725					return;
726				}
727
728				let deferred = $.Deferred();
729
730				const selectAutocompleteAddress = function (item)
731				{
732					const callback = (result) => {
733						if (result.isPoBox && !validatePoBox(addressType))
734						{
735							resetAddressFields(addressFields);
736							setFieldValidity(
737								intlField,
738								__('Sorry, we cannot ship to a PO Box address.', 'postcode-eu-address-validation')
739							);
740						}
741						else
742						{
743							fillAddressFieldsIntl(result);
744							toggleAddressFields(addressFields, true);
745							intlField.trigger('address-result', result);
746							setFieldValidity(intlField);
747						}
748
749						deferred.resolve();
750					};
751
752					if (addressDetailsCache.has(item.context))
753					{
754						callback(addressDetailsCache.get(item.context));
755						return;
756					}
757
758					intlField.addClass(loadingClassName);
759
760					autocompleteInstance.getDetails(item.context, (result) => {
761						callback(result);
762						addressDetailsCache.set(item.context, result);
763						intlField.removeClass(loadingClassName);
764					});
765				};
766
767				const isSingleAddressMatch = () => matches.length === 1 && matches[0].precision === 'Address';
768
769				autocompleteInstance = new PostcodeNl.AutocompleteAddress(intlField[0], {
770					autocompleteUrl: settings.actions.autocomplete,
771					addressDetailsUrl: settings.actions.getDetails,
772					context: (settings.enabledCountries[countryToState.val()]?.iso3 ?? 'nld').toLowerCase(),
773				});
774
775				autocompleteInstance.getSuggestions = function (context, term, response)
776				{
777					const encodedTerm = new TextEncoder().encode(term),
778						binaryTerm = Array.from(encodedTerm, (byte) => String.fromCodePoint(byte)).join(''),
779						url = this.options.autocompleteUrl.replace('${context}', encodeURIComponent(context)).replace('${term}', encodeURIComponent(btoa(binaryTerm)));
780
781					return this.xhrGet(url, response);
782				};
783
784				autocompleteInstance.getDetails = function (addressId, response)
785				{
786					const url = this.options.addressDetailsUrl.replace('${context}', encodeURIComponent(addressId));
787					return this.xhrGet(url, response);
788				};
789
790				intlField[0].addEventListener('autocomplete-xhr-send', ({detail: xhr}) => {
791					xhr.setRequestHeader('X-WC-Checkout-Type', 'classic');
792				});
793
794				intlField[0].addEventListener('autocomplete-select', function (e) {
795					if (e.detail.precision === 'Address')
796					{
797						selectAutocompleteAddress(e.detail);
798					}
799				});
800
801				intlField[0].addEventListener('autocomplete-error', function (e) {
802					console.error('Autocomplete XHR error', e);
803					intlField.removeClass(loadingClassName);
804
805					if (e.detail.request.status === 503)
806					{
807						// API seems to be down, restore standard address fields.
808						destruct();
809						return;
810					}
811
812					toggleAddressFields(addressFields, true);
813					setFieldValidity(
814						intlField,
815						__('An error has occurred while retrieving address data. Please contact us if the problem persists.', 'postcode-eu-address-validation')
816					);
817				});
818
819				// Clear the previous values when searching for a new address.
820				intlField[0].addEventListener('autocomplete-search', function () {
821					resetAddressFields(addressFields);
822				});
823
824				intlField[0].addEventListener('autocomplete-response', function (e) {
825					matches = e.detail.matches;
826
827					deferred = $.Deferred();
828
829					deferred.fail(() => setFieldValidity(intlField, __('Please enter an address and select it', 'postcode-eu-address-validation')));
830				});
831
832				intlField.on('blur', () => {
833					if (
834						false === isSingleAddressMatch()
835						&& false === autocompleteInstance.elements.menu.classList.contains('postcodenl-autocomplete-menu-open')
836					)
837					{
838						deferred.reject();
839					}
840
841					matches = [];
842				});
843
844				// Prevent default validation via delegated event handler.
845				intlField.on('change focusout validate', (e) => e.stopPropagation());
846			});
847		});
848
849		intlFieldObserver.observe(intlFormRow[0]);
850
851		intlFormRow.toggle(isSupportedCountryIntl(countryToState.val()));
852
853		// Initialize
854		(() => {
855			if (false === isSupportedCountryIntl(countryToState.val()))
856			{
857				return; // Only use values from supported countries.
858			}
859
860			const prefilledAddressValues = getMappedAddressValues(addressType);
861			if (prefilledAddressValues === null)
862			{
863				return;
864			}
865
866			const {postcode, city, streetAndHouseNumber} = prefilledAddressValues;
867			intlField.val(`${postcode} ${city} ${streetAndHouseNumber}`.trim());
868
869			// Check for stored address to use.
870			if (!storedAddress.isExpired() && storedAddress.isEqual(getAddressFieldValues(addressType)))
871			{
872				setFieldValidity(intlField);
873
874				const {mailLines} = storedAddress.get();
875				intlFormRow.trigger('address-result', {mailLines});
876
877				return;
878			}
879
880			intlField.addClass(loadingClassName);
881
882			// Autocomplete prefilled address values using the Validate API.
883			const countryIso3 = settings.enabledCountries[countryToState.val()].iso3;
884			getValidatedAddress(countryIso3, streetAndHouseNumber, postcode, city)
885				.then((result) => {
886					if (result === null)
887					{
888						resetAddressFields(addressFields);
889						setFieldValidity(
890							intlField,
891							__('Please select a valid address', 'postcode-eu-address-validation')
892						);
893					}
894					else if (result.isPoBox && !validatePoBox(addressType))
895					{
896						resetAddressFields(addressFields);
897						setFieldValidity(
898							intlField,
899							__('Sorry, we cannot ship to a PO Box address.', 'postcode-eu-address-validation')
900						);
901					}
902					else
903					{
904						fillAddressFieldsIntl(result);
905						setFieldValidity(intlField);
906						intlField.trigger('address-result', result);
907						toggleAddressFields(addressFields, true);
908					}
909				})
910				.catch((error) => {
911					if (error.cause?.status === 503)
912					{
913						destruct();
914					}
915				})
916				.finally(() => intlField.removeClass(loadingClassName));
917		})();
918	};
919
920	const getMailLinesNl = function (address)
921	{
922		return [address.streetLine, `${address.postcode}  ${address.city}`];
923	};
924
925	const addFormattedAddressOutput = function (container)
926	{
927		const formRow = $('<div>', {class: 'form-row form-row-wide postcode-eu-autofill form-row-postcode-eu-autofill-address'}),
928			resetButton = $(
929				'<span>',
930				{
931					text: '✕',
932					title:__('Remove address', 'postcode-eu-address-validation'),
933					class: 'postcode-eu-autofill-address-reset',
934					click: () => {
935						container.trigger('reset-address');
936						formRow.hide();
937					},
938				}
939			),
940			addressElement = $('<address>', {class: 'postcode-eu-autofill-address'});
941
942		formRow.append(resetButton, addressElement);
943		container.find('.postcode-eu-autofill').last().after(formRow);
944
945		container.find('.country_to_state').on('change', function () {
946			formRow.hide();
947		});
948
949		container.find('.postcode-eu-autofill-intl').on('address-result', function (_, result) {
950			addressElement.html(result.mailLines.join('<br>'));
951			formRow.show();
952		});
953
954		container.find('.postcode-eu-autofill-intl input').on('autocomplete-search', function () {
955			formRow.hide();
956		});
957
958		container.find('.postcode-eu-autofill-nl').on('address-result', function (_, result) {
959			if (result.status !== 'valid')
960			{
961				formRow.hide();
962				return;
963			}
964
965			addressElement.html(getMailLinesNl(result.address).join('<br>'));
966			formRow.show();
967		});
968	};
969
970	const addAutofillIntlBypass = function (container, addressType)
971	{
972		const formRow = container.find('.form-row.postcode-eu-autofill-intl'),
973			wrapper = $('<span>', {'class': 'postcode-eu-autofill-intl-bypass'}),
974			link = $('<a>', {text: settings.autofillIntlBypassLinkText});
975
976		link.on('click', function () {
977			toggleAddressFields(checkoutElements[addressType].addressFields, true, true);
978			formRow.hide();
979			return false;
980		});
981
982		wrapper.append(link);
983		formRow.append(wrapper);
984	};
985
986	const hasUnusedShippingForm = function ()
987	{
988		if (typeof window.checkout.ship_to_different_address === 'undefined')
989		{
990			return false; // No separate shipping form exists.
991		}
992
993		return !window.checkout.ship_to_different_address.checked;
994	};
995
996	const isBillingAsShipping = function ()
997	{
998		if (typeof window.checkout.ship_to_different_address === 'undefined')
999		{
1000			return true; // Forced shipping to billing address. No separate shipping form.
1001		}
1002
1003		return !window.checkout.ship_to_different_address.checked;
1004	};
1005
1006	class StoredAddress {
1007		storageKey;
1008
1009		constructor(addressType)
1010		{
1011			this.storageKey = 'postcode-eu-validated-address-' + addressType;
1012		}
1013
1014		get()
1015		{
1016			return JSON.parse(window.localStorage.getItem(this.storageKey)) ?? null;
1017		}
1018
1019		set(values, mailLines)
1020		{
1021			window.localStorage.setItem(
1022				this.storageKey,
1023				JSON.stringify({
1024					timestamp: Date.now(),
1025					token: settings.localStorageToken,
1026					values: Object.fromEntries(Object.entries(values).filter(([, v]) => v !== '')),
1027					mailLines,
1028				})
1029			);
1030		}
1031
1032		isEqual(values)
1033		{
1034			const storedValues = this.get()?.values;
1035			return (storedValues ?? false) && Object.entries(storedValues).every(([k, v]) => values[k] === v);
1036		}
1037
1038		isExpired()
1039		{
1040			const data = JSON.parse(window.localStorage.getItem(this.storageKey));
1041			return data?.timestamp + 90 * 24 * 60 * 60 * 1000 < Date.now() || data?.token !== settings.localStorageToken;
1042		}
1043
1044		clear()
1045		{
1046			window.localStorage.removeItem(this.storageKey);
1047		}
1048	}
1049})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.