1const SECTION_MODE_TO_SKELETON_MODE = { 2 tous: 'all', 3 merchants: 'by_merchant', 4 boutique: 'boutique', 5}; 6 7const SKELETON_DIMENSION_SELECTORS = { 8 sortingWidth: '#favoris-sort-select', 9 sortingHeight: '#favoris-sort-select', 10 totalHitsWidth: '#favoris-filtered-count, #favoris-boutique-count', 11 totalHitsHeight: '#favoris-filtered-count, #favoris-boutique-count', 12 promoToggleWidth: '#favoris-promo-toggle', 13 promoToggleHeight: '#favoris-promo-toggle', 14 soldToggleWidth: '#favoris-sold-toggle', 15 soldToggleHeight: '#favoris-sold-toggle', 16 searchWidth: '#favoris-search', 17 searchHeight: '#favoris-search', 18}; 19 20const PRESENCE_SELECTORS = { 21 hasCategoryChips: '#favoris-category-chips', 22 hasPromoToggle: '#favoris-promo-toggle', 23 hasSoldToggle: '#favoris-sold-toggle', 24 hasSortSelect: '#favoris-sort-select', 25 hasTotalHits: '#favoris-filtered-count, #favoris-boutique-count', 26 hasSearch: '#favoris-search', 27}; 28 29document.addEventListener('alpine:init', () => { 30 Alpine.data('skeletonFavoris', () => ({ 31 sortingWidth: '0px', 32 sortingHeight: '0px', 33 totalHitsWidth: '0px', 34 totalHitsHeight: '0px', 35 promoToggleWidth: '0px', 36 promoToggleHeight: '0px', 37 soldToggleWidth: '0px', 38 soldToggleHeight: '0px', 39 searchWidth: '0px', 40 searchHeight: '0px', 41 chipWidths: [], 42 chipHeight: '0px', 43 headerOffset: '0px', 44 hasCategoryChips: false, 45 hasPromoToggle: false, 46 hasSoldToggle: false, 47 hasSortSelect: false, 48 hasTotalHits: false, 49 hasSearch: false, 50 mode: 'all', 51 loading: false, 52 53 init() { 54 const declaredMode = this.$el.dataset.mode; 55 if (declaredMode) { 56 this.mode = declaredMode; 57 } 58 59 this._boundCompute = this.computeDimensions.bind(this); 60 this._boundAfterSwap = this._handleAfterSwap.bind(this); 61 this._boundResize = this._handleResize.bind(this); 62 this._resizeRaf = null; 63 this._resizeObserver = typeof ResizeObserver === 'function' ? new ResizeObserver(this._boundCompute) : null; 64 65 this.computeDimensions(); 66 setTimeout(this._boundCompute, 500); 67 this._observeFilterBar(); 68 69 document.addEventListener('htmx:afterSwap', this._boundAfterSwap); 70 window.addEventListener('resize', this._boundResize, { passive: true }); 71 72 this.$el.addEventListener('alpine:destroy', () => { 73 document.removeEventListener('htmx:afterSwap', this._boundAfterSwap); 74 window.removeEventListener('resize', this._boundResize); 75 if (this._resizeObserver) { 76 this._resizeObserver.disconnect(); 77 } 78 if (this._resizeRaf) { 79 cancelAnimationFrame(this._resizeRaf); 80 } 81 }); 82 }, 83 84 _observeFilterBar() { 85 if (!this._resizeObserver) return; 86 const bar = document.getElementById('favoris-filter-bar'); 87 this._resizeObserver.disconnect(); 88 if (bar) { 89 this._resizeObserver.observe(bar); 90 } 91 }, 92 93 _handleResize() { 94 if (this._resizeRaf) return; 95 this._resizeRaf = requestAnimationFrame(() => { 96 this._resizeRaf = null; 97 this.computeDimensions(); 98 }); 99 }, 100 101 _handleAfterSwap(event) { 102 const target = event?.detail?.target; 103 if (target && target.id === 'favoris-content') { 104 const section = document.getElementById('favoris-content'); 105 if (section) { 106 const sectionMode = section.dataset.mode; 107 const mapped = SECTION_MODE_TO_SKELETON_MODE[sectionMode]; 108 if (mapped) { 109 this.mode = mapped; 110 } 111 } 112 } 113 114 this.loading = false; 115 this._observeFilterBar(); 116 this.computeDimensions(); 117 }, 118 119 computeDimensions() { 120 const measurements = {};
121 Object.entries(SKELETON_DIMENSION_SELECTORS).forEach(([key, selector]) => { 122 const element = document.querySelector(selector); 123 if (!element) return; 124 const rect = element.getBoundingClientRect(); 125 measurements[key] = key.endsWith('Width') ? Math.ceil(rect.width) : Math.ceil(rect.height); 126 }); 127 128 const presence = {}; 129 Object.entries(PRESENCE_SELECTORS).forEach(([key, selector]) => { 130 const el = document.querySelector(selector); 131 presence[key] = key === 'hasCategoryChips' ? Boolean(el && el.children.length > 0) : Boolean(el); 132 }); 133 134 const chipWidths = []; 135 let chipHeightPx = 0; 136 const chipsContainer = document.getElementById('favoris-category-chips'); 137 if (chipsContainer) { 138 Array.from(chipsContainer.children).forEach((chip) => { 139 const rect = chip.getBoundingClientRect(); 140 chipWidths.push(Math.ceil(rect.width)); 141 chipHeightPx = Math.max(chipHeightPx, Math.ceil(rect.height)); 142 }); 143 } 144 145 let headerOffsetPx = 0; 146 const host = document.getElementById('favoris-skeleton-host'); 147 const filterBar = document.getElementById('favoris-filter-bar'); 148 if (host && filterBar) { 149 const hostRect = host.getBoundingClientRect(); 150 const barRect = filterBar.getBoundingClientRect(); 151 headerOffsetPx = Math.max(0, Math.ceil(barRect.top - hostRect.top)); 152 } 153 154 requestAnimationFrame(() => { 155 Object.entries(measurements).forEach(([key, value]) => { 156 this[key] = `${value}px`; 157 }); 158 Object.entries(presence).forEach(([key, value]) => { 159 this[key] = value; 160 }); 161 this.chipWidths = chipWidths; 162 this.chipHeight = `${chipHeightPx}px`; 163 this.headerOffset = `${headerOffsetPx}px`; 164 }); 165 }, 166 })); 167});
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.