@liquidcommercedev/rmn-sdk 1.5.0-beta.2 → 1.5.0-beta.20

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.
Files changed (38) hide show
  1. package/dist/index.cjs +2710 -946
  2. package/dist/index.esm.js +2711 -947
  3. package/dist/types/common/helpers/event-type.helper.d.ts +8 -0
  4. package/dist/types/common/helpers/extract-deep.helper.d.ts +28 -0
  5. package/dist/types/common/helpers/index.d.ts +5 -0
  6. package/dist/types/common/helpers/utils.helper.d.ts +35 -0
  7. package/dist/types/enums.d.ts +8 -1
  8. package/dist/types/modules/element/component/skeleton/index.d.ts +2 -0
  9. package/dist/types/modules/element/component/skeleton/skeleton.component.d.ts +3 -0
  10. package/dist/types/modules/element/component/skeleton/skeleton.interface.d.ts +13 -0
  11. package/dist/types/modules/element/component/skeleton/skeleton.template.d.ts +2 -0
  12. package/dist/types/modules/element/element.constant.d.ts +3 -0
  13. package/dist/types/modules/element/element.interface.d.ts +5 -2
  14. package/dist/types/modules/element/element.service.d.ts +11 -0
  15. package/dist/types/modules/element/template/helper.d.ts +2 -1
  16. package/dist/types/modules/event/event.interface.d.ts +11 -32
  17. package/dist/types/modules/event/event.service.d.ts +11 -27
  18. package/dist/types/modules/event/index.d.ts +0 -1
  19. package/dist/types/modules/{event/helpers → helper-service}/index.d.ts +1 -0
  20. package/dist/types/modules/{event/helpers → helper-service}/intersection.service.d.ts +1 -1
  21. package/dist/types/modules/helper-service/localstorage.service.d.ts +77 -0
  22. package/dist/types/modules/{event/pubsub.d.ts → helper-service/pubsub.service.d.ts} +8 -8
  23. package/dist/types/modules/{event/helpers → helper-service}/resize.service.d.ts +1 -1
  24. package/dist/types/modules/monitor/index.d.ts +2 -0
  25. package/dist/types/modules/monitor/monitor.enums.d.ts +4 -0
  26. package/dist/types/modules/monitor/monitor.interface.d.ts +16 -0
  27. package/dist/types/modules/monitor/monitor.service.d.ts +12 -0
  28. package/dist/types/modules/monitor/monitors/datalayer.monitor.d.ts +12 -0
  29. package/dist/types/modules/selection/selection.interface.d.ts +10 -2
  30. package/dist/types/modules/selection/selection.service.d.ts +1 -0
  31. package/dist/types/rmn-client.d.ts +17 -25
  32. package/dist/types/rmn-client.helper.d.ts +41 -0
  33. package/dist/types/types.d.ts +5 -4
  34. package/package.json +1 -1
  35. package/umd/liquidcommerce-rmn-sdk.min.js +1 -1
  36. package/dist/types/modules/element/component/utils.d.ts +0 -1
  37. package/dist/types/modules/event/helpers/localstorage.service.d.ts +0 -26
  38. /package/dist/types/{static.constant.d.ts → example.constant.d.ts} +0 -0
package/dist/index.cjs CHANGED
@@ -3,6 +3,7 @@
3
3
  exports.RMN_SPOT_TYPE = void 0;
4
4
  (function (RMN_SPOT_TYPE) {
5
5
  // Reserve Bar Spot Types
6
+ RMN_SPOT_TYPE["RB_HOMEPAGE_HERO"] = "rbHomepageHero";
6
7
  RMN_SPOT_TYPE["RB_HOMEPAGE_HERO_THREE_TILE"] = "rbHomepageHeroThreeTile";
7
8
  RMN_SPOT_TYPE["RB_HOMEPAGE_HERO_TWO_TILE"] = "rbHomepageHeroTwoTile";
8
9
  RMN_SPOT_TYPE["RB_HOMEPAGE_HERO_FULL_IMAGE"] = "rbHomepageHeroFullImage";
@@ -54,14 +55,21 @@ exports.RMN_FILTER_PROPERTIES = void 0;
54
55
  RMN_FILTER_PROPERTIES["PUBLISHERS"] = "publishers";
55
56
  RMN_FILTER_PROPERTIES["SECTION"] = "section";
56
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 = {}));
57
63
  exports.RMN_SPOT_EVENT = void 0;
58
64
  (function (RMN_SPOT_EVENT) {
59
- RMN_SPOT_EVENT["LIFECYCLE_STATE"] = "LIFECYCLE_STATE";
60
65
  RMN_SPOT_EVENT["IMPRESSION"] = "IMPRESSION";
61
66
  RMN_SPOT_EVENT["CLICK"] = "CLICK";
62
67
  RMN_SPOT_EVENT["PURCHASE"] = "PURCHASE";
63
68
  RMN_SPOT_EVENT["ADD_TO_CART"] = "ADD_TO_CART";
69
+ RMN_SPOT_EVENT["REMOVE_FROM_CART"] = "REMOVE_FROM_CART";
70
+ RMN_SPOT_EVENT["ADD_TO_CART_FROM_DETAILS"] = "ADD_TO_CART_FROM_DETAILS";
64
71
  RMN_SPOT_EVENT["ADD_TO_WISHLIST"] = "ADD_TO_WISHLIST";
72
+ RMN_SPOT_EVENT["EXPAND_PRODUCT"] = "EXPAND_PRODUCT";
65
73
  RMN_SPOT_EVENT["BUY_NOW"] = "BUY_NOW";
66
74
  })(exports.RMN_SPOT_EVENT || (exports.RMN_SPOT_EVENT = {}));
67
75
  exports.RMN_ENV = void 0;
@@ -6034,6 +6042,241 @@ axios.HttpStatusCode = HttpStatusCode;
6034
6042
 
6035
6043
  axios.default = axios;
6036
6044
 
6045
+ /**
6046
+ * Keyword patterns for each RMN_SPOT_EVENT.
6047
+ * Each event type has required keywords that must be present and optional ones.
6048
+ *
6049
+ * Note: The order of checks matters - more specific patterns must be checked before general ones
6050
+ * to avoid incorrect matches.
6051
+ */
6052
+ const EVENT_KEYWORDS = {
6053
+ [exports.RMN_SPOT_EVENT.ADD_TO_CART_FROM_DETAILS]: {
6054
+ required: ['add', 'cart'],
6055
+ optional: ['details', 'detail', 'single', 'profile', 'page', 'pdp'],
6056
+ },
6057
+ [exports.RMN_SPOT_EVENT.BUY_NOW]: {
6058
+ required: ['buy', 'now'],
6059
+ },
6060
+ [exports.RMN_SPOT_EVENT.EXPAND_PRODUCT]: {
6061
+ required: ['product'],
6062
+ optional: ['details', 'expand', 'modal', 'popup', 'quickview', 'view'],
6063
+ },
6064
+ [exports.RMN_SPOT_EVENT.ADD_TO_WISHLIST]: {
6065
+ required: ['add', 'wishlist'],
6066
+ },
6067
+ [exports.RMN_SPOT_EVENT.REMOVE_FROM_CART]: {
6068
+ required: ['remove', 'cart'],
6069
+ },
6070
+ [exports.RMN_SPOT_EVENT.PURCHASE]: {
6071
+ required: ['purchase'],
6072
+ },
6073
+ [exports.RMN_SPOT_EVENT.ADD_TO_CART]: {
6074
+ required: ['add', 'cart'],
6075
+ },
6076
+ };
6077
+ /**
6078
+ * Normalizes an event name by converting to lowercase, removing special characters,
6079
+ * and splitting into words.
6080
+ */
6081
+ function normalizeEventName(event) {
6082
+ return event
6083
+ .toLowerCase()
6084
+ .replace(/[^a-z0-9\s]+/g, ' ') // Replace special chars with spaces
6085
+ .split(/\s+/) // Split on whitespace
6086
+ .filter(Boolean); // Remove empty strings
6087
+ }
6088
+ /**
6089
+ * Checks if a word matches a keyword, considering word boundaries.
6090
+ * This prevents partial word matches (e.g., "card" shouldn't match "cart").
6091
+ */
6092
+ function wordMatchesKeyword(word, keyword) {
6093
+ // Create a RegExp that matches the keyword as a whole word
6094
+ const keywordRegex = new RegExp(`^${keyword}s?$|${keyword}s?\\W|\\W${keyword}s?$|\\W${keyword}s?\\W`);
6095
+ return keywordRegex.test(` ${word} `); // Add spaces to handle word boundaries
6096
+ }
6097
+ /**
6098
+ * Checks if all required keywords and at least one optional keyword (if specified) are present.
6099
+ */
6100
+ function matchesKeywordPattern(words, required, optional) {
6101
+ // Check if all required keywords are present as whole words
6102
+ const hasAllRequired = required.every((keyword) => words.some((word) => wordMatchesKeyword(word, keyword)));
6103
+ if (!hasAllRequired) {
6104
+ return false;
6105
+ }
6106
+ // If no optional keywords are specified, return true
6107
+ if (!(optional === null || optional === void 0 ? void 0 : optional.length)) {
6108
+ return true;
6109
+ }
6110
+ // If optional keywords exist, check if at least one matches as a whole word
6111
+ return optional.some((keyword) => words.some((word) => wordMatchesKeyword(word, keyword)));
6112
+ }
6113
+ /**
6114
+ * Determines the event type from a raw event string by checking for keyword patterns.
6115
+ *
6116
+ * @param {string} [event] - The raw event string to evaluate
6117
+ * @returns {RMN_SPOT_EVENT | null} - The corresponding RMN_SPOT_EVENT or null if no match
6118
+ */
6119
+ function getEventTypeFromRawEvent(event) {
6120
+ if (!(event === null || event === void 0 ? void 0 : event.trim())) {
6121
+ return null;
6122
+ }
6123
+ const words = normalizeEventName(event);
6124
+ // Use Object.entries to maintain the exact order defined in EVENT_KEYWORDS
6125
+ for (const [eventType, { required, optional }] of Object.entries(EVENT_KEYWORDS)) {
6126
+ if (matchesKeywordPattern(words, required, optional)) {
6127
+ return eventType;
6128
+ }
6129
+ }
6130
+ return null;
6131
+ }
6132
+
6133
+ // Configuration object with target field names
6134
+ const extractorConfig = {
6135
+ ids: [
6136
+ // Ps: The function handles all the variations of keywords that end with "id" or "ids"
6137
+ // Universal product identifiers
6138
+ 'gtin',
6139
+ 'gtin8',
6140
+ 'gtin12',
6141
+ 'gtin13',
6142
+ 'gtin14',
6143
+ 'mpn',
6144
+ 'sku',
6145
+ 'upc',
6146
+ 'ean',
6147
+ 'isbn',
6148
+ 'isbn10',
6149
+ 'isbn13',
6150
+ 'asin',
6151
+ // Product codes and references
6152
+ 'coupon',
6153
+ 'barcode',
6154
+ 'product_code',
6155
+ 'part_number',
6156
+ 'model_number',
6157
+ 'item_variant',
6158
+ 'item_number',
6159
+ 'article_number',
6160
+ 'reference',
6161
+ ],
6162
+ price: [
6163
+ 'price',
6164
+ 'unitPrice',
6165
+ 'cost',
6166
+ 'current_price',
6167
+ 'sale_price',
6168
+ 'price_value',
6169
+ 'sale_price_value',
6170
+ 'regular_price',
6171
+ 'discount_price',
6172
+ 'unit_price',
6173
+ 'original_price',
6174
+ 'final_price',
6175
+ 'retail_price',
6176
+ ],
6177
+ };
6178
+ /**
6179
+ * Extracts deep values from an object based on specified target type
6180
+ * @param data - The source data object to extract values from
6181
+ * @param target - The type of values to extract ('ids' or 'price')
6182
+ * @param options - Optional configuration for the extraction process
6183
+ * @returns Array of extracted values or a single value if onlyFirst is true
6184
+ */
6185
+ function extractDeepValues(data, target, options = {}) {
6186
+ const {
6187
+ // eslint-disable-next-line @typescript-eslint/naming-convention
6188
+ onlyFirst = false, shouldIncludeZero = false, } = options;
6189
+ const values = [];
6190
+ const targetProperties = new Set(extractorConfig[target].map((name) => name.toLowerCase()));
6191
+ /**
6192
+ * Checks if a property name matches the target criteria
6193
+ */
6194
+ const isTargetField = (key) => {
6195
+ const normalizedKey = key.toLowerCase();
6196
+ const hasTarget = targetProperties.has(normalizedKey);
6197
+ if (target === 'ids') {
6198
+ return normalizedKey.endsWith('id') || normalizedKey.endsWith('ids') || hasTarget;
6199
+ }
6200
+ return hasTarget;
6201
+ };
6202
+ /**
6203
+ * Validates and normalizes extracted values
6204
+ */
6205
+ const validateValue = (value) => {
6206
+ if (typeof value === 'string') {
6207
+ return value.trim().length > 0;
6208
+ }
6209
+ if (typeof value === 'number') {
6210
+ return !isNaN(value) && (shouldIncludeZero || value !== 0);
6211
+ }
6212
+ return false;
6213
+ };
6214
+ /**
6215
+ * Processes a value and extracts matching fields
6216
+ */
6217
+ const processValue = (value, currentKey) => {
6218
+ // Early exit conditions
6219
+ if (value == null || (onlyFirst && values.length > 0))
6220
+ return;
6221
+ // Process current value if it matches target criteria
6222
+ if (currentKey && isTargetField(currentKey)) {
6223
+ if (Array.isArray(value)) {
6224
+ const validValues = value.filter(validateValue);
6225
+ values.push(...validValues);
6226
+ }
6227
+ else if (validateValue(value)) {
6228
+ values.push(value);
6229
+ }
6230
+ return;
6231
+ }
6232
+ // Recursive processing for nested structures
6233
+ if (Array.isArray(value)) {
6234
+ value.forEach((item) => processValue(item));
6235
+ }
6236
+ else if (typeof value === 'object') {
6237
+ Object.entries(value).forEach(([key, val]) => processValue(val, key));
6238
+ }
6239
+ };
6240
+ processValue(data);
6241
+ // Return based on options
6242
+ if (values.length === 0)
6243
+ return undefined;
6244
+ return onlyFirst ? values[0] : values;
6245
+ }
6246
+ /**
6247
+ * Cleans and normalizes an array of product IDs by:
6248
+ * 1. Converting all IDs to strings
6249
+ * 2. Trimming whitespace
6250
+ * 3. Removing leading zeros while preserving single "0"
6251
+ * 4. Filtering out empty/invalid values
6252
+ *
6253
+ * @param productIds - Array of product IDs that can be either strings or numbers
6254
+ * @returns Array of cleaned string IDs
6255
+ *
6256
+ * @example
6257
+ * cleanProductIds(["001", " 123 ", 456, "0"]) // ["1", "123", "456", "0"]
6258
+ */
6259
+ function cleanProductIds(productIds) {
6260
+ if (!Array.isArray(productIds)) {
6261
+ return [];
6262
+ }
6263
+ return productIds
6264
+ .map((id) => {
6265
+ // Guard against null/undefined
6266
+ if (id == null) {
6267
+ return '';
6268
+ }
6269
+ // Convert to string and trim
6270
+ const stringId = String(id).trim();
6271
+ if (!stringId) {
6272
+ return '';
6273
+ }
6274
+ // Remove leading zeros while preserving single "0"
6275
+ return stringId === '0' ? '0' : stringId.replace(/^0+/, '');
6276
+ })
6277
+ .filter((id) => id !== ''); // Remove empty strings
6278
+ }
6279
+
6037
6280
  class SingletonManager {
6038
6281
  /**
6039
6282
  * Retrieves an instance of the specified class using the provided instance creator function.
@@ -6199,126 +6442,406 @@ class ObjectHelper {
6199
6442
  }
6200
6443
  }
6201
6444
 
6202
- var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
6203
-
6204
- function getAugmentedNamespace(n) {
6205
- if (n.__esModule) return n;
6206
- var f = n.default;
6207
- if (typeof f == "function") {
6208
- var a = function a () {
6209
- if (this instanceof a) {
6210
- return Reflect.construct(f, arguments, this.constructor);
6211
- }
6212
- return f.apply(this, arguments);
6213
- };
6214
- a.prototype = f.prototype;
6215
- } else a = {};
6216
- Object.defineProperty(a, '__esModule', {value: true});
6217
- Object.keys(n).forEach(function (k) {
6218
- var d = Object.getOwnPropertyDescriptor(n, k);
6219
- Object.defineProperty(a, k, d.get ? d : {
6220
- enumerable: true,
6221
- get: function () {
6222
- return n[k];
6223
- }
6224
- });
6225
- });
6226
- return a;
6445
+ // Fallback method using fetch if sendBeacon isn't available
6446
+ async function fallbackEventFire(url) {
6447
+ try {
6448
+ const racePromise = Promise.race([
6449
+ // Promise #1: The fetch request
6450
+ fetch(url, {
6451
+ method: 'POST',
6452
+ keepalive: true,
6453
+ }),
6454
+ // Promise #2: The timeout
6455
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), 2000)),
6456
+ ]);
6457
+ /**
6458
+ * Prevent requests from hanging indefinitely
6459
+ * Improve user experience by failing fast
6460
+ * Handle slow network conditions gracefully
6461
+ * Ensure resources are freed up in a timely manner
6462
+ */
6463
+ const response = await racePromise;
6464
+ return response.ok;
6465
+ }
6466
+ catch (_a) {
6467
+ return false;
6468
+ }
6227
6469
  }
6228
-
6229
- var cryptoJs = {exports: {}};
6230
-
6231
- function commonjsRequire(path) {
6232
- throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
6470
+ /**
6471
+ * Helper function to decode base64 string and parse JSON
6472
+ *
6473
+ * @param {string} base64String - The base64 encoded JSON string
6474
+ * @returns {T | null} - Decoded and parsed object or null if invalid
6475
+ */
6476
+ function decodeBase64Json(base64String) {
6477
+ try {
6478
+ return JSON.parse(atob(base64String));
6479
+ }
6480
+ catch (_a) {
6481
+ return null;
6482
+ }
6483
+ }
6484
+ /**
6485
+ * Extracts and decodes a URL from a base64-encoded query parameter.
6486
+ *
6487
+ * @param {string} url - The URL containing the base64-encoded query parameter.
6488
+ * @returns {string | null} - The decoded URL or null if not found or invalid.
6489
+ * @throws {Error} - If URL is malformed or payload is invalid.
6490
+ */
6491
+ function getRedirectUrlFromPayload(url) {
6492
+ if (!url)
6493
+ return null;
6494
+ try {
6495
+ // Extract initial payload
6496
+ const payload = new URL(url).searchParams.get('p');
6497
+ if (!payload)
6498
+ return null;
6499
+ // Decode first layer
6500
+ const decodedData = decodeBase64Json(payload);
6501
+ if (!(decodedData === null || decodedData === void 0 ? void 0 : decodedData.u))
6502
+ return null;
6503
+ // Extract URL from nested query
6504
+ const eventPayload = new URLSearchParams(decodedData.u).get('e');
6505
+ if (!eventPayload)
6506
+ return null;
6507
+ // Decode second layer
6508
+ const eventData = decodeBase64Json(eventPayload);
6509
+ return (eventData === null || eventData === void 0 ? void 0 : eventData.ur) || null;
6510
+ }
6511
+ catch (_a) {
6512
+ console.warn('RmnSdk: Failed to extract redirect URL from payload.');
6513
+ return null;
6514
+ }
6515
+ }
6516
+ /**
6517
+ * Fires an event using the navigator.sendBeacon method or a fallback method if sendBeacon is not available.
6518
+ * If the event is a click event and a redirect URL is found, it redirects the user to that URL.
6519
+ *
6520
+ * @param {IFireEventParams} params - The parameters for firing the event.
6521
+ * @param {RMN_SPOT_EVENT} params.event - The event type.
6522
+ * @param {string} params.eventUrl - The URL to which the event is sent.
6523
+ * @returns {Promise<void>} - A promise that resolves when the event is fired.
6524
+ */
6525
+ async function fireEvent({ event, eventUrl }) {
6526
+ var _a;
6527
+ try {
6528
+ 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));
6529
+ if (!didFireEvent) {
6530
+ return;
6531
+ }
6532
+ if (event === exports.RMN_SPOT_EVENT.CLICK) {
6533
+ const redirectUrl = getRedirectUrlFromPayload(eventUrl);
6534
+ if (redirectUrl) {
6535
+ window.location.href = redirectUrl;
6536
+ }
6537
+ }
6538
+ }
6539
+ catch (_b) {
6540
+ // Handle errors silently
6541
+ }
6542
+ }
6543
+ function calculateScaleFactor(elementScale) {
6544
+ // Step 1: Apply square root for non-linear scaling
6545
+ // This creates a more gradual scaling effect, especially for larger changes
6546
+ // For example:
6547
+ // - elementScale of 0.25 (1/4 size) becomes 0.5
6548
+ // - elementScale of 1 (unchanged) remains 1
6549
+ // - elementScale of 4 (4x size) becomes 2
6550
+ const baseFactor = Math.sqrt(elementScale);
6551
+ // Step 2: Apply additional dampening to further soften the scaling effect
6552
+ // The dampening factor (0.5) can be adjusted:
6553
+ // - Lower values (closer to 0) make scaling more subtle
6554
+ // - Higher values (closer to 1) make scaling more pronounced
6555
+ const dampening = 0.35;
6556
+ // Calculate the scaleFactor:
6557
+ // 1. (baseFactor - 1) represents the change from the original size
6558
+ // 2. Multiply by dampening to reduce the effect
6559
+ // 3. Add 1 to center the scaling around the original size
6560
+ // For example, if baseFactor is 2:
6561
+ // scaleFactor = 1 + (2 - 1) * 0.5 = 1.5
6562
+ const scaleFactor = 1 + (baseFactor - 1) * dampening;
6563
+ // Step 3: Define the allowed range for the scale factor
6564
+ // This ensures that the font size never changes too drastically
6565
+ const minScale = 0.35; // Font will never be smaller than 50% of original
6566
+ const maxScale = 1.5; // Font will never be larger than 150% of original
6567
+ // Step 4: Clamp the scale factor to the defined range
6568
+ // Math.min ensures the value doesn't exceed maxScale
6569
+ // Math.max ensures the value isn't less than minScale
6570
+ return Math.max(minScale, Math.min(maxScale, scaleFactor));
6571
+ }
6572
+ /**
6573
+ * Converts an object to a query string.
6574
+ *
6575
+ * @param {Record<string, string|number|undefined|null>} obj - The object to be converted to a query string.
6576
+ * @returns {string} - The query string.
6577
+ */
6578
+ function objectToQueryParams(obj) {
6579
+ return Object.entries(obj !== null && obj !== void 0 ? obj : {})
6580
+ .map(([key, value]) => {
6581
+ if (value == null) {
6582
+ return '';
6583
+ }
6584
+ return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
6585
+ })
6586
+ .filter(Boolean)
6587
+ .join('&');
6233
6588
  }
6234
6589
 
