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