@crystaldesign/widget-library 25.8.0-beta.5 → 25.8.0-beta.7

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.
@@ -1,12 +1,12 @@
1
- import { useEffect, useMemo, useState, forwardRef, useRef } from 'react';
1
+ import { useEffect, useMemo, useCallback, useState, useRef, forwardRef } from 'react';
2
2
  import { useDivaCore, getLogger, DivaError, useTranslation } from '@crystaldesign/diva-core';
3
+ import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';
3
4
  import _asyncToGenerator from '@babel/runtime/helpers/asyncToGenerator';
4
5
  import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
5
6
  import _regeneratorRuntime from '@babel/runtime/regenerator';
6
7
  import _defineProperty from '@babel/runtime/helpers/defineProperty';
7
8
  import { Navigation, Thumbs, Pagination } from 'swiper/modules';
8
9
  import { Swiper, SwiperSlide } from 'swiper/react';
9
- import _toConsumableArray from '@babel/runtime/helpers/toConsumableArray';
10
10
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
11
  import classNames from 'classnames';
12
12
  import { useLayer, Arrow } from 'react-laag';
@@ -80,20 +80,234 @@ function useConfiguration(type, componentSettings) {
80
80
  return settings;
81
81
  }
82
82
 
83
- function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
84
- function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
85
- function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
83
+ var LOG$2 = getLogger('DivaWidget', 'useGlobalCache');
84
+
85
+ // Global cache for shared data between widgets
86
+
87
+ // Initialize global cache if not exists
88
+ if (typeof window !== 'undefined' && !window.__divaWidgetCache) {
89
+ window.__divaWidgetCache = {
90
+ cache: new Map()
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Global cache for shared data between widgets of a website
96
+ * Simple implementation with request deduplication
97
+ */
98
+ function useGlobalCache(_ref) {
99
+ var uniqueWidgetId = _ref.uniqueWidgetId,
100
+ widgetType = _ref.widgetType;
101
+ var getOrFetch = useCallback(function (key, fetcher) {
102
+ LOG$2.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] getOrFetch called for key: ").concat(key));
103
+ if (typeof window === 'undefined' || !window.__divaWidgetCache) {
104
+ LOG$2.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] No global cache available, fetching directly for key: ").concat(key));
105
+ return fetcher();
106
+ }
107
+ var cache = window.__divaWidgetCache.cache;
108
+ var entry = cache.get(key);
109
+
110
+ // If we have cached data, return it immediately
111
+ if (entry !== null && entry !== void 0 && entry.data) {
112
+ LOG$2.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Cache hit for key: ").concat(key, ", returning cached data"));
113
+ return Promise.resolve(entry.data);
114
+ }
115
+
116
+ // If there's already a pending request, return that promise (deduplication)
117
+ if (entry !== null && entry !== void 0 && entry.promise) {
118
+ LOG$2.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Cache hit for pending request, returning existing promise for key: ").concat(key));
119
+ return entry.promise;
120
+ }
121
+ LOG$2.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Cache miss for key: ").concat(key, ", creating new request"));
122
+
123
+ // Create new entry or reset existing one
124
+ var newEntry = {
125
+ data: null,
126
+ promise: null,
127
+ error: null,
128
+ timestamp: Date.now()
129
+ };
130
+
131
+ // Create new request
132
+ var promise = fetcher().then(function (data) {
133
+ LOG$2.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Request successful for key: ").concat(key, ", updating cache with ").concat(data.length, " items"));
134
+ var currentEntry = cache.get(key);
135
+ if (currentEntry) {
136
+ currentEntry.data = data;
137
+ currentEntry.promise = null;
138
+ currentEntry.error = null;
139
+ currentEntry.timestamp = Date.now();
140
+ }
141
+ return data;
142
+ })["catch"](function (error) {
143
+ LOG$2.error(new DivaError("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Request failed for key: ").concat(key), {
144
+ cause: error
145
+ }));
146
+ var currentEntry = cache.get(key);
147
+ if (currentEntry) {
148
+ currentEntry.promise = null;
149
+ currentEntry.error = error;
150
+ currentEntry.timestamp = Date.now();
151
+ }
152
+ throw error;
153
+ });
154
+ newEntry.promise = promise;
155
+ cache.set(key, newEntry);
156
+ return promise;
157
+ }, [uniqueWidgetId, widgetType]);
158
+ var clear = useCallback(function (key) {
159
+ LOG$2.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Clearing cache entry for key: ").concat(key));
160
+ if (typeof window !== 'undefined' && window.__divaWidgetCache) {
161
+ var deleted = window.__divaWidgetCache.cache["delete"](key);
162
+ LOG$2.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Cache entry ").concat(deleted ? 'deleted' : 'not found', " for key: ").concat(key));
163
+ }
164
+ }, [uniqueWidgetId, widgetType]);
165
+ var clearAll = useCallback(function () {
166
+ LOG$2.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Clearing all cache entries"));
167
+ if (typeof window !== 'undefined' && window.__divaWidgetCache) {
168
+ var size = window.__divaWidgetCache.cache.size;
169
+ window.__divaWidgetCache.cache.clear();
170
+ LOG$2.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Cleared ").concat(size, " cache entries"));
171
+ }
172
+ }, [uniqueWidgetId, widgetType]);
173
+ return useMemo(function () {
174
+ return {
175
+ getOrFetch: getOrFetch,
176
+ clear: clear,
177
+ clearAll: clearAll
178
+ };
179
+ }, [getOrFetch, clear, clearAll]);
180
+ }
181
+
182
+ var LOG$1 = getLogger('DivaWidget', 'useGlobalSelectedProduct');
183
+ var EVENT_NAME_PRODUCT_SELECTED = 'diva:widget:product-selected';
184
+
185
+ /**
186
+ * Hook to get the globally selected product
187
+ *
188
+ * Initially and when the product changes, it gets selected.
189
+ * When ever the selected product is changed, an event is dispatched to notify other widgets, so they can also set the new product as selected.
190
+ * The hook also listens for product selection events from other widgets and updates the selected product accordingly.
191
+ *
192
+ * @param product - The product to select initially
193
+ * @returns The selected product and a function to set the selected product
194
+ */
195
+ function useGlobalSelectedProduct(_ref) {
196
+ var product = _ref.product,
197
+ uniqueWidgetId = _ref.uniqueWidgetId,
198
+ widgetType = _ref.widgetType;
199
+ var _useState = useState(product),
200
+ _useState2 = _slicedToArray(_useState, 2),
201
+ selectedProduct = _useState2[0],
202
+ setInternalSelectedProduct = _useState2[1];
203
+ var lastEventTimestamp = useRef(0);
204
+
205
+ // Update internal state when prop changes - prop always takes precedence for this widget
206
+ useEffect(function () {
207
+ if (product && product._id !== (selectedProduct === null || selectedProduct === void 0 ? void 0 : selectedProduct._id)) {
208
+ var timestamp = Date.now();
209
+ lastEventTimestamp.current = timestamp;
210
+ LOG$1.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Product prop changed, updating selected product from ").concat((selectedProduct === null || selectedProduct === void 0 ? void 0 : selectedProduct._id) || 'none', " to ").concat(product._id));
211
+ setInternalSelectedProduct(product);
212
+ }
213
+ }, [product]);
214
+
215
+ // Listen to product selection events from other widgets
216
+ var handleProductSelection = useCallback(function (event) {
217
+ var customEvent = event;
218
+ var _customEvent$detail = customEvent.detail,
219
+ product = _customEvent$detail.product,
220
+ sourceId = _customEvent$detail.sourceId,
221
+ timestamp = _customEvent$detail.timestamp;
222
+ LOG$1.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Received product selection event from ").concat(sourceId, ", product: ").concat(product._id, ", timestamp: ").concat(timestamp));
223
+
224
+ // Ignore our own events
225
+ if (sourceId === uniqueWidgetId) {
226
+ LOG$1.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Ignoring own event"));
227
+ return;
228
+ }
229
+
230
+ // Ignore older events (handle out-of-order events)
231
+ if (timestamp <= lastEventTimestamp.current) {
232
+ LOG$1.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Ignoring older event (timestamp: ").concat(timestamp, " <= ").concat(lastEventTimestamp.current, ")"));
233
+ return;
234
+ }
235
+
236
+ // Ignore if product hasn't actually changed
237
+ if ((selectedProduct === null || selectedProduct === void 0 ? void 0 : selectedProduct._id) === product._id) {
238
+ LOG$1.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Ignoring event - product already selected: ").concat(product._id));
239
+ return;
240
+ }
241
+ LOG$1.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Updating selected product from ").concat((selectedProduct === null || selectedProduct === void 0 ? void 0 : selectedProduct._id) || 'none', " to ").concat(product._id));
242
+ lastEventTimestamp.current = timestamp;
243
+ setInternalSelectedProduct(product);
244
+ }, [uniqueWidgetId, selectedProduct, widgetType]);
245
+
246
+ // Stable event listener setup
247
+ useEffect(function () {
248
+ window.addEventListener(EVENT_NAME_PRODUCT_SELECTED, handleProductSelection);
249
+ return function () {
250
+ window.removeEventListener(EVENT_NAME_PRODUCT_SELECTED, handleProductSelection);
251
+ };
252
+ }, [handleProductSelection]);
253
+ var setSelectedProduct = useCallback(function (product) {
254
+ // Avoid unnecessary updates
255
+ if ((selectedProduct === null || selectedProduct === void 0 ? void 0 : selectedProduct._id) === product._id) {
256
+ LOG$1.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Ignoring setSelectedProduct - product already selected: ").concat(product._id));
257
+ return;
258
+ }
259
+ var timestamp = Date.now();
260
+ lastEventTimestamp.current = timestamp;
261
+ LOG$1.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Setting selected product from ").concat((selectedProduct === null || selectedProduct === void 0 ? void 0 : selectedProduct._id) || 'none', " to ").concat(product._id));
262
+ setInternalSelectedProduct(product);
263
+
264
+ // Dispatch event to notify other widgets
265
+ LOG$1.debug("[".concat(widgetType, ":").concat(uniqueWidgetId, "] Dispatching product selection event to notify other widgets"));
266
+ window.dispatchEvent(new CustomEvent(EVENT_NAME_PRODUCT_SELECTED, {
267
+ detail: {
268
+ product: product,
269
+ sourceId: uniqueWidgetId,
270
+ timestamp: timestamp
271
+ }
272
+ }));
273
+ }, [uniqueWidgetId, selectedProduct, widgetType]);
274
+ return {
275
+ selectedProduct: selectedProduct,
276
+ setSelectedProduct: setSelectedProduct
277
+ };
278
+ }
279
+
86
280
  var LOG = getLogger('DivaWidget', 'useProductData');