6235
- var core = {exports: {}};
6236
-
6237
- var _polyfillNode_crypto = {};
6238
-
6239
- var _polyfillNode_crypto$1 = /*#__PURE__*/Object.freeze({
6240
- __proto__: null,
6241
- default: _polyfillNode_crypto
6242
- });
6243
-
6244
- var require$$0 = /*@__PURE__*/getAugmentedNamespace(_polyfillNode_crypto$1);
6245
-
6246
- var hasRequiredCore;
6247
-
6248
- function requireCore () {
6249
- if (hasRequiredCore) return core.exports;
6250
- hasRequiredCore = 1;
6251
- (function (module, exports) {
6252
- (function (root, factory) {
6253
- {
6254
- // CommonJS
6255
- module.exports = factory();
6256
- }
6257
- }(commonjsGlobal, function () {
6258
-
6259
- /*globals window, global, require*/
6260
-
6261
- /**
6262
- * CryptoJS core components.
6263
- */
6264
- var CryptoJS = CryptoJS || (function (Math, undefined$1) {
6265
-
6266
- var crypto;
6267
-
6268
- // Native crypto from window (Browser)
6269
- if (typeof window !== 'undefined' && window.crypto) {
6270
- crypto = window.crypto;
6271
- }
6272
-
6273
- // Native crypto in web worker (Browser)
6274
- if (typeof self !== 'undefined' && self.crypto) {
6275
- crypto = self.crypto;
6276
- }
6277
-
6278
- // Native crypto from worker
6279
- if (typeof globalThis !== 'undefined' && globalThis.crypto) {
6280
- crypto = globalThis.crypto;
6281
- }
6282
-
6283
- // Native (experimental IE 11) crypto from window (Browser)
6284
- if (!crypto && typeof window !== 'undefined' && window.msCrypto) {
6285
- crypto = window.msCrypto;
6286
- }
6287
-
6288
- // Native crypto from global (NodeJS)
6289
- if (!crypto && typeof commonjsGlobal !== 'undefined' && commonjsGlobal.crypto) {
6290
- crypto = commonjsGlobal.crypto;
6291
- }
6292
-
6293
- // Native crypto import via require (NodeJS)
6294
- if (!crypto && typeof commonjsRequire === 'function') {
6295
- try {
6296
- crypto = require$$0;
6297
- } catch (err) {}
6298
- }
6299
-
6300
- /*
6301
- * Cryptographically secure pseudorandom number generator
6302
- *
6303
- * As Math.random() is cryptographically not safe to use
6304
- */
6305
- var cryptoSecureRandomInt = function () {
6306
- if (crypto) {
6307
- // Use getRandomValues method (Browser)
6308
- if (typeof crypto.getRandomValues === 'function') {
6309
- try {
6310
- return crypto.getRandomValues(new Uint32Array(1))[0];
6311
- } catch (err) {}
6312
- }
6313
-
6314
- // Use randomBytes method (NodeJS)
6315
- if (typeof crypto.randomBytes === 'function') {
6316
- try {
6317
- return crypto.randomBytes(4).readInt32LE();
6318
- } catch (err) {}
6319
- }
6320
- }
6321
-
6590
+ class UniqueIdGenerator {
6591
+ /**
6592
+ * Initialize the generator with a node ID
6593
+ * @param nodeId Number between 0-1023 to identify this instance
6594
+ */
6595
+ static initialize(nodeId = Math.floor(Math.random() * 1024)) {
6596
+ if (nodeId < 0 || nodeId >= 1 << this.nodeBits) {
6597
+ throw new Error(`Node ID must be between 0 and ${(1 << this.nodeBits) - 1}`);
6598
+ }
6599
+ this.nodeId = nodeId;
6600
+ }
6601
+ /**
6602
+ * Convert a number to base32 string with specified length
6603
+ */
6604
+ static toBase32(num, length) {
6605
+ let result = '';
6606
+ while (num > 0) {
6607
+ result = this.base32Chars[Number(num % BigInt(32))] + result;
6608
+ // @ts-expect-error - TS doesn't support bigint division
6609
+ num = num / 32n;
6610
+ }
6611
+ return result.padStart(length, '0');
6612
+ }
6613
+ /**
6614
+ * Generate a cryptographically secure random number
6615
+ */
6616
+ static getSecureRandom() {
6617
+ if (typeof crypto !== 'undefined') {
6618
+ const buffer = new Uint32Array(1);
6619
+ crypto.getRandomValues(buffer);
6620
+ return buffer[0];
6621
+ }
6622
+ return Math.floor(Math.random() * 0xffffffff);
6623
+ }
6624
+ /**
6625
+ * Wait until next millisecond
6626
+ */
6627
+ static waitNextMillis(lastTimestamp) {
6628
+ let timestamp = Date.now();
6629
+ while (timestamp <= lastTimestamp) {
6630
+ timestamp = Date.now();
6631
+ }
6632
+ return timestamp;
6633
+ }
6634
+ /**
6635
+ * Generates a highly unique ID with the following format:
6636
+ * TTTTTTTTTTCCCCNNNNNRRRR
6637
+ * T: Timestamp (10 chars)
6638
+ * C: Counter (4 chars)
6639
+ * N: Node ID (5 chars)
6640
+ * R: Random (4 chars)
6641
+ *
6642
+ * Total length: 23 characters, always uppercase alphanumeric
6643
+ */
6644
+ static generate() {
6645
+ if (this.nodeId === undefined) {
6646
+ this.initialize();
6647
+ }
6648
+ let timestamp = Date.now() - this.epoch;
6649
+ // Handle clock moving backwards or same millisecond
6650
+ if (timestamp < this.lastTimestamp) {
6651
+ throw new Error('Clock moved backwards. Refusing to generate ID.');
6652
+ }
6653
+ if (timestamp === this.lastTimestamp) {
6654
+ this.sequence = (this.sequence + 1) & ((1 << this.sequenceBits) - 1);
6655
+ if (this.sequence === 0) {
6656
+ timestamp = this.waitNextMillis(this.lastTimestamp);
6657
+ }
6658
+ }
6659
+ else {
6660
+ this.sequence = 0;
6661
+ }
6662
+ this.lastTimestamp = timestamp;
6663
+ // Generate random component
6664
+ const random = this.getSecureRandom() & 0xffff; // 16 bits of randomness
6665
+ // Combine all components into a BigInt
6666
+ // const id =
6667
+ // (BigInt(timestamp) << BigInt(this.nodeBits + this.sequenceBits + 16)) |
6668
+ // (BigInt(this.nodeId) << BigInt(this.sequenceBits + 16)) |
6669
+ // (BigInt(this.sequence) << BigInt(16)) |
6670
+ // BigInt(random);
6671
+ // Convert to base32 representation
6672
+ const timeComponent = this.toBase32(BigInt(timestamp), 10);
6673
+ const counterComponent = this.toBase32(BigInt(this.sequence), 4);
6674
+ const nodeComponent = this.toBase32(BigInt(this.nodeId), 5);
6675
+ const randomComponent = this.toBase32(BigInt(random), 4);
6676
+ return `${timeComponent}${counterComponent}${nodeComponent}${randomComponent}`;
6677
+ }
6678
+ /**
6679
+ * Validates if a string matches the expected ID format
6680
+ */
6681
+ static isValid(id) {
6682
+ if (!/^[0-9A-HJ-NP-Z]{23}$/.test(id))
6683
+ return false;
6684
+ try {
6685
+ const timeComponent = id.slice(0, 10);
6686
+ const timestamp = this.decodeBase32(timeComponent);
6687
+ const now = Date.now() - this.epoch;
6688
+ return timestamp >= 0 && timestamp <= now;
6689
+ }
6690
+ catch (_a) {
6691
+ return false;
6692
+ }
6693
+ }
6694
+ /**
6695
+ * Decode base32 string to number
6696
+ */
6697
+ static decodeBase32(str) {
6698
+ let result = 0;
6699
+ for (const char of str) {
6700
+ result = result * 32 + this.base32Chars.indexOf(char);
6701
+ }
6702
+ return result;
6703
+ }
6704
+ /**
6705
+ * Extract timestamp from ID
6706
+ */
6707
+ static getTimestamp(id) {
6708
+ if (!this.isValid(id))
6709
+ throw new Error('Invalid ID format');
6710
+ const timeComponent = id.slice(0, 10);
6711
+ const timestamp = this.decodeBase32(timeComponent);
6712
+ return new Date(timestamp + this.epoch);
6713
+ }
6714
+ }
6715
+ // Constants for bit manipulation
6716
+ UniqueIdGenerator.epoch = 1577836800000; // 2020-01-01 as epoch
6717
+ UniqueIdGenerator.nodeBits = 10;
6718
+ UniqueIdGenerator.sequenceBits = 12;
6719
+ // Instance variables
6720
+ UniqueIdGenerator.lastTimestamp = -1;
6721
+ UniqueIdGenerator.sequence = 0;
6722
+ // Character set for base32 encoding (excluding similar looking characters)
6723
+ UniqueIdGenerator.base32Chars = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
6724
+
6725
+ var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
6726
+
6727
+ function getAugmentedNamespace(n) {
6728
+ if (n.__esModule) return n;
6729
+ var f = n.default;
6730
+ if (typeof f == "function") {
6731
+ var a = function a () {
6732
+ if (this instanceof a) {
6733
+ return Reflect.construct(f, arguments, this.constructor);
6734
+ }
6735
+ return f.apply(this, arguments);
6736
+ };
6737
+ a.prototype = f.prototype;
6738
+ } else a = {};
6739
+ Object.defineProperty(a, '__esModule', {value: true});
6740
+ Object.keys(n).forEach(function (k) {
6741
+ var d = Object.getOwnPropertyDescriptor(n, k);
6742
+ Object.defineProperty(a, k, d.get ? d : {
6743
+ enumerable: true,
6744
+ get: function () {
6745
+ return n[k];
6746
+ }
6747
+ });
6748
+ });
6749
+ return a;
6750
+ }
6751
+
6752
+ var cryptoJs = {exports: {}};
6753
+
6754
+ function commonjsRequire(path) {
6755
+ throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
6756
+ }
6757
+
6758
+ var core = {exports: {}};
6759
+
6760
+ var _polyfillNode_crypto = {};
6761
+
6762
+ var _polyfillNode_crypto$1 = /*#__PURE__*/Object.freeze({
6763
+ __proto__: null,
6764
+ default: _polyfillNode_crypto
6765
+ });
6766
+
6767
+ var require$$0 = /*@__PURE__*/getAugmentedNamespace(_polyfillNode_crypto$1);
6768
+
6769
+ var hasRequiredCore;
6770
+
6771
+ function requireCore () {
6772
+ if (hasRequiredCore) return core.exports;
6773
+ hasRequiredCore = 1;
6774
+ (function (module, exports) {
6775
+ (function (root, factory) {
6776
+ {
6777
+ // CommonJS
6778
+ module.exports = factory();
6779
+ }
6780
+ }(commonjsGlobal, function () {
6781
+
6782
+ /*globals window, global, require*/
6783
+
6784
+ /**
6785
+ * CryptoJS core components.
6786
+ */
6787
+ var CryptoJS = CryptoJS || (function (Math, undefined$1) {
6788
+
6789
+ var crypto;
6790
+
6791
+ // Native crypto from window (Browser)
6792
+ if (typeof window !== 'undefined' && window.crypto) {
6793
+ crypto = window.crypto;
6794
+ }
6795
+
6796
+ // Native crypto in web worker (Browser)
6797
+ if (typeof self !== 'undefined' && self.crypto) {
6798
+ crypto = self.crypto;
6799
+ }
6800
+
6801
+ // Native crypto from worker
6802
+ if (typeof globalThis !== 'undefined' && globalThis.crypto) {
6803
+ crypto = globalThis.crypto;
6804
+ }
6805
+
6806
+ // Native (experimental IE 11) crypto from window (Browser)
6807
+ if (!crypto && typeof window !== 'undefined' && window.msCrypto) {
6808
+ crypto = window.msCrypto;
6809
+ }
6810
+
6811
+ // Native crypto from global (NodeJS)
6812
+ if (!crypto && typeof commonjsGlobal !== 'undefined' && commonjsGlobal.crypto) {
6813
+ crypto = commonjsGlobal.crypto;
6814
+ }
6815
+
6816
+ // Native crypto import via require (NodeJS)
6817
+ if (!crypto && typeof commonjsRequire === 'function') {
6818
+ try {
6819
+ crypto = require$$0;
6820
+ } catch (err) {}
6821
+ }
6822
+
6823
+ /*
6824
+ * Cryptographically secure pseudorandom number generator
6825
+ *
6826
+ * As Math.random() is cryptographically not safe to use
6827
+ */
6828
+ var cryptoSecureRandomInt = function () {
6829
+ if (crypto) {
6830
+ // Use getRandomValues method (Browser)
6831
+ if (typeof crypto.getRandomValues === 'function') {
6832
+ try {
6833
+ return crypto.getRandomValues(new Uint32Array(1))[0];
6834
+ } catch (err) {}
6835
+ }
6836
+
6837
+ // Use randomBytes method (NodeJS)
6838
+ if (typeof crypto.randomBytes === 'function') {
6839
+ try {
6840
+ return crypto.randomBytes(4).readInt32LE();
6841
+ } catch (err) {}
6842
+ }
6843
+ }
6844
+
6322
6845
  throw new Error('Native crypto module could not be used to get secure random number.');
6323
6846
  };
6324
6847
 
@@ -15154,6 +15677,7 @@ class AuthService extends BaseApi {
15154
15677
 
15155
15678
  const SPOT_ELEMENT_TAG = 'spot-element';
15156
15679
  const CAROUSEL_ELEMENT_TAG = 'spot-carousel-element';
15680
+ const SKELETON_ELEMENT_TAG = 'spot-skeleton-element';
15157
15681
  const GFONT_PRECONNECT = `
15158
15682
  <link rel="preconnect" href="https://fonts.googleapis.com">
15159
15683
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -15164,6 +15688,45 @@ const GFONT_SOURCE_SANS_3 = `
15164
15688
  const GFONT_CORMORANT = `
15165
15689
  <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
15690
  `;
15691
+ const SPOT_DIMENSIONS = {
15692
+ [exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO]: { width: 1140, height: 640 },
15693
+ [exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE]: { width: 1140, height: 640 },
15694
+ [exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE]: { width: 1140, height: 640 },
15695
+ [exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE]: { width: 1140, height: 640 },
15696
+ [exports.RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT]: { width: 468, height: 410 },
15697
+ [exports.RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT]: { width: 224, height: 378 },
15698
+ [exports.RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT]: { width: 224, height: 410 },
15699
+ [exports.RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK]: { width: 887, height: 344 },
15700
+ [exports.RMN_SPOT_TYPE.RB_PRODUCT_UPCS]: { width: 1, height: 1 },
15701
+ [exports.RMN_SPOT_TYPE.RB_NAVIGATION_BANNER]: { width: 440, height: 220 },
15702
+ [exports.RMN_SPOT_TYPE.SMALL_RECTANGLE]: { width: 180, height: 150 },
15703
+ [exports.RMN_SPOT_TYPE.MEDIUM_RECTANGLE]: { width: 300, height: 250 },
15704
+ [exports.RMN_SPOT_TYPE.LARGE_RECTANGLE]: { width: 336, height: 280 },
15705
+ [exports.RMN_SPOT_TYPE.VERTICAL_RECTANGLE]: { width: 240, height: 400 },
15706
+ [exports.RMN_SPOT_TYPE.BANNER]: { width: 468, height: 60 },
15707
+ [exports.RMN_SPOT_TYPE.LEADERBOARD]: { width: 728, height: 90 },
15708
+ [exports.RMN_SPOT_TYPE.LARGE_LEADERBOARD]: { width: 970, height: 90 },
15709
+ [exports.RMN_SPOT_TYPE.BILLBOARD]: { width: 970, height: 250 },
15710
+ [exports.RMN_SPOT_TYPE.SKYSCRAPER]: { width: 120, height: 600 },
15711
+ [exports.RMN_SPOT_TYPE.WIDE_SKYSCRAPER]: { width: 160, height: 600 },
15712
+ [exports.RMN_SPOT_TYPE.HALF_PAGE]: { width: 300, height: 600 },
15713
+ [exports.RMN_SPOT_TYPE.SMALL_SQUARE]: { width: 200, height: 200 },
15714
+ [exports.RMN_SPOT_TYPE.SQUARE]: { width: 250, height: 250 },
15715
+ [exports.RMN_SPOT_TYPE.VERTICAL_BANNER]: { width: 120, height: 240 },
15716
+ [exports.RMN_SPOT_TYPE.BUTTON_2]: { width: 120, height: 60 },
15717
+ [exports.RMN_SPOT_TYPE.MICRO_BAR]: { width: 88, height: 31 },
15718
+ [exports.RMN_SPOT_TYPE.POP_UP]: { width: 550, height: 480 },
15719
+ [exports.RMN_SPOT_TYPE.PORTRAIT]: { width: 300, height: 1050 },
15720
+ [exports.RMN_SPOT_TYPE.SMARTPHONE_BANNER_1]: { width: 300, height: 50 },
15721
+ [exports.RMN_SPOT_TYPE.SMARTPHONE_BANNER_2]: { width: 320, height: 50 },
15722
+ [exports.RMN_SPOT_TYPE.MOBILE_PHONE_INTERSTITIAL_1]: { width: 640, height: 1136 },
15723
+ [exports.RMN_SPOT_TYPE.MOBILE_PHONE_INTERSTITIAL_2]: { width: 750, height: 1334 },
15724
+ [exports.RMN_SPOT_TYPE.MOBILE_PHONE_INTERSTITIAL_3]: { width: 1080, height: 1920 },
15725
+ [exports.RMN_SPOT_TYPE.FEATURE_PHONE_SMALL_BANNER]: { width: 120, height: 20 },
15726
+ [exports.RMN_SPOT_TYPE.FEATURE_PHONE_MEDIUM_BANNER]: { width: 168, height: 28 },
15727
+ [exports.RMN_SPOT_TYPE.FEATURE_PHONE_LARGE_BANNER]: { width: 216, height: 36 },
15728
+ [exports.RMN_SPOT_TYPE.IN_TEXT]: { width: 1, height: 1 },
15729
+ };
15167
15730
 
15168
15731
  class IntersectionObserverService {
15169
15732
  constructor(defaultOptions = {}) {
@@ -15205,8 +15768,33 @@ class IntersectionObserverService {
15205
15768
  }
15206
15769
  }
15207
15770
 
15208
- class LocalStorage {
15771
+ var ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX;
15772
+ (function (ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX) {
15773
+ ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX["PLACEMENT_ID"] = 0] = "PLACEMENT_ID";
15774
+ ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX["SPOT_ID"] = 1] = "SPOT_ID";
15775
+ ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX["SPOT_TYPE"] = 2] = "SPOT_TYPE";
15776
+ ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX["EVENTS"] = 3] = "EVENTS";
15777
+ ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX["PRODUCT_IDS"] = 4] = "PRODUCT_IDS";
15778
+ ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX["CREATED_AT"] = 5] = "CREATED_AT";
15779
+ })(ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX || (ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX = {}));
15780
+ const LOCAL_STORAGE_SPOT_EVENTS_ARRAY_INDEX = {
15781
+ [exports.RMN_SPOT_EVENT.IMPRESSION]: 0,
15782
+ [exports.RMN_SPOT_EVENT.CLICK]: 1,
15783
+ [exports.RMN_SPOT_EVENT.PURCHASE]: 2,
15784
+ [exports.RMN_SPOT_EVENT.ADD_TO_CART]: 3,
15785
+ [exports.RMN_SPOT_EVENT.REMOVE_FROM_CART]: 4,
15786
+ [exports.RMN_SPOT_EVENT.ADD_TO_CART_FROM_DETAILS]: 5,
15787
+ [exports.RMN_SPOT_EVENT.ADD_TO_WISHLIST]: 6,
15788
+ [exports.RMN_SPOT_EVENT.EXPAND_PRODUCT]: 7,
15789
+ [exports.RMN_SPOT_EVENT.BUY_NOW]: 8,
15790
+ };
15791
+ class LocalStorageService {
15209
15792
  constructor() {
15793
+ if (typeof window.localStorage === 'undefined') {
15794
+ console.warn('Local storage is not supported in this environment');
15795
+ return;
15796
+ }
15797
+ this.setUserId();
15210
15798
  this.spots = new Map();
15211
15799
  // Sync local storage with the current state
15212
15800
  this.syncLocalStorage();
@@ -15214,19 +15802,23 @@ class LocalStorage {
15214
15802
  this.removeExpiredSpots();
15215
15803
  }
15216
15804
  static getInstance() {
15217
- if (!LocalStorage.instance) {
15218
- LocalStorage.instance = new LocalStorage();
15805
+ if (!LocalStorageService.instance) {
15806
+ LocalStorageService.instance = new LocalStorageService();
15219
15807
  }
15220
- return LocalStorage.instance;
15808
+ return LocalStorageService.instance;
15221
15809
  }
15222
15810
  syncLocalStorage() {
15223
- const localStorageData = localStorage.getItem(LocalStorage.localStorageKey);
15224
- // TODO: Encrypt the data before storing it in the local storage
15811
+ const localStorageData = window.localStorage.getItem(LocalStorageService.localStorageKey);
15225
15812
  if (localStorageData) {
15226
15813
  try {
15227
- const parsedData = JSON.parse(localStorageData);
15814
+ const decryptedData = this.decryptData(localStorageData);
15815
+ const parsedData = JSON.parse(decryptedData);
15228
15816
  if (parsedData && typeof parsedData === 'object') {
15229
- this.spots = this.objToMap(parsedData);
15817
+ const data = {};
15818
+ for (const [key, value] of Object.entries(parsedData)) {
15819
+ data[key] = this.spotArrayToObject(value);
15820
+ }
15821
+ this.spots = this.objectToMap(data);
15230
15822
  }
15231
15823
  else {
15232
15824
  this.clearLocalStorage();
@@ -15239,43 +15831,237 @@ class LocalStorage {
15239
15831
  }
15240
15832
  }
15241
15833
  setSpot(spotId, data) {
15834
+ var _a;
15242
15835
  data.createdAt = Date.now();
15243
- this.spots.set(spotId, data);
15836
+ (_a = this.spots) === null || _a === void 0 ? void 0 : _a.set(spotId, data);
15244
15837
  this.updateLocalStorage();
15245
15838
  }
15246
- getSpot(spotId) {
15247
- return this.spots.get(spotId);
15248
- }
15249
15839
  removeSpot(spotId) {
15250
- this.spots.delete(spotId);
15840
+ var _a;
15841
+ (_a = this.spots) === null || _a === void 0 ? void 0 : _a.delete(spotId);
15251
15842
  this.updateLocalStorage();
15252
15843
  }
15844
+ getSpot(spotId) {
15845
+ var _a;
15846
+ return (_a = this.spots) === null || _a === void 0 ? void 0 : _a.get(spotId);
15847
+ }
15848
+ getSpots() {
15849
+ if (!this.spots)
15850
+ return undefined;
15851
+ return this.mapToObject(this.spots);
15852
+ }
15253
15853
  updateLocalStorage() {
15254
- const data = this.mapToObj(this.spots);
15255
- localStorage.setItem(LocalStorage.localStorageKey, JSON.stringify(data));
15854
+ if (!this.spots)
15855
+ return undefined;
15856
+ const data = this.mapToObject(this.spots);
15857
+ const dataArray = {};
15858
+ for (const [key, value] of Object.entries(data)) {
15859
+ dataArray[key] = this.spotObjectToArray(value);
15860
+ }
15861
+ try {
15862
+ const encryptedData = this.encryptData(JSON.stringify(dataArray));
15863
+ window.localStorage.setItem(LocalStorageService.localStorageKey, encryptedData);
15864
+ }
15865
+ catch (_a) {
15866
+ // If there is an error parsing the data, clear the local storage to prevent any issues
15867
+ this.clearLocalStorage();
15868
+ }
15256
15869
  }
15257
15870
  clearLocalStorage() {
15258
- localStorage.removeItem(LocalStorage.localStorageKey);
15871
+ window.localStorage.removeItem(LocalStorageService.localStorageKey);
15259
15872
  }
15260
15873
  removeExpiredSpots() {
15874
+ var _a;
15261
15875
  const currentTime = Date.now();
15262
- this.spots.forEach((spot, spotId) => {
15263
- var _a;
15264
- if (currentTime - ((_a = spot.createdAt) !== null && _a !== void 0 ? _a : 0) > LocalStorage.spotExpirationTime) {
15265
- this.spots.delete(spotId);
15876
+ (_a = this.spots) === null || _a === void 0 ? void 0 : _a.forEach((spot, spotId) => {
15877
+ var _a, _b;
15878
+ if (currentTime - ((_a = spot.createdAt) !== null && _a !== void 0 ? _a : 0) > LocalStorageService.spotExpirationTime) {
15879
+ (_b = this.spots) === null || _b === void 0 ? void 0 : _b.delete(spotId);
15266
15880
  }
15267
15881
  });
15268
15882
  this.updateLocalStorage();
15269
15883
  }
15270
- mapToObj(map) {
15884
+ // ======================== Utility functions ======================== //
15885
+ getUserId() {
15886
+ const key = LocalStorageService.localStorageKey;
15887
+ if (!key) {
15888
+ this.setUserId();
15889
+ }
15890
+ return key.replace(`${LocalStorageService.localStorageKeyPrefix}_`, '');
15891
+ }
15892
+ /**
15893
+ * Sets the user ID in the local storage.
15894
+ * If no existing key is found,
15895
+ * it generates a new unique ID and sets it as the local storage key.
15896
+ */
15897
+ setUserId() {
15898
+ const prefix = LocalStorageService.localStorageKeyPrefix;
15899
+ const existingKeys = Object.keys(window.localStorage).filter((key) => key.startsWith(prefix));
15900
+ const setNewKey = () => {
15901
+ const uniqueId = UniqueIdGenerator.generate();
15902
+ const newLocalStorageKey = `${prefix}_${uniqueId}`;
15903
+ window.localStorage.setItem(newLocalStorageKey, '');
15904
+ LocalStorageService.localStorageKey = newLocalStorageKey;
15905
+ };
15906
+ if (existingKeys.length === 0) {
15907
+ setNewKey();
15908
+ }
15909
+ else {
15910
+ const validKey = existingKeys.find((key) => UniqueIdGenerator.isValid(key.replace(`${prefix}_`, '')));
15911
+ // Delete all other keys except the valid one
15912
+ existingKeys.forEach((key) => {
15913
+ if (key !== validKey) {
15914
+ window.localStorage.removeItem(key);
15915
+ }
15916
+ });
15917
+ if (validKey) {
15918
+ // Found a valid key, assign it to localStorageKey
15919
+ LocalStorageService.localStorageKey = validKey;
15920
+ }
15921
+ else {
15922
+ // No valid key found, generate a new key
15923
+ setNewKey();
15924
+ }
15925
+ }
15926
+ }
15927
+ mapToObject(map) {
15271
15928
  return Object.fromEntries(map);
15272
15929
  }
15273
- objToMap(obj) {
15930
+ objectToMap(obj) {
15274
15931
  return new Map(Object.entries(obj));
15275
15932
  }
15933
+ spotEventObjectToArray(events) {
15934
+ return Object.keys(LOCAL_STORAGE_SPOT_EVENTS_ARRAY_INDEX).map((type) => {
15935
+ const result = events.find((item) => item.event === type);
15936
+ return result && result.event === type ? result.url : '';
15937
+ });
15938
+ }
15939
+ spotEventArrayToObject(arr) {
15940
+ return Object.keys(LOCAL_STORAGE_SPOT_EVENTS_ARRAY_INDEX).map((type) => ({
15941
+ event: type,
15942
+ url: arr[LOCAL_STORAGE_SPOT_EVENTS_ARRAY_INDEX[type]],
15943
+ }));
15944
+ }
15945
+ spotObjectToArray(obj) {
15946
+ return [
15947
+ obj.placementId,
15948
+ obj.spotId,
15949
+ obj.spotType,
15950
+ this.spotEventObjectToArray(obj.events),
15951
+ obj.productIds,
15952
+ obj.createdAt,
15953
+ ];
15954
+ }
15955
+ spotArrayToObject(arr) {
15956
+ return {
15957
+ placementId: arr[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX.PLACEMENT_ID],
15958
+ spotId: arr[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX.SPOT_ID],
15959
+ spotType: arr[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX.SPOT_TYPE],
15960
+ events: this.spotEventArrayToObject(arr[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX.EVENTS]),
15961
+ productIds: arr[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX.PRODUCT_IDS],
15962
+ createdAt: arr[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX.CREATED_AT],
15963
+ };
15964
+ }
15965
+ encryptData(data) {
15966
+ if (!LocalStorageService.encryptData)
15967
+ return data;
15968
+ // For now, we are using base64 encoding to encrypt the data
15969
+ // Later we will use Jose encryption
15970
+ return btoa(data);
15971
+ }
15972
+ decryptData(data) {
15973
+ if (!LocalStorageService.encryptData)
15974
+ return data;
15975
+ // For now, we are using base64 encoding to encrypt
15976
+ // Later we will use Jose encryption
15977
+ return atob(data);
15978
+ }
15979
+ }
15980
+ LocalStorageService.localStorageKeyPrefix = 'lc_rmn';
15981
+ LocalStorageService.localStorageKey = '';
15982
+ LocalStorageService.spotExpirationTime = 1000 * 60 * 60 * 24 * 7; // 7 days
15983
+ LocalStorageService.encryptData = true;
15984
+
15985
+ /**
15986
+ * PubsubService class
15987
+ * Manages event subscriptions and publications
15988
+ * @template IRmnEventMap A record type defining the structure of events and their data
15989
+ */
15990
+ class PubsubService {
15991
+ constructor() {
15992
+ /**
15993
+ * Object to store subscribers for each event type
15994
+ */
15995
+ this.subscribers = {};
15996
+ }
15997
+ static getInstance() {
15998
+ if (!PubsubService.instance) {
15999
+ PubsubService.instance = new PubsubService();
16000
+ }
16001
+ return PubsubService.instance;
16002
+ }
16003
+ /**
16004
+ * Subscribe to an event
16005
+ * @param eventType - The type of event to subscribe to
16006
+ * @param callback - The function to be called when the event is published
16007
+ * @returns A function to unsubscribe from the event
16008
+ *
16009
+ * @Example:
16010
+ * const unsubscribe = pubSub.subscribe('userLogin', (data) => {
16011
+ * console.log(`User ${data.username} logged in`);
16012
+ * });
16013
+ */
16014
+ subscribe(eventType, callback) {
16015
+ if (!this.subscribers[eventType]) {
16016
+ this.subscribers[eventType] = [];
16017
+ }
16018
+ this.subscribers[eventType].push(callback);
16019
+ // Return an unsubscribe function
16020
+ return () => {
16021
+ this.subscribers[eventType] = this.subscribers[eventType].filter((cb) => cb !== callback);
16022
+ };
16023
+ }
16024
+ /**
16025
+ * Publish an event
16026
+ * @param eventType - The type of event to publish
16027
+ * @param data - The data to be passed to the event subscribers
16028
+ *
16029
+ * @Example:
16030
+ * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
16031
+ */
16032
+ publish(eventType, data) {
16033
+ if (!this.subscribers[eventType]) {
16034
+ return;
16035
+ }
16036
+ this.subscribers[eventType].forEach((callback) => callback(data));
16037
+ }
15276
16038
  }
15277
- LocalStorage.localStorageKey = 'lc_rmn';
15278
- LocalStorage.spotExpirationTime = 1000 * 60 * 60 * 24 * 7; // 7 days
16039
+ /**
16040
+ * Usage Example:
16041
+ *
16042
+ * interface IRmnEventMap {
16043
+ * userLogin: { username: string; timestamp: number };
16044
+ * pageView: { url: string; timestamp: number };
16045
+ * }
16046
+ *
16047
+ * const pubSub = new PubsubService<IRmnEventMap>();
16048
+ *
16049
+ * // Subscribe to events
16050
+ * const unsubscribeLogin = pubSub.subscribe('userLogin', (data) => {
16051
+ * console.log(`User ${data.username} logged in at ${new Date(data.timestamp)}`);
16052
+ * });
16053
+ *
16054
+ * pubSub.subscribe('pageView', (data) => {
16055
+ * console.log(`Page ${data.url} viewed at ${new Date(data.timestamp)}`);
16056
+ * });
16057
+ *
16058
+ * // Publish events
16059
+ * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
16060
+ * pubSub.publish('pageView', { url: '/home', timestamp: Date.now() });
16061
+ *
16062
+ * // Unsubscribe from an event
16063
+ * unsubscribeLogin();
16064
+ */
15279
16065
 
15280
16066
  class ResizeObserverService {
15281
16067
  constructor({ element, maxSize, minScale }) {
@@ -15345,36 +16131,6 @@ class ResizeObserverService {
15345
16131
  }
15346
16132
  }
15347
16133
 
15348
- function calculateScaleFactor(elementScale) {
15349
- // Step 1: Apply square root for non-linear scaling
15350
- // This creates a more gradual scaling effect, especially for larger changes
15351
- // For example:
15352
- // - elementScale of 0.25 (1/4 size) becomes 0.5
15353
- // - elementScale of 1 (unchanged) remains 1
15354
- // - elementScale of 4 (4x size) becomes 2
15355
- const baseFactor = Math.sqrt(elementScale);
15356
- // Step 2: Apply additional dampening to further soften the scaling effect
15357
- // The dampening factor (0.5) can be adjusted:
15358
- // - Lower values (closer to 0) make scaling more subtle
15359
- // - Higher values (closer to 1) make scaling more pronounced
15360
- const dampening = 0.35;
15361
- // Calculate the scaleFactor:
15362
- // 1. (baseFactor - 1) represents the change from the original size
15363
- // 2. Multiply by dampening to reduce the effect
15364
- // 3. Add 1 to center the scaling around the original size
15365
- // For example, if baseFactor is 2:
15366
- // scaleFactor = 1 + (2 - 1) * 0.5 = 1.5
15367
- const scaleFactor = 1 + (baseFactor - 1) * dampening;
15368
- // Step 3: Define the allowed range for the scale factor
15369
- // This ensures that the font size never changes too drastically
15370
- const minScale = 0.35; // Font will never be smaller than 50% of original
15371
- const maxScale = 1.5; // Font will never be larger than 150% of original
15372
- // Step 4: Clamp the scale factor to the defined range
15373
- // Math.min ensures the value doesn't exceed maxScale
15374
- // Math.max ensures the value isn't less than minScale
15375
- return Math.max(minScale, Math.min(maxScale, scaleFactor));
15376
- }
15377
-
15378
16134
  const CAROUSEL_COMPONENT_STYLE = ({ width, height, fluid }) => `
15379
16135
  :host {
15380
16136
  position: relative;
@@ -15385,15 +16141,23 @@ const CAROUSEL_COMPONENT_STYLE = ({ width, height, fluid }) => `
15385
16141
  height: ${fluid ? '100%' : `${height}px`};
15386
16142
  }
15387
16143
 
16144
+ .container {
16145
+ position: relative;
16146
+ width: 100%;
16147
+ height: 100%;
16148
+ }
16149
+
15388
16150
  .slides {
15389
16151
  position: relative;
15390
16152
  height: 100%;
15391
16153
  width: 100%;
16154
+ display: flex;
16155
+ transition: transform 0.5s ease-in-out;
15392
16156
  }
15393
16157
 
15394
16158
  .slide {
15395
- display: none;
15396
-
16159
+ flex: 0 0 100%;
16160
+ display: flex;
15397
16161
  justify-content: center;
15398
16162
  align-items: center;
15399
16163
  height: 100%;
@@ -15578,57 +16342,74 @@ const CAROUSEL_COMPONENT_STYLE = ({ width, height, fluid }) => `
15578
16342
  let CarouselElement;
15579
16343
  if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined') {
15580
16344
  class CustomCarouselElement extends HTMLElement {
16345
+ /**
16346
+ * Initializes the web component and attaches shadow DOM
16347
+ * Binds all event handlers to maintain proper 'this' context
16348
+ */
15581
16349
  constructor() {
15582
16350
  super();
15583
- this.currentSlide = 0;
15584
- this.dotElements = [];
15585
- this.prevButton = null;
15586
- this.nextButton = null;
15587
- this.autoplayInterval = null;
15588
- this.useDots = false;
15589
- this.useButtons = false;
16351
+ this.state = {
16352
+ currentSlide: 0,
16353
+ autoplayInterval: null,
16354
+ isAutoplayPaused: false,
16355
+ useDots: false,
16356
+ useButtons: false,
16357
+ autoplay: true,
16358
+ interval: CustomCarouselElement.defaultConfigs.interval,
16359
+ dots: { ...CustomCarouselElement.defaultConfigs.dots },
16360
+ buttons: { ...CustomCarouselElement.defaultConfigs.buttons },
16361
+ isDragging: false,
16362
+ startX: 0,
16363
+ currentX: 0,
16364
+ dragStartTime: 0,
16365
+ dragDistance: 0,
16366
+ containerWidth: 0,
16367
+ isTransitioning: false,
16368
+ realIndex: 0, // Track the actual slide index
16369
+ virtualIndex: 1, // Track the virtual slide position (start at 1 to show clone)
16370
+ isVirtualizing: false, // Flag for handling virtual slide transitions
16371
+ };
16372
+ this.elements = {
16373
+ slidesContainer: null,
16374
+ dots: [],
16375
+ prevButton: null,
16376
+ nextButton: null,
16377
+ };
15590
16378
  this.originalFontSizes = new Map();
16379
+ this.cloneToOriginalMap = new WeakMap();
15591
16380
  this.attachShadow({ mode: 'open' });
16381
+ this.handleTransitionEnd = this.handleTransitionEnd.bind(this);
16382
+ this.handleTouchStart = this.handleTouchStart.bind(this);
16383
+ this.handleTouchMove = this.handleTouchMove.bind(this);
16384
+ this.handleTouchEnd = this.handleTouchEnd.bind(this);
16385
+ this.handleDragStart = this.handleDragStart.bind(this);
16386
+ this.handleDrag = this.handleDrag.bind(this);
16387
+ this.handleDragEnd = this.handleDragEnd.bind(this);
16388
+ this.updateContainerWidth = this.updateContainerWidth.bind(this);
15592
16389
  }
16390
+ /**
16391
+ * Web component lifecycle method called when element is added to DOM
16392
+ * Initializes state, renders UI, sets up events and starts autoplay
16393
+ */
15593
16394
  connectedCallback() {
15594
- this.initializeOptions();
15595
- this.setupResizeObserver();
16395
+ this.initializeState();
15596
16396
  this.render();
15597
16397
  this.setupCarousel();
16398
+ this.setupTouchEvents();
16399
+ this.startAutoplayIfEnabled();
16400
+ this.updateContainerWidth();
16401
+ window.addEventListener('resize', this.updateContainerWidth.bind(this));
16402
+ this.setupResizeObserver();
15598
16403
  }
16404
+ /**
16405
+ * Web component lifecycle method called when element is removed from DOM
16406
+ * Cleans up all event listeners and intervals
16407
+ */
15599
16408
  disconnectedCallback() {
15600
16409
  var _a;
15601
- this.stopAutoplay();
16410
+ this.cleanupEventListeners();
15602
16411
  (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
15603
16412
  }
15604
- initializeOptions() {
15605
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
15606
- this.useDots = ((_a = this.data) === null || _a === void 0 ? void 0 : _a.useDots) === true || typeof ((_b = this.data) === null || _b === void 0 ? void 0 : _b.useDots) === 'object';
15607
- this.useButtons = ((_c = this.data) === null || _c === void 0 ? void 0 : _c.useButtons) === true || typeof ((_d = this.data) === null || _d === void 0 ? void 0 : _d.useButtons) === 'object';
15608
- this.autoplay = (_f = (_e = this.data) === null || _e === void 0 ? void 0 : _e.autoplay) !== null && _f !== void 0 ? _f : true;
15609
- this.interval = (_h = (_g = this.data) === null || _g === void 0 ? void 0 : _g.interval) !== null && _h !== void 0 ? _h : CustomCarouselElement.defaultInterval;
15610
- this.dotsOptions = {
15611
- position: 'bottom-center',
15612
- color: '#d9d9d9',
15613
- activeColor: '#b5914a',
15614
- size: 'base',
15615
- opacity: 1,
15616
- ...(typeof ((_j = this.data) === null || _j === void 0 ? void 0 : _j.useDots) === 'object' ? this.data.useDots : {}),
15617
- };
15618
- this.buttonsOptions = {
15619
- together: false,
15620
- position: 'middle-sides',
15621
- textColor: '#000000',
15622
- backgroundColor: '#ffffff',
15623
- borderRadius: '50%',
15624
- prev: 'Prev',
15625
- next: 'Next',
15626
- size: 'base',
15627
- opacity: 1,
15628
- ...(typeof ((_k = this.data) === null || _k === void 0 ? void 0 : _k.useButtons) === 'object' ? this.data.useButtons : {}),
15629
- };
15630
- this.validateOptions();
15631
- }
15632
16413
  setupResizeObserver() {
15633
16414
  if (this.data && !this.data.fluid) {
15634
16415
  this.resizeObserver = new ResizeObserverService({
@@ -15643,18 +16424,20 @@ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined
15643
16424
  }
15644
16425
  }
15645
16426
  handleCarouselSizeChanged(event) {
15646
- const isRBSpot = 'rbHeroadaksda'.startsWith('rb');
15647
- if (!isRBSpot) {
15648
- // Adjust text elements font size based on the scale factor
15649
- this.adjustFontSize(event.detail.scale);
15650
- }
16427
+ // Adjust text elements font size based on the scale factor
16428
+ this.adjustFontSize(event.detail.scale);
15651
16429
  }
15652
16430
  adjustFontSize(elementScale) {
15653
16431
  var _a;
15654
- const scaleFactor = calculateScaleFactor(elementScale);
15655
16432
  // Find all text elements within the shadow root
15656
- const elements = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('h1, h2, h3, h4, p, span');
15657
- elements === null || elements === void 0 ? void 0 : elements.forEach((element) => {
16433
+ const selectors = ['h1', 'h2', 'h3', 'h4', 'p', 'span']
16434
+ .map((tag) => `[data-spot="iab"] ${tag}`)
16435
+ .join(', ');
16436
+ const elements = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll(selectors);
16437
+ if (!elements)
16438
+ return;
16439
+ const scaleFactor = calculateScaleFactor(elementScale);
16440
+ elements.forEach((element) => {
15658
16441
  if (element instanceof HTMLElement) {
15659
16442
  if (!this.originalFontSizes.has(element)) {
15660
16443
  const originalSize = parseFloat(window.getComputedStyle(element).fontSize);
@@ -15666,180 +16449,610 @@ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined
15666
16449
  }
15667
16450
  });
15668
16451
  }
16452
+ /** State and Configuration Management */
16453
+ /**
16454
+ * Initializes all state values from provided configuration
16455
+ * Sets up dots, buttons, autoplay and other feature flags
16456
+ */
16457
+ initializeState() {
16458
+ var _a, _b, _c, _d, _e, _f, _g, _h;
16459
+ this.state.useDots = Boolean((_a = this.data) === null || _a === void 0 ? void 0 : _a.useDots);
16460
+ this.state.useButtons = Boolean((_b = this.data) === null || _b === void 0 ? void 0 : _b.useButtons);
16461
+ this.state.autoplay = (_d = (_c = this.data) === null || _c === void 0 ? void 0 : _c.autoplay) !== null && _d !== void 0 ? _d : true;
16462
+ this.state.interval = (_f = (_e = this.data) === null || _e === void 0 ? void 0 : _e.interval) !== null && _f !== void 0 ? _f : CustomCarouselElement.defaultConfigs.interval;
16463
+ if (typeof ((_g = this.data) === null || _g === void 0 ? void 0 : _g.useDots) === 'object') {
16464
+ this.state.dots = { ...this.state.dots, ...this.data.useDots };
16465
+ }
16466
+ if (typeof ((_h = this.data) === null || _h === void 0 ? void 0 : _h.useButtons) === 'object') {
16467
+ this.state.buttons = { ...this.state.buttons, ...this.data.useButtons };
16468
+ }
16469
+ this.validateConfiguration();
16470
+ }
16471
+ /**
16472
+ * Validates all configuration options
16473
+ * Ensures positions and other settings are valid
16474
+ */
16475
+ validateConfiguration() {
16476
+ this.validatePosition(this.state.dots.position, 'dotsPosition', 'bottom-center');
16477
+ this.validateButtonsPosition();
16478
+ }
16479
+ /**
16480
+ * Validates navigation element positions
16481
+ * Ensures dots and buttons are placed in valid locations
16482
+ */
16483
+ validatePosition(position, optionName, defaultValue) {
16484
+ if (!CustomCarouselElement.validPositions.has(position)) {
16485
+ console.warn(`Invalid ${optionName}: ${position}. Defaulting to '${defaultValue}'.`);
16486
+ if (optionName === 'dotsPosition') {
16487
+ this.state.dots.position = defaultValue;
16488
+ }
16489
+ else if (optionName === 'buttonsPosition') {
16490
+ this.state.buttons.position = defaultValue;
16491
+ }
16492
+ }
16493
+ }
16494
+ /** Rendering and DOM Management */
16495
+ /**
16496
+ * Main rendering function that builds the carousel structure
16497
+ * Creates slides, dots, and navigation buttons
16498
+ */
15669
16499
  render() {
15670
- var _a;
15671
16500
  if (!this.shadowRoot)
15672
16501
  return;
15673
16502
  const style = document.createElement('style');
15674
16503
  style.textContent = CAROUSEL_COMPONENT_STYLE(this.data);
15675
16504
  this.shadowRoot.appendChild(style);
15676
- const slides = this.renderSlides();
15677
- this.shadowRoot.appendChild(slides);
15678
- this.slidesContainer = (_a = this.shadowRoot.querySelector('.slides')) !== null && _a !== void 0 ? _a : undefined;
15679
- if (this.useDots) {
15680
- const dots = this.renderDots();
15681
- if (dots)
15682
- this.shadowRoot.appendChild(dots);
15683
- }
15684
- if (this.useButtons) {
15685
- const buttons = this.renderButtons();
15686
- if (buttons)
15687
- this.shadowRoot.appendChild(buttons);
16505
+ const carouselContainer = document.createElement('div');
16506
+ carouselContainer.className = 'container';
16507
+ const slides = this.createSlides();
16508
+ carouselContainer.appendChild(slides);
16509
+ if (this.state.useDots) {
16510
+ const dots = this.createDots();
16511
+ carouselContainer.appendChild(dots);
15688
16512
  }
15689
- }
15690
- setupCarousel() {
15691
- this.setupDots();
15692
- this.setupButtons();
15693
- if (this.autoplay) {
15694
- this.startAutoplay();
16513
+ if (this.state.useButtons) {
16514
+ const buttons = this.createButtons();
16515
+ carouselContainer.appendChild(buttons);
15695
16516
  }
15696
- this.updateCarousel();
16517
+ this.shadowRoot.appendChild(carouselContainer);
16518
+ this.cacheElements();
15697
16519
  }
15698
- renderSlides() {
16520
+ /**
16521
+ * Creates the slides container with infinite scroll support
16522
+ * Adds cloned slides at start and end for seamless scrolling
16523
+ */
16524
+ createSlides() {
15699
16525
  const slidesContainer = document.createElement('div');
15700
16526
  slidesContainer.className = 'slides';
15701
- this.slides.forEach((slide, index) => {
15702
- const slideElement = document.createElement('div');
15703
- slideElement.className = `slide ${index === this.currentSlide ? 'active' : ''}`;
15704
- if (slide instanceof HTMLElement) {
15705
- slideElement.appendChild(slide);
15706
- }
15707
- slidesContainer.appendChild(slideElement);
16527
+ // Add the last slide at the beginning for seamless backward transition
16528
+ this.appendSlide(slidesContainer, this.slides.length - 1);
16529
+ // Add all slides
16530
+ this.slides.forEach((_, index) => {
16531
+ this.appendSlide(slidesContainer, index);
15708
16532
  });
16533
+ // Add the first slide at the end for seamless forward transition
16534
+ this.appendSlide(slidesContainer, 0);
16535
+ // Set initial position to show first real slide
16536
+ slidesContainer.style.transform = 'translateX(-100%)';
15709
16537
  return slidesContainer;
15710
16538
  }
15711
- renderDots() {
16539
+ /**
16540
+ * Appends a single slide to the container
16541
+ * Handles cloning of slide elements
16542
+ */
16543
+ appendSlide(container, index) {
16544
+ const slideElement = document.createElement('div');
16545
+ slideElement.className = 'slide';
16546
+ if (this.slides[index] instanceof HTMLElement) {
16547
+ // Clone the slide
16548
+ const clonedSlide = this.slides[index].cloneNode(true);
16549
+ // Map clone to original for event handling
16550
+ this.cloneToOriginalMap.set(clonedSlide, this.slides[index]);
16551
+ // Add event delegation to the slide container
16552
+ slideElement.addEventListener('click', (e) => {
16553
+ const original = this.slides[index];
16554
+ // Dispatch a new event on the original element
16555
+ const cloneEvent = new Event(e.type, { bubbles: true, cancelable: true });
16556
+ original.dispatchEvent(cloneEvent);
16557
+ });
16558
+ slideElement.appendChild(clonedSlide);
16559
+ }
16560
+ container.appendChild(slideElement);
16561
+ }
16562
+ /**
16563
+ * Creates navigation dots based on number of slides
16564
+ * Sets up initial active state and styling
16565
+ */
16566
+ createDots() {
16567
+ const { position, size, opacity, color } = this.state.dots;
15712
16568
  const dotsContainer = document.createElement('div');
15713
- dotsContainer.className = `dots ${this.dotsOptions.position} ${this.dotsOptions.size}`;
15714
- dotsContainer.style.cssText = `--opacity: ${this.dotsOptions.opacity}`;
16569
+ dotsContainer.className = `dots ${position} ${size}`;
16570
+ dotsContainer.style.setProperty('--opacity', opacity.toString());
15715
16571
  this.slides.forEach((_, index) => {
15716
16572
  const dot = document.createElement('span');
15717
- dot.className = `dot ${index === this.currentSlide ? 'active' : ''}`;
15718
- dot.style.backgroundColor = this.dotsOptions.color;
16573
+ dot.className = `dot ${index === this.state.currentSlide ? 'active' : ''}`;
16574
+ dot.style.backgroundColor = color;
15719
16575
  dotsContainer.appendChild(dot);
15720
16576
  });
15721
16577
  return dotsContainer;
15722
16578
  }
15723
- renderButtons() {
16579
+ /**
16580
+ * Creates navigation buttons (prev/next)
16581
+ * Applies styling and position configuration
16582
+ */
16583
+ createButtons() {
16584
+ // eslint-disable-next-line @typescript-eslint/naming-convention
16585
+ const { together, position, size, opacity } = this.state.buttons;
15724
16586
  const buttonsContainer = document.createElement('div');
15725
- const buttonsClass = this.buttonsOptions.together
15726
- ? `buttons-together ${this.buttonsOptions.position}`
15727
- : 'buttons-separate';
15728
- buttonsContainer.className = `buttons ${buttonsClass} ${this.buttonsOptions.size}`;
15729
- buttonsContainer.style.cssText = `--opacity: ${this.buttonsOptions.opacity}`;
15730
- this.prevButton = this.createButton('prev-button', this.buttonsOptions.prev);
15731
- this.nextButton = this.createButton('next-button', this.buttonsOptions.next);
15732
- buttonsContainer.appendChild(this.prevButton);
15733
- buttonsContainer.appendChild(this.nextButton);
16587
+ const buttonsClass = together ? `buttons-together ${position}` : 'buttons-separate';
16588
+ buttonsContainer.className = `buttons ${buttonsClass} ${size}`;
16589
+ buttonsContainer.style.setProperty('--opacity', opacity.toString());
16590
+ this.elements.prevButton = this.createButton('prev-button', this.state.buttons.prev);
16591
+ this.elements.nextButton = this.createButton('next-button', this.state.buttons.next);
16592
+ buttonsContainer.appendChild(this.elements.prevButton);
16593
+ buttonsContainer.appendChild(this.elements.nextButton);
15734
16594
  return buttonsContainer;
15735
16595
  }
16596
+ /**
16597
+ * Creates a single button element with specified properties
16598
+ * Used for both prev and next buttons
16599
+ */
15736
16600
  createButton(className, text) {
16601
+ const { textColor, backgroundColor, borderRadius } = this.state.buttons;
15737
16602
  const button = document.createElement('button');
15738
16603
  button.className = className;
15739
16604
  button.textContent = text;
15740
- button.style.color = this.buttonsOptions.textColor;
15741
- button.style.backgroundColor = this.buttonsOptions.backgroundColor;
15742
- button.style.borderRadius = this.buttonsOptions.borderRadius;
16605
+ button.style.color = textColor;
16606
+ button.style.backgroundColor = backgroundColor;
16607
+ button.style.borderRadius = borderRadius;
15743
16608
  return button;
15744
16609
  }
15745
- setupDots() {
15746
- if (!this.shadowRoot || !this.useDots)
16610
+ /**
16611
+ * Caches references to important DOM elements
16612
+ * Stores references to slides container, dots, and buttons
16613
+ */
16614
+ cacheElements() {
16615
+ if (!this.shadowRoot)
15747
16616
  return;
15748
- this.dotElements = Array.from(this.shadowRoot.querySelectorAll('.dot'));
15749
- this.dotElements.forEach((dot, index) => {
15750
- dot.addEventListener('click', () => {
15751
- this.goToSlide(index);
15752
- this.resetAutoplay();
15753
- });
15754
- });
16617
+ this.elements.slidesContainer = this.shadowRoot.querySelector('.slides');
16618
+ this.elements.dots = Array.from(this.shadowRoot.querySelectorAll('.dot'));
16619
+ this.elements.prevButton = this.shadowRoot.querySelector('.prev-button');
16620
+ this.elements.nextButton = this.shadowRoot.querySelector('.next-button');
15755
16621
  }
15756
- setupButtons() {
15757
- var _a, _b;
15758
- if (!this.useButtons)
16622
+ /** Event Management */
16623
+ /**
16624
+ * Sets up all carousel event listeners
16625
+ * Includes dots, buttons, and transition events
16626
+ */
16627
+ setupCarousel() {
16628
+ this.setupEventListeners();
16629
+ // Add transition event listener with bound method
16630
+ if (this.elements.slidesContainer) {
16631
+ this.elements.slidesContainer.addEventListener('transitionend', this.handleTransitionEnd);
16632
+ }
16633
+ this.updateCarousel(false);
16634
+ }
16635
+ /**
16636
+ * Sets up touch and drag event listeners
16637
+ * Handles both mobile touch and desktop mouse events
16638
+ */
16639
+ setupTouchEvents() {
16640
+ if (!this.elements.slidesContainer)
15759
16641
  return;
15760
- (_a = this.prevButton) === null || _a === void 0 ? void 0 : _a.addEventListener('click', () => {
15761
- this.prevSlide();
15762
- this.resetAutoplay();
16642
+ // Touch events
16643
+ this.elements.slidesContainer.addEventListener('touchstart', this.handleTouchStart, {
16644
+ passive: true,
15763
16645
  });
15764
- (_b = this.nextButton) === null || _b === void 0 ? void 0 : _b.addEventListener('click', () => {
15765
- this.nextSlide();
15766
- this.resetAutoplay();
16646
+ this.elements.slidesContainer.addEventListener('touchmove', this.handleTouchMove, {
16647
+ passive: false,
15767
16648
  });
16649
+ this.elements.slidesContainer.addEventListener('touchend', this.handleTouchEnd);
16650
+ // Mouse drag events
16651
+ this.elements.slidesContainer.addEventListener('mousedown', this.handleDragStart);
16652
+ document.addEventListener('mousemove', this.handleDrag);
16653
+ document.addEventListener('mouseup', this.handleDragEnd);
15768
16654
  }
15769
- nextSlide() {
15770
- this.goToSlide((this.currentSlide + 1) % this.slides.length);
15771
- }
15772
- prevSlide() {
15773
- this.goToSlide((this.currentSlide - 1 + this.slides.length) % this.slides.length);
16655
+ /**
16656
+ * Removes all event listeners
16657
+ * Called during cleanup and before re-adding listeners
16658
+ */
16659
+ removeEventListeners() {
16660
+ this.elements.dots.forEach((dot) => {
16661
+ dot.replaceWith(dot.cloneNode(true));
16662
+ });
16663
+ if (this.elements.prevButton) {
16664
+ this.elements.prevButton.replaceWith(this.elements.prevButton.cloneNode(true));
16665
+ }
16666
+ if (this.elements.nextButton) {
16667
+ this.elements.nextButton.replaceWith(this.elements.nextButton.cloneNode(true));
16668
+ }
15774
16669
  }
15775
- goToSlide(index) {
15776
- this.currentSlide = index;
15777
- this.updateCarousel();
16670
+ /**
16671
+ * Removes touch and drag event listeners
16672
+ * Separate from other event listeners for better management
16673
+ */
16674
+ removeTouchEvents() {
16675
+ if (!this.elements.slidesContainer)
16676
+ return;
16677
+ this.elements.slidesContainer.removeEventListener('touchstart', this.handleTouchStart);
16678
+ this.elements.slidesContainer.removeEventListener('touchmove', this.handleTouchMove);
16679
+ this.elements.slidesContainer.removeEventListener('touchend', this.handleTouchEnd);
16680
+ this.elements.slidesContainer.removeEventListener('mousedown', this.handleDragStart);
16681
+ document.removeEventListener('mousemove', this.handleDrag);
16682
+ document.removeEventListener('mouseup', this.handleDragEnd);
16683
+ }
16684
+ /** Touch and Drag Event Handlers */
16685
+ /**
16686
+ * Handles start of touch interaction
16687
+ * Initializes drag tracking and pauses autoplay
16688
+ */
16689
+ handleTouchStart(e) {
16690
+ if (this.state.isTransitioning)
16691
+ return;
16692
+ this.pauseAutoplay();
16693
+ this.state.startX = e.touches[0].clientX;
16694
+ this.state.dragStartTime = Date.now();
16695
+ this.state.isDragging = true;
16696
+ this.state.dragDistance = 0;
15778
16697
  }
15779
- updateCarousel() {
15780
- if (!this.slidesContainer)
16698
+ /**
16699
+ * Handles touch movement
16700
+ * Calculates drag distance and updates carousel position
16701
+ */
16702
+ handleTouchMove(e) {
16703
+ if (!this.state.isDragging || this.state.isTransitioning)
15781
16704
  return;
15782
- const slides = Array.from(this.slidesContainer.children);
15783
- slides.forEach((slide, index) => {
15784
- slide.classList.toggle('active', index === this.currentSlide);
15785
- });
15786
- this.updateDots();
16705
+ e.preventDefault();
16706
+ const currentX = e.touches[0].clientX;
16707
+ this.state.dragDistance = this.state.startX - currentX;
16708
+ this.updateDragPosition();
15787
16709
  }
15788
- updateDots() {
15789
- if (!this.useDots)
16710
+ /**
16711
+ * Handles end of touch interaction
16712
+ * Determines if slide should change based on drag distance
16713
+ */
16714
+ handleTouchEnd() {
16715
+ if (!this.state.isDragging)
15790
16716
  return;
15791
- this.dotElements.forEach((dot, index) => {
15792
- const isActive = index === this.currentSlide;
15793
- dot.classList.toggle('active', isActive);
15794
- dot.style.backgroundColor = isActive
15795
- ? this.dotsOptions.activeColor
15796
- : this.dotsOptions.color;
15797
- });
16717
+ const dragDuration = Date.now() - this.state.dragStartTime;
16718
+ const velocity = Math.abs(this.state.dragDistance) / dragDuration;
16719
+ const isQuickSwipe = velocity > 0.5;
16720
+ this.handleDragComplete(isQuickSwipe);
16721
+ setTimeout(() => this.resumeAutoplay(), 100);
15798
16722
  }
15799
- startAutoplay() {
15800
- this.autoplayInterval = window.setInterval(() => this.nextSlide(), this.interval);
16723
+ /**
16724
+ * Handles start of mouse drag
16725
+ * Similar to touch start but for desktop interaction
16726
+ */
16727
+ handleDragStart(e) {
16728
+ if (this.state.isTransitioning)
16729
+ return;
16730
+ this.pauseAutoplay();
16731
+ this.state.startX = e.clientX;
16732
+ this.state.dragStartTime = Date.now();
16733
+ this.state.isDragging = true;
16734
+ this.state.dragDistance = 0;
16735
+ e.preventDefault();
15801
16736
  }
15802
- stopAutoplay() {
15803
- if (this.autoplayInterval !== null) {
15804
- window.clearInterval(this.autoplayInterval);
15805
- this.autoplayInterval = null;
15806
- }
16737
+ /**
16738
+ * Handles mouse movement during drag
16739
+ * Updates carousel position during drag
16740
+ */
16741
+ handleDrag(e) {
16742
+ if (!this.state.isDragging || this.state.isTransitioning)
16743
+ return;
16744
+ const currentX = e.clientX;
16745
+ this.state.dragDistance = this.state.startX - currentX;
16746
+ this.updateDragPosition();
15807
16747
  }
16748
+ /**
16749
+ * Handles end of mouse drag
16750
+ * Determines final slide position based on drag
16751
+ */
16752
+ handleDragEnd() {
16753
+ if (!this.state.isDragging)
16754
+ return;
16755
+ const dragDuration = Date.now() - this.state.dragStartTime;
16756
+ const velocity = Math.abs(this.state.dragDistance) / dragDuration;
16757
+ const isQuickSwipe = velocity > 0.5;
16758
+ this.handleDragComplete(isQuickSwipe);
16759
+ setTimeout(() => this.resumeAutoplay(), 100);
16760
+ }
16761
+ /** Navigation and Position Management */
16762
+ /**
16763
+ * Handles navigation button clicks and programmatic navigation
16764
+ * Updates both real and virtual indices
16765
+ */
16766
+ handleNavigation(direction) {
16767
+ if (this.state.isTransitioning)
16768
+ return;
16769
+ const totalSlides = this.slides.length;
16770
+ if (direction === 'next') {
16771
+ this.state.realIndex = (this.state.realIndex + 1) % totalSlides;
16772
+ this.state.virtualIndex++;
16773
+ }
16774
+ else {
16775
+ this.state.realIndex = (this.state.realIndex - 1 + totalSlides) % totalSlides;
16776
+ this.state.virtualIndex--;
16777
+ }
16778
+ this.updateCarousel(true);
16779
+ }
16780
+ /**
16781
+ * Handles dot navigation clicks
16782
+ * Calculates shortest path to target slide
16783
+ */
16784
+ handleDotClick(index) {
16785
+ const currentRealIndex = this.state.realIndex;
16786
+ const totalSlides = this.slides.length;
16787
+ // Calculate the shortest path to the target slide
16788
+ let diff = index - currentRealIndex;
16789
+ // Adjust for shortest path when crossing the boundary
16790
+ if (Math.abs(diff) > totalSlides / 2) {
16791
+ diff = diff > 0 ? diff - totalSlides : diff + totalSlides;
16792
+ }
16793
+ // Update both real and virtual indices
16794
+ this.state.realIndex = index;
16795
+ this.state.virtualIndex += diff;
16796
+ this.updateCarousel(true);
16797
+ this.resetAutoplay();
16798
+ }
16799
+ /**
16800
+ * Completes a drag interaction
16801
+ * Determines whether to change slides based on drag distance
16802
+ */
16803
+ handleDragComplete(isQuickSwipe) {
16804
+ this.state.isDragging = false;
16805
+ if (!this.elements.slidesContainer)
16806
+ return;
16807
+ const dragPercentage = this.state.dragDistance / this.state.containerWidth;
16808
+ const threshold = isQuickSwipe ? 0.1 : CustomCarouselElement.defaultConfigs.dragThreshold;
16809
+ if (Math.abs(dragPercentage) > threshold) {
16810
+ if (this.state.dragDistance > 0) {
16811
+ this.handleNavigation('next');
16812
+ }
16813
+ else {
16814
+ this.handleNavigation('prev');
16815
+ }
16816
+ }
16817
+ else {
16818
+ this.updateCarousel(true);
16819
+ }
16820
+ this.state.dragDistance = 0;
16821
+ }
16822
+ /**
16823
+ * Updates carousel position during drag
16824
+ * Applies transform without transition
16825
+ */
16826
+ updateDragPosition() {
16827
+ if (!this.elements.slidesContainer || this.state.isTransitioning)
16828
+ return;
16829
+ const translateX = -this.state.virtualIndex * 100 -
16830
+ (this.state.dragDistance / this.state.containerWidth) * 100;
16831
+ this.elements.slidesContainer.style.transform = `translateX(${translateX}%)`;
16832
+ this.elements.slidesContainer.style.transition = 'none';
16833
+ }
16834
+ /**
16835
+ * Navigates to a specific slide
16836
+ * Used primarily by dot navigation
16837
+ */
16838
+ goToSlide(index) {
16839
+ if (this.state.isTransitioning)
16840
+ return;
16841
+ this.state.currentSlide = index;
16842
+ this.updateCarousel(true);
16843
+ }
16844
+ /** Transition and Animation Management */
16845
+ /**
16846
+ * Updates carousel position and state
16847
+ * Handles both animated and immediate updates
16848
+ */
16849
+ updateCarousel(animated = true) {
16850
+ if (!this.elements.slidesContainer)
16851
+ return;
16852
+ const totalSlides = this.slides.length;
16853
+ // Calculate the translation for the current virtual position
16854
+ const translateX = -this.state.virtualIndex * 100;
16855
+ if (animated) {
16856
+ this.state.isTransitioning = true;
16857
+ this.elements.slidesContainer.style.transition = 'transform 0.3s ease-out';
16858
+ }
16859
+ else {
16860
+ this.elements.slidesContainer.style.transition = 'none';
16861
+ }
16862
+ this.elements.slidesContainer.style.transform = `translateX(${translateX}%)`;
16863
+ this.updateDots();
16864
+ // Check if we need to reset virtual position
16865
+ if (this.state.virtualIndex <= 0 || this.state.virtualIndex >= totalSlides + 1) {
16866
+ this.state.isVirtualizing = true;
16867
+ }
16868
+ }
16869
+ /**
16870
+ * Handles the end of slide transitions
16871
+ * Manages infinite scroll position reset
16872
+ */
16873
+ handleTransitionEnd(event) {
16874
+ if (event.target !== this.elements.slidesContainer || event.propertyName !== 'transform')
16875
+ return;
16876
+ this.state.isTransitioning = false;
16877
+ if (this.state.isVirtualizing) {
16878
+ this.resetVirtualPosition();
16879
+ }
16880
+ if (this.elements.slidesContainer) {
16881
+ this.elements.slidesContainer.style.transition = 'none';
16882
+ }
16883
+ }
16884
+ /**
16885
+ * Resets virtual position for infinite scroll
16886
+ * Called after hitting first or last clone
16887
+ */
16888
+ resetVirtualPosition() {
16889
+ var _a;
16890
+ const totalSlides = this.slides.length;
16891
+ if (this.state.virtualIndex <= 0) {
16892
+ // If we've moved before the first clone, jump to the last real slide
16893
+ this.state.virtualIndex = totalSlides;
16894
+ }
16895
+ else if (this.state.virtualIndex >= totalSlides + 1) {
16896
+ // If we've moved after the last clone, jump to the first real slide
16897
+ this.state.virtualIndex = 1;
16898
+ }
16899
+ if (this.elements.slidesContainer) {
16900
+ this.elements.slidesContainer.style.transition = 'none';
16901
+ this.elements.slidesContainer.style.transform = `translateX(${-this.state.virtualIndex * 100}%)`;
16902
+ }
16903
+ this.state.isVirtualizing = false;
16904
+ // Force reflow to ensure the style change takes effect immediately
16905
+ void ((_a = this.elements.slidesContainer) === null || _a === void 0 ? void 0 : _a.offsetHeight);
16906
+ }
16907
+ /**
16908
+ * Updates dot active states
16909
+ * Reflects current slide position in navigation
16910
+ */
16911
+ updateDots() {
16912
+ if (!this.state.useDots)
16913
+ return;
16914
+ this.elements.dots.forEach((dot, index) => {
16915
+ const isActive = index === this.state.realIndex;
16916
+ dot.classList.toggle('active', isActive);
16917
+ dot.style.backgroundColor = isActive ? this.state.dots.activeColor : this.state.dots.color;
16918
+ });
16919
+ }
16920
+ /** Autoplay Management */
16921
+ /**
16922
+ * Starts autoplay if enabled
16923
+ * Called during initialization
16924
+ */
16925
+ startAutoplayIfEnabled() {
16926
+ if (this.state.autoplay && !this.state.isAutoplayPaused) {
16927
+ this.startAutoplay();
16928
+ }
16929
+ }
16930
+ /**
16931
+ * Starts the autoplay interval
16932
+ * Ensures only one interval is running
16933
+ */
16934
+ startAutoplay() {
16935
+ this.stopAutoplay(); // Ensure any existing interval is cleared first
16936
+ if (!this.state.isAutoplayPaused && this.state.autoplay) {
16937
+ this.state.autoplayInterval = window.setInterval(() => this.handleNavigation('next'), this.state.interval);
16938
+ }
16939
+ }
16940
+ /**
16941
+ * Pauses autoplay
16942
+ * Used during user interaction
16943
+ */
16944
+ pauseAutoplay() {
16945
+ this.state.isAutoplayPaused = true;
16946
+ this.stopAutoplay();
16947
+ }
16948
+ /**
16949
+ * Resumes autoplay after pause
16950
+ * Used after user interaction ends
16951
+ */
16952
+ resumeAutoplay() {
16953
+ this.state.isAutoplayPaused = false;
16954
+ if (this.state.autoplay) {
16955
+ this.startAutoplay();
16956
+ }
16957
+ }
16958
+ /**
16959
+ * Stops autoplay completely
16960
+ * Cleans up interval
16961
+ */
16962
+ stopAutoplay() {
16963
+ if (this.state.autoplayInterval !== null) {
16964
+ window.clearInterval(this.state.autoplayInterval);
16965
+ this.state.autoplayInterval = null;
16966
+ }
16967
+ }
16968
+ /**
16969
+ * Resets autoplay interval
16970
+ * Used after user interactions
16971
+ */
15808
16972
  resetAutoplay() {
15809
- if (this.autoplay) {
16973
+ if (!this.state.isDragging && this.state.autoplay) {
15810
16974
  this.stopAutoplay();
15811
16975
  this.startAutoplay();
15812
16976
  }
15813
16977
  }
15814
- validateOptions() {
15815
- this.validatePosition(this.dotsOptions.position, 'dotsPosition', 'bottom-center');
15816
- this.validateButtonsPosition();
16978
+ /** Utility Methods */
16979
+ /**
16980
+ * Updates container width on resize
16981
+ * Used for drag distance calculations
16982
+ */
16983
+ updateContainerWidth() {
16984
+ if (this.elements.slidesContainer) {
16985
+ this.state.containerWidth = this.elements.slidesContainer.offsetWidth;
16986
+ }
15817
16987
  }
15818
- validatePosition(position, optionName, defaultValue) {
15819
- if (!CustomCarouselElement.validPositions.includes(position)) {
15820
- console.warn(`Invalid ${optionName}: ${position}. Defaulting to '${defaultValue}'.`);
15821
- if (optionName === 'dotsPosition') {
15822
- this.dotsOptions.position = defaultValue;
15823
- }
15824
- else if (optionName === 'buttonsPosition') {
15825
- this.buttonsOptions.position = defaultValue;
15826
- }
16988
+ /**
16989
+ * Cleans up all event listeners
16990
+ * Called during component cleanup
16991
+ */
16992
+ cleanupEventListeners() {
16993
+ this.stopAutoplay();
16994
+ this.removeEventListeners();
16995
+ this.removeTouchEvents();
16996
+ // Remove transition listener
16997
+ if (this.elements.slidesContainer) {
16998
+ this.elements.slidesContainer.removeEventListener('transitionend', this.handleTransitionEnd);
16999
+ }
17000
+ window.removeEventListener('resize', this.updateContainerWidth);
17001
+ }
17002
+ /**
17003
+ * Sets up all event listeners
17004
+ * Includes dots, buttons, and transition events
17005
+ */
17006
+ setupEventListeners() {
17007
+ var _a, _b;
17008
+ if (this.state.useDots) {
17009
+ this.elements.dots.forEach((dot, index) => {
17010
+ dot.addEventListener('click', () => this.handleDotClick(index));
17011
+ });
17012
+ }
17013
+ if (this.state.useButtons) {
17014
+ (_a = this.elements.prevButton) === null || _a === void 0 ? void 0 : _a.addEventListener('click', () => this.handleNavigation('prev'));
17015
+ (_b = this.elements.nextButton) === null || _b === void 0 ? void 0 : _b.addEventListener('click', () => this.handleNavigation('next'));
15827
17016
  }
15828
17017
  }
17018
+ /**
17019
+ * Validates button position configuration
17020
+ * Ensures only valid positions are used
17021
+ */
15829
17022
  validateButtonsPosition() {
15830
- if (this.useButtons) {
15831
- if (this.buttonsOptions.together) {
15832
- this.validatePosition(this.buttonsOptions.position, 'buttonsPosition', 'bottom-center');
15833
- }
15834
- else if (this.buttonsOptions.position !== 'middle-sides') {
15835
- console.warn(`Invalid buttonsPosition: ${this.buttonsOptions.position}. When buttons are not together, only 'middle-sides' is allowed. Defaulting to 'middle-sides'.`);
15836
- this.buttonsOptions.position = 'middle-sides';
15837
- }
17023
+ if (this.state.useButtons &&
17024
+ !this.state.buttons.together &&
17025
+ this.state.buttons.position !== 'middle-sides') {
17026
+ console.warn('When buttons are not together, only "middle-sides" is allowed. Defaulting to "middle-sides".');
17027
+ this.state.buttons.position = 'middle-sides';
15838
17028
  }
15839
17029
  }
15840
17030
  }
15841
- CustomCarouselElement.defaultInterval = 5000;
15842
- CustomCarouselElement.validPositions = [
17031
+ /** Core initialization and lifecycle methods */
17032
+ CustomCarouselElement.defaultConfigs = {
17033
+ interval: 5000,
17034
+ touchThreshold: 50, // minimum swipe distance in pixels
17035
+ dragThreshold: 0.2, // minimum drag percentage of carousel width
17036
+ dots: {
17037
+ position: 'bottom-center',
17038
+ color: '#d9d9d9',
17039
+ activeColor: '#b5914a',
17040
+ size: 'base',
17041
+ opacity: 1,
17042
+ },
17043
+ buttons: {
17044
+ together: false,
17045
+ position: 'middle-sides',
17046
+ textColor: '#000000',
17047
+ backgroundColor: '#ffffff',
17048
+ borderRadius: '50%',
17049
+ prev: 'Prev',
17050
+ next: 'Next',
17051
+ size: 'base',
17052
+ opacity: 1,
17053
+ },
17054
+ };
17055
+ CustomCarouselElement.validPositions = new Set([
15843
17056
  'top-left',
15844
17057
  'top-center',
15845
17058
  'top-right',
@@ -15849,10 +17062,122 @@ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined
15849
17062
  'middle-left',
15850
17063
  'middle-right',
15851
17064
  'middle-sides',
15852
- ];
17065
+ ]);
15853
17066
  CarouselElement = CustomCarouselElement;
15854
17067
  }
15855
17068
 
17069
+ function SkeletonTemplate({ fluid, width, height }) {
17070
+ return `
17071
+ <style>
17072
+ :host {
17073
+ display: block;
17074
+ position: relative;
17075
+ box-sizing: border-box;
17076
+ overflow: hidden;
17077
+ width: ${fluid ? '100%' : `${width}px`};
17078
+ height: ${fluid ? '100%' : `${height}px`};
17079
+ background: #ffffff;
17080
+ padding: 20px;
17081
+ border-radius: 5px;
17082
+ }
17083
+
17084
+ .content {
17085
+ height: 100%;
17086
+ display: flex;
17087
+ flex-direction: column;
17088
+ gap: 20px;
17089
+ }
17090
+
17091
+ .image-placeholder {
17092
+ width: 100%;
17093
+ height: 100%;
17094
+ background: #f0f0f0;
17095
+ border-radius: 4px;
17096
+ position: relative;
17097
+ overflow: hidden;
17098
+ }
17099
+
17100
+ .lines-container {
17101
+ display: flex;
17102
+ flex-direction: column;
17103
+ justify-content: flex-end;
17104
+ }
17105
+
17106
+ .line {
17107
+ height: 20px;
17108
+ background: #f0f0f0;
17109
+ border-radius: 4px;
17110
+ margin-bottom: 15px;
17111
+ position: relative;
17112
+ overflow: hidden;
17113
+ }
17114
+
17115
+ .image-placeholder::after,
17116
+ .line::after {
17117
+ content: "";
17118
+ position: absolute;
17119
+ top: 0;
17120
+ left: 0;
17121
+ width: 100%;
17122
+ height: 100%;
17123
+ background: linear-gradient(
17124
+ 90deg,
17125
+ rgba(255, 255, 255, 0) 0%,
17126
+ rgba(255, 255, 255, 0.5) 50%,
17127
+ rgba(255, 255, 255, 0) 100%
17128
+ );
17129
+ animation: shimmer 1.5s infinite;
17130
+ }
17131
+
17132
+ .line.header {
17133
+ width: 25%;
17134
+ }
17135
+
17136
+ .line.description {
17137
+ width: 65%;
17138
+ }
17139
+
17140
+ .line.button {
17141
+ width: 40%;
17142
+ }
17143
+
17144
+ @keyframes shimmer {
17145
+ 0% {
17146
+ transform: translateX(-100%);
17147
+ }
17148
+ 100% {
17149
+ transform: translateX(100%);
17150
+ }
17151
+ }
17152
+ </style>
17153
+
17154
+ <div class="content">
17155
+ <div class="image-placeholder"></div>
17156
+ <div class="lines-container">
17157
+ <div class="line header"></div>
17158
+ <div class="line description"></div>
17159
+ <div class="line button"></div>
17160
+ </div>
17161
+ </div>
17162
+ `;
17163
+ }
17164
+
17165
+ let SkeletonElement;
17166
+ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined') {
17167
+ class CustomSkeletonElement extends HTMLElement {
17168
+ constructor() {
17169
+ super();
17170
+ this.attachShadow({ mode: 'open' });
17171
+ }
17172
+ connectedCallback() {
17173
+ if (!this.shadowRoot || !this.data)
17174
+ return;
17175
+ this.shadowRoot.innerHTML = SkeletonTemplate(this.data);
17176
+ }
17177
+ }
17178
+ SkeletonElement = CustomSkeletonElement;
17179
+ }
17180
+
15856
17181
  let SpotElement;
15857
17182
  if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined') {
15858
17183
  class CustomSpotElement extends HTMLElement {
@@ -15892,19 +17217,20 @@ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined
15892
17217
  * #########################################################
15893
17218
  */
15894
17219
  handleSpotSizeChanged(event) {
15895
- var _a, _b, _c;
15896
- const isRBSpot = (_c = (_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a.spot) === null || _b === void 0 ? void 0 : _b.startsWith('rb')) !== null && _c !== void 0 ? _c : false;
15897
- if (!isRBSpot) {
15898
- // Adjust text elements font size based on the scale factor
15899
- this.adjustFontSize(event.detail.scale);
15900
- }
17220
+ // Adjust text elements font size based on the scale factor
17221
+ this.adjustFontSize(event.detail.scale);
15901
17222
  }
15902
17223
  adjustFontSize(elementScale) {
15903
17224
  var _a;
15904
- const scaleFactor = calculateScaleFactor(elementScale);
15905
17225
  // Find all text elements within the shadow root
15906
- const elements = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll('h1, h2, h3, h4, p, span');
15907
- elements === null || elements === void 0 ? void 0 : elements.forEach((element) => {
17226
+ const selectors = ['h1', 'h2', 'h3', 'h4', 'p', 'span']
17227
+ .map((tag) => `[data-spot="iab"] ${tag}`)
17228
+ .join(', ');
17229
+ const elements = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelectorAll(selectors);
17230
+ if (!elements)
17231
+ return;
17232
+ const scaleFactor = calculateScaleFactor(elementScale);
17233
+ elements.forEach((element) => {
15908
17234
  if (element instanceof HTMLElement) {
15909
17235
  if (!this.originalFontSizes.has(element)) {
15910
17236
  const originalSize = parseFloat(window.getComputedStyle(element).fontSize);
@@ -15960,15 +17286,14 @@ class ElementService {
15960
17286
  * @return {HTMLElement | null} - The html element or null if the browser environment is not available.
15961
17287
  */
15962
17288
  createSpotElement({ content, config }) {
15963
- var _a, _b;
17289
+ var _a;
15964
17290
  if (!this.ensureBrowserEnvironmentAndDefineElement()) {
15965
17291
  return null;
15966
17292
  }
15967
17293
  const spot = document.createElement(SPOT_ELEMENT_TAG);
15968
- spot.setAttribute('type', (_a = config === null || config === void 0 ? void 0 : config.spot) !== null && _a !== void 0 ? _a : '');
15969
17294
  spot.data = {
15970
17295
  spot: config === null || config === void 0 ? void 0 : config.spot,
15971
- fluid: (_b = config === null || config === void 0 ? void 0 : config.fluid) !== null && _b !== void 0 ? _b : false,
17296
+ fluid: (_a = config === null || config === void 0 ? void 0 : config.fluid) !== null && _a !== void 0 ? _a : false,
15972
17297
  ...config,
15973
17298
  };
15974
17299
  spot.content = content;
@@ -15996,6 +17321,27 @@ class ElementService {
15996
17321
  carousel.slides = slides;
15997
17322
  return carousel;
15998
17323
  }
17324
+ /**
17325
+ * Creates the skeleton html element based on the provided data using shadow dom.
17326
+ *
17327
+ * This method is only available in browser environments.
17328
+ *
17329
+ * @param {ICreateSkeletonElementParams} params - The parameters to create the final element.
17330
+ *
17331
+ * @return {HTMLElement | null} - The html element or null if the browser environment is not available.
17332
+ */
17333
+ createSkeletonElement(params) {
17334
+ if (!this.ensureBrowserEnvironmentAndDefineElement()) {
17335
+ return null;
17336
+ }
17337
+ const skeleton = document.createElement(SKELETON_ELEMENT_TAG);
17338
+ const dimensions = SPOT_DIMENSIONS[params.spotType];
17339
+ skeleton.data = {
17340
+ fluid: params.fluid,
17341
+ ...dimensions,
17342
+ };
17343
+ return skeleton;
17344
+ }
15999
17345
  /**
16000
17346
  * Overrides the spot colors with the provided colors.
16001
17347
  *
@@ -16030,158 +17376,26 @@ class ElementService {
16030
17376
  if (!window.customElements.get(CAROUSEL_ELEMENT_TAG)) {
16031
17377
  window.customElements.define(CAROUSEL_ELEMENT_TAG, CarouselElement);
16032
17378
  }
17379
+ if (!window.customElements.get(SKELETON_ELEMENT_TAG)) {
17380
+ window.customElements.define(SKELETON_ELEMENT_TAG, SkeletonElement);
17381
+ }
16033
17382
  return true;
16034
17383
  }
16035
17384
  }
16036
17385
 
16037
- class UniqueIdGenerator {
16038
- /**
16039
- * Initialize the generator with a node ID
16040
- * @param nodeId Number between 0-1023 to identify this instance
16041
- */
16042
- static initialize(nodeId = Math.floor(Math.random() * 1024)) {
16043
- if (nodeId < 0 || nodeId >= 1 << this.nodeBits) {
16044
- throw new Error(`Node ID must be between 0 and ${(1 << this.nodeBits) - 1}`);
16045
- }
16046
- this.nodeId = nodeId;
16047
- }
16048
- /**
16049
- * Convert a number to base32 string with specified length
16050
- */
16051
- static toBase32(num, length) {
16052
- let result = '';
16053
- while (num > 0) {
16054
- result = this.base32Chars[Number(num % BigInt(32))] + result;
16055
- // @ts-expect-error - TS doesn't support bigint division
16056
- num = num / 32n;
16057
- }
16058
- return result.padStart(length, '0');
16059
- }
16060
- /**
16061
- * Generate a cryptographically secure random number
16062
- */
16063
- static getSecureRandom() {
16064
- if (typeof crypto !== 'undefined') {
16065
- const buffer = new Uint32Array(1);
16066
- crypto.getRandomValues(buffer);
16067
- return buffer[0];
16068
- }
16069
- return Math.floor(Math.random() * 0xffffffff);
16070
- }
16071
- /**
16072
- * Wait until next millisecond
16073
- */
16074
- static waitNextMillis(lastTimestamp) {
16075
- let timestamp = Date.now();
16076
- while (timestamp <= lastTimestamp) {
16077
- timestamp = Date.now();
16078
- }
16079
- return timestamp;
16080
- }
16081
- /**
16082
- * Generates a highly unique ID with the following format:
16083
- * TTTTTTTTTTCCCCNNNNNRRRR
16084
- * T: Timestamp (10 chars)
16085
- * C: Counter (4 chars)
16086
- * N: Node ID (5 chars)
16087
- * R: Random (4 chars)
16088
- *
16089
- * Total length: 23 characters, always uppercase alphanumeric
16090
- */
16091
- static generate() {
16092
- if (this.nodeId === undefined) {
16093
- this.initialize();
16094
- }
16095
- let timestamp = Date.now() - this.epoch;
16096
- // Handle clock moving backwards or same millisecond
16097
- if (timestamp < this.lastTimestamp) {
16098
- throw new Error('Clock moved backwards. Refusing to generate ID.');
16099
- }
16100
- if (timestamp === this.lastTimestamp) {
16101
- this.sequence = (this.sequence + 1) & ((1 << this.sequenceBits) - 1);
16102
- if (this.sequence === 0) {
16103
- timestamp = this.waitNextMillis(this.lastTimestamp);
16104
- }
16105
- }
16106
- else {
16107
- this.sequence = 0;
16108
- }
16109
- this.lastTimestamp = timestamp;
16110
- // Generate random component
16111
- const random = this.getSecureRandom() & 0xffff; // 16 bits of randomness
16112
- // Combine all components into a BigInt
16113
- // const id =
16114
- // (BigInt(timestamp) << BigInt(this.nodeBits + this.sequenceBits + 16)) |
16115
- // (BigInt(this.nodeId) << BigInt(this.sequenceBits + 16)) |
16116
- // (BigInt(this.sequence) << BigInt(16)) |
16117
- // BigInt(random);
16118
- // Convert to base32 representation
16119
- const timeComponent = this.toBase32(BigInt(timestamp), 10);
16120
- const counterComponent = this.toBase32(BigInt(this.sequence), 4);
16121
- const nodeComponent = this.toBase32(BigInt(this.nodeId), 5);
16122
- const randomComponent = this.toBase32(BigInt(random), 4);
16123
- return `${timeComponent}${counterComponent}${nodeComponent}${randomComponent}`;
16124
- }
16125
- /**
16126
- * Validates if a string matches the expected ID format
16127
- */
16128
- static isValid(id) {
16129
- if (!/^[0-9A-HJ-NP-Z]{23}$/.test(id))
16130
- return false;
16131
- try {
16132
- const timeComponent = id.slice(0, 10);
16133
- const timestamp = this.decodeBase32(timeComponent);
16134
- const now = Date.now() - this.epoch;
16135
- return timestamp >= 0 && timestamp <= now;
16136
- }
16137
- catch (_a) {
16138
- return false;
16139
- }
16140
- }
16141
- /**
16142
- * Decode base32 string to number
16143
- */
16144
- static decodeBase32(str) {
16145
- let result = 0;
16146
- for (const char of str) {
16147
- result = result * 32 + this.base32Chars.indexOf(char);
16148
- }
16149
- return result;
16150
- }
16151
- /**
16152
- * Extract timestamp from ID
16153
- */
16154
- static getTimestamp(id) {
16155
- if (!this.isValid(id))
16156
- throw new Error('Invalid ID format');
16157
- const timeComponent = id.slice(0, 10);
16158
- const timestamp = this.decodeBase32(timeComponent);
16159
- return new Date(timestamp + this.epoch);
16160
- }
16161
- }
16162
- // Constants for bit manipulation
16163
- UniqueIdGenerator.epoch = 1577836800000; // 2020-01-01 as epoch
16164
- UniqueIdGenerator.nodeBits = 10;
16165
- UniqueIdGenerator.sequenceBits = 12;
16166
- // Instance variables
16167
- UniqueIdGenerator.lastTimestamp = -1;
16168
- UniqueIdGenerator.sequence = 0;
16169
- // Character set for base32 encoding (excluding similar looking characters)
16170
- UniqueIdGenerator.base32Chars = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
16171
-
16172
- function convertHexToRgba(hex, opacity = 1) {
16173
- // Remove # if present
16174
- const cleanHex = hex.replace('#', '');
16175
- // Convert hex to RGB
16176
- const r = parseInt(cleanHex.substring(0, 2), 16);
16177
- const g = parseInt(cleanHex.substring(2, 4), 16);
16178
- const b = parseInt(cleanHex.substring(4, 6), 16);
16179
- // Return rgba string
16180
- return `rgba(${r}, ${g}, ${b}, ${opacity})`;
16181
- }
16182
- function generateGradientColor(overlay, fallback = '') {
16183
- if (!overlay) {
16184
- return fallback;
17386
+ function convertHexToRgba(hex, opacity = 1) {
17387
+ // Remove # if present
17388
+ const cleanHex = hex.replace('#', '');
17389
+ // Convert hex to RGB
17390
+ const r = parseInt(cleanHex.substring(0, 2), 16);
17391
+ const g = parseInt(cleanHex.substring(2, 4), 16);
17392
+ const b = parseInt(cleanHex.substring(4, 6), 16);
17393
+ // Return rgba string
17394
+ return `rgba(${r}, ${g}, ${b}, ${opacity})`;
17395
+ }
17396
+ function generateGradientColor(overlay, fallback = '') {
17397
+ if (!overlay) {
17398
+ return fallback;
16185
17399
  }
16186
17400
  const OVERLAY_SIZE = {
16187
17401
  small: 10,
@@ -16201,17 +17415,19 @@ function generateGradientColor(overlay, fallback = '') {
16201
17415
  const gradientColor = convertHexToRgba(color, overlayOpacity);
16202
17416
  return `${fullColor} 0%, ${gradientColor} ${goTo}%, ${transparentColor} 100%`;
16203
17417
  }
16204
- function spotHtmlStringToElement(htmlString) {
17418
+ function spotHtmlStringToElement(htmlString, spotType) {
16205
17419
  const spot = document.createElement('div');
17420
+ spot.setAttribute('data-spot', spotType.startsWith('rb') ? 'rb' : 'iab');
16206
17421
  spot.className = 'spot';
16207
17422
  spot.innerHTML = htmlString;
16208
17423
  Object.assign(spot.style, {
16209
- position: 'relative',
16210
17424
  display: 'block',
16211
17425
  width: '100%',
16212
17426
  height: '100%',
16213
17427
  margin: '0',
16214
17428
  padding: '0',
17429
+ containerType: 'inline-size',
17430
+ position: 'relative',
16215
17431
  });
16216
17432
  return spot;
16217
17433
  }
@@ -17081,7 +18297,6 @@ const STYLES$6 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
17081
18297
  box-sizing: border-box;
17082
18298
  color: ${textColor};
17083
18299
  cursor: pointer;
17084
- container-type: inline-size;
17085
18300
  }
17086
18301
 
17087
18302
  .${prefix}__text {
@@ -17097,7 +18312,7 @@ const STYLES$6 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
17097
18312
  .${prefix}__header {
17098
18313
  font-size: 24px;
17099
18314
  margin: 0;
17100
- font-family: "Cormorant";
18315
+ font-family: "Cormorant", system-ui;
17101
18316
  font-style: normal;
17102
18317
  font-weight: 300;
17103
18318
  line-height: normal;
@@ -17134,6 +18349,10 @@ const STYLES$6 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
17134
18349
  .${prefix} {
17135
18350
  background-image: linear-gradient(to top, ${linearGradient}), url("${primaryImage}");
17136
18351
  }
18352
+
18353
+ .${prefix}__text {
18354
+ width: 70%;
18355
+ }
17137
18356
  }
17138
18357
 
17139
18358
  @container (min-width: 768px) {
@@ -17216,7 +18435,6 @@ const STYLES$5 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
17216
18435
  height: 100%;
17217
18436
  display: block;
17218
18437
  position: relative;
17219
- container-type: inline-size;
17220
18438
  }
17221
18439
 
17222
18440
  .${prefix}__content {
@@ -17407,15 +18625,20 @@ function rbHomepageHeroThreeTileTemplate(spot, config) {
17407
18625
  const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextColor = textColor, primaryImage, mobilePrimaryImage = primaryImage, }, { prefix }) => `
17408
18626
  <style>
17409
18627
  .${prefix} {
18628
+ width: 100%;
18629
+ height: 100%;
18630
+ background-color: transparent;
18631
+ cursor: pointer;
18632
+ position: relative;
18633
+ }
18634
+
18635
+ .${prefix}__content {
17410
18636
  width: 100%;
17411
18637
  height: 100%;
17412
18638
  display: flex;
17413
18639
  flex-direction: column-reverse;
17414
18640
  background-color: transparent;
17415
18641
  gap: 6px;
17416
- cursor: pointer;
17417
- container-type: inline-size;
17418
- position: relative;
17419
18642
  }
17420
18643
 
17421
18644
  .${prefix}__image {
@@ -17446,7 +18669,7 @@ const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
17446
18669
  font-size: 18px;
17447
18670
  margin: 0;
17448
18671
  color: inherit;
17449
- font-family: "Cormorant";
18672
+ font-family: "Cormorant", system-ui;
17450
18673
  font-style: normal;
17451
18674
  font-weight: 700;
17452
18675
  line-height: normal;
@@ -17488,7 +18711,7 @@ const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
17488
18711
  }
17489
18712
 
17490
18713
  @container (min-width: 768px) {
17491
- .${prefix} {
18714
+ .${prefix}__content {
17492
18715
  flex-direction: row;
17493
18716
  }
17494
18717
  .${prefix}__image {
@@ -17543,12 +18766,14 @@ function rbHomepageHeroTwoTileTemplate(spot, config) {
17543
18766
  ${GFONT_CORMORANT}
17544
18767
  ${STYLES$4(spot, config)}
17545
18768
  <div class="${prefix}">
17546
- <div class="${prefix}__text">
17547
- ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17548
- ${spot.description ? `<p class="${prefix}__description">${spot.description}</p>` : ''}
17549
- ${spot.ctaText ? `<span class="${prefix}__cta-button">${spot.ctaText}</span>` : ''}
17550
- </div>
17551
- <div class="${prefix}__image"></div>
18769
+ <div class="${prefix}__content">
18770
+ <div class="${prefix}__text">
18771
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
18772
+ ${spot.description ? `<p class="${prefix}__description">${spot.description}</p>` : ''}
18773
+ ${spot.ctaText ? `<span class="${prefix}__cta-button">${spot.ctaText}</span>` : ''}
18774
+ </div>
18775
+ <div class="${prefix}__image"></div>
18776
+ </div>
17552
18777
  </div>
17553
18778
  `;
17554
18779
  }
@@ -17571,12 +18796,10 @@ const STYLES$3 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
17571
18796
  overflow: hidden;
17572
18797
  cursor: pointer;
17573
18798
  color: ${textColor};
17574
- container-type: inline-size;
17575
18799
  }
17576
18800
 
17577
18801
  .${prefix}__text {
17578
18802
  padding: 20px;
17579
- width: 70%;
17580
18803
  display: flex;
17581
18804
  flex-direction: column;
17582
18805
  justify-content: center;
@@ -17588,7 +18811,7 @@ const STYLES$3 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
17588
18811
  color: inherit;
17589
18812
  margin: 0;
17590
18813
  font-size: 20px;
17591
- font-family: "Cormorant";
18814
+ font-family: "Cormorant", system-ui;
17592
18815
  font-style: normal;
17593
18816
  font-weight: 300;
17594
18817
  line-height: normal;
@@ -17707,7 +18930,6 @@ const STYLES$2 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = pr
17707
18930
  background-size: cover;
17708
18931
  background-position: center;
17709
18932
  background-repeat: no-repeat;
17710
- container-type: inline-size;
17711
18933
  position: relative;
17712
18934
  }
17713
18935
 
@@ -17777,12 +18999,7 @@ const STYLES$1 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = pr
17777
18999
  border-radius: 5px;
17778
19000
  overflow: hidden;
17779
19001
  cursor: pointer;
17780
- container-type: inline-size;
17781
- }
17782
-
17783
- .${prefix}__text {
17784
- padding: 10px;
17785
- width: 70%;
19002
+ position: relative;
17786
19003
  }
17787
19004
 
17788
19005
  .${prefix}__header {
@@ -17793,6 +19010,7 @@ const STYLES$1 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = pr
17793
19010
  font-style: normal;
17794
19011
  font-weight: 400;
17795
19012
  margin: 0;
19013
+ padding: 10px;
17796
19014
  }
17797
19015
 
17798
19016
  @container (min-width: 640px) {
@@ -17810,9 +19028,7 @@ function rbSmallCategoryImageToutTemplate(spot, config) {
17810
19028
  ${GFONT_CORMORANT}
17811
19029
  ${STYLES$1(spot, config)}
17812
19030
  <div class="${prefix}">
17813
- <div class="${prefix}__text">
17814
- ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17815
- </div>
19031
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17816
19032
  </div>
17817
19033
  `;
17818
19034
  }
@@ -17827,7 +19043,6 @@ const STYLES = ({ textColor = '#000000', backgroundColor = 'transparent', primar
17827
19043
  display: flex;
17828
19044
  flex-direction: column;
17829
19045
  border-radius: 5px;
17830
- container-type: inline-size;
17831
19046
  }
17832
19047
 
17833
19048
  .${prefix}__image {
@@ -17960,97 +19175,186 @@ const SPOT_TEMPLATE_HTML_ELEMENT = (spot, config) => {
17960
19175
  // Generate a highly unique prefix to avoid conflicts with other elements.
17961
19176
  const prefix = 's' + UniqueIdGenerator.generate().toLowerCase();
17962
19177
  const spotHtmlString = variantTemplate(spot, { ...config, prefix });
17963
- return spotHtmlStringToElement(spotHtmlString);
19178
+ return spotHtmlStringToElement(spotHtmlString, spot.spot);
17964
19179
  };
17965
19180
 
17966
- /**
17967
- * PubSub class
17968
- * Manages event subscriptions and publications
17969
- * @template IEventMap A record type defining the structure of events and their data
17970
- */
17971
- class PubSub {
19181
+ // For the moment, we will only focus on sites that use Google Analytics,
19182
+ // but we will add support for other analytics tools in the future.
19183
+ var AnalyticsTool;
19184
+ (function (AnalyticsTool) {
19185
+ AnalyticsTool["GoogleAnalytics"] = "google-analytics";
19186
+ AnalyticsTool["Other"] = "Other";
19187
+ })(AnalyticsTool || (AnalyticsTool = {}));
19188
+
19189
+ class DataLayerMonitor {
17972
19190
  constructor() {
17973
- /**
17974
- * Object to store subscribers for each event type
17975
- */
17976
- this.subscribers = {};
19191
+ if (!window.dataLayer) {
19192
+ return;
19193
+ }
19194
+ this.originalPush = window.dataLayer.push;
17977
19195
  }
17978
19196
  static getInstance() {
17979
- if (!PubSub.instance) {
17980
- PubSub.instance = new PubSub();
17981
- }
17982
- return PubSub.instance;
19197
+ if (!DataLayerMonitor.instance) {
19198
+ DataLayerMonitor.instance = new DataLayerMonitor();
19199
+ }
19200
+ return DataLayerMonitor.instance;
19201
+ }
19202
+ setListener(listener) {
19203
+ this.listener = listener;
19204
+ }
19205
+ start() {
19206
+ window.dataLayer.push = (...args) => {
19207
+ const result = this.originalPush.apply(window.dataLayer, args);
19208
+ const pushedEvent = args[0];
19209
+ if (this.listener) {
19210
+ const normalizedData = this.cleanEventData(pushedEvent);
19211
+ if (normalizedData) {
19212
+ this.listener(normalizedData);
19213
+ }
19214
+ }
19215
+ return result;
19216
+ };
17983
19217
  }
17984
- /**
17985
- * Subscribe to an event
17986
- * @param eventType - The type of event to subscribe to
17987
- * @param callback - The function to be called when the event is published
17988
- * @returns A function to unsubscribe from the event
17989
- *
17990
- * @Example:
17991
- * const unsubscribe = pubSub.subscribe('userLogin', (data) => {
17992
- * console.log(`User ${data.username} logged in`);
17993
- * });
17994
- */
17995
- subscribe(eventType, callback) {
17996
- if (!this.subscribers[eventType]) {
17997
- this.subscribers[eventType] = [];
19218
+ cleanEventData(data) {
19219
+ const eventName = getEventTypeFromRawEvent(data.event);
19220
+ if (!eventName) {
19221
+ return null;
17998
19222
  }
17999
- this.subscribers[eventType].push(callback);
18000
- // Return an unsubscribe function
18001
- return () => {
18002
- this.subscribers[eventType] = this.subscribers[eventType].filter((cb) => cb !== callback);
19223
+ const productIds = extractDeepValues(data, 'ids', {
19224
+ onlyFirst: false,
19225
+ shouldIncludeZero: true,
19226
+ });
19227
+ if (Array.isArray(productIds) && productIds.length === 0) {
19228
+ return null;
19229
+ }
19230
+ const normalizedData = {
19231
+ event: eventName,
19232
+ productIds,
18003
19233
  };
19234
+ if (eventName === exports.RMN_SPOT_EVENT.PURCHASE) {
19235
+ const productPrice = extractDeepValues(data, 'price', {
19236
+ onlyFirst: true,
19237
+ shouldIncludeZero: true,
19238
+ });
19239
+ if (productPrice) {
19240
+ normalizedData.productPrice = productPrice;
19241
+ }
19242
+ }
19243
+ return normalizedData;
18004
19244
  }
18005
- /**
18006
- * Publish an event
18007
- * @param eventType - The type of event to publish
18008
- * @param data - The data to be passed to the event subscribers
18009
- *
18010
- * @Example:
18011
- * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
18012
- */
18013
- publish(eventType, data) {
18014
- if (!this.subscribers[eventType]) {
19245
+ stop() {
19246
+ if (this.originalPush) {
19247
+ window.dataLayer.push = this.originalPush;
19248
+ }
19249
+ this.listener = undefined;
19250
+ }
19251
+ }
19252
+
19253
+ // @TODO: Add support for user to push events to our own data layer, if they don't use any analytics tool.
19254
+ // window.rmnDataLayer = window.rmnDataLayer || [];
19255
+ class MonitorService {
19256
+ constructor() {
19257
+ const analyticsTool = this.detectAnalyticsTool();
19258
+ switch (analyticsTool) {
19259
+ case AnalyticsTool.GoogleAnalytics:
19260
+ this.implementedMonitor = DataLayerMonitor.getInstance();
19261
+ break;
19262
+ case AnalyticsTool.Other:
19263
+ default:
19264
+ console.warn('This site uses an unsupported analytics tool.');
19265
+ break;
19266
+ }
19267
+ if (analyticsTool === AnalyticsTool.Other) {
18015
19268
  return;
18016
19269
  }
18017
- this.subscribers[eventType].forEach((callback) => callback(data));
19270
+ this.pubSubService = PubsubService.getInstance();
19271
+ this.localStorageService = LocalStorageService.getInstance();
19272
+ }
19273
+ static getInstance() {
19274
+ if (!MonitorService.instance) {
19275
+ MonitorService.instance = new MonitorService();
19276
+ }
19277
+ return MonitorService.instance;
19278
+ }
19279
+ start() {
19280
+ if (!this.implementedMonitor)
19281
+ return;
19282
+ this.implementedMonitor.setListener(async (eventData) => {
19283
+ var _a;
19284
+ await this.matchAndFireEvent(eventData, (_a = this.localStorageService) === null || _a === void 0 ? void 0 : _a.getSpots());
19285
+ });
19286
+ this.implementedMonitor.start();
19287
+ }
19288
+ async matchAndFireEvent(eventData, spots) {
19289
+ var _a, _b;
19290
+ if (!spots)
19291
+ return;
19292
+ const eventProductIds = new Set(cleanProductIds(eventData.productIds));
19293
+ for (const spot of Object.values(spots)) {
19294
+ if (!spot.productIds.length)
19295
+ continue;
19296
+ const hasCommonProductIds = cleanProductIds(spot.productIds).find((productId) => eventProductIds.has(productId));
19297
+ if (!hasCommonProductIds || !Object.values(exports.RMN_SPOT_EVENT).includes(eventData.event)) {
19298
+ continue;
19299
+ }
19300
+ const eventUrl = (_b = (_a = spot.events.find((event) => event.event === eventData.event)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '';
19301
+ const additionalQueryParams = objectToQueryParams({
19302
+ override: eventData.productPrice,
19303
+ });
19304
+ await this.fireAndPublishSpotEvent({
19305
+ spotEvent: eventData.event,
19306
+ eventUrl: `${eventUrl}${additionalQueryParams ? `&${additionalQueryParams}` : ''}`,
19307
+ placementId: spot.placementId,
19308
+ spotId: spot.spotId,
19309
+ });
19310
+ }
19311
+ }
19312
+ async fireAndPublishSpotEvent({ spotEvent, eventUrl, placementId, spotId, }) {
19313
+ await fireEvent({
19314
+ event: spotEvent,
19315
+ eventUrl,
19316
+ });
19317
+ if (!this.pubSubService)
19318
+ return;
19319
+ this.pubSubService.publish(exports.RMN_EVENT.SPOT_EVENT, {
19320
+ eventType: spotEvent,
19321
+ placementId,
19322
+ spotId,
19323
+ });
19324
+ }
19325
+ detectAnalyticsTool() {
19326
+ let analyticsTool = AnalyticsTool.Other;
19327
+ // Check for Google Analytics
19328
+ if (typeof window.ga !== 'undefined') {
19329
+ analyticsTool = AnalyticsTool.GoogleAnalytics;
19330
+ }
19331
+ // Check for Google Analytics 4
19332
+ if (typeof window.gtag !== 'undefined') {
19333
+ analyticsTool = AnalyticsTool.GoogleAnalytics;
19334
+ }
19335
+ // Check for Google Tag Manager
19336
+ if (typeof window.google_tag_manager !== 'undefined') {
19337
+ analyticsTool = AnalyticsTool.GoogleAnalytics;
19338
+ }
19339
+ // @TODO: Add support for other analytics tools
19340
+ // Check for Heap Analytics
19341
+ // Check for Mixpanel
19342
+ // Check for Woopra
19343
+ // Check for Segment
19344
+ // Check for Amplitude
19345
+ return analyticsTool;
18018
19346
  }
18019
19347
  }
18020
- /**
18021
- * Usage Example:
18022
- *
18023
- * interface IEventMap {
18024
- * userLogin: { username: string; timestamp: number };
18025
- * pageView: { url: string; timestamp: number };
18026
- * }
18027
- *
18028
- * const pubSub = new PubSub<IEventMap>();
18029
- *
18030
- * // Subscribe to events
18031
- * const unsubscribeLogin = pubSub.subscribe('userLogin', (data) => {
18032
- * console.log(`User ${data.username} logged in at ${new Date(data.timestamp)}`);
18033
- * });
18034
- *
18035
- * pubSub.subscribe('pageView', (data) => {
18036
- * console.log(`Page ${data.url} viewed at ${new Date(data.timestamp)}`);
18037
- * });
18038
- *
18039
- * // Publish events
18040
- * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
18041
- * pubSub.publish('pageView', { url: '/home', timestamp: Date.now() });
18042
- *
18043
- * // Unsubscribe from an event
18044
- * unsubscribeLogin();
18045
- */
18046
19348
 
18047
19349
  class EventService {
18048
19350
  constructor() {
18049
- this.pubSub = PubSub.getInstance();
18050
- this.localStorage = LocalStorage.getInstance();
19351
+ this.pubSubService = PubsubService.getInstance();
19352
+ this.localStorageService = LocalStorageService.getInstance();
18051
19353
  this.activeSpots = new Map();
18052
19354
  this.spotStates = new Map();
18053
19355
  this.intersectionObserver = new IntersectionObserverService();
19356
+ // Start the user monitor, which will track and check user interactions
19357
+ MonitorService.getInstance().start();
18054
19358
  }
18055
19359
  static getInstance() {
18056
19360
  if (!EventService.instance) {
@@ -18059,25 +19363,28 @@ class EventService {
18059
19363
  return EventService.instance;
18060
19364
  }
18061
19365
  subscribe(eventType, callback) {
18062
- return this.pubSub.subscribe(eventType, callback);
19366
+ return this.pubSubService.subscribe(eventType, callback);
18063
19367
  }
18064
19368
  publish(eventType, data) {
18065
- this.pubSub.publish(eventType, data);
19369
+ this.pubSubService.publish(eventType, data);
18066
19370
  }
18067
19371
  registerSpot(params) {
18068
19372
  const { placementId, spot, spotElement } = params;
18069
- this.activeSpots.set(placementId, { spotElement });
18070
- // Fire impression event
18071
- this.fireImpressionEvent(placementId, spot, spotElement);
18072
- // Handle intersection observer
18073
- this.handleIntersectionObserver(placementId, spot, spotElement);
18074
- // Attach click event listener
18075
- spotElement.addEventListener('click', async () => await this.handleClick(params));
19373
+ this.activeSpots.set(spot.id, { placementId, spotElement });
19374
+ // Handle Impression Event
19375
+ this.handleImpressionEvent(placementId, spot);
19376
+ // Handle Click Event
19377
+ spotElement.addEventListener('click', async () => {
19378
+ await this.handleClickEvent(params);
19379
+ });
19380
+ // Handle Intersection Observer
19381
+ this.handleIntersectionObserver(placementId, spotElement);
18076
19382
  }
18077
19383
  unregisterSpot(placementId) {
18078
19384
  const placementIdClean = placementId.replace('#', '');
18079
- const spotData = this.activeSpots.get(placementIdClean);
18080
- if (!spotData) {
19385
+ const spotId = this.getSpotIdByPlacementId(this.activeSpots, placementIdClean);
19386
+ const data = this.activeSpots.get(placementIdClean !== null && placementIdClean !== void 0 ? placementIdClean : '');
19387
+ if (!spotId || !data) {
18081
19388
  this.handleSpotState(placementIdClean, {
18082
19389
  state: {
18083
19390
  error: `Active spot with placementId ${placementIdClean} not found.`,
@@ -18085,7 +19392,7 @@ class EventService {
18085
19392
  });
18086
19393
  return;
18087
19394
  }
18088
- this.intersectionObserver.unobserve(spotData.spotElement);
19395
+ this.intersectionObserver.unobserve(data.spotElement);
18089
19396
  this.handleSpotState(placementIdClean, {
18090
19397
  dom: {
18091
19398
  spotElement: undefined,
@@ -18094,9 +19401,10 @@ class EventService {
18094
19401
  state: {
18095
19402
  unmounted: true,
18096
19403
  mounted: false,
19404
+ loading: false,
18097
19405
  },
18098
19406
  });
18099
- this.activeSpots.delete(placementIdClean);
19407
+ this.activeSpots.delete(spotId);
18100
19408
  const placementElement = document.getElementById(placementIdClean);
18101
19409
  if (!placementElement) {
18102
19410
  this.handleSpotState(placementIdClean, {
@@ -18143,128 +19451,77 @@ class EventService {
18143
19451
  },
18144
19452
  };
18145
19453
  }
18146
- this.spotStates.set(placementId, { ...currentState, ...updates });
19454
+ const merged = this.deepMerge(currentState, updates);
19455
+ this.spotStates.set(placementId, merged);
18147
19456
  if (publish) {
18148
- this.pubSub.publish(exports.RMN_SPOT_EVENT.LIFECYCLE_STATE, this.spotStates.get(placementId));
19457
+ this.pubSubService.publish(exports.RMN_EVENT.LIFECYCLE_STATE, this.spotStates.get(placementId));
18149
19458
  }
18150
19459
  }
18151
- async handleClick({ placementId, spot, spotElement, }) {
19460
+ async handleClickEvent({ placementId, spot }) {
18152
19461
  var _a, _b, _c;
18153
- // Publish click event
18154
- this.pubSub.publish(exports.RMN_SPOT_EVENT.CLICK, {
19462
+ this.pubSubService.publish(exports.RMN_EVENT.SPOT_EVENT, {
19463
+ eventType: exports.RMN_SPOT_EVENT.CLICK,
18155
19464
  placementId,
18156
19465
  spotId: spot.id,
18157
- spotElement,
18158
19466
  });
18159
- // Fire click event
18160
- await this.fireEvent({
19467
+ await fireEvent({
18161
19468
  event: exports.RMN_SPOT_EVENT.CLICK,
18162
19469
  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 : '',
18163
19470
  });
18164
19471
  // Save spot to local storage for event tracking
18165
- this.localStorage.setSpot(spot.id, {
19472
+ this.localStorageService.setSpot(spot.id, {
19473
+ placementId,
18166
19474
  spotId: spot.id,
18167
19475
  spotType: spot.spot,
18168
19476
  events: spot.events,
18169
- productIds: (_c = spot.productIds) !== null && _c !== void 0 ? _c : [1, 'GROUPING-12345', 'DAN-12345', 131398103],
19477
+ productIds: (_c = spot.productIds) !== null && _c !== void 0 ? _c : [],
18170
19478
  });
18171
19479
  }
18172
- handleIntersectionObserver(placementId, _spot, spotElement) {
18173
- const spotIsVisibleCallback = async () => {
18174
- this.intersectionObserver.unobserve(spotElement);
18175
- this.handleSpotState(placementId, {
18176
- dom: {
18177
- spotElement,
18178
- visibleOnViewport: true,
18179
- },
18180
- });
18181
- };
18182
- this.intersectionObserver.observe(spotElement, spotIsVisibleCallback);
18183
- }
18184
- fireImpressionEvent(placementId, spot, spotElement) {
18185
- this.pubSub.publish(exports.RMN_SPOT_EVENT.IMPRESSION, {
19480
+ handleImpressionEvent(placementId, spot) {
19481
+ this.pubSubService.publish(exports.RMN_EVENT.SPOT_EVENT, {
19482
+ eventType: exports.RMN_SPOT_EVENT.IMPRESSION,
18186
19483
  placementId,
18187
19484
  spotId: spot.id,
18188
- spotElement,
18189
19485
  });
18190
19486
  (async () => {
18191
19487
  var _a, _b;
18192
- await this.fireEvent({
19488
+ await fireEvent({
18193
19489
  event: exports.RMN_SPOT_EVENT.IMPRESSION,
18194
19490
  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 : '',
18195
19491
  });
18196
19492
  })();
18197
19493
  }
18198
- /**
18199
- * Fires an event using the navigator.sendBeacon method or a fallback method if sendBeacon is not available.
18200
- * If the event is a click event and a redirect URL is found, it redirects the user to that URL.
18201
- *
18202
- * @param {IFireEventParams} params - The parameters for firing the event.
18203
- * @param {RMN_SPOT_EVENT} params.event - The event type.
18204
- * @param {string} params.eventUrl - The URL to which the event is sent.
18205
- * @returns {Promise<void>} - A promise that resolves when the event is fired.
18206
- */
18207
- async fireEvent({ event, eventUrl }) {
18208
- var _a;
18209
- try {
18210
- 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));
18211
- if (!didFireEvent) {
18212
- return;
18213
- }
18214
- if (event === exports.RMN_SPOT_EVENT.CLICK) {
18215
- const redirectUrl = this.getRedirectUrlFromPayload(eventUrl);
18216
- if (redirectUrl) {
18217
- window.location.href = redirectUrl;
18218
- }
19494
+ getSpotIdByPlacementId(activeSpots, searchPlacementId) {
19495
+ for (const [spotId, spotData] of activeSpots) {
19496
+ if (spotData.placementId === searchPlacementId) {
19497
+ return spotId;
18219
19498
  }
18220
19499
  }
18221
- catch (_b) {
18222
- // Handle errors silently
18223
- }
19500
+ return null;
18224
19501
  }
18225
- // Fallback method using fetch if sendBeacon isn't available
18226
- async fallbackEventFire(url) {
18227
- try {
18228
- const racePromise = Promise.race([
18229
- // Promise #1: The fetch request
18230
- fetch(url, {
18231
- method: 'POST',
18232
- keepalive: true,
18233
- }),
18234
- // Promise #2: The timeout
18235
- new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), 2000)),
18236
- ]);
18237
- /**
18238
- * Prevent requests from hanging indefinitely
18239
- * Improve user experience by failing fast
18240
- * Handle slow network conditions gracefully
18241
- * Ensure resources are freed up in a timely manner
18242
- */
18243
- const response = await racePromise;
18244
- return response.ok;
18245
- }
18246
- catch (_a) {
18247
- return false;
18248
- }
19502
+ deepMerge(current, updates) {
19503
+ return {
19504
+ identifier: updates.identifier
19505
+ ? { ...current.identifier, ...updates.identifier }
19506
+ : current.identifier,
19507
+ dom: updates.dom ? { ...current.dom, ...updates.dom } : current.dom,
19508
+ state: updates.state ? { ...current.state, ...updates.state } : current.state,
19509
+ displayConfig: updates.displayConfig
19510
+ ? { ...current.displayConfig, ...updates.displayConfig }
19511
+ : current.displayConfig,
19512
+ };
18249
19513
  }
18250
- /**
18251
- * Extracts and decodes a URL from a base64-encoded query parameter.
18252
- *
18253
- * @param {string} url - The URL containing the base64-encoded query parameter.
18254
- * @returns {string | null} - The decoded URL or null if not found or invalid.
18255
- */
18256
- getRedirectUrlFromPayload(url) {
18257
- try {
18258
- const base64String = new URL(url).searchParams.get('e');
18259
- if (!base64String) {
18260
- return null;
18261
- }
18262
- const data = JSON.parse(atob(base64String));
18263
- return data.ur || null;
18264
- }
18265
- catch (_a) {
18266
- return null;
18267
- }
19514
+ handleIntersectionObserver(placementId, spotElement) {
19515
+ const spotIsVisibleCallback = async () => {
19516
+ this.intersectionObserver.unobserve(spotElement);
19517
+ this.handleSpotState(placementId, {
19518
+ dom: {
19519
+ spotElement,
19520
+ visibleOnViewport: true,
19521
+ },
19522
+ });
19523
+ };
19524
+ this.intersectionObserver.observe(spotElement, spotIsVisibleCallback);
18268
19525
  }
18269
19526
  }
18270
19527
 
@@ -18285,6 +19542,9 @@ class SelectionService extends BaseApi {
18285
19542
  * @return {Promise<ISpots | { error: string }>} - The spots response object.
18286
19543
  */
18287
19544
  async spotSelection(data) {
19545
+ if (data.userId === undefined) {
19546
+ data.userId = this.getUserId();
19547
+ }
18288
19548
  const { isOk, val, isErr } = await this.post(SELECTION_API_PATH, data, {});
18289
19549
  if (isErr) {
18290
19550
  return { error: `There was an error during spot selection: (${isErr === null || isErr === void 0 ? void 0 : isErr.errorMessage})` };
@@ -18296,6 +19556,14 @@ class SelectionService extends BaseApi {
18296
19556
  }
18297
19557
  return { error: 'Spot selection response was not successful' };
18298
19558
  }
19559
+ getUserId() {
19560
+ const isWeb = typeof window !== 'undefined';
19561
+ if (isWeb) {
19562
+ const localStorageService = LocalStorageService.getInstance();
19563
+ return localStorageService.getUserId();
19564
+ }
19565
+ return undefined;
19566
+ }
18299
19567
  }
18300
19568
 
18301
19569
  const SPOT_EVENTS_EXAMPLE = [
@@ -18324,80 +19592,278 @@ const SPOT_EVENTS_EXAMPLE = [
18324
19592
  url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjY5fQ&s=l6MOscQC-q-FkC2Ksd7w6jjySCQ',
18325
19593
  },
18326
19594
  ];
18327
- const RB_SPOTS_SELECTION_EXAMPLE = {
19595
+ ({
19596
+ rbHomepageHero: [
19597
+ {
19598
+ id: 'abc123',
19599
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19600
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19601
+ width: 1140,
19602
+ height: 640,
19603
+ header: 'Premium Wine Collection',
19604
+ description: 'Discover our exclusive selection of vintage wines',
19605
+ ctaText: 'Shop Wines',
19606
+ textColor: '#ffffff',
19607
+ ctaTextColor: '#ffffff',
19608
+ primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Collection',
19609
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Wine',
19610
+ events: SPOT_EVENTS_EXAMPLE,
19611
+ productIds: [1, 2, 3],
19612
+ },
19613
+ {
19614
+ id: 'jkl012',
19615
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19616
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19617
+ width: 1140,
19618
+ height: 640,
19619
+ header: 'Whiskey Collection',
19620
+ description: 'Premium whiskeys from around the world',
19621
+ ctaText: 'Shop Whiskeys',
19622
+ textColor: '#ffffff',
19623
+ backgroundColor: '#2c1810',
19624
+ ctaTextColor: '#2c1810',
19625
+ primaryImage: 'https://placehold.co/1140x640/png?text=Whiskey',
19626
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Whiskey',
19627
+ events: SPOT_EVENTS_EXAMPLE,
19628
+ productIds: [10, 11],
19629
+ },
19630
+ {
19631
+ id: 'stu901',
19632
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19633
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19634
+ width: 1140,
19635
+ height: 640,
19636
+ header: 'Summer Cocktails',
19637
+ description: 'Essential spirits for summer mixing',
19638
+ ctaText: 'Mix It Up',
19639
+ textColor: '#ffffff',
19640
+ backgroundColor: '#4d3a0a',
19641
+ ctaTextColor: '#4d3a0a',
19642
+ primaryImage: 'https://placehold.co/1140x640/png?text=Cocktails',
19643
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Mixers',
19644
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Cocktails',
19645
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Mixers',
19646
+ events: SPOT_EVENTS_EXAMPLE,
19647
+ productIds: [16, 17, 18],
19648
+ },
19649
+ {
19650
+ id: 'def456',
19651
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19652
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19653
+ width: 1140,
19654
+ height: 640,
19655
+ header: 'Craft Beer Festival',
19656
+ description: 'Local breweries and exclusive releases',
19657
+ ctaText: 'Explore Beers',
19658
+ textColor: '#ffffff',
19659
+ ctaTextColor: '#ffffff',
19660
+ primaryImage: 'https://placehold.co/1140x640/png?text=Beer+Festival',
19661
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Beer',
19662
+ events: SPOT_EVENTS_EXAMPLE,
19663
+ productIds: [4, 5, 6],
19664
+ },
19665
+ {
19666
+ id: 'mno345',
19667
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19668
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19669
+ width: 1140,
19670
+ height: 640,
19671
+ header: 'Champagne Selection',
19672
+ description: 'Finest champagnes for every occasion',
19673
+ ctaText: 'View Champagnes',
19674
+ textColor: '#ffffff',
19675
+ backgroundColor: '#1a1a1a',
19676
+ ctaTextColor: '#1a1a1a',
19677
+ primaryImage: 'https://placehold.co/1140x640/png?text=Champagne',
19678
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Champagne',
19679
+ events: SPOT_EVENTS_EXAMPLE,
19680
+ productIds: [12, 13],
19681
+ },
19682
+ {
19683
+ id: 'vwx234',
19684
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19685
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19686
+ width: 1140,
19687
+ height: 640,
19688
+ header: 'Wine Regions',
19689
+ description: 'Explore wines from top regions',
19690
+ ctaText: 'Tour Regions',
19691
+ textColor: '#ffffff',
19692
+ backgroundColor: '#4d0a0a',
19693
+ ctaTextColor: '#4d0a0a',
19694
+ primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Regions',
19695
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Vineyards',
19696
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Regions',
19697
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Vineyards',
19698
+ events: SPOT_EVENTS_EXAMPLE,
19699
+ productIds: [19, 20, 21],
19700
+ },
19701
+ {
19702
+ id: 'ghi789',
19703
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19704
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19705
+ width: 1140,
19706
+ height: 640,
19707
+ header: 'Rare Spirits',
19708
+ description: 'Limited edition spirits collection',
19709
+ ctaText: 'View Collection',
19710
+ textColor: '#ffffff',
19711
+ ctaTextColor: '#ffffff',
19712
+ primaryImage: 'https://placehold.co/1140x640/png?text=Rare+Spirits',
19713
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Spirits',
19714
+ events: SPOT_EVENTS_EXAMPLE,
19715
+ productIds: [7, 8, 9],
19716
+ },
19717
+ {
19718
+ id: 'pqr678',
19719
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19720
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19721
+ width: 1140,
19722
+ height: 640,
19723
+ header: 'Gin Collection',
19724
+ description: 'Artisanal gins and botanicals',
19725
+ ctaText: 'Explore Gins',
19726
+ textColor: '#ffffff',
19727
+ backgroundColor: '#0a4d4d',
19728
+ ctaTextColor: '#0a4d4d',
19729
+ primaryImage: 'https://placehold.co/1140x640/png?text=Gin',
19730
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gin',
19731
+ events: SPOT_EVENTS_EXAMPLE,
19732
+ productIds: [14, 15],
19733
+ },
19734
+ {
19735
+ id: 'yz5678',
19736
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19737
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19738
+ width: 1140,
19739
+ height: 640,
19740
+ header: 'Tequila Collection',
19741
+ description: 'Premium tequilas and mezcals',
19742
+ ctaText: 'Shop Tequila',
19743
+ textColor: '#ffffff',
19744
+ backgroundColor: '#0a4d2b',
19745
+ ctaTextColor: '#0a4d2b',
19746
+ primaryImage: 'https://placehold.co/1140x640/png?text=Tequila',
19747
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Mezcal',
19748
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Tequila',
19749
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Mezcal',
19750
+ events: SPOT_EVENTS_EXAMPLE,
19751
+ productIds: [22, 23, 24],
19752
+ },
19753
+ ],
18328
19754
  rbHomepageHeroFullImage: [
18329
19755
  {
18330
- id: '111111_111111',
19756
+ id: 'hjk567',
18331
19757
  spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
18332
19758
  variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
18333
19759
  width: 1140,
18334
19760
  height: 640,
18335
- header: 'Artisanal Craft Beer Collection',
18336
- description: 'Discover our curated selection of small-batch, flavor-packed craft beers.',
18337
- ctaText: 'Explore the Collection',
19761
+ header: 'Holiday Gift Guide',
19762
+ description: 'Perfect spirits for every occasion',
19763
+ ctaText: 'Shop Gifts',
18338
19764
  textColor: '#ffffff',
18339
19765
  ctaTextColor: '#ffffff',
18340
- primaryImage: 'https://placehold.co/1140x640/png?text=Craft+Beer+Collection',
18341
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Craft+Beer',
19766
+ primaryImage: 'https://placehold.co/1140x640/png?text=Gift+Guide',
19767
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gifts',
18342
19768
  events: SPOT_EVENTS_EXAMPLE,
19769
+ productIds: [25, 26],
18343
19770
  },
18344
19771
  {
18345
- id: '222222_222222',
19772
+ id: 'qwe890',
18346
19773
  spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
18347
19774
  variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
18348
19775
  width: 1140,
18349
19776
  height: 640,
18350
- header: 'Summer Wine Spectacular',
18351
- description: 'Refresh your palate with our handpicked selection of crisp, summer wines.',
18352
- ctaText: 'Shop Summer Wines',
18353
- textColor: '#000000',
19777
+ header: 'Summer Wine Festival',
19778
+ description: 'Refreshing wines for summer',
19779
+ ctaText: 'Shop Festival',
19780
+ textColor: '#ffffff',
18354
19781
  ctaTextColor: '#ffffff',
18355
- primaryImage: 'https://placehold.co/1140x640/png?text=Summer+Wines',
18356
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Summer+Wines',
19782
+ primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Festival',
19783
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Festival',
18357
19784
  events: SPOT_EVENTS_EXAMPLE,
19785
+ productIds: [27, 28],
18358
19786
  },
18359
19787
  ],
18360
19788
  rbHomepageHeroTwoTile: [
18361
19789
  {
18362
- id: '333333_333333',
19790
+ id: 'iop987',
18363
19791
  spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
18364
19792
  variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
18365
19793
  width: 1140,
18366
19794
  height: 640,
18367
- header: 'Whiskey Wonderland',
18368
- description: 'Embark on a journey through our premium whiskey selection.',
18369
- ctaText: 'Discover Whiskeys',
19795
+ header: 'Bourbon Selection',
19796
+ description: "Kentucky's finest bourbons",
19797
+ ctaText: 'Shop Bourbon',
18370
19798
  textColor: '#ffffff',
18371
- backgroundColor: '#2c1a05',
18372
- ctaTextColor: '#2c1a05',
18373
- primaryImage: 'https://placehold.co/1140x640/png?text=Whiskey+Collection',
18374
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Whiskey',
19799
+ backgroundColor: '#2c1810',
19800
+ ctaTextColor: '#2c1810',
19801
+ primaryImage: 'https://placehold.co/1140x640/png?text=Bourbon',
19802
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Bourbon',
19803
+ events: SPOT_EVENTS_EXAMPLE,
19804
+ productIds: [29, 30],
19805
+ },
19806
+ {
19807
+ id: 'lkj012',
19808
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19809
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19810
+ width: 1140,
19811
+ height: 640,
19812
+ header: 'Vodka Collection',
19813
+ description: 'Premium vodkas from around the world',
19814
+ ctaText: 'Shop Vodka',
19815
+ textColor: '#ffffff',
19816
+ backgroundColor: '#1a1a1a',
19817
+ ctaTextColor: '#1a1a1a',
19818
+ primaryImage: 'https://placehold.co/1140x640/png?text=Vodka',
19819
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Vodka',
18375
19820
  events: SPOT_EVENTS_EXAMPLE,
19821
+ productIds: [31, 32],
18376
19822
  },
18377
19823
  ],
18378
19824
  rbHomepageHeroThreeTile: [
18379
19825
  {
18380
- id: '444444_444444',
19826
+ id: 'bnm345',
19827
+ spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19828
+ variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19829
+ width: 1140,
19830
+ height: 640,
19831
+ header: 'Rum Collection',
19832
+ description: 'Caribbean and craft rums',
19833
+ ctaText: 'Shop Rum',
19834
+ textColor: '#ffffff',
19835
+ backgroundColor: '#4d3a0a',
19836
+ ctaTextColor: '#4d3a0a',
19837
+ primaryImage: 'https://placehold.co/1140x640/png?text=Rum',
19838
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Craft+Rum',
19839
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Rum',
19840
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Craft',
19841
+ events: SPOT_EVENTS_EXAMPLE,
19842
+ productIds: [33, 34],
19843
+ },
19844
+ {
19845
+ id: 'fgh678',
18381
19846
  spot: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
18382
19847
  variant: exports.RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
18383
19848
  width: 1140,
18384
19849
  height: 640,
18385
- header: 'Cocktail Essentials',
18386
- description: 'Stock your bar with premium spirits and mixers for the perfect cocktail.',
18387
- ctaText: 'Build Your Bar',
19850
+ header: 'Scotch Selection',
19851
+ description: 'Single malts and blends',
19852
+ ctaText: 'Shop Scotch',
18388
19853
  textColor: '#ffffff',
18389
- backgroundColor: '#1a3c4d',
18390
- ctaTextColor: '#1a3c4d',
18391
- primaryImage: 'https://placehold.co/1140x640/png?text=Cocktail+Spirits',
18392
- secondaryImage: 'https://placehold.co/1140x640/png?text=Cocktail+Mixers',
18393
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Cocktail+Kit',
18394
- mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Cocktail+Mixers',
19854
+ backgroundColor: '#4d0a0a',
19855
+ ctaTextColor: '#4d0a0a',
19856
+ primaryImage: 'https://placehold.co/1140x640/png?text=Scotch',
19857
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Single+Malts',
19858
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Scotch',
19859
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Malts',
18395
19860
  events: SPOT_EVENTS_EXAMPLE,
19861
+ productIds: [35, 36],
18396
19862
  },
18397
19863
  ],
18398
19864
  rbLargeCategoryImageTout: [
18399
19865
  {
18400
- id: '555555_555555',
19866
+ id: 'cde567',
18401
19867
  spot: exports.RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
18402
19868
  variant: exports.RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
18403
19869
  width: 468,
@@ -18410,11 +19876,44 @@ const RB_SPOTS_SELECTION_EXAMPLE = {
18410
19876
  mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Rare+Spirits',
18411
19877
  ctaText: 'Shop Rare Spirits',
18412
19878
  events: SPOT_EVENTS_EXAMPLE,
19879
+ productIds: [37, 38, 39, 40, 41],
19880
+ },
19881
+ {
19882
+ id: 'efg789',
19883
+ spot: exports.RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19884
+ variant: exports.RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19885
+ width: 468,
19886
+ height: 410,
19887
+ header: 'Vintage Champagnes',
19888
+ description: 'Explore our prestigious collection of aged champagnes.',
19889
+ textColor: '#ffffff',
19890
+ ctaTextColor: '#ffffff',
19891
+ primaryImage: 'https://placehold.co/468x410/png?text=Vintage+Champagne',
19892
+ mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Champagne',
19893
+ ctaText: 'View Collection',
19894
+ events: SPOT_EVENTS_EXAMPLE,
19895
+ productIds: [42, 43, 44, 45, 46],
19896
+ },
19897
+ {
19898
+ id: 'ghi012',
19899
+ spot: exports.RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19900
+ variant: exports.RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19901
+ width: 468,
19902
+ height: 410,
19903
+ header: 'Small Batch Bourbon',
19904
+ description: 'Hand-selected premium bourbon from craft distilleries.',
19905
+ textColor: '#ffffff',
19906
+ ctaTextColor: '#ffffff',
19907
+ primaryImage: 'https://placehold.co/468x410/png?text=Craft+Bourbon',
19908
+ mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Bourbon',
19909
+ ctaText: 'Explore Bourbon',
19910
+ events: SPOT_EVENTS_EXAMPLE,
19911
+ productIds: [47, 48, 49, 50, 51],
18413
19912
  },
18414
19913
  ],
18415
19914
  rbSmallDiscoverTout: [
18416
19915
  {
18417
- id: '666666_666666',
19916
+ id: 'jkl345',
18418
19917
  spot: exports.RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
18419
19918
  variant: exports.RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
18420
19919
  width: 224,
@@ -18424,11 +19923,38 @@ const RB_SPOTS_SELECTION_EXAMPLE = {
18424
19923
  primaryImage: 'https://placehold.co/224x378/png?text=Château+Margaux',
18425
19924
  mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Château+Margaux',
18426
19925
  events: SPOT_EVENTS_EXAMPLE,
19926
+ productIds: [52, 53, 54, 55, 56],
19927
+ },
19928
+ {
19929
+ id: 'lmn678',
19930
+ spot: exports.RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19931
+ variant: exports.RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19932
+ width: 224,
19933
+ height: 378,
19934
+ header: 'Macallan 25 Year',
19935
+ textColor: '#ffffff',
19936
+ primaryImage: 'https://placehold.co/224x378/png?text=Macallan+25',
19937
+ mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Macallan',
19938
+ events: SPOT_EVENTS_EXAMPLE,
19939
+ productIds: [57, 58, 59, 60, 61],
19940
+ },
19941
+ {
19942
+ id: 'nop901',
19943
+ spot: exports.RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19944
+ variant: exports.RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19945
+ width: 224,
19946
+ height: 378,
19947
+ header: 'Louis XIII Cognac',
19948
+ textColor: '#ffffff',
19949
+ primaryImage: 'https://placehold.co/224x378/png?text=Louis+XIII',
19950
+ mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Louis+XIII',
19951
+ events: SPOT_EVENTS_EXAMPLE,
19952
+ productIds: [62, 63, 64, 65, 66],
18427
19953
  },
18428
19954
  ],
18429
19955
  rbSmallCategoryImageTout: [
18430
19956
  {
18431
- id: '777777_777777',
19957
+ id: 'qrs234',
18432
19958
  spot: exports.RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
18433
19959
  variant: exports.RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
18434
19960
  width: 224,
@@ -18438,11 +19964,38 @@ const RB_SPOTS_SELECTION_EXAMPLE = {
18438
19964
  primaryImage: 'https://placehold.co/224x410/png?text=Japanese+Sake',
18439
19965
  mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Japanese+Sake',
18440
19966
  events: SPOT_EVENTS_EXAMPLE,
19967
+ productIds: [67, 68, 69, 70, 71],
19968
+ },
19969
+ {
19970
+ id: 'stu567',
19971
+ spot: exports.RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19972
+ variant: exports.RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19973
+ width: 224,
19974
+ height: 410,
19975
+ header: 'Craft Vermouth',
19976
+ textColor: '#ffffff',
19977
+ primaryImage: 'https://placehold.co/224x410/png?text=Craft+Vermouth',
19978
+ mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Vermouth',
19979
+ events: SPOT_EVENTS_EXAMPLE,
19980
+ productIds: [72, 73, 74, 75, 76],
19981
+ },
19982
+ {
19983
+ id: 'vwx890',
19984
+ spot: exports.RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19985
+ variant: exports.RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19986
+ width: 224,
19987
+ height: 410,
19988
+ header: 'Premium Mezcal',
19989
+ textColor: '#ffffff',
19990
+ primaryImage: 'https://placehold.co/224x410/png?text=Premium+Mezcal',
19991
+ mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Mezcal',
19992
+ events: SPOT_EVENTS_EXAMPLE,
19993
+ productIds: [77, 78, 79, 80, 81],
18441
19994
  },
18442
19995
  ],
18443
19996
  rbCollectionBannerWithoutTextBlock: [
18444
19997
  {
18445
- id: '888888_888888',
19998
+ id: 'yz1234',
18446
19999
  spot: exports.RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
18447
20000
  variant: exports.RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
18448
20001
  width: 887,
@@ -18450,11 +20003,34 @@ const RB_SPOTS_SELECTION_EXAMPLE = {
18450
20003
  primaryImage: 'https://placehold.co/887x344/png?text=Summer+Cocktails',
18451
20004
  mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Summer+Cocktails',
18452
20005
  events: SPOT_EVENTS_EXAMPLE,
20006
+ productIds: [82, 83, 84, 85, 86],
20007
+ },
20008
+ {
20009
+ id: 'abc567',
20010
+ spot: exports.RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
20011
+ variant: exports.RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
20012
+ width: 887,
20013
+ height: 344,
20014
+ primaryImage: 'https://placehold.co/887x344/png?text=Holiday+Spirits',
20015
+ mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Holiday+Spirits',
20016
+ events: SPOT_EVENTS_EXAMPLE,
20017
+ productIds: [87, 88, 89, 90, 91],
20018
+ },
20019
+ {
20020
+ id: 'def901',
20021
+ spot: exports.RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
20022
+ variant: exports.RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
20023
+ width: 887,
20024
+ height: 344,
20025
+ primaryImage: 'https://placehold.co/887x344/png?text=Wine+Essentials',
20026
+ mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Wine+Essentials',
20027
+ events: SPOT_EVENTS_EXAMPLE,
20028
+ productIds: [92, 93, 94, 95, 96],
18453
20029
  },
18454
20030
  ],
18455
20031
  rbNavigationBanner: [
18456
20032
  {
18457
- id: '999999_999999',
20033
+ id: 'ghi234',
18458
20034
  spot: exports.RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
18459
20035
  variant: exports.RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
18460
20036
  width: 440,
@@ -18464,15 +20040,215 @@ const RB_SPOTS_SELECTION_EXAMPLE = {
18464
20040
  primaryImage: 'https://placehold.co/440x220/png?text=Tequila+Collection',
18465
20041
  mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Tequila+Collection',
18466
20042
  events: SPOT_EVENTS_EXAMPLE,
20043
+ productIds: [97, 98, 99, 100, 101],
20044
+ },
20045
+ {
20046
+ id: 'jkl678',
20047
+ spot: exports.RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
20048
+ variant: exports.RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
20049
+ width: 440,
20050
+ height: 220,
20051
+ header: 'Craft Gin Selection',
20052
+ textColor: '#ffffff',
20053
+ primaryImage: 'https://placehold.co/440x220/png?text=Gin+Selection',
20054
+ mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Gin+Selection',
20055
+ events: SPOT_EVENTS_EXAMPLE,
20056
+ productIds: [102, 103, 104, 105, 106],
20057
+ },
20058
+ {
20059
+ id: 'mno012',
20060
+ spot: exports.RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
20061
+ variant: exports.RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
20062
+ width: 440,
20063
+ height: 220,
20064
+ header: 'Premium Vodka',
20065
+ textColor: '#ffffff',
20066
+ primaryImage: 'https://placehold.co/440x220/png?text=Vodka+Premium',
20067
+ mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Vodka+Premium',
20068
+ events: SPOT_EVENTS_EXAMPLE,
20069
+ productIds: [107, 108, 109, 110, 111],
18467
20070
  },
18468
20071
  ],
18469
- };
20072
+ });
20073
+ ({
20074
+ banner: [],
20075
+ billboard: [
20076
+ {
20077
+ id: 'kol567',
20078
+ spot: exports.RMN_SPOT_TYPE.BILLBOARD,
20079
+ variant: `${exports.RMN_SPOT_TYPE.BILLBOARD}V2`,
20080
+ width: 1140,
20081
+ height: 640,
20082
+ header: 'Holiday Gift Guide',
20083
+ description: 'Perfect spirits for every occasion',
20084
+ ctaText: 'Shop Gifts',
20085
+ textColor: '#ffffff',
20086
+ ctaTextColor: '#ffffff',
20087
+ primaryImage: 'https://placehold.co/1140x640/png?text=Gift+Guide',
20088
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gifts',
20089
+ events: SPOT_EVENTS_EXAMPLE,
20090
+ productIds: [25, 26],
20091
+ },
20092
+ {
20093
+ id: 'hpm390',
20094
+ spot: exports.RMN_SPOT_TYPE.BILLBOARD,
20095
+ variant: `${exports.RMN_SPOT_TYPE.BILLBOARD}V2`,
20096
+ width: 1140,
20097
+ height: 640,
20098
+ header: 'Summer Wine Festival',
20099
+ description: 'Refreshing wines for summer',
20100
+ ctaText: 'Shop Festival',
20101
+ textColor: '#ffffff',
20102
+ ctaTextColor: '#ffffff',
20103
+ primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Festival',
20104
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Festival',
20105
+ events: SPOT_EVENTS_EXAMPLE,
20106
+ productIds: [27, 28],
20107
+ },
20108
+ ],
20109
+ button2: [],
20110
+ featurePhoneLargeBanner: [],
20111
+ featurePhoneMediumBanner: [],
20112
+ featurePhoneSmallBanner: [],
20113
+ halfPage: [],
20114
+ inText: [],
20115
+ largeLeaderboard: [],
20116
+ largeRectangle: [],
20117
+ leaderboard: [],
20118
+ mediumRectangle: [],
20119
+ microBar: [],
20120
+ mobilePhoneInterstitial1: [],
20121
+ mobilePhoneInterstitial2: [],
20122
+ mobilePhoneInterstitial3: [],
20123
+ popUp: [],
20124
+ portrait: [],
20125
+ rbProductUpcs: [],
20126
+ skyscraper: [],
20127
+ smallRectangle: [],
20128
+ smallSquare: [],
20129
+ smartphoneBanner1: [],
20130
+ smartphoneBanner2: [],
20131
+ square: [],
20132
+ verticalBanner: [],
20133
+ verticalRectangle: [],
20134
+ wideSkyscraper: [],
20135
+ });
18470
20136
 
20137
+ /**
20138
+ * Checks if the current environment is a browser environment.
20139
+ *
20140
+ * @return {boolean} - Whether the current environment is a browser environment.
20141
+ */
20142
+ function isBrowserEnvironment() {
20143
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
20144
+ console.warn('LiquidCommerce Rmn Sdk: Methods which create elements are only available in browser environments.');
20145
+ return false;
20146
+ }
20147
+ return true;
20148
+ }
20149
+ /**
20150
+ * Validates the inject data by preventing duplicate placement ids and non-existent spot types.
20151
+ *
20152
+ * @param {IInjectSpotElement[]} inject - The inject data.
20153
+ * @return {IInjectSpotElement[]} - The validated inject data.
20154
+ */
20155
+ function validateInjectData(inject) {
20156
+ const eventService = EventService.getInstance();
20157
+ const placementIds = new Set();
20158
+ const validSpotTypes = new Set(Object.values(exports.RMN_SPOT_TYPE));
20159
+ const validatedInject = [];
20160
+ for (const item of inject) {
20161
+ const placementId = item.placementId.replace('#', '');
20162
+ // Check for duplicate placement ids
20163
+ if (placementIds.has(placementId)) {
20164
+ eventService.handleSpotState(placementId, {
20165
+ state: {
20166
+ error: `Duplicate placement id (${placementId}) found. Please provide a unique placement id for each spot element.`,
20167
+ },
20168
+ });
20169
+ continue;
20170
+ }
20171
+ // Check for non-existent spot types
20172
+ if (!validSpotTypes.has(item.spotType)) {
20173
+ eventService.handleSpotState(placementId, {
20174
+ state: {
20175
+ error: `Invalid spot type (${item.spotType}) found. Please provide a valid spot type for each spot element.`,
20176
+ },
20177
+ });
20178
+ continue;
20179
+ }
20180
+ placementIds.add(placementId);
20181
+ validatedInject.push(item);
20182
+ }
20183
+ return validatedInject;
20184
+ }
20185
+ /**
20186
+ * Clears the placement element by removing all its children.
20187
+ *
20188
+ * @param {string} placementId - The placement id.
20189
+ *
20190
+ * @return {void}
20191
+ */
20192
+ function clearPlacement(placementId) {
20193
+ var _a;
20194
+ (_a = document.getElementById(placementId)) === null || _a === void 0 ? void 0 : _a.replaceChildren();
20195
+ }
20196
+ /**
20197
+ * Prepares the spot placement for rendering by taking care of its styling.
20198
+ *
20199
+ * @param {HTMLElement} placement - The placement element.
20200
+ *
20201
+ * @return {void}
20202
+ */
20203
+ function prepareSpotPlacement(placement) {
20204
+ placement.removeAttribute('style');
20205
+ placement.removeAttribute('class');
20206
+ const styles = {
20207
+ width: '100%',
20208
+ height: '100%',
20209
+ display: 'flex',
20210
+ justifyContent: 'center',
20211
+ };
20212
+ Object.assign(placement.style, styles);
20213
+ }
20214
+ /**
20215
+ * Waits for the DOM to be ready before continuing.
20216
+ *
20217
+ * @return {Promise<void>} - A promise that resolves when the DOM is ready.
20218
+ */
20219
+ async function waitForDOM() {
20220
+ if (!isBrowserEnvironment()) {
20221
+ return;
20222
+ }
20223
+ if (document.readyState === 'complete' || document.readyState === 'interactive') {
20224
+ return;
20225
+ }
20226
+ return new Promise((resolve) => {
20227
+ document.addEventListener('DOMContentLoaded', () => {
20228
+ resolve();
20229
+ });
20230
+ });
20231
+ }
20232
+ // Sets the id for the user who is browsing the website
20233
+ // This id is used to identify the user and provide personalized content
20234
+ function setUserId() {
20235
+ if (isBrowserEnvironment()) {
20236
+ const localStorageService = LocalStorageService.getInstance();
20237
+ localStorageService.setUserId();
20238
+ }
20239
+ }
20240
+
20241
+ /**
20242
+ * LiquidCommerce Rmn Client
20243
+ *
20244
+ * @class
20245
+ */
18471
20246
  class LiquidCommerceRmnClient {
18472
20247
  constructor(auth) {
18473
20248
  this.selectionService = SelectionService.getInstance(auth);
18474
20249
  this.elementService = ElementService.getInstance();
18475
20250
  this.eventService = EventService.getInstance();
20251
+ this.intersectionObserver = new IntersectionObserverService();
18476
20252
  }
18477
20253
  /**
18478
20254
  * Makes a selection request on our server based on the provided data.
@@ -18481,7 +20257,7 @@ class LiquidCommerceRmnClient {
18481
20257
  *
18482
20258
  * @param {ISpotSelectionParams} params - Spots selection parameters.
18483
20259
  *
18484
- * @return {Promise<ISpots | {error : string}>} - The spots response object.
20260
+ * @return {Promise<ISpots | { error : string }>} - The spots response object.
18485
20261
  */
18486
20262
  async spotSelection(params) {
18487
20263
  return this.selectionService.spotSelection(params);
@@ -18495,104 +20271,188 @@ class LiquidCommerceRmnClient {
18495
20271
  */
18496
20272
  async injectSpotElement(params) {
18497
20273
  var _a;
20274
+ // Wait for the DOM to be ready before continuing, to avoid issues with the spot placements
20275
+ await waitForDOM();
18498
20276
  const config = params.config;
18499
- let inject = params.inject;
18500
- if (!inject.length) {
20277
+ // Handle no spots error state
20278
+ if (!params.inject.length) {
18501
20279
  this.eventService.handleSpotState('all', {
18502
20280
  state: {
18503
20281
  error: 'No spot elements provided for injection.',
18504
- loading: false,
18505
- },
18506
- });
18507
- return;
18508
- }
18509
- // Update the state of the spots to loading
18510
- this.updateSpotsState(inject);
18511
- // Prevent duplicate placement ids
18512
- const hasDuplicatePlacementIds = this.preventDuplicateSpotPlacementIds(inject);
18513
- if (!hasDuplicatePlacementIds) {
18514
- return;
18515
- }
18516
- // Prevent non-existent spot types
18517
- inject = this.preventNonExistentSpotTypes(inject);
18518
- // Make the spot selection request
18519
- const response = await this.spotSelectionRequest({ ...params, inject });
18520
- // const response = await this.useSpotSelectionExample(inject);
18521
- // Handle the response
18522
- if (typeof response === 'object' && 'error' in response) {
18523
- this.eventService.handleSpotState('all', {
18524
- state: {
18525
- error: response.error,
18526
20282
  },
18527
20283
  });
18528
20284
  return;
18529
20285
  }
20286
+ // Validate inject data
20287
+ const inject = validateInjectData(params.inject);
18530
20288
  for (const item of inject) {
18531
- const itemConfig = (_a = item.config) !== null && _a !== void 0 ? _a : config;
18532
- const spots = response[item.placementId];
18533
- if (!(spots === null || spots === void 0 ? void 0 : spots.length)) {
20289
+ // Identify the spot element
20290
+ this.eventService.handleSpotState(item.placementId, {
20291
+ identifier: {
20292
+ placementId: item.placementId,
20293
+ spotType: item.spotType,
20294
+ },
20295
+ }, false);
20296
+ const placement = document.getElementById(item.placementId);
20297
+ // Handle placement not found error state
20298
+ if (!placement) {
18534
20299
  this.eventService.handleSpotState(item.placementId, {
18535
20300
  state: {
18536
- error: `No spots found for type "${item.spotType}".`,
20301
+ error: `Placement not found for id "${item.placementId}".`,
20302
+ mounted: false,
18537
20303
  loading: false,
18538
20304
  },
18539
20305
  });
18540
20306
  continue;
18541
20307
  }
18542
- const placementId = item.placementId.replace('#', '');
18543
- const placement = document.getElementById(placementId);
18544
- if (!placement) {
20308
+ prepareSpotPlacement(placement);
20309
+ const skeletonElement = this.elementService.createSkeletonElement({
20310
+ fluid: (_a = config === null || config === void 0 ? void 0 : config.fluid) !== null && _a !== void 0 ? _a : false,
20311
+ spotType: item.spotType,
20312
+ });
20313
+ // Handle skeleton loader error state
20314
+ if (!skeletonElement) {
18545
20315
  this.eventService.handleSpotState(item.placementId, {
18546
20316
  state: {
18547
- error: `Placement not found for id "${placementId}".`,
18548
- loading: false,
20317
+ error: `Failed to create skeleton loader element.`,
20318
+ mounted: false,
20319
+ loading: true,
18549
20320
  },
18550
20321
  });
18551
20322
  continue;
18552
20323
  }
18553
- // Take over placement styles
18554
- placement.removeAttribute('style');
18555
- placement.removeAttribute('class');
18556
- Object.assign(placement.style, {
18557
- width: '100%',
18558
- height: 'auto',
18559
- display: 'flex',
18560
- justifyContent: 'center',
18561
- });
18562
- if (spots.length === 1) {
18563
- const isInjected = this.injectOneSpotElement(item, placement, spots[0], itemConfig);
18564
- if (!isInjected) {
18565
- continue;
20324
+ placement.replaceChildren(skeletonElement);
20325
+ const spotPlacementIsNearCallback = async () => {
20326
+ var _a;
20327
+ // Stop observing the placement, as we only need to do this once
20328
+ this.intersectionObserver.unobserve(placement);
20329
+ // Set the spot element to loading state
20330
+ this.eventService.handleSpotState(item.placementId, { state: { loading: true } });
20331
+ // Make the spot selection request
20332
+ const response = await this.injectSpotSelectionRequest({ ...params, inject: [item] });
20333
+ // const response = await useSpotSelectionExample(inject);
20334
+ // Handle request error state
20335
+ if (typeof response === 'object' && 'error' in response) {
20336
+ this.eventService.handleSpotState(item.placementId, {
20337
+ state: {
20338
+ error: response.error,
20339
+ mounted: false,
20340
+ loading: false,
20341
+ },
20342
+ });
20343
+ clearPlacement(item.placementId);
20344
+ return;
18566
20345
  }
18567
- }
18568
- if (spots.length > 1) {
18569
- const isInjected = this.injectCarouselSpotElement(placement, spots, itemConfig);
18570
- if (!isInjected) {
18571
- continue;
20346
+ const itemConfig = (_a = item.config) !== null && _a !== void 0 ? _a : config;
20347
+ const spots = response[item.placementId];
20348
+ // Handle no spots found error state
20349
+ if (!(spots === null || spots === void 0 ? void 0 : spots.length)) {
20350
+ this.eventService.handleSpotState(item.placementId, {
20351
+ state: {
20352
+ error: `No spots found for type "${item.spotType}".`,
20353
+ mounted: false,
20354
+ loading: false,
20355
+ },
20356
+ });
20357
+ clearPlacement(item.placementId);
20358
+ return;
18572
20359
  }
18573
- }
20360
+ // Handle single spot
20361
+ if (spots.length === 1) {
20362
+ this.injectOneSpotElement(placement, spots[0], itemConfig);
20363
+ }
20364
+ // Handle multiple spots (carousel)
20365
+ if (spots.length > 1) {
20366
+ this.injectCarouselSpotElement(placement, spots, itemConfig);
20367
+ }
20368
+ };
20369
+ /**
20370
+ * Observe the placement element to check if it is near the viewport.
20371
+ * If it is near, make the spot selection request.
20372
+ */
20373
+ this.intersectionObserver.observe(placement, spotPlacementIsNearCallback, {
20374
+ rootMargin: '1000px',
20375
+ threshold: 0,
20376
+ });
18574
20377
  }
18575
20378
  }
18576
20379
  /**
18577
- * Makes a selection request on our server based on the provided data.
20380
+ * Injects a single spot element into the provided placement.
18578
20381
  *
18579
- * @param {IInjectSpotElementParams} params - Parameters for injecting spot elements.
20382
+ * @param {HTMLElement} placement - The placement element.
20383
+ * @param {ISpot} spot - The spot data.
20384
+ * @param {IInjectSpotElementConfig} config - The configuration object.
18580
20385
  *
18581
- * @return {Promise<ISpots | {error: string}>} - The spots response object.
20386
+ * @return {void}
18582
20387
  */
18583
- async spotSelectionRequest(params) {
18584
- const { inject, filter, config } = params;
18585
- const request = {
18586
- url: config === null || config === void 0 ? void 0 : config.url,
18587
- filter,
18588
- spots: inject.map((item) => ({
18589
- placementId: item.placementId,
18590
- spot: item.spotType,
18591
- count: item === null || item === void 0 ? void 0 : item.count,
18592
- ...item === null || item === void 0 ? void 0 : item.filter,
18593
- })),
18594
- };
18595
- return this.spotSelection(request);
20388
+ injectOneSpotElement(placement, spot, config) {
20389
+ var _a;
20390
+ const placementId = placement.id;
20391
+ const spotType = spot.spot;
20392
+ this.eventService.handleSpotState(placementId, {
20393
+ identifier: {
20394
+ placementId,
20395
+ spotType,
20396
+ spotId: spot.id,
20397
+ },
20398
+ displayConfig: {
20399
+ isSingleItem: true,
20400
+ isCarousel: false,
20401
+ isCarouselItem: false,
20402
+ },
20403
+ });
20404
+ const spotData = this.elementService.overrideSpotColors(spot, config === null || config === void 0 ? void 0 : config.colors);
20405
+ const content = SPOT_TEMPLATE_HTML_ELEMENT(spotData, { overlay: config === null || config === void 0 ? void 0 : config.overlay });
20406
+ if (!content) {
20407
+ this.eventService.handleSpotState(placementId, {
20408
+ state: {
20409
+ error: `Failed to inject spot element. Could not create element for type "${spotType}".`,
20410
+ mounted: false,
20411
+ loading: false,
20412
+ },
20413
+ });
20414
+ clearPlacement(placementId);
20415
+ return;
20416
+ }
20417
+ // Create the spot element
20418
+ const spotElement = this.elementService.createSpotElement({
20419
+ content,
20420
+ config: {
20421
+ fluid: config === null || config === void 0 ? void 0 : config.fluid,
20422
+ spot: spot.spot,
20423
+ width: spot.width,
20424
+ height: spot.height,
20425
+ minScale: (_a = config === null || config === void 0 ? void 0 : config.minScale) !== null && _a !== void 0 ? _a : 0.25, // Scale down to 25% of the original size
20426
+ },
20427
+ });
20428
+ if (!spotElement) {
20429
+ this.eventService.handleSpotState(placementId, {
20430
+ state: {
20431
+ error: `Failed to inject spot element. Could not create element for type "${spotType}".`,
20432
+ mounted: false,
20433
+ loading: false,
20434
+ },
20435
+ });
20436
+ clearPlacement(placementId);
20437
+ return;
20438
+ }
20439
+ this.eventService.registerSpot({
20440
+ spot: spotData,
20441
+ placementId,
20442
+ spotElement,
20443
+ });
20444
+ placement.replaceChildren(spotElement);
20445
+ this.eventService.handleSpotState(placementId, {
20446
+ dom: {
20447
+ spotElement,
20448
+ visibleOnViewport: false,
20449
+ },
20450
+ state: {
20451
+ mounted: true,
20452
+ loading: false,
20453
+ error: undefined,
20454
+ },
20455
+ });
18596
20456
  }
18597
20457
  /**
18598
20458
  * Injects a carousel element with the provided spots into the placement.
@@ -18605,21 +20465,28 @@ class LiquidCommerceRmnClient {
18605
20465
  */
18606
20466
  injectCarouselSpotElement(placement, spots, config) {
18607
20467
  var _a;
20468
+ const placementId = placement.id;
18608
20469
  const carouselSlides = [];
18609
20470
  for (const spotItem of spots) {
18610
- this.eventService.handleSpotState(placement.id, {
20471
+ this.eventService.handleSpotState(placementId, {
20472
+ identifier: {
20473
+ placementId,
20474
+ spotType: spotItem.spot,
20475
+ spotId: spotItem.id,
20476
+ },
18611
20477
  displayConfig: {
20478
+ isSingleItem: false,
18612
20479
  isCarousel: true,
18613
20480
  isCarouselItem: true,
18614
- isSingleItem: false,
18615
20481
  },
18616
- }, false);
20482
+ });
18617
20483
  const spot = this.elementService.overrideSpotColors(spotItem, config === null || config === void 0 ? void 0 : config.colors);
18618
20484
  const content = SPOT_TEMPLATE_HTML_ELEMENT(spot, { overlay: config === null || config === void 0 ? void 0 : config.overlay });
18619
20485
  if (!content) {
18620
- this.eventService.handleSpotState(placement.id, {
20486
+ this.eventService.handleSpotState(placementId, {
18621
20487
  state: {
18622
20488
  error: `Failed to inject carousel spot item element. Could not create element for type "${spot.spot}".`,
20489
+ mounted: false,
18623
20490
  loading: false,
18624
20491
  },
18625
20492
  });
@@ -18627,7 +20494,7 @@ class LiquidCommerceRmnClient {
18627
20494
  }
18628
20495
  this.eventService.registerSpot({
18629
20496
  spot,
18630
- placementId: placement.id,
20497
+ placementId,
18631
20498
  spotElement: content,
18632
20499
  });
18633
20500
  carouselSlides.push(content);
@@ -18649,154 +20516,50 @@ class LiquidCommerceRmnClient {
18649
20516
  },
18650
20517
  });
18651
20518
  if (!carouselElement) {
18652
- this.eventService.handleSpotState(placement.id, {
20519
+ this.eventService.handleSpotState(placementId, {
18653
20520
  state: {
18654
20521
  error: `Failed to inject spot carousel element. Could not create spot carousel element.`,
20522
+ mounted: false,
18655
20523
  loading: false,
18656
20524
  },
18657
20525
  });
18658
- return false;
20526
+ clearPlacement(placementId);
20527
+ return;
18659
20528
  }
18660
20529
  placement.replaceChildren(carouselElement);
18661
- this.eventService.handleSpotState(placement.id, {
20530
+ this.eventService.handleSpotState(placementId, {
18662
20531
  dom: {
18663
20532
  spotElement: carouselElement,
20533
+ visibleOnViewport: false,
18664
20534
  },
18665
20535
  state: {
18666
20536
  mounted: true,
18667
20537
  loading: false,
20538
+ error: undefined,
18668
20539
  },
18669
20540
  });
18670
- return true;
18671
- }
18672
- /**
18673
- * Injects a single spot element into the provided placement.
18674
- *
18675
- * @param {IInjectSpotElement} injectItem - The inject item data.
18676
- * @param {HTMLElement} placement - The placement element.
18677
- * @param {ISpot} spot - The spot data.
18678
- * @param {IInjectSpotElementConfig} config - The configuration object.
18679
- *
18680
- * @return {void}
18681
- */
18682
- injectOneSpotElement(injectItem, placement, spot, config) {
18683
- var _a;
18684
- const spotData = this.elementService.overrideSpotColors(spot, config === null || config === void 0 ? void 0 : config.colors);
18685
- this.eventService.handleSpotState(injectItem.placementId, {
18686
- displayConfig: {
18687
- isSingleItem: true,
18688
- },
18689
- }, false);
18690
- // Create the spot template element
18691
- const content = SPOT_TEMPLATE_HTML_ELEMENT(spotData, { overlay: config === null || config === void 0 ? void 0 : config.overlay });
18692
- if (!content) {
18693
- this.eventService.handleSpotState(injectItem.placementId, {
18694
- state: {
18695
- error: `Failed to inject spot element. Could not create element for type "${injectItem.spotType}".`,
18696
- loading: false,
18697
- },
18698
- });
18699
- return false;
18700
- }
18701
- // Create the spot element
18702
- const spotElement = this.elementService.createSpotElement({
18703
- content,
18704
- config: {
18705
- fluid: config === null || config === void 0 ? void 0 : config.fluid,
18706
- spot: spot.spot,
18707
- width: spot.width,
18708
- height: spot.height,
18709
- minScale: (_a = config === null || config === void 0 ? void 0 : config.minScale) !== null && _a !== void 0 ? _a : 0.25, // Scale down to 25% of the original size
18710
- },
18711
- });
18712
- if (!spotElement) {
18713
- this.eventService.handleSpotState(injectItem.placementId, {
18714
- state: {
18715
- error: `Failed to inject spot element. Could not create element for type "${injectItem.spotType}".`,
18716
- loading: false,
18717
- },
18718
- });
18719
- return false;
18720
- }
18721
- this.eventService.registerSpot({
18722
- spot: spotData,
18723
- placementId: injectItem.placementId,
18724
- spotElement,
18725
- });
18726
- placement.replaceChildren(spotElement);
18727
- this.eventService.handleSpotState(injectItem.placementId, {
18728
- dom: {
18729
- spotElement,
18730
- },
18731
- state: {
18732
- mounted: true,
18733
- loading: false,
18734
- },
18735
- });
18736
- return true;
18737
20541
  }
18738
20542
  /**
18739
- * Prevents duplicate placement ids in the inject data.
18740
- *
18741
- * @param {IInjectSpotElement[]} inject - The inject data.
20543
+ * Makes a selection request on our server based on the provided data.
18742
20544
  *
18743
- * @throws {Error} - If a duplicate placement id is found.
20545
+ * @param {IInjectSpotElementParams} params - Parameters for injecting spot elements.
18744
20546
  *
18745
- * @return {void}
20547
+ * @return {Promise<ISpots | {error: string}>} - The spots response object.
18746
20548
  */
18747
- preventDuplicateSpotPlacementIds(inject) {
18748
- const placementIds = new Set();
18749
- for (const item of inject) {
18750
- if (placementIds.has(item.placementId)) {
18751
- this.eventService.handleSpotState(item.placementId, {
18752
- state: {
18753
- error: `Duplicate placement id (${item.placementId}) found. Please provide a unique placement id for each spot element.`,
18754
- },
18755
- });
18756
- return false;
18757
- }
18758
- placementIds.add(item.placementId);
18759
- }
18760
- return true;
18761
- }
18762
- preventNonExistentSpotTypes(inject) {
18763
- const newInject = [];
18764
- for (const item of inject) {
18765
- if (!Object.values(exports.RMN_SPOT_TYPE).includes(item.spotType)) {
18766
- this.eventService.handleSpotState(item.placementId, {
18767
- state: {
18768
- error: `Invalid spot type (${item.spotType}) found. Please provide a valid spot type for each spot element.`,
18769
- },
18770
- });
18771
- continue;
18772
- }
18773
- newInject.push(item);
18774
- }
18775
- return newInject;
18776
- }
18777
- updateSpotsState(inject) {
18778
- for (const item of inject) {
18779
- this.eventService.handleSpotState(item.placementId, {
18780
- identifier: {
18781
- placementId: item.placementId,
18782
- spotType: item.spotType,
18783
- },
18784
- state: {
18785
- loading: true,
18786
- },
18787
- });
18788
- }
18789
- }
18790
- useSpotSelectionExample(inject) {
18791
- const examples = RB_SPOTS_SELECTION_EXAMPLE;
18792
- const data = {};
18793
- inject.map((item) => {
18794
- var _a, _b, _c;
18795
- data[item.placementId] = (_c = (_a = examples[item.spotType]) === null || _a === void 0 ? void 0 : _a.slice(0, (_b = item.count) !== null && _b !== void 0 ? _b : 1)) !== null && _c !== void 0 ? _c : [];
18796
- });
18797
- return new Promise((resolve) => {
18798
- resolve(data);
18799
- });
20549
+ async injectSpotSelectionRequest(params) {
20550
+ const { inject, filter, config } = params;
20551
+ const spots = inject.map((item) => ({
20552
+ placementId: item.placementId,
20553
+ spot: item.spotType,
20554
+ count: item === null || item === void 0 ? void 0 : item.count,
20555
+ ...item === null || item === void 0 ? void 0 : item.filter,
20556
+ }));
20557
+ const request = {
20558
+ url: config === null || config === void 0 ? void 0 : config.url,
20559
+ filter,
20560
+ spots,
20561
+ };
20562
+ return this.spotSelection(request);
18800
20563
  }
18801
20564
  }
18802
20565
  /**
@@ -18810,6 +20573,7 @@ class LiquidCommerceRmnClient {
18810
20573
  async function RmnClient(apiKey, config) {
18811
20574
  const authService = AuthService.getInstance(apiKey, config.env);
18812
20575
  const credentials = await authService.initialize();
20576
+ setUserId();
18813
20577
  return new LiquidCommerceRmnClient(credentials);
18814
20578
  }
18815
20579
  /**