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