281
+ // Generate unique widget instance ID
282
+ var generateWidgetId = function generateWidgetId() {
283
+ return "widget-".concat(Date.now(), "-").concat(Math.random().toString(36).substr(2, 9));
284
+ };
87
285
  function useProductData(_ref) {
88
286
  var productIds = _ref.productIds,
89
287
  variants = _ref.variants,
90
- onProductLoaded = _ref.onProductLoaded;
288
+ widgetType = _ref.widgetType;
289
+ // Generate stable widget ID that persists for the lifetime of this hook instance
290
+ var widgetId = useMemo(function () {
291
+ return generateWidgetId();
292
+ }, []);
91
293
  var _useDivaCore = useDivaCore(),
92
294
  handler = _useDivaCore.handler;
93
- var _useState = useState(variants !== null && variants !== void 0 ? variants : []),
295
+ var _useGlobalCache = useGlobalCache({
296
+ uniqueWidgetId: widgetId,
297
+ widgetType: widgetType
298
+ }),
299
+ getOrFetch = _useGlobalCache.getOrFetch;
300
+ var _useState = useState([]),
94
301
  _useState2 = _slicedToArray(_useState, 2),
95
302
  productVariants = _useState2[0],
96
303
  setProductVariants = _useState2[1];
304
+ var _useGlobalSelectedPro = useGlobalSelectedProduct({
305
+ product: productVariants[0],
306
+ uniqueWidgetId: widgetId,
307
+ widgetType: widgetType
308
+ }),
309
+ selectedProduct = _useGlobalSelectedPro.selectedProduct,
310
+ setSelectedProduct = _useGlobalSelectedPro.setSelectedProduct;
97
311
  var _useState3 = useState(false),
98
312
  _useState4 = _slicedToArray(_useState3, 2),
99
313
  error = _useState4[0],
@@ -102,120 +316,206 @@ function useProductData(_ref) {
102
316
  _useState6 = _slicedToArray(_useState5, 2),
103
317
  loading = _useState6[0],
104
318
  setLoading = _useState6[1];
319
+ var normalizedProductIds = useMemo(function () {
320
+ if (!productIds) return [];
321
+ return Array.isArray(productIds) ? productIds : [productIds];
322
+ }, [productIds]);
323
+ var fetchProducts = useCallback(/*#__PURE__*/function () {
324
+ var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2(ids) {
325
+ var productPromises, results, flattenedResults;
326
+ return _regeneratorRuntime.wrap(function _callee2$(_context2) {
327
+ while (1) switch (_context2.prev = _context2.next) {
328
+ case 0:
329
+ LOG.debug("[".concat(widgetType, ":").concat(widgetId, "] fetchProducts called with ids: ").concat(ids.join(', ')));
330
+ if (!variants) {
331
+ _context2.next = 4;
332
+ break;
333
+ }
334
+ LOG.debug("[".concat(widgetType, ":").concat(widgetId, "] Using provided variants instead of fetching"));
335
+ return _context2.abrupt("return", variants);
336
+ case 4:
337
+ // Create promises for all products (cached or to be fetched) to preserve order
338
+ productPromises = ids.map(/*#__PURE__*/function () {
339
+ var _ref3 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(id) {
340
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
341
+ while (1) switch (_context.prev = _context.next) {
342
+ case 0:
343
+ LOG.debug("[".concat(widgetType, ":").concat(widgetId, "] Fetching product variants for id: ").concat(id));
344
+ return _context.abrupt("return", getOrFetch(id, function () {
345
+ return handler.productHandler.apiGetProductVariants(id, true);
346
+ }));
347
+ case 2:
348
+ case "end":
349
+ return _context.stop();
350
+ }
351
+ }, _callee);
352
+ }));
353
+ return function (_x2) {
354
+ return _ref3.apply(this, arguments);
355
+ };
356
+ }());
357
+ _context2.next = 7;
358
+ return Promise.all(productPromises);
359
+ case 7:
360
+ results = _context2.sent;
361
+ flattenedResults = results.flat();
362
+ LOG.debug("[".concat(widgetType, ":").concat(widgetId, "] Fetched ").concat(flattenedResults.length, " total product variants"));
363
+ return _context2.abrupt("return", flattenedResults);
364
+ case 11:
365
+ case "end":
366
+ return _context2.stop();
367
+ }
368
+ }, _callee2);
369
+ }));
370
+ return function (_x) {
371
+ return _ref2.apply(this, arguments);
372
+ };
373
+ }(), [handler.productHandler, getOrFetch, variants, widgetId, widgetType]);
374
+
375
+ // Main effect for loading products when productIds or variants change
105
376
  useEffect(function () {
377
+ LOG.debug("[".concat(widgetType, ":").concat(widgetId, "] Main effect triggered with normalizedProductIds: ").concat(normalizedProductIds.join(', ')));
378
+ if (!normalizedProductIds.length) {
379
+ LOG.error(new DivaError("[".concat(widgetType, ":").concat(widgetId, "] ProductIds array is required and cannot be empty")));
380
+ setError(true);
381
+ setLoading(false);
382
+ setProductVariants([]);
383
+ return;
384
+ }
106
385
  setError(false);
107
386
  setLoading(true);
108
- if (!(productIds !== null && productIds !== void 0 && productIds.length)) {
109
- LOG.error(new DivaError('productIds array is required and cannot be empty'));
387
+ fetchProducts(normalizedProductIds).then(function (variants) {
388
+ LOG.debug("[".concat(widgetType, ":").concat(widgetId, "] Successfully loaded ").concat(variants.length, " product variants"));
389
+ setProductVariants(variants);
390
+ setLoading(false);
391
+ })["catch"](function (error) {
392
+ LOG.error(new DivaError("[".concat(widgetType, ":").concat(widgetId, "] Error loading products"), {
393
+ cause: error
394
+ }));
110
395
  setError(true);
111
396
  setLoading(false);
397
+ setProductVariants([]);
398
+ });
399
+ }, [normalizedProductIds]);
400
+
401
+ // Effect for handling selectedProduct changes
402
+ useEffect(function () {
403
+ // If a product is selected that is not in the productVariants list,
404
+ // fetch the variants for that product and append them to the productVariants list
405
+ if (!selectedProduct || productVariants.find(function (p) {
406
+ return p._id === selectedProduct._id;
407
+ })) {
112
408
  return;
113
409
  }
114
- (function () {
115
- var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(ids) {
116
- var newVariants, productsPromises, productsResults, allProducts, _iterator, _step, _loop;
117
- return _regeneratorRuntime.wrap(function _callee$(_context2) {
118
- while (1) switch (_context2.prev = _context2.next) {
119
- case 0:
120
- _context2.prev = 0;
121
- if (!variants) {
122
- _context2.next = 6;
123
- break;
124
- }
125
- setProductVariants(variants);
126
- newVariants = variants;
127
- _context2.next = 13;
410
+ LOG.debug("[".concat(widgetType, ":").concat(widgetId, "] Selected product ").concat(selectedProduct._id, " not in current variants, fetching variants for it"));
411
+ setLoading(true);
412
+ setError(false);
413
+ fetchProducts([selectedProduct._id]).then(function (variants) {
414
+ setProductVariants(function (old) {
415
+ // Prevent duplicate products
416
+ var existingIds = new Set(old.map(function (p) {
417
+ return p._id;
418
+ }));
419
+ var newVariants = variants.filter(function (v) {
420
+ return !existingIds.has(v._id);
421
+ });
422
+ var updatedVariants = newVariants.length > 0 ? [].concat(_toConsumableArray(old), _toConsumableArray(newVariants)) : old;
423
+ LOG.debug("[".concat(widgetType, ":").concat(widgetId, "] Added ").concat(newVariants.length, " new variants to productVariants, total: ").concat(updatedVariants.length));
424
+ return updatedVariants;
425
+ });
426
+ setLoading(false);
427
+ })["catch"](function (error) {
428
+ LOG.error(new DivaError("[".concat(widgetType, ":").concat(widgetId, "] Error loading product"), {
429
+ cause: error
430
+ }));
431
+ setError(true);
432
+ setLoading(false);
433
+ });
434
+ }, [selectedProduct, productVariants, fetchProducts, widgetId, widgetType]);
435
+ var setSelectedProductId = useCallback(/*#__PURE__*/function () {
436
+ var _ref4 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee3(productId) {
437
+ var product, _variants, _product;
438
+ return _regeneratorRuntime.wrap(function _callee3$(_context3) {
439
+ while (1) switch (_context3.prev = _context3.next) {
440
+ case 0:
441
+ LOG.debug("[".concat(widgetType, ":").concat(widgetId, "] setSelectedProductId called with productId: ").concat(productId));
442
+
443
+ // If a product is selected that is not in the productVariants list,
444
+ // fetch the variants for that product and append them to the productVariants list
445
+ product = productVariants.find(function (p) {
446
+ return p._id === productId;
447
+ });
448
+ if (!product) {
449
+ _context3.next = 6;
128
450
  break;
129
- case 6:
130
- // Fetch all products in parallel
131
- productsPromises = ids.map(function (id) {
132
- return handler.productHandler.apiGetProductVariants(id, true);
133
- });
134
- _context2.next = 9;
135
- return Promise.all(productsPromises);
136
- case 9:
137
- productsResults = _context2.sent;
138
- // Flatten the array of arrays into a single array of variants
139
- allProducts = productsResults.flat();
140
- setProductVariants(allProducts);
141
- newVariants = allProducts;
142
- case 13:
143
- // Log business events for loaded products
144
- _iterator = _createForOfIteratorHelper(newVariants);
145
- _context2.prev = 14;
146
- _loop = /*#__PURE__*/_regeneratorRuntime.mark(function _loop() {
147
- var product, matchingProductId;
148
- return _regeneratorRuntime.wrap(function _loop$(_context) {
149
- while (1) switch (_context.prev = _context.next) {
150
- case 0:
151
- product = _step.value;
152
- matchingProductId = ids.find(function (id) {
153
- return product._id === id;
154
- });
155
- if (matchingProductId) {
156
- LOG.businessEvent('onLoadProductPDP', 'ProductDetailPage', 'Neues Product in PDP geladen.', {
157
- productId: matchingProductId,
158
- catalogName: product.modelName,
159
- catalogCodex: product.catCodex
160
- });
161
- }
162
- case 3:
163
- case "end":
164
- return _context.stop();
165
- }
166
- }, _loop);
451
+ }
452
+ LOG.debug("[".concat(widgetType, ":").concat(widgetId, "] Product ").concat(productId, " found in current variants, setting as selected"));
453
+ setSelectedProduct(product);
454
+ return _context3.abrupt("return");
455
+ case 6:
456
+ LOG.debug("[".concat(widgetType, ":").concat(widgetId, "] Product ").concat(productId, " not found in current variants, fetching variants for it"));
457
+ _context3.prev = 7;
458
+ setError(false);
459
+ setLoading(true);
460
+ _context3.next = 12;
461
+ return fetchProducts([productId]);
462
+ case 12:
463
+ _variants = _context3.sent;
464
+ setProductVariants(function (old) {
465
+ // Prevent duplicate products
466
+ var existingIds = new Set(old.map(function (p) {
467
+ return p._id;
468
+ }));
469
+ var newVariants = _variants.filter(function (v) {
470
+ return !existingIds.has(v._id);
167
471
  });
168
- _iterator.s();
169
- case 17:
170
- if ((_step = _iterator.n()).done) {
171
- _context2.next = 21;
172
- break;
173
- }
174
- return _context2.delegateYield(_loop(), "t0", 19);
175
- case 19:
176
- _context2.next = 17;
177
- break;
178
- case 21:
179
- _context2.next = 26;
180
- break;
181
- case 23:
182
- _context2.prev = 23;
183
- _context2.t1 = _context2["catch"](14);
184
- _iterator.e(_context2.t1);
185
- case 26:
186
- _context2.prev = 26;
187
- _iterator.f();
188
- return _context2.finish(26);
189
- case 29:
190
- onProductLoaded === null || onProductLoaded === void 0 || onProductLoaded(newVariants);
191
- _context2.next = 36;
472
+ var updatedVariants = [].concat(_toConsumableArray(old), _toConsumableArray(newVariants));
473
+ LOG.debug("[".concat(widgetType, ":").concat(widgetId, "] Added ").concat(newVariants.length, " new variants to productVariants, total: ").concat(updatedVariants.length));
474
+ return updatedVariants;
475
+ });
476
+ _product = _variants.find(function (p) {
477
+ return p._id === productId;
478
+ });
479
+ if (!_product) {
480
+ _context3.next = 20;
192
481
  break;
193
- case 32:
194
- _context2.prev = 32;
195
- _context2.t2 = _context2["catch"](0);
196
- LOG.error(new DivaError('Error loading products', {
197
- cause: _context2.t2
198
- }));
199
- setError(true);
200
- case 36:
201
- _context2.prev = 36;
202
- setLoading(false);
203
- return _context2.finish(36);
204
- case 39:
205
- case "end":
206
- return _context2.stop();
207
- }
208
- }, _callee, null, [[0, 32, 36, 39], [14, 23, 26, 29]]);
209
- }));
210
- return function (_x) {
211
- return _ref2.apply(this, arguments);
212
- };
213
- })()(productIds);
214
- }, [variants, productIds]);
482
+ }
483
+ LOG.debug("[".concat(widgetType, ":").concat(widgetId, "] Setting product ").concat(productId, " as selected after fetching"));
484
+ setSelectedProduct(_product);
485
+ _context3.next = 21;
486
+ break;
487
+ case 20:
488
+ throw new DivaError("[".concat(widgetType, ":").concat(widgetId, "] Product ").concat(productId, " not found"));
489
+ case 21:
490
+ _context3.next = 27;
491
+ break;
492
+ case 23:
493
+ _context3.prev = 23;
494
+ _context3.t0 = _context3["catch"](7);
495
+ LOG.error(new DivaError("[".concat(widgetType, ":").concat(widgetId, "] Error loading product"), {
496
+ cause: _context3.t0
497
+ }));
498
+ setError(true);
499
+ case 27:
500
+ _context3.prev = 27;
501
+ setLoading(false);
502
+ return _context3.finish(27);
503
+ case 30:
504
+ case "end":
505
+ return _context3.stop();
506
+ }
507
+ }, _callee3, null, [[7, 23, 27, 30]]);
508
+ }));
509
+ return function (_x3) {
510
+ return _ref4.apply(this, arguments);
511
+ };
512
+ }(), [productVariants, fetchProducts, setSelectedProduct, widgetId, widgetType]);
215
513
  return {
216
514
  productVariants: productVariants,
217
515
  error: error,
218
- loading: loading
516
+ loading: loading,
517
+ selectedProduct: selectedProduct,
518
+ setSelectedProductId: setSelectedProductId
219
519
  };
