@inappstory/slide-api 0.1.25 → 0.1.27

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 CHANGED
@@ -1045,6 +1045,12 @@ class EsModuleSdkApi {
1045
1045
  constructor(sdkBinding) {
1046
1046
  this.sdkBinding = sdkBinding;
1047
1047
  }
1048
+ onWidgetRequirePauseUI() {
1049
+ return this.sdkBinding.onWidgetRequirePauseUI();
1050
+ }
1051
+ onWidgetRequireResumeUI() {
1052
+ return this.sdkBinding.onWidgetRequireResumeUI();
1053
+ }
1048
1054
  getCardServerData(cardId) {
1049
1055
  return this.sdkBinding.getCardServerData(cardId);
1050
1056
  }
@@ -1135,8 +1141,11 @@ class EsModuleSdkApi {
1135
1141
  getCardSessionValue(element, key) {
1136
1142
  return this.sdkBinding.getCardSessionValue(key);
1137
1143
  }
1138
- updateTimeline(slideIndex, action, currentTime, duration, showLoader, showError) {
1139
- this.sdkBinding.updateTimeline(slideIndex, action, currentTime, duration, showLoader, showError);
1144
+ isSdkSupportUpdateTimeline() {
1145
+ return true;
1146
+ }
1147
+ async updateTimeline(slideIndex, action, currentTime, duration, showLoader, showError) {
1148
+ await this.sdkBinding.updateTimeline(slideIndex, action, currentTime, duration, showLoader, showError);
1140
1149
  }
1141
1150
  cardPausedCallback(currentTime) { }
1142
1151
  cardResumedCallback(currentTime) { }
@@ -1179,12 +1188,87 @@ class EsModuleSdkApi {
1179
1188
  this.sdkBinding.onEvent(name, event);
1180
1189
  }
1181
1190
  onCardLoadingStateChange(state, reason) {
1182
- console.log("onCardLoadingStateChange", { state });
1191
+ // console.log("onCardLoadingStateChange", { state });
1183
1192
  this.sdkBinding.onCardLoadingStateChange(state, reason);
1184
1193
  }
1194
+ get isSdkSupportProductCart() {
1195
+ return true;
1196
+ }
1197
+ productCartUpdate(data) {
1198
+ return this.sdkBinding.productCartUpdate(data);
1199
+ }
1200
+ productCartGetState() {
1201
+ return this.sdkBinding.productCartGetState();
1202
+ }
1203
+ productCartClicked() {
1204
+ this.sdkBinding.productCartClicked();
1205
+ }
1185
1206
  static get [Symbol.for("___CTOR_ARGS___")]() { return [`SDKInterface`]; }
1186
1207
  }
1187
1208
 
1209
+ class CardLoadingStateController {
1210
+ onCardLoadingStateChange;
1211
+ constructor(onCardLoadingStateChange) {
1212
+ this.onCardLoadingStateChange = onCardLoadingStateChange;
1213
+ }
1214
+ timeout = 300;
1215
+ onSetLoadStartState() {
1216
+ this.onBeforeStateChanged();
1217
+ // prevent micro loaders in UI
1218
+ if (this.currentState === 1 /* CARD_LOADING_STATE.LOADED */) {
1219
+ this.deferredDataWaitingStateTimerId = window.setTimeout(() => {
1220
+ this.onCardLoadingStateChange(0 /* CARD_LOADING_STATE.LOADING */);
1221
+ this.dataWaitingStartedAt = Date.now();
1222
+ }, this.timeout);
1223
+ }
1224
+ else {
1225
+ this.onCardLoadingStateChange(0 /* CARD_LOADING_STATE.LOADING */);
1226
+ this.dataWaitingStartedAt = Date.now();
1227
+ }
1228
+ this.currentState = 0 /* CARD_LOADING_STATE.LOADING */;
1229
+ }
1230
+ onSetLoadEndState() {
1231
+ this.onBeforeStateChanged();
1232
+ // prevent micro loaders in UI
1233
+ const delay = this.timeout;
1234
+ let dataWaitingLoaderSpent = delay;
1235
+ if (this.dataWaitingStartedAt != null) {
1236
+ dataWaitingLoaderSpent = Date.now() - this.dataWaitingStartedAt;
1237
+ }
1238
+ if (dataWaitingLoaderSpent < delay) {
1239
+ this.deferredResumeStateTimerId = window.setTimeout(() => this.onCardLoadingStateChange(1 /* CARD_LOADING_STATE.LOADED */), dataWaitingLoaderSpent - delay);
1240
+ }
1241
+ else {
1242
+ this.onCardLoadingStateChange(1 /* CARD_LOADING_STATE.LOADED */);
1243
+ }
1244
+ this.currentState = 1 /* CARD_LOADING_STATE.LOADED */;
1245
+ }
1246
+ onSetLoadErrorState(reason) {
1247
+ this.onBeforeStateChanged();
1248
+ this.onCardLoadingStateChange(2 /* CARD_LOADING_STATE.LOADING_ERROR */, reason);
1249
+ this.currentState = 2 /* CARD_LOADING_STATE.LOADING_ERROR */;
1250
+ }
1251
+ currentState = 0 /* CARD_LOADING_STATE.LOADING */;
1252
+ deferredDataWaitingStateTimerId = null;
1253
+ deferredResumeStateTimerId = null;
1254
+ dataWaitingStartedAt = null;
1255
+ clearDeferredDataWaitingStateTimerId() {
1256
+ if (this.deferredDataWaitingStateTimerId != null) {
1257
+ window.clearTimeout(this.deferredDataWaitingStateTimerId);
1258
+ }
1259
+ }
1260
+ clearDeferredResumeStateTimerId() {
1261
+ if (this.deferredResumeStateTimerId != null) {
1262
+ window.clearTimeout(this.deferredResumeStateTimerId);
1263
+ }
1264
+ }
1265
+ onBeforeStateChanged() {
1266
+ this.clearDeferredDataWaitingStateTimerId();
1267
+ this.clearDeferredResumeStateTimerId();
1268
+ }
1269
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`(state: CARD_LOADING_STATE, reason?: string) => void`]; }
1270
+ }
1271
+
1188
1272
  const DEFAULT_SLIDE_DURATION = 10000;
1189
1273
 
