1const FAVORIS_SWAP_TARGETS = new Set([ 2 'favoris-content', 3 'favoris-grid', 4 'favoris-boutique-grid', 5 'favoris-lists-picker-host', 6 'favoris-lists-picker-rows-host', 7]); 8const FAVORIS_GRID_TARGETS = new Set(['favoris-grid', 'favoris-boutique-grid']); 9let favorisFocusInsideSwapTarget = false; 10 11// htmx émet htmx:afterSwap une fois PAR élément top-level du fragment swappé, 12// tous avec la même cible â d'où un garde-fou par xhr pour l'event GA4. 13const ga4TrackedFavorisXhrs = new WeakSet(); 14 15function focusFirstInteractive(target) { 16 const trash = target.querySelector('.favoris-trash-button'); 17 if (trash && typeof trash.focus === 'function') { 18 trash.focus({ preventScroll: true }); 19 return true; 20 } 21 const link = target.querySelector('a[href]'); 22 if (link && typeof link.focus === 'function') { 23 link.focus({ preventScroll: true }); 24 return true; 25 } 26 const emptyState = target.querySelector('.favoris-empty'); 27 if (emptyState && typeof emptyState.focus === 'function') { 28 emptyState.focus({ preventScroll: true }); 29 return true; 30 } 31 const section = document.getElementById('favoris-content'); 32 if (section) { 33 section.focus({ preventScroll: true }); 34 return true; 35 } 36 return false; 37} 38 39htmx.on('htmx:beforeRequest', (event) => { 40 // hx-disabled-elt blurs the trigger to <body> before htmx:beforeSwap, 41 // so capture focus origin here from the requesting element itself. 42 const elt = event?.detail?.elt; 43 if (!elt || typeof elt.closest !== 'function') return; 44 for (const id of FAVORIS_SWAP_TARGETS) { 45 if (elt.closest('#' + id)) { 46 favorisFocusInsideSwapTarget = true; 47 return; 48 } 49 } 50}); 51 52htmx.on('htmx:beforeSwap', (event) => { 53 const target = event?.detail?.target; 54 if (!target || !FAVORIS_SWAP_TARGETS.has(target.id)) return; 55 Alpine.destroyTree(target); 56}); 57 58htmx.on('htmx:afterSwap', (event) => { 59 const target = event?.detail?.target; 60 if (!target || !FAVORIS_SWAP_TARGETS.has(target.id)) { 61 favorisFocusInsideSwapTarget = false;
62 return; 63 } 64 65 // outerHTML swap detaches the original target; the live element is 66 // re-queried by id so focus() lands on a connected node. 67 const liveTarget = document.getElementById(target.id) || target; 68 69 Alpine.initTree(liveTarget); 70 71 if (window.productCardManager) { 72 window.productCardManager.applyFavorisToCards(); 73 window.productCardManager.applyCartToCards(); 74 window.productCardManager.setupEventListeners(); 75 } 76 77 if (liveTarget.id === 'favoris-content') { 78 const xhr = event?.detail?.xhr; 79 if (!xhr || !ga4TrackedFavorisXhrs.has(xhr)) { 80 if (xhr) { 81 ga4TrackedFavorisXhrs.add(xhr); 82 } 83 ga4.pageView(); 84 } 85 liveTarget.focus({ preventScroll: true }); 86 } else if ( 87 FAVORIS_GRID_TARGETS.has(liveTarget.id) && 88 favorisFocusInsideSwapTarget && 89 (document.activeElement === document.body || document.activeElement === null) 90 ) { 91 focusFirstInteractive(liveTarget); 92 } 93 94 favorisFocusInsideSwapTarget = false; 95}); 96 97htmx.on('htmx:afterSettle', (event) => { 98 const target = event?.detail?.target; 99 if (!target) return; 100 if (target.id === 'favoris-lists-picker-host') { 101 window.dispatchEvent( 102 new CustomEvent('toggle-favoris-lists-picker-modal', { 103 detail: { triggerSelector: '#favoris-lists-picker-trigger' }, 104 }) 105 ); 106 return; 107 } 108 if (target.id === 'favoris-lists-picker-rows-host') { 109 const form = document.getElementById('favoris-lists-picker-create-form'); 110 if (form) form.reset(); 111 const heading = document.getElementById('favoris-lists-picker-heading'); 112 if (heading) heading.focus(); 113 } 114}); 115 116htmx.on('htmx:confirm', (event) => { 117 const form = event?.detail?.elt?.closest('form[data-list-name]'); 118 if (!form) return; 119 event.preventDefault(); 120 window.dispatchEvent( 121 new CustomEvent('open-delete-list-confirm', { 122 detail: { 123 name: form.dataset.listName, 124 message: event.detail.question, 125 issueRequest: () => event.detail.issueRequest(true), 126 }, 127 }) 128 ); 129}); 130 131htmx.on('htmx:oobBeforeSwap', (event) => { 132 const target = event?.detail?.target; 133 if (!target || target.id !== 'favoris-filter-bar') return; 134 Alpine.destroyTree(target); 135}); 136 137htmx.on('htmx:oobAfterSwap', (event) => { 138 const target = event?.detail?.target; 139 if (!target || target.id !== 'favoris-filter-bar') return; 140 Alpine.initTree(target); 141});
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.