PageSourceSearch

https://www.label-emmaus.co/static/js/alpine/custom/favoris/merchantRailPdp.js?v=204e6a99b7

js label-emmaus.co collected 2026-10-02 05:00:59 UTC 7,344 bytes, 185 lines download raw bytes

1const SLIDE_GAP_PX = 24;
2
3document.addEventListener('alpine:init', () => {
4  Alpine.data('merchantRailPdp', () => ({
5    swiper: null,
6    needsSwiper: false,
7
8    init() {
9      // Conteneur de largeur nulle — rail replié, modale fermée : toute diapositive « déborde »
10      // et `setupSwiper` détruirait le carrousel vivant pour le rebâtir sur 0px, avec des flèches
11      // qui ne bougent rien. On ne remesure pas, l'observateur rejouera au retour de la largeur.
12      this._remeasure = () => {
13        const container = this.$refs.swiperContainer;
14        if (container && container.offsetWidth === 0) return;
15        this.checkSwiperNeeded();
16        this.setupSwiper();
17      };
18      this.$nextTick(() => {
19        this._remeasure();
20        this._watchWidth();
21      });
22      this._onFavorisRemove = () => {
23        // product-card.js detaches the card first; wait for it before pruning rail slides.
24        this.$nextTick(() => this._pruneSlides((slide) => !slide.querySelector('.product-card')));
25      };
26      window.addEventListener('favoris:remove', this._onFavorisRemove);
27      // Le rail du tunnel arme le cœur du storefront et non la poubelle : son retrait passe par
28      // `product-state:selection-change`, qui ne détache aucune carte. Sans cette jambe, un rail
29      // qui ne contient QUE des favoris garde une carte au cœur vide.
30      this._onSelectionChange = (event) => {
31        const detail = (event && event.detail) || {};
32        if (detail.action !== 'remove') return;
33        const productId = Number(detail.productId) || null;
34        const offerId = Number(detail.bestOfferId) || null;
35        if (!productId && !offerId) return;
36        this._pruneSlides((slide) => {
37          const card = slide.querySelector('.product-card');
38          if (!card) return false;
39          if (productId) return Number(card.dataset.productId) === productId;
40          return Number(card.dataset.id) === offerId;
41        });
42      };
43      window.addEventListener('product-state:selection-change', this._onSelectionChange);
44    },
45
46    destroy() {
47      window.removeEventListener('resize', this._remeasure);
48      window.removeEventListener('favoris:remove', this._onFavorisRemove);
49      window.removeEventListener('product-state:selection-change', this._onSelectionChange);
50      if (this._widthObserver) this._widthObserver.disconnect();
51      this.destroySwiper();
52    },
53
54    _pruneSlides(shouldDrop) {
55      const wrapper = this.$refs.swiperWrapper;
56      if (!wrapper) return;
57      let removed = 0;
58      wrapper.querySelectorAll('.favoris-merchant-rail-pdp-slide').forEach((slide) => {
59        if (!shouldDrop(slide)) return;
60        slide.remove();
61        removed += 1;
62      });
63      if (!removed) return;
64      this._decrementCaption(removed);
65      if (wrapper.querySelector('.favoris-merchant-rail-pdp-slide')) {
66        this._remeasure();
67        return;
68      }
69      // Le rail ne survit pas à sa dernière carte : titre, repli et « Voir tout » resteraient
70      // au-dessus d'une bande vide. Swiper part AVANT le détachement — il lit encore ses nœuds.
71      this.destroySwiper();
72      const section = this.$el.closest('section');
73      if (section) section.remove();
74    },
75
76    // Le rail du panier vit sous un repli et celui du tiroir hérité sous une modale fermée : la
77    // largeur revient sans qu'aucun `resize` de fenêtre ne le dise, et le garde de `_remeasure`
78    // aurait laissé le rail sur sa dernière mesure jusqu'au prochain redimensionnement de fenêtre.
79    _watchWidth() {
80      const container = this.$refs.swiperContainer;
81      if (!container) return;
82      // La diapositive fait 174px en dur, sans point de rupture : la largeur du conteneur décide
83      // seule du carrousel, et l'observateur voit tout ce qu'un `resize` de fenêtre aurait dit.
84      if (typeof ResizeObserver === 'undefined') {
85        window.addEventListener('resize', this._remeasure);
86        return;
87      }
88      this._measuredWidth = container.offsetWidth;
89      this._widthObserver = new ResizeObserver(() => {
90        if (container.offsetWidth === this._measuredWidth) return;
91        this._measuredWidth = container.offsetWidth;
92        this._remeasure();
93      });
94      this._widthObserver.observe(container);
95    },
96
97    _decrementCaption(by) {
98      const caption = document.querySelector('[data-favoris-merchant-count]');
99      if (!caption) return;
100      const current = parseInt(caption.dataset.count, 10) || 0;
101      const next = Math.max(0, current - by);
102      if (next === 0) {
103        caption.remove();
104        return;
105      }
106      caption.dataset.count = String(next);
107      const link = caption.querySelector('a');
108      if (!link) return;
109      const tpl = next > 1 ? caption.dataset.tplPlural : caption.dataset.tplSingular;
110      if (tpl) link.textContent = tpl.replace('{count}', String(next));
111    },
112
113    checkSwiperNeeded() {
114      const container = this.$refs.swiperContainer;
115      const wrapper = this.$refs.swiperWrapper;
116      if (!container || !wrapper) {
117        this.needsSwiper = false;
118        return;
119      }
120      const slides = wrapper.querySelectorAll('.favoris-merchant-rail-pdp-slide');
121      if (slides.length === 0) {
122        this.needsSwiper = false;
123        return;
124      }
125      let totalWidth = 0;
126      slides.forEach((slide, idx) => {
127        totalWidth += slide.offsetWidth;
128        if (idx < slides.length - 1) totalWidth += SLIDE_GAP_PX;
129      });
130      this.needsSwiper = totalWidth > container.offsetWidth;
131    },
132
133    setupSwiper() {
134      this.destroySwiper();
135      if (this.needsSwiper) this.initSwiper();
136    },
137
138    initSwiper() {
139      const container = this.$refs.swiperContainer;
140      const wrapper = this.$refs.swiperWrapper;
141      if (!container || !wrapper) return;
142
143      container.classList.add('swiper');
144      wrapper.classList.add('swiper-wrapper');
145      wrapper.querySelectorAll('.favoris-merchant-rail-pdp-slide').forEach((slide) => {
146        slide.classList.add('swiper-slide');
147      });
148
149      const options = {
150        slidesPerView: 'auto',
151        slidesPerGroupAuto: true,
152        spaceBetween: SLIDE_GAP_PX,
153        watchOverflow: true,
154        preventClicks: true,
155        a11y: false,
156        navigation: {
157          nextEl: this.$el.querySelector('.favoris-merchant-rail-pdp-next'),
158          prevEl: this.$el.querySelector('.favoris-merchant-rail-pdp-prev'),
159          disabledClass: 'disabled',
160        },
161        pagination: {
162          el: this.$el.querySelector('.favoris-merchant-rail-pdp-pagination'),
163          clickable: true,
164          bulletClass: 'swiper-pagination-bullet swiper-pagination-bullet-custom',
165          bulletActiveClass: 'swiper-pagination-bullet-active bullet-active-custom',
166        },
167      };
168
169      this.swiper = new Swiper(container, options);
170    },
171
172    destroySwiper() {
173      if (!this.swiper) return;
174      const container = this.$refs.swiperContainer;
175      const wrapper = this.$refs.swiperWrapper;
176      if (container) container.classList.remove('swiper');
177      if (wrapper) wrapper.classList.remove('swiper-wrapper');
178      wrapper?.querySelectorAll('.favoris-merchant-rail-pdp-slide').forEach((slide) => {
179        slide.classList.remove('swiper-slide');
180      });
181      this.swiper.destroy(true, true);
182      this.swiper = null;
183    },
184  }));
185});

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.