@snabcentr/client-ui 4.14.4 → 4.14.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
- import { EventEmitter, InputSignal, QueryList, TemplateRef } from '@angular/core';
1
+ import { AfterViewInit, EventEmitter, InputSignal, QueryList, TemplateRef, WritableSignal } from '@angular/core';
2
2
  import { ScBanner } from '@snabcentr/client-core';
3
3
  import * as i0 from "@angular/core";
4
4
  /**
5
5
  * Баннер с прокруткой переданных {@link TemplateRef} элементов, и баннеров локации.
6
6
  */
7
- export declare class ScBannerComponent {
7
+ export declare class ScBannerComponent implements AfterViewInit {
8
8
  /**
9
9
  * Признак, что необходимо показывать кнопки навигации.
10
10
  */
@@ -40,7 +40,11 @@ export declare class ScBannerComponent {
40
40
  /**
41
41
  * Идентификатор текущего баннера.
42
42
  */
43
- protected currentBannerId: number;
43
+ protected readonly currentBannerId: WritableSignal<number>;
44
+ /**
45
+ * Идентификатор баннера на момент начала касания.
46
+ */
47
+ private touchStartBannerId;
44
48
  /**
45
49
  * Сервис для работы с данными о баннерах.
46
50
  */
@@ -49,6 +53,14 @@ export declare class ScBannerComponent {
49
53
  * Сервис для сбора метрик о действиях пользователей.
50
54
  */
51
55
  private readonly userMetrikaService;
56
+ /**
57
+ * Поток событий свайпа по баннеру.
58
+ */
59
+ private readonly swipes$;
60
+ /**
61
+ * Ссылка для автоматического управления уничтожением зависимостей.
62
+ */
63
+ private readonly destroyRef;
52
64
  /**
53
65
  * Список баннеров.
54
66
  */
@@ -60,6 +72,10 @@ export declare class ScBannerComponent {
60
72
  * tuiClamp с max < min в TuiCarouselComponent.onAutoscroll → updateIndex.
61
73
  */
62
74
  readonly totalItems: import("@angular/core").Signal<number>;
75
+ /**
76
+ * @inheritDoc
77
+ */
78
+ ngAfterViewInit(): void;
63
79
  /**
64
80
  * trackBy для *ngFor баннеров: сохраняет узлы при обновлении списка,
65
81
  * чтобы карусель не видела 0 слайдов между тиками и не вызывала tuiClamp(…, -1).
@@ -68,12 +84,25 @@ export declare class ScBannerComponent {
68
84
  * @param banner Данные о баннере.
69
85
  */
70
86
  trackByBannerId(_index: number, banner: ScBanner): number;
87
+ /**
88
+ * Обработчик начала касания баннера.
89
+ */
90
+ protected onTouchStart(): void;
71
91
  /**
72
92
  * Обработчик нажатия на баннер.
73
93
  *
74
94
  * @param banner Баннер, по ссылке которого совершён переход.
75
95
  */
76
96
  onClick(banner: ScBanner): void;
97
+ /**
98
+ * Обработчик свайпа по баннеру.
99
+ *
100
+ * Баннер отсчитывается от показанного в начале касания: часть жестов карусель обрабатывает сама
101
+ * (короткий свайп, перетаскивание слайда пальцем), и жест не должен пролистать два баннера.
102
+ *
103
+ * @param swipe Событие свайпа.
104
+ */
105
+ private onSwipe;
77
106
  static ɵfac: i0.ɵɵFactoryDeclaration<ScBannerComponent, never>;
78
107
  static ɵcmp: i0.ɵɵComponentDeclaration<ScBannerComponent, "sc-banner", never, { "navigateButton": { "alias": "navigateButton"; "required": false; }; "duration": { "alias": "duration"; "required": false; }; "bannerLocation": { "alias": "bannerLocation"; "required": false; "isSignal": true; }; "resizable": { "alias": "resizable"; "required": false; }; }, { "loadBannersEvent": "loadBannersEvent"; "clickBannerEvent": "clickBannerEvent"; }, ["bannersListRef"], never, false, never>;
79
108
  }
@@ -1,7 +1,8 @@
1
- import { ChangeDetectionStrategy, Component, computed, ContentChildren, EventEmitter, HostBinding, inject, Input, input, Output, } from '@angular/core';
2
- import { toObservable, toSignal } from '@angular/core/rxjs-interop';
1
+ import { ChangeDetectionStrategy, Component, computed, ContentChildren, DestroyRef, EventEmitter, HostBinding, inject, Input, input, Output, signal, } from '@angular/core';
2
+ import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
3
3
  import { IntersectionObserverService } from '@ng-web-apis/intersection-observer';
4
4
  import { ScBannerService, ScUserMetrikaGoalsEnum, ScUserMetrikaService } from '@snabcentr/client-core';
5
+ import { TUI_SWIPE_OPTIONS, TuiSwipeService } from '@taiga-ui/cdk';
5
6
  import { map, shareReplay, switchMap, tap } from 'rxjs';
6
7
  import { SC_BANNER_DURATION } from '../providers';
7
8
  import * as i0 from "@angular/core";
@@ -46,7 +47,11 @@ export class ScBannerComponent {
46
47
  /**
47
48
  * Идентификатор текущего баннера.
48
49
  */
49
- this.currentBannerId = 0;
50
+ this.currentBannerId = signal(0);
51
+ /**
52
+ * Идентификатор баннера на момент начала касания.
53
+ */
54
+ this.touchStartBannerId = 0;
50
55
  /**
51
56
  * Сервис для работы с данными о баннерах.
52
57
  */
@@ -55,6 +60,14 @@ export class ScBannerComponent {
55
60
  * Сервис для сбора метрик о действиях пользователей.
56
61
  */
57
62
  this.userMetrikaService = inject(ScUserMetrikaService);
63
+ /**
64
+ * Поток событий свайпа по баннеру.
65
+ */
66
+ this.swipes$ = inject(TuiSwipeService);
67
+ /**
68
+ * Ссылка для автоматического управления уничтожением зависимостей.
69
+ */
70
+ this.destroyRef = inject(DestroyRef);
58
71
  /**
59
72
  * Список баннеров.
60
73
  */
@@ -74,6 +87,15 @@ export class ScBannerComponent {
74
87
  */
75
88
  this.totalItems = computed(() => this.banners().length + this.bannersListRef.length);
76
89
  }
90
+ /**
91
+ * @inheritDoc
92
+ */
93
+ ngAfterViewInit() {
94
+ // Подписка идёт после карусели, чтобы свайп обрабатывался последним: иначе её собственная обработка жеста добавит ещё один слайд.
95
+ this.swipes$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((swipe) => {
96
+ this.onSwipe(swipe);
97
+ });
98
+ }
77
99
  /**
78
100
  * trackBy для *ngFor баннеров: сохраняет узлы при обновлении списка,
79
101
  * чтобы карусель не видела 0 слайдов между тиками и не вызывала tuiClamp(…, -1).
@@ -86,6 +108,12 @@ export class ScBannerComponent {
86
108
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
87
109
  return banner.id ?? _index;
88
110
  }
111
+ /**
112
+ * Обработчик начала касания баннера.
113
+ */
114
+ onTouchStart() {
115
+ this.touchStartBannerId = this.currentBannerId();
116
+ }
89
117
  /**
90
118
  * Обработчик нажатия на баннер.
91
119
  *
@@ -102,15 +130,32 @@ export class ScBannerComponent {
102
130
  this.clickBannerEvent.emit(banner);
103
131
  }
104
132
  }
133
+ /**
134
+ * Обработчик свайпа по баннеру.
135
+ *
136
+ * Баннер отсчитывается от показанного в начале касания: часть жестов карусель обрабатывает сама
137
+ * (короткий свайп, перетаскивание слайда пальцем), и жест не должен пролистать два баннера.
138
+ *
139
+ * @param swipe Событие свайпа.
140
+ */
141
+ onSwipe(swipe) {
142
+ if (swipe.direction === 'left') {
143
+ this.currentBannerId.set(Math.min(this.touchStartBannerId + 1, this.totalItems() - 1));
144
+ }
145
+ else if (swipe.direction === 'right') {
146
+ this.currentBannerId.set(Math.max(this.touchStartBannerId - 1, 0));
147
+ }
148
+ }
105
149
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScBannerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
106
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScBannerComponent, selector: "sc-banner", inputs: { navigateButton: { classPropertyName: "navigateButton", publicName: "navigateButton", isSignal: false, isRequired: false, transformFunction: null }, duration: { classPropertyName: "duration", publicName: "duration", isSignal: false, isRequired: false, transformFunction: null }, bannerLocation: { classPropertyName: "bannerLocation", publicName: "bannerLocation", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { loadBannersEvent: "loadBannersEvent", clickBannerEvent: "clickBannerEvent" }, host: { attributes: { "ngSkipHydration": "true" }, properties: { "class.!hidden": "totalItems() === 0", "style.aspect-ratio": "this.aspectRatio" } }, providers: [IntersectionObserverService], queries: [{ propertyName: "bannersListRef", predicate: ["banner"] }], ngImport: i0, template: "@let effectiveDuration = totalItems() > 1 ? duration : 0;\n\n<tui-carousel\n [duration]=\"effectiveDuration\"\n #carousel\n [attr.resizable]=\"resizable\"\n class=\"size-full overflow-hidden rounded-xl bg-white\"\n [(index)]=\"currentBannerId\"\n>\n @for (banner of banners(); track trackByBannerId($index, banner)) {\n @switch (banner.mediaType) {\n @case ('image') {\n <a\n *tuiItem\n (click)=\"onClick(banner)\"\n target=\"_blank\"\n [title]=\"banner.title\"\n [style.aspect-ratio]=\"aspectRatio\"\n [attr.href]=\"banner.url ? banner.url : null\"\n class=\"size-full\"\n >\n <picture>\n @if (banner.mediaFileWebp) {\n <source\n type=\"image/webp\"\n [srcset]=\"banner.mediaFileWebp | scMediaImageTransformer\"\n />\n }\n <img\n [src]=\"banner.mediaFile | scMediaImageTransformer: true\"\n [alt]=\"banner.title\"\n class=\"size-full object-cover\"\n />\n </picture>\n </a>\n }\n }\n }\n @for (item of bannersListRef; track $index) {\n <div\n *tuiItem\n [style.aspect-ratio]=\"aspectRatio\"\n class=\"size-full overflow-hidden\"\n >\n <ng-container [ngTemplateOutlet]=\"item\"></ng-container>\n </div>\n }\n</tui-carousel>\n\n@if (navigateButton && duration && banners() && totalItems() > 1) {\n <div\n tuiTheme=\"light\"\n class=\"flex items-center\"\n >\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-left\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.prev()\"\n class=\"!absolute left-2\"\n ></button>\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-right\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.next()\"\n class=\"!absolute right-2\"\n ></button>\n </div>\n}\n", styles: [":host{--tui-carousel-padding: 0;display:flex;position:relative}::ng-deep tui-carousel[resizable=true] .t-scroller,::ng-deep tui-carousel[resizable=true] .t-items{width:100%;height:100%}\n"], dependencies: [{ kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "directive", type: i3.TuiItem, selector: "[tuiItem]" }, { kind: "component", type: i4.TuiCarouselComponent, selector: "tui-carousel", inputs: ["draggable", "itemsCount", "index"], outputs: ["indexChange", "shift"] }, { kind: "pipe", type: i5.ScMediaImageTransformerPipe, name: "scMediaImageTransformer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
150
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScBannerComponent, selector: "sc-banner", inputs: { navigateButton: { classPropertyName: "navigateButton", publicName: "navigateButton", isSignal: false, isRequired: false, transformFunction: null }, duration: { classPropertyName: "duration", publicName: "duration", isSignal: false, isRequired: false, transformFunction: null }, bannerLocation: { classPropertyName: "bannerLocation", publicName: "bannerLocation", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { loadBannersEvent: "loadBannersEvent", clickBannerEvent: "clickBannerEvent" }, host: { attributes: { "ngSkipHydration": "true" }, listeners: { "touchstart": "onTouchStart()" }, properties: { "class.!hidden": "totalItems() === 0", "style.aspect-ratio": "this.aspectRatio" } }, providers: [IntersectionObserverService, TuiSwipeService, { provide: TUI_SWIPE_OPTIONS, useValue: { timeout: Number.POSITIVE_INFINITY, threshold: 30 } }], queries: [{ propertyName: "bannersListRef", predicate: ["banner"] }], ngImport: i0, template: "@let effectiveDuration = totalItems() > 1 ? duration : 0;\n\n<tui-carousel\n [duration]=\"effectiveDuration\"\n #carousel\n [attr.resizable]=\"resizable\"\n class=\"size-full overflow-hidden rounded-xl bg-white\"\n [(index)]=\"currentBannerId\"\n>\n @for (banner of banners(); track trackByBannerId($index, banner)) {\n @switch (banner.mediaType) {\n @case ('image') {\n <a\n *tuiItem\n (click)=\"onClick(banner)\"\n target=\"_blank\"\n [title]=\"banner.title\"\n [style.aspect-ratio]=\"aspectRatio\"\n [attr.href]=\"banner.url ? banner.url : null\"\n class=\"size-full\"\n >\n <picture>\n @if (banner.mediaFileWebp) {\n <source\n type=\"image/webp\"\n [srcset]=\"banner.mediaFileWebp | scMediaImageTransformer\"\n />\n }\n <img\n [src]=\"banner.mediaFile | scMediaImageTransformer: true\"\n [alt]=\"banner.title\"\n class=\"size-full object-cover\"\n />\n </picture>\n </a>\n }\n }\n }\n @for (item of bannersListRef; track $index) {\n <div\n *tuiItem\n [style.aspect-ratio]=\"aspectRatio\"\n class=\"size-full overflow-hidden\"\n >\n <ng-container [ngTemplateOutlet]=\"item\"></ng-container>\n </div>\n }\n</tui-carousel>\n\n@if (navigateButton && totalItems() > 1) {\n <div\n tuiTheme=\"light\"\n class=\"flex items-center\"\n >\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-left\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.prev()\"\n class=\"!absolute left-2\"\n ></button>\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-right\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.next()\"\n class=\"!absolute right-2\"\n ></button>\n </div>\n}\n", styles: [":host{--tui-carousel-padding: 0;display:flex;position:relative}::ng-deep tui-carousel[resizable=true] .t-scroller,::ng-deep tui-carousel[resizable=true] .t-items{width:100%;height:100%}\n"], dependencies: [{ kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "directive", type: i3.TuiItem, selector: "[tuiItem]" }, { kind: "component", type: i4.TuiCarouselComponent, selector: "tui-carousel", inputs: ["draggable", "itemsCount", "index"], outputs: ["indexChange", "shift"] }, { kind: "pipe", type: i5.ScMediaImageTransformerPipe, name: "scMediaImageTransformer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
107
151
  }
108
152
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScBannerComponent, decorators: [{
109
153
  type: Component,
110
- args: [{ selector: 'sc-banner', providers: [IntersectionObserverService], host: {
154
+ args: [{ selector: 'sc-banner', providers: [IntersectionObserverService, TuiSwipeService, { provide: TUI_SWIPE_OPTIONS, useValue: { timeout: Number.POSITIVE_INFINITY, threshold: 30 } }], host: {
111
155
  ngSkipHydration: 'true',
112
156
  '[class.!hidden]': 'totalItems() === 0',
113
- }, changeDetection: ChangeDetectionStrategy.OnPush, template: "@let effectiveDuration = totalItems() > 1 ? duration : 0;\n\n<tui-carousel\n [duration]=\"effectiveDuration\"\n #carousel\n [attr.resizable]=\"resizable\"\n class=\"size-full overflow-hidden rounded-xl bg-white\"\n [(index)]=\"currentBannerId\"\n>\n @for (banner of banners(); track trackByBannerId($index, banner)) {\n @switch (banner.mediaType) {\n @case ('image') {\n <a\n *tuiItem\n (click)=\"onClick(banner)\"\n target=\"_blank\"\n [title]=\"banner.title\"\n [style.aspect-ratio]=\"aspectRatio\"\n [attr.href]=\"banner.url ? banner.url : null\"\n class=\"size-full\"\n >\n <picture>\n @if (banner.mediaFileWebp) {\n <source\n type=\"image/webp\"\n [srcset]=\"banner.mediaFileWebp | scMediaImageTransformer\"\n />\n }\n <img\n [src]=\"banner.mediaFile | scMediaImageTransformer: true\"\n [alt]=\"banner.title\"\n class=\"size-full object-cover\"\n />\n </picture>\n </a>\n }\n }\n }\n @for (item of bannersListRef; track $index) {\n <div\n *tuiItem\n [style.aspect-ratio]=\"aspectRatio\"\n class=\"size-full overflow-hidden\"\n >\n <ng-container [ngTemplateOutlet]=\"item\"></ng-container>\n </div>\n }\n</tui-carousel>\n\n@if (navigateButton && duration && banners() && totalItems() > 1) {\n <div\n tuiTheme=\"light\"\n class=\"flex items-center\"\n >\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-left\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.prev()\"\n class=\"!absolute left-2\"\n ></button>\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-right\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.next()\"\n class=\"!absolute right-2\"\n ></button>\n </div>\n}\n", styles: [":host{--tui-carousel-padding: 0;display:flex;position:relative}::ng-deep tui-carousel[resizable=true] .t-scroller,::ng-deep tui-carousel[resizable=true] .t-items{width:100%;height:100%}\n"] }]
157
+ '(touchstart)': 'onTouchStart()',
158
+ }, changeDetection: ChangeDetectionStrategy.OnPush, template: "@let effectiveDuration = totalItems() > 1 ? duration : 0;\n\n<tui-carousel\n [duration]=\"effectiveDuration\"\n #carousel\n [attr.resizable]=\"resizable\"\n class=\"size-full overflow-hidden rounded-xl bg-white\"\n [(index)]=\"currentBannerId\"\n>\n @for (banner of banners(); track trackByBannerId($index, banner)) {\n @switch (banner.mediaType) {\n @case ('image') {\n <a\n *tuiItem\n (click)=\"onClick(banner)\"\n target=\"_blank\"\n [title]=\"banner.title\"\n [style.aspect-ratio]=\"aspectRatio\"\n [attr.href]=\"banner.url ? banner.url : null\"\n class=\"size-full\"\n >\n <picture>\n @if (banner.mediaFileWebp) {\n <source\n type=\"image/webp\"\n [srcset]=\"banner.mediaFileWebp | scMediaImageTransformer\"\n />\n }\n <img\n [src]=\"banner.mediaFile | scMediaImageTransformer: true\"\n [alt]=\"banner.title\"\n class=\"size-full object-cover\"\n />\n </picture>\n </a>\n }\n }\n }\n @for (item of bannersListRef; track $index) {\n <div\n *tuiItem\n [style.aspect-ratio]=\"aspectRatio\"\n class=\"size-full overflow-hidden\"\n >\n <ng-container [ngTemplateOutlet]=\"item\"></ng-container>\n </div>\n }\n</tui-carousel>\n\n@if (navigateButton && totalItems() > 1) {\n <div\n tuiTheme=\"light\"\n class=\"flex items-center\"\n >\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-left\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.prev()\"\n class=\"!absolute left-2\"\n ></button>\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-right\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.next()\"\n class=\"!absolute right-2\"\n ></button>\n </div>\n}\n", styles: [":host{--tui-carousel-padding: 0;display:flex;position:relative}::ng-deep tui-carousel[resizable=true] .t-scroller,::ng-deep tui-carousel[resizable=true] .t-items{width:100%;height:100%}\n"] }]
114
159
  }], propDecorators: { navigateButton: [{
115
160
  type: Input
116
161
  }], duration: [{
@@ -128,4 +173,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
128
173
  type: HostBinding,
129
174
  args: ['style.aspect-ratio']
130
175
  }] } });
131
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2MtYmFubmVyLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Byb2plY3RzL2NsaWVudC11aS9iYW5uZXIvc2MtYmFubmVyLmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uL3Byb2plY3RzL2NsaWVudC11aS9iYW5uZXIvc2MtYmFubmVyLmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFDSCx1QkFBdUIsRUFDdkIsU0FBUyxFQUNULFFBQVEsRUFDUixlQUFlLEVBQ2YsWUFBWSxFQUNaLFdBQVcsRUFDWCxNQUFNLEVBQ04sS0FBSyxFQUNMLEtBQUssRUFFTCxNQUFNLEdBR1QsTUFBTSxlQUFlLENBQUM7QUFDdkIsT0FBTyxFQUFFLFlBQVksRUFBRSxRQUFRLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUNwRSxPQUFPLEVBQUUsMkJBQTJCLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUNqRixPQUFPLEVBQVksZUFBZSxFQUFFLHNCQUFzQixFQUFFLG9CQUFvQixFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDakgsT0FBTyxFQUFFLEdBQUcsRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFLEdBQUcsRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUV4RCxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxjQUFjLENBQUM7Ozs7Ozs7QUFFbEQ7O0dBRUc7QUFZSCxNQUFNLE9BQU8saUJBQWlCO0lBWDlCO1FBWUk7O1dBRUc7UUFFSSxtQkFBYyxHQUFZLElBQUksQ0FBQztRQUV0Qzs7V0FFRztRQUVJLGFBQVEsR0FBVyxNQUFNLENBQUMsa0JBQWtCLENBQUMsQ0FBQztRQUVyRDs7V0FFRztRQUNJLG1CQUFjLEdBQW9DLEtBQUssRUFBVSxDQUFDO1FBRXpFOztXQUVHO1FBRUksY0FBUyxHQUFZLEtBQUssQ0FBQztRQUVsQzs7V0FFRztRQUVJLHFCQUFnQixHQUF5QixJQUFJLFlBQVksRUFBVSxD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QUFnQjtzQkFEdEIsTUFBTTtnQkFRQSxjQUFjO3NCQUZwQixlQUFlO3VCQUFDLFFBQVE7Z0JBUWxCLFdBQVc7c0JBRGpCLFdBQVc7dUJBQUMsb0JBQW9CIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgICBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneSxcbiAgICBDb21wb25lbnQsXG4gICAgY29tcHV0ZWQsXG4gICAgQ29udGVudENoaWxkcmVuLFxuICAgIEV2ZW50RW1pdHRlcixcbiAgICBIb3N0QmluZGluZyxcbiAgICBpbmplY3QsXG4gICAgSW5wdXQsXG4gICAgaW5wdXQsXG4gICAgSW5wdXRTaWduYWwsXG4gICAgT3V0cHV0LFxuICAgIFF1ZXJ5TGlzdCxcbiAgICBUZW1wbGF0ZVJlZixcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyB0b09ic2VydmFibGUsIHRvU2lnbmFsIH0gZnJvbSAnQGFuZ3VsYXIvY29yZS9yeGpzLWludGVyb3AnO1xuaW1wb3J0IHsgSW50ZXJzZWN0aW9uT2JzZXJ2ZXJTZXJ2aWNlIH0gZnJvbSAnQG5nLXdlYi1hcGlzL2ludGVyc2VjdGlvbi1vYnNlcnZlcic7XG5pbXBvcnQgeyBTY0Jhbm5lciwgU2NCYW5uZXJTZXJ2aWNlLCBTY1VzZXJNZXRyaWthR29hbHNFbnVtLCBTY1VzZXJNZXRyaWthU2VydmljZSB9IGZyb20gJ0BzbmFiY2VudHIvY2xpZW50LWNvcmUnO1xuaW1wb3J0IHsgbWFwLCBzaGFyZVJlcGxheSwgc3dpdGNoTWFwLCB0YXAgfSBmcm9tICdyeGpzJztcblxuaW1wb3J0IHsgU0NfQkFOTkVSX0RVUkFUSU9OIH0gZnJvbSAnLi4vcHJvdmlkZXJzJztcblxuLyoqXG4gKiDQkdCw0L3QvdC10YAg0YEg0L/RgNC+0LrRgNGD0YLQutC+0Lkg0L/QtdGA0LXQtNCw0L3QvdGL0YUge0BsaW5rIFRlbXBsYXRlUmVmfSDRjdC70LXQvNC10L3RgtC+0LIsINC4INCx0LDQvdC90LXRgNC+0LIg0LvQvtC60LDRhtC40LguXG4gKi9cbkBDb21wb25lbnQoe1xuICAgIHNlbGVjdG9yOiAnc2MtYmFubmVyJyxcbiAgICB0ZW1wbGF0ZVVybDogJy4vc2MtYmFubmVyLmNvbXBvbmVudC5odG1sJyxcbiAgICBzdHlsZVVybHM6IFsnLi9zYy1iYW5uZXIuY29tcG9uZW50LnNjc3MnXSxcbiAgICBwcm92aWRlcnM6IFtJbnRlcnNlY3Rpb25PYnNlcnZlclNlcnZpY2VdLFxuICAgIGhvc3Q6IHtcbiAgICAgICAgbmdTa2lwSHlkcmF0aW9uOiAndHJ1ZScsXG4gICAgICAgICdbY2xhc3MuIWhpZGRlbl0nOiAndG90YWxJdGVtcygpID09PSAwJyxcbiAgICB9LFxuICAgIGNoYW5nZURldGVjdGlvbjogQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3kuT25QdXNoLFxufSlcbmV4cG9ydCBjbGFzcyBTY0Jhbm5lckNvbXBvbmVudCB7XG4gICAgLyoqXG4gICAgICog0J/RgNC40LfQvdCw0LosINGH0YLQviDQvdC10L7QsdGF0L7QtNC40LzQviDQv9C+0LrQsNC30YvQstCw0YLRjCDQutC90L7Qv9C60Lgg0L3QsNCy0LjQs9Cw0YbQuNC4LlxuICAgICAqL1xuICAgIEBJbnB1dCgpXG4gICAgcHVibGljIG5hdmlnYXRlQnV0dG9uOiBib29sZWFuID0gdHJ1ZTtcblxuICAgIC8qKlxuICAgICAqINCY0L3RgtC10YDQstCw0Lsg0LDQstGC0L7QvNCw0YLQuNGH0LXRgdC60L7QuSDRgdC80LXQvdGLINGB0LvQsNC50LTQvtCyINCyINC80LjQu9C70LjRgdC10LrRg9C90LTQsNGFICjQuNGB0L/QvtC70YzQt9GD0LnRgtC1IDAsINGH0YLQvtCx0Ysg0L7RgtC60LvRjtGH0LjRgtGMINCw0LLRgtC+0LzQsNGC0LjRh9C10YHQutGD0Y4g0YHQvNC10L3RgyDRgdC70LDQudC00LApLlxuICAgICAqL1xuICAgIEBJbnB1dCgpXG4gICAgcHVibGljIGR1cmF0aW9uOiBudW1iZXIgPSBpbmplY3QoU0NfQkFOTkVSX0RVUkFUSU9OKTtcblxuICAgIC8qKlxuICAgICAqINCc0LXRgdGC0L7Qv9C+0LvQvtC20LXQvdC40LUg0LHQsNC90L3QtdGA0LAuXG4gICAgICovXG4gICAgcHVibGljIGJhbm5lckxvY2F0aW9uOiBJbnB1dFNpZ25hbDxzdHJpbmcgfCB1bmRlZmluZWQ+ID0gaW5wdXQ8c3RyaW5nPigpO1xuXG4gICAgLyoqXG4gICAgICog0J/RgNC40LfQvdCw0LosINGH0YLQviDQutC+0LzQv9C+0L3QtdC90YIg0LTQvtC70LbQtdC9INGA0LDRgdGC0Y/Qs9C40LLQsNGC0YzRgdGPLlxuICAgICAqL1xuICAgIEBJbnB1dCgpXG4gICAgcHVibGljIHJlc2l6YWJsZTogYm9vbGVhbiA9IGZhbHNlO1xuXG4gICAgLyoqXG4gICAgICog0KHQvtCx0YvRgtC40LUg0LfQsNCz0YDRg9C30LrQuCDQsdCw0L3QvdC10YDQvtCyINGBINC60L7Qu9C40YfQtdGB0YLQstC+0Lwg0L/QvtC70YPRh9C10L3QvdGL0YUg0LHQsNC90L3QtdGA0L7Qsi5cbiAgICAgKi9cbiAgICBAT3V0cHV0KClcbiAgICBwdWJsaWMgbG9hZEJhbm5lcnNFdmVudDogRXZlbnRFbWl0dGVyPG51bWJlcj4gPSBuZXcgRXZlbnRFbWl0dGVyPG51bWJlcj4oKTtcblxuICAgIC8qKlxuICAgICAqINCh0L7QsdGL0YLQuNC1INC90LDQttCw0YLQuNGPINC90LAg0LjQt9C+0LHRgNCw0LbQtdC90LjQtSDQsdCw0L3QvdC10YDQsC5cbiAgICAgKi9cbiAgICBAT3V0cHV0KClcbiAgICBwdWJsaWMgY2xpY2tCYW5uZXJFdmVudDogRXZlbnRFbWl0dGVyPFNjQmFubmVyPiA9IG5ldyBFdmVudEVtaXR0ZXI8U2NCYW5uZXI+KCk7XG5cbiAgICAvKipcbiAgICAgKiDQodC/0LjRgdC+0Log0YHRgdGL0LvQvtC6INC90LAg0Y3Qu9C10LzQtdC90YIg0L/RgNC10LTRgdGC0LDQstC70LXQvdC40Lkg0YjQsNCx0LvQvtC90L7Qsi5cbiAgICAgKi9cbiAgICBAQ29udGVudENoaWxkcmVuKCdiYW5uZXInKVxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tZXhwbGljaXQtYW55XG4gICAgcHVibGljIGJhbm5lcnNMaXN0UmVmOiBRdWVyeUxpc3Q8VGVtcGxhdGVSZWY8YW55Pj47XG5cbiAgICAvKipcbiAgICAgKiDQodCy0L7QudGB0YLQstC+LCDQvtGCINC60L7RgtC+0YDQvtCz0L4g0LfQsNCy0LjRgdC40YIg0YHQvtC+0YLQvdC+0YjQtdC90LjQtSBgOmhvc3RgINC60L7QvNC/0L7QvdC10L3RgtCwLlxuICAgICAqL1xuICAgIEBIb3N0QmluZGluZygnc3R5bGUuYXNwZWN0LXJhdGlvJylcbiAgICBwdWJsaWMgYXNwZWN0UmF0aW86IHN0cmluZyA9ICcnO1xuXG4gICAgLyoqXG4gICAgICog0JjQtNC10L3RgtC40YTQuNC60LDRgtC+0YAg0YLQtdC60YPRidC10LPQviDQsdCw0L3QvdC10YDQsC5cbiAgICAgKi9cbiAgICBwcm90ZWN0ZWQgY3VycmVudEJhbm5lcklkOiBudW1iZXIgPSAwO1xuXG4gICAgLyoqXG4gICAgICog0KHQtdGA0LLQuNGBINC00LvRjyDRgNCw0LHQvtGC0Ysg0YEg0LTQsNC90L3Ri9C80Lgg0L4g0LHQsNC90L3QtdGA0LDRhS5cbiAgICAgKi9cbiAgICBwcml2YXRlIHJlYWRvbmx5IGJhbm5lclNlcnZpY2UgPSBpbmplY3QoU2NCYW5uZXJTZXJ2aWNlKTtcblxuICAgIC8qKlxuICAgICAqINCh0LXRgNCy0LjRgSDQtNC70Y8g0YHQsdC+0YDQsCDQvNC10YLRgNC40Log0L4g0LTQtdC50YHRgtCy0LjRj9GFINC/0L7Qu9GM0LfQvtCy0LDRgtC10LvQtdC5LlxuICAgICAqL1xuICAgIHByaXZhdGUgcmVhZG9ubHkgdXNlck1ldHJpa2FTZXJ2aWNlID0gaW5qZWN0KFNjVXNlck1ldHJpa2FTZXJ2aWNlKTtcblxuICAgIC8qKlxuICAgICAqINCh0L/QuNGB0L7QuiDQsdCw0L3QvdC10YDQvtCyLlxuICAgICAqL1xuICAgIHByb3RlY3RlZCByZWFkb25seSBiYW5uZXJzJCA9IHRvT2JzZXJ2YWJsZSh0aGlzLmJhbm5lckxvY2F0aW9uKS5waXBlKFxuICAgICAgICBzd2l0Y2hNYXAoKGJhbm5lckxvY2F0aW9uKSA9PlxuICAgICAgICAgICAgdGhpcy5iYW5uZXJTZXJ2aWNlLmJhbm5lcnMkLnBpcGUoXG4gICAgICAgICAgICAgICAgbWFwKChiYW5uZXJzKSA9PiBiYW5uZXJzLmZpbHRlcigoYmFubmVyKSA9PiBiYW5uZXIubG9jYXRpb24gPT09IGJhbm5lckxvY2F0aW9uKSksXG4gICAgICAgICAgICAgICAgdGFwKChiYW5uZXJzKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChiYW5uZXJzLmxlbmd0aCA+IDAgJiYgIXRoaXMucmVzaXphYmxlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmFzcGVjdFJhdGlvID0gYCR7YmFubmVyc1swXS53aWR0aH0gLyAke2Jhbm5lcnNbMF0uaGVpZ2h0fWA7XG4gICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICB0aGlzLmxvYWRCYW5uZXJzRXZlbnQuZW1pdChiYW5uZXJzLmxlbmd0aCk7XG4gICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgIClcbiAgICAgICAgKSxcbiAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIHJ4anMvbm8tc2hhcmVyZXBsYXlcbiAgICAgICAgc2hhcmVSZXBsYXkoMSlcbiAgICApO1xuXG4gICAgcHJvdGVjdGVkIHJlYWRvbmx5IGJhbm5lcnMgPSB0b1NpZ25hbCh0aGlzLmJhbm5lcnMkLCB7IGluaXRpYWxWYWx1ZTogW10gYXMgU2NCYW5uZXJbXSB9KTtcblxuICAgIC8qKlxuICAgICAqINCe0LHRidC10LUg0LrQvtC70LjRh9C10YHRgtCy0L4g0YHQu9Cw0LnQtNC+0LIgKNCx0LDQvdC90LXRgNGLICsg0LrQvtC90YLQtdC90YIt0L/RgNC+0LXQutGG0LjRjykuXG4gICAgICog0JjRgdC/0L7Qu9GM0LfRg9C10YLRgdGPINC00LvRjyDQvtGC0LrQu9GO0YfQtdC90LjRjyDQsNCy0YLQvtGB0LrRgNC+0LvQu9CwINC/0YDQuCAw4oCTMSDRgdC70LDQudC00LDRhSwg0YfRgtC+0LHRiyDQvdC1INCy0YvQt9GL0LLQsNGC0YxcbiAgICAgKiB0dWlDbGFtcCDRgSBtYXggPCBtaW4g0LIgVHVpQ2Fyb3VzZWxDb21wb25lbnQub25BdXRvc2Nyb2xsIOKGkiB1cGRhdGVJbmRleC5cbiAgICAgKi9cbiAgICBwdWJsaWMgcmVhZG9ubHkgdG90YWxJdGVtcyA9IGNvbXB1dGVkKCgpID0+IHRoaXMuYmFubmVycygpLmxlbmd0aCArIHRoaXMuYmFubmVyc0xpc3RSZWYubGVuZ3RoKTtcblxuICAgIC8qKlxuICAgICAqIHRyYWNrQnkg0LTQu9GPICpuZ0ZvciDQsdCw0L3QvdC10YDQvtCyOiDRgdC+0YXRgNCw0L3Rj9C10YIg0YPQt9C70Ysg0L/RgNC4INC+0LHQvdC+0LLQu9C10L3QuNC4INGB0L/QuNGB0LrQsCxcbiAgICAgKiDRh9GC0L7QsdGLINC60LDRgNGD0YHQtdC70Ywg0L3QtSDQstC40LTQtdC70LAgMCDRgdC70LDQudC00L7QsiDQvNC10LbQtNGDINGC0LjQutCw0LzQuCDQuCDQvdC1INCy0YvQt9GL0LLQsNC70LAgdHVpQ2xhbXAo4oCmLCAtMSkuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gX2luZGV4INCY0L3QtNC10LrRgS5cbiAgICAgKiBAcGFyYW0gYmFubmVyINCU0LDQvdC90YvQtSDQviDQsdCw0L3QvdC10YDQtS5cbiAgICAgKi9cbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgY2xhc3MtbWV0aG9kcy11c2UtdGhpc1xuICAgIHB1YmxpYyB0cmFja0J5QmFubmVySWQoX2luZGV4OiBudW1iZXIsIGJhbm5lcjogU2NCYW5uZXIpOiBudW1iZXIge1xuICAgICAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLXVubmVjZXNzYXJ5LWNvbmRpdGlvblxuICAgICAgICByZXR1cm4gYmFubmVyLmlkID8/IF9pbmRleDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiDQntCx0YDQsNCx0L7RgtGH0LjQuiDQvdCw0LbQsNGC0LjRjyDQvdCwINCx0LDQvdC90LXRgC5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBiYW5uZXIg0JHQsNC90L3QtdGALCDQv9C+INGB0YHRi9C70LrQtSDQutC+0YLQvtGA0L7Qs9C+INGB0L7QstC10YDRiNGR0L0g0L/QtdGA0LXRhdC+0LQuXG4gICAgICovXG4gICAgcHVibGljIG9uQ2xpY2soYmFubmVyOiBTY0Jhbm5lcik6IHZvaWQge1xuICAgICAgICB0aGlzLnVzZXJNZXRyaWthU2VydmljZS5lbWl0VXNlck1ldHJpa2FFdmVudCh7XG4gICAgICAgICAgICB0YXJnZXQ6IFNjVXNlck1ldHJpa2FHb2Fsc0VudW0uYmFubmVyQ2xpY2ssXG4gICAgICAgICAgICBwYXJhbXM6IHtcbiAgICAgICAgICAgICAgICBiYW5uZXJfaWQ6IGJhbm5lci5pZCxcbiAgICAgICAgICAgIH0sXG4gICAgICAgIH0pO1xuXG4gICAgICAgIGlmIChiYW5uZXIudXJsKSB7XG4gICAgICAgICAgICB0aGlzLmNsaWNrQmFubmVyRXZlbnQuZW1pdChiYW5uZXIpO1xuICAgICAgICB9XG4gICAgfVxufVxuIiwiQGxldCBlZmZlY3RpdmVEdXJhdGlvbiA9IHRvdGFsSXRlbXMoKSA+IDEgPyBkdXJhdGlvbiA6IDA7XG5cbjx0dWktY2Fyb3VzZWxcbiAgICBbZHVyYXRpb25dPVwiZWZmZWN0aXZlRHVyYXRpb25cIlxuICAgICNjYXJvdXNlbFxuICAgIFthdHRyLnJlc2l6YWJsZV09XCJyZXNpemFibGVcIlxuICAgIGNsYXNzPVwic2l6ZS1mdWxsIG92ZXJmbG93LWhpZGRlbiByb3VuZGVkLXhsIGJnLXdoaXRlXCJcbiAgICBbKGluZGV4KV09XCJjdXJyZW50QmFubmVySWRcIlxuPlxuICAgIEBmb3IgKGJhbm5lciBvZiBiYW5uZXJzKCk7IHRyYWNrIHRyYWNrQnlCYW5uZXJJZCgkaW5kZXgsIGJhbm5lcikpIHtcbiAgICAgICAgQHN3aXRjaCAoYmFubmVyLm1lZGlhVHlwZSkge1xuICAgICAgICAgICAgQGNhc2UgKCdpbWFnZScpIHtcbiAgICAgICAgICAgICAgICA8YVxuICAgICAgICAgICAgICAgICAgICAqdHVpSXRlbVxuICAgICAgICAgICAgICAgICAgICAoY2xpY2spPVwib25DbGljayhiYW5uZXIpXCJcbiAgICAgICAgICAgICAgICAgICAgdGFyZ2V0PVwiX2JsYW5rXCJcbiAgICAgICAgICAgICAgICAgICAgW3RpdGxlXT1cImJhbm5lci50aXRsZVwiXG4gICAgICAgICAgICAgICAgICAgIFtzdHlsZS5hc3BlY3QtcmF0aW9dPVwiYXNwZWN0UmF0aW9cIlxuICAgICAgICAgICAgICAgICAgICBbYXR0ci5ocmVmXT1cImJhbm5lci51cmwgPyBiYW5uZXIudXJsIDogbnVsbFwiXG4gICAgICAgICAgICAgICAgICAgIGNsYXNzPVwic2l6ZS1mdWxsXCJcbiAgICAgICAgICAgICAgICA+XG4gICAgICAgICAgICAgICAgICAgIDxwaWN0dXJlPlxuICAgICAgICAgICAgICAgICAgICAgICAgQGlmIChiYW5uZXIubWVkaWFGaWxlV2VicCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxzb3VyY2VcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZT1cImltYWdlL3dlYnBcIlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBbc3Jjc2V0XT1cImJhbm5lci5tZWRpYUZpbGVXZWJwIHwgc2NNZWRpYUltYWdlVHJhbnNmb3JtZXJcIlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8+XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICA8aW1nXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgW3NyY109XCJiYW5uZXIubWVkaWFGaWxlIHwgc2NNZWRpYUltYWdlVHJhbnNmb3JtZXI6IHRydWVcIlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIFthbHRdPVwiYmFubmVyLnRpdGxlXCJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcz1cInNpemUtZnVsbCBvYmplY3QtY292ZXJcIlxuICAgICAgICAgICAgICAgICAgICAgICAgLz5cbiAgICAgICAgICAgICAgICAgICAgPC9waWN0dXJlPlxuICAgICAgICAgICAgICAgIDwvYT5cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbiAgICBAZm9yIChpdGVtIG9mIGJhbm5lcnNMaXN0UmVmOyB0cmFjayAkaW5kZXgpIHtcbiAgICAgICAgPGRpdlxuICAgICAgICAgICAgKnR1aUl0ZW1cbiAgICAgICAgICAgIFtzdHlsZS5hc3BlY3QtcmF0aW9dPVwiYXNwZWN0UmF0aW9cIlxuICAgICAgICAgICAgY2xhc3M9XCJzaXplLWZ1bGwgb3ZlcmZsb3ctaGlkZGVuXCJcbiAgICAgICAgPlxuICAgICAgICAgICAgPG5nLWNvbnRhaW5lciBbbmdUZW1wbGF0ZU91dGxldF09XCJpdGVtXCI+PC9uZy1jb250YWluZXI+XG4gICAgICAgIDwvZGl2PlxuICAgIH1cbjwvdHVpLWNhcm91c2VsPlxuXG5AaWYgKG5hdmlnYXRlQnV0dG9uICYmIGR1cmF0aW9uICYmIGJhbm5lcnMoKSAmJiB0b3RhbEl0ZW1zKCkgPiAxKSB7XG4gICAgPGRpdlxuICAgICAgICB0dWlUaGVtZT1cImxpZ2h0XCJcbiAgICAgICAgY2xhc3M9XCJmbGV4IGl0ZW1zLWNlbnRlclwiXG4gICAgPlxuICAgICAgICA8YnV0dG9uXG4gICAgICAgICAgICB0dWlJY29uQnV0dG9uXG4gICAgICAgICAgICBpY29uU3RhcnQ9XCJAdHVpLmNoZXZyb24tbGVmdFwiXG4gICAgICAgICAgICBzaXplPVwibVwiXG4gICAgICAgICAgICBbc3R5bGUuYm9yZGVyLXJhZGl1cy4lXT1cIjEwMFwiXG4gICAgICAgICAgICBhcHBlYXJhbmNlPVwiZmxhdFwiXG4gICAgICAgICAgICAoY2xpY2spPVwiY2Fyb3VzZWwucHJldigpXCJcbiAgICAgICAgICAgIGNsYXNzPVwiIWFic29sdXRlIGxlZnQtMlwiXG4gICAgICAgID48L2J1dHRvbj5cbiAgICAgICAgPGJ1dHRvblxuICAgICAgICAgICAgdHVpSWNvbkJ1dHRvblxuICAgICAgICAgICAgaWNvblN0YXJ0PVwiQHR1aS5jaGV2cm9uLXJpZ2h0XCJcbiAgICAgICAgICAgIHNpemU9XCJtXCJcbiAgICAgICAgICAgIFtzdHlsZS5ib3JkZXItcmFkaXVzLiVdPVwiMTAwXCJcbiAgICAgICAgICAgIGFwcGVhcmFuY2U9XCJmbGF0XCJcbiAgICAgICAgICAgIChjbGljayk9XCJjYXJvdXNlbC5uZXh0KClcIlxuICAgICAgICAgICAgY2xhc3M9XCIhYWJzb2x1dGUgcmlnaHQtMlwiXG4gICAgICAgID48L2J1dHRvbj5cbiAgICA8L2Rpdj5cbn1cbiJdfQ==
176
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2MtYmFubmVyLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Byb2plY3RzL2NsaWVudC11aS9iYW5uZXIvc2MtYmFubmVyLmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uL3Byb2plY3RzL2NsaWVudC11aS9iYW5uZXIvc2MtYmFubmVyLmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFFSCx1QkFBdUIsRUFDdkIsU0FBUyxFQUNULFFBQVEsRUFDUixlQUFlLEVBQ2YsVUFBVSxFQUNWLFlBQVksRUFDWixXQUFXLEVBQ1gsTUFBTSxFQUNOLEtBQUssRUFDTCxLQUFLLEVBRUwsTUFBTSxFQUVOLE1BQU0sR0FHVCxNQUFNLGVBQWUsQ0FBQztBQUN2QixPQUFPLEVBQUUsa0JBQWtCLEVBQUUsWUFBWSxFQUFFLFFBQVEsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ3hGLE9BQU8sRUFBRSwyQkFBMkIsRUFBRSxNQUFNLG9DQUFvQyxDQUFDO0FBQ2pGLE9BQU8sRUFBWSxlQUFlLEVBQUUsc0JBQXNCLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUNqSCxPQUFPLEVBQUUsaUJBQWlCLEVBQWlCLGVBQWUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUNsRixPQUFPLEVBQUUsR0FBRyxFQUFFLFdBQVcsRUFBRSxTQUFTLEVBQUUsR0FBRyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBRXhELE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGNBQWMsQ0FBQzs7Ozs7OztBQUVsRDs7R0FFRztBQWFILE1BQU0sT0FBTyxpQkFBaUI7SUFaOUI7UUFhSTs7V0FFRztRQUVJLG1CQUFjLEdBQVksSUFBSSxDQUFDO1FBRXRDOztXQUVHO1FBRUksYUFBUSxHQUFXLE1BQU0sQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDO1FBRXJEOztXQUVHO1FBQ0ksbUJBQWMsR0FBb0MsS0FBSyxFQUFVLENB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aUJBQWlCLEVBQUUsUUFBUSxFQUFFLEVBQUUsT0FBTyxFQUFFLE1BQU0sQ0FBQyxpQkFBaUIsRUFBRSxTQUFTLEVBQUUsRUFBRSxFQUFFLEVBQUUsQ0FBQyxRQUNuSjt3QkFDRixlQUFlLEVBQUUsTUFBTTt3QkFDdkIsaUJBQWlCLEVBQUUsb0JBQW9CO3dCQUN2QyxjQUFjLEVBQUUsZ0JBQWdCO3FCQUNuQyxtQkFDZ0IsdUJBQXVCLENBQUMsTUFBTTs4QkFPeEMsY0FBYztzQkFEcEIsS0FBSztnQkFPQyxRQUFRO3NCQURkLEtBQUs7Z0JBWUMsU0FBUztzQkFEZixLQUFLO2dCQU9DLGdCQUFnQjtzQkFEdEIsTUFBTTtnQkFPQSxnQkFBZ0I7c0JBRHRCLE1BQU07Z0JBUUEsY0FBYztzQkFGcEIsZUFBZTt1QkFBQyxRQUFRO2dCQVFsQixXQUFXO3NCQURqQixXQUFXO3VCQUFDLG9CQUFvQiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gICAgQWZ0ZXJWaWV3SW5pdCxcbiAgICBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneSxcbiAgICBDb21wb25lbnQsXG4gICAgY29tcHV0ZWQsXG4gICAgQ29udGVudENoaWxkcmVuLFxuICAgIERlc3Ryb3lSZWYsXG4gICAgRXZlbnRFbWl0dGVyLFxuICAgIEhvc3RCaW5kaW5nLFxuICAgIGluamVjdCxcbiAgICBJbnB1dCxcbiAgICBpbnB1dCxcbiAgICBJbnB1dFNpZ25hbCxcbiAgICBPdXRwdXQsXG4gICAgUXVlcnlMaXN0LFxuICAgIHNpZ25hbCxcbiAgICBUZW1wbGF0ZVJlZixcbiAgICBXcml0YWJsZVNpZ25hbCxcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyB0YWtlVW50aWxEZXN0cm95ZWQsIHRvT2JzZXJ2YWJsZSwgdG9TaWduYWwgfSBmcm9tICdAYW5ndWxhci9jb3JlL3J4anMtaW50ZXJvcCc7XG5pbXBvcnQgeyBJbnRlcnNlY3Rpb25PYnNlcnZlclNlcnZpY2UgfSBmcm9tICdAbmctd2ViLWFwaXMvaW50ZXJzZWN0aW9uLW9ic2VydmVyJztcbmltcG9ydCB7IFNjQmFubmVyLCBTY0Jhbm5lclNlcnZpY2UsIFNjVXNlck1ldHJpa2FHb2Fsc0VudW0sIFNjVXNlck1ldHJpa2FTZXJ2aWNlIH0gZnJvbSAnQHNuYWJjZW50ci9jbGllbnQtY29yZSc7XG5pbXBvcnQgeyBUVUlfU1dJUEVfT1BUSU9OUywgVHVpU3dpcGVFdmVudCwgVHVpU3dpcGVTZXJ2aWNlIH0gZnJvbSAnQHRhaWdhLXVpL2Nkayc7XG5pbXBvcnQgeyBtYXAsIHNoYXJlUmVwbGF5LCBzd2l0Y2hNYXAsIHRhcCB9IGZyb20gJ3J4anMnO1xuXG5pbXBvcnQgeyBTQ19CQU5ORVJfRFVSQVRJT04gfSBmcm9tICcuLi9wcm92aWRlcnMnO1xuXG4vKipcbiAqINCR0LDQvdC90LXRgCDRgSDQv9GA0L7QutGA0YPRgtC60L7QuSDQv9C10YDQtdC00LDQvdC90YvRhSB7QGxpbmsgVGVtcGxhdGVSZWZ9INGN0LvQtdC80LXQvdGC0L7Qsiwg0Lgg0LHQsNC90L3QtdGA0L7QsiDQu9C+0LrQsNGG0LjQuC5cbiAqL1xuQENvbXBvbmVudCh7XG4gICAgc2VsZWN0b3I6ICdzYy1iYW5uZXInLFxuICAgIHRlbXBsYXRlVXJsOiAnLi9zYy1iYW5uZXIuY29tcG9uZW50Lmh0bWwnLFxuICAgIHN0eWxlVXJsczogWycuL3NjLWJhbm5lci5jb21wb25lbnQuc2NzcyddLFxuICAgIHByb3ZpZGVyczogW0ludGVyc2VjdGlvbk9ic2VydmVyU2VydmljZSwgVHVpU3dpcGVTZXJ2aWNlLCB7IHByb3ZpZGU6IFRVSV9TV0lQRV9PUFRJT05TLCB1c2VWYWx1ZTogeyB0aW1lb3V0OiBOdW1iZXIuUE9TSVRJVkVfSU5GSU5JVFksIHRocmVzaG9sZDogMzAgfSB9XSxcbiAgICBob3N0OiB7XG4gICAgICAgIG5nU2tpcEh5ZHJhdGlvbjogJ3RydWUnLFxuICAgICAgICAnW2NsYXNzLiFoaWRkZW5dJzogJ3RvdGFsSXRlbXMoKSA9PT0gMCcsXG4gICAgICAgICcodG91Y2hzdGFydCknOiAnb25Ub3VjaFN0YXJ0KCknLFxuICAgIH0sXG4gICAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2gsXG59KVxuZXhwb3J0IGNsYXNzIFNjQmFubmVyQ29tcG9uZW50IGltcGxlbWVudHMgQWZ0ZXJWaWV3SW5pdCB7XG4gICAgLyoqXG4gICAgICog0J/RgNC40LfQvdCw0LosINGH0YLQviDQvdC10L7QsdGF0L7QtNC40LzQviDQv9C+0LrQsNC30YvQstCw0YLRjCDQutC90L7Qv9C60Lgg0L3QsNCy0LjQs9Cw0YbQuNC4LlxuICAgICAqL1xuICAgIEBJbnB1dCgpXG4gICAgcHVibGljIG5hdmlnYXRlQnV0dG9uOiBib29sZWFuID0gdHJ1ZTtcblxuICAgIC8qKlxuICAgICAqINCY0L3RgtC10YDQstCw0Lsg0LDQstGC0L7QvNCw0YLQuNGH0LXRgdC60L7QuSDRgdC80LXQvdGLINGB0LvQsNC50LTQvtCyINCyINC80LjQu9C70LjRgdC10LrRg9C90LTQsNGFICjQuNGB0L/QvtC70YzQt9GD0LnRgtC1IDAsINGH0YLQvtCx0Ysg0L7RgtC60LvRjtGH0LjRgtGMINCw0LLRgtC+0LzQsNGC0LjRh9C10YHQutGD0Y4g0YHQvNC10L3RgyDRgdC70LDQudC00LApLlxuICAgICAqL1xuICAgIEBJbnB1dCgpXG4gICAgcHVibGljIGR1cmF0aW9uOiBudW1iZXIgPSBpbmplY3QoU0NfQkFOTkVSX0RVUkFUSU9OKTtcblxuICAgIC8qKlxuICAgICAqINCc0LXRgdGC0L7Qv9C+0LvQvtC20LXQvdC40LUg0LHQsNC90L3QtdGA0LAuXG4gICAgICovXG4gICAgcHVibGljIGJhbm5lckxvY2F0aW9uOiBJbnB1dFNpZ25hbDxzdHJpbmcgfCB1bmRlZmluZWQ+ID0gaW5wdXQ8c3RyaW5nPigpO1xuXG4gICAgLyoqXG4gICAgICog0J/RgNC40LfQvdCw0LosINGH0YLQviDQutC+0LzQv9C+0L3QtdC90YIg0LTQvtC70LbQtdC9INGA0LDRgdGC0Y/Qs9C40LLQsNGC0YzRgdGPLlxuICAgICAqL1xuICAgIEBJbnB1dCgpXG4gICAgcHVibGljIHJlc2l6YWJsZTogYm9vbGVhbiA9IGZhbHNlO1xuXG4gICAgLyoqXG4gICAgICog0KHQvtCx0YvRgtC40LUg0LfQsNCz0YDRg9C30LrQuCDQsdCw0L3QvdC10YDQvtCyINGBINC60L7Qu9C40YfQtdGB0YLQstC+0Lwg0L/QvtC70YPRh9C10L3QvdGL0YUg0LHQsNC90L3QtdGA0L7Qsi5cbiAgICAgKi9cbiAgICBAT3V0cHV0KClcbiAgICBwdWJsaWMgbG9hZEJhbm5lcnNFdmVudDogRXZlbnRFbWl0dGVyPG51bWJlcj4gPSBuZXcgRXZlbnRFbWl0dGVyPG51bWJlcj4oKTtcblxuICAgIC8qKlxuICAgICAqINCh0L7QsdGL0YLQuNC1INC90LDQttCw0YLQuNGPINC90LAg0LjQt9C+0LHRgNCw0LbQtdC90LjQtSDQsdCw0L3QvdC10YDQsC5cbiAgICAgKi9cbiAgICBAT3V0cHV0KClcbiAgICBwdWJsaWMgY2xpY2tCYW5uZXJFdmVudDogRXZlbnRFbWl0dGVyPFNjQmFubmVyPiA9IG5ldyBFdmVudEVtaXR0ZXI8U2NCYW5uZXI+KCk7XG5cbiAgICAvKipcbiAgICAgKiDQodC/0LjRgdC+0Log0YHRgdGL0LvQvtC6INC90LAg0Y3Qu9C10LzQtdC90YIg0L/RgNC10LTRgdGC0LDQstC70LXQvdC40Lkg0YjQsNCx0LvQvtC90L7Qsi5cbiAgICAgKi9cbiAgICBAQ29udGVudENoaWxkcmVuKCdiYW5uZXInKVxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tZXhwbGljaXQtYW55XG4gICAgcHVibGljIGJhbm5lcnNMaXN0UmVmOiBRdWVyeUxpc3Q8VGVtcGxhdGVSZWY8YW55Pj47XG5cbiAgICAvKipcbiAgICAgKiDQodCy0L7QudGB0YLQstC+LCDQvtGCINC60L7RgtC+0YDQvtCz0L4g0LfQsNCy0LjRgdC40YIg0YHQvtC+0YLQvdC+0YjQtdC90LjQtSBgOmhvc3RgINC60L7QvNC/0L7QvdC10L3RgtCwLlxuICAgICAqL1xuICAgIEBIb3N0QmluZGluZygnc3R5bGUuYXNwZWN0LXJhdGlvJylcbiAgICBwdWJsaWMgYXNwZWN0UmF0aW86IHN0cmluZyA9ICcnO1xuXG4gICAgLyoqXG4gICAgICog0JjQtNC10L3RgtC40YTQuNC60LDRgtC+0YAg0YLQtdC60YPRidC10LPQviDQsdCw0L3QvdC10YDQsC5cbiAgICAgKi9cbiAgICBwcm90ZWN0ZWQgcmVhZG9ubHkgY3VycmVudEJhbm5lcklkOiBXcml0YWJsZVNpZ25hbDxudW1iZXI+ID0gc2lnbmFsKDApO1xuXG4gICAgLyoqXG4gICAgICog0JjQtNC10L3RgtC40YTQuNC60LDRgtC+0YAg0LHQsNC90L3QtdGA0LAg0L3QsCDQvNC+0LzQtdC90YIg0L3QsNGH0LDQu9CwINC60LDRgdCw0L3QuNGPLlxuICAgICAqL1xuICAgIHByaXZhdGUgdG91Y2hTdGFydEJhbm5lcklkOiBudW1iZXIgPSAwO1xuXG4gICAgLyoqXG4gICAgICog0KHQtdGA0LLQuNGBINC00LvRjyDRgNCw0LHQvtGC0Ysg0YEg0LTQsNC90L3Ri9C80Lgg0L4g0LHQsNC90L3QtdGA0LDRhS5cbiAgICAgKi9cbiAgICBwcml2YXRlIHJlYWRvbmx5IGJhbm5lclNlcnZpY2UgPSBpbmplY3QoU2NCYW5uZXJTZXJ2aWNlKTtcblxuICAgIC8qKlxuICAgICAqINCh0LXRgNCy0LjRgSDQtNC70Y8g0YHQsdC+0YDQsCDQvNC10YLRgNC40Log0L4g0LTQtdC50YHRgtCy0LjRj9GFINC/0L7Qu9GM0LfQvtCy0LDRgtC10LvQtdC5LlxuICAgICAqL1xuICAgIHByaXZhdGUgcmVhZG9ubHkgdXNlck1ldHJpa2FTZXJ2aWNlID0gaW5qZWN0KFNjVXNlck1ldHJpa2FTZXJ2aWNlKTtcblxuICAgIC8qKlxuICAgICAqINCf0L7RgtC+0Log0YHQvtCx0YvRgtC40Lkg0YHQstCw0LnQv9CwINC/0L4g0LHQsNC90L3QtdGA0YMuXG4gICAgICovXG4gICAgcHJpdmF0ZSByZWFkb25seSBzd2lwZXMkID0gaW5qZWN0KFR1aVN3aXBlU2VydmljZSk7XG5cbiAgICAvKipcbiAgICAgKiDQodGB0YvQu9C60LAg0LTQu9GPINCw0LLRgtC+0LzQsNGC0LjRh9C10YHQutC+0LPQviDRg9C/0YDQsNCy0LvQtdC90LjRjyDRg9C90LjRh9GC0L7QttC10L3QuNC10Lwg0LfQsNCy0LjRgdC40LzQvtGB0YLQtdC5LlxuICAgICAqL1xuICAgIHByaXZhdGUgcmVhZG9ubHkgZGVzdHJveVJlZiA9IGluamVjdChEZXN0cm95UmVmKTtcblxuICAgIC8qKlxuICAgICAqINCh0L/QuNGB0L7QuiDQsdCw0L3QvdC10YDQvtCyLlxuICAgICAqL1xuICAgIHByb3RlY3RlZCByZWFkb25seSBiYW5uZXJzJCA9IHRvT2JzZXJ2YWJsZSh0aGlzLmJhbm5lckxvY2F0aW9uKS5waXBlKFxuICAgICAgICBzd2l0Y2hNYXAoKGJhbm5lckxvY2F0aW9uKSA9PlxuICAgICAgICAgICAgdGhpcy5iYW5uZXJTZXJ2aWNlLmJhbm5lcnMkLnBpcGUoXG4gICAgICAgICAgICAgICAgbWFwKChiYW5uZXJzKSA9PiBiYW5uZXJzLmZpbHRlcigoYmFubmVyKSA9PiBiYW5uZXIubG9jYXRpb24gPT09IGJhbm5lckxvY2F0aW9uKSksXG4gICAgICAgICAgICAgICAgdGFwKChiYW5uZXJzKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChiYW5uZXJzLmxlbmd0aCA+IDAgJiYgIXRoaXMucmVzaXphYmxlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmFzcGVjdFJhdGlvID0gYCR7YmFubmVyc1swXS53aWR0aH0gLyAke2Jhbm5lcnNbMF0uaGVpZ2h0fWA7XG4gICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICB0aGlzLmxvYWRCYW5uZXJzRXZlbnQuZW1pdChiYW5uZXJzLmxlbmd0aCk7XG4gICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgIClcbiAgICAgICAgKSxcbiAgICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIHJ4anMvbm8tc2hhcmVyZXBsYXlcbiAgICAgICAgc2hhcmVSZXBsYXkoMSlcbiAgICApO1xuXG4gICAgcHJvdGVjdGVkIHJlYWRvbmx5IGJhbm5lcnMgPSB0b1NpZ25hbCh0aGlzLmJhbm5lcnMkLCB7IGluaXRpYWxWYWx1ZTogW10gYXMgU2NCYW5uZXJbXSB9KTtcblxuICAgIC8qKlxuICAgICAqINCe0LHRidC10LUg0LrQvtC70LjRh9C10YHRgtCy0L4g0YHQu9Cw0LnQtNC+0LIgKNCx0LDQvdC90LXRgNGLICsg0LrQvtC90YLQtdC90YIt0L/RgNC+0LXQutGG0LjRjykuXG4gICAgICog0JjRgdC/0L7Qu9GM0LfRg9C10YLRgdGPINC00LvRjyDQvtGC0LrQu9GO0YfQtdC90LjRjyDQsNCy0YLQvtGB0LrRgNC+0LvQu9CwINC/0YDQuCAw4oCTMSDRgdC70LDQudC00LDRhSwg0YfRgtC+0LHRiyDQvdC1INCy0YvQt9GL0LLQsNGC0YxcbiAgICAgKiB0dWlDbGFtcCDRgSBtYXggPCBtaW4g0LIgVHVpQ2Fyb3VzZWxDb21wb25lbnQub25BdXRvc2Nyb2xsIOKGkiB1cGRhdGVJbmRleC5cbiAgICAgKi9cbiAgICBwdWJsaWMgcmVhZG9ubHkgdG90YWxJdGVtcyA9IGNvbXB1dGVkKCgpID0+IHRoaXMuYmFubmVycygpLmxlbmd0aCArIHRoaXMuYmFubmVyc0xpc3RSZWYubGVuZ3RoKTtcblxuICAgIC8qKlxuICAgICAqIEBpbmhlcml0RG9jXG4gICAgICovXG4gICAgcHVibGljIG5nQWZ0ZXJWaWV3SW5pdCgpOiB2b2lkIHtcbiAgICAgICAgLy8g0J/QvtC00L/QuNGB0LrQsCDQuNC00ZHRgiDQv9C+0YHQu9C1INC60LDRgNGD0YHQtdC70LgsINGH0YLQvtCx0Ysg0YHQstCw0LnQvyDQvtCx0YDQsNCx0LDRgtGL0LLQsNC70YHRjyDQv9C+0YHQu9C10LTQvdC40Lw6INC40L3QsNGH0LUg0LXRkSDRgdC+0LHRgdGC0LLQtdC90L3QsNGPINC+0LHRgNCw0LHQvtGC0LrQsCDQttC10YHRgtCwINC00L7QsdCw0LLQuNGCINC10YnRkSDQvtC00LjQvSDRgdC70LDQudC0LlxuICAgICAgICB0aGlzLnN3aXBlcyQucGlwZSh0YWtlVW50aWxEZXN0cm95ZWQodGhpcy5kZXN0cm95UmVmKSkuc3Vic2NyaWJlKChzd2lwZSkgPT4ge1xuICAgICAgICAgICAgdGhpcy5vblN3aXBlKHN3aXBlKTtcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogdHJhY2tCeSDQtNC70Y8gKm5nRm9yINCx0LDQvdC90LXRgNC+0LI6INGB0L7RhdGA0LDQvdGP0LXRgiDRg9C30LvRiyDQv9GA0Lgg0L7QsdC90L7QstC70LXQvdC40Lgg0YHQv9C40YHQutCwLFxuICAgICAqINGH0YLQvtCx0Ysg0LrQsNGA0YPRgdC10LvRjCDQvdC1INCy0LjQtNC10LvQsCAwINGB0LvQsNC50LTQvtCyINC80LXQttC00YMg0YLQuNC60LDQvNC4INC4INC90LUg0LLRi9C30YvQstCw0LvQsCB0dWlDbGFtcCjigKYsIC0xKS5cbiAgICAgKlxuICAgICAqIEBwYXJhbSBfaW5kZXgg0JjQvdC00LXQutGBLlxuICAgICAqIEBwYXJhbSBiYW5uZXIg0JTQsNC90L3Ri9C1INC+INCx0LDQvdC90LXRgNC1LlxuICAgICAqL1xuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBjbGFzcy1tZXRob2RzLXVzZS10aGlzXG4gICAgcHVibGljIHRyYWNrQnlCYW5uZXJJZChfaW5kZXg6IG51bWJlciwgYmFubmVyOiBTY0Jhbm5lcik6IG51bWJlciB7XG4gICAgICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tdW5uZWNlc3NhcnktY29uZGl0aW9uXG4gICAgICAgIHJldHVybiBiYW5uZXIuaWQgPz8gX2luZGV4O1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqINCe0LHRgNCw0LHQvtGC0YfQuNC6INC90LDRh9Cw0LvQsCDQutCw0YHQsNC90LjRjyDQsdCw0L3QvdC10YDQsC5cbiAgICAgKi9cbiAgICBwcm90ZWN0ZWQgb25Ub3VjaFN0YXJ0KCk6IHZvaWQge1xuICAgICAgICB0aGlzLnRvdWNoU3RhcnRCYW5uZXJJZCA9IHRoaXMuY3VycmVudEJhbm5lcklkKCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICog0J7QsdGA0LDQsdC+0YLRh9C40Log0L3QsNC20LDRgtC40Y8g0L3QsCDQsdCw0L3QvdC10YAuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gYmFubmVyINCR0LDQvdC90LXRgCwg0L/QviDRgdGB0YvQu9C60LUg0LrQvtGC0L7RgNC+0LPQviDRgdC+0LLQtdGA0YjRkdC9INC/0LXRgNC10YXQvtC0LlxuICAgICAqL1xuICAgIHB1YmxpYyBvbkNsaWNrKGJhbm5lcjogU2NCYW5uZXIpOiB2b2lkIHtcbiAgICAgICAgdGhpcy51c2VyTWV0cmlrYVNlcnZpY2UuZW1pdFVzZXJNZXRyaWthRXZlbnQoe1xuICAgICAgICAgICAgdGFyZ2V0OiBTY1VzZXJNZXRyaWthR29hbHNFbnVtLmJhbm5lckNsaWNrLFxuICAgICAgICAgICAgcGFyYW1zOiB7XG4gICAgICAgICAgICAgICAgYmFubmVyX2lkOiBiYW5uZXIuaWQsXG4gICAgICAgICAgICB9LFxuICAgICAgICB9KTtcblxuICAgICAgICBpZiAoYmFubmVyLnVybCkge1xuICAgICAgICAgICAgdGhpcy5jbGlja0Jhbm5lckV2ZW50LmVtaXQoYmFubmVyKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKlxuICAgICAqINCe0LHRgNCw0LHQvtGC0YfQuNC6INGB0LLQsNC50L/QsCDQv9C+INCx0LDQvdC90LXRgNGDLlxuICAgICAqXG4gICAgICog0JHQsNC90L3QtdGAINC+0YLRgdGH0LjRgtGL0LLQsNC10YLRgdGPINC+0YIg0L/QvtC60LDQt9Cw0L3QvdC+0LPQviDQsiDQvdCw0YfQsNC70LUg0LrQsNGB0LDQvdC40Y86INGH0LDRgdGC0Ywg0LbQtdGB0YLQvtCyINC60LDRgNGD0YHQtdC70Ywg0L7QsdGA0LDQsdCw0YLRi9Cy0LDQtdGCINGB0LDQvNCwXG4gICAgICogKNC60L7RgNC+0YLQutC40Lkg0YHQstCw0LnQvywg0L/QtdGA0LXRgtCw0YHQutC40LLQsNC90LjQtSDRgdC70LDQudC00LAg0L/QsNC70YzRhtC10LwpLCDQuCDQttC10YHRgiDQvdC1INC00L7Qu9C20LXQvSDQv9GA0L7Qu9C40YHRgtCw0YLRjCDQtNCy0LAg0LHQsNC90L3QtdGA0LAuXG4gICAgICpcbiAgICAgKiBAcGFyYW0gc3dpcGUg0KHQvtCx0YvRgtC40LUg0YHQstCw0LnQv9CwLlxuICAgICAqL1xuICAgIHByaXZhdGUgb25Td2lwZShzd2lwZTogVHVpU3dpcGVFdmVudCk6IHZvaWQge1xuICAgICAgICBpZiAoc3dpcGUuZGlyZWN0aW9uID09PSAnbGVmdCcpIHtcbiAgICAgICAgICAgIHRoaXMuY3VycmVudEJhbm5lcklkLnNldChNYXRoLm1pbih0aGlzLnRvdWNoU3RhcnRCYW5uZXJJZCArIDEsIHRoaXMudG90YWxJdGVtcygpIC0gMSkpO1xuICAgICAgICB9IGVsc2UgaWYgKHN3aXBlLmRpcmVjdGlvbiA9PT0gJ3JpZ2h0Jykge1xuICAgICAgICAgICAgdGhpcy5jdXJyZW50QmFubmVySWQuc2V0KE1hdGgubWF4KHRoaXMudG91Y2hTdGFydEJhbm5lcklkIC0gMSwgMCkpO1xuICAgICAgICB9XG4gICAgfVxufVxuIiwiQGxldCBlZmZlY3RpdmVEdXJhdGlvbiA9IHRvdGFsSXRlbXMoKSA+IDEgPyBkdXJhdGlvbiA6IDA7XG5cbjx0dWktY2Fyb3VzZWxcbiAgICBbZHVyYXRpb25dPVwiZWZmZWN0aXZlRHVyYXRpb25cIlxuICAgICNjYXJvdXNlbFxuICAgIFthdHRyLnJlc2l6YWJsZV09XCJyZXNpemFibGVcIlxuICAgIGNsYXNzPVwic2l6ZS1mdWxsIG92ZXJmbG93LWhpZGRlbiByb3VuZGVkLXhsIGJnLXdoaXRlXCJcbiAgICBbKGluZGV4KV09XCJjdXJyZW50QmFubmVySWRcIlxuPlxuICAgIEBmb3IgKGJhbm5lciBvZiBiYW5uZXJzKCk7IHRyYWNrIHRyYWNrQnlCYW5uZXJJZCgkaW5kZXgsIGJhbm5lcikpIHtcbiAgICAgICAgQHN3aXRjaCAoYmFubmVyLm1lZGlhVHlwZSkge1xuICAgICAgICAgICAgQGNhc2UgKCdpbWFnZScpIHtcbiAgICAgICAgICAgICAgICA8YVxuICAgICAgICAgICAgICAgICAgICAqdHVpSXRlbVxuICAgICAgICAgICAgICAgICAgICAoY2xpY2spPVwib25DbGljayhiYW5uZXIpXCJcbiAgICAgICAgICAgICAgICAgICAgdGFyZ2V0PVwiX2JsYW5rXCJcbiAgICAgICAgICAgICAgICAgICAgW3RpdGxlXT1cImJhbm5lci50aXRsZVwiXG4gICAgICAgICAgICAgICAgICAgIFtzdHlsZS5hc3BlY3QtcmF0aW9dPVwiYXNwZWN0UmF0aW9cIlxuICAgICAgICAgICAgICAgICAgICBbYXR0ci5ocmVmXT1cImJhbm5lci51cmwgPyBiYW5uZXIudXJsIDogbnVsbFwiXG4gICAgICAgICAgICAgICAgICAgIGNsYXNzPVwic2l6ZS1mdWxsXCJcbiAgICAgICAgICAgICAgICA+XG4gICAgICAgICAgICAgICAgICAgIDxwaWN0dXJlPlxuICAgICAgICAgICAgICAgICAgICAgICAgQGlmIChiYW5uZXIubWVkaWFGaWxlV2VicCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxzb3VyY2VcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZT1cImltYWdlL3dlYnBcIlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBbc3Jjc2V0XT1cImJhbm5lci5tZWRpYUZpbGVXZWJwIHwgc2NNZWRpYUltYWdlVHJhbnNmb3JtZXJcIlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8+XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICA8aW1nXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgW3NyY109XCJiYW5uZXIubWVkaWFGaWxlIHwgc2NNZWRpYUltYWdlVHJhbnNmb3JtZXI6IHRydWVcIlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIFthbHRdPVwiYmFubmVyLnRpdGxlXCJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcz1cInNpemUtZnVsbCBvYmplY3QtY292ZXJcIlxuICAgICAgICAgICAgICAgICAgICAgICAgLz5cbiAgICAgICAgICAgICAgICAgICAgPC9waWN0dXJlPlxuICAgICAgICAgICAgICAgIDwvYT5cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbiAgICBAZm9yIChpdGVtIG9mIGJhbm5lcnNMaXN0UmVmOyB0cmFjayAkaW5kZXgpIHtcbiAgICAgICAgPGRpdlxuICAgICAgICAgICAgKnR1aUl0ZW1cbiAgICAgICAgICAgIFtzdHlsZS5hc3BlY3QtcmF0aW9dPVwiYXNwZWN0UmF0aW9cIlxuICAgICAgICAgICAgY2xhc3M9XCJzaXplLWZ1bGwgb3ZlcmZsb3ctaGlkZGVuXCJcbiAgICAgICAgPlxuICAgICAgICAgICAgPG5nLWNvbnRhaW5lciBbbmdUZW1wbGF0ZU91dGxldF09XCJpdGVtXCI+PC9uZy1jb250YWluZXI+XG4gICAgICAgIDwvZGl2PlxuICAgIH1cbjwvdHVpLWNhcm91c2VsPlxuXG5AaWYgKG5hdmlnYXRlQnV0dG9uICYmIHRvdGFsSXRlbXMoKSA+IDEpIHtcbiAgICA8ZGl2XG4gICAgICAgIHR1aVRoZW1lPVwibGlnaHRcIlxuICAgICAgICBjbGFzcz1cImZsZXggaXRlbXMtY2VudGVyXCJcbiAgICA+XG4gICAgICAgIDxidXR0b25cbiAgICAgICAgICAgIHR1aUljb25CdXR0b25cbiAgICAgICAgICAgIGljb25TdGFydD1cIkB0dWkuY2hldnJvbi1sZWZ0XCJcbiAgICAgICAgICAgIHNpemU9XCJtXCJcbiAgICAgICAgICAgIFtzdHlsZS5ib3JkZXItcmFkaXVzLiVdPVwiMTAwXCJcbiAgICAgICAgICAgIGFwcGVhcmFuY2U9XCJmbGF0XCJcbiAgICAgICAgICAgIChjbGljayk9XCJjYXJvdXNlbC5wcmV2KClcIlxuICAgICAgICAgICAgY2xhc3M9XCIhYWJzb2x1dGUgbGVmdC0yXCJcbiAgICAgICAgPjwvYnV0dG9uPlxuICAgICAgICA8YnV0dG9uXG4gICAgICAgICAgICB0dWlJY29uQnV0dG9uXG4gICAgICAgICAgICBpY29uU3RhcnQ9XCJAdHVpLmNoZXZyb24tcmlnaHRcIlxuICAgICAgICAgICAgc2l6ZT1cIm1cIlxuICAgICAgICAgICAgW3N0eWxlLmJvcmRlci1yYWRpdXMuJV09XCIxMDBcIlxuICAgICAgICAgICAgYXBwZWFyYW5jZT1cImZsYXRcIlxuICAgICAgICAgICAgKGNsaWNrKT1cImNhcm91c2VsLm5leHQoKVwiXG4gICAgICAgICAgICBjbGFzcz1cIiFhYnNvbHV0ZSByaWdodC0yXCJcbiAgICAgICAgPjwvYnV0dG9uPlxuICAgIDwvZGl2PlxufVxuIl19
@@ -7,7 +7,7 @@ import { EMPTY, BehaviorSubject, switchMap, of, shareReplay, map, Subject, filte
7
7
  import { WA_WINDOW } from '@ng-web-apis/common';
8
8
  import { takeUntilDestroyed, toSignal, outputFromObservable, toObservable } from '@angular/core/rxjs-interop';
9
9
  import * as i4$1 from '@taiga-ui/cdk';
10
- import { TuiDay, TuiTime, TuiValueTransformer, TUI_IS_MOBILE, tuiControlValue, tuiIsPresent, tuiMarkControlAsTouchedAndValidate, tuiIsFalsy, TuiAutoFocus, TuiRepeatTimes, TuiHovered, TuiDayRange, TuiMonth, tuiPure, tuiProvide, TuiValidationError, TUI_WINDOW_SIZE, tuiClamp, TUI_TRUE_HANDLER } from '@taiga-ui/cdk';
10
+ import { TuiDay, TuiTime, TuiValueTransformer, TUI_IS_MOBILE, tuiControlValue, tuiIsPresent, tuiMarkControlAsTouchedAndValidate, tuiIsFalsy, TuiAutoFocus, TuiRepeatTimes, TuiSwipeService, TUI_SWIPE_OPTIONS, TuiHovered, TuiDayRange, TuiMonth, tuiPure, tuiProvide, TuiValidationError, TUI_WINDOW_SIZE, tuiClamp, TUI_TRUE_HANDLER } from '@taiga-ui/cdk';
11
11
  import * as i2$1 from '@taiga-ui/kit';
12
12
  import { TUI_DATE_VALUE_TRANSFORMER, TuiPreview, TuiAvatar, TuiAccordionItem, TuiElasticContainer, TuiAccordion, TuiFieldErrorPipe, TuiFilterByInputPipe, TuiStringifyContentPipe, TuiDataListWrapper, TuiButtonLoading, TuiSortCountriesPipe, TuiStepper, TuiCarousel, TuiPush, TuiCheckbox, TuiFiles, TuiChip, TuiInputNumber, TuiBadge, TuiTooltip, TuiHighlight, TuiLineClamp, TuiFade, TuiRange, tuiInputNumberOptionsProvider, TuiDataListWrapperComponent, TuiChevron, TuiSelect, TuiInputSlider, TuiTreeService, TuiTreeItemContent, TUI_TREE_START, TUI_TREE_CONTENT, TUI_TREE_LOADING, TUI_TREE_LOADER, TuiTree, TuiPreviewDialogService, tuiFilesAccepted, TuiInputFiles, TuiInputFilesDirective, TuiFilesComponent, TuiFile, TuiFileRejectedPipe, TUI_CONFIRM, tuiItemsHandlersProvider as tuiItemsHandlersProvider$1, TUI_PAGINATION_TEXTS, TuiAccordionDirective, TuiAccordionItemContent } from '@taiga-ui/kit';
13
13
  import { HttpClient, HttpErrorResponse } from '@angular/common/http';
@@ -4024,7 +4024,11 @@ class ScBannerComponent {
4024
4024
  /**
4025
4025
  * Идентификатор текущего баннера.
4026
4026
  */
4027
- this.currentBannerId = 0;
4027
+ this.currentBannerId = signal(0);
4028
+ /**
4029
+ * Идентификатор баннера на момент начала касания.
4030
+ */
4031
+ this.touchStartBannerId = 0;
4028
4032
  /**
4029
4033
  * Сервис для работы с данными о баннерах.
4030
4034
  */
@@ -4033,6 +4037,14 @@ class ScBannerComponent {
4033
4037
  * Сервис для сбора метрик о действиях пользователей.
4034
4038
  */
4035
4039
  this.userMetrikaService = inject(ScUserMetrikaService);
4040
+ /**
4041
+ * Поток событий свайпа по баннеру.
4042
+ */
4043
+ this.swipes$ = inject(TuiSwipeService);
4044
+ /**
4045
+ * Ссылка для автоматического управления уничтожением зависимостей.
4046
+ */
4047
+ this.destroyRef = inject(DestroyRef);
4036
4048
  /**
4037
4049
  * Список баннеров.
4038
4050
  */
@@ -4052,6 +4064,15 @@ class ScBannerComponent {
4052
4064
  */
4053
4065
  this.totalItems = computed(() => this.banners().length + this.bannersListRef.length);
4054
4066
  }
4067
+ /**
4068
+ * @inheritDoc
4069
+ */
4070
+ ngAfterViewInit() {
4071
+ // Подписка идёт после карусели, чтобы свайп обрабатывался последним: иначе её собственная обработка жеста добавит ещё один слайд.
4072
+ this.swipes$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((swipe) => {
4073
+ this.onSwipe(swipe);
4074
+ });
4075
+ }
4055
4076
  /**
4056
4077
  * trackBy для *ngFor баннеров: сохраняет узлы при обновлении списка,
4057
4078
  * чтобы карусель не видела 0 слайдов между тиками и не вызывала tuiClamp(…, -1).
@@ -4064,6 +4085,12 @@ class ScBannerComponent {
4064
4085
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
4065
4086
  return banner.id ?? _index;
4066
4087
  }
4088
+ /**
4089
+ * Обработчик начала касания баннера.
4090
+ */
4091
+ onTouchStart() {
4092
+ this.touchStartBannerId = this.currentBannerId();
4093
+ }
4067
4094
  /**
4068
4095
  * Обработчик нажатия на баннер.
4069
4096
  *
@@ -4080,15 +4107,32 @@ class ScBannerComponent {
4080
4107
  this.clickBannerEvent.emit(banner);
4081
4108
  }
4082
4109
  }
4110
+ /**
4111
+ * Обработчик свайпа по баннеру.
4112
+ *
4113
+ * Баннер отсчитывается от показанного в начале касания: часть жестов карусель обрабатывает сама
4114
+ * (короткий свайп, перетаскивание слайда пальцем), и жест не должен пролистать два баннера.
4115
+ *
4116
+ * @param swipe Событие свайпа.
4117
+ */
4118
+ onSwipe(swipe) {
4119
+ if (swipe.direction === 'left') {
4120
+ this.currentBannerId.set(Math.min(this.touchStartBannerId + 1, this.totalItems() - 1));
4121
+ }
4122
+ else if (swipe.direction === 'right') {
4123
+ this.currentBannerId.set(Math.max(this.touchStartBannerId - 1, 0));
4124
+ }
4125
+ }
4083
4126
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScBannerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4084
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScBannerComponent, selector: "sc-banner", inputs: { navigateButton: { classPropertyName: "navigateButton", publicName: "navigateButton", isSignal: false, isRequired: false, transformFunction: null }, duration: { classPropertyName: "duration", publicName: "duration", isSignal: false, isRequired: false, transformFunction: null }, bannerLocation: { classPropertyName: "bannerLocation", publicName: "bannerLocation", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { loadBannersEvent: "loadBannersEvent", clickBannerEvent: "clickBannerEvent" }, host: { attributes: { "ngSkipHydration": "true" }, properties: { "class.!hidden": "totalItems() === 0", "style.aspect-ratio": "this.aspectRatio" } }, providers: [IntersectionObserverService], queries: [{ propertyName: "bannersListRef", predicate: ["banner"] }], ngImport: i0, template: "@let effectiveDuration = totalItems() > 1 ? duration : 0;\n\n<tui-carousel\n [duration]=\"effectiveDuration\"\n #carousel\n [attr.resizable]=\"resizable\"\n class=\"size-full overflow-hidden rounded-xl bg-white\"\n [(index)]=\"currentBannerId\"\n>\n @for (banner of banners(); track trackByBannerId($index, banner)) {\n @switch (banner.mediaType) {\n @case ('image') {\n <a\n *tuiItem\n (click)=\"onClick(banner)\"\n target=\"_blank\"\n [title]=\"banner.title\"\n [style.aspect-ratio]=\"aspectRatio\"\n [attr.href]=\"banner.url ? banner.url : null\"\n class=\"size-full\"\n >\n <picture>\n @if (banner.mediaFileWebp) {\n <source\n type=\"image/webp\"\n [srcset]=\"banner.mediaFileWebp | scMediaImageTransformer\"\n />\n }\n <img\n [src]=\"banner.mediaFile | scMediaImageTransformer: true\"\n [alt]=\"banner.title\"\n class=\"size-full object-cover\"\n />\n </picture>\n </a>\n }\n }\n }\n @for (item of bannersListRef; track $index) {\n <div\n *tuiItem\n [style.aspect-ratio]=\"aspectRatio\"\n class=\"size-full overflow-hidden\"\n >\n <ng-container [ngTemplateOutlet]=\"item\"></ng-container>\n </div>\n }\n</tui-carousel>\n\n@if (navigateButton && duration && banners() && totalItems() > 1) {\n <div\n tuiTheme=\"light\"\n class=\"flex items-center\"\n >\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-left\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.prev()\"\n class=\"!absolute left-2\"\n ></button>\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-right\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.next()\"\n class=\"!absolute right-2\"\n ></button>\n </div>\n}\n", styles: [":host{--tui-carousel-padding: 0;display:flex;position:relative}::ng-deep tui-carousel[resizable=true] .t-scroller,::ng-deep tui-carousel[resizable=true] .t-items{width:100%;height:100%}\n"], dependencies: [{ kind: "directive", type: i2$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "directive", type: i1$4.TuiItem, selector: "[tuiItem]" }, { kind: "component", type: i2$1.TuiCarouselComponent, selector: "tui-carousel", inputs: ["draggable", "itemsCount", "index"], outputs: ["indexChange", "shift"] }, { kind: "pipe", type: i1.ScMediaImageTransformerPipe, name: "scMediaImageTransformer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4127
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ScBannerComponent, selector: "sc-banner", inputs: { navigateButton: { classPropertyName: "navigateButton", publicName: "navigateButton", isSignal: false, isRequired: false, transformFunction: null }, duration: { classPropertyName: "duration", publicName: "duration", isSignal: false, isRequired: false, transformFunction: null }, bannerLocation: { classPropertyName: "bannerLocation", publicName: "bannerLocation", isSignal: true, isRequired: false, transformFunction: null }, resizable: { classPropertyName: "resizable", publicName: "resizable", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { loadBannersEvent: "loadBannersEvent", clickBannerEvent: "clickBannerEvent" }, host: { attributes: { "ngSkipHydration": "true" }, listeners: { "touchstart": "onTouchStart()" }, properties: { "class.!hidden": "totalItems() === 0", "style.aspect-ratio": "this.aspectRatio" } }, providers: [IntersectionObserverService, TuiSwipeService, { provide: TUI_SWIPE_OPTIONS, useValue: { timeout: Number.POSITIVE_INFINITY, threshold: 30 } }], queries: [{ propertyName: "bannersListRef", predicate: ["banner"] }], ngImport: i0, template: "@let effectiveDuration = totalItems() > 1 ? duration : 0;\n\n<tui-carousel\n [duration]=\"effectiveDuration\"\n #carousel\n [attr.resizable]=\"resizable\"\n class=\"size-full overflow-hidden rounded-xl bg-white\"\n [(index)]=\"currentBannerId\"\n>\n @for (banner of banners(); track trackByBannerId($index, banner)) {\n @switch (banner.mediaType) {\n @case ('image') {\n <a\n *tuiItem\n (click)=\"onClick(banner)\"\n target=\"_blank\"\n [title]=\"banner.title\"\n [style.aspect-ratio]=\"aspectRatio\"\n [attr.href]=\"banner.url ? banner.url : null\"\n class=\"size-full\"\n >\n <picture>\n @if (banner.mediaFileWebp) {\n <source\n type=\"image/webp\"\n [srcset]=\"banner.mediaFileWebp | scMediaImageTransformer\"\n />\n }\n <img\n [src]=\"banner.mediaFile | scMediaImageTransformer: true\"\n [alt]=\"banner.title\"\n class=\"size-full object-cover\"\n />\n </picture>\n </a>\n }\n }\n }\n @for (item of bannersListRef; track $index) {\n <div\n *tuiItem\n [style.aspect-ratio]=\"aspectRatio\"\n class=\"size-full overflow-hidden\"\n >\n <ng-container [ngTemplateOutlet]=\"item\"></ng-container>\n </div>\n }\n</tui-carousel>\n\n@if (navigateButton && totalItems() > 1) {\n <div\n tuiTheme=\"light\"\n class=\"flex items-center\"\n >\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-left\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.prev()\"\n class=\"!absolute left-2\"\n ></button>\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-right\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.next()\"\n class=\"!absolute right-2\"\n ></button>\n </div>\n}\n", styles: [":host{--tui-carousel-padding: 0;display:flex;position:relative}::ng-deep tui-carousel[resizable=true] .t-scroller,::ng-deep tui-carousel[resizable=true] .t-items{width:100%;height:100%}\n"], dependencies: [{ kind: "directive", type: i2$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1$2.TuiButton, selector: "a[tuiButton],button[tuiButton],a[tuiIconButton],button[tuiIconButton]", inputs: ["size"] }, { kind: "directive", type: i1$4.TuiItem, selector: "[tuiItem]" }, { kind: "component", type: i2$1.TuiCarouselComponent, selector: "tui-carousel", inputs: ["draggable", "itemsCount", "index"], outputs: ["indexChange", "shift"] }, { kind: "pipe", type: i1.ScMediaImageTransformerPipe, name: "scMediaImageTransformer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4085
4128
  }
4086
4129
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ScBannerComponent, decorators: [{
4087
4130
  type: Component,
4088
- args: [{ selector: 'sc-banner', providers: [IntersectionObserverService], host: {
4131
+ args: [{ selector: 'sc-banner', providers: [IntersectionObserverService, TuiSwipeService, { provide: TUI_SWIPE_OPTIONS, useValue: { timeout: Number.POSITIVE_INFINITY, threshold: 30 } }], host: {
4089
4132
  ngSkipHydration: 'true',
4090
4133
  '[class.!hidden]': 'totalItems() === 0',
4091
- }, changeDetection: ChangeDetectionStrategy.OnPush, template: "@let effectiveDuration = totalItems() > 1 ? duration : 0;\n\n<tui-carousel\n [duration]=\"effectiveDuration\"\n #carousel\n [attr.resizable]=\"resizable\"\n class=\"size-full overflow-hidden rounded-xl bg-white\"\n [(index)]=\"currentBannerId\"\n>\n @for (banner of banners(); track trackByBannerId($index, banner)) {\n @switch (banner.mediaType) {\n @case ('image') {\n <a\n *tuiItem\n (click)=\"onClick(banner)\"\n target=\"_blank\"\n [title]=\"banner.title\"\n [style.aspect-ratio]=\"aspectRatio\"\n [attr.href]=\"banner.url ? banner.url : null\"\n class=\"size-full\"\n >\n <picture>\n @if (banner.mediaFileWebp) {\n <source\n type=\"image/webp\"\n [srcset]=\"banner.mediaFileWebp | scMediaImageTransformer\"\n />\n }\n <img\n [src]=\"banner.mediaFile | scMediaImageTransformer: true\"\n [alt]=\"banner.title\"\n class=\"size-full object-cover\"\n />\n </picture>\n </a>\n }\n }\n }\n @for (item of bannersListRef; track $index) {\n <div\n *tuiItem\n [style.aspect-ratio]=\"aspectRatio\"\n class=\"size-full overflow-hidden\"\n >\n <ng-container [ngTemplateOutlet]=\"item\"></ng-container>\n </div>\n }\n</tui-carousel>\n\n@if (navigateButton && duration && banners() && totalItems() > 1) {\n <div\n tuiTheme=\"light\"\n class=\"flex items-center\"\n >\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-left\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.prev()\"\n class=\"!absolute left-2\"\n ></button>\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-right\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.next()\"\n class=\"!absolute right-2\"\n ></button>\n </div>\n}\n", styles: [":host{--tui-carousel-padding: 0;display:flex;position:relative}::ng-deep tui-carousel[resizable=true] .t-scroller,::ng-deep tui-carousel[resizable=true] .t-items{width:100%;height:100%}\n"] }]
4134
+ '(touchstart)': 'onTouchStart()',
4135
+ }, changeDetection: ChangeDetectionStrategy.OnPush, template: "@let effectiveDuration = totalItems() > 1 ? duration : 0;\n\n<tui-carousel\n [duration]=\"effectiveDuration\"\n #carousel\n [attr.resizable]=\"resizable\"\n class=\"size-full overflow-hidden rounded-xl bg-white\"\n [(index)]=\"currentBannerId\"\n>\n @for (banner of banners(); track trackByBannerId($index, banner)) {\n @switch (banner.mediaType) {\n @case ('image') {\n <a\n *tuiItem\n (click)=\"onClick(banner)\"\n target=\"_blank\"\n [title]=\"banner.title\"\n [style.aspect-ratio]=\"aspectRatio\"\n [attr.href]=\"banner.url ? banner.url : null\"\n class=\"size-full\"\n >\n <picture>\n @if (banner.mediaFileWebp) {\n <source\n type=\"image/webp\"\n [srcset]=\"banner.mediaFileWebp | scMediaImageTransformer\"\n />\n }\n <img\n [src]=\"banner.mediaFile | scMediaImageTransformer: true\"\n [alt]=\"banner.title\"\n class=\"size-full object-cover\"\n />\n </picture>\n </a>\n }\n }\n }\n @for (item of bannersListRef; track $index) {\n <div\n *tuiItem\n [style.aspect-ratio]=\"aspectRatio\"\n class=\"size-full overflow-hidden\"\n >\n <ng-container [ngTemplateOutlet]=\"item\"></ng-container>\n </div>\n }\n</tui-carousel>\n\n@if (navigateButton && totalItems() > 1) {\n <div\n tuiTheme=\"light\"\n class=\"flex items-center\"\n >\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-left\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.prev()\"\n class=\"!absolute left-2\"\n ></button>\n <button\n tuiIconButton\n iconStart=\"@tui.chevron-right\"\n size=\"m\"\n [style.border-radius.%]=\"100\"\n appearance=\"flat\"\n (click)=\"carousel.next()\"\n class=\"!absolute right-2\"\n ></button>\n </div>\n}\n", styles: [":host{--tui-carousel-padding: 0;display:flex;position:relative}::ng-deep tui-carousel[resizable=true] .t-scroller,::ng-deep tui-carousel[resizable=true] .t-items{width:100%;height:100%}\n"] }]
4092
4136
  }], propDecorators: { navigateButton: [{
4093
4137
  type: Input
4094
4138
  }], duration: [{