/** * CPFM Deactivation Feedback — modal behaviour (vanilla, no jQuery). * * Intercepts each registered plugin's Deactivate link on plugins.php, opens the * matching modal (focus-trapped, ESC/backdrop to close), and on submit POSTs the * reason to the plugin's feedback API before letting the deactivation proceed. * Feedback failure never blocks deactivation. */ ( function () { 'use strict'; var cfg = window.cpfmDeactivation; if ( ! cfg || ! cfg.plugins ) { return; } var openModal = null; var deactivateUrl = null; var lastFocus = null; function qs( sel, ctx ) { return ( ctx || document ).querySelector( sel ); } function qsa( sel, ctx ) { return Array.prototype.slice.call( ( ctx || document ).querySelectorAll( sel ) ); } function open( modal, url ) { openModal = modal; deactivateUrl = url; lastFocus = document.activeElement; modal.classList.add( 'is-open' ); modal.setAttribute( 'aria-hidden', 'false' ); // Focus the DIALOG, not the first radio: focusing a radio draws a ring on // it and reads as "already selected". The dialog is focusable (tabindex // -1) so screen readers still land inside and the tab trap still works. var dialog = qs( '.cpfm-df__dialog', modal ); if ( dialog ) { dialog.focus(); } document.addEventListener( 'keydown', onKey ); } function close() { if ( ! openModal ) { return; } openModal.classList.remove( 'is-open', 'is-sending' ); openModal.setAttribute( 'aria-hidden', 'true' ); // Restore the submit button. The send path disables it and swaps the // label to "Deactivating…"; closing mid-flight (ESC inside the sendBeacon // release window, or the fallback deadline) would otherwise leave a dead // button behind the next time the modal opens. var submitBtn = qs( '[data-cpfm-df-submit]', openModal ); if ( submitBtn ) { submitBtn.disabled = false; var prevLabel = submitBtn.getAttribute( 'data-cpfm-df-label' ); var labelEl = qs( '.cpfm-df__submit-label', submitBtn ); if ( labelEl && prevLabel ) { labelEl.textContent = prevLabel; } } document.removeEventListener( 'keydown', onKey ); if ( lastFocus && lastFocus.focus ) { lastFocus.focus(); } openModal = null; deactivateUrl = null; } function onKey( e ) { var k = e.key || e.keyCode; if ( 'Escape' === k || 27 === k ) { close(); return; } if ( ( 'Tab' === k || 9 === k ) && openModal ) { trapTab( e ); } } function trapTab( e ) { var focusables = qsa( 'a[href], button:not([disabled]), textarea, input, [tabindex]:not([tabindex="-1"])', openModal ) .filter( function ( el ) { return null !== el.offsetParent; } ); if ( ! focusables.length ) { return; } var first = focusables[ 0 ]; var last = focusables[ focusables.length - 1 ]; if ( e.shiftKey && document.activeElement === first ) { e.preventDefault(); last.focus(); } else if ( ! e.shiftKey && document.activeElement === last ) { e.preventDefault(); first.focus(); } } function wireModal( modal, id ) { qsa( '[data-cpfm-df-close]', modal ).forEach( function ( el ) { el.addEventListener( 'click', function ( e ) { e.preventDefault(); close(); } ); } ); var skip = qs( '[data-cpfm-df-skip]', modal ); if ( skip ) { skip.addEventListener( 'click', function ( e ) { e.preventDefault(); if ( deactivateUrl ) { window.location.href = deactivateUrl; } } ); } var submit = qs( '[data-cpfm-df-submit]', modal ); if ( submit ) { submit.addEventListener( 'click', function ( e ) { e.preventDefault(); doSubmit( modal, id, submit ); } ); } // Picking a reason drops the caret straight into that reason's note, so // people can type without a second click. (Reasons with no note — e.g. // "temporary deactivation" — simply have nothing to focus.) qsa( '.cpfm-df__radio', modal ).forEach( function ( radio ) { radio.addEventListener( 'change', function () { var err = qs( '.cpfm-df__error', modal ); if ( err ) { err.hidden = true; // A choice was made — clear "choose a reason". } // Scope to the ROW, not radio.parentNode: the radio now sits in an // inner