PageSourceSearch

https://www.lidl.es/s/storesearch-frontend/26_20_3/storeselector-flyer-addon.js

js lidl.es collected 2026-10-01 10:20:39 UTC 25,600 bytes, 726 lines download raw bytes

1import { selectAppLocale } from "./store-search-utils.js";
2
3const template = document.createElement("template");
4template.innerHTML = `
5<style>
6   .lirt-storeselector-flyer-addon {
7        --background-brand: #F5F8FC;
8        --background-white: #F5F5F5;
9        --gray-darker: #636D79;
10        --success: #00704E;
11        --danger: #E60A14;
12        --primary: #0050AA;
13        --warning: #a84700;
14    
15        display: flex;
16        height: 2.5rem;
17        padding: 0 0.5rem;
18        align-items: center;
19        justify-content: center;
20    }   
21    
22    .lirt-storeselector-flyer-addon__setStore,
23    .lirt-storeselector-flyer-addon__storeInfo {
24        display: flex;
25        flex-direction: row;
26        align-items: center;
27        justify-content: center;
28        width: calc(100vw - 1rem);
29        max-width: 21.5rem;
30        padding: 0.5rem;
31        gap: 0.25rem;
32        border-radius: 0.625rem;
33        background-color: unset;
34        border: none;
35        cursor: pointer;        
36        
37        &:focus-visible {
38            position: relative;
39            outline: none;
40        }
41        
42        &:focus-visible:before {
43            content: "";
44            display: block;
45            width: 100%;
46            height: 100%;
47            position: absolute;
48            outline: solid .125rem #ffffff;
49            outline-offset: .25rem;
50            pointer-events: none;
51            left: 0;
52            top: 0;
53        }
54        
55        &:focus-visible:after {
56            content: "";
57            display: block;
58            width: 100%;
59            height: 100%;
60            position: absolute;
61            outline: solid .125rem var(--primary);
62            outline-offset: .125rem;
63            pointer-events: none;
64            left: 0;
65            top: 0;
66        }
67    }
68    
69    @media (min-width: 768px) {
70        .lirt-storeselector-flyer-addon__setStore,
71        .lirt-storeselector-flyer-addon__storeInfo {
72            width: 24rem;
73            max-width: unset;
74        }
75    }
76    @media (min-width: 1024px) {
77        .lirt-storeselector-flyer-addon__setStore,
78        .lirt-storeselector-flyer-addon__storeInfo {
79            width: 29rem;
80        }
81    }
82    
83    @media (min-width: 1440px) {
84        .lirt-storeselector-flyer-addon__setStore,
85        .lirt-storeselector-flyer-addon__storeInfo {
86            width: 39.125rem;
87        }
88    }
89    
90    :host([wonheader]) .lirt-storeselector-flyer-addon__setStore,
91    :host([wonheader]) .lirt-storeselector-flyer-addon__storeInfo {
92        width: auto;
93        padding: 0.5rem 1rem;
94        gap 0.5rem;
95        background-color: #f0f7ff;
96        border-radius: 62.4375rem;
97        
98        @media (min-width: 768px) {
99            padding: 0.75rem 1rem;
100        }
101    }
102
103    /* When the component has the 'wonheader' attribute, left-align the content */
104    :host([wonheader]) .lirt-storeselector-flyer-addon__setStore,
105    :host([wonheader]) .lirt-storeselector-flyer-addon__storeInfo {
106        justify-content: flex-start;
107    }
108
109    /* Truncate setStore headline only when 'wonheader' is active */
110    :host([wonheader]) .lirt-storeselector-flyer-addon__setStore-text {
111        /* Allow text container to take remaining space and clip overflow in flex layout */
112        min-width: 0; /* required for text-overflow inside flex item */
113        width: 100%;
114        overflow: hidden;        
115    }
116    
117    :host([wonheader]) .lirt-storeselector-flyer-addon__storeInfo {
118      white-space: nowrap;
119    }
120    
121    :host([wonheader]) .lirt-storeselector-flyer-addon__setStoreHeadline,
122    :host([wonheader]) .lirt-storeselector-flyer-addon__storeInfoHeadline {
123        display: block; /* ensure ellipsis works reliably */
124        text-align: left;
125        white-space: nowrap;
126        overflow: hidden;
127        text-overflow: ellipsis;
128    }
129    
130    :host([wonheader]) .lirt-storeselector-flyer-addon__storeInfoHeadline {
131        min-width: 2.5rem;
132        max-width: 10rem; 
133        
134        @media (min-width: 1440px) {
135            max-width: 14rem;
136        }
137    }
138    
139    :host([wonheader]) .lirt-storeselector-flyer-addon__setStore,
140    :host([wonheader]) .lirt-storeselector-flyer-addon__storeInfo {
141        @media (min-width: 768px) {
142        max-width: 16rem;
143        }
144        
145        @media (min-width: 1440px) {
146        max-width: 24rem;
147        }
148    }    
149    
150    :host([wonheader]) .lirt-storeselector-flyer-addon__addInfo,
151    :host([wonheader]) .lirt-storeselector-flyer-addon__separator {
152      display: none;
153    }
154    
155    :host([wonheader]) .lirt-storeselector-flyer-addon {
156        width: 100%;
157        padding: 0;
158    }
159    
160    .lirt-storeselector-flyer-addon__storeInfo-text {
161        display: flex;
162        overflow: hidden;
163        gap: 0.25rem;
164        align-items: center;
165        
166        @media (min-width: 1440px) {
167            gap: 0.5rem;
168        }
169    }
170    
171    .lirt-storeselector-flyer-addon__setStoreHeadline,
172    .lirt-storeselector-flyer-addon__storeInfoHeadline {           
173        font-family: 'LidlFontPro';
174        font-style: normal;
175        font-weight: 400;
176        font-size: 0.688rem;
177        line-height: 1rem;
178        color: var(--primary);
179        text-decoration: underline;
180        
181        @media (min-width: 1440px) {
182            font-size: 0.813rem;
183            line-height: 1.25rem;
184        }
185    }
186    
187    .lirt-storeselector-flyer-addon__storeInfoHeadline {
188       
189        white-space: nowrap;
190        overflow: hidden;
191        text-overflow: ellipsis;
192        flex-shrink: 1;
193    }   
194            
195    .lirt-storeselector-flyer-addon__storeInfoSubheadline {
196        display: flex;
197        flex-shrink: 0;
198        gap: 0.25rem;
199    }
200    
201    .lirt-storeselector-flyer-addon__setStore-icons,
202    .lirt-storeselector-flyer-addon__storeInfo-icons {
203        height: 1rem;
204        flex-shrink: 0;
205        fill: var(--primary);
206    }
207      
208    .lirt-storeselector-flyer-addon__status,
209    .lirt-storeselector-flyer-addon__separator,
210    .lirt-storeselector-flyer-addon__addInfo {
211        font-family: 'LidlFontPro';
212        font-style: normal;
213        font-size: 0.688rem;
214        line-height: 1rem;
215        
216        @media (min-width: 1440px) {
217            font-size: 0.813rem;
218            line-height: 1.25rem;
219        }
220    }
221    
222    .lirt-storeselector-flyer-addon__status,
223    .lirt-storeselector-flyer-addon__addInfo {
224        white-space: nowrap;
225    }
226    
227    .lirt-storeselector-flyer-addon__status {
228        font-weight: 400;
229    }
230    
231    .lirt-storeselector-flyer-addon__separator,
232    .lirt-storeselector-flyer-addon__addInfo {
233        font-weight: 400;
234    
235    }
236    
237    .lirt-storeselector-flyer-addon__status--open {
238        color: var(--success);
239    }
240    
241    .lirt-storeselector-flyer-addon__status--closed {
242        color: var(--danger);
243    }
244
245    .lirt-storeselector-flyer-addon__status--closingSoon {
246        color: var(--warning);
247    }
248    
249    .hidden\\@all {
250        display: none;
251    }
252</style>
253<article class="lirt-storeselector-flyer-addon">
254    <button class="lirt-storeselector-flyer-addon__setStore" part="set-store">
255        <svg role="img" class="lirt-storeselector-flyer-addon__setStore-icons" part="icon" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 24 24">
256            <path d="M12 2c3.584 0 6.5 2.916 6.5 6.5 0 3.685-4.07 9.51-6.5 12.792C9.57 18.01 5.5 12.185 5.5 8.5 5.5 4.916 8.416 2 12 2m0-1a7.5 7.5 0 0 0-7.5 7.5c0 4.142 4.286 10.179 7.5 14.464 3.214-4.286 7.5-10.322 7.5-14.464A7.5 7.5 0 0 0 12 1z"/>
257            <path d="M12 12.5c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4zm0-7c-1.654 0-3 1.346-3 3s1.346 3 3 3 3-1.346 3-3-1.346-3-3-3z"/>
258        </svg>
259        <span class="lirt-storeselector-flyer-addon__setStore-text">
260            <span class="lirt-storeselector-flyer-addon__setStoreHeadline" part="text"></span>
261        </span>
262    </button>
263
264    <button class="lirt-storeselector-flyer-addon__storeInfo hidden@all">
265
266        <span class="lirt-storeselector-flyer-addon__storeInfo-text">
267            <svg role="img" class="lirt-storeselector-flyer-addon__storeInfo-icons" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
268                <defs>
269                    <path d="M18.5881634,55.3343014 L18.5881634,55.3343014 C19.2657532,56.2218995 20.643519,56.2218995 21.3211088,55.3343014 L21.3211088,55.3343014 C33.4273793,39.5090762 40,29.0635616 40,19.2133872 C40,8.63798019 31.0445219,0.032608061 20.0223951,6.73387979e-05 C7.14818954,-0.0323381438 -1.98797913,11.63633 0.372291886,23.7596215 C2.04367997,32.3649937 8.32267847,41.9120858 18.5881634,55.3343014 Z" id="path-1"></path>
270                    <path d="M21.4105263,1.67480719 C19.92,0.372627585 18.0589474,-0.198132955 16.1726316,0.0682219635 L16.1263158,0.0766776752 C15.9789474,0.0978169544 15.8315789,0.127411945 15.6884211,0.157006936 C14.2736842,0.465640413 12.9642105,1.30698373 12,2.50346693 C11.0357895,1.30698373 9.72631579,0.461412557 8.31578947,0.15277908 C8.16842105,0.118956234 8.02526316,0.0935890986 7.87789474,0.0724498193 L7.82736842,0.0639941076 C5.94105263,-0.202360811 4.08,0.368399729 2.58947368,1.67057933 C0.943157895,3.11227818 0,5.25157324 0,7.54307111 L0,7.80519817 C0,10.0375061 0.808421053,12.1472061 2.26947368,13.7495635 L11.3768421,23.7273033 C11.7094737,24.0908989 12.2863158,24.0908989 12.6189474,23.7273033 L21.7263158,13.7495635 C23.1915789,12.1472061 24,10.0375061 24,7.80519817 L24,7.54307111 C24,5.25157324 23.0568421,3.11227818 21.4105263,1.67480719 Z" id="path-3"></path>
271                    <path d="M32.24,0.46012468 C31.96,0.43012468 31.72,0.63012468 31.69,0.90012468 L31.67,1.11012468 C31.52,2.44012468 32.44,3.65012468 33.76,3.86012468 C39.08,4.68012468 41.51,5.89012468 42,6.50012468 C41.05,7.78012468 34.11,9.92012468 22.6,9.92012468 C11.09,9.92012468 4.15,7.79012468 3.2,6.50012468 C3.69,5.89012468 6.1,4.68012468 11.38,3.87012468 C12.7,3.67012468 13.62,2.45012468 13.47,1.12012468 L13.45,0.91012468 C13.42,0.63012468 13.17,0.43012468 12.9,0.47012468 C6.9,1.19012468 0,2.83012468 0,6.50012468 C0,13.0101247 21.69,13.0801247 22.61,13.0801247 C23.53,13.0801247 45.21,13.0101247 45.21,6.50012468 C45.21,2.81012468 38.28,1.18012468 32.24,0.46012468 Z" id="path-5"></path>
272                </defs>
273                <g id="map-/-locator-/-favorite" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
274                    <g id="lidl_icon_pin_default" transform="translate(12.000000, 0.000000)">
275                        <mask id="mask-2" fill="white">
276                                <use xlink:href="#path-1"></use>
277                        </mask>
278                        <use id="Shape" fill="#353B42" fill-rule="nonzero" xlink:href="#path-1"></use>
279                        <g id="Group" mask="url(#mask-2)">
280                            <g transform="translate(-12.000000, 0.000000)" id="colors-/-01-brand-primary-/-10-base-#0050AA">
281                                    <rect id="Rectangle" fill="#353B42" x="0" y="0" width="64" height="64"></rect>
282                            </g>
283                        </g>
284                        <g id="Group" mask="url(#mask-2)">
285                            <g transform="translate(8.000000, 8.000000)" id="logos-/-RGB">
286                                <mask id="mask-4" fill="white">
287                                        <use xlink:href="#path-3"></use>
288                                </mask>
289                                <g id="Shape" fill-rule="nonzero"></g>
290                                <g id="Group" mask="url(#mask-4)" fill="#FFF">
291                                        <g id="">
292                                                <rect id="Rectangle" x="0" y="0" width="24" height="24"></rect>
293                                        </g>
294                                </g>
295                            </g>
296                        </g>
297                    </g>
298                </g>
299            </svg>
300            <span class="lirt-storeselector-flyer-addon__storeInfoHeadline"></span>
301            <span class="lirt-storeselector-flyer-addon__storeInfoSubheadline">
302                <span class="lirt-storeselector-flyer-addon__status"></span>
303                <span class="lirt-storeselector-flyer-addon__separator hidden@all">&#9679</span>
304                <span class="lirt-storeselector-flyer-addon__addInfo hidden@all"></span>
305            </span>
306        </span>
307    </button>
308</article>
309`;
310
311// web component
312class StoreselectorFlyerAddon extends HTMLElement {
313  constructor() {
314    super();
315
316    this.locale = selectAppLocale(
317      window.mindShiftNav && window.mindShiftNav.locale,
318    );
319    const timestamp =
320      this.getAttribute("data-deploy-timestamp") || "0000000000000";
321    this._queryTranslations(timestamp).then((translations) => {
322      this.translations = translations;
323      const shadowRoot = this.attachShadow({ mode: "closed" });
324      // clone template content nodes to the shadow DOM
325      shadowRoot.appendChild(template.content.cloneNode(true));
326
327      this.setStore = shadowRoot.querySelector(
328        ".lirt-storeselector-flyer-addon__setStore",
329      );
330      this.storeInfo = shadowRoot.querySelector(
331        ".lirt-storeselector-flyer-addon__storeInfo",
332      );
333      this.setStoreHeadline = shadowRoot.querySelector(
334        ".lirt-storeselector-flyer-addon__setStoreHeadline",
335      );
336      this.setStoreSubheadline = shadowRoot.querySelector(
337        ".lirt-storeselector-flyer-addon__setStoreSubheadline",
338      );
339      this.storeInfoSubheadline = shadowRoot.querySelector(
340        ".lirt-storeselector-flyer-addon__storeInfoSubheadline",
341      );
342      this.fallback = shadowRoot.querySelector(
343        ".lirt-storeselector-flyer-addon__fallback",
344      );
345      this.storeName = shadowRoot.querySelector(
346        ".lirt-storeselector-flyer-addon__storeInfoHeadline",
347      );
348      this.storeStatus = shadowRoot.querySelector(
349        ".lirt-storeselector-flyer-addon__status",
350      );
351      this.storeAddInfo = shadowRoot.querySelector(
352        ".lirt-storeselector-flyer-addon__addInfo",
353      );
354      this.seperator = shadowRoot.querySelector(
355        ".lirt-storeselector-flyer-addon__separator",
356      );
357      this.pinIconDefault = shadowRoot.querySelector(
358        ".lirt-storeselector-flyer-addon__setStore-icons",
359      );
360      this.pinIconFavorite = shadowRoot.querySelector(
361        ".lirt-storeselector-flyer-addon__storeInfo-icons",
362      );
363
364      this.countryCode = this.locale.slice(3, 5);
365
366      window.addEventListener("storesearch", async (e) => {
367        if (e.detail.cookieSet === false) {
368          this.storeInfo.classList.add("hidden@all");
369          this.setStore.classList.remove("hidden@all");
370        } else {
371          let cookieValue = this._getCookie("ar");
372          if (cookieValue !== undefined) {
373            await this._setValues(this._getDataFromCookie(cookieValue));
374            this.setStore.classList.add("hidden@all");
375            this.storeInfo.classList.remove("hidden@all");
376          }
377        }
378      });
379
380      // set text from translations
381      this.setStoreHeadline.innerText =
382        translations[
383          `storeSearch.storeselectorFlyerAddon.textSetStoreHeadline`
384        ];
385
386      this.pinIconDefault.setAttribute(
387        "aria-label",
388        translations[
389          `storeSearch.storeselectorFlyerAddon.a11yLabelPinIconDefault`
390        ],
391      );
392
393      this.pinIconFavorite.setAttribute(
394        "aria-label",
395        translations[
396          `storeSearch.storeselectorFlyerAddon.a11yLabelPinIconFavorite`
397        ],
398      );
399
400      // get cookie if it is available
401      let cookieValue = this._getCookie("ar");
402      if (cookieValue !== undefined) {
403        (async () => {
404          await this._setValues(this._getDataFromCookie(cookieValue));
405        })();
406      }
407    });
408  }
409
410  async _queryTranslations(timestamp) {
411    const url = `/s/translation/${this.locale}.json?_=${timestamp}`;
412    try {
413      let response = await fetch(url, {
414        method: "GET",
415        headers: {
416          "Content-Type": "application/json",
417        },
418      });
419      if (response.ok) {
420        return await response.json();
421      } else {
422        console.error("unable to fetch translations");
423        return null;
424      }
425    } catch (error) {
426      console.error("unable to fetch translations", error);
427      return null;
428    }
429  }
430
431  /**
432   * fetch data from timings:
433   * @param countryCode {string}
434   * @param entityID {string}
435   * @returns {Promise<any>}
436   */
437  async _queryTimings(countryCode, entityID) {
438    // entityID needs to be 4 digits with leading 0 if it is less
439    let storeId = "00000" + entityID;
440    storeId = storeId.slice(-5);
441
442    let stage = "";
443    if (
444      window.location.href.indexOf("localhost") !== -1 ||
445      window.location.href.indexOf("stores-frontend.local") !== -1
446    ) {
447      stage = "test";
448    } else if (window.location.href.indexOf("https://dev.lidl") !== -1) {
449      stage = "dev";
450    } else if (window.location.href.indexOf("https://test.lidl") !== -1) {
451      stage = "test";
452    } else if (window.location.href.indexOf("https://qa.lidl") !== -1) {
453      stage = "qas";
454    } else {
455      stage = "live";
456    }
457
458    if (countryCode === "XI") {
459      countryCode = "NI";
460    }
461
462    const queryUrl = `https://${stage}.api.schwarz/odj/stores-api/v2/myapi/stores-frontend/stores?object_number=${countryCode}${storeId}`;
463
464    try {
465      let response = await fetch(queryUrl, {
466        method: "GET",
467        headers: {
468          "Content-Type": "application/json",
469          "x-apikey": "KxboQtt40BG4VpBL16IhaRd2CXh0QbAc",
470        },
471      });
472      if (response.ok) {
473        const responseData = await response.json();
474        return responseData;
475      } else {
476        return null;
477      }
478    } catch (error) {
479      console.error("unable to fetch data", error);
480      return null;
481    }
482  }
483
484  /**
485   * returns a cookie
486   * @param cookieName {string}
487   * @returns {string|undefined}
488   */
489  _getCookie(cookieName) {
490    let matches = document.cookie.match(
491      new RegExp(
492        "(?:^|; )" +
493          cookieName.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1") +
494          "=([^;]*)",
495      ),
496    );
497    return matches ? decodeURIComponent(matches[1]) : undefined;
498  }
499
500  _mapV2ToV1(v2Response) {
501    if (!v2Response?.items?.[0]) return null;
502    const store = v2Response.items[0];
503    const tz = store.openingHours.timeZone || "Europe/Berlin";
504    const items = store.openingHours.items;
505    const todayStr = new Date().toLocaleDateString("en-CA", { timeZone: tz });
506    const todayIndex = items.findIndex((i) => i.date === todayStr);
507    const start = todayIndex === -1 ? 0 : todayIndex;
508
509    const fixTime = (iso) => {
510      const time = iso?.split("T")[1] || "00:00:00";
511      return time.startsWith("24:") ? "23:59:59" : time;
512    };
513
514    return {
515      timeZone: tz,
516      specialStatus: store.status,
517      currentDate: {
518        date: items[start].date,
519        closed: items[start].timeRanges.length === 0,
520        reason: items[start].reason,
521        openingHours: items[start].timeRanges.map((r) => ({
522          from: fixTime(r.from),
523          to: fixTime(r.to),
524        })),
525      },
526      nextOpenDate: items.slice(start + 1).find((i) => i.timeRanges.length > 0),
527    };
528  }
529
530  /**
531   * returns the store data from the cookie
532   * @param {string} cookieValue
533   * @returns {Map<any, any>|null}
534   * @private
535   */
536  _getDataFromCookie(cookieValue) {
537    if (typeof cookieValue !== "undefined") {
538      this.setStore.classList.add("hidden@all");
539      this.storeInfo.classList.remove("hidden@all");
540
541      const cookieDataMap = new Map();
542      const cookieData = cookieValue.split(";");
543      for (const entry of cookieData) {
544        cookieDataMap.set(entry.split("=")[0], entry.split("=")[1]);
545      }
546      return cookieDataMap;
547    } else {
548      return null;
549    }
550  }
551
552  _formatDisplayTime(timeStr) {
553    if (!timeStr) return "";
554
555    // Extract hours and minutes from the "HH:mm" string
556    const [hours, minutes] = timeStr.split(":").map(Number);
557
558    // Create a temporary date object to format the clock face
559    const dummyDate = new Date();
560    dummyDate.setHours(hours, minutes, 0, 0);
561    // Explicitly force 12-hour format if the locale or country is US
562    const is12Hour = this.locale.includes("US") || this.locale.includes("XI");
563
564    return new Intl.DateTimeFormat(this.locale, {
565      hour: "numeric",
566      minute: "2-digit",
567      hour12: is12Hour ? true : undefined, // 'undefined' falls back to the local country convention
568    }).format(dummyDate);
569  }
570
571  _isOpenTomorrow(currentDate, nextOpenDate) {
572    const sharedT = (key) =>
573      this.translations[`shared.openingHours.${key}`].toLowerCase() || key;
574    const storeToday = new Date(currentDate.date);
575    const storeTomorrow = new Date(storeToday);
576    storeTomorrow.setDate(storeTomorrow.getDate() + 1);
577    const tomorrowStr = storeTomorrow.toLocaleDateString("en-CA");
578
579    let msg = "";
580    if (nextOpenDate && nextOpenDate.date === tomorrowStr) {
581      if (nextOpenDate.timeRanges?.[0]) {
582        const nextTime = nextOpenDate.timeRanges[0].from
583          .split("T")[1]
584          .slice(0, 5);
585        msg += ` - ${sharedT("opensAt")} ${this._formatDisplayTime(nextTime)}`;
586      }
587    }
588
589    if (currentDate.reason === "HOLIDAY") {
590      msg += ` (${sharedT("reason.publicHoliday")})`;
591    }
592
593    return msg;
594  }
595
596  _calculateCurrentStatus(data) {
597    const t = (key) =>
598      this.translations[`storeSearch.storeselectorFlyerAddon.${key}`] || key;
599    const sharedT = (key) =>
600      this.translations[`shared.openingHours.${key}`] || key;
601
602    const formatter = new Intl.DateTimeFormat("en-GB", {
603      timeZone: data.timeZone,
604      hour: "numeric",
605      minute: "numeric",
606      hour12: false,
607    });
608
609    // Get current "Wall Clock" time at the store
610    const parts = formatter.formatToParts(new Date());
611    const storeHour = parseInt(parts.find((p) => p.type === "hour").value);
612    const storeMinute = parseInt(parts.find((p) => p.type === "minute").value);
613    const storeTotalMins = storeHour * 60 + storeMinute; // current time in minutes based on stores timezone
614
615    const toMins = (h) => {
616      const [hour, min] = h.split(":").map(Number);
617      return hour * 60 + min;
618    };
619
620    const today = data.currentDate;
621    const ranges = today.openingHours;
622
623    if (data.specialStatus.name === "new_opening") {
624      const label = `${t("textWillOpen")} ${this._formatShortDate(data.specialStatus.from)}`;
625      return { msg: label, class: "closed" };
626    }
627
628    if (today.closed && today.reason === "TEMPORARY_CLOSING") {
629      return { msg: t("textStatusTemporaryClosed"), class: "closed" };
630    }
631
632    // Check every range
633    for (let i = 0; i < ranges.length; i++) {
634      const openMins = toMins(ranges[i].from);
634 // converting opening hours to minutes
635      const closeMins = toMins(ranges[i].to);
636
637      const closeTimeIn24HFormat = ranges[i].to.slice(0, 5);
638
639      // ARE WE OPEN?
640      if (storeTotalMins < openMins) {
641        let label = `${sharedT("closed")}`;
642
643        label += this._isOpenTomorrow(today, data.nextOpenDate);
644        return {
645          msg: label,
646          class: "closed",
647        };
648      } else if (storeTotalMins >= openMins && storeTotalMins < closeMins) {
649        const diff = closeMins - storeTotalMins;
650        const isClosingSoon = diff <= 60; // Within 1 hour
651
652        const state = isClosingSoon ? "closingSoon" : "open";
653        const label = isClosingSoon
654          ? sharedT("closesSoonAt")
655          : sharedT("openTill");
656
657        let secondLabel = "";
658        if (today.reason === "HOLIDAY") {
659          secondLabel += `(${sharedT("reason.publicHoliday").toLowerCase()})`;
660        }
661
662        return {
663          msg: `${label} ${this._formatDisplayTime(closeTimeIn24HFormat)} ${secondLabel}`,
664          class: state,
665        };
666      }
667    }
668
669    let finalMsg =
670      sharedT("closed") + this._isOpenTomorrow(today, data.nextOpenDate);
671
672    return { msg: finalMsg, class: "closed" };
673  }
674
675  /**
676   * display the store data from the cookie and the timings endpoint
677   * @param storeDataFromCookie
678   * @returns {Promise<void>}
679   * @private
680   */
681
682  async _setValues(storeDataFromCookie) {
683    if (storeDataFromCookie.size > 0) {
684      const street = (storeDataFromCookie.get("street") || "").replace(
685        /,$/,
686        "",
687      );
688      this.storeName.innerText = `${street}, ${storeDataFromCookie.get("city")}`;
689
690      const v2RawData = await this._queryTimings(
691        this.countryCode,
692        storeDataFromCookie.get("EntityID"),
693      );
694      const openingTimes = this._mapV2ToV1(v2RawData);
695      if (openingTimes) {
696        const status = this._calculateCurrentStatus(openingTimes);
697
698        this.storeStatus.innerText = status.msg;
699        this.storeStatus.className = `lirt-storeselector-flyer-addon__status lirt-storeselector-flyer-addon__status--${status.class}`;
700
701        this.storeInfo.classList.remove("hidden@all");
702        this.setStore.classList.add("hidden@all");
703        this.storeInfoSubheadline.classList.remove("hidden@all");
704      }
705    }
706  }
707
708  /**
709   * format a date to a format to display: hh:mm
710   * @param {Date} time
711   * @returns {string}
712   * @private
713   */
714  _formatShortDate(dateStr) {
715    const date = new Date(dateStr);
716
717    return new Intl.DateTimeFormat(this.locale, {
718      weekday: "short",
719      day: "2-digit",
720      month: "2-digit",
721    }).format(date);
722  }
723}
724
725// register component
726customElements.define("store-selector-flyer-addon", StoreselectorFlyerAddon);

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.