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