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