1190
1274
  class DataInput {
@@ -3303,19 +3387,7 @@ class SlideTimeline {
3303
3387
  return SlideTimeline.layoutService;
3304
3388
  }
3305
3389
  get isSDKSupportUpdateTimeline() {
3306
- if (this.slideApiDeps.isAndroid) {
3307
- return Boolean(SlideTimeline.layoutService.env.Android && "updateTimeline" in SlideTimeline.layoutService.env.Android);
3308
- }
3309
- else if (this.slideApiDeps.isIOS) {
3310
- const mh = SlideTimeline.layoutService.env?.webkit?.messageHandlers ?? {};
3311
- return "updateTimeline" in mh;
3312
- }
3313
- else if (this.slideApiDeps.isWeb) {
3314
- return true;
3315
- }
3316
- else {
3317
- return false;
3318
- }
3390
+ return this.slideApiDeps.isSdkSupportUpdateTimeline();
3319
3391
  }
3320
3392
  get isSdkSupportTimelineOnBeforeStart() {
3321
3393
  return this.slideApiDeps.isSdkSupportTimelineOnBeforeStart();
@@ -3359,7 +3431,7 @@ class SlideTimeline {
3359
3431
  await this.slideReady.then();
3360
3432
  // window._log(`updateTimeline, a: ${action} ct: ${currentTime} d: ${duration} tds: ${this.timelineDisabledState}`, true);
3361
3433
  // console.trace(`updateTimeline ${action} slideIndex: ${this.slideIndex} currentTime:${currentTime} duration:${duration} tds: ${this.timelineDisabledState} showLoader: ${showLoader} showError: ${showError}`);
3362
- this.slideApiDeps.updateTimeline(this.slideIndex, action, currentTime, duration, showLoader, showError);
3434
+ await this.slideApiDeps.updateTimeline(this.slideIndex, action, currentTime, duration, showLoader, showError);
3363
3435
  if (action === "pause" /* TIMELINE_ACTION.PAUSE */ && showLoader) {
3364
3436
  this.dataWaitingStartedAt = Date.now();
3365
3437
  }
@@ -3385,13 +3457,13 @@ class SlideTimeline {
3385
3457
  * Start timeline after slide started
3386
3458
  * Nothing do if old sdk
3387
3459
  */
3388
- slideStarted() {
3460
+ async slideStarted() {
3389
3461
  // console.trace("slideStarted");
3390
3462
  this.onBeforeStateChanged();
3391
3463
  if (this.isSDKSupportUpdateTimeline) {
3392
3464
  this.resumedAt = new Date().getTime();
3393
3465
  this.timeSpent = 0; // for case when instance exists, but we return to slide again
3394
- this.updateTimeline("start" /* TIMELINE_ACTION.START */);
3466
+ await this.updateTimeline("start" /* TIMELINE_ACTION.START */);
3395
3467
  }
3396
3468
  }
3397
3469
  slideRestarted() {
@@ -3460,6 +3532,7 @@ class SlideTimeline {
3460
3532
  this.onBeforeStateChanged();
3461
3533
  if (this.isSDKSupportUpdateTimeline) {
3462
3534
  this.updateTimeline("stop" /* TIMELINE_ACTION.STOP */);
3535
+ // console.log(`TIMELINE_ACTION -> slideStopped, index: ${this.slideIndex}`);
3463
3536
  }
3464
3537
  }
3465
3538
  /**
@@ -3596,10 +3669,20 @@ class Layer {
3596
3669
  });
3597
3670
  };
3598
3671
  const onWidgetRequirePauseUI = (cardId, slideIndex) => {
3599
- this._slidePauseUI();
3672
+ if (this.slideApiDeps.isWeb) {
3673
+ this.slideApiDeps.onWidgetRequirePauseUI();
3674
+ }
3675
+ else {
3676
+ this._slidePauseUI();
3677
+ }
3600
3678
  };
3601
3679
  const onWidgetRequireResumeUI = (cardId, slideIndex) => {
3602
- this._slideResumeUI();
3680
+ if (this.slideApiDeps.isWeb) {
3681
+ this.slideApiDeps.onWidgetRequireResumeUI();
3682
+ }
3683
+ else {
3684
+ this._slideResumeUI();
3685
+ }
3603
3686
  };
3604
3687
  const _elementsNodes = this._nodeRef.querySelectorAll(".narrative-slide-elements .narrative-element:not(.narrative-element-child-element)");
3605
3688
  let layerWithWidgetQuest = false;
@@ -3838,7 +3921,7 @@ class Layer {
3838
3921
  }
3839
3922
  // skip start timeline if we returned to slide with enabled timer and opened WidgetProducts modal
3840
3923
  if (!this.isLayerForcePaused) {
3841
- this.timeline.slideStarted();
3924
+ await this.timeline.slideStarted();
3842
3925
  }
3843
3926
  return { currentTime };
3844
3927
  }
@@ -4691,7 +4774,9 @@ let SlideApi$1 = class SlideApi {
4691
4774
  this._state = 5 /* STATE.RESUMED */;
4692
4775
  }
4693
4776
  async slideStop(options) {
4694
- if (!(this._state === 3 /* STATE.STARTED */ || this._state === 5 /* STATE.RESUMED */ || this._state === 9 /* STATE.USER_PAUSED */)) {
4777
+ // console.log(`TIMELINE_ACTION -> STOP, index: ${this.slide.slideIndex}, state: ${this._state}`);
4778
+ // из состояния START (слайд (например с видео) еще не стартовал) тоже позволяем прерывать - чтобы был правильный LC при быстром пролистывании слайдера
4779
+ if (!(this._state === 2 /* STATE.START */ || this._state === 3 /* STATE.STARTED */ || this._state === 5 /* STATE.RESUMED */ || this._state === 9 /* STATE.USER_PAUSED */)) {
4695
4780
  return;
4696
4781
  }
4697
4782
  this._state = 11 /* STATE.STOP */;
@@ -5431,6 +5516,12 @@ class SlideApiDepsMultiSlideMode {
5431
5516
  this.sdkApi = sdkApi;
5432
5517
  this.slider = slider;
5433
5518
  }
5519
+ onWidgetRequirePauseUI() {
5520
+ return this.sdkApi.onWidgetRequirePauseUI();
5521
+ }
5522
+ onWidgetRequireResumeUI() {
5523
+ return this.sdkApi.onWidgetRequireResumeUI();
5524
+ }
5434
5525
  getWidgetsSharedData(cardId, widget) {
5435
5526
  return this.sdkApi.getWidgetsSharedData(cardId, widget);
5436
5527
  }
@@ -5530,7 +5621,9 @@ class SlideApiDepsMultiSlideMode {
5530
5621
  return this.sdkApi.isExistsShowNextCard;
5531
5622
  }
5532
5623
  cardShowNext() {
5533
- this.sdkApi.cardShowNext();
5624
+ return;
5625
+ // skip action in multislide mode (business logic)
5626
+ // this.sdkApi.cardShowNext();
5534
5627
  }
5535
5628
  /** @deprecated, used only in native sdk **/
5536
5629
  cardPausedCallback(currentTime) {
@@ -5552,6 +5645,18 @@ class SlideApiDepsMultiSlideMode {
5552
5645
  showLayer(index) {
5553
5646
  this.sdkApi.showLayer(index);
5554
5647
  }
5648
+ get isSdkSupportProductCart() {
5649
+ return this.sdkApi.isSdkSupportProductCart;
5650
+ }
5651
+ productCartUpdate(data) {
5652
+ return this.sdkApi.productCartUpdate(data);
5653
+ }
5654
+ productCartGetState() {
5655
+ return this.sdkApi.productCartGetState();
5656
+ }
5657
+ productCartClicked() {
5658
+ this.sdkApi.productCartClicked();
5659
+ }
5555
5660
  /**
5556
5661
  * For single slide mode - proxy these methods via SDKApi
5557
5662
  * =================================================================================================================
@@ -5562,8 +5667,11 @@ class SlideApiDepsMultiSlideMode {
5562
5667
  showCardSlide(index) {
5563
5668
  this.slider.showByIndex(index);
5564
5669
  }
5565
- updateTimeline(slideIndex, action, currentTime, duration, showLoader, showError) {
5566
- this.slider.updateTimeline(slideIndex, action, currentTime, duration, showLoader, showError);
5670
+ isSdkSupportUpdateTimeline() {
5671
+ return true;
5672
+ }
5673
+ async updateTimeline(slideIndex, action, currentTime, duration, showLoader, showError) {
5674
+ await this.slider.updateTimeline(slideIndex, action, currentTime, duration, showLoader, showError);
5567
5675
  }
5568
5676
  isSdkSupportTimelineOnBeforeStart() {
5569
5677
  return true;
@@ -5582,6 +5690,12 @@ class SlideApiDepsSingleSlideMode {
5582
5690
  constructor(sdkApi) {
5583
5691
  this.sdkApi = sdkApi;
5584
5692
  }
5693
+ onWidgetRequirePauseUI() {
5694
+ return this.sdkApi.onWidgetRequirePauseUI();
5695
+ }
5696
+ onWidgetRequireResumeUI() {
5697
+ return this.sdkApi.onWidgetRequireResumeUI();
5698
+ }
5585
5699
  getWidgetsSharedData(cardId, widget) {
5586
5700
  return this.sdkApi.getWidgetsSharedData(cardId, widget);
5587
5701
  }
@@ -5703,6 +5817,18 @@ class SlideApiDepsSingleSlideMode {
5703
5817
  showLayer(index) {
5704
5818
  this.sdkApi.showLayer(index);
5705
5819
  }
5820
+ get isSdkSupportProductCart() {
5821
+ return this.sdkApi.isSdkSupportProductCart;
5822
+ }
5823
+ productCartUpdate(data) {
5824
+ return this.sdkApi.productCartUpdate(data);
5825
+ }
5826
+ productCartGetState() {
5827
+ return this.sdkApi.productCartGetState();
5828
+ }
5829
+ productCartClicked() {
5830
+ this.sdkApi.productCartClicked();
5831
+ }
5706
5832
  /**
5707
5833
  * For single slide mode - proxy these methods via SDKApi
5708
5834
  */
@@ -5712,8 +5838,11 @@ class SlideApiDepsSingleSlideMode {
5712
5838
  showCardSlide(index) {
5713
5839
  this.sdkApi.showCardSlide(index);
5714
5840
  }
5715
- updateTimeline(slideIndex, action, currentTime, duration, showLoader, showError) {
5716
- this.sdkApi.updateTimeline(slideIndex, action, currentTime, duration, showLoader, showError);
5841
+ isSdkSupportUpdateTimeline() {
5842
+ return this.sdkApi.isSdkSupportUpdateTimeline();
5843
+ }
5844
+ async updateTimeline(slideIndex, action, currentTime, duration, showLoader, showError) {
5845
+ await this.sdkApi.updateTimeline(slideIndex, action, currentTime, duration, showLoader, showError);
5717
5846
  }
5718
5847
  isSdkSupportTimelineOnBeforeStart() {
5719
5848
  return this.sdkApi.isSdkSupportTimelineOnBeforeStart();
@@ -5768,12 +5897,16 @@ class SlideTimer {
5768
5897
  this.timerId = null;
5769
5898
  }
5770
5899
  }
5771
- resume(duration = 0) {
5900
+ resume(currentTime = undefined, duration = 0) {
5772
5901
  this._pause = false;
5773
5902
  if (!this._started) {
5774
5903
  this.start(duration);
5775
5904
  }
5776
5905
  else {
5906
+ if (currentTime != null) {
5907
+ // timeLeft correction
5908
+ this.timeLeft = this.duration - currentTime;
5909
+ }
5777
5910
  this.tick();
5778
5911
  }
5779
5912
  }
@@ -5789,12 +5922,12 @@ class SlideTimer {
5789
5922
  }
5790
5923
  const dtDiff = new Date().getTime() - rafStartTime;
5791
5924
  this.timeLeft -= dtDiff;
5792
- let progress = Math.round(((this.duration - this.timeLeft) / this.duration) * 100 * 10000) / 10000;
5793
- if (progress > 100) {
5794
- progress = 100;
5925
+ let progressPercent = Math.round(((this.duration - this.timeLeft) / this.duration) * 100 * 10000) / 10000;
5926
+ if (progressPercent > 100) {
5927
+ progressPercent = 100;
5795
5928
  }
5796
- this.updateCb(progress);
5797
- if (progress < 100) {
5929
+ this.updateCb(progressPercent);
5930
+ if (progressPercent < 100) {
5798
5931
  if (this._pause !== true) {
5799
5932
  this.tick();
5800
5933
  }
@@ -5804,7 +5937,43 @@ class SlideTimer {
5804
5937
  }
5805
5938
  });
5806
5939
  }
5807
- static get [Symbol.for("___CTOR_ARGS___")]() { return [`(callback: () => void) => number`, `(timerId: number) => void`, `(progress: number) => void`, `() => void`]; }
5940
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`(callback: () => void) => number`, `(timerId: number) => void`, `(progressPercent: number) => void`, `() => void`]; }
5941
+ }
5942
+
5943
+ /**
5944
+ * usage TS interface with ctor
5945
+ * https://blog.logrocket.com/writing-constructor-typescript/
5946
+ *
5947
+ * @param ctor
5948
+ * @param slides
5949
+ * @param getLayoutDirection
5950
+ */
5951
+ function createTimelineController(ctor, slides, getLayoutDirection) {
5952
+ return new ctor(slides, getLayoutDirection);
5953
+ }
5954
+ class TimelineController {
5955
+ slides;
5956
+ getLayoutDirection;
5957
+ constructor(slides, getLayoutDirection) {
5958
+ this.slides = slides;
5959
+ this.getLayoutDirection = getLayoutDirection;
5960
+ }
5961
+ onSlideTimerUpdate(index, progressPercent) {
5962
+ this.slides[index]?.updateTimelineProgress(progressPercent, true);
5963
+ }
5964
+ onSlideBeforeStart(index) {
5965
+ // fill to 100% all previous slides
5966
+ for (let i = 0; i < index; ++i) {
5967
+ this.slides[i]?.updateTimelineProgress(100, false);
5968
+ }
5969
+ // fill to 0% all next slides (for user revert direction)
5970
+ for (let i = index + 1; i < this.slides.length; ++i) {
5971
+ this.slides[i]?.updateTimelineProgress(0, false);
5972
+ }
5973
+ // activate current slide
5974
+ this.slides[index]?.updateTimelineProgress(0, true);
5975
+ }
5976
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`Array`, `GetLayoutDirection`]; }
5808
5977
  }
5809
5978
 
5810
5979
  class Slider {
@@ -5826,15 +5995,19 @@ class Slider {
5826
5995
  slides: config.slides,
5827
5996
  nonce: config.nonce,
5828
5997
  slideRender: config.slideRender,
5998
+ navbarAppearance: config.navbarAppearance,
5829
5999
  });
5830
6000
  config.root.appendChild(slideWrapper);
5831
6001
  this.slideWrapperElement = slideWrapper;
6002
+ this.timelineController = createTimelineController(TimelineController, this.slides, this.config.getLayoutDirection.bind(this));
5832
6003
  // cardMounted: (card: HTMLElement) => Promise<void>
5833
6004
  requestAnimationFrame(async () => {
5834
6005
  await Promise.all(this.slides.map((slide, index) => config.onSlideMounted(slide.element, index)));
5835
6006
  });
5836
6007
  }
5837
6008
  isAnimating = false;
6009
+ // block click while slide loading and starting (prevent several timeline starting)
6010
+ isSlideStarting = false;
5838
6011
  activeIndex = -1;
5839
6012
  get activeSlide() {
5840
6013
  return this.slides[this.activeIndex];
@@ -5844,133 +6017,192 @@ class Slider {
5844
6017
  bulletsContainer = null;
5845
6018
  slidesFirstRenders = [];
5846
6019
  slideWrapperElement;
5847
- updateBulletActiveIndex;
5848
- updateTimelineProgress;
5849
- createSliderElement = ({ slides, nonce, slideRender }) => {
6020
+ timelineController;
6021
+ assembleNavbarCssVariables(navbarAppearance) {
6022
+ let css = "";
6023
+ if (navbarAppearance.edge_offset != null) {
6024
+ css += `--navbar-edge-offset: ${navbarAppearance.edge_offset};`;
6025
+ }
6026
+ if (navbarAppearance.active_timeline_width != null) {
6027
+ css += `--navbar-active-timeline-width: ${navbarAppearance.active_timeline_width};`;
6028
+ }
6029
+ if (navbarAppearance.background_color != null) {
6030
+ css += `--navbar-background-color: ${navbarAppearance.background_color};`;
6031
+ }
6032
+ if (navbarAppearance.border_radius != null) {
6033
+ css += `--navbar-border-radius: ${navbarAppearance.border_radius};`;
6034
+ }
6035
+ if (navbarAppearance.drop_shadow_blur != null) {
6036
+ css += `--navbar-drop-shadow-blur: ${navbarAppearance.drop_shadow_blur};`;
6037
+ }
6038
+ if (navbarAppearance.drop_shadow_color != null) {
6039
+ css += `--navbar-drop-shadow-color: ${navbarAppearance.drop_shadow_color};`;
6040
+ }
6041
+ if (navbarAppearance.drop_shadow_x_offset != null) {
6042
+ css += `--navbar-drop-shadow-x-offset: ${navbarAppearance.drop_shadow_x_offset};`;
6043
+ }
6044
+ if (navbarAppearance.drop_shadow_y_offset != null) {
6045
+ css += `--navbar-drop-shadow-y-offset: ${navbarAppearance.drop_shadow_y_offset};`;
6046
+ }
6047
+ if (navbarAppearance.timeline_background_color != null) {
6048
+ css += `--navbar-timeline-background-color: ${navbarAppearance.timeline_background_color};`;
6049
+ }
6050
+ if (navbarAppearance.timeline_fill_color != null) {
6051
+ css += `--navbar-timeline-fill-color: ${navbarAppearance.timeline_fill_color};`;
6052
+ }
6053
+ if (navbarAppearance.timeline_size != null) {
6054
+ css += `--navbar-timeline-size: ${navbarAppearance.timeline_size};`;
6055
+ }
6056
+ if (navbarAppearance.timelines_gap != null) {
6057
+ css += `--navbar-timelines-gap: ${navbarAppearance.timelines_gap};`;
6058
+ }
6059
+ if (navbarAppearance.vertical_padding != null) {
6060
+ css += `--navbar-vertical-padding: ${navbarAppearance.vertical_padding};`;
6061
+ }
6062
+ if (navbarAppearance.horizontal_padding != null) {
6063
+ css += `--navbar-horizontal-padding: ${navbarAppearance.horizontal_padding};`;
6064
+ }
6065
+ return `.cards-slider {${css}`;
6066
+ }
6067
+ createSliderElement = ({ slides, nonce, slideRender, navbarAppearance, }) => {
5850
6068
  const style = document.createElement("style");
5851
6069
  if (nonce != null) {
5852
6070
  style.nonce = nonce;
5853
6071
  }
6072
+ style.innerText = this.assembleNavbarCssVariables(navbarAppearance);
5854
6073
  const slider = document.createElement("div");
5855
6074
  slider.classList.add("cards-slider");
5856
6075
  const track = document.createElement("div");
5857
6076
  track.classList.add("cards-slider__track");
5858
6077
  this.sliderTrack = track;
5859
- for (let i = 0; i < slides.length; ++i) {
5860
- const slide = document.createElement("div");
5861
- slide.classList.add("cards-slider__slide");
5862
- slide.setAttribute("data-index", String(i));
5863
- this.slides[i] = {
5864
- element: slideRender(slides[i], i, this.slidesFirstRenders[i].firstRenderCallPromise),
5865
- timer: new SlideTimer(cb => window.requestAnimationFrame(cb), handle => window.cancelAnimationFrame(handle), this.onSlideTimerUpdate.bind(this), this.onSlideTimerEnd.bind(this)),
5866
- };
5867
- slide.appendChild(this.slides[i].element);
5868
- track.appendChild(slide);
5869
- }
5870
- const [bullets, updateBulletActiveIndex, updateTimelineProgress] = this.createBulletPoints(slides.length);
6078
+ const [bullets, updateTimelineProgress] = this.createBulletPoints(slides.length, this.config.getLayoutDirection.bind(this), navbarAppearance.position);
5871
6079
  this.bulletsContainer = bullets;
5872
- this.updateBulletActiveIndex = updateBulletActiveIndex.bind(this);
5873
- this.updateTimelineProgress = updateTimelineProgress.bind(this);
6080
+ this.slides = slides.map((slide, i) => ({
6081
+ element: slideRender(slide, i, this.slidesFirstRenders[i].firstRenderCallPromise),
6082
+ timer: new SlideTimer(cb => window.requestAnimationFrame(cb), handle => window.cancelAnimationFrame(handle), progressPercent => this.timelineController.onSlideTimerUpdate(i, progressPercent), this.onSlideTimerEnd.bind(this)),
6083
+ updateTimelineProgress: (progressPercent, isActive) => updateTimelineProgress(i, progressPercent, isActive),
6084
+ }));
6085
+ for (let i = 0; i < this.slides.length; ++i) {
6086
+ const slideElement = document.createElement("div");
6087
+ slideElement.classList.add("cards-slider__slide");
6088
+ slideElement.setAttribute("data-index", String(i));
6089
+ slideElement.appendChild(this.slides[i].element);
6090
+ track.appendChild(slideElement);
6091
+ }
5874
6092
  slider.appendChild(track);
5875
- slider.appendChild(bullets);
6093
+ const safeAreaView = document.createElement("div");
6094
+ safeAreaView.classList.add("safe-area-view", "inset-0", "non-touchable");
6095
+ const safeAreaRelativeZoneView = document.createElement("div");
6096
+ safeAreaRelativeZoneView.classList.add("safe-area-relative-zone-view");
6097
+ safeAreaRelativeZoneView.appendChild(bullets);
6098
+ safeAreaView.appendChild(safeAreaRelativeZoneView);
6099
+ slider.appendChild(safeAreaView);
5876
6100
  slider.appendChild(style);
5877
6101
  return slider;
5878
6102
  };
5879
- createBulletPoints(count) {
6103
+ createBulletPoints(count, getLayoutDirection, position) {
5880
6104
  const bullets = document.createElement("div");
5881
- bullets.classList.toggle("cards-slider__bullets");
5882
- for (let i = 0; i < count; ++i) {
5883
- const bullet = document.createElement("div");
5884
- bullet.classList.add("cards-slider__bullet");
5885
- bullet.setAttribute("data-index", String(i));
5886
- bullet.onclick = (e) => {
5887
- e.stopPropagation();
5888
- e.preventDefault();
5889
- const bullet = e.target;
5890
- if (bullet != null) {
5891
- const index = bullet.dataset.index;
5892
- if (index != null) {
5893
- this.showByIndex(parseInt(index));
6105
+ bullets.classList.add("cards-slider__navbar");
6106
+ if (position === 0) {
6107
+ bullets.classList.add("cards-slider__navbar--position-none");
6108
+ }
6109
+ else if (position === 1) {
6110
+ bullets.classList.add("cards-slider__navbar--position-top");
6111
+ }
6112
+ else if (position === 2) {
6113
+ bullets.classList.add("cards-slider__navbar--position-bottom");
6114
+ }
6115
+ else {
6116
+ // default
6117
+ bullets.classList.add("cards-slider__navbar--position-bottom");
6118
+ }
6119
+ bullets.dir = getLayoutDirection();
6120
+ // skip creating view elements for single slide
6121
+ if (count > 1) {
6122
+ for (let i = 0; i < count; ++i) {
6123
+ const bullet = document.createElement("div");
6124
+ bullet.classList.add("cards-slider__timeline", "touchable");
6125
+ bullet.setAttribute("data-index", String(i));
6126
+ bullet.onclick = (e) => {
6127
+ e.stopPropagation();
6128
+ e.preventDefault();
6129
+ const bullet = e.target;
6130
+ if (bullet != null) {
6131
+ const index = bullet.dataset.index;
6132
+ if (index != null) {
6133
+ this.showByIndex(parseInt(index));
6134
+ }
5894
6135
  }
5895
- }
5896
- };
5897
- const bulletFill = document.createElement("div");
5898
- bulletFill.classList.add("cards-slider__bullet-fill");
5899
- bullet.append(bulletFill);
5900
- bullets.appendChild(bullet);
5901
- }
5902
- const onUpdateActiveIndex = (activeIndex) => {
5903
- if (activeIndex >= 0 && activeIndex < count) {
5904
- for (const bullet of bullets.querySelectorAll(".cards-slider__bullet--active")) {
5905
- bullet.classList.toggle("cards-slider__bullet--active");
5906
- }
5907
- bullets.querySelector(`.cards-slider__bullet[data-index="${activeIndex}"]`)?.classList.toggle("cards-slider__bullet--active");
5908
- }
5909
- };
5910
- const onUpdateTimelineProgress = (index, progress) => {
6136
+ };
6137
+ const bulletFill = document.createElement("div");
6138
+ bulletFill.classList.add("cards-slider__timeline-fill");
6139
+ bullet.append(bulletFill);
6140
+ bullets.appendChild(bullet);
6141
+ }
6142
+ }
6143
+ // const onUpdateActiveIndex = (activeIndex: number) => {
6144
+ // if (activeIndex >= 0 && activeIndex < count) {
6145
+ // for (const bullet of bullets.querySelectorAll(".cards-slider__bullet--active")) {
6146
+ // bullet.classList.toggle("cards-slider__bullet--active");
6147
+ // }
6148
+ // bullets.querySelector(`.cards-slider__bullet[data-index="${activeIndex}"]`)?.classList.toggle("cards-slider__bullet--active");
6149
+ // }
6150
+ // };
6151
+ const onUpdateTimelineProgress = (index, progress, isActive) => {
5911
6152
  if (index >= 0 && index < count) {
5912
- const bullet = bullets.querySelector(`.cards-slider__bullet:nth-child(${index + 1}) .cards-slider__bullet-fill`);
5913
- if (bullet != null) {
5914
- // todo RTL
5915
- //need (in css) right: 100% instead of left -100%;
6153
+ const bullet = bullets.querySelector(`.cards-slider__timeline:nth-child(${index + 1})`);
6154
+ const bulletFill = bullet?.querySelector(".cards-slider__timeline-fill");
6155
+ bullet?.classList[isActive ? "add" : "remove"]("cards-slider__timeline--active");
6156
+ if (bulletFill != null) {
5916
6157
  // todo remove progress after slide changed?
5917
- bullet.style.setProperty("transform", `translateX(${progress}%)`);
5918
- // transform: `translateZ(0) translateX(${
5919
- // index < this.$props.index
5920
- // ? "100%"
5921
- // : index === this.$props.index
5922
- // ? `${this.$props.timePercent}%`
5923
- // : "0%"
5924
- // })`,
6158
+ if (this.config.getLayoutDirection() === "rtl") {
6159
+ progress = -1 * progress;
6160
+ }
6161
+ bulletFill.style.setProperty("transform", `translateX(${progress}%)`);
5925
6162
  }
5926
6163
  }
5927
6164
  };
5928
- return [bullets, onUpdateActiveIndex, onUpdateTimelineProgress];
6165
+ return [bullets, onUpdateTimelineProgress];
5929
6166
  }
5930
6167
  async showByIndex(newIndex) {
5931
6168
  const prevIndex = this.activeIndex;
5932
- if (this.isAnimating)
6169
+ if (this.isAnimating) {
5933
6170
  return prevIndex;
5934
- if (prevIndex !== -1) {
5935
- // skip for slider start
5936
- this.config.onSlideLeft(this.slides[prevIndex].element, prevIndex);
5937
- this.config.onSlideStop();
5938
- }
5939
- const { index, loadingError } = await this.initAndRenderSlide(newIndex);
5940
- if (loadingError) {
5941
- // todo via updateTimeline ????
5942
- this.config.onSlideLoadingError(index, loadingError);
5943
6171
  }
5944
- await this.slideTo(index);
5945
- if (!loadingError) {
5946
- this.onShowSlide(index);
6172
+ if (this.isSlideStarting) {
6173
+ // block click while slide loading and starting (prevent several timeline starting)
6174
+ return prevIndex;
5947
6175
  }
5948
- return newIndex;
5949
- }
5950
- async showNextSlide() {
5951
- const prevIndex = this.activeIndex;
5952
- if (this.isAnimating)
6176
+ if (newIndex === prevIndex) {
5953
6177
  return prevIndex;
5954
- const newIndex = prevIndex + 1;
6178
+ }
5955
6179
  if (newIndex < 0 || newIndex >= this.slides.length) {
5956
- return null;
6180
+ return prevIndex;
5957
6181
  }
5958
6182
  if (prevIndex !== -1) {
5959
6183
  // skip for slider start
5960
6184
  this.config.onSlideLeft(this.slides[prevIndex].element, prevIndex);
5961
- this.config.onSlideStop();
6185
+ await this.config.onSlideStop();
5962
6186
  }
6187
+ this.isSlideStarting = true;
5963
6188
  const { index, loadingError } = await this.initAndRenderSlide(newIndex);
5964
6189
  if (loadingError) {
5965
6190
  // todo via updateTimeline ????
5966
6191
  this.config.onSlideLoadingError(index, loadingError);
5967
6192
  }
5968
- await this.slideTo(index);
5969
6193
  if (!loadingError) {
5970
6194
  this.onShowSlide(index);
5971
6195
  }
6196
+ this.config.onSlideChangeActiveIndex(await this.slideTo(index));
6197
+ if (!loadingError) {
6198
+ await this.config.onSlideStart();
6199
+ }
6200
+ this.isSlideStarting = false;
5972
6201
  return newIndex;
5973
6202
  }
6203
+ async showNextSlide() {
6204
+ return this.showByIndex(this.activeIndex + 1);
6205
+ }
5974
6206
  async initAndRenderSlide(index) {
5975
6207
  this.config.onBeforeLoadSlide(index);
5976
6208
  let showSlidePromise;
@@ -6003,9 +6235,9 @@ class Slider {
6003
6235
  }
6004
6236
  }
6005
6237
  onShowSlide(index) {
6006
- this.updateBulletActiveIndex(index);
6238
+ // this.updateBulletActiveIndex(index);
6007
6239
  this.config.onShowSlide(this.slides[index].element, index);
6008
- this.config.onSlideStart();
6240
+ // this.config.onSlideStart();
6009
6241
  }
6010
6242
  onUpdateSizeMetrics(metrics) {
6011
6243
  // this.slideTo(this.activeIndex, 0);
@@ -6018,11 +6250,13 @@ class Slider {
6018
6250
  return k * index * cardWidth;
6019
6251
  }
6020
6252
  async slideTo(index, speed = 300) {
6021
- if (index < 0 || index > this.slides.length - 1 || this.isAnimating)
6022
- return;
6253
+ if (index < 0 || index > this.slides.length - 1 || this.isAnimating) {
6254
+ return this.activeIndex;
6255
+ }
6023
6256
  const cardOffset = this.getSlideOffset(index);
6024
6257
  await this.translateTo(cardOffset, speed);
6025
6258
  this.activeIndex = index;
6259
+ return this.activeIndex;
6026
6260
  }
6027
6261
  setTranslate(value) {
6028
6262
  this.sliderTrack?.style.setProperty("transform", `translateX(${value}px)`);
@@ -6061,23 +6295,36 @@ class Slider {
6061
6295
  this.config.root.removeChild(this.slideWrapperElement);
6062
6296
  }
6063
6297
  }
6064
- updateTimeline(slideIndex, action, currentTime, duration, showLoader, showError) {
6298
+ async updateTimeline(slideIndex, action, currentTime, duration, showLoader, showError) {
6065
6299
  switch (action) {
6066
6300
  case "before_start" /* TIMELINE_ACTION.BEFORE_START */: {
6067
6301
  // switch timeline to active slide and wait for start (wait VOD loading)
6068
- console.log("TIMELINE_ACTION.BEFORE_START", { activeSlide: this.activeSlide, duration });
6302
+ // console.log("TIMELINE_ACTION.BEFORE_START", { activeSlide: this.activeSlide, duration });
6303
+ this.timelineController.onSlideBeforeStart(slideIndex);
6069
6304
  break;
6070
6305
  }
6071
6306
  case "start" /* TIMELINE_ACTION.START */: {
6072
6307
  this.config.onSlideDataResume();
6073
6308
  // also start after data waiting or pause
6074
- // window.setTimeout(() => {
6075
- // this.config.onSlideStop();
6076
- // this.config.onSlideTimerEnd();
6077
- // }, 10000);
6078
6309
  // destroy on IAM closing
6079
- console.log("TIMELINE_ACTION.START", { activeSlide: this.activeSlide, index: this.activeIndex, duration });
6080
- this.activeSlide?.timer.resume(duration);
6310
+ // console.log("TIMELINE_ACTION.START", {
6311
+ // activeSlide: this.activeSlide,
6312
+ // index: this.activeIndex,
6313
+ // duration,
6314
+ // });
6315
+ let resumeTimer = false;
6316
+ if (duration > 0) {
6317
+ // skip timer start for slide/layer with disabled timeline
6318
+ resumeTimer = true;
6319
+ }
6320
+ if (currentTime === duration) {
6321
+ // skip timer start for already ended layer timeline
6322
+ // skip timer start for already ended layer timeline
6323
+ resumeTimer = false;
6324
+ }
6325
+ if (resumeTimer) {
6326
+ this.activeSlide?.timer.resume(currentTime, duration);
6327
+ }
6081
6328
  break;
6082
6329
  }
6083
6330
  case "pause" /* TIMELINE_ACTION.PAUSE */: {
@@ -6085,7 +6332,7 @@ class Slider {
6085
6332
  this.config.onSlideDataWaiting();
6086
6333
  }
6087
6334
  this.activeSlide?.timer.pause();
6088
- console.log("TIMELINE_ACTION.PAUSE", { activeSlide: this.activeSlide, duration });
6335
+ // console.log("TIMELINE_ACTION.PAUSE", { activeSlide: this.activeSlide, duration });
6089
6336
  break;
6090
6337
  }
6091
6338
  case "stop" /* TIMELINE_ACTION.STOP */: {
@@ -6095,27 +6342,23 @@ class Slider {
6095
6342
  }
6096
6343
  this.activeSlide?.timer.stop();
6097
6344
  // todo нужен STOP когда вручную переключаем слайд на другой
6098
- console.log("TIMELINE_ACTION.STOP", { activeSlide: this.activeSlide, duration });
6345
+ // console.log("TIMELINE_ACTION.STOP", { activeSlide: this.activeSlide, duration });
6099
6346
  break;
6100
6347
  }
6101
6348
  }
6102
6349
  }
6103
- onSlideTimerUpdate(progress) {
6104
- this.updateTimelineProgress(this.activeIndex, progress);
6105
- }
6106
- onSlideTimerEnd() {
6107
- this.config.onSlideStop();
6350
+ // private onSlideTimerUpdate(progress: number) {
6351
+ // this.updateTimelineProgress(this.activeIndex, progress);
6352
+ // }
6353
+ async onSlideTimerEnd() {
6354
+ await this.config.onSlideStop();
6108
6355
  this.config.onSlideTimerEnd();
6109
6356
  }
6110
6357
  static get [Symbol.for("___CTOR_ARGS___")]() { return [`{
6111
6358
  slides: Array<T>;
6112
6359
  root: HTMLElement;
6113
- // isFullscreen: boolean;
6114
6360
  nonce?: string;
6115
- // viewport: Window;
6116
- // userResizeHandler?: (data: { viewportWidth: number; viewportHeight: number; fontSize: number }) => void;
6117
- // VODPlayer?: typeof VODPlayer;
6118
- // overlappingActionBarHeight?: number;
6361
+ navbarAppearance: NavbarAppearance;
6119
6362
  slideRender: SlideRender<T>;
6120
6363
  onSlideMounted: OnSlideMounted;
6121
6364
  onBeforeShowSlide: OnBeforeShowSlide;
@@ -6126,6 +6369,7 @@ class Slider {
6126
6369
  getLayoutDirection: GetLayoutDirection;
6127
6370
 
6128
6371
  onSlideTimerEnd: OnSlideTimerEnd;
6372
+ onSlideChangeActiveIndex: OnSlideChangeActiveIndex;
6129
6373
  onSlideStart: OnSlideStart;
6130
6374
  onSlideStop: OnSlideStop;
6131
6375
  onSlideDataWaiting: OnSlideDataWaiting;
@@ -6152,6 +6396,7 @@ class CardApi {
6152
6396
  activeSlide = null;
6153
6397
  slides = [];
6154
6398
  slidesMode = 0 /* SLIDES_MODE.SINGLE */;
6399
+ cardLoadingStateController = null;
6155
6400
  sizeMetrics = {
6156
6401
  fontSize: "0px",
6157
6402
  isFullscreen: false,
@@ -6327,7 +6572,10 @@ class CardApi {
6327
6572
  slider = null;
6328
6573
  async showSlides(slides, cardAppearance, index = 0) {
6329
6574
  this.slidesMode = 1 /* SLIDES_MODE.MULTIPLE */;
6330
- this.sdkApi.onCardLoadingStateChange(0 /* CARD_LOADING_STATE.LOADING */);
6575
+ if (this.cardLoadingStateController === null) {
6576
+ this.cardLoadingStateController = new CardLoadingStateController(this.sdkApi.onCardLoadingStateChange.bind(this.sdkApi));
6577
+ }
6578
+ this.cardLoadingStateController.onSetLoadStartState();
6331
6579
  slides.forEach((content, index) => {
6332
6580
  if (this.slides[index] == null) {
6333
6581
  const item = { content, resourcesReadyPromise: null };
@@ -6380,6 +6628,15 @@ class CardApi {
6380
6628
  this.activeSlide = this.slides[index].slide;
6381
6629
  return this.slides[index].slide.showSlide().then(_ => ({ moveToIndex: index }));
6382
6630
  }
6631
+ else {
6632
+ // quot - // TODO для виджета квест нужно при каждом показе слайда инициализировать виджет???
6633
+ // // пока вызов убран из за того что init жестко завязан на single slide mode
6634
+ // из-за того что перед показом нет приватного вызова SlideApi::_init пока что нужно руками вызывать
6635
+ // slide.activeLayer.timeline.triggerSlideLoadState();
6636
+ // для того чтобы запустить TIMELINE_ACTION.BEFORE_START что нужно для таймлайна слайдера
6637
+ // todo убрать в случае если будем вызывать new SlideApi перед каждым показом слайда
6638
+ this.slides[index].slide.activeLayer.timeline.triggerSlideLoadState();
6639
+ }
6383
6640
  this.activeSlide = this.slides[index].slide;
6384
6641
  // TODO handle moveToIndex
6385
6642
  // return resolve or reject with reason
@@ -6393,13 +6650,13 @@ class CardApi {
6393
6650
  // внутри слайдера - обрабатываем moveToIndex
6394
6651
  };
6395
6652
  const onBeforeLoadSlide = index => {
6396
- this.sdkApi.onCardLoadingStateChange(0 /* CARD_LOADING_STATE.LOADING */);
6653
+ this.cardLoadingStateController.onSetLoadStartState();
6397
6654
  };
6398
6655
  const onSlideLoadingError = (index, reason) => {
6399
- this.sdkApi.onCardLoadingStateChange(2 /* CARD_LOADING_STATE.LOADING_ERROR */, reason);
6656
+ this.cardLoadingStateController.onSetLoadErrorState(reason);
6400
6657
  };
6401
6658
  const onShowSlide = (slide, index) => {
6402
- this.sdkApi.onCardLoadingStateChange(1 /* CARD_LOADING_STATE.LOADED */);
6659
+ this.cardLoadingStateController.onSetLoadEndState();
6403
6660
  this.sdkApi.emitEvent("showSlide", { cardId: this.slides[index]?.slide?.slide.cardId ?? 0, index });
6404
6661
  // if (index === 0) {
6405
6662
  // this.activeSlide = slideApi;
@@ -6491,6 +6748,7 @@ class CardApi {
6491
6748
  canMediaMount: resourcesReadyPromise,
6492
6749
  })),
6493
6750
  nonce: this.config.nonce,
6751
+ navbarAppearance: cardAppearance.navbar ?? {},
6494
6752
  slideRender,
6495
6753
  onSlideMounted,
6496
6754
  onBeforeShowSlide,
@@ -6500,14 +6758,16 @@ class CardApi {
6500
6758
  onSlideLeft,
6501
6759
  getLayoutDirection: () => this.layoutDirection,
6502
6760
  onSlideTimerEnd: () => this.activeSlide.slideTimerEnd(),
6761
+ onSlideChangeActiveIndex: index => (this.activeSlide = this.slides[index].slide),
6503
6762
  onSlideStart: () => {
6504
- this.sdkApi.onCardLoadingStateChange(1 /* CARD_LOADING_STATE.LOADED */);
6763
+ // return Promise.resolve({currentTime: 0});
6764
+ this.cardLoadingStateController.onSetLoadEndState();
6505
6765
  return this.activeSlide.slideStart({ muted: true });
6506
6766
  },
6507
6767
  onSlideStop: () => this.activeSlide.slideStop({ prepareForRestart: 1 /* ON_SLIDE_STOP_PREPARE_FOR_RESTART.PREPARE */ }),
6508
- onSlideDataWaiting: () => this.sdkApi.onCardLoadingStateChange(0 /* CARD_LOADING_STATE.LOADING */),
6509
- onSlideDataResume: () => this.sdkApi.onCardLoadingStateChange(1 /* CARD_LOADING_STATE.LOADED */),
6510
- onSlideDataError: () => this.sdkApi.onCardLoadingStateChange(2 /* CARD_LOADING_STATE.LOADING_ERROR */),
6768
+ onSlideDataWaiting: () => this.cardLoadingStateController.onSetLoadStartState(),
6769
+ onSlideDataResume: () => this.cardLoadingStateController.onSetLoadEndState(),
6770
+ onSlideDataError: () => this.cardLoadingStateController.onSetLoadEndState(),
6511
6771
  });
6512
6772
  await this.slider.showByIndex(index);
6513
6773
  // return await onShowActiveCard;
@@ -6581,6 +6841,15 @@ class CardApi {
6581
6841
  if (this.slidesMode === 1 /* SLIDES_MODE.MULTIPLE */ && result.canClickNext) {
6582
6842
  result.canClickNext = false; // handle nav click via CardsSlider, not via SDK
6583
6843
  const currentIndex = this.activeSlide.index;
6844
+ /**
6845
+ * business logic - avoid action on side slides click
6846
+ */
6847
+ if (currentIndex === 0 && navigationDirection === "backward") {
6848
+ return result;
6849
+ }
6850
+ if (currentIndex === this.slides.length - 1 && navigationDirection === "forward") {
6851
+ return result;
6852
+ }
6584
6853
  let index = navigationDirection === "forward" ? currentIndex + 1 : currentIndex - 1;
6585
6854
  if (index >= this.slides.length) {
6586
6855
  index = 0;
@@ -6588,11 +6857,7 @@ class CardApi {
6588
6857
  else if (index < 0) {
6589
6858
  index = this.slides.length - 1;
6590
6859
  }
6591
- this.slider.showByIndex(index).then(index => {
6592
- if (currentIndex === index)
6593
- return;
6594
- this.activeSlide = this.slides[index].slide;
6595
- });
6860
+ this.slider.showByIndex(index);
6596
6861
  }
6597
6862
  return result;
6598
6863
  }
@@ -18744,6 +19009,832 @@ class SwipeGestureDetector {
18744
19009
  static get [Symbol.for("___CTOR_ARGS___")]() { return [`HTMLElement`, `boolean`, `(e: MouseEvent | TouchEvent) => boolean`, `(e: MouseEvent | TouchEvent) => void`, `(e: MouseEvent | TouchEvent) => void`, `(e: MouseEvent | TouchEvent, gesture: SwipeGesture) => void`]; }
18745
19010
  }
18746
19011
 
19012
+ class RenderableComponent {
19013
+ _root = null;
19014
+ props;
19015
+ constructor(props) {
19016
+ this.props = (props ?? {});
19017
+ }
19018
+ render() {
19019
+ const newRoot = this.renderTemplate();
19020
+ const style = this.styleContent();
19021
+ if (style) {
19022
+ newRoot?.prepend(this.createStyleElement(style));
19023
+ }
19024
+ if (this._root) {
19025
+ this._root.replaceWith(newRoot ?? "");
19026
+ }
19027
+ this._root = newRoot;
19028
+ return this._root;
19029
+ }
19030
+ updateProps(props) {
19031
+ this.props = { ...this.props, ...props };
19032
+ this.render();
19033
+ }
19034
+ destroy() {
19035
+ this._root?.remove();
19036
+ this._root = null;
19037
+ }
19038
+ createStyleElement(css) {
19039
+ const styleEl = document.createElement("style");
19040
+ styleEl.textContent = css;
19041
+ return styleEl;
19042
+ }
19043
+ styleContent() {
19044
+ return "";
19045
+ }
19046
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`Props`]; }
19047
+ }
19048
+
19049
+ function h(tag, ...rest) {
19050
+ let props = {};
19051
+ let children = [];
19052
+ if (rest[0] && typeof rest[0] === "object" && !(rest[0] instanceof Node)) {
19053
+ props = rest.shift();
19054
+ }
19055
+ children = rest.flat();
19056
+ const el = document.createElement(tag);
19057
+ for (const [key, value] of Object.entries(props)) {
19058
+ if (value == null)
19059
+ continue;
19060
+ if (key === "class" || key === "className") {
19061
+ el.className = value;
19062
+ }
19063
+ else if (key === "textContent") {
19064
+ el.textContent = value;
19065
+ }
19066
+ else if (key === "html" || key === "innerHTML") {
19067
+ el.innerHTML = value;
19068
+ }
19069
+ else if (key === "ref" && typeof value === "function") {
19070
+ value(el);
19071
+ }
19072
+ else if (key.startsWith("data-")) {
19073
+ el.setAttribute(key, value);
19074
+ }
19075
+ else if (key.startsWith("on") && typeof value === "function") {
19076
+ const event = key.slice(2).toLowerCase();
19077
+ el.addEventListener(event, value);
19078
+ }
19079
+ else if (key in el) {
19080
+ // @ts-ignore — any valid DOM props
19081
+ el[key] = value;
19082
+ }
19083
+ else {
19084
+ el.setAttribute(key, value);
19085
+ }
19086
+ }
19087
+ for (const child of children) {
19088
+ if (child == null)
19089
+ continue;
19090
+ if (typeof child === "string") {
19091
+ el.appendChild(document.createTextNode(child));
19092
+ }
19093
+ else {
19094
+ el.appendChild(child);
19095
+ }
19096
+ }
19097
+ return el;
19098
+ }
19099
+
19100
+ class BottomSheet extends RenderableComponent {
19101
+ isClosing = false;
19102
+ _isOpened = false;
19103
+ constructor(props) {
19104
+ super(props ?? {});
19105
+ }
19106
+ get isOpened() {
19107
+ return this._isOpened;
19108
+ }
19109
+ renderTemplate() {
19110
+ return h("div", { class: `ias-bottom-sheet ${this._isOpened ? "ias-bottom-sheet--open" : ""}` }, h("div", {
19111
+ class: "ias-bottom-sheet__backdrop",
19112
+ onClick: this.handleBackdropClick,
19113
+ }), h("div", { class: "ias-bottom-sheet__container" }, h("div", { class: "ias-bottom-sheet__content" },
19114
+ /* h("div", { class: "ias-bottom-sheet__header" }), */
19115
+ h("div", { class: "ias-bottom-sheet__body" }, ...(this.props?.children?.map(c => c.render()) ?? [])))));
19116
+ }
19117
+ setHeight(height) {
19118
+ if (!this._root)
19119
+ return;
19120
+ const container = this._root.querySelector(".ias-bottom-sheet__container");
19121
+ if (!container)
19122
+ return;
19123
+ container.style.height = height;
19124
+ }
19125
+ open() {
19126
+ if (this.isOpened)
19127
+ return;
19128
+ this._isOpened = true;
19129
+ requestAnimationFrame(() => {
19130
+ requestAnimationFrame(() => {
19131
+ this._root?.classList.add("ias-bottom-sheet--open");
19132
+ });
19133
+ });
19134
+ }
19135
+ async close() {
19136
+ if (!this._root || this.isClosing)
19137
+ return;
19138
+ const root = this._root;
19139
+ root.classList.remove("ias-bottom-sheet--open");
19140
+ this.isClosing = true;
19141
+ await new Promise(resolve => {
19142
+ root.addEventListener("transitionend", () => resolve(), { once: true });
19143
+ });
19144
+ this.isClosing = false;
19145
+ this.props?.onClose?.();
19146
+ }
19147
+ handleBackdropClick = (e) => {
19148
+ e.preventDefault();
19149
+ e.stopPropagation();
19150
+ this.close();
19151
+ };
19152
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`BottomSheetProps`]; }
19153
+ }
19154
+
19155
+ class ProductDetailsDescription extends RenderableComponent {
19156
+ constructor(props) {
19157
+ super(props);
19158
+ }
19159
+ renderTemplate() {
19160
+ const { name, description } = this.props;
19161
+ return h("div", { class: "ias-product-details__description" }, name ? h("h6", { class: "ias-product-details__name", textContent: name }) : null, description ? h("p", { class: "ias-product-details__text", textContent: description }) : null);
19162
+ }
19163
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`ProductDetailsDescriptionProps`]; }
19164
+ }
19165
+
19166
+ class ProductDetailsGallery extends RenderableComponent {
19167
+ constructor(props) {
19168
+ super(props);
19169
+ }
19170
+ renderTemplate() {
19171
+ const { imageUrls = [] } = this.props;
19172
+ return h("div", { class: "ias-product-details__gallery" }, imageUrls.map(url => h("figure", { class: "ias-product-details__image" }, h("img", { src: url /* , loading: "lazy" */ }))));
19173
+ }
19174
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`ProductDetailsGalleryProps`]; }
19175
+ }
19176
+
19177
+ const ADD_TO_CART_TIMEOUT = 60000;
19178
+
19179
+ class ProductDetailsPurchaseAction extends RenderableComponent {
19180
+ button;
19181
+ constructor(props) {
19182
+ super(props);
19183
+ }
19184
+ renderTemplate() {
19185
+ this.button = h("button", {
19186
+ class: "ias-product-details__action-button",
19187
+ textContent: this.props.translations.addToCart,
19188
+ onClick: this.handleClickAddToCart,
19189
+ });
19190
+ return this.button;
19191
+ }
19192
+ handleClickAddToCart = async () => {
19193
+ try {
19194
+ this.showLoader();
19195
+ await this.addToCart();
19196
+ }
19197
+ catch (error) {
19198
+ this.handleAddToCartError(error);
19199
+ }
19200
+ finally {
19201
+ this.hideLoader();
19202
+ }
19203
+ };
19204
+ showLoader() {
19205
+ this.button.classList.add("ias-product-details__action-button--loading");
19206
+ }
19207
+ hideLoader() {
19208
+ this.button.classList.remove("ias-product-details__action-button--loading");
19209
+ }
19210
+ handleAddToCartError(error) {
19211
+ this.props.onAddToCartError({ error });
19212
+ }
19213
+ async addToCart() {
19214
+ await Promise.race([this.props.onAddToCart(), new Promise((resolve, reject) => setTimeout(reject, ADD_TO_CART_TIMEOUT))]);
19215
+ }
19216
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`ProductDetailsPurchaseActionProps`]; }
19217
+ }
19218
+
19219
+ class ProductDetailsPurchasePrice extends RenderableComponent {
19220
+ constructor(props) {
19221
+ super(props);
19222
+ }
19223
+ renderTemplate() {
19224
+ const { price, oldPrice, currency } = this.props.product;
19225
+ const rootEl = h("div", { class: "ias-product-details__price" });
19226
+ if (price != null && currency) {
19227
+ const currentPriceEl = h("div", {
19228
+ class: "ias-product-details__current-price",
19229
+ html: formatter.asCurrency(price, currency),
19230
+ });
19231
+ rootEl.appendChild(currentPriceEl);
19232
+ }
19233
+ if (oldPrice != null && currency) {
19234
+ const oldPriceEl = h("div", {
19235
+ class: "ias-product-details__old-price",
19236
+ html: formatter.asCurrency(oldPrice, currency),
19237
+ });
19238
+ rootEl.appendChild(oldPriceEl);
19239
+ }
19240
+ return rootEl;
19241
+ }
19242
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`ProductDetailsPurchasePriceProps`]; }
19243
+ }
19244
+
19245
+ class ProductDetailsCounter extends RenderableComponent {
19246
+ _value = 1;
19247
+ countElement = null;
19248
+ get value() {
19249
+ return this._value;
19250
+ }
19251
+ updateProps() {
19252
+ this._value = 1;
19253
+ this.render();
19254
+ }
19255
+ renderTemplate() {
19256
+ return h("div", { class: "ias-product-details__counter" }, h("button", {
19257
+ class: "ias-product-details__counter-button",
19258
+ textContent: "−",
19259
+ onClick: this.handleClickOnMinusButton,
19260
+ }), h("span", {
19261
+ class: "ias-product-details__counter-count",
19262
+ textContent: this._value.toString(),
19263
+ ref: (el) => (this.countElement = el),
19264
+ }), h("button", {
19265
+ class: "ias-product-details__counter-button",
19266
+ textContent: "+",
19267
+ onClick: this.handleClickOnPlusButton,
19268
+ }));
19269
+ }
19270
+ handleClickOnMinusButton = () => {
19271
+ this._value = Math.max(this._value - 1, 1);
19272
+ this.updateCountValue();
19273
+ };
19274
+ handleClickOnPlusButton = () => {
19275
+ this._value++;
19276
+ this.updateCountValue();
19277
+ };
19278
+ updateCountValue() {
19279
+ if (this.countElement) {
19280
+ this.countElement.textContent = this._value.toString();
19281
+ }
19282
+ }
19283
+ }
19284
+
19285
+ class ProductDetailsPurchase extends RenderableComponent {
19286
+ price;
19287
+ action;
19288
+ counter = new ProductDetailsCounter();
19289
+ constructor(props) {
19290
+ super(props);
19291
+ }
19292
+ renderTemplate() {
19293
+ this.price = new ProductDetailsPurchasePrice(this.props);
19294
+ this.action = new ProductDetailsPurchaseAction({
19295
+ onAddToCart: this.handleOnAddToCart,
19296
+ onAddToCartError: this.props.onAddToCartError,
19297
+ translations: this.props.translations,
19298
+ });
19299
+ return h("div", { class: "ias-product-details__purchase" }, h("div", { class: "ias-product-details__price-box" }, this.price.render(), this.counter.render()), this.action.render());
19300
+ }
19301
+ updateProps(props) {
19302
+ this.props = { ...this.props, ...props };
19303
+ this.price.updateProps(this.props);
19304
+ this.counter.updateProps();
19305
+ }
19306
+ handleOnAddToCart = async () => {
19307
+ return this.props.onAddToCart({
19308
+ quantity: this.counter.value,
19309
+ product: this.props.product,
19310
+ });
19311
+ };
19312
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`ProductDetailsPurchaseProps`]; }
19313
+ }
19314
+
19315
+ class ProductOptionButton extends RenderableComponent {
19316
+ constructor(props) {
19317
+ super(props);
19318
+ }
19319
+ renderTemplate() {
19320
+ const params = {
19321
+ class: `ias-product-details__option-button ${this.props.selected ? "ias-product-details__option-button--active" : ""} ${this.props.disabled ? "ias-product-details__option-button--disabled" : ""}`,
19322
+ textContent: this.props.label,
19323
+ onclick: () => {
19324
+ this.props.onClick();
19325
+ },
19326
+ };
19327
+ return h("button", params);
19328
+ }
19329
+ setSelected(selected) {
19330
+ if (!this._root)
19331
+ return;
19332
+ if (selected)
19333
+ this._root.classList.add("ias-product-details__option-button--active");
19334
+ else
19335
+ this._root.classList.remove("ias-product-details__option-button--active");
19336
+ }
19337
+ setDisabled(disabled) {
19338
+ if (!this._root)
19339
+ return;
19340
+ if (disabled)
19341
+ this._root.classList.add("ias-product-details__option-button--disabled");
19342
+ else
19343
+ this._root.classList.remove("ias-product-details__option-button--disabled");
19344
+ }
19345
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`ProductOptionButtonProps`]; }
19346
+ }
19347
+
19348
+ class ProductOptionsManager {
19349
+ optionNames;
19350
+ productVariants;
19351
+ subscribers = [];
19352
+ selectedOptions = {};
19353
+ allOptionsByName = {};
19354
+ availableOptionsByName = {};
19355
+ constructor(optionNames, productVariants) {
19356
+ this.optionNames = optionNames;
19357
+ this.productVariants = productVariants;
19358
+ this.allOptionsByName = this.extractAllOptions();
19359
+ }
19360
+ get selected() {
19361
+ return { ...this.selectedOptions };
19362
+ }
19363
+ get availableOptions() {
19364
+ return this.availableOptionsByName;
19365
+ }
19366
+ selectOption(option) {
19367
+ const compatibleVariants = this.findCompatibleVariants(option);
19368
+ this.ensureVariantsExist(option, compatibleVariants);
19369
+ const matchingVariant = this.findMatchingVariant(compatibleVariants);
19370
+ this.selectVariant(matchingVariant);
19371
+ }
19372
+ selectVariant(variant) {
19373
+ this.optionNames.forEach(name => {
19374
+ const currentSelected = this.selectedOptions[name];
19375
+ const newOption = variant.options[name];
19376
+ if (currentSelected && newOption && currentSelected.value === newOption.value) {
19377
+ this.selectedOptions[name] = currentSelected;
19378
+ }
19379
+ else {
19380
+ this.selectedOptions[name] = newOption;
19381
+ }
19382
+ });
19383
+ this.availableOptionsByName = this.extractAvailableOptions();
19384
+ this.notifySubscribers(variant);
19385
+ }
19386
+ onChange(callback) {
19387
+ this.subscribers.push(callback);
19388
+ }
19389
+ isOptionSelected(option) {
19390
+ return this.selected[option.name]?.value === option.value;
19391
+ }
19392
+ isOptionDisabled(option) {
19393
+ const options = this.availableOptions[option.name];
19394
+ return !options?.some(o => o.value === option.value);
19395
+ }
19396
+ getOptionsFor(name) {
19397
+ return this.allOptionsByName[name] ?? [];
19398
+ }
19399
+ findCompatibleVariants(option) {
19400
+ return this.productVariants.filter(variant => variant.options[option.name]?.value === option.value);
19401
+ }
19402
+ ensureVariantsExist(option, variants) {
19403
+ if (variants.length === 0) {
19404
+ throw new Error(`[IAS]: No compatible variant for option <${option.name}:${option.value}>`);
19405
+ }
19406
+ }
19407
+ findMatchingVariant(variants) {
19408
+ return (variants.find(variant => this.optionNames.every(name => !this.selectedOptions[name] || variant.options[name]?.value === this.selectedOptions[name]?.value)) ?? variants[0]);
19409
+ }
19410
+ extractAvailableOptions() {
19411
+ const result = {};
19412
+ for (const name of this.optionNames) {
19413
+ const filteredVariants = this.filterVariantsExcludingOption(name);
19414
+ const available = filteredVariants.filter(v => v.available && v.options[name]).map(v => v.options[name]);
19415
+ result[name] = this.getUniqueByKey(available, o => o.value);
19416
+ }
19417
+ return result;
19418
+ }
19419
+ filterVariantsExcludingOption(excludedName) {
19420
+ const filters = { ...this.selectedOptions };
19421
+ delete filters[excludedName];
19422
+ return this.productVariants.filter(variant => Object.entries(filters).every(([name, selected]) => variant.options[name]?.value === selected?.value));
19423
+ }
19424
+ extractAllOptions() {
19425
+ const all = {};
19426
+ for (const name of this.optionNames) {
19427
+ const options = this.productVariants.map(v => v.options[name]).filter(Boolean);
19428
+ all[name] = this.getUniqueByKey(options, o => o.value);
19429
+ }
19430
+ return all;
19431
+ }
19432
+ getUniqueByKey(items, getKey) {
19433
+ return items.filter((item, index, self) => index === self.findIndex(i => getKey(i) === getKey(item)));
19434
+ }
19435
+ notifySubscribers(variant) {
19436
+ this.subscribers.forEach(cb => cb(variant));
19437
+ }
19438
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`ProductOptionName[]`, `ProductVariant[]`]; }
19439
+ }
19440
+
19441
+ class ProductOptions extends RenderableComponent {
19442
+ optionsManager;
19443
+ optionsContainer;
19444
+ optionButtonsMap = new Map();
19445
+ constructor(props) {
19446
+ super(props);
19447
+ this.optionsManager = new ProductOptionsManager(props.optionNames, props.products);
19448
+ this.init(props.products);
19449
+ }
19450
+ renderTemplate() {
19451
+ this.optionsContainer = h("div", { class: "ias-product-details__options" }, this.renderOptionSelectors());
19452
+ return this.optionsContainer;
19453
+ }
19454
+ init(products) {
19455
+ this.optionsManager.selectVariant(products[0]);
19456
+ this.optionsManager.onChange(variant => {
19457
+ this.updateButtonsState();
19458
+ this.props.onChange(variant);
19459
+ });
19460
+ }
19461
+ renderOptionSelectors() {
19462
+ return this.props.optionNames.map(optionName => this.optionsManager.getOptionsFor(optionName).length > 0
19463
+ ? h("div", { class: "ias-product-details__selector" }, h("div", { class: "ias-product-details__selector-name", textContent: this.getOptionLabelByName(optionName) }), h("div", { class: "ias-product-details__option-selector" }, this.renderOptionsBySelector(optionName)))
19464
+ : null);
19465
+ }
19466
+ getOptionLabelByName(optionName) {
19467
+ switch (optionName) {
19468
+ case "color":
19469
+ return this.props.translations.color;
19470
+ case "size":
19471
+ return this.props.translations.size;
19472
+ }
19473
+ }
19474
+ renderOptionsBySelector(name) {
19475
+ const options = this.optionsManager.getOptionsFor(name);
19476
+ const buttons = [];
19477
+ const buttonElements = options.map(option => {
19478
+ const disabled = this.optionsManager.isOptionDisabled(option);
19479
+ const selected = this.optionsManager.isOptionSelected(option);
19480
+ const productOptionButton = new ProductOptionButton({
19481
+ selected,
19482
+ label: option.value,
19483
+ disabled,
19484
+ onClick: () => {
19485
+ this.optionsManager.selectOption(option);
19486
+ },
19487
+ });
19488
+ buttons.push(productOptionButton);
19489
+ return productOptionButton.render();
19490
+ });
19491
+ this.optionButtonsMap.set(name, buttons);
19492
+ return buttonElements;
19493
+ }
19494
+ updateButtonsState() {
19495
+ for (const [optionName, buttons] of this.optionButtonsMap.entries()) {
19496
+ const options = this.optionsManager.getOptionsFor(optionName);
19497
+ buttons.forEach((button, index) => {
19498
+ const option = options[index];
19499
+ const isSelected = this.optionsManager.isOptionSelected(option);
19500
+ const isDisabled = this.optionsManager.isOptionDisabled(option);
19501
+ button.setSelected(isSelected);
19502
+ button.setDisabled(isDisabled);
19503
+ });
19504
+ }
19505
+ }
19506
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`ProductOptionsProps`]; }
19507
+ }
19508
+
19509
+ class ProductDetailsCover extends RenderableComponent {
19510
+ renderTemplate() {
19511
+ return h(`div`, { class: "ias-product-details__cover" }, h("img", { src: this.props.src }));
19512
+ }
19513
+ styleContent() {
19514
+ return `
19515
+ .ias-product-details__cover {
19516
+ position: absolute;
19517
+ z-index: -1;
19518
+ inset: 0;
19519
+ }
19520
+
19521
+ .ias-product-details__cover img {
19522
+ width: 100%;
19523
+ height: 100%;
19524
+ object-fit: cover;
19525
+ }
19526
+ `;
19527
+ }
19528
+ }
19529
+
19530
+ class ProductDetails extends RenderableComponent {
19531
+ constructor(props) {
19532
+ super(props);
19533
+ }
19534
+ renderTemplate() {
19535
+ const { props } = this;
19536
+ const purchase = new ProductDetailsPurchase(props);
19537
+ const cover = new ProductDetailsCover({
19538
+ src: this.props.product.coverUrl ?? "",
19539
+ });
19540
+ const gallery = new ProductDetailsGallery({
19541
+ imageUrls: this.props.product.imageUrls ?? [],
19542
+ });
19543
+ const description = new ProductDetailsDescription(props.product);
19544
+ const productOptions = this.createProductOptions({ purchase, gallery, description, cover });
19545
+ const relatedProductsLength = this.props.relatedProducts.length ?? 0;
19546
+ return h("div", { class: "ias-product-details" },
19547
+ /* this.props.product.coverUrl ? cover.render() : null, */
19548
+ h("div", { class: "ias-product-details__scrollable" }, gallery.render(), h("div", { class: "ias-product-details__content" }, description.render(), relatedProductsLength > 0 ? productOptions.render() : null)), this.createFooter(purchase));
19549
+ }
19550
+ createProductOptions({ purchase, gallery, description, cover, }) {
19551
+ return new ProductOptions({
19552
+ optionNames: ["color", "size"],
19553
+ products: [this.props.product, ...this.props.relatedProducts],
19554
+ onChange: (product) => {
19555
+ if (this.props.isCartSupported)
19556
+ purchase.updateProps({ product: product });
19557
+ gallery.updateProps(product);
19558
+ description.updateProps(product);
19559
+ cover.updateProps({
19560
+ src: product.coverUrl,
19561
+ });
19562
+ },
19563
+ translations: this.props.translations,
19564
+ });
19565
+ }
19566
+ createFooter(purchase) {
19567
+ if (this.props.isCartSupported) {
19568
+ return h("div", { class: "ias-product-details__footer" }, purchase.render());
19569
+ }
19570
+ else if (this.props.product.url) {
19571
+ return h("div", { class: "ias-product-details__footer" }, this.createOpenUrlButton());
19572
+ }
19573
+ return null;
19574
+ }
19575
+ createOpenUrlButton() {
19576
+ return h("button", {
19577
+ class: "ias-product-details__action-button ias-product-details__open-url-button",
19578
+ textContent: this.props.translations.openUrl,
19579
+ onclick: () => {
19580
+ this.props.onOpenUrl();
19581
+ },
19582
+ });
19583
+ }
19584
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`ProductDetailsProps`]; }
19585
+ }
19586
+
19587
+ class ProductOfferMapper {
19588
+ static fromOfferDtoToProductOffer(offerDto) {
19589
+ return {
19590
+ id: offerDto.id.toString(),
19591
+ offerId: offerDto.offerId,
19592
+ available: offerDto.availability !== 0,
19593
+ availability: offerDto.availability,
19594
+ options: {
19595
+ color: offerDto.color ? { name: "color", value: offerDto.color } : { name: "color", value: "Undefined" },
19596
+ size: offerDto.size ? { name: "size", value: offerDto.size } : { name: "size", value: "Undefined" },
19597
+ },
19598
+ name: offerDto.name,
19599
+ price: offerDto.price,
19600
+ oldPrice: offerDto.oldPrice,
19601
+ currency: offerDto.currency,
19602
+ description: offerDto.description,
19603
+ imageUrls: offerDto.images?.map(image => image.url) ?? [],
19604
+ adult: offerDto.adult,
19605
+ coverUrl: offerDto.coverUrl,
19606
+ groupId: offerDto.groupId,
19607
+ url: offerDto.url,
19608
+ };
19609
+ }
19610
+ static fromOfferDtoToProductCartOffer(offerDto, quantity) {
19611
+ return {
19612
+ offerId: offerDto.offerId,
19613
+ imageUrls: offerDto.images?.map(image => image.url) ?? [],
19614
+ availability: offerDto.availability,
19615
+ quantity,
19616
+ groupId: offerDto.groupId,
19617
+ name: offerDto.name,
19618
+ description: offerDto.description,
19619
+ url: offerDto.url,
19620
+ coverUrl: offerDto.coverUrl,
19621
+ currency: offerDto.currency,
19622
+ price: offerDto.price,
19623
+ oldPrice: offerDto.oldPrice,
19624
+ adult: offerDto.adult,
19625
+ size: offerDto.size,
19626
+ color: offerDto.color,
19627
+ };
19628
+ }
19629
+ }
19630
+
19631
+ const successSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none"><path stroke="#212121" stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M40 14 18 36 8 26"></path></svg>`;
19632
+ class ProductCheckout extends RenderableComponent {
19633
+ constructor(props) {
19634
+ super(props);
19635
+ }
19636
+ renderTemplate() {
19637
+ return h("div", { class: "ias-product-checkout" }, h("div", { class: "ias-product-checkout__box" }, h("div", {
19638
+ class: "ias-product-checkout__icon",
19639
+ html: successSvg,
19640
+ }), h("span", {
19641
+ class: "ias-product-checkout__text",
19642
+ textContent: this.props.translations.successAddToCart,
19643
+ }), h("span", {
19644
+ class: "ias-product-checkout__sub-text",
19645
+ textContent: this.props.translations.successSubAddToCart,
19646
+ })), h("div", {
19647
+ class: "ias-product-checkout__actions",
19648
+ }, h("button", {
19649
+ class: "ias-product-checkout__button ias-product-checkout__button--close",
19650
+ textContent: this.props.translations.continueBtn,
19651
+ onclick: () => {
19652
+ this.props.onClose();
19653
+ },
19654
+ }), h("button", {
19655
+ class: "ias-product-checkout__button ias-product-checkout__button--checkout",
19656
+ textContent: this.props.translations.goToCartBtn,
19657
+ onclick: () => {
19658
+ this.props.onCheckout();
19659
+ },
19660
+ })));
19661
+ }
19662
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`ProductCheckoutProps`]; }
19663
+ }
19664
+
19665
+ const cartSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 20 20" fill="none">
19666
+ <path stroke="rgba(255, 255, 255, 1)" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.333" d="M4 1.333 2 4v9.333a1.333 1.333 0 0 0 1.333 1.334h9.334A1.334 1.334 0 0 0 14 13.333V4l-2-2.667H4ZM2 4h12"></path>
19667
+ <path stroke="rgba(255, 255, 255, 1)" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.333" d="M10.667 6.667a2.667 2.667 0 1 1-5.333 0"></path>
19668
+ </svg>`;
19669
+ class ProductCartButton extends RenderableComponent {
19670
+ constructor(props) {
19671
+ super(props);
19672
+ }
19673
+ renderTemplate() {
19674
+ return h("div", { class: "ias-product-cart-button", onclick: () => this.props.onClick(), innerHTML: cartSvg });
19675
+ }
19676
+ styleContent() {
19677
+ return `
19678
+ .ias-product-cart-button {
19679
+ position: absolute;
19680
+ left: 0;
19681
+ top: 20%;
19682
+ padding: .25em;
19683
+ padding-left: .5em;
19684
+ background: black;
19685
+ border-top-right-radius: 0.69em;
19686
+ border-bottom-right-radius: 0.69em;
19687
+ display: flex;
19688
+ justify-content: center;
19689
+ align-items: center;
19690
+ cursor: pointer;
19691
+ transition: all 200ms ease-in;
19692
+ transform: translate3d(-.25em, 0, 0);
19693
+ }
19694
+
19695
+ .ias-product-cart-button:hover {
19696
+ transform: translate3d(0, 0, 0);
19697
+ }
19698
+ `;
19699
+ }
19700
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`ProductCartButtonProps`]; }
19701
+ }
19702
+
19703
+ class ProductComponentsFactory {
19704
+ widgetDeps;
19705
+ props;
19706
+ constructor(widgetDeps, props) {
19707
+ this.widgetDeps = widgetDeps;
19708
+ this.props = props;
19709
+ }
19710
+ createBottomSheet() {
19711
+ const bs = new BottomSheet({
19712
+ onClose: () => {
19713
+ bs.destroy();
19714
+ },
19715
+ children: [],
19716
+ });
19717
+ return bs;
19718
+ }
19719
+ createProductDetails(params) {
19720
+ return new ProductDetails({
19721
+ product: ProductOfferMapper.fromOfferDtoToProductOffer(params.offer),
19722
+ relatedProducts: params.offer.subOffersApi
19723
+ ? params.offer.subOffersApi.map(subOffer => ProductOfferMapper.fromOfferDtoToProductOffer(subOffer))
19724
+ : [],
19725
+ onAddToCart: params.onAddToCart,
19726
+ onAddToCartError: ({ error }) => {
19727
+ this.widgetDeps.slideApiDeps.showToast(error.message);
19728
+ },
19729
+ translations: this.props.translations,
19730
+ isCartSupported: params.isCartSupported,
19731
+ onOpenUrl: () => {
19732
+ if (params.offer.url)
19733
+ this.widgetDeps.slideApiDeps.openUrl({ type: "link", link: { type: "url", target: params.offer.url } });
19734
+ },
19735
+ });
19736
+ }
19737
+ createProductCheckout(bottomSheet, offers) {
19738
+ const offersByIds = {};
19739
+ for (const offer of offers)
19740
+ offersByIds[offer.offerId] = offer;
19741
+ return new ProductCheckout({
19742
+ onClose: () => bottomSheet.close(),
19743
+ onCheckout: () => {
19744
+ this.widgetDeps.slideApiDeps.productCartClicked();
19745
+ },
19746
+ translations: this.props.translations,
19747
+ });
19748
+ }
19749
+ createProductCartButton() {
19750
+ return new ProductCartButton({
19751
+ onClick: async () => {
19752
+ try {
19753
+ const state = await this.widgetDeps.slideApiDeps.productCartGetState();
19754
+ console.log({ cart: state });
19755
+ this.widgetDeps.slideApiDeps.showToast(JSON.stringify(state.offers.map(({ offerId, name, quantity }) => ({
19756
+ offerId,
19757
+ name,
19758
+ quantity,
19759
+ }))));
19760
+ }
19761
+ catch (error) {
19762
+ this.widgetDeps.slideApiDeps.showToast(error.message);
19763
+ }
19764
+ },
19765
+ });
19766
+ }
19767
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`WidgetDeps`, `{
19768
+ translations: TranslationsMap;
19769
+ }`]; }
19770
+ }
19771
+
19772
+ class ProductDetailsBottomSheet extends RenderableComponent {
19773
+ widgetDeps;
19774
+ factory;
19775
+ bottomSheet;
19776
+ constructor(widgetDeps, props) {
19777
+ super(props);
19778
+ this.widgetDeps = widgetDeps;
19779
+ this.factory = new ProductComponentsFactory(this.widgetDeps, this.props);
19780
+ this.bottomSheet = this.factory.createBottomSheet();
19781
+ }
19782
+ renderTemplate() {
19783
+ return this.bottomSheet.render();
19784
+ }
19785
+ open(params) {
19786
+ this.showProductDetails(params);
19787
+ this.bottomSheet.open();
19788
+ }
19789
+ showProductDetails = ({ offer, isCartSupported }) => {
19790
+ const factory = new ProductComponentsFactory(this.widgetDeps, this.props);
19791
+ const offerDtos = [offer, ...(offer.subOffersApi ?? [])];
19792
+ this.renderProductDetails({ factory, offer, offerDtos, isCartSupported });
19793
+ };
19794
+ renderProductDetails({ factory, offer, offerDtos, isCartSupported, }) {
19795
+ const productDetails = factory.createProductDetails({
19796
+ offer,
19797
+ onAddToCart: async (payload) => {
19798
+ await this.productCartUpdate({
19799
+ offerDtos,
19800
+ product: payload.product,
19801
+ quantity: payload.quantity,
19802
+ });
19803
+ this.showProductCheckout({ offerDtos });
19804
+ },
19805
+ isCartSupported,
19806
+ });
19807
+ this.updateBottomSheetContent(productDetails);
19808
+ }
19809
+ showProductCheckout({ offerDtos }) {
19810
+ const productCheckout = this.factory.createProductCheckout(this.bottomSheet, offerDtos);
19811
+ this.updateBottomSheetContent(productCheckout);
19812
+ this.bottomSheet.setHeight("auto");
19813
+ }
19814
+ updateBottomSheetContent(content) {
19815
+ const cartButton = this.factory.createProductCartButton();
19816
+ const children = process.env.NODE_ENV === "staging" ? [cartButton, content] : [content];
19817
+ this.bottomSheet.updateProps({
19818
+ children,
19819
+ });
19820
+ }
19821
+ async productCartUpdate({ offerDtos, product, quantity }) {
19822
+ const offerDto = offerDtos.find(offerDto => product.offerId === offerDto.offerId);
19823
+ if (!offerDto)
19824
+ throw new Error(`[IAS]: Not found offer for ID ${product.offerId}`);
19825
+ const cartOffer = ProductOfferMapper.fromOfferDtoToProductCartOffer(offerDto, quantity);
19826
+ const delay = async () => new Promise(resolve => setTimeout(resolve, 300));
19827
+ const [productCart] = await Promise.all([
19828
+ await await this.widgetDeps.slideApiDeps.productCartUpdate({
19829
+ offer: cartOffer,
19830
+ }),
19831
+ delay(),
19832
+ ]);
19833
+ return productCart.offers;
19834
+ }
19835
+ static get [Symbol.for("___CTOR_ARGS___")]() { return [`WidgetDeps`, `ProductDetailsBottomSheetProps`]; }
19836
+ }
19837
+
18747
19838
  /**
18748
19839
  * adult: null
18749
19840
  * availability: 1
@@ -18898,7 +19989,7 @@ class WidgetProducts extends WidgetBase {
18898
19989
  if (!this.linkTarget.length) {
18899
19990
  return { message: this.msgServiceError ?? "", models: [] };
18900
19991
  }
18901
- let qs = `id=${this.linkTarget.join(",")}`;
19992
+ let qs = `id=${this.linkTarget.join(",")}&expand=images,subOffersApi`;
18902
19993
  const sdkClientVariables = this.widgetDeps.getSdkClientVariables();
18903
19994
  if (sdkClientVariables != null && sdkClientVariables.pos != null) {
18904
19995
  qs += `&pos=${String(sdkClientVariables.pos)}`;
@@ -19121,9 +20212,7 @@ class WidgetProducts extends WidgetBase {
19121
20212
  e.stopPropagation();
19122
20213
  e.preventDefault();
19123
20214
  this._statEventWidgetCardClick(offer);
19124
- if (offer.url) {
19125
- this.widgetDeps.slideApiDeps.openUrl({ type: "link", link: { type: "url", target: offer.url } });
19126
- }
20215
+ this.showProductDetails({ offer, card });
19127
20216
  };
19128
20217
  card.appendChild(figure);
19129
20218
  // card.appendChild(subTitle);
@@ -19131,6 +20220,27 @@ class WidgetProducts extends WidgetBase {
19131
20220
  card.appendChild(title);
19132
20221
  return card;
19133
20222
  }
20223
+ showProductDetails = ({ card, offer }) => {
20224
+ if (!this.productsView)
20225
+ return;
20226
+ const bs = new ProductDetailsBottomSheet(this.widgetDeps, {
20227
+ translations: {
20228
+ color: getTagData(this.element, "msgColor") ?? "",
20229
+ size: getTagData(this.element, "msgSize") ?? "",
20230
+ addToCart: getTagData(this.element, "msgAddToCart") ?? "",
20231
+ successAddToCart: getTagData(this.element, "msgSuccess") ?? "",
20232
+ successSubAddToCart: getTagData(this.element, "msgSuccessSub") ?? "",
20233
+ errorAddToCart: getTagData(this.element, "msgError") ?? "",
20234
+ goToCartBtn: getTagData(this.element, "msgGoToCart") ?? "",
20235
+ continueBtn: getTagData(this.element, "msgContinue") ?? "",
20236
+ openUrl: getTagData(this.element, "msgOpenUrl") ?? "",
20237
+ },
20238
+ });
20239
+ const isCartEnabled = getTagData(this.element, "isCartEnabled") === "true";
20240
+ const isCartSupported = this.widgetDeps.slideApiDeps.isSdkSupportProductCart && isCartEnabled;
20241
+ this.productsView.appendChild(bs.render());
20242
+ bs.open({ offer, isCartSupported });
20243
+ };
19134
20244
  createScrollView(offers) {
19135
20245
  const scrollViewGroup = document.createElement("div");
19136
20246
  scrollViewGroup.classList.add("ias-products-scroll-view-group");