1/** 2 * Backup Suit Size Visibility + Length Visibility â standalone add-on. 3 * 4 * Loaded as a separate <script> AFTER the main Vite bundle, because the 5 * committed/live bundle cannot be reproduced from repo sources (src<->prod 6 * drift), so we must not rebuild/replace it. All behaviour here is self-contained 7 * jQuery on WordPress' global jQuery â it does not depend on bundle internals. 8 * 9 * - Length visibility: fills the server-rendered hidden <span data-length-garment> 10 * placeholders from the customer-height field. On the cart page there is no 11 * height input, so the value comes from the checkout form's sessionStorage copy. 12 * - Backup preview: on the product page, previews the backup size (next option in 13 * the size <select>; previous one when the largest is selected). 14 * 15 * The backup SIZE lines in cart/checkout/minicart/thank-you are rendered 16 * server-side (PHP) and need no JS. With more than one suit in the cart the 17 * server renders no placeholders at all, so this script is a no-op there. 18 */ 19(function ($) { 20 'use strict'; 21 22 if (typeof $ === 'undefined') { 23 return; 24 } 25 26 // --- Length visibility ------------------------------------------------- 27 28 // Mirrors PHP suitor_get_assigned_length() in inc/woocommerce-customisation/length-visibility.php. 29 function assignedLength(garment, heightValue) { 30 var raw = String(heightValue == null ? '' : heightValue).trim(); 31 if (!/^\d+$/.test(raw)) return null; 32 var height = parseInt(raw, 10); 33 if (height < 20 || height > 250) return null; 34 if (garment === 'jacket') return height >= 188 ? 'Long' : 'Regular'; 35 if (garment === 'trousers') { 36 if (height >= 188) return 'Long'; 37 return height >= 177 ? 'Regular' : 'Short'; 38 } 39 return null; 40 } 41 42 // The checkout form (incl. height) is persisted to sessionStorage on every 43 // change; the cart page has no height input, so lengths there are resolved 44 // from that stored copy. A corrupt entry must not break the rest of init. 45 function storedCheckoutHeight() { 46 try { 47 var formStr = sessionStorage.getItem('checkoutForm'); 48 if (!formStr) return ''; 49 var parsed = JSON.parse(formStr); 50 if (!Array.isArray(parsed)) return ''; 51 for (var i = 0; i < parsed.length; i++) { 52 if (parsed[i] && parsed[i].name === 'billing_customer_height') { 53 return parsed[i].value; 54 } 55 } 56 return ''; 57 } catch (e) { 58 return ''; 59 } 60 } 61 62 function updateLengthVisibility() { 63 var $input = $('input[name="billing_customer_height"]'); 64 var height = $input.length ? $input.val() : storedCheckoutHeight(); 65 $('span[data-length-garment]').each(function () { 66 var $span = $(this); 67 var length = assignedLength($span.attr('data-length-garment'), height); 68 // The placeholder carries an info icon as a sibling of the value span â 69 // write into .js-length-value, never into the wrapper, or the icon dies. 70 var $value = $span.find('.js-length-value'); 71 var $target = $value.length ? $value : $span; 72 if (length === null) { 73 $target.text(''); 74 $span.attr('hidden', 'hidden'); 75 } else { 76 $target.text(' ' + length); 77 $span.removeAttr('hidden'); 78 } 79 // Keep the row's native tooltip in sync with the shown length. 80 // data-title-base caches the original so this stays idempotent. 81 var $line = $span.closest('p[title]'); 82 if ($line.length) { 83 if (typeof $line.attr('data-title-base') === 'undefined') { 84 $line.attr('data-title-base', $line.attr('title') || ''); 85 } 86 var baseTitle = $line.attr('data-title-base'); 87 $line.attr('title', length === null ? baseTitle : (baseTitle + ' ' + length).replace(/^\s+|\s+$/g, '')); 88 } 89 }); 90 } 91 92 // --- Backup size preview (product page) -------------------------------- 93 94 // Next option in the select; previous one when the largest is selected. 95 function updateBackupSizePreview($form) { 96 var $box = $form.find('.js-backup-size-preview'); 97 if (!$box.length) return; 98 var entries = []; 99 $form.find('select[name^="size["]').each(function () { 100 var $select = $(this); 101 var name = ($select.attr('name') || '').toLowerCase(); 102 if (name.indexOf('length') !== -1) return; 103 if (name.indexOf('jacket') === -1 && name.indexOf('trouser') === -1) return; 104 var $options = $select.find('option').filter(function () { 105 return $(this).attr('value'); 106 }); 107 var idx = $options.index($select.find('option:selected')); 108 if (idx === -1 || $options.length < 2) return;
109 var backupIdx = idx === $options.length - 1 ? idx - 1 : idx + 1; 110 // option value format: "M/40|Jacket/Shirt" 111 var parts = $options.eq(backupIdx).attr('value').split('|'); 112 if (parts.length < 2) return; 113 // Composite bundles name the attribute 'Jacket/Shirt', so the row needs 114 // ' Size' appended. Simple products already call it 'Jacket Size' â 115 // appending there produced 'Backup Jacket Size Size'. 116 var label = parts[1].replace('Jacket/Shirt', 'Jacket'); 117 entries.push({ label: label, needsSize: !/\s*size\s*$/i.test(label), size: parts[0] }); 118 }); 119 // Build DOM with .text() so option values can never inject markup. 120 $box.empty(); 121 if (entries.length) { 122 entries.forEach(function (e) { 123 $('<span class="d-block"></span>') 124 .text('Backup ' + e.label + (e.needsSize ? ' Size' : '') + ': ' + e.size) 125 .appendTo($box); 126 }); 127 } else { 128 // The card is always visible now, so it needs a resting state 129 // instead of being hidden until sizes are picked. 130 $('<span class="d-block backupsuit-placeholder"></span>') 131 .text('Select your sizes above') 132 .appendTo($box); 133 } 134 } 135 136 // --- Bindings ---------------------------------------------------------- 137 138 // The customer-height field is a plain <input>, so real typing fires native 139 // input/change events that our (global) jQuery hears. Delegated for safety. 140 $(document).on('input change', 'input[name="billing_customer_height"]', updateLengthVisibility); 141 142 // The cart table is replaced wholesale by WooCommerce AJAX (quantity change, 143 // item removal); the fresh markup needs filling again. 144 $(document.body).on('updated_wc_div updated_cart_totals', updateLengthVisibility); 145 146 // The size <select> is wrapped in the theme's custom-select widget. When a 147 // user picks an option the theme calls `$select.trigger('change')` on ITS OWN 148 // bundled jQuery instance â a synthetic event our separate jQuery never hears, 149 // and no native change is dispatched. So listen for the native click on the 150 // styled dropdown option instead. Capture phase: the theme's own li handler 151 // calls stopPropagation(), which would kill a bubbling listener. Defer the 152 // read so it runs after the theme has updated the underlying <select> value. 153 document.addEventListener('click', function (e) { 154 var li = e.target && e.target.closest ? e.target.closest('ul.select-options li') : null; 155 if (!li) return; 156 var form = li.closest('form.addtocart-form'); 157 if (!form) return; 158 setTimeout(function () { updateBackupSizePreview($(form)); }, 0); 159 }, true); 160 161 // Fallback for any path that does dispatch a native change on the select. 162 $(document).on('change', 'form.addtocart-form select[name^="size["]', function () { 163 updateBackupSizePreview($(this).closest('form')); 164 }); 165 166 function init() { 167 updateLengthVisibility(); 168 $('form.addtocart-form').each(function () { 169 updateBackupSizePreview($(this)); 170 }); 171 } 172 173 $(function () { init(); }); 174 // Re-run after full load and shortly after, to catch the bundle's 175 // sessionStorage restore of the height field (which may setDate/val without 176 // firing an event we can hear). 177 $(window).on('load', function () { 178 init(); 179 setTimeout(init, 500); 180 setTimeout(init, 1200); 181 }); 182})(window.jQuery);
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.