PageSourceSearch

https://www.allimmunology.org/js/corner-cart-progress-bar.js

js allimmunology.org collected 2026-09-25 08:59:35 UTC 17,577 bytes, 452 lines download raw bytes

1/**
2 * Promise that resolves once `CornerWebComponentsAPI` is ready.
3 * Falls back to queuing the resolver on `window.CORNER_PENDING_EXTENDED_COMPONENTS`
4 * if the API has not been bootstrapped yet.
5 *
6 * @type {Promise<void>}
7 */
8const whenReadyPromise =
9  window.CornerWebComponentsAPI?.whenReady?.call(window.CornerWebComponentsAPI) ||
10  new Promise(resolve => {
11    // Fallback: API not loaded yet, queue the callback
12    window.CORNER_PENDING_EXTENDED_COMPONENTS = window.CORNER_PENDING_EXTENDED_COMPONENTS || [];
13    window.CORNER_PENDING_EXTENDED_COMPONENTS.push(resolve);
14  });
15
16whenReadyPromise.then(() => {
17  window.CornerWebComponentsAPI.createVariantComponent(
18    'corner-cart-progress-bar',
19    'CornerCartProgressBar',
20    'CornerCartProgress',
21    /**
22     * Factory callback used by `CornerWebComponentsAPI.createVariantComponent`.
23     *
24     * @param {Function} BaseClass Base cart progress core class that provides milestone and rendering helpers.
25     * @returns {Function} Extended variant class for the cart progress bar.
26     */
27    BaseClass => {
28      /**
29       * Web component variant that renders the cart goal milestones as a horizontal progress bar,
30       * backed by a `corner-carousel` for paging through milestones.
31       *
32       * @class CornerCartProgressBar
33       * @extends {CornerCartProgressCore}
34       */
35      return class CornerCartProgressBar extends BaseClass {
36        /**
37         * Static counter used to generate unique container IDs for each instance.
38         *
39         * @type {number}
40         * @static
41         */
42        static instanceCounter = 0;
43
44        /**
45         * Creates a cart progress bar instance and assigns a unique container id.
46         *
47         * @param {...any} args - Arguments passed to the parent constructor.
48         */
49        constructor(...args) {
50          super(...args);
51          /**
52           * Unique container ID for this instance.
53           *
54           * @type {string}
55           * @private
56           */
57          this._containerId = `corner-cowi-cart-progress-bar-container-${++CornerCartProgressBar.instanceCounter}`;
58        }
59
60        /**
61         * Reference to the inner `corner-carousel` element used to paginate milestones.
62         * Returns `null` if the carousel element is not yet rendered.
63         *
64         * @type {HTMLElement | null}
65         * @readonly
66         */
67        get carouselElement() {
68          return this.renderRoot.querySelector('#corner-cowi-cart-progress-bar-carou
68sel');
69        }
70
71        /**
72         * Whether there is a next carousel page available.
73         * Returns `false` if the carousel element is not yet rendered.
74         *
75         * @type {boolean}
76         * @readonly
77         */
78        get hasNextPage() {
79          return this.carouselElement?.hasNextPage;
80        }
81
82        /**
83         * Whether there is a previous carousel page available.
84         * Returns `false` if the carousel element is not yet rendered.
85         *
86         * @type {boolean}
87         * @readonly
88         */
89        get hasPrevPage() {
90          return this.carouselElement?.hasPrevPage;
91        }
92
93        /**
94         * Returns the primary button color based on advanced color customisation configuration.
95         * Falls back to `rgb(25,35,55)` if advanced color customisation is not enabled.
96         *
97         * @type {string}
98         * @readonly
99         */
100        get buttonColor() {
101          if (
102            this.globalContext?.config?.general_config?.featureFlag?.apps?.cornercart?.cornerwidget
103              .cornerwidget_advanced_color_customisation
104          )
105            return this.globalContext?.config?.cornercart?.cartConfig?.colors?.primaryType;
106          else return 'rgb(25,35,55)';
107        }
108
109        /**
110         * Color used for the gift / milestone icons when not yet completed.
111         * Delegates to `buttonColor` for consistency.
112         *
113         * @type {string}
114         * @readonly
115         */
116        get giftIconColor() {
117          return this.buttonColor;
118        }
119
120        /**
121         * Lifecycle callback fired when the element is added to the DOM.
122         * Ensures the shared styles are injected once by calling the parent implementation.
123         *
124         * @returns {void}
125         */
126        connectedCallback() {
127          super.connectedCallback();
128        }
129
130        /**
131         * Helper to convert an RGB color into an RGBA string using the shared Corner utility.
132         *
133         * @param {string} color - Base RGB color (e.g. `rgb(0,0,0)`).
134         * @param {number} [alphaValue=0] - Alpha channel between 0 and 1.
135         * @returns {string | undefined} RGBA color string, or `undefined` if the helper is unavailable.
136         */
137        rgbToRgba(color, alphaValue = 0) {
138          return window?.corner?.utils?.rgbToRgba(color, alphaValue);
139        }
140
141        /**
142         * Determines the border radius for the progress fill bar based on whether
143         * the entire page has reached 100% progress.
144         *
145         * @param {boolean} isPageProgressed - `true` when the page is fully completed (100% progress).
146         * @returns {string} CSS `border-radius` value. Returns `'10px'` when completed, otherwise `'10px 0 0 10px'`.
147         */
148        getBorderRadius(isPageProgressed) {
149          if (isPageProgressed) return '10px';
150          else return '10px 0 0 10px';
151        }
152
153        /**
154         * Handler for the "previous" navigation button on the carousel.
155         * Navigates to the previous page of milestones if available.
156         *
157         * @returns {void}
158         */
159        handlePrevClick() {
160          this.carouselElement?.previousPage();
161        }
162
163        /**
164         * Handler for the "next" navigation button on the carousel.
165         * Navigates to the next page of milestones if available.
166         *
167         * @returns {void}
168         */
169        handleNextClick() {
170          this.carouselElement?.nextPage();
171        }
172
173        /**
174         * Event handler fired when the underlying `corner-carousel` page changes.
175         * Triggers a re-render to update icon and label states for the new page.
176         *
177         * @returns {void}
178         */
179        handlePageChange() {
180          this.requestUpdate();
181        }
182
183        /**
184         * Root render method for the component, wrapping the above text and body
185         * in a padded container.
186         *
187         * @returns {TemplateResult} Full component template.
188         */
189        render() {
190          const containerClassMap = this.classMap({
191            'w-full': true,
192            'py-3': true,
193            'text-center': true,
194            'rounded-lg': true,
195            'px-1': true,
196
197            'h-full': true,
198          });
199
200          const carouselNavButtonClasses = {
201            'p-1': true,
202            'bg-cowi-card-bg': true,
203            'cursor-pointer': true,
204            'rounded-lg': true,
205            flex: true,
206            'shrink-0': true,
207            'items-center': true,
208            'justify-center': true,
209            border: true,
210            'border-cowi-outlines': true,
211            'border-solid': true,
212            'pointer-events-auto': true,
213            absolute: true,
214            'top-[5px]': true,
215            'z-30': true,
216          };
217
218          const leftButtonClasses = {
219            ...carouselNavButtonClasses,
220            invisible: !this.hasPrevPage,
221            'left-[10px]': true,
222          };
223
224          const rightButtonClasses = {
225            ...carouselNavButtonClasses,
226            invisible: !this.hasNextPage,
227            'right-[4px]': true,
228          };
229
230          const iconClasses = {
231            'w-3': true,
232            'h-3': true,
233          };
234
235          const rightIconWrapperClasses = {
236            ...iconClasses,
237            'inline-block': true,
238            'rotate-180': true,
239          };
240
241          return this.html`
242          <div
243            id=${this._containerId}
244            class=${containerClassMap}
245          >
246            ${this.html`
247            <style>
248            .widgets-by-corner .corner-cowi-progress-bar {
249              display: grid;
250              grid-template-columns: 10% 1fr 10%;
251            }
252
253            .widgets-by-corner .corner-cowi-progress-bar-segment-1-tier {
254              grid-template-columns: 1fr;
255            }
256            .widgets-by-corner .corner-cowi-progress-bar-segment-2-tier {
257              grid-template-columns: 1fr 1fr;
258            }
259            .widgets-by-corner .corner-cowi-progress-bar-segment-3-tier {
260              grid-template-columns: 1fr 1fr 1fr;
261            }
262            .widgets-by-corner .corner-cowi-progress-bar-segment-4-tier {
263              grid-template-columns: 1fr 1fr 1fr 1fr;
264            }
265
266            .widgets-by-corner .corner-cowi-progress-milestones-1-tier {
267              grid-template-columns: 1fr 20%;
268            }
269            .widgets-by-corner .corner-cowi-progress-milestones-2-tier {
270              // grid-template-columns: 21% 1fr 23%;
271              grid-template-columns: 20% 1fr 20%;
272            }
273            .widgets-by-corner .corner-cowi-progress-milestones-3-tier {
274              // grid-template-columns: 23% 24% 30% 23%;
275              grid-template-columns: 23% 26% 28% 23%;
276            }
277            .widgets-by-corner .corner-cowi-progress-milestones-4-tier {
278              // grid-template-columns: 19% 19% 21% 18% 23%;
279              grid-template-columns: 20% 20% 20% 20% 20%;
280            }
281            .widgets-by-corner .grid div {
282              margin: 0;
283            }
284            </style>
285          `}
286            ${this.html`
287            <div
288              id=${`corner-cowi-cart-progress-bar-text`}
289              class="text-cowi-primary-type text-center text-sm px-4"
290            >
291              ${this.unsafeHTML(this.renderMilestoneText('aboveText', this.currentMilestone))}
292            </div>
293        `}
294            ${this.html`
295          <div class="w-full h-full relative">
296           
297              <button
298                  id="slider-left-btn"
299                  aria-label="Previous slide"
300                  class=${this.classMap(leftButtonClasses)}
301                  @click=${this.handlePrevClick}
302                >
303                <span class="${this.classMap(iconClasses)}">
304                  <corner-icon type="caret" color=${this.buttonColor}></corner-icon>
305                </span>
306                </button>
307            
308            <corner-carousel id="corner-cowi-cart-progress-bar-carou
308sel"
309              .autoScroll=${false} 
310              .pageIndex=${this.currentMilestonePageIndex}
311              .showDots=${false}
312              .controllerType=${'none'}
313              .itemsPerPage=${this.milestonesPerPage}
314              @change=${this.handlePageChange}
315              @carousel-ready=${this.handlePageChange}>
316              ${this.repeat(
317                this.milestonePages,
318                milestonePage => milestonePage.id,
319                (milestonePage, outerIndex) => {
320                  if (!milestonePage) return this.html``;
321
322                  const segmentClassName = `corner-cowi-progress-bar-segment-${milestonePage.milestones.length}-tier`;
323                  const progressBarSegmentClassMap = this.classMap({
324                    [segmentClassName]: true,
325                    relative: true,
326                    grid: true,
327                  });
328                  const isPageProgressed = milestonePage.progress === 100;
329                  const borderRadius = this.getBorderRadius(isPageProgressed);
330
331                  return this.html`
332                    <div class="w-full h-full corner-cowi-progress-bar-container">
333                      <div class="corner-cowi-progress-bar-wrapper w-full pt-2 pb-0">
334                          <div class="corner-cowi-progress-bar relative items-center grid m-0">
335                              <div class="relative flex items-center justify-start">
336                              
337                              </div>
338                              <div class=${progressBarSegmentClassMap}>
339                                <div class="h-2 absolute block corner-cowi-progress-bar-track w-full bg-cowi-secondary-bg left-0 top-[4px] rounded-full"></div>
340                                <div class="h-2 absolute block corner-cowi-progress-bar-track-fill transition-[width] duration-300 ease-in-out bg-cowi-accent-bg left-0 top-[4px]"
341                                  style=${this.styleMap({
342                                    width: `${milestonePage.progress}%`,
343                                    borderRadius: borderRadius,
344                                  })}
345                                ></div>
346
347
348                               ${this.repeat(
349                                 milestonePage.milestones,
350                                 milestone => milestone.id,
351                                 (milestone, innerIndex) => {
352                                   const progress = milestone.progress;
353
354                                   // Determine icon type and color
355                                   const isShippingDiscount =
356                                     milestone.rewards?.rewardStack?.[0]?.type ===
357                                     'shippingDiscount';
358
359                                   const icon =
360                                     progress === 100
361                                       ? 'check'
362                                       : isShippingDiscount
363                                         ? 'freeShipping'
364                                         : 'gift';
365
366                                   const iconColor =
367                                     progress === 100
368                                       ? this.globalContext?.config?.cornercart?.cartConfig?.colors
369                                           .accentFg
370                                       : this.giftIconColor;
371
372                                   return this.html`
373                                      <div class="relative w-full corner-cowi-progress-bar-segment corner-cowi-progress-bar-segment-${innerIndex + 1}">
374
375                                      <div
376                                          id=${`corner-cowi-goal-milestone-icon-${innerIndex + 1}`}
377                                          class=${this.classMap({
378                                            'corner-cowi-goal-milestone-icon': true,
379                                            'rounded-full': true,
380                                            'w-5': true,
381                                            'h-5': true,
382                                            'p-[.27em]': true,
383                                            'bg-cowi-accent-bg': progress === 100,
384                                            'bg-cowi-secondary-bg': progress !== 100,
385                                            absolute: true,
386                                            'right-[-8px]': true,
387                                            'top-[-2px]': true,
388                                          })}
389                                        >
390                                          <corner-icon .type=${icon} .color=${iconColor}></corner-icon>
391                                        </div>
392                                      </div>
393                                    `;
394                                 },
395                               )}
396
397
398                                
399                                
400                              </div>
401                              <div class="relative flex items-center justify-end">
402                               
403                              </div>
404                          </div>
405                        </div>
406                        
407                        <div class="corner-cowi-progress-milestones-wrapper grid w-full corner-cowi-progress-milestones-${milestonePage.milestones.length}-tier text-center mt-5">
408                          
409                          <div class="block"></div>
410
411                          ${this.repeat(
412                            milestonePage.milestones,
413                            milestone => milestone.id,
414                            (milestone, innerIndex) => {
415                              return this.html`
416                                
417                                <div
418                                  id=${`corner-cowi-goal-milestone-label-${outerIndex + 1}-${innerIndex + 1}`}
419                                  class="corner-cowi-goal-milestone-label text-cowi-primary-type w-full text-center text-xs leading-tight"
420                                >
421                                  <span class="px-1 pb-1">${this.unsafeHTML(this.renderMilestoneText('belowText', milestone))}</span>
422                                </div>
423                              `;
424                            },
425                          )}
426                          
427                        </div>
428                    </div>
429                  `;
430                },
431              )}
432            </corner-carousel>
433            
434                <button
435                  id="slider-right-btn"
436                  aria-label="Next slide"
437                  class=${this.classMap(rightButtonClasses)}
438                  @click=${this.handleNextClick}
439                  >
440                  <span class=${this.classMap(rightIconWrapperClasses)}>
441                    <corner-icon type="caret" color=${this.buttonColor}></corner-icon>
442                  </span>
443                </button>
444              
445          </div>`}
446    
447          </div>`;
448        }
449      };
450    },
451  );
452});

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.