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

Sign up to get free protection for your applications and to get access to all the features.
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
  /**