@liquidcommercedev/rmn-sdk 1.5.0-beta.7 → 1.5.0-beta.9

Sign up to get free protection for your applications and to get access to all the features.
package/dist/index.cjs CHANGED
@@ -55,9 +55,13 @@ exports.RMN_FILTER_PROPERTIES = void 0;
55
55
  RMN_FILTER_PROPERTIES["PUBLISHERS"] = "publishers";
56
56
  RMN_FILTER_PROPERTIES["SECTION"] = "section";
57
57
  })(exports.RMN_FILTER_PROPERTIES || (exports.RMN_FILTER_PROPERTIES = {}));
58
+ exports.RMN_EVENT = void 0;
59
+ (function (RMN_EVENT) {
60
+ RMN_EVENT["LIFECYCLE_STATE"] = "LIFECYCLE_STATE";
61
+ RMN_EVENT["SPOT_EVENT"] = "SPOT_EVENT";
62
+ })(exports.RMN_EVENT || (exports.RMN_EVENT = {}));
58
63
  exports.RMN_SPOT_EVENT = void 0;
59
64
  (function (RMN_SPOT_EVENT) {
60
- RMN_SPOT_EVENT["LIFECYCLE_STATE"] = "LIFECYCLE_STATE";
61
65
  RMN_SPOT_EVENT["IMPRESSION"] = "IMPRESSION";
62
66
  RMN_SPOT_EVENT["CLICK"] = "CLICK";
63
67
  RMN_SPOT_EVENT["PURCHASE"] = "PURCHASE";
@@ -15167,6 +15171,12 @@ const GFONT_CORMORANT = `
15167
15171
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant:ital,wght@0,300..700;1,300..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap">
15168
15172
  `;
15169
15173
 
15174
+ /**
15175
+ * Determines the event type from a raw event string.
15176
+ *
15177
+ * @param {string} [event] - The raw event string to evaluate.
15178
+ * @returns {RMN_SPOT_EVENT | null} - The corresponding RMN_SPOT_EVENT or null if no match is found.
15179
+ */
15170
15180
  function getEventTypeFromRawEvent(event) {
15171
15181
  if (!event) {
15172
15182
  return null;
@@ -15195,7 +15205,7 @@ function getEventTypeFromRawEvent(event) {
15195
15205
  * Searches for specified property names and collects their primitive values (strings/numbers).
15196
15206
  *
15197
15207
  * @param data - The data structure to search through (can be nested objects/arrays)
15198
- * @param propertyNames - Array of property names to look for (defaults to ['id', 'upc', 'groupingId', 'sku', 'productId'])
15208
+ * @param propertyNames - Array of property names to look for
15199
15209
  * @returns Array of extracted ID values (strings/numbers only)
15200
15210
  *
15201
15211
  * @example
@@ -15206,10 +15216,27 @@ function getEventTypeFromRawEvent(event) {
15206
15216
  * };
15207
15217
  * extractDeepIds(data); // Returns [1, 2, 3, 'abc', 456]
15208
15218
  */
15209
- function extractDeepIds(data, propertyNames = ['id', 'upc', 'groupingId', 'sku', 'productId']) {
15219
+ function extractDeepIds(data, propertyNames) {
15210
15220
  const ids = [];
15221
+ const defaulPropertyNames = [
15222
+ 'id',
15223
+ 'upc',
15224
+ 'groupingId',
15225
+ 'sku',
15226
+ 'productId',
15227
+ 'item_id',
15228
+ 'isbn',
15229
+ 'asin',
15230
+ 'mpn',
15231
+ 'model_number',
15232
+ 'article_number',
15233
+ 'variant_id',
15234
+ 'item_number',
15235
+ 'catalog_id',
15236
+ 'reference_id',
15237
+ ];
15211
15238
  // Set for faster property name lookups
15212
- const propertySet = new Set(propertyNames);
15239
+ const propertySet = new Set(defaulPropertyNames);
15213
15240
  /**
15214
15241
  * Processes a value and extracts IDs if it matches criteria
15215
15242
  * @param value - The value to process
@@ -15244,54 +15271,106 @@ function extractDeepIds(data, propertyNames = ['id', 'upc', 'groupingId', 'sku',
15244
15271
  processValue(data);
15245
15272
  return ids; // No need to filter nulls as we handle that during collection
15246
15273
  }
15247
-
15248
- class DataLayerMonitor {
15249
- constructor() {
15250
- if (!window.dataLayer) {
15251
- return;
15252
- }
15253
- this.originalPush = window.dataLayer.push;
15274
+ // Fallback method using fetch if sendBeacon isn't available
15275
+ async function fallbackEventFire(url) {
15276
+ try {
15277
+ const racePromise = Promise.race([
15278
+ // Promise #1: The fetch request
15279
+ fetch(url, {
15280
+ method: 'POST',
15281
+ keepalive: true,
15282
+ }),
15283
+ // Promise #2: The timeout
15284
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), 2000)),
15285
+ ]);
15286
+ /**
15287
+ * Prevent requests from hanging indefinitely
15288
+ * Improve user experience by failing fast
15289
+ * Handle slow network conditions gracefully
15290
+ * Ensure resources are freed up in a timely manner
15291
+ */
15292
+ const response = await racePromise;
15293
+ return response.ok;
15254
15294
  }
15255
- static getInstance() {
15256
- if (!DataLayerMonitor.instance) {
15257
- DataLayerMonitor.instance = new DataLayerMonitor();
15295
+ catch (_a) {
15296
+ return false;
15297
+ }
15298
+ }
15299
+ /**
15300
+ * Extracts and decodes a URL from a base64-encoded query parameter.
15301
+ *
15302
+ * @param {string} url - The URL containing the base64-encoded query parameter.
15303
+ * @returns {string | null} - The decoded URL or null if not found or invalid.
15304
+ */
15305
+ function getRedirectUrlFromPayload(url) {
15306
+ try {
15307
+ const base64String = new URL(url).searchParams.get('e');
15308
+ if (!base64String) {
15309
+ return null;
15258
15310
  }
15259
- return DataLayerMonitor.instance;
15311
+ const data = JSON.parse(atob(base64String));
15312
+ return data.ur || null;
15260
15313
  }
15261
- setListener(listener) {
15262
- this.listener = listener;
15314
+ catch (_a) {
15315
+ return null;
15263
15316
  }
15264
- start() {
15265
- window.dataLayer.push = (...args) => {
15266
- const result = this.originalPush.apply(window.dataLayer, args);
15267
- const pushedEvent = args[0];
15268
- if (this.listener) {
15269
- const normalizedData = this.cleanEventData(pushedEvent);
15270
- if (normalizedData) {
15271
- this.listener(normalizedData);
15272
- }
15317
+ }
15318
+ /**
15319
+ * Fires an event using the navigator.sendBeacon method or a fallback method if sendBeacon is not available.
15320
+ * If the event is a click event and a redirect URL is found, it redirects the user to that URL.
15321
+ *
15322
+ * @param {IFireEventParams} params - The parameters for firing the event.
15323
+ * @param {RMN_SPOT_EVENT} params.event - The event type.
15324
+ * @param {string} params.eventUrl - The URL to which the event is sent.
15325
+ * @returns {Promise<void>} - A promise that resolves when the event is fired.
15326
+ */
15327
+ async function fireEvent({ event, eventUrl }) {
15328
+ var _a;
15329
+ try {
15330
+ const didFireEvent = ((_a = navigator === null || navigator === void 0 ? void 0 : navigator.sendBeacon) === null || _a === void 0 ? void 0 : _a.call(navigator, eventUrl)) || (await fallbackEventFire(eventUrl));
15331
+ if (!didFireEvent) {
15332
+ return;
15333
+ }
15334
+ if (event === exports.RMN_SPOT_EVENT.CLICK) {
15335
+ const redirectUrl = getRedirectUrlFromPayload(eventUrl);
15336
+ if (redirectUrl) {
15337
+ window.location.href = redirectUrl;
15273
15338
  }
15274
- return result;
15275
- };
15276
- }
15277
- cleanEventData(data) {
15278
- const eventName = getEventTypeFromRawEvent(data.event);
15279
- if (!eventName) {
15280
- return null;
15281
15339
  }
15282
- const productIds = extractDeepIds(data.value);
15283
- return {
15284
- event: eventName,
15285
- productIds,
15286
- };
15287
15340
  }
15288
- stop() {
15289
- if (this.originalPush) {
15290
- window.dataLayer.push = this.originalPush;
15291
- }
15292
- this.listener = undefined;
15341
+ catch (_b) {
15342
+ // Handle errors silently
15293
15343
  }
15294
15344
  }
15345
+ function calculateScaleFactor(elementScale) {
15346
+ // Step 1: Apply square root for non-linear scaling
15347
+ // This creates a more gradual scaling effect, especially for larger changes
15348
+ // For example:
15349
+ // - elementScale of 0.25 (1/4 size) becomes 0.5
15350
+ // - elementScale of 1 (unchanged) remains 1
15351
+ // - elementScale of 4 (4x size) becomes 2
15352
+ const baseFactor = Math.sqrt(elementScale);
15353
+ // Step 2: Apply additional dampening to further soften the scaling effect
15354
+ // The dampening factor (0.5) can be adjusted:
15355
+ // - Lower values (closer to 0) make scaling more subtle
15356
+ // - Higher values (closer to 1) make scaling more pronounced
15357
+ const dampening = 0.35;
15358
+ // Calculate the scaleFactor:
15359
+ // 1. (baseFactor - 1) represents the change from the original size
15360
+ // 2. Multiply by dampening to reduce the effect
15361
+ // 3. Add 1 to center the scaling around the original size
15362
+ // For example, if baseFactor is 2:
15363
+ // scaleFactor = 1 + (2 - 1) * 0.5 = 1.5
15364
+ const scaleFactor = 1 + (baseFactor - 1) * dampening;
15365
+ // Step 3: Define the allowed range for the scale factor
15366
+ // This ensures that the font size never changes too drastically
15367
+ const minScale = 0.35; // Font will never be smaller than 50% of original
15368
+ const maxScale = 1.5; // Font will never be larger than 150% of original
15369
+ // Step 4: Clamp the scale factor to the defined range
15370
+ // Math.min ensures the value doesn't exceed maxScale
15371
+ // Math.max ensures the value isn't less than minScale
15372
+ return Math.max(minScale, Math.min(maxScale, scaleFactor));
15373
+ }
15295
15374
 
15296
15375
  class IntersectionObserverService {
15297
15376
  constructor(defaultOptions = {}) {
@@ -15341,7 +15420,7 @@ var ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX;
15341
15420
  ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX["PRODUCT_IDS"] = 3] = "PRODUCT_IDS";
15342
15421
  ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX["CREATED_AT"] = 4] = "CREATED_AT";
15343
15422
  })(ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX || (ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX = {}));
15344
- class LocalStorage {
15423
+ class LocalStorageService {
15345
15424
  constructor() {
15346
15425
  if (typeof window.localStorage === 'undefined') {
15347
15426
  console.warn('Local storage is not supported in this environment');
@@ -15354,13 +15433,13 @@ class LocalStorage {
15354
15433
  this.removeExpiredSpots();
15355
15434
  }
15356
15435
  static getInstance() {
15357
- if (!LocalStorage.instance) {
15358
- LocalStorage.instance = new LocalStorage();
15436
+ if (!LocalStorageService.instance) {
15437
+ LocalStorageService.instance = new LocalStorageService();
15359
15438
  }
15360
- return LocalStorage.instance;
15439
+ return LocalStorageService.instance;
15361
15440
  }
15362
15441
  syncLocalStorage() {
15363
- const localStorageData = window.localStorage.getItem(LocalStorage.localStorageKey);
15442
+ const localStorageData = window.localStorage.getItem(LocalStorageService.localStorageKey);
15364
15443
  // TODO: Encrypt the data before storing it in the local storage
15365
15444
  if (localStorageData) {
15366
15445
  try {
@@ -15410,17 +15489,17 @@ class LocalStorage {
15410
15489
  for (const [key, value] of Object.entries(data)) {
15411
15490
  dataArray[key] = this.objectToArray(value);
15412
15491
  }
15413
- window.localStorage.setItem(LocalStorage.localStorageKey, JSON.stringify(dataArray));
15492
+ window.localStorage.setItem(LocalStorageService.localStorageKey, JSON.stringify(dataArray));
15414
15493
  }
15415
15494
  clearLocalStorage() {
15416
- window.localStorage.removeItem(LocalStorage.localStorageKey);
15495
+ window.localStorage.removeItem(LocalStorageService.localStorageKey);
15417
15496
  }
15418
15497
  removeExpiredSpots() {
15419
15498
  var _a;
15420
15499
  const currentTime = Date.now();
15421
15500
  (_a = this.spots) === null || _a === void 0 ? void 0 : _a.forEach((spot, spotId) => {
15422
15501
  var _a, _b;
15423
- if (currentTime - ((_a = spot.createdAt) !== null && _a !== void 0 ? _a : 0) > LocalStorage.spotExpirationTime) {
15502
+ if (currentTime - ((_a = spot.createdAt) !== null && _a !== void 0 ? _a : 0) > LocalStorageService.spotExpirationTime) {
15424
15503
  (_b = this.spots) === null || _b === void 0 ? void 0 : _b.delete(spotId);
15425
15504
  }
15426
15505
  });
@@ -15445,8 +15524,89 @@ class LocalStorage {
15445
15524
  };
15446
15525
  }
15447
15526
  }
15448
- LocalStorage.localStorageKey = 'lc_rmn';
15449
- LocalStorage.spotExpirationTime = 1000 * 60 * 60 * 24 * 7; // 7 days
15527
+ LocalStorageService.localStorageKey = 'lc_rmn';
15528
+ LocalStorageService.spotExpirationTime = 1000 * 60 * 60 * 24 * 7; // 7 days
15529
+
15530
+ /**
15531
+ * PubsubService class
15532
+ * Manages event subscriptions and publications
15533
+ * @template IRmnEventMap A record type defining the structure of events and their data
15534
+ */
15535
+ class PubsubService {
15536
+ constructor() {
15537
+ /**
15538
+ * Object to store subscribers for each event type
15539
+ */
15540
+ this.subscribers = {};
15541
+ }
15542
+ static getInstance() {
15543
+ if (!PubsubService.instance) {
15544
+ PubsubService.instance = new PubsubService();
15545
+ }
15546
+ return PubsubService.instance;
15547
+ }
15548
+ /**
15549
+ * Subscribe to an event
15550
+ * @param eventType - The type of event to subscribe to
15551
+ * @param callback - The function to be called when the event is published
15552
+ * @returns A function to unsubscribe from the event
15553
+ *
15554
+ * @Example:
15555
+ * const unsubscribe = pubSub.subscribe('userLogin', (data) => {
15556
+ * console.log(`User ${data.username} logged in`);
15557
+ * });
15558
+ */
15559
+ subscribe(eventType, callback) {
15560
+ if (!this.subscribers[eventType]) {
15561
+ this.subscribers[eventType] = [];
15562
+ }
15563
+ this.subscribers[eventType].push(callback);
15564
+ // Return an unsubscribe function
15565
+ return () => {
15566
+ this.subscribers[eventType] = this.subscribers[eventType].filter((cb) => cb !== callback);
15567
+ };
15568
+ }
15569
+ /**
15570
+ * Publish an event
15571
+ * @param eventType - The type of event to publish
15572
+ * @param data - The data to be passed to the event subscribers
15573
+ *
15574
+ * @Example:
15575
+ * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
15576
+ */
15577
+ publish(eventType, data) {
15578
+ if (!this.subscribers[eventType]) {
15579
+ return;
15580
+ }
15581
+ this.subscribers[eventType].forEach((callback) => callback(data));
15582
+ }
15583
+ }
15584
+ /**
15585
+ * Usage Example:
15586
+ *
15587
+ * interface IRmnEventMap {
15588
+ * userLogin: { username: string; timestamp: number };
15589
+ * pageView: { url: string; timestamp: number };
15590
+ * }
15591
+ *
15592
+ * const pubSub = new PubsubService<IRmnEventMap>();
15593
+ *
15594
+ * // Subscribe to events
15595
+ * const unsubscribeLogin = pubSub.subscribe('userLogin', (data) => {
15596
+ * console.log(`User ${data.username} logged in at ${new Date(data.timestamp)}`);
15597
+ * });
15598
+ *
15599
+ * pubSub.subscribe('pageView', (data) => {
15600
+ * console.log(`Page ${data.url} viewed at ${new Date(data.timestamp)}`);
15601
+ * });
15602
+ *
15603
+ * // Publish events
15604
+ * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
15605
+ * pubSub.publish('pageView', { url: '/home', timestamp: Date.now() });
15606
+ *
15607
+ * // Unsubscribe from an event
15608
+ * unsubscribeLogin();
15609
+ */
15450
15610
 
15451
15611
  class ResizeObserverService {
15452
15612
  constructor({ element, maxSize, minScale }) {
@@ -15516,36 +15676,6 @@ class ResizeObserverService {
15516
15676
  }
15517
15677
  }
15518
15678
 
15519
- function calculateScaleFactor(elementScale) {
15520
- // Step 1: Apply square root for non-linear scaling
15521
- // This creates a more gradual scaling effect, especially for larger changes
15522
- // For example:
15523
- // - elementScale of 0.25 (1/4 size) becomes 0.5
15524
- // - elementScale of 1 (unchanged) remains 1
15525
- // - elementScale of 4 (4x size) becomes 2
15526
- const baseFactor = Math.sqrt(elementScale);
15527
- // Step 2: Apply additional dampening to further soften the scaling effect
15528
- // The dampening factor (0.5) can be adjusted:
15529
- // - Lower values (closer to 0) make scaling more subtle
15530
- // - Higher values (closer to 1) make scaling more pronounced
15531
- const dampening = 0.35;
15532
- // Calculate the scaleFactor:
15533
- // 1. (baseFactor - 1) represents the change from the original size
15534
- // 2. Multiply by dampening to reduce the effect
15535
- // 3. Add 1 to center the scaling around the original size
15536
- // For example, if baseFactor is 2:
15537
- // scaleFactor = 1 + (2 - 1) * 0.5 = 1.5
15538
- const scaleFactor = 1 + (baseFactor - 1) * dampening;
15539
- // Step 3: Define the allowed range for the scale factor
15540
- // This ensures that the font size never changes too drastically
15541
- const minScale = 0.35; // Font will never be smaller than 50% of original
15542
- const maxScale = 1.5; // Font will never be larger than 150% of original
15543
- // Step 4: Clamp the scale factor to the defined range
15544
- // Math.min ensures the value doesn't exceed maxScale
15545
- // Math.max ensures the value isn't less than minScale
15546
- return Math.max(minScale, Math.min(maxScale, scaleFactor));
15547
- }
15548
-
15549
15679
  const CAROUSEL_COMPONENT_STYLE = ({ width, height, fluid }) => `
15550
15680
  :host {
15551
15681
  position: relative;
@@ -15560,11 +15690,13 @@ const CAROUSEL_COMPONENT_STYLE = ({ width, height, fluid }) => `
15560
15690
  position: relative;
15561
15691
  height: 100%;
15562
15692
  width: 100%;
15693
+ display: flex;
15694
+ transition: transform 0.5s ease-in-out;
15563
15695
  }
15564
15696
 
15565
15697
  .slide {
15566
- display: none;
15567
-
15698
+ flex: 0 0 100%;
15699
+ display: flex;
15568
15700
  justify-content: center;
15569
15701
  align-items: center;
15570
15702
  height: 100%;
@@ -15869,9 +16001,9 @@ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined
15869
16001
  renderSlides() {
15870
16002
  const slidesContainer = document.createElement('div');
15871
16003
  slidesContainer.className = 'slides';
15872
- this.slides.forEach((slide, index) => {
16004
+ this.slides.forEach((slide) => {
15873
16005
  const slideElement = document.createElement('div');
15874
- slideElement.className = `slide ${index === this.currentSlide ? 'active' : ''}`;
16006
+ slideElement.className = 'slide';
15875
16007
  if (slide instanceof HTMLElement) {
15876
16008
  slideElement.appendChild(slide);
15877
16009
  }
@@ -15950,10 +16082,9 @@ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined
15950
16082
  updateCarousel() {
15951
16083
  if (!this.slidesContainer)
15952
16084
  return;
15953
- const slides = Array.from(this.slidesContainer.children);
15954
- slides.forEach((slide, index) => {
15955
- slide.classList.toggle('active', index === this.currentSlide);
15956
- });
16085
+ // Calculate the translation distance based on current slide
16086
+ const translateX = -this.currentSlide * 100;
16087
+ this.slidesContainer.style.transform = `translateX(${translateX}%)`;
15957
16088
  this.updateDots();
15958
16089
  }
15959
16090
  updateDots() {
@@ -16377,12 +16508,13 @@ function spotHtmlStringToElement(htmlString) {
16377
16508
  spot.className = 'spot';
16378
16509
  spot.innerHTML = htmlString;
16379
16510
  Object.assign(spot.style, {
16380
- position: 'relative',
16381
16511
  display: 'block',
16382
16512
  width: '100%',
16383
16513
  height: '100%',
16384
16514
  margin: '0',
16385
16515
  padding: '0',
16516
+ containerType: 'inline-size',
16517
+ position: 'relative',
16386
16518
  });
16387
16519
  return spot;
16388
16520
  }
@@ -17252,7 +17384,6 @@ const STYLES$6 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
17252
17384
  box-sizing: border-box;
17253
17385
  color: ${textColor};
17254
17386
  cursor: pointer;
17255
- container-type: inline-size;
17256
17387
  }
17257
17388
 
17258
17389
  .${prefix}__text {
@@ -17268,7 +17399,7 @@ const STYLES$6 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
17268
17399
  .${prefix}__header {
17269
17400
  font-size: 24px;
17270
17401
  margin: 0;
17271
- font-family: "Cormorant";
17402
+ font-family: "Cormorant", system-ui;
17272
17403
  font-style: normal;
17273
17404
  font-weight: 300;
17274
17405
  line-height: normal;
@@ -17387,7 +17518,6 @@ const STYLES$5 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
17387
17518
  height: 100%;
17388
17519
  display: block;
17389
17520
  position: relative;
17390
- container-type: inline-size;
17391
17521
  }
17392
17522
 
17393
17523
  .${prefix}__content {
@@ -17578,15 +17708,20 @@ function rbHomepageHeroThreeTileTemplate(spot, config) {
17578
17708
  const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextColor = textColor, primaryImage, mobilePrimaryImage = primaryImage, }, { prefix }) => `
17579
17709
  <style>
17580
17710
  .${prefix} {
17711
+ width: 100%;
17712
+ height: 100%;
17713
+ background-color: transparent;
17714
+ cursor: pointer;
17715
+ position: relative;
17716
+ }
17717
+
17718
+ .${prefix}__content {
17581
17719
  width: 100%;
17582
17720
  height: 100%;
17583
17721
  display: flex;
17584
17722
  flex-direction: column-reverse;
17585
17723
  background-color: transparent;
17586
17724
  gap: 6px;
17587
- cursor: pointer;
17588
- container-type: inline-size;
17589
- position: relative;
17590
17725
  }
17591
17726
 
17592
17727
  .${prefix}__image {
@@ -17617,7 +17752,7 @@ const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
17617
17752
  font-size: 18px;
17618
17753
  margin: 0;
17619
17754
  color: inherit;
17620
- font-family: "Cormorant";
17755
+ font-family: "Cormorant", system-ui;
17621
17756
  font-style: normal;
17622
17757
  font-weight: 700;
17623
17758
  line-height: normal;
@@ -17659,7 +17794,7 @@ const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
17659
17794
  }
17660
17795
 
17661
17796
  @container (min-width: 768px) {
17662
- .${prefix} {
17797
+ .${prefix}__content {
17663
17798
  flex-direction: row;
17664
17799
  }
17665
17800
  .${prefix}__image {
@@ -17714,12 +17849,14 @@ function rbHomepageHeroTwoTileTemplate(spot, config) {
17714
17849
  ${GFONT_CORMORANT}
17715
17850
  ${STYLES$4(spot, config)}
17716
17851
  <div class="${prefix}">
17717
- <div class="${prefix}__text">
17718
- ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17719
- ${spot.description ? `<p class="${prefix}__description">${spot.description}</p>` : ''}
17720
- ${spot.ctaText ? `<span class="${prefix}__cta-button">${spot.ctaText}</span>` : ''}
17721
- </div>
17722
- <div class="${prefix}__image"></div>
17852
+ <div class="${prefix}__content">
17853
+ <div class="${prefix}__text">
17854
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17855
+ ${spot.description ? `<p class="${prefix}__description">${spot.description}</p>` : ''}
17856
+ ${spot.ctaText ? `<span class="${prefix}__cta-button">${spot.ctaText}</span>` : ''}
17857
+ </div>
17858
+ <div class="${prefix}__image"></div>
17859
+ </div>
17723
17860
  </div>
17724
17861
  `;
17725
17862
  }
@@ -17742,12 +17879,10 @@ const STYLES$3 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
17742
17879
  overflow: hidden;
17743
17880
  cursor: pointer;
17744
17881
  color: ${textColor};
17745
- container-type: inline-size;
17746
17882
  }
17747
17883
 
17748
17884
  .${prefix}__text {
17749
17885
  padding: 20px;
17750
- width: 70%;
17751
17886
  display: flex;
17752
17887
  flex-direction: column;
17753
17888
  justify-content: center;
@@ -17759,7 +17894,7 @@ const STYLES$3 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
17759
17894
  color: inherit;
17760
17895
  margin: 0;
17761
17896
  font-size: 20px;
17762
- font-family: "Cormorant";
17897
+ font-family: "Cormorant", system-ui;
17763
17898
  font-style: normal;
17764
17899
  font-weight: 300;
17765
17900
  line-height: normal;
@@ -17878,7 +18013,6 @@ const STYLES$2 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = pr
17878
18013
  background-size: cover;
17879
18014
  background-position: center;
17880
18015
  background-repeat: no-repeat;
17881
- container-type: inline-size;
17882
18016
  position: relative;
17883
18017
  }
17884
18018
 
@@ -17948,12 +18082,7 @@ const STYLES$1 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = pr
17948
18082
  border-radius: 5px;
17949
18083
  overflow: hidden;
17950
18084
  cursor: pointer;
17951
- container-type: inline-size;
17952
- }
17953
-
17954
- .${prefix}__text {
17955
- padding: 10px;
17956
- width: 70%;
18085
+ position: relative;
17957
18086
  }
17958
18087
 
17959
18088
  .${prefix}__header {
@@ -17964,6 +18093,7 @@ const STYLES$1 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = pr
17964
18093
  font-style: normal;
17965
18094
  font-weight: 400;
17966
18095
  margin: 0;
18096
+ padding: 10px;
17967
18097
  }
17968
18098
 
17969
18099
  @container (min-width: 640px) {
@@ -17981,9 +18111,7 @@ function rbSmallCategoryImageToutTemplate(spot, config) {
17981
18111
  ${GFONT_CORMORANT}
17982
18112
  ${STYLES$1(spot, config)}
17983
18113
  <div class="${prefix}">
17984
- <div class="${prefix}__text">
17985
- ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17986
- </div>
18114
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17987
18115
  </div>
17988
18116
  `;
17989
18117
  }
@@ -17998,7 +18126,6 @@ const STYLES = ({ textColor = '#000000', backgroundColor = 'transparent', primar
17998
18126
  display: flex;
17999
18127
  flex-direction: column;
18000
18128
  border-radius: 5px;
18001
- container-type: inline-size;
18002
18129
  }
18003
18130
 
18004
18131
  .${prefix}__image {
@@ -18134,97 +18261,65 @@ const SPOT_TEMPLATE_HTML_ELEMENT = (spot, config) => {
18134
18261
  return spotHtmlStringToElement(spotHtmlString);
18135
18262
  };
18136
18263
 
18137
- /**
18138
- * PubSub class
18139
- * Manages event subscriptions and publications
18140
- * @template IRmnEventMap A record type defining the structure of events and their data
18141
- */
18142
- class PubSub {
18264
+ // For the moment, we will only focus on sites that use Google Analytics,
18265
+ // but we will add support for other analytics tools in the future.
18266
+ var AnalyticsTool;
18267
+ (function (AnalyticsTool) {
18268
+ AnalyticsTool["GoogleAnalytics"] = "google-analytics";
18269
+ AnalyticsTool["Other"] = "Other";
18270
+ })(AnalyticsTool || (AnalyticsTool = {}));
18271
+
18272
+ class DataLayerMonitor {
18143
18273
  constructor() {
18144
- /**
18145
- * Object to store subscribers for each event type
18146
- */
18147
- this.subscribers = {};
18274
+ if (!window.dataLayer) {
18275
+ return;
18276
+ }
18277
+ this.originalPush = window.dataLayer.push;
18148
18278
  }
18149
18279
  static getInstance() {
18150
- if (!PubSub.instance) {
18151
- PubSub.instance = new PubSub();
18280
+ if (!DataLayerMonitor.instance) {
18281
+ DataLayerMonitor.instance = new DataLayerMonitor();
18152
18282
  }
18153
- return PubSub.instance;
18283
+ return DataLayerMonitor.instance;
18154
18284
  }
18155
- /**
18156
- * Subscribe to an event
18157
- * @param eventType - The type of event to subscribe to
18158
- * @param callback - The function to be called when the event is published
18159
- * @returns A function to unsubscribe from the event
18160
- *
18161
- * @Example:
18162
- * const unsubscribe = pubSub.subscribe('userLogin', (data) => {
18163
- * console.log(`User ${data.username} logged in`);
18164
- * });
18165
- */
18166
- subscribe(eventType, callback) {
18167
- if (!this.subscribers[eventType]) {
18168
- this.subscribers[eventType] = [];
18285
+ setListener(listener) {
18286
+ this.listener = listener;
18287
+ }
18288
+ start() {
18289
+ window.dataLayer.push = (...args) => {
18290
+ const result = this.originalPush.apply(window.dataLayer, args);
18291
+ const pushedEvent = args[0];
18292
+ if (this.listener) {
18293
+ const normalizedData = this.cleanEventData(pushedEvent);
18294
+ if (normalizedData) {
18295
+ this.listener(normalizedData);
18296
+ }
18297
+ }
18298
+ return result;
18299
+ };
18300
+ }
18301
+ cleanEventData(data) {
18302
+ const eventName = getEventTypeFromRawEvent(data.event);
18303
+ if (!eventName) {
18304
+ return null;
18169
18305
  }
18170
- this.subscribers[eventType].push(callback);
18171
- // Return an unsubscribe function
18172
- return () => {
18173
- this.subscribers[eventType] = this.subscribers[eventType].filter((cb) => cb !== callback);
18306
+ const productIds = extractDeepIds(data.value);
18307
+ return {
18308
+ event: eventName,
18309
+ productIds,
18174
18310
  };
18175
18311
  }
18176
- /**
18177
- * Publish an event
18178
- * @param eventType - The type of event to publish
18179
- * @param data - The data to be passed to the event subscribers
18180
- *
18181
- * @Example:
18182
- * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
18183
- */
18184
- publish(eventType, data) {
18185
- if (!this.subscribers[eventType]) {
18186
- return;
18312
+ stop() {
18313
+ if (this.originalPush) {
18314
+ window.dataLayer.push = this.originalPush;
18187
18315
  }
18188
- this.subscribers[eventType].forEach((callback) => callback(data));
18316
+ this.listener = undefined;
18189
18317
  }
18190
18318
  }
18191
- /**
18192
- * Usage Example:
18193
- *
18194
- * interface IRmnEventMap {
18195
- * userLogin: { username: string; timestamp: number };
18196
- * pageView: { url: string; timestamp: number };
18197
- * }
18198
- *
18199
- * const pubSub = new PubSub<IRmnEventMap>();
18200
- *
18201
- * // Subscribe to events
18202
- * const unsubscribeLogin = pubSub.subscribe('userLogin', (data) => {
18203
- * console.log(`User ${data.username} logged in at ${new Date(data.timestamp)}`);
18204
- * });
18205
- *
18206
- * pubSub.subscribe('pageView', (data) => {
18207
- * console.log(`Page ${data.url} viewed at ${new Date(data.timestamp)}`);
18208
- * });
18209
- *
18210
- * // Publish events
18211
- * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
18212
- * pubSub.publish('pageView', { url: '/home', timestamp: Date.now() });
18213
- *
18214
- * // Unsubscribe from an event
18215
- * unsubscribeLogin();
18216
- */
18217
18319
 
18218
18320
  // @TODO: Add support for user to push events to our own data layer, if they don't use any analytics tool.
18219
18321
  // window.rmnDataLayer = window.rmnDataLayer || [];
18220
- // For the moment, we will only focus on sites that use Google Analytics,
18221
- // but we will add support for other analytics tools in the future.
18222
- var AnalyticsTool;
18223
- (function (AnalyticsTool) {
18224
- AnalyticsTool["GoogleAnalytics"] = "google-analytics";
18225
- AnalyticsTool["Other"] = "Other";
18226
- })(AnalyticsTool || (AnalyticsTool = {}));
18227
- class UserMonitor {
18322
+ class MonitorService {
18228
18323
  constructor() {
18229
18324
  const analyticsTool = this.detectAnalyticsTool();
18230
18325
  switch (analyticsTool) {
@@ -18239,29 +18334,97 @@ class UserMonitor {
18239
18334
  if (analyticsTool === AnalyticsTool.Other) {
18240
18335
  return;
18241
18336
  }
18242
- this.localStorage = LocalStorage.getInstance();
18337
+ this.pubSubService = PubsubService.getInstance();
18338
+ this.localStorageService = LocalStorageService.getInstance();
18243
18339
  }
18244
18340
  static getInstance() {
18245
- if (!UserMonitor.instance) {
18246
- UserMonitor.instance = new UserMonitor();
18341
+ if (!MonitorService.instance) {
18342
+ MonitorService.instance = new MonitorService();
18247
18343
  }
18248
- return UserMonitor.instance;
18344
+ return MonitorService.instance;
18249
18345
  }
18250
18346
  start() {
18251
18347
  if (!this.implementedMonitor)
18252
18348
  return;
18253
- this.implementedMonitor.setListener((eventData) => {
18349
+ this.implementedMonitor.setListener(async (eventData) => {
18254
18350
  var _a;
18255
- this.matchAndFireEvent(eventData, (_a = this.localStorage) === null || _a === void 0 ? void 0 : _a.getSpots());
18351
+ await this.matchAndFireEvent(eventData, (_a = this.localStorageService) === null || _a === void 0 ? void 0 : _a.getSpots());
18256
18352
  });
18257
18353
  this.implementedMonitor.start();
18258
18354
  }
18259
- matchAndFireEvent(
18260
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
18261
- _eventData,
18262
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
18263
- _spots) {
18264
- // console.info({ eventData, spots });
18355
+ async matchAndFireEvent(eventData, spots) {
18356
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
18357
+ if (!spots)
18358
+ return;
18359
+ const eventProductIds = new Set(eventData.productIds);
18360
+ for (const spot of Object.values(spots)) {
18361
+ if (!spot.productIds.length)
18362
+ continue;
18363
+ const hasCommonProductIds = spot.productIds.find((productId) => eventProductIds.has(productId));
18364
+ if (hasCommonProductIds) {
18365
+ switch (eventData.event) {
18366
+ case exports.RMN_SPOT_EVENT.ADD_TO_CART:
18367
+ await this.fireAndPublishSpotEvent({
18368
+ spotEvent: exports.RMN_SPOT_EVENT.ADD_TO_CART,
18369
+ eventUrl: (_b = (_a = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.ADD_TO_CART)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
18370
+ placementId: '',
18371
+ spotId: spot.spotId,
18372
+ spotElement: undefined,
18373
+ });
18374
+ break;
18375
+ case exports.RMN_SPOT_EVENT.REMOVE_FROM_CART:
18376
+ await this.fireAndPublishSpotEvent({
18377
+ spotEvent: exports.RMN_SPOT_EVENT.REMOVE_FROM_CART,
18378
+ eventUrl: (_d = (_c = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.REMOVE_FROM_CART)) === null || _c === void 0 ? void 0 : _c.url) !== null && _d !== void 0 ? _d : '',
18379
+ placementId: '',
18380
+ spotId: spot.spotId,
18381
+ spotElement: undefined,
18382
+ });
18383
+ break;
18384
+ case exports.RMN_SPOT_EVENT.PURCHASE:
18385
+ await this.fireAndPublishSpotEvent({
18386
+ spotEvent: exports.RMN_SPOT_EVENT.PURCHASE,
18387
+ eventUrl: (_f = (_e = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.PURCHASE)) === null || _e === void 0 ? void 0 : _e.url) !== null && _f !== void 0 ? _f : '',
18388
+ placementId: '',
18389
+ spotId: spot.spotId,
18390
+ spotElement: undefined,
18391
+ });
18392
+ break;
18393
+ case exports.RMN_SPOT_EVENT.ADD_TO_WISHLIST:
18394
+ await this.fireAndPublishSpotEvent({
18395
+ spotEvent: exports.RMN_SPOT_EVENT.ADD_TO_WISHLIST,
18396
+ eventUrl: (_h = (_g = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.ADD_TO_WISHLIST)) === null || _g === void 0 ? void 0 : _g.url) !== null && _h !== void 0 ? _h : '',
18397
+ placementId: '',
18398
+ spotId: spot.spotId,
18399
+ spotElement: undefined,
18400
+ });
18401
+ break;
18402
+ case exports.RMN_SPOT_EVENT.BUY_NOW:
18403
+ await this.fireAndPublishSpotEvent({
18404
+ spotEvent: exports.RMN_SPOT_EVENT.BUY_NOW,
18405
+ eventUrl: (_k = (_j = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.BUY_NOW)) === null || _j === void 0 ? void 0 : _j.url) !== null && _k !== void 0 ? _k : '',
18406
+ placementId: '',
18407
+ spotId: spot.spotId,
18408
+ spotElement: undefined,
18409
+ });
18410
+ break;
18411
+ }
18412
+ }
18413
+ }
18414
+ }
18415
+ async fireAndPublishSpotEvent({ spotEvent, eventUrl, placementId, spotId, spotElement, }) {
18416
+ await fireEvent({
18417
+ event: spotEvent,
18418
+ eventUrl,
18419
+ });
18420
+ if (!this.pubSubService)
18421
+ return;
18422
+ this.pubSubService.publish(exports.RMN_EVENT.SPOT_EVENT, {
18423
+ eventType: spotEvent,
18424
+ placementId,
18425
+ spotId,
18426
+ spotElement,
18427
+ });
18265
18428
  }
18266
18429
  detectAnalyticsTool() {
18267
18430
  let analyticsTool = AnalyticsTool.Other;
@@ -18289,13 +18452,13 @@ class UserMonitor {
18289
18452
 
18290
18453
  class EventService {
18291
18454
  constructor() {
18292
- this.pubSub = PubSub.getInstance();
18293
- this.localStorage = LocalStorage.getInstance();
18455
+ this.pubSubService = PubsubService.getInstance();
18456
+ this.localStorageService = LocalStorageService.getInstance();
18294
18457
  this.activeSpots = new Map();
18295
18458
  this.spotStates = new Map();
18296
18459
  this.intersectionObserver = new IntersectionObserverService();
18297
18460
  // Start the user monitor, which will track and check user interactions
18298
- UserMonitor.getInstance().start();
18461
+ MonitorService.getInstance().start();
18299
18462
  }
18300
18463
  static getInstance() {
18301
18464
  if (!EventService.instance) {
@@ -18304,10 +18467,10 @@ class EventService {
18304
18467
  return EventService.instance;
18305
18468
  }
18306
18469
  subscribe(eventType, callback) {
18307
- return this.pubSub.subscribe(eventType, callback);
18470
+ return this.pubSubService.subscribe(eventType, callback);
18308
18471
  }
18309
18472
  publish(eventType, data) {
18310
- this.pubSub.publish(eventType, data);
18473
+ this.pubSubService.publish(eventType, data);
18311
18474
  }
18312
18475
  registerSpot(params) {
18313
18476
  const { placementId, spot, spotElement } = params;
@@ -18391,7 +18554,7 @@ class EventService {
18391
18554
  const merged = this.deepMerge(currentState, updates);
18392
18555
  this.spotStates.set(placementId, merged);
18393
18556
  if (publish) {
18394
- this.pubSub.publish(exports.RMN_SPOT_EVENT.LIFECYCLE_STATE, this.spotStates.get(placementId));
18557
+ this.pubSubService.publish(exports.RMN_EVENT.LIFECYCLE_STATE, this.spotStates.get(placementId));
18395
18558
  }
18396
18559
  }
18397
18560
  deepMerge(current, updates) {
@@ -18422,19 +18585,18 @@ class EventService {
18422
18585
  }
18423
18586
  async handleClick({ placementId, spot, spotElement, }) {
18424
18587
  var _a, _b, _c;
18425
- // Publish click event
18426
- this.pubSub.publish(exports.RMN_SPOT_EVENT.CLICK, {
18588
+ this.pubSubService.publish(exports.RMN_EVENT.SPOT_EVENT, {
18589
+ eventType: exports.RMN_SPOT_EVENT.CLICK,
18427
18590
  placementId,
18428
18591
  spotId: spot.id,
18429
18592
  spotElement,
18430
18593
  });
18431
- // Fire click event
18432
- await this.fireEvent({
18594
+ await fireEvent({
18433
18595
  event: exports.RMN_SPOT_EVENT.CLICK,
18434
18596
  eventUrl: (_b = (_a = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.CLICK)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
18435
18597
  });
18436
18598
  // Save spot to local storage for event tracking
18437
- this.localStorage.setSpot(spot.id, {
18599
+ this.localStorageService.setSpot(spot.id, {
18438
18600
  spotId: spot.id,
18439
18601
  spotType: spot.spot,
18440
18602
  events: spot.events,
@@ -18454,90 +18616,20 @@ class EventService {
18454
18616
  this.intersectionObserver.observe(spotElement, spotIsVisibleCallback);
18455
18617
  }
18456
18618
  fireImpressionEvent(placementId, spot, spotElement) {
18457
- this.pubSub.publish(exports.RMN_SPOT_EVENT.IMPRESSION, {
18619
+ this.pubSubService.publish(exports.RMN_EVENT.SPOT_EVENT, {
18620
+ eventType: exports.RMN_SPOT_EVENT.IMPRESSION,
18458
18621
  placementId,
18459
18622
  spotId: spot.id,
18460
18623
  spotElement,
18461
18624
  });
18462
18625
  (async () => {
18463
18626
  var _a, _b;
18464
- await this.fireEvent({
18627
+ await fireEvent({
18465
18628
  event: exports.RMN_SPOT_EVENT.IMPRESSION,
18466
18629
  eventUrl: (_b = (_a = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.IMPRESSION)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
18467
18630
  });
18468
18631
  })();
18469
18632
  }
18470
- /**
18471
- * Fires an event using the navigator.sendBeacon method or a fallback method if sendBeacon is not available.
18472
- * If the event is a click event and a redirect URL is found, it redirects the user to that URL.
18473
- *
18474
- * @param {IFireEventParams} params - The parameters for firing the event.
18475
- * @param {RMN_SPOT_EVENT} params.event - The event type.
18476
- * @param {string} params.eventUrl - The URL to which the event is sent.
18477
- * @returns {Promise<void>} - A promise that resolves when the event is fired.
18478
- */
18479
- async fireEvent({ event, eventUrl }) {
18480
- var _a;
18481
- try {
18482
- const didFireEvent = ((_a = navigator === null || navigator === void 0 ? void 0 : navigator.sendBeacon) === null || _a === void 0 ? void 0 : _a.call(navigator, eventUrl)) || (await this.fallbackEventFire(eventUrl));
18483
- if (!didFireEvent) {
18484
- return;
18485
- }
18486
- if (event === exports.RMN_SPOT_EVENT.CLICK) {
18487
- const redirectUrl = this.getRedirectUrlFromPayload(eventUrl);
18488
- if (redirectUrl) {
18489
- window.location.href = redirectUrl;
18490
- }
18491
- }
18492
- }
18493
- catch (_b) {
18494
- // Handle errors silently
18495
- }
18496
- }
18497
- // Fallback method using fetch if sendBeacon isn't available
18498
- async fallbackEventFire(url) {
18499
- try {
18500
- const racePromise = Promise.race([
18501
- // Promise #1: The fetch request
18502
- fetch(url, {
18503
- method: 'POST',
18504
- keepalive: true,
18505
- }),
18506
- // Promise #2: The timeout
18507
- new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), 2000)),
18508
- ]);
18509
- /**
18510
- * Prevent requests from hanging indefinitely
18511
- * Improve user experience by failing fast
18512
- * Handle slow network conditions gracefully
18513
- * Ensure resources are freed up in a timely manner
18514
- */
18515
- const response = await racePromise;
18516
- return response.ok;
18517
- }
18518
- catch (_a) {
18519
- return false;
18520
- }
18521
- }
18522
- /**
18523
- * Extracts and decodes a URL from a base64-encoded query parameter.
18524
- *
18525
- * @param {string} url - The URL containing the base64-encoded query parameter.
18526
- * @returns {string | null} - The decoded URL or null if not found or invalid.
18527
- */
18528
- getRedirectUrlFromPayload(url) {
18529
- try {
18530
- const base64String = new URL(url).searchParams.get('e');
18531
- if (!base64String) {
18532
- return null;
18533
- }
18534
- const data = JSON.parse(atob(base64String));
18535
- return data.ur || null;
18536
- }
18537
- catch (_a) {
18538
- return null;
18539
- }
18540
- }
18541
18633
  }
18542
18634
 
18543
18635
  const SELECTION_API_PATH = '/spots/selection';