1/** 2 * Phyto SystemPay - modal partagee de selection de frequence (slide-in depuis la droite). 3 * Composant sans etat serveur : construit son DOM au premier appel, reutilise ensuite. 4 * Consomme par la fiche produit (Task 3), le panier (Task 2) et le panneau de relance 5 * "Mes abonnements" (Task 10). 6 */ 7(function () { 8 'use strict'; 9 10 var root = null; 11 var currentOptions = null; 12 var closeTimer = null; 13 14 function frequencyId(value) { 15 return 'sp-fm-freq-' + value.replace(/[^a-z0-9]/gi, ''); 16 } 17 18 function escapeHtml(str) { 19 if (typeof str !== 'string') { return ''; } 20 var div = document.createElement('div'); 21 div.textContent = str; 22 return div.innerHTML; 23 } 24 25 function build() { 26 root = document.createElement('div'); 27 root.id = 'phyto-sp-frequency-modal'; 28 root.hidden = true; 29 root.innerHTML = 30 '<div class="sp-fm__overlay" data-sp-fm-dismiss></div>' + 31 '<div class="sp-fm__panel" role="dialog" aria-modal="true" aria-labelledby="sp-fm-title">' + 32 '<div class="sp-fm__header">' + 33 '<h2 class="sp-fm__title" id="sp-fm-title">Personnaliser votre abonnement</h2>' + 34 '<button type="button" class="sp-fm__close" data-sp-fm-dismiss aria-label="Fermer">×</button>' + 35 '</div>' + 36 '<div class="sp-fm__sep"></div>' + 37 '<div class="sp-fm__body">' + 38 '<h3 class="sp-fm__subtitle">Fréquence de livraison</h3>' + 39 '<div class="sp-fm__list"></div>' + 40 '</div>' + 41 '<div class="sp-fm__footer">' + 42 '<button type="button" class="sp-fm__cta"></button>' + 43 '<p class="sp-fm__note" hidden></p>' + 44 '</div>' + 45 '</div>'; 46 document.body.appendChild(root); 47 48 root.addEventListener('click', function (e) { 49 if (e.target.hasAttribute('data-sp-fm-dismiss')) { close(); } 50 }); 51 root.querySelector('.sp-fm__cta').addEventListener('click', function () { 52 if (!currentOptions) { return; } 53 var checked = root.querySelector('input[name="sp-fm-freq"]:checked'); 54 var rrule = checked ? checked.value : currentOptions.currentRrule; 55 var onConfirm = currentOptions.onConfirm; 56 close(); 57 if (typeof onConfirm === 'function') { onConfirm(rrule); } 58 }); 59 } 60 61 function onKeydown(e) { 62 if (e.key === 'Escape') { close(); } 63 } 64 65 function renderList(options) { 66 var list = root.querySelector('.sp-fm__list'); 67 list.innerHTML = ''; 68 (options.frequencies || []).forEach(function (freq) { 69 var id = frequencyId(freq.value); 70 var escapedValue = escapeHtml(freq.value); 71 var escapedLabel = escapeHtml(freq.label); 72 var row = document.createElement('label'); 73 row.className = 'sp-fm__row'; 74 row.setAttribute('for', id); 75 row.innerHTML = 76 '<span class="sp-fm__radio"><input type="radio" name="sp-fm-freq" id="' + id + '" value="' + escapedValue + '"' + 77 (freq.value === options.currentRrule ? ' checked' : '') + '></span>' + 78 '<span class="sp-fm__freq-label">' + escapedLabel + '</span>' + 79 '<span class="sp-fm__freq-days">Choix de nos clients</span>'; 80 list.appendChild(row); 81 }); 82 } 83 84 function formatPrice(cents, sign) { 85 if (typeof cents !== 'number' || !sign) { return ''; } 86 return (cents / 100).toFixed(2).replace('.', ',') + ' ' + sign; 87 } 88 89 function open(options) { 90 if (!root) { build(); } 91 if (closeTimer) { window.clearTimeout(closeTimer); closeTimer = null; } 92 currentOptions = options || {}; 93 renderList(currentOptions); 94 95 var cta = root.querySelector('.sp-fm__cta'); 96 var priceLabel = formatPrice(currentOptions.priceCents, currentOptions.currencySign); 97 var ctaLabel = currentOptions.ctaLabel || 'Confirmer'; 98 cta.textContent = priceLabel ? (priceLabel + ' â ' + ctaLabel) : ctaLabel; 99 100 var note = root.querySelector('.sp-fm__note'); 101 note.textContent = currentOptions.note || ''; 102 note.hidden = !currentOptions.note; 103 104 root.hidden = false;
105 // Force un reflow avant d'ajouter la classe d'ouverture, sinon la transition CSS ne joue pas. 106 void root.offsetWidth; 107 root.classList.add('is-open'); 108 document.body.classList.add('sp-fm-open'); 109 document.addEventListener('keydown', onKeydown); 110 } 111 112 function close() { 113 if (!root || root.hidden) { return; } 114 root.classList.remove('is-open'); 115 document.body.classList.remove('sp-fm-open'); 116 document.removeEventListener('keydown', onKeydown); 117 closeTimer = window.setTimeout(function () { 118 if (root && !root.classList.contains('is-open')) { root.hidden = true; } 119 }, 300); 120 currentOptions = null; 121 } 122 123 window.PhytoSpFrequencyModal = { open: open, close: close }; 124})();
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.