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