303 lines
7.7 KiB
JavaScript
303 lines
7.7 KiB
JavaScript
( function () {
|
|
|
|
window.WPO_IPS_PeppolEndpointDerivation = window.WPO_IPS_PeppolEndpointDerivation || {};
|
|
|
|
window.WPO_IPS_PeppolEndpointDerivation.init = function init( CONFIG, adapter ) {
|
|
|
|
const LOCK_COUNTRIES = Array.isArray( CONFIG.countries )
|
|
? CONFIG.countries.map( ( c ) => String( c ).toUpperCase() )
|
|
: [];
|
|
|
|
const DEBUG = !!CONFIG.debug;
|
|
const VAT_SELECTOR = CONFIG.vat_field_selector;
|
|
const AUTOFILL_ENDPOINT_ROUTE = CONFIG.peppol_autofill_endpoint_route;
|
|
|
|
function log( ...args ) {
|
|
if ( DEBUG ) console.log( '[WPO IPS Peppol]', ...args );
|
|
}
|
|
|
|
function getValue( selector ) {
|
|
if ( ! selector ) return '';
|
|
const el = document.querySelector( selector );
|
|
return el ? String( el.value || '' ).trim() : '';
|
|
}
|
|
|
|
function isLockCountry( country ) {
|
|
const c = String( country || '' ).toUpperCase();
|
|
return LOCK_COUNTRIES.includes( c );
|
|
}
|
|
|
|
function normalizeVat( vat ) {
|
|
return String( vat || '' )
|
|
.replace( /\s+/g, '' )
|
|
.toUpperCase()
|
|
.trim();
|
|
}
|
|
|
|
function setFieldValue( el, value ) {
|
|
if ( ! el ) return false;
|
|
|
|
const newVal = String( value ?? '' );
|
|
|
|
// React controlled input friendly setter
|
|
const valueSetter =
|
|
Object.getOwnPropertyDescriptor( window.HTMLInputElement.prototype, 'value' )?.set;
|
|
|
|
// Some handlers ignore changes while readOnly. Temporarily lift it.
|
|
const prevReadOnly = !! el.readOnly;
|
|
el.readOnly = false;
|
|
|
|
if ( valueSetter ) {
|
|
valueSetter.call( el, newVal );
|
|
} else {
|
|
el.value = newVal;
|
|
}
|
|
|
|
el.dispatchEvent( new Event( 'input', { bubbles: true } ) );
|
|
el.dispatchEvent( new Event( 'change', { bubbles: true } ) );
|
|
|
|
el.readOnly = prevReadOnly;
|
|
|
|
if ( typeof adapter.onSetFieldValue === 'function' ) {
|
|
adapter.onSetFieldValue( el, newVal );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function reapplyValueAfterUnlock( input, value ) {
|
|
if ( ! input ) return;
|
|
|
|
requestAnimationFrame( () => {
|
|
setFieldValue( input, value );
|
|
requestAnimationFrame( () => setFieldValue( input, value ) );
|
|
} );
|
|
}
|
|
|
|
function fetchPeppolEndpointValue( billingCountry, vatValue ) {
|
|
if ( typeof adapter.fetchEndpoint === 'function' ) {
|
|
return adapter.fetchEndpoint( billingCountry, vatValue );
|
|
}
|
|
|
|
const apiFetch = window.wp?.apiFetch;
|
|
if ( ! apiFetch ) return Promise.resolve( null );
|
|
|
|
return apiFetch( {
|
|
path: AUTOFILL_ENDPOINT_ROUTE,
|
|
method: 'POST',
|
|
data: {
|
|
billing_country: String( billingCountry || '' ).toUpperCase(),
|
|
vat: String( vatValue || '' ),
|
|
},
|
|
} );
|
|
}
|
|
|
|
// Debounce + cache.
|
|
let endpointLookupTimer = null;
|
|
let endpointLookupLast = null;
|
|
|
|
// Manual override flag (session only).
|
|
let manualOverride = false;
|
|
let manualOverrideValue = '';
|
|
|
|
// Reset override when VAT/country changes.
|
|
let lastVat = null;
|
|
let lastCountry = null;
|
|
|
|
let lastLocked = null;
|
|
let lastEndpointEl = null;
|
|
let scheduled = false;
|
|
|
|
function ensureOverrideLink( wrapper, endpointInput, locked ) {
|
|
if ( ! wrapper ) return;
|
|
|
|
let link = wrapper.querySelector( '.wpo-ips-override' );
|
|
|
|
if ( ! locked ) {
|
|
if ( link ) link.remove();
|
|
return;
|
|
}
|
|
|
|
if ( link ) return link;
|
|
|
|
link = document.createElement( 'a' );
|
|
link.href = '#';
|
|
link.className = 'wpo-ips-override';
|
|
link.textContent = CONFIG.override_link_text || 'Override (edit manually)';
|
|
|
|
link.addEventListener( 'click', ( e ) => {
|
|
e.preventDefault();
|
|
|
|
const preservedValue = endpointInput ? String( endpointInput.value || '' ) : '';
|
|
|
|
manualOverride = true;
|
|
manualOverrideValue = preservedValue;
|
|
|
|
applyLockState( 'override-click' );
|
|
|
|
if ( endpointInput ) {
|
|
if ( preservedValue ) {
|
|
reapplyValueAfterUnlock( endpointInput, preservedValue );
|
|
}
|
|
|
|
endpointInput.focus();
|
|
}
|
|
} );
|
|
|
|
// Let adapter decide where to place link, default to append.
|
|
if ( typeof adapter.appendOverrideLink === 'function' ) {
|
|
adapter.appendOverrideLink( wrapper, endpointInput, link );
|
|
} else {
|
|
wrapper.appendChild( link );
|
|
}
|
|
|
|
return link;
|
|
}
|
|
|
|
function setLocked( endpoint, wrapper, locked ) {
|
|
endpoint.readOnly = locked;
|
|
endpoint.style.pointerEvents = locked ? 'none' : '';
|
|
endpoint.setAttribute( 'aria-disabled', locked ? 'true' : 'false' );
|
|
endpoint.classList.toggle( 'wpo-is-locked', locked );
|
|
|
|
if ( wrapper ) {
|
|
wrapper.classList.toggle( 'wpo-ips-locked-wrap', locked );
|
|
}
|
|
}
|
|
|
|
function bindManualOverridePersistence( endpoint ) {
|
|
if ( ! endpoint || endpoint.__wpoPeppolManualBound ) return;
|
|
|
|
endpoint.__wpoPeppolManualBound = true;
|
|
|
|
function captureCurrentValue() {
|
|
const current = String( endpoint.value || '' );
|
|
if ( current ) {
|
|
manualOverrideValue = current;
|
|
}
|
|
}
|
|
|
|
endpoint.addEventListener( 'input', () => {
|
|
if ( manualOverride ) {
|
|
manualOverrideValue = String( endpoint.value || '' );
|
|
}
|
|
} );
|
|
|
|
endpoint.addEventListener( 'pointerdown', () => {
|
|
if ( manualOverride ) {
|
|
captureCurrentValue();
|
|
}
|
|
} );
|
|
|
|
endpoint.addEventListener( 'mousedown', () => {
|
|
if ( manualOverride ) {
|
|
captureCurrentValue();
|
|
}
|
|
} );
|
|
|
|
endpoint.addEventListener( 'focus', () => {
|
|
if ( manualOverride && manualOverrideValue ) {
|
|
reapplyValueAfterUnlock( endpoint, manualOverrideValue );
|
|
}
|
|
} );
|
|
|
|
endpoint.addEventListener( 'blur', () => {
|
|
if ( manualOverride ) {
|
|
captureCurrentValue();
|
|
|
|
if ( manualOverrideValue ) {
|
|
reapplyValueAfterUnlock( endpoint, manualOverrideValue );
|
|
}
|
|
}
|
|
} );
|
|
}
|
|
|
|
function applyLockState( source = 'unknown' ) {
|
|
scheduled = false;
|
|
|
|
const nodes = adapter.getEndpointNodes && adapter.getEndpointNodes();
|
|
const wrapper = nodes ? nodes.wrapper : null;
|
|
const endpoint = nodes ? nodes.endpoint : null;
|
|
|
|
if ( ! endpoint ) return;
|
|
|
|
bindManualOverridePersistence( endpoint );
|
|
|
|
const country = String( adapter.getBillingCountry ? adapter.getBillingCountry() : '' ).toUpperCase();
|
|
const vatValue = getValue( VAT_SELECTOR );
|
|
|
|
// reset override when VAT/country changes
|
|
if ( country !== lastCountry || vatValue !== lastVat ) {
|
|
manualOverride = false;
|
|
manualOverrideValue = '';
|
|
lastCountry = country;
|
|
lastVat = vatValue;
|
|
endpointLookupLast = null;
|
|
}
|
|
|
|
// always locked unless user clicked override link
|
|
const locked = ! manualOverride;
|
|
|
|
if ( isLockCountry( country ) && ! manualOverride && vatValue !== '' ) {
|
|
const vat = normalizeVat( vatValue );
|
|
const key = country + '|' + vat;
|
|
|
|
if ( endpointLookupLast && endpointLookupLast.key === key ) {
|
|
if ( endpointLookupLast.value ) {
|
|
setFieldValue( endpoint, endpointLookupLast.value );
|
|
}
|
|
} else {
|
|
if ( endpointLookupTimer ) clearTimeout( endpointLookupTimer );
|
|
|
|
endpointLookupTimer = setTimeout( () => {
|
|
fetchPeppolEndpointValue( country, vat )
|
|
.then( ( res ) => {
|
|
const id = String( res?.id || '' ).trim();
|
|
|
|
endpointLookupLast = { key, value: id };
|
|
|
|
if ( id ) {
|
|
setFieldValue( endpoint, id );
|
|
}
|
|
} )
|
|
.catch( () => {} );
|
|
}, 250 );
|
|
}
|
|
}
|
|
|
|
if ( locked === lastLocked && endpoint === lastEndpointEl ) return;
|
|
|
|
ensureOverrideLink( wrapper, endpoint, locked );
|
|
setLocked( endpoint, wrapper, locked );
|
|
|
|
lastLocked = locked;
|
|
lastEndpointEl = endpoint;
|
|
|
|
log( 'lock state applied', {
|
|
source,
|
|
country,
|
|
vatValue,
|
|
locked,
|
|
manualOverride,
|
|
lastValue: endpointLookupLast ? endpointLookupLast.value : '',
|
|
manualOverrideValue,
|
|
} );
|
|
}
|
|
|
|
function scheduleApply( source ) {
|
|
if ( scheduled ) return;
|
|
|
|
scheduled = true;
|
|
requestAnimationFrame( () => applyLockState( source ) );
|
|
}
|
|
|
|
// Expose minimal API for wrapper files.
|
|
return {
|
|
apply: applyLockState,
|
|
schedule: scheduleApply,
|
|
log,
|
|
};
|
|
};
|
|
|
|
} )();
|