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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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';