220
520
  }
221
521
 
@@ -596,7 +896,7 @@ var css_248z$1 = ".gallery-XR09O .gallery-thumb .swiper-slide {\n opacity: 0.4;
596
896
  styleInject(css_248z$1);
597
897
 
598
898
  var galleryAdditional = "gallery-additional-oIgEY";
599
- var css_248z = ".gallery-additional-oIgEY.diva-widget-gallery {\n width: 100%;\n height: 100%;\n --swiper-navigation-color: #bbb;\n --swiper-navigation-sides-offset: 4px;\n}\n\n.gallery-additional-oIgEY .gallery-main {\n height: 85%;\n}\n\n.gallery-additional-oIgEY .gallery-thumb {\n height: 15%;\n}\n\n.gallery-additional-oIgEY .gallery-image,\n.gallery-additional-oIgEY .gallery-image-thumb {\n width: 100%;\n height: 100%;\n -o-object-fit: scale-down;\n object-fit: scale-down;\n}\n\n.gallery-additional-oIgEY .gallery-image-thumb {\n cursor: pointer;\n}\n\n.gallery-additional-oIgEY .mobile .gallery-main {\n height: 100%;\n}\n\n.gallery-additional-oIgEY .mobile .gallery-thumb {\n display: none;\n}\n\n.gallery-additional-oIgEY .gallery-page-number {\n position: absolute;\n bottom: 16px;\n left: 16px;\n border-radius: 15px;\n z-index: 1000;\n padding: 0 16px;\n}\n";
899
+ var css_248z = ".gallery-additional-oIgEY.diva-widget-gallery {\n width: 100%;\n height: 100%;\n --swiper-navigation-color: #bbb;\n --swiper-navigation-sides-offset: 4px;\n}\n\n.gallery-additional-oIgEY .gallery-main {\n height: 85%;\n}\n\n.gallery-additional-oIgEY .gallery-thumb {\n height: 15%;\n}\n\n.gallery-additional-oIgEY .diva-widget-mediaitem,\n.gallery-additional-oIgEY .gallery-image-thumb {\n width: 100%;\n height: 100%;\n -o-object-fit: scale-down;\n object-fit: scale-down;\n}\n\n.gallery-additional-oIgEY .gallery-image-thumb {\n cursor: pointer;\n}\n\n.gallery-additional-oIgEY .mobile .gallery-main {\n height: 100%;\n}\n\n.gallery-additional-oIgEY .mobile .gallery-thumb {\n display: none;\n}\n\n.gallery-additional-oIgEY .gallery-page-number {\n position: absolute;\n bottom: 16px;\n left: 16px;\n border-radius: 15px;\n z-index: 1000;\n padding: 0 16px;\n}\n";
600
900
  styleInject(css_248z);
601
901
 
602
902
  function ownKeys$1(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
@@ -0,0 +1,62 @@
1
+ interface CacheEntry<T> {
2
+ /**
3
+ * The actual data after the request is resolved
4
+ */
5
+ data: T[] | null;
6
+ /**
7
+ * The promise for the request
8
+ */
9
+ promise: Promise<T[]> | null;
10
+ /**
11
+ * The error if the request failed
12
+ */
13
+ error: Error | null;
14
+ /**
15
+ * Timestamp when the entry was last updated
16
+ */
17
+ timestamp: number;
18
+ }
19
+ interface DivaWidgetCache {
20
+ /**
21
+ * Get data from cache or fetch if not available
22
+ * Implements request deduplication - multiple components requesting the same data
23
+ * will share the same promise
24
+ * @param key - Cache key (product ID)
25
+ * @param fetcher - Function to fetch data
26
+ * @returns Promise that resolves to the data
27
+ */
28
+ getOrFetch: <T>(key: string, fetcher: () => Promise<T[]>) => Promise<T[]>;
29
+ /**
30
+ * Clear a specific cache entry
31
+ * @param key - Cache key to clear
32
+ */
33
+ clear: (key: string) => void;
34
+ /**
35
+ * Clear all cache entries
36
+ */
37
+ clearAll: () => void;
38
+ }
39
+ interface UseGlobalCacheProps {
40
+ /**
41
+ * Unique widget instance ID, used for events and logging
42
+ */
43
+ uniqueWidgetId: string;
44
+ /**
45
+ * Widget type, used for events and logging
46
+ */
47
+ widgetType: string;
48
+ }
49
+ declare global {
50
+ interface Window {
51
+ __divaWidgetCache: {
52
+ cache: Map<string, CacheEntry<any>>;
53
+ };
54
+ }
55
+ }
56
+ /**
57
+ * Global cache for shared data between widgets of a website
58
+ * Simple implementation with request deduplication
59
+ */
60
+ export declare function useGlobalCache({ uniqueWidgetId, widgetType }: UseGlobalCacheProps): DivaWidgetCache;
61
+ export {};
62
+ //# sourceMappingURL=useGlobalCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGlobalCache.d.ts","sourceRoot":"","sources":["../../../../../src/hooks/useGlobalCache.ts"],"names":[],"mappings":"AAKA,UAAU,UAAU,CAAC,CAAC;IACpB;;OAEG;IACH,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC;IACjB;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;IAC7B;;OAEG;IACH,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,eAAe;IACvB;;;;;;;OAOG;IACH,UAAU,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAC1E;;;OAGG;IACH,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B;;OAEG;IACH,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,UAAU,mBAAmB;IAC3B;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAGD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,iBAAiB,EAAE;YACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;SACrC,CAAC;KACH;CACF;AASD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,cAAc,EAAE,UAAU,EAAE,EAAE,mBAAmB,GAAG,eAAe,CAwFnG"}
@@ -0,0 +1,29 @@
1
+ import { ProductData } from '@crystaldesign/diva-core';
2
+ interface UseGlobalSelectedProductProps {
3
+ product?: ProductData;
4
+ /**
5
+ * Unique widget instance ID, used for events and logging
6
+ */
7
+ uniqueWidgetId: string;
8
+ /**
9
+ * Widget type, used for events and logging
10
+ */
11
+ widgetType: string;
12
+ }
13
+ interface UseGlobalSelectedProductReturn {
14
+ selectedProduct: ProductData | undefined;
15
+ setSelectedProduct: (product: ProductData) => void;
16
+ }
17
+ /**
18
+ * Hook to get the globally selected product
19
+ *
20
+ * Initially and when the product changes, it gets selected.
21
+ * When ever the selected product is changed, an event is dispatched to notify other widgets, so they can also set the new product as selected.
22
+ * The hook also listens for product selection events from other widgets and updates the selected product accordingly.
23
+ *
24
+ * @param product - The product to select initially
25
+ * @returns The selected product and a function to set the selected product
26
+ */
27
+ export declare function useGlobalSelectedProduct({ product, uniqueWidgetId, widgetType }: UseGlobalSelectedProductProps): UseGlobalSelectedProductReturn;
28
+ export {};
29
+ //# sourceMappingURL=useGlobalSelectedProduct.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGlobalSelectedProduct.d.ts","sourceRoot":"","sources":["../../../../../src/hooks/useGlobalSelectedProduct.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIlE,UAAU,6BAA6B;IACrC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,8BAA8B;IACtC,eAAe,EAAE,WAAW,GAAG,SAAS,CAAC;IACzC,kBAAkB,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CACpD;AAYD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,EAAE,6BAA6B,GAAG,8BAA8B,CA6F/I"}
@@ -1,12 +1,17 @@
1
1
  import { ProductData } from '@crystaldesign/diva-core';
2
2
  export interface UseProductDataProps {
3
- productIds?: string[];
3
+ productIds?: string[] | string;
4
4
  variants?: ProductData[];
5
- onProductLoaded?: (variants: ProductData[]) => void;
5
+ /**
6
+ * Widget type, used for logging
7
+ */
8
+ widgetType: string;
6
9
  }
7
- export declare function useProductData({ productIds, variants, onProductLoaded }: UseProductDataProps): {
10
+ export declare function useProductData({ productIds, variants, widgetType }: UseProductDataProps): {
8
11
  productVariants: ProductData[];
9
12
  error: boolean;
10
13
  loading: boolean;
14
+ selectedProduct: ProductData | undefined;
15
+ setSelectedProductId: (productId: string) => Promise<void>;
11
16
  };
12
17
  //# sourceMappingURL=useProductData.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useProductData.d.ts","sourceRoot":"","sources":["../../../../../src/hooks/useProductData.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,WAAW,EAA0B,MAAM,0BAA0B,CAAC;AAI1F,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;CACrD;AAED,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,mBAAmB;;;;EAyD5F"}
1
+ {"version":3,"file":"useProductData.d.ts","sourceRoot":"","sources":["../../../../../src/hooks/useProductData.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,WAAW,EAA0B,MAAM,0BAA0B,CAAC;AAM1F,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAKD,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,mBAAmB;;;;;sCAoGlE,MAAM;EA6C3B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crystaldesign/widget-library",
3
- "version": "25.8.0-beta.5",
3
+ "version": "25.8.0-beta.7",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "storybook": "storybook dev -p 6006",
@@ -36,5 +36,5 @@
36
36
  "suiteName": "widget-library",
37
37
  "outputDirectory": "./test-reports"
38
38
  },
39
- "gitHead": "76b96b4a0137b36b1f7c4cf20ef39045040a2491"
39
+ "gitHead": "d160f39c475acc47e32f9641b14a25de2c27cfd4"
40
40
  }