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