1/** 2 * WP EasyCart: account subscription pages ( 6.0.0 ). 3 * 4 * Loaded after ec-store.js as its own file so stores with a custom copy of ec-store.js still get it. 5 * Replaces ec_show_update_subscription_payment() / ec_show_update_subscription_details() with versions that 6 * keep the legacy toggles for older template copies and, on the 6.0.0 card layout ( .ec_account_subscription_v2 ), 7 * open one panel at a time, scroll it into view below any fixed or sticky site header and focus its heading. 8 * The account page can be loaded by AJAX, so every control uses delegated events. 9 */ 10( function( $, window, document ) { 11 'use strict'; 12 13 if ( ! $ ) { 14 return; 15 } 16 17 var PANELS = { 18 payment: { panel: '.ec_account_subscription_details_payment_form', trigger: '.ec_account_subscription_details_card_change' }, 19 plan: { panel: '.ec_account_subscription_upgrade_row', trigger: '.ec_account_subscription_details_plan_change' } 20 }; 21 22 function prefersReducedMotion() { 23 return !! ( window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches ); 24 } 25 26 function isPinned( el ) { 27 var position; 28 try { 29 position = window.getComputedStyle( el ).position; 30 } catch ( e ) { 31 return false; 32 } 33 return 'fixed' === position || 'sticky' === position || '-webkit-sticky' === position; 34 } 35 36 /** 37 * Height covered at the top of the viewport by fixed / sticky elements ( site headers, WP admin bar ). 38 * Samples points across the top edge and walks up from each hit to a pinned ancestor, then repeats 39 * below what it found so stacked bars ( announcement bar + nav ) add up. 40 */
41 function topOffset() { 42 var offset = 0; 43 var viewportWidth = window.innerWidth || document.documentElement.clientWidth; 44 var viewportHeight = window.innerHeight || document.documentElement.clientHeight; 45 var adminBar = document.getElementById( 'wpadminbar' ); 46 if ( adminBar && isPinned( adminBar ) ) { 47 var barRect = adminBar.getBoundingClientRect(); 48 if ( barRect.bottom > 0 && barRect.height > 0 ) { 49 offset = Math.max( offset, barRect.bottom ); 50 } 51 } 52 if ( ! document.elementFromPoint ) { 53 return offset; 54 } 55 var xs = [ 12, Math.round( viewportWidth / 2 ), viewportWidth - 12 ]; 56 for ( var pass = 0; pass < 4; pass++ ) { 57 var found = offset; 58 var y = Math.min( offset + 1, viewportHeight - 1 ); 59 for ( var i = 0; i < xs.length; i++ ) { 60 var el = document.elementFromPoint( xs[ i ], y ); 61 while ( el && el !== document.body && el !== document.documentElement ) { 62 if ( isPinned( el ) ) { 63 var rect = el.getBoundingClientRect(); 64 /* Ignore full-screen overlays and anything not actually touching the top area. */ 65 if ( rect.height > 0 && rect.height < viewportHeight * 0.5 && rect.top <= y + 1 && rect.bottom > found ) { 66 found = rect.bottom; 67 } 68 break; 69 } 70 el = el.parentElement; 71 } 72 } 73 if ( found <= offset ) { 74 break; 75 } 76 offset = found; 77 } 78 return Math.max( 0, Math.round( offset ) ); 79 } 80 81 /** Scroll only when the panel is not already comfortably on screen; then focus its heading. */ 82 function revealPanel( panel ) { 83 if ( ! panel || ! panel.getBoundingClientRect ) { 84 return; 85 } 86 var gap = 16; 87 var offset = topOffset(); 88 var rect = panel.getBoundingClientRect(); 89 var viewportHeight = window.innerHeight || document.documentElement.clientHeight; 90 var available = viewportHeight - offset; 91 var fullyVisible = rect.top >= offset && rect.bottom <= viewportHeight; 92 var tallButTopVisible = rect.height > available && rect.top >= offset && rect.top <= offset + available / 3; 93 if ( ! fullyVisible && ! tallButTopVisible ) { 94 var target = Math.max( 0, ( window.pageYOffset || document.documentElement.scrollTop ) + rect.top - offset - gap ); 95 try { 96 window.scrollTo( { top: target, behavior: prefersReducedMotion() ? 'auto' : 'smooth' } ); 97 } catch ( e ) { 98 window.scrollTo( 0, target ); 99 } 100 } 101 var heading = panel.querySelector( '.ec_account_subscription_v2_panel_title' ); 102 if ( heading ) { 103 try { 104 heading.focus( { preventScroll: true } ); 105 } catch ( e ) { 106 heading.focus(); 107 } 108 } 109 } 110 111 function openPanel( key ) { 112 var other = 'payment' === key ? 'plan' : 'payment'; 113 var $panel = $( PANELS[ key ].panel ); 114 if ( ! $panel.closest( '.ec_account_subscription_v2' ).length ) { 115 /* Older template copies: the toggles as they always were. */ 116 $panel.show(); 117 $( PANELS[ key ].trigger ).hide(); 118 $( PANELS[ other ].panel ).hide(); 119 $( PANELS[ other ].trigger ).show(); 120 return false; 121 } 122 $( PANELS[ other ].panel ).hide(); 123 $( PANELS[ other ].trigger ).attr( 'aria-expanded', 'false' ).removeClass( 'is-open' ); 124 $panel.show(); 125 $( PANELS[ key ].trigger ).attr( 'aria-expanded', 'true' ).addClass( 'is-open' ); 126 if ( 'plan' === key ) { 127 $panel.each( function() { 128 planState( this ); 129 } ); 130 } 131 revealPanel( $panel.get( 0 ) ); 132 return false; 133 } 134 135 function closePanel( key ) { 136 var $panel = $( PANELS[ key ].panel ); 137 $panel.hide(); 138 var $trigger = $( PANELS[ key ].trigger ).attr( 'aria-expanded', 'false' ).removeClass( 'is-open' ).show(); 139 if ( $trigger.length ) { 140 try { 141 $trigger.get( 0 ).focus( { preventScroll: true } ); 142 } catch ( e ) { 143 $trigger.get( 0 ).focus(); 144 } 145 } 146 return false; 147 } 148 149 /** Enable Save only when the plan or quantity changed and the quantity is valid. */ 150 function planState( panel ) { 151 var $panel = $( panel ); 152 var currentPlan = String( $panel.attr( 'data-current-plan' ) || '' ); 153 var currentQuantity = parseInt( $panel.attr( 'data-current-quantity' ), 10 ) || 1;
154 var plan = String( $panel.find( 'input[name="ec_selected_plan"]' ).val() || currentPlan ); 155 var $quantity = $panel.find( 'input.ec_account_subscription_v2_qty' ); 156 var quantity = currentQuantity; 157 var valid = true; 158 if ( $quantity.length ) { 159 var raw = $.trim( String( $quantity.val() ) ); 160 quantity = parseInt( raw, 10 ); 161 valid = /^\d+$/.test( raw ) && quantity >= 1 && quantity <= 1000000; 162 $panel.find( '.ec_account_subscription_v2_qty_error' ).prop( 'hidden', valid ); 163 $quantity.attr( 'aria-invalid', valid ? 'false' : 'true' ); 164 $panel.find( '[data-ec-sub-step="-1"]' ).prop( 'disabled', valid && quantity <= 1 ); 165 } 166 var changed = plan !== currentPlan || ( valid && quantity !== currentQuantity ); 167 $panel.find( '[data-ec-sub-save="plan"]' ).prop( 'disabled', ! ( changed && valid ) ); 168 return changed && valid; 169 } 170 171 window.ec_show_update_subscription_payment = function() { 172 return openPanel( 'payment' ); 173 }; 174 175 window.ec_show_update_subscription_details = function() { 176 return openPanel( 'plan' ); 177 }; 178 179 window.ec_account_subscription_close_panel = function( key ) { 180 return closePanel( 'plan' === key ? 'plan' : 'payment' ); 181 }; 182
183 /** Plan Save: validate, then hand over to ec_update_subscription_info() ( ec-store.js ). */ 184 window.ec_account_subscription_save_plan = function( button, subscription_id, nonce ) { 185 var panel = $( button ).closest( '.ec_account_subscription_upgrade_row' ).get( 0 ); 186 if ( panel && ! planState( panel ) ) { 187 return false; 188 } 189 $( button ).prop( 'disabled', true ); 190 return window.ec_update_subscription_info( subscription_id, nonce ); 191 }; 192 193 $( document ).on( 'change', '.ec_account_subscription_v2_plans input[type="radio"]', function() { 194 var $panel = $( this ).closest( '.ec_account_subscription_upgrade_row' ); 195 $panel.find( 'input[name="ec_selected_plan"]' ).val( this.value ); 196 $panel.find( '.ec_account_subscription_v2_plan' ).removeClass( 'is-selected' ); 197 $( this ).closest( '.ec_account_subscription_v2_plan' ).addClass( 'is-selected' ); 198 planState( $panel.get( 0 ) ); 199 } ); 200 201 $( document ).on( 'click', '[data-ec-sub-step]', function( event ) { 202 event.preventDefault(); 203 var $panel = $( this ).closest( '.ec_account_subscription_upgrade_row' ); 204 var $quantity = $panel.find( 'input.ec_account_subscription_v2_qty' ); 205 var value = parseInt( $quantity.val(), 10 ); 206 value = ( isNaN( value ) ? 1 : value ) + parseInt( $( this ).attr( 'data-ec-sub-step' ), 10 ); 207 value = Math.min( 1000000, Math.max( 1, value ) ); 208 $quantity.val( value ); 209 planState( $panel.get( 0 ) ); 210 } ); 211 212 $( document ).on( 'input change', '.ec_account_subscription_v2 input.ec_account_subscription_v2_qty', function() { 213 planState( $( this ).closest( '.ec_account_subscription_upgrade_row' ).get( 0 ) ); 214 } ); 215 216 /* Enter in the plan panel would submit the shared form into the card update; run Save instead. */ 217 $( document ).on( 'keydown', '.ec_account_subscription_v2 .ec_account_subscription_upgrade_row input', function( event ) { 218 if ( 13 === event.which ) { 219 event.preventDefault(); 220 var $save = $( this ).closest( '.ec_account_subscription_upgrade_row' ).find( '[data-ec-sub-save="plan"]' ); 221 if ( $save.length && ! $save.prop( 'disabled' ) ) { 222 $save.trigger( 'click' ); 223 } 224 } 225 } ); 226 227 $( document ).on( 'click', '[data-ec-sub-close]', function( event ) { 228 event.preventDefault(); 229 closePanel( 'plan' === $( this ).attr( 'data-ec-sub-close' ) ? 'plan' : 'payment' ); 230 } ); 231 232 $( document ).on( 'keydown', '.ec_account_subscription_v2 .ec_account_subscription_v2_panel', function( event ) { 233 if ( 27 === event.which ) { 234 closePanel( $( this ).is( PANELS.plan.panel ) ? 'plan' : 'payment' ); 235 } 236 } ); 237} )( window.jQuery, window, document );
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.