\n
\n {this.state.articleVariantColors.map((sav, index) => {\n return (
\n
\n
)\n })}\n
\n
\n {\n this.state.articleVariantColors.map((sav, index) => {\n return (
)\n })\n }\n
\n {this.props.toggleAvailableSizes !== null &&\n
this.tabNav.setGroupAtt(el)}>\n
this.tabNav.setFocusAtt(el)}\n data-testid='arrow_left'\n onClick={(e) => { this.handleLeftRightClick(true, e) }}>\n
\n
\n
this.tabNav.setFocusAtt(el)}\n data-testid='arrow_right'\n onClick={(e) => { this.handleLeftRightClick(false, e) }}>\n
\n
\n div>\n }\n
\n {this.props.toggleAvailableSizes !== null &&\n
this.tabNav.setGroupAtt(el)}>\n
\n
this.tabNav.setFocusAtt(el)}\n data-testid='arrow_down'\n onClick={(e) => { this.handleUpDownClick(true, e) }}>\n
\n
\n
this.tabNav.setFocusAtt(el)}\n data-testid='arrow_up'\n onClick={(e) => { this.handleUpDownClick(false, e) }}>\n
\n
\n
\n
this.tabNav.setFocusAtt(el)}\n data-testid='close_button'\n onClick={this.props.toggleAvailableSizes}\n onKeyDown={this.handleKeyDown}>\n \n
\n div>\n }\n
\n );\n }\n}\n","import * as React from 'react';\nimport styles from './availableSizesBtn.scss';\nimport ArrowDownIcn from '../../../../Assets/svg/arrow_down';\nimport * as helper from '../../../../Common/html-helper';\nimport TabNavigationHelper from '../../../../Common/tabNavigationHelper';\n\nexport default class AvailableSizesBtn extends React.Component<{\n toggleAvailableSizes(): void;\n btnText: string;\n\n}, {}> {\n private tabNav: TabNavigationHelper;\n\n constructor(props) {\n super(props);\n this.tabNav = TabNavigationHelper.instance;\n }\n\n private handleKeyDown(event): void {\n // prevent events on tile core:\n event.stopPropagation(); \n }\n\n public render() {\n const btn_text = helper.decodeHTML(this.props.btnText);\n return (\n
this.tabNav.setFocusAtt(el)}\n onKeyDown={this.handleKeyDown}\n className={styles.available_sizes_btn}\n data-testid='available_sizes_btn'\n onClick={this.props.toggleAvailableSizes}\n onTouchEnd={this.props.toggleAvailableSizes}\n title={btn_text}\n >\n {btn_text}\n
\n
\n );\n }\n}\n","import React, { Component } from 'react';\nimport styles from './availableSizesOverlayModal.scss';\nimport AvailableSizes from './availableSizes';\nimport { IL10N } from '../../../../Common/l10n-keys';\nimport { ISdvColor } from '../article-tile.d';\nimport { ISize } from '../article-tile.d';\nimport { ISalesArticleVariantsLookup } from '../article-tile.d';\nimport classNames from 'classnames';\nimport Cross from '../../../../Assets/svg/cross';\nimport * as helper from '../../../../Common/html-helper';\n\nexport default class AvailableSizesOverlayModal extends Component<{l10n: IL10N,\n salesArticleVariantColors: ISdvColor[],\n toggleAvailableSizes(): void,\n sizes: ISize[]\n salesArticleVariantsLookup: ISalesArticleVariantsLookup[]\n showAvailableSizesOverlay: boolean,\n dcvColorCode: number\n masterArticleNo: number\n}, {}> {\n constructor(props) {\n super(props);\n }\n\n public render() {\n return (\n
\n
\n
\n \n {helper.decodeHTML(this.props.l10n.availableSizes)}\n \n \n \n \n
\n
\n
\n {helper.decodeHTML(this.props.l10n.close)} \n
\n
\n
\n );\n }\n}\n","import React, { Component } from 'react';\nimport styles from './bookmark.scss';\nimport { ISdvColor } from '../article-tile.d';\nimport { ViewType } from '../../../../Common/enums';\nimport TabNavigationHelper from '../../../../Common/tabNavigationHelper';\n\nexport default class CompareBookmark extends Component<{\n id: string, variant: ISdvColor,\n viewType: ViewType, gridNumber: number,\n categoryName: string, bookmarkMlt: string\n}, {}> {\n private tabNav: TabNavigationHelper;\n\n constructor(props) {\n super(props);\n this.tabNav = TabNavigationHelper.instance;\n }\n\n componentDidMount(): void {\n if (typeof window !== 'undefined' && window.shell) {\n // publish msg to bookmark-add-button\n window.shell.publishTo('ESCID.ESPP.Bookmark.CreateClientSideAddBtn', {\n containerId: this.getId(false),\n isMobile: false,\n isRedesignOrderModal: true,\n localization: { bookmark: this.props.bookmarkMlt }\n });\n }\n }\n\n private handleKeyDown(event): void {\n // prevent events on tile core:\n event.stopPropagation(); \n }\n\n public render() {\n return (\n
this.tabNav.setGroupAtt(el)}\n className={styles.bookmark_button}>\n
\n
\n );\n }\n\n private getId(isWrapper: boolean): string {\n let id = isWrapper ? `ats_${this.props.id}` : this.props.id;\n if (this.props.viewType === ViewType.TeaserSuggestion) id += `_${this.props.gridNumber}`;\n return id;\n }\n}\n","import React, { Component } from 'react';\nimport styles from './buttonColumn.scss';\nimport classNames from 'classnames';\nimport { ISdvColor } from '../article-tile.d';\nimport Bookmark from '../bookmark/bookmark';\nimport { ViewType } from '../../../../Common/enums';\nimport defaultStyles from '../../../../Common/defaults.scss';\n\nexport default class ButtonColumn extends Component<{\n variant: ISdvColor, seoSlug: string,\n navigationKey: string, on_hover: boolean, \n id: string, viewType: ViewType, gridNumber: number,\n mpcMobileTouchPointsVisible: boolean,\n IsGlobal: boolean, categoryName: string,\n bookmarkMlt: string}, {isMobileView}> {\n private readonly mpcRef = React.createRef
();\n\n // max break point when hover is not working:\n private breakpoint_tablet_max_width_1199 = parseInt(defaultStyles.breakpoint_tablet_max_width_1199);\n\n constructor(props) {\n super(props);\n\n this.state = {\n isMobileView: false\n };\n }\n\n componentDidMount(): void {\n // for tablet devices:\n this.setState({\n isMobileView: window.innerWidth <= this.breakpoint_tablet_max_width_1199\n });\n }\n\n componentDidUpdate(prevProps: { variant: ISdvColor }) {\n if (this.props.variant.salesArticleVariantKey != prevProps.variant.salesArticleVariantKey) {\n if (window.shell)\n window.shell.publishTo('ManualProductComparison.UpdateTouchPoints',\n {\n newSavKey: this.props.variant.salesArticleVariantKey,\n oldSavKey: prevProps.variant.salesArticleVariantKey\n });\n }\n } \n\n public render() {\n return (\n \n {(!this.state.isMobileView && !this.props.IsGlobal) &&
\n }\n \n \n );\n }\n}\n","import React from 'react';\nimport styles from './tileDetails.scss';\nimport { Component } from 'react';\nimport * as helper from '../../../../Common/html-helper';\nimport {\n IScalePriceData, ICurrencyInformation, IPriceData,\n IComparisonPrice, IVariantCountInfo\n} from '../article-tile.d';\nimport IconColors from '../../../../Assets/svg/icon_colors';\nimport classNames from 'classnames';\nimport {IPackagingInformation} from '../article-tile.d';\nimport { ArticleTileContext } from '../../../globalState/articleTileContext';\n\nexport default class TileDetails extends Component<{\n priceType: number, minimalPrice: IScalePriceData,\n description: string, variantCountInfo: IVariantCountInfo, currencyInformation: ICurrencyInformation,\n hideSelfForHover: boolean, originalPrice: IPriceData, comparisonPrice: IComparisonPrice, isMobile: boolean,\n flagAdvertisedAsCostFreeInSet: boolean, packagingInformation: IPackagingInformation}> {\n static contextType = ArticleTileContext;\n\n private isGlobal: boolean;\n private originalPriceElementRef = React.createRef();\n private reducedPriceElementRef = React.createRef();\n private withVat: boolean;\n\n constructor(props) {\n super(props);\n\n this.withVat = this.getWithVatValue(this.props.currencyInformation.priceMode,\n typeof localStorage !== 'undefined' ? localStorage.getItem('PriceMode') : '');\n\n this.isGlobal = typeof document !== 'undefined' && document.getElementsByClassName('fas_global').length > 0;\n }\n\n private getWithVatValue(priceModeFromServerSide: string, priceModeCookie?: string): boolean {\n let result;\n \n switch (priceModeFromServerSide) {\n case 'Gross':\n result = true;\n break;\n case 'Net':\n result = false;\n break;\n default:\n result =\n typeof document !== 'undefined'\n ? document.getElementsByClassName('current-pricemode-gross').length > 0\n : priceModeCookie == 'Gross';\n }\n \n return result;\n }\n\n private getPriceString(price: IPriceData): string {\n let result = '---';\n\n if (price) {\n result = this.getPriceStringInner(price.netValue, price.grossValue);\n }\n\n return result;\n }\n\n private getPriceStringComparison(price: IComparisonPrice): string {\n return this.getPriceStringInner(price.netValue, price.grossValue);\n }\n\n private getPriceStringInner(priceNet: string, priceGross: string): string {\n let numberStr: string;\n if (this.withVat && !this.isGlobal) {\n numberStr = priceGross;\n } else {\n numberStr = priceNet;\n }\n const result = this.props.currencyInformation.prependCurrencySymbol \n ? `${this.props.currencyInformation.symbol} ${numberStr}`\n : `${numberStr} ${this.props.currencyInformation.symbol}`;\n\n return result;\n }\n\n private get minQuantity(): number {\n return this.props.minimalPrice ? this.props.minimalPrice.quantity : 0;\n }\n\n public render() {\n const l10n = this.context.l10n;\n const quantityUnitTxt = this.props.packagingInformation ?\n this.props.packagingInformation.quantityUnit.plural : l10n.items;\n const vatAcronym = this.withVat ? l10n.incVATAcronym : l10n.exVATAcronym;\n const vatString = !this.isGlobal ? `(${vatAcronym}) ` : '';\n const extraStringInternal = (this.minQuantity > 1) ?\n `${vatString}${l10n.priceFromLocale} ${this.minQuantity} ${quantityUnitTxt}` : `${vatString}`;\n const extraString =\n helper.decodeHTML(extraStringInternal);\n const showOriginalPrice = this.props.originalPrice && !this.props.flagAdvertisedAsCostFreeInSet;\n const showBreakLineOriginalPrice = this.originalPriceElementRef.current ?\n this.originalPriceElementRef.current.clientHeight > 20 : false;\n\n const showBreakLineReducedPrice = this.reducedPriceElementRef.current ?\n this.reducedPriceElementRef.current.clientHeight > 20 : false;\n \n\n return (\n \n
\n {this.props.description}\n
\n
\n <>\n {this.props.isMobile ?\n <>\n
\n {this.renderVariantCountText()}\n
\n >\n : null\n }\n >\n
\n {showOriginalPrice &&\n \n {this.props.priceType > 1 &&\n \n {helper.decodeHTML(l10n.priceFromLocale)} \n \n }\n {showBreakLineOriginalPrice && }\n {this.getPriceString(this.props.originalPrice)} \n \n }\n\n \n {this.props.priceType > 1 &&\n \n {helper.decodeHTML(l10n.priceFromLocale)} \n \n }\n {showBreakLineReducedPrice && }\n {!this.isGlobal ?\n {this.getPriceString(this.props.minimalPrice.price)} \n : {this.getPriceString(this.props.minimalPrice.price)} \n }\n \n
\n {this.props.comparisonPrice ?\n
\n {helper.decodeHTML(l10n.comparisonBasicPrice)}{helper.decodeHTML(l10n.colon+' ')}\n {this.getPriceStringComparison(this.props.comparisonPrice)}\n /{this.props.comparisonPrice.unit}\n
: null\n }\n
\n {extraString} \n {!this.props.isMobile ? this.renderVariantCountText() : null}\n
\n
\n
\n );\n }\n\n private renderVariantCountText() {\n if (this.props.variantCountInfo && this.props.variantCountInfo.mode === VariantCountMode.Colors) {\n return \n \n {!this.props.variantCountInfo.labelContainsCountsAlready &&\n {this.props.variantCountInfo.totalCount} }\n \n {helper.decodeHTML(this.props.variantCountInfo.labelLocalized)}\n \n
\n } else if (this.props.variantCountInfo && this.props.variantCountInfo.mode === VariantCountMode.Models) {\n return \n {!this.props.variantCountInfo.labelContainsCountsAlready &&\n {this.props.variantCountInfo.totalCount} }\n \n {helper.decodeHTML(this.props.variantCountInfo.labelLocalized)}\n \n
\n }\n }\n}\n\n// placing it in article-tile.d.ts does not work because d.ts files are special \n// https://stackoverflow.com/questions/38335668/how-to-refer-to-typescript-enum-in-d-ts-file-when-using-amd\nenum VariantCountMode {\n None = 0,\n Colors = 1,\n Models = 2\n}\n","import React, { Component, RefObject } from 'react';\nimport styles from './eyeCatcher.scss';\nimport classNames from 'classnames';\nimport { ArticleTileContext } from '../../../globalState/articleTileContext';\n\ninterface IEyeCatcherProps\n{\n isNew: boolean,\n hasUvp: boolean,\n setPrice: boolean,\n sale: boolean,\n priceDeduction: number,\n availableSizesOpen: boolean\n flagAdvertisedAsCostFreeInSet: boolean;\n numberOfColorsForAvailableSizes: number;\n eyeCatcherRef?: RefObject;\n}\n\nexport default class EyeCatcher extends Component {\n static contextType = ArticleTileContext;\n\n constructor(props) {\n super(props);\n }\n\n private get hasPriceDeduction(): boolean {\n return this.props.priceDeduction && this.props.priceDeduction != 0;\n }\n\n private get baseStyles(): { [key: string]: boolean } {\n const baseStyles = {};\n baseStyles[styles.t_eyeCatcher] = true;\n \n if (!this.props.availableSizesOpen) \n return baseStyles;\n\n return baseStyles;\n }\n\n public render() {\n const baseStyles = this.baseStyles;\n const { l10n } = this.context;\n return (\n \n {this.props.isNew &&\n
\n {l10n.isNew} \n
}\n {this.props.hasUvp && this.hasPriceDeduction &&\n
\n \n {`${l10n.uvp} ${this.props.priceDeduction}%`}\n \n
}\n {this.props.setPrice && this.hasPriceDeduction && !this.props.flagAdvertisedAsCostFreeInSet &&\n
\n \n {`${l10n.setPrice} ${this.props.priceDeduction}%`}\n \n
}\n {this.props.flagAdvertisedAsCostFreeInSet &&\n
\n \n {l10n.sale}\n \n
}\n {this.props.sale && !this.props.setPrice\n && this.hasPriceDeduction && !this.props.flagAdvertisedAsCostFreeInSet &&\n
\n \n {`${l10n.sale} ${this.props.priceDeduction}%`}\n \n
\n }\n
\n );\n }\n}\n","/* eslint-disable max-len */\nimport { Component } from 'react';\nimport React from 'react';\nimport styles from './playPauseBtn.scss';\nimport classNames from 'classnames';\nimport TabNavigationHelper from '../../../../../Common/tabNavigationHelper';\n\nexport default class PlayPauseBtn extends Component<{slideShowPaused: boolean,\n imageAmount: number, on_hover: boolean, slideIndex: number\n sdvColorColorCode: number, tileId: string}, {useRegularSlideDuration: boolean}> {\n private circleMask = 'cm_' + this.props.tileId;\n private circle_1_id = 'c1_' + this.props.tileId;\n private circle_2_id = 'c2_' + this.props.tileId;\n private circleAnimationTimer: ReturnType;\n private circleAnimationTimer_play: ReturnType;\n private resetOnMouseLeaveTimer: ReturnType;\n private readonly ref_circle_2 = React.createRef();\n private readonly defaultPathLength = 1000;\n private readonly filledStrokes = 1000;\n private tabNav: TabNavigationHelper;\n\n constructor(props) {\n super(props);\n this.tabNav = TabNavigationHelper.instance;\n this.state = {\n useRegularSlideDuration: false\n };\n }\n \n componentDidUpdate(prevProps): void {\n if(!this.ref_circle_2.current)\n return;\n\n if(prevProps.sdvColorColorCode !== this.props.sdvColorColorCode)\n {\n this.setState({\n useRegularSlideDuration: true\n }, () => {this.resetLoadingStrokeDasharray();});\n }\n else if (prevProps.slideShowPaused !== this.props.slideShowPaused) {\n if (this.props.slideShowPaused) {\n if(this.props.slideIndex === 1 && this.props.slideIndex < prevProps.slideIndex) {\n // animation was finished:\n this.resetLoadingStrokeDasharray();\n }\n else {\n // animation was paused:\n this.ref_circle_2.current.classList.remove(styles.lets_play_a_game);\n }\n }\n else { \n clearTimeout(this.circleAnimationTimer);\n clearTimeout(this.circleAnimationTimer_play);\n clearTimeout(this.resetOnMouseLeaveTimer);\n\n this.circleAnimationTimer = setTimeout(() => {\n this.ref_circle_2.current.classList.add(styles.circle_progress);\n }, 15);\n\n this.circleAnimationTimer_play = setTimeout(() => {\n this.ref_circle_2.current.classList.add(styles.lets_play_a_game);\n }, 25);\n }\n }\n \n // hover ended, reset play btn state, and avoid inner endless state change loop\n if(!this.props.on_hover && (prevProps.on_hover !== this.props.on_hover)) {\n this.resetOnMouseLeaveTimer = setTimeout(() => {\n this.setState({\n useRegularSlideDuration: false\n }, () => {this.resetLoadingStrokeDasharray();});\n }, 700);\n }\n\n // Update animation timer on second slide and same article variant.\n // It is needed because first slide takes only 0.75 second.\n if(this.props.slideIndex === 2 && prevProps.sdvColorColorCode === this.props.sdvColorColorCode \n && this.props.on_hover) {\n this.setAnimationDuration();\n }\n }\n\n componentDidMount(): void {\n if(this.ref_circle_2.current) {\n this.setAnimationDuration();\n }\n }\n\n componentWillUnmount(): void {\n // clear previous timeout:\n clearTimeout(this.circleAnimationTimer);\n clearTimeout(this.circleAnimationTimer_play);\n clearTimeout(this.resetOnMouseLeaveTimer);\n }\n\n private setAnimationDuration(): void {\n // default values:\n const regularSlideDuration = 3; // time in seconds\n const firstSlideDuration = 0.75; // time in seconds\n let animationDuration = 4;\n let filledStrokes = this.filledStrokes;\n\n // About useRegularSlideDuration ->\n // changes first slide duration to regular, because only main article needs short main image animation:\n\n // first slide loader goes fast and just for that single gap\n if(this.props.slideIndex === 1 && !this.state.useRegularSlideDuration) {\n animationDuration = firstSlideDuration;\n filledStrokes = this.defaultPathLength / this.props.imageAmount;\n }\n else {\n const firsSlideNumber = this.state.useRegularSlideDuration ? 0 : 1;\n // continue rest of the animation normally:\n animationDuration = (this.props.imageAmount - firsSlideNumber) * regularSlideDuration;\n let transparencyFraction = 0;\n\n if(!this.state.useRegularSlideDuration) {\n // value for smooth start after first slide:\n switch (true) {\n case (this.props.imageAmount <= 2): {\n transparencyFraction = 150;\n break;\n }\n case (this.props.imageAmount <= 4): {\n transparencyFraction = 100;\n break;\n }\n case (this.props.imageAmount <= 8): {\n transparencyFraction = 50;\n break;\n }\n case (this.props.imageAmount <= 10): {\n transparencyFraction = 25;\n break;\n }\n }\n\n this.ref_circle_2.current.style.setProperty('--startingPoint', \n ((this.defaultPathLength / this.props.imageAmount) - transparencyFraction).toString());\n }\n }\n\n this.ref_circle_2.current.style.setProperty('--filledStrokes', \n filledStrokes.toString());\n \n this.ref_circle_2.current.style.setProperty('--animationDuration', \n animationDuration.toString()+'s');\n }\n\n private resetLoadingStrokeDasharray(): void {\n if (!this.ref_circle_2.current)\n return;\n\n this.setAnimationDuration();\n this.ref_circle_2.current.classList.remove(styles.lets_play_a_game);\n this.ref_circle_2.current.classList.remove(styles.circle_progress);\n\n this.circleAnimationTimer = setTimeout(() => {\n this.ref_circle_2.current.classList.add(styles.circle_progress);\n }, 15);\n\n this.ref_circle_2.current.style.setProperty('--startingPoint', '0');\n }\n\n private getStrokeDasharray(): string {\n return `${(this.defaultPathLength - this.props.imageAmount * 30)/this.props.imageAmount} 30`;\n }\n\n private handleKeyDown(event): void {\n // prevent events on tile core:\n event.stopPropagation(); \n }\n\n public render() {\n return (\n this.tabNav.setFocusAtt(el)}\n onKeyDown={this.handleKeyDown}>\n
\n \n \n \n \n \n \n \n \n \n \n \n {this.props.slideShowPaused ?\n \n \n :\n \n \n \n \n \n \n }\n \n
\n );\n }\n}\n","import React, { Component } from 'react';\nimport styles from './slideShow.scss';\nimport { ISdvColor, IFileData } from '../article-tile.d';\nimport PlayPauseBtn from './playPauseBtn/playPauseBtn';\nimport classNames from 'classnames';\nimport TabNavigationHelper from '../../../../Common/tabNavigationHelper';\n\nexport default class SlideShow extends Component<\n {\n on_hover: boolean, \n sdvColor: ISdvColor,\n detailsPageLink: string,\n articleName: string,\n tileId: string,\n showAvailableSizesMenu: boolean,\n isMobile: boolean\n preventDefaultEvents,\n singleArticleView: boolean,\n mainViewMlt: string,\n shoeSoleViewMlt: string,\n detailViewMlt: string,\n otherViewMlt: string,\n categoryName: string,\n colorName: string\n }, \n {\n paused: boolean,\n animationEnded: boolean,\n slideIndex: number,\n images: IFileData[],\n sdvColorColorCode: number,\n firstSlideAnimationDuration: number \n }> {\n private readonly slideShowRef = React.createRef();\n private readonly slidesRef = React.createRef();\n private animationTimer: ReturnType;\n private animationStartupTimer: ReturnType;\n private tabNav: TabNavigationHelper;\n \n // public because of tests:\n public readonly animationDuration: number = 3000;\n private readonly defaultFirstSlideAnimationDuration: number = 750;\n private startTime: number;\n private timeLeft: number;\n private previousSlideIndex: number;\n\n constructor(props) {\n super(props);\n this.tabNav = TabNavigationHelper.instance;\n this.state = {\n paused: true,\n animationEnded: false,\n slideIndex: 1,\n images: this.collectUrls(true),\n sdvColorColorCode: this.props.sdvColor.color.code,\n firstSlideAnimationDuration: this.defaultFirstSlideAnimationDuration\n };\n\n this.handlePlayPauseClick = this.handlePlayPauseClick.bind(this);\n }\n\n componentDidMount(): void {\n // firs component init, it helps to avoid transition during page load:\n if (this.slideShowRef.current) {\n this.slideShowRef.current.classList.add(styles.use_animation);\n this.tabNav.setGroupAtt(this.slideShowRef.current);\n }\n }\n\n componentDidUpdate(prevProps) {\n if (prevProps.on_hover !== this.props.on_hover) {\n // collect remaining or default variant images (depends if hover started or ended):\n this.setState({\n images: this.collectUrls(false)\n });\n\n // Reset slide show to starting point:\n if (!this.props.on_hover) {\n this.tabNav.removeFocusAtt(this.slidesRef.current);\n\n this.setState({\n sdvColorColorCode: this.props.sdvColor.color.code,\n firstSlideAnimationDuration: this.defaultFirstSlideAnimationDuration \n });\n \n clearTimeout(this.animationStartupTimer);\n this.resetSlideShow();\n\n // add first slide animation on startup:\n if(this.slideShowRef.current) {\n this.slideShowRef.current.classList.add(styles.introAnimation);\n }\n } else {\n clearTimeout(this.animationStartupTimer);\n\n // Start the animation:\n this.animationStartupTimer = setTimeout(() => {\n if(this.state.images.length > 1) {\n this.previousSlideIndex = 0;\n this.handlePlayPauseClick();\n\n // remove first slide animation, avoid flickering on variant change:\n if(this.slideShowRef.current) {\n this.slideShowRef.current.classList.remove(styles.introAnimation);\n }\n }\n }, 700);\n }\n }\n\n // variant changed:\n if(prevProps.sdvColor.color.code !== this.props.sdvColor.color.code && this.props.on_hover) {\n // change first slide duration to regular, because only main article needs short main image animation:\n this.setState({firstSlideAnimationDuration: this.animationDuration})\n \n clearTimeout(this.animationStartupTimer);\n\n // Start the animation:\n this.animationStartupTimer = setTimeout(() => {\n this.resetSlideShow();\n\n this.setState({\n images: this.collectUrls(false),\n sdvColorColorCode: this.props.sdvColor.color.code\n });\n\n if(this.state.images.length > 1) {\n this.previousSlideIndex = 0;\n this.handlePlayPauseClick();\n }\n }, 700);\n }\n\n // when avs was opened:\n if(this.props.showAvailableSizesMenu != prevProps.showAvailableSizesMenu \n && (!this.state.paused && this.props.showAvailableSizesMenu)) {\n this.handlePlayPauseClick();\n }\n }\n\n componentWillUnmount() {\n // No longer need to keep endless loop if component is unmounted:\n // clear previous timeout:\n clearTimeout(this.animationTimer);\n clearTimeout(this.animationStartupTimer);\n }\n\n private collectUrls(singleUrlOnly: boolean): IFileData[] {\n let otherViewCounter = 1;\n // main image:\n this.props.sdvColor.image.imageTypeMlt = this.props.mainViewMlt;\n const result = [this.props.sdvColor.image];\n \n // mobile device use only first/ main image,\n // if hove is not active, we do not need all article images:\n if(this.props.isMobile || singleUrlOnly)\n return result;\n\n if(this.props.sdvColor.mainActionImage) {\n this.props.sdvColor.mainActionImage.imageTypeMlt = this.props.otherViewMlt + ' ' + otherViewCounter++;\n result.push(this.props.sdvColor.mainActionImage);\n }\n\n if(this.props.sdvColor.additionalImages && this.props.sdvColor.additionalImages.length > 0) {\n this.props.sdvColor.additionalImages.forEach(ai => {\n ai.imageTypeMlt = this.props.otherViewMlt + ' ' + otherViewCounter++;\n result.push(ai);\n });\n }\n\n if(this.props.sdvColor.detailedImage) {\n this.props.sdvColor.detailedImage.imageTypeMlt = this.props.detailViewMlt;\n result.push(this.props.sdvColor.detailedImage);\n }\n \n if(this.props.sdvColor.secondaryImage) {\n this.props.sdvColor.secondaryImage.imageTypeMlt = this.props.otherViewMlt + ' ' + otherViewCounter++;\n result.push(this.props.sdvColor.secondaryImage);\n }\n\n if(this.props.sdvColor.soleImage) {\n this.props.sdvColor.soleImage.imageTypeMlt = this.props.shoeSoleViewMlt;\n result.push(this.props.sdvColor.soleImage);\n }\n\n return result;\n }\n\n private handlePlayPauseClick(event?): boolean {\n this.props.preventDefaultEvents(event);\n\n // in case if animation ended, and user clicks play once again:\n if(this.state.animationEnded)\n this.setState({animationEnded: false});\n\n this.setState({\n paused: !this.state.paused\n }, ()=> {\n if (this.state.paused) {\n // clear previous timeout:\n clearTimeout(this.animationTimer);\n \n this.timeLeft = this.startTime - new Date().getTime();\n this.previousSlideIndex = this.state.slideIndex;\n } else {\n this.startTime = new Date().getTime() + this.timeLeft;\n }\n\n // get active slide:\n const active_slide = this.slideShowRef.current.querySelector('.fas_active');\n\n if(active_slide) {\n this.state.paused ? active_slide.classList.add(styles.pause_zoom_in) \n : active_slide.classList.remove(styles.pause_zoom_in);\n }\n\n this.runSlideShow(this.state.slideIndex);\n });\n return false;\n }\n\n private runSlideShow(index: number): void {\n if (this.state.paused)\n return;\n\n // set next slide index:\n this.setState({ slideIndex: index });\n\n // animation ends here, when the last slides passes:\n if (index === this.state.images.length) {\n this.previousSlideIndex = 0;\n clearTimeout(this.animationTimer);\n\n this.animationTimer = setTimeout(() => {\n this.resetSlideShow();\n this.setState({animationEnded: true});\n }, this.animationDuration);\n }\n else {\n let mixedAnimationDuration = 0;\n\n if(this.previousSlideIndex !== index) {\n // new slide goes on:\n // clear previous timeout:\n clearTimeout(this.animationTimer);\n\n this.startTime = new Date().getTime() + this.animationDuration;\n\n if(index === 1) { \n // first slide should pass faster, because it is the same main image:\n mixedAnimationDuration = this.state.firstSlideAnimationDuration;\n }\n else {\n mixedAnimationDuration = this.animationDuration;\n }\n }\n else {\n // continue previous slide:\n mixedAnimationDuration = this.timeLeft;\n }\n\n this.animationTimer = setTimeout(() => {\n this.runSlideShow(++index);\n }, mixedAnimationDuration);\n }\n }\n\n private resetSlideShow(): void {\n this.setState({\n paused: true,\n slideIndex: 1,\n animationEnded: false\n });\n }\n\n private slideStyles(renderableSlideIndex: number): string {\n // only main article first image, does not have zoom in animation and animation duration is shorter:\n const isMainArticleImage = renderableSlideIndex === 1\n && this.state.firstSlideAnimationDuration === this.defaultFirstSlideAnimationDuration;\n\n let baseStyles = isMainArticleImage ?\n [styles.slide, styles.first].join(' ') : [styles.slide].join(' ');\n\n let finishingZoomInStyles = '';\n\n if(this.state.images.length > 1) {\n baseStyles = [baseStyles, styles.slow_zoom_in].join(' ');\n finishingZoomInStyles = styles.finishing_zoom_in;\n }\n\n if (this.state.slideIndex === renderableSlideIndex && isMainArticleImage) {\n // first slide does not need finish animation effect on slide change\n return [baseStyles, styles.active].join(' ');\n }\n else if (this.state.slideIndex === renderableSlideIndex\n && !isMainArticleImage && renderableSlideIndex === 1) {\n if (this.state.animationEnded) {\n // first variant slides should stop, and animation, has to be removed:\n return [styles.slide, styles.active].join(' ');\n }\n else {\n // first variant slide plays with animation like other slides:\n if (this.state.sdvColorColorCode !== this.props.sdvColor.color.code) {\n return [styles.slide, styles.active].join(' ');\n }\n else {\n return [baseStyles, styles.active, finishingZoomInStyles].join(' ');\n }\n }\n }\n else if (this.state.slideIndex === renderableSlideIndex) {\n // all other slides require finish animation effect on slide change\n return [baseStyles, styles.active, finishingZoomInStyles].join(' ');\n }\n else {\n if (renderableSlideIndex > 1 && renderableSlideIndex < this.state.slideIndex) {\n // finish animation during slide switch, it will keep smooth effect:\n return [baseStyles, finishingZoomInStyles].join(' ');\n }\n else\n return baseStyles;\n }\n }\n\n private getImageSrcSet(imgUrl: string ): string { \n // DPR related subfix\n const subFix = this.props.isMobile ? ' 1200w,' : ' 768w,';\n\n // up to 768:\n const path_768 = imgUrl.replace('ArticleTileV3', 'TileImage768') + subFix;\n \n // up to HD:\n const path_hd = imgUrl + ' 1800w'\n\n return path_768 + ' ' + path_hd;\n }\n\n private getAltTagValue(imageTypeMlt: string): string {\n const categoryName = this.props.categoryName ? ', ' + this.props.categoryName: '';\n const colorName = this.props.colorName ? ', ' + this.props.colorName : '';\n return imageTypeMlt + categoryName + ', ' + this.props.articleName + colorName;\n }\n\n private createSlideShow(): JSX.Element {\n return (\n \n {this.state.images.map((img, imageIndex) => {\n const tmpIndex = imageIndex + 1;\n return
\n })}\n
\n );\n }\n\n public render() {\n return (\n 0 })}\n data-testid={'tile_slide_show'}>\n
\n {\n this.state.images.length > 1 &&\n
\n }\n
\n {this.createSlideShow()}\n
\n );\n }\n}\n","import React from 'react';\nimport styles from './tileVariants.scss';\nimport { Component } from 'react';\nimport { ISalesDesignationView } from '../article-tile.d';\nimport classNames from 'classnames';\nimport AvailableSizesBtn from '../availableSizes/availableSizesBtn';\nimport { IScoredProduct } from '../../../productfinderresultpage/productfinderresultpage.d';\nimport TabNavigationHelper from '../../../../Common/tabNavigationHelper';\nimport { ArticleTileContext } from '../../../globalState/articleTileContext';\n\nexport default class TileVariants extends Component<{\n article?: ISalesDesignationView, \n product?: IScoredProduct;\n changeColorVariant(colorCode: number): void,\n tileIsHovering: boolean,\n selectedColor: number,\n toggleAvailableSizes?(): void,\n showAvailableSizesButton?: boolean,\n pfResultColorClick?(): void,\n showAvailableSizesMenu: boolean\n}, { mounted: boolean, onHover: boolean }> {\n static contextType = ArticleTileContext;\n\n private varRef: React.RefObject;\n private hoverDelay: ReturnType;\n private tabNav: TabNavigationHelper;\n\n constructor(props) {\n super(props);\n this.tabNav = TabNavigationHelper.instance;\n this.onMouseEnter = this.onMouseEnter.bind(this);\n this.onMouseLeave = this.onMouseLeave.bind(this);\n this.varRef = React.createRef();\n this.state = {\n mounted: false,\n onHover: false\n };\n }\n\n componentDidMount(): void {\n this.setState({ mounted: true });\n\n if(this.varRef.current) {\n this.tabNav.setGroupAtt(this.varRef.current);\n }\n }\n\n componentDidUpdate(): void {\n if(this.varRef.current) {\n this.props.showAvailableSizesMenu === true ? \n this.varRef.current.setAttribute('disabled', ''):\n this.varRef.current.removeAttribute('disabled');\n }\n }\n\n private onMouseEnter(colorCode: number): void {\n clearTimeout(this.hoverDelay);\n this.setState({ onHover: false });\n\n this.hoverDelay = setTimeout(() => {\n this.props.changeColorVariant(colorCode);\n this.setState({ onHover: true });\n }, 300);\n }\n\n private onMouseLeave(): void {\n clearTimeout(this.hoverDelay);\n\n this.setState({\n onHover: false\n });\n }\n\n public render() {\n return (\n \n {this.renderVariants()}\n {this.renderSizeButton()}\n
\n );\n }\n\n private getIconSrc(colorCode: number): string {\n return `${this.context.l10n.cdnUrl}assets/ats/colors64px/Original/${colorCode}.png`\n }\n\n private openPdpOnSelectedVariantOnly(event, colorCode: number): void {\n if (this.props.selectedColor !== colorCode) {\n this.props.changeColorVariant(colorCode);\n event.stopPropagation();\n }\n }\n\n // click on already selected color - trigger\n private preventParentEventPropagation(event): void {\n if (!(event.target.classList.contains('fas_color_icon') || \n event.target.classList.contains('fas_available_sizes_btn'))) {\n event.preventDefault();\n event.stopPropagation();\n }\n }\n\n private renderVariants() {\n if (!this.state.mounted) return null;\n if (!this.varRef.current) return null;\n if (!this.props.tileIsHovering) return null;\n if (!this.props?.article?.salesArticleVariantColors && !this.props.product?.variants || \n this.props?.article?.salesArticleVariantColors && this.props.product?.variants) return null;\n if (this.props?.article?.salesArticleVariantColors.length <= 1 \n && this.props?.product?.variants && this.props.product?.variants.length <= 1) return null;\n\n const variantContainerElement = this.varRef.current;\n const containerWidth = variantContainerElement.clientWidth;\n const columns = containerWidth / 44; // 44 width of color icon element\n const numOfRowsAllowed = this.props.showAvailableSizesButton ? 1 : 2; // Single/ two row(s) for color icons;\n const iconCount = Math.floor(columns) * numOfRowsAllowed;\n const variantColors = this.props?.article?.salesArticleVariantColors || this.props?.product?.variants;\n\n let allColorsShown = true;\n let displayColors = [...variantColors];\n let mode = 'sdv';\n if (this.props?.product?.variants.length > 1 && !this.props?.article?.salesArticleVariantColors.length) {\n mode = 'productFinder';\n }\n\n if (iconCount < displayColors.length) {\n allColorsShown = false;\n displayColors = displayColors\n .slice(0, iconCount - 1)\n }\n\n return ( this.tabNav.setGroupAtt(el)}>\n {displayColors.map((variant, index) => {\n let clrCode = 1;\n if (mode === 'sdv') {\n clrCode = variant['color'].code as number;\n } else {\n clrCode = variant['colorCode'] as number;\n }\n\n const clickHandler = typeof this.props.pfResultColorClick != 'function' ?\n (event) => { this.openPdpOnSelectedVariantOnly(event, clrCode); } :\n this.props.pfResultColorClick;\n return (\n
{ this.onMouseEnter(clrCode) }}\n onMouseLeave={this.onMouseLeave}\n onTouchEnd={clickHandler}\n data-testid='display_color'\n onClick={clickHandler}\n ref={(el)=> this.tabNav.setFocusAtt(el)}\n >\n
\n
\n )\n })}\n {!allColorsShown &&\n
\n +{(mode === 'sdv' ?\n this.props.article.salesArticleVariantColors.length\n : this.props.product.variants.length) - displayColors.length}\n }\n
)\n }\n\n private renderSizeButton() {\n if (!this.state.mounted) return null;\n if (!this.varRef.current) return null;\n if (!this.props.tileIsHovering) return null;\n if (!this.props.showAvailableSizesButton) return null;\n return (\n \n );\n }\n}\n","import * as React from 'react';\nimport * as helper from '../../Common/html-helper';\nimport classNames from 'classnames';\nimport styles from './availabilityFilter.scss';\nimport InfoIcon from '../../Assets/svg/info';\nimport { IAvailabilityFilter, IAvailabilityFilterState } from './availabilityFilter.d';\nimport TabNavigationHelper from '../../Common/tabNavigationHelper';\n\nexport default class AvailabilityFilter extends React.Component {\n private tabNav: TabNavigationHelper;\n\n constructor(props: IAvailabilityFilter) {\n super(props);\n this.state = {};\n\n this.tabNav = TabNavigationHelper.instance;\n\n this.handleFiltersIsAvailableChanged = this.handleFiltersIsAvailableChanged.bind(this);\n this.onTooltipClicked = this.onTooltipClicked.bind(this);\n }\n\n public render() {\n const af_text = helper.decodeHTML(this.props.mltTitle);\n return (\n this.tabNav.setGroupAtt(el)}>\n
\n \n {af_text}\n \n \n this.tabNav.setFocusAtt(el)}> \n
\n
{af_text}
\n
{helper.decodeHTML(this.props.mltTitleSmall)}
\n
\n \n
\n
\n );\n }\n\n private handleFiltersIsAvailableChanged(): void {\n if (this.props.setIsAvailableFilter)\n this.props.setIsAvailableFilter();\n }\n\n private onTooltipClicked(event: React.MouseEvent) {\n event.preventDefault();\n event.stopPropagation();\n this.props.updateTooltip(true, this.props.tooltipText);\n }\n}\n","import * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport SearchResultPage from './searchresultpage/searchresultpage';\n\nfunction hydrateSearchResultPage() {\n if (typeof window.__PRELOADED_SEARCH_RESULT_PAGE_STATE__ !== 'undefined') {\n const data = JSON.parse(JSON.stringify(window.__PRELOADED_SEARCH_RESULT_PAGE_STATE__));\n delete window.__PRELOADED_SEARCH_RESULT_PAGE_STATE__;\n ReactDOM.hydrate(\n ,\n document.getElementById('ats_searchresultpage'),\n );\n }\n}\n\nhydrateSearchResultPage();\n","import * as helper from '../../Common/html-helper';\nimport * as FasBarTypes from './fas-bar.d';\nimport { SortType } from '../sort-side-content/sort-side-content.d';\nimport * as React from 'react';\nimport styles from './fas-bar.scss';\nimport FilterDropDown from '../filterDropDown/filterDropDown';\nimport {\n AvailableFiltersState,\n IFilterModelChangedEvent,\n ICategoryData\n} from '../globalState/AvailableFiltersState';\nimport { IFilterSelectionChangedEvent, SelectedFiltersState } from '../globalState/SelectedFiltersState';\nimport { ISelectedFilter } from '../globalState/selectedFilters.d';\nimport { Handler } from '../globalState/globalState';\nimport { convertFromServiceToReactWorld } from '../globalState/ServiceSelectedFilterConverter';\nimport { FilterAjaxUpdateController } from '../globalState/FilterAjaxUpdateController';\nimport { IFilterProperty } from '../fas-bar/filter-property.d';\nimport { IFilterPropertyValue } from '../fas-bar/filter-property-value.d';\nimport FilterIcn2021 from '../../Assets/svg/filter_2021';\nimport ProductFinderIcn2021 from '../../Assets/svg/productfinder_2021';\nimport KioskFilter from '../kiosk-filter/kiosk-filter';\nimport * as Constants from '../../Common/constants';\nimport classNames from 'classnames';\nimport SpinnerComponent from '../spinnerComponent/spinnerComponent';\nimport FragmentPlaceholder from '../../Common/fragment-placeholder/fragment-placeholder';\nimport SortIcn2021 from '../../Assets/svg/sort_2021';\nimport CrossIcn from '../../Assets/svg/cross_2021';\nimport { ISortContext } from '../globalState/sortContextProvider.d';\nimport { ViewType } from '../../Common/enums';\nimport { publish } from '../../Common/customEventHelper';\nimport defaultStyles from '../../Common/defaults.scss';\nimport TabNavigationHelper from '../../Common/tabNavigationHelper';\nimport { MultipleContext } from '../globalState/MultipleContext';\n\nexport default class FasBar extends React.Component {\n static contextType = MultipleContext;\n // max break point when hover is not working:\n private breakpoint_tablet_max_width_1199 = parseInt(defaultStyles.breakpoint_tablet_max_width_1199);\n\n private readonly availableFiltersState: AvailableFiltersState;\n private readonly selectedFilterState: SelectedFiltersState;\n private readonly componentName: string = 'fas-bar';\n private windowWidth: number;\n private readonly initialGloveSortCount: number;\n\n private elementRef = React.createRef();\n private top4FilterElementRef = React.createRef();\n private filterBtnElementRef = React.createRef();\n private sortBtnElementRef = React.createRef();\n private tabNav: TabNavigationHelper;\n\n constructor(props: FasBarTypes.IFasBarProps, context) {\n super(props, context);\n this.state = {\n // we need only one spinner. If dropDown or sidePanel is open, cat., overlay is not visible:\n fasBarVisible: true,\n mspOpen: false,\n filterModel: props.filterModel,\n selectedFilters: convertFromServiceToReactWorld(\n props.userSelectedFilters,\n props.filterModel),\n topDisplay: 4,\n windowWidth: 0,\n isMounted: false,\n isMobileView: (this.props.isMobile || this.props.isTablet)\n };\n this.tabNav = TabNavigationHelper.instance;\n this.removeAllFilters = this.removeAllFilters.bind(this);\n this.addSelectedFilter = this.addSelectedFilter.bind(this);\n this.removeSelectedFilter = this.removeSelectedFilter.bind(this);\n this.handleFilterIsAvailableChange = this.handleFilterIsAvailableChange.bind(this);\n this.globalyRemoveSelectedFilter = this.globalyRemoveSelectedFilter.bind(this);\n this.isAvailableFilterActive = this.isAvailableFilterActive.bind(this);\n this.updateWindowWidth = this.updateWindowWidth.bind(this);\n this.getGlobalArticleCountString = this.getGlobalArticleCountString.bind(this);\n this.checkBrakePointForTablet = this.checkBrakePointForTablet.bind(this);\n this.handleMoreFBtnClick = this.handleMoreFBtnClick.bind(this);\n this.handleSortBtnClick = this.handleSortBtnClick.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n\n const categoryData: ICategoryData = {\n categoryPath: this.props.categoryPath,\n navigationKey: this.props.navigationKey,\n seoSlug: this.props.seoSlug,\n };\n\n this.availableFiltersState = new AvailableFiltersState(\n this.props.globalState, categoryData, this.componentName,\n this.props.filterModel, this.state.selectedFilters, this.props.searchTerm, this.props.kiosk);\n this.selectedFilterState = this.availableFiltersState.selectedFilterState;\n this.props.globalState.selectedFiltersState = this.selectedFilterState;\n this.availableFiltersState.registerOnStateChanged(this.onAvailableFiltersChanged.bind(this));\n this.selectedFilterState.registerOnStateChanged(this.onSelectedFiltersChanged.bind(this));\n this.initialGloveSortCount = this.props.sorting?.gloves?.filter(gloveSort => gloveSort.sort > 0).length || 0\n\n this.initSort();\n if (typeof window !== 'undefined') {\n window.addEventListener('scroll', this.handleScroll.bind(this));\n\n if (typeof window.shell !== 'undefined') {\n window.shell.subscribeTo(\n 'ESPP.MainSidePanel.Opened',\n () => {\n this.setState({ mspOpen: true });\n }, null);\n\n window.shell.subscribeTo(\n 'ESPP.MainSidePanel.Closed',\n () => {\n this.setState({ mspOpen: false });\n }, null);\n }\n }\n }\n\n public handleFilterIsAvailableChange(): void {\n if (!this.state.filterModel.filters)\n return;\n\n const filter: IFilterProperty | undefined\n = this.state.filterModel.filters.find((x) => x.name === Constants.DELIVERY_TIME);\n const isSelected: boolean = this.isAvailableFilterActive();\n if (typeof filter !== 'undefined')\n if (isSelected) {\n const selectedFilter = {\n dimension: filter,\n value: filter.filterPropertyValues[0],\n } as ISelectedFilter;\n this.removeSelectedFilter(selectedFilter);\n } else\n this.addSelectedFilter(filter, filter.filterPropertyValues[0]);\n else {\n // eslint-disable-next-line no-console\n console.warn('Data inconsistent! delivery_time filter should always available base on design');\n }\n }\n\n public componentDidMount() {\n this.handleScroll(); // init call, for page reload;\n // update search count for search result\n if (this.props.isBreadcrumbOnly)\n publish('ESPP.FilterAndSort.UpdateGlobalStateDependencies',\n this.availableFiltersState.getFilterModel() ?\n this.availableFiltersState.getFilterModel().matchingArticleCount : null);\n else\n this.updateWindowWidth();\n\n // enable filter button when component is mounted, only for mobile:\n if (this.props.isMobile && !this.props.isBreadcrumbOnly)\n this.filterBtnElementRef.current.classList.remove(styles.f_btn_disabled);\n\n this.setState({\n isMobileView: window.innerWidth <= this.breakpoint_tablet_max_width_1199\n });\n\n this.setState({\n isMounted: true,\n }, () => {\n window.addEventListener('resize', this.checkBrakePointForTablet);\n });\n\n if(this.top4FilterElementRef.current) {\n this.tabNav.setGroupAtt(this.top4FilterElementRef.current);\n }\n\n if(this.filterBtnElementRef.current) {\n this.tabNav.setFocusAtt(this.filterBtnElementRef.current);\n }\n\n if(this.sortBtnElementRef.current) {\n this.tabNav.setFocusAtt(this.sortBtnElementRef.current);\n }\n }\n\n public componentDidUpdate() {\n if (this.props.isBreadcrumbOnly)\n publish('ESPP.FilterAndSort.UpdateGlobalStateDependencies',\n this.availableFiltersState.getFilterModel().matchingArticleCount);\n }\n\n public componentWillUnmount() {\n window.removeEventListener('load', this.updateWindowWidth);\n window.removeEventListener('resize', this.checkBrakePointForTablet);\n }\n\n private checkBrakePointForTablet(): void {\n this.setState({\n isMobileView: window.innerWidth <= this.breakpoint_tablet_max_width_1199\n });\n }\n\n public updateWindowWidth() {\n this.windowWidth = window.innerWidth;\n this.setState({\n windowWidth: this.windowWidth,\n });\n\n this.checkTop4ContainerWidth();\n }\n\n private checkTop4ContainerWidth() {\n const currentTop4Container: HTMLDivElement = this.top4FilterElementRef.current;\n if (currentTop4Container) {\n const childWidthArray: number[] = [];\n Array.from(currentTop4Container.children).forEach((value) => {\n childWidthArray.push(value.clientWidth);\n });\n\n let totalChildWidth = childWidthArray.reduce((a, b) => a + b, 0);\n while (totalChildWidth > currentTop4Container.clientWidth) {\n childWidthArray.pop();\n totalChildWidth = childWidthArray.reduce((a, b) => a + b, 0);\n }\n\n let currentTop4Display = childWidthArray.length;\n if (currentTop4Display < 2)\n currentTop4Display = 1;\n this.setState({ topDisplay: currentTop4Display });\n }\n }\n\n public handleActiveDropDownFilterChange = (prActiveDropDownFilter: string) => {\n this.props.dropDownFilterChange(prActiveDropDownFilter);\n this.availableFiltersState.restoreToGlobalState();\n }\n\n public render() {\n return (\n \n {this.renderTopFourFilters()}\n \n
\n );\n }\n\n private renderTopFourFilters() {\n if (this.props.isBreadcrumbOnly)\n return null;\n else\n return (\n this.tabNav.setGroupAtt(el)}>\n {this.renderSearchResultText()}\n {this.renderTop4Wrapper()}\n {this.renderProductFinderButton()}\n {\n this.props.globalState.filterModel\n ? (\n
\n {this.getGlobalArticleCountString()}\n
\n )\n :
\n }\n {\n this.state.isMobileView\n ? (
)\n : this.renderFilterButton(this.createTopFilterContainers().length)\n }\n {this.props.viewType !== ViewType.Search && this.renderManualProductComparisonButton()}\n
\n {this.props.viewType === ViewType.Search && this.renderManualProductComparisonButton()}\n {this.state.isMobileView ? this.renderFilterButton(0) : (
)}\n {this.renderSortButton()}\n {this.renderProductFinderButton()}\n
\n
\n );\n }\n\n private get useSingleLineLayout(): boolean {\n if (this.elementRef.current) {\n const mpcBtn = this.elementRef.current.querySelector('.toolbar-button-wrapper');\n\n return !this.shouldShowProductFinder || !mpcBtn;\n }\n\n return true;\n }\n\n private shouldShowProductFinder(): boolean {\n return this.props.productFinders && this.props.productFinders.length > 0;\n }\n\n private renderSearchResultText() {\n if (this.props.viewType === ViewType.Search) {\n const maxLength = 28;\n const shouldHaveEllipsis = this.props.originalSearchTerm.length > maxLength;\n const searchTerm = shouldHaveEllipsis\n ? this.props.originalSearchTerm.substring(0, 24) + '...'\n : this.props.originalSearchTerm;\n\n return (\n \n \n {this.props.l10n.yourSearchFor}\n \n \n \n {this.props.originalSearchTerm} \n \n \n {!this.state.isMobileView &&\n \n \n {searchTerm} \n \n \n }\n
\n );\n }\n }\n\n private handleKeyDown(event): void {\n event.stopPropagation(); // avoid triggering parent:\n\n // prevent page jump down on Space btn., click\n if (event.key === ' ' || event.key === 'Enter'){\n event.preventDefault();\n this.removeAllFilters()\n }\n }\n\n private renderTop4Wrapper() {\n const topFilters = this.createTopFilterContainers();\n const notRender = this.props.isMobile;\n\n if (notRender)\n return (
);\n else if (this.props.kiosk) {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n return ( );\n }\n else\n return (\n \n {\n topFilters.length > 0 ?\n
\n {this.renderTopFilters()}\n
\n :
\n }\n {\n this.getFilterCount() > 0 ?\n
this.tabNav.setFocusAtt(el)}\n onKeyDown={this.handleKeyDown}>\n
\n
\n {this.props.l10n.clearAllFilters}\n \n
\n
\n :\n null\n }\n
);\n }\n\n private handleMoreFBtnClick() {\n this.context.context2.setMspOrigin(this.props.mspOrigin);\n this.sendEvent('Filter');\n }\n\n private handleSortBtnClick() {\n this.context.context2.setMspOrigin(this.props.mspOrigin + '_sort');\n this.sendEvent('Sort');\n }\n\n private renderFilterButton(topFilterCounts: number) {\n return (\n this.tabNav.setGroupAtt(el)}>\n
this.tabNav.handleKeyDown(el, )}\n >\n {\n this.isFilterActive() &&
\n
{this.getFilterCount()}
\n
\n }\n\n
\n {\n // -1 is delivery time, we do not count it as a filter:\n this.props.filterModel && topFilterCounts > 0 &&\n this.props.filterModel.filters &&\n this.props.filterModel.filters.length - 1 > topFilterCounts\n ? (\n
\n \n {helper.decodeHTML(this.props.l10n.moreFilter)}\n \n {this.getGlobalArticleCount()} \n {this.renderArticleCountForMobile()}\n
\n\n )\n : (\n
\n \n {helper.decodeHTML(this.props.l10n.filter)}\n \n {this.getGlobalArticleCount()} \n {this.renderArticleCountForMobile()}\n
\n )\n }\n
\n\n
);\n }\n\n private renderArticleCountForMobile() {\n return this.props.globalState.filterModel\n ? (\n \n {this.props.l10n.filter} \n {' ('} \n {this.getGlobalArticleCount()}\n {')'} \n \n )\n : \n }\n\n private renderSortButton() {\n const sortContext: ISortContext = this.context.context1;\n const selectedSort = sortContext.sortPropertyList.find(f => f.selected);\n let sortText = this.props.l10n.sort;\n\n if (this.getGloveSortCount() > 0) {\n const popularitySort = sortContext.sortPropertyList.find(f =>\n f.sortType === SortType.Popularity);\n if (popularitySort) {\n sortText = popularitySort.title;\n }\n } else if (selectedSort) {\n sortText = selectedSort.title;\n }\n\n return (\n this.tabNav.handleKeyDown(el)}>\n
\n
\n {helper.decodeHTML(sortText)}\n
\n
);\n }\n\n private renderManualProductComparisonButton() {\n return (\n (!this.props.kiosk) ?\n \n \n
:
\n );\n }\n\n private renderProductFinderButton() {\n return (\n this.shouldShowProductFinder() ?\n this.tabNav.setFocusAtt(el)} \n className={styles.product_finder_btn}\n data-testid='product_finder_btn'\n onClick={() => {\n this.sendEvent(this.props.productFinders?.[0].key);\n }}\n onKeyDown={(el)=> this.tabNav.handleKeyDown(el)}>\n
\n
\n {helper.decodeHTML(this.props.productFinders[0].label)}\n
\n
: (
)\n );\n }\n\n private getGlobalArticleCountString(): string {\n let articleCount: number;\n if (typeof window !== 'undefined' &&\n window.document && window.document.createElement) {\n articleCount = this.props.globalState.filterModel.matchingArticleCount;\n } else {\n articleCount = this.props.filterModel.matchingArticleCount;\n }\n\n const unitString = articleCount > 1\n ? this.props.l10n.articles\n : this.props.l10n.article;\n return articleCount?.toString()\n + ' ' + unitString;\n }\n\n private getGlobalArticleCount(): string {\n let articleCount: number;\n if (typeof window !== 'undefined' &&\n window.document && window.document.createElement) {\n articleCount = this.props.globalState.filterModel.matchingArticleCount;\n } else {\n articleCount = this.props.filterModel.matchingArticleCount;\n }\n\n return articleCount?.toString();\n }\n\n private getMPCEndpoint(): string {\n const mpcSearchEndpoint = '/ESPP1-ManualProductComparison/TouchPoint/ForMobileToolbar';\n const mpcCategoryEndpoint = '/ESPP1-ManualProductComparison/TouchPoint/ForMobileToolbar?categoryNavKey=' +\n this.props.navigationKey;\n return this.props.viewType === ViewType.Search ? mpcSearchEndpoint : mpcCategoryEndpoint;\n }\n private isAvailableFilterActive(): boolean {\n const deliveryTimeFilterIndex: number = this.state.selectedFilters.findIndex(\n (x) => x.dimension.name === Constants.DELIVERY_TIME);\n return deliveryTimeFilterIndex >= 0;\n }\n\n private removeAllFilters() {\n const sortContext: ISortContext = this.context.context1;\n sortContext.removeAllGloveSortValues();\n this.selectedFilterState.globalRemoveAllFilters();\n }\n\n private addSelectedFilter(dimension: IFilterProperty, value: IFilterPropertyValue) {\n // activate loading spinner:\n this.selectedFilterState.addFilter({ dimension, value } as ISelectedFilter);\n }\n\n private globalyRemoveSelectedFilter(filter: ISelectedFilter) {\n this.selectedFilterState.globalRemoveFilter(filter);\n }\n\n private removeSelectedFilter(filter: ISelectedFilter) {\n this.selectedFilterState.removeFilter(filter);\n }\n\n private onSelectedFiltersChanged: Handler = (event: IFilterSelectionChangedEvent) => {\n this.setState({ selectedFilters: event.selectedFilters });\n }\n\n private createElement(index: number, filterProperty: IFilterProperty): JSX.Element {\n const isAvailable: boolean = this.isAvailableFilterActive();\n const availableFilterToolTip: string = this.availableFilterToolTip();\n\n return (\n );\n }\n\n private availableFilterToolTip(): string {\n if (!this.state.filterModel.filters)\n return null;\n\n const filter: IFilterProperty | undefined\n = this.state.filterModel.filters.find((x) => x.name === Constants.DELIVERY_TIME);\n return (filter !== undefined && filter.filterPropertyValues[0])\n ? filter.filterPropertyValues[0].tooltip\n : null;\n }\n\n private createTopFilterContainers(): Array {\n const topFilterContainer: Array = [];\n\n if (this.props.globalState.filterModel &&\n this.props.globalState.filterModel.filters && this.props.globalState.filterModel.filters.length > 0) {\n const relevantFilters = this.props.globalState.filterModel.filters\n .filter((x) => x.name !== Constants.DELIVERY_TIME);\n\n if (relevantFilters) {\n const globalTopFourFilters = relevantFilters.slice(0, 4);\n\n for (let index = 0; index < globalTopFourFilters.length; index++) {\n const filterProperty: IFilterProperty = globalTopFourFilters[index];\n const newElement = this.createElement(index, filterProperty);\n topFilterContainer.push(newElement);\n }\n }\n }\n\n return topFilterContainer;\n }\n\n private onAvailableFiltersChanged: Handler = (event: IFilterModelChangedEvent) => {\n this.setState({ filterModel: event.filterModel });\n }\n\n private renderTopFilters() {\n if (!this.state.filterModel || !this.state.filterModel.filters)\n return (\n \n no filters found\n
\n );\n\n const topFilterContainer: Array = this.createTopFilterContainers();\n\n return (topFilterContainer.length > 1 ?\n (\n topFilterContainer.map((filtersContainer, index) => {\n return ( (this.state.topDisplay - 1),\n })}\n key={index}>{filtersContainer}
);\n })\n ) :
\n );\n }\n\n private sendEvent = (payload: string) => {\n if(payload)\n publish('ESPP.MainSidePanel.ShouldOpen', payload);\n }\n\n private handleScroll() {\n const fasBarElement: HTMLElement = this.elementRef.current;\n if (fasBarElement) {\n if (fasBarElement.getBoundingClientRect().top <= 80 && this.state.fasBarVisible) {\n if (typeof window !== 'undefined' && window.shell)\n publish('ESPP.FilterAndSort.ShowFasButton', null);\n\n this.setState({ fasBarVisible: false });\n } else if (fasBarElement.getBoundingClientRect().top > 80 && !this.state.fasBarVisible) {\n if (typeof window !== 'undefined' && window.shell)\n publish('ESPP.FilterAndSort.HideFasButton', null);\n\n this.setState({ fasBarVisible: true });\n }\n if (FilterAjaxUpdateController.IsRequestRunning) return;\n this.availableFiltersState.restoreToGlobalState();\n }\n }\n\n private initSort(): void {\n publish('ESPP.FilterAndSort.UpdateGlobalStateDependencies',\n this.availableFiltersState.getFilterModel().matchingArticleCount);\n }\n\n private isFilterActive(): boolean {\n return this.state.selectedFilters.length > 0 || this.getGloveSortCount() > 0;\n }\n\n private getGloveSortCount(): number {\n const sortContext: ISortContext = this.context.context1;\n let gloveSortCount = 0;\n if (this.state.isMounted) {\n gloveSortCount = sortContext.activeGlovesSortPropValues.length;\n } else {\n gloveSortCount = this.initialGloveSortCount;\n }\n return gloveSortCount;\n }\n\n private getFilterCount(): number {\n return this.state.selectedFilters.length + this.getGloveSortCount();\n }\n}\n","import * as helper from '../../Common/html-helper';\nimport * as React from 'react';\nimport * as FasSideContentTypes from './fas-side-content.d';\nimport { IFilterProperty } from '../fas-bar/filter-property.d';\nimport { IFilterPropertyValue } from '../fas-bar/filter-property-value.d';\nimport styles from './fas-side-content.scss';\nimport XIcon from '../../Assets/svg/x';\nimport classNames from 'classnames';\nimport FilterContent from '../filterContent/filterContent';\nimport { Handler } from '../globalState/globalState';\nimport {\n IFilterSelectionChangedEvent,\n SelectedFiltersState,\n FilterSelectionChangedType\n} from '../globalState/SelectedFiltersState';\nimport { ISelectedFilter } from '../globalState/selectedFilters.d';\nimport { convertFromServiceToReactWorld } from '../globalState/ServiceSelectedFilterConverter';\nimport {\n AvailableFiltersState,\n IFilterModelChangedEvent,\n ICategoryData\n} from '../globalState/AvailableFiltersState';\nimport AvailabilityFilter from '../availabilityFilter/availabilityFilter';\nimport ArrowDownIcn from '../../Assets/svg/arrow_down';\nimport SpinnerComponent from '../spinnerComponent/spinnerComponent';\nimport TooltipComponent from '../tooltipComponent/tooltipComponent';\nimport * as Constants from '../../Common/constants';\nimport { Scrollbars } from 'react-custom-scrollbars';\nimport { SortContext } from '../globalState/sortContextProvider';\nimport { ISortContext } from '../globalState/sortContextProvider.d';\nimport { publish, subscribe } from '../../Common/customEventHelper';\nimport TabNavigationHelper from '../../Common/tabNavigationHelper';\n\nexport default class FasSideContent extends React.Component {\n static contextType = SortContext;\n\n private readonly availableFiltersState: AvailableFiltersState;\n private readonly selectedFilterState: SelectedFiltersState;\n private readonly componentName: string = 'fas-side-content';\n private filterContentRef = React.createRef();\n private selectedFiltersRef = React.createRef();\n private gradientContainerRight = React.createRef();\n private gradientContainerLeft = React.createRef();\n private tabNav: TabNavigationHelper;\n\n constructor(props: FasSideContentTypes.IFasSideContentProps) {\n super(props);\n this.tabNav = TabNavigationHelper.instance;\n const selectedFilters = convertFromServiceToReactWorld(\n props.initialUserSelectedFilters,\n props.initialFilterModel);\n\n this.removeAllFilters = this.removeAllFilters.bind(this);\n this.applyFilters = this.applyFilters.bind(this);\n this.addSelectedFilter = this.addSelectedFilter.bind(this);\n this.removeSelectedFilter = this.removeSelectedFilter.bind(this);\n this.handleFilterIsAvailableChange = this.handleFilterIsAvailableChange.bind(this);\n this.updateTooltip = this.updateTooltip.bind(this);\n this.updateGlovesSort = this.updateGlovesSort.bind(this);\n this.getLastSelectedFilterName = this.getLastSelectedFilterName.bind(this);\n this.onHeaderClick = this.onHeaderClick.bind(this);\n this.toggleSelectedFiltersView = this.toggleSelectedFiltersView.bind(this);\n this.handleSortContextChange = this.handleSortContextChange.bind(this);\n\n const categoryData: ICategoryData = {\n categoryPath: this.props.categoryPath,\n navigationKey: this.props.navigationKey,\n seoSlug: this.props.seoSlug,\n };\n\n this.availableFiltersState = new AvailableFiltersState(this.props.globalState, categoryData, this.componentName,\n this.props.initialFilterModel, selectedFilters, this.props.searchTerm, this.props.kiosk);\n this.availableFiltersState.registerOnStateChanged(this.onAvailableFiltersChanged.bind(this));\n this.selectedFilterState = this.availableFiltersState.selectedFilterState;\n this.props.globalState.selectedFiltersState = this.selectedFilterState;\n this.selectedFilterState.registerOnStateChanged(this.onSelectedFiltersChanged.bind(this));\n\n this.state = {\n collapsedFilter: this.getLastSelectedFilterName(selectedFilters),\n mspOpen: false,\n filterModel: props.initialFilterModel,\n selectedFilters,\n selectedTooltipText: null,\n showTooltip: false,\n tooltipImageName: null,\n componentMounted: false,\n selectedFiltersExpanded: false,\n };\n\n subscribe('ESPP.MainSidePanel.Closed',\n () => {\n this.setState({ mspOpen: false });\n this.resetState();\n if (this.props.isMobile) {\n this.availableFiltersState.restoreToGlobalState();\n }\n });\n\n subscribe('ESPP.MainSidePanel.Opened',\n () => {\n this.setState({ mspOpen: true });\n setTimeout(() => {\n this.setSelectedFilterView();\n }, 500);\n });\n\n subscribe(\n 'ESPP.MainSidePanel.TabSwitchOff',\n (payload: CustomEvent) => {\n if (payload.detail === 'Filter')\n this.resetState();\n });\n }\n\n componentDidMount(): void { \n // restore history.scrollRestoration\n // it was removed during filter add/remove on mobile devices\n // this is mainly for apple devices\n if(this.props.isMobile) {\n setTimeout(() => {\n history.scrollRestoration = 'auto';\n }, 500);\n }\n\n this.setState({\n componentMounted: true\n });\n\n const sortContext: ISortContext = this.context;\n sortContext.addOnChangeSideEffect(this.handleSortContextChange);\n }\n\n componentWillUnmount() {\n const sortContext: ISortContext = this.context;\n sortContext.removeOnChangeSideEffect(this.handleSortContextChange);\n }\n\n public handleSortContextChange() {\n this.setSelectedFilterView();\n }\n\n private setSelectedFilterView(): void {\n const selectedFiltersContainer: HTMLDivElement = this.selectedFiltersRef.current;\n const gradientContainerRight: HTMLDivElement = this.gradientContainerRight.current;\n const gradientContainerLeft: HTMLDivElement = this.gradientContainerLeft.current;\n\n if (!selectedFiltersContainer)\n return;\n\n const filtersList: HTMLDivElement = selectedFiltersContainer.querySelector('.fas_selected_filters');\n if (!filtersList)\n return;\n\n if (this.props.isMobile) {\n if (filtersList.clientWidth < filtersList.scrollWidth) {\n gradientContainerRight.classList.add('fas_gradient_container_show');\n gradientContainerLeft.classList.add('fas_gradient_container_show');\n }\n else {\n gradientContainerRight.classList.remove('fas_gradient_container_show');\n gradientContainerLeft.classList.remove('fas_gradient_container_show');\n }\n }\n\n else {\n if (filtersList.clientHeight + 18 < filtersList.scrollHeight ||\n (filtersList.classList.contains('fas_expanded_view') &&\n filtersList.clientHeight === filtersList.scrollHeight && filtersList.clientHeight !== 92))\n selectedFiltersContainer.classList.add('fas_overflown_container');\n else {\n selectedFiltersContainer.classList.remove('fas_overflown_container');\n filtersList.classList.remove('fas_expanded_view');\n }\n }\n }\n\n public render() {\n const sortContext: ISortContext = this.context;\n const normalFilters = this.state.filterModel.filters ?\n this.state.filterModel.filters.filter((x) => x.name !== Constants.DELIVERY_TIME) : [];\n const isDeliveryTimeAvailable = this.state.filterModel && this.state.filterModel.filters\n && this.state.filterModel.filters.filter((x) => x.name === Constants.DELIVERY_TIME).length > 0;\n const showSelectedFiltersWrapper = (this.state.selectedFilters && (this.state.selectedFilters.length > 0\n || sortContext.activeGlovesSortPropValues.length > 0));\n return (\n \n {\n this.state.filterModel.filters && this.state.filterModel.filters.length > 0 &&\n <>\n
\n
\n >\n }\n {\n normalFilters.length > 0\n ? (\n <>\n {this.renderAllAvailableFilters(showSelectedFiltersWrapper)}\n >\n ) :\n (\n
\n
\n {helper.decodeHTML(this.props.l10n.noFurtherFilterDimensions)}\n
\n
\n )\n }\n {\n this.state.componentMounted &&\n
\n {\n showSelectedFiltersWrapper\n && (\n
\n {\n this.props.isMobile &&\n
\n }\n
\n {\n // gloves sort properties:\n sortContext.activeGlovesSortPropValues.map((gSort) => (\n
\n {\n gSort.title\n }\n \n
\n ))\n }\n {\n this.selectedFilterState.sortSelectedFilters().map((filter: ISelectedFilter) =>\n (\n
\n {this.selectedFilterName(filter)}\n \n
\n ))\n }\n {\n !this.props.isMobile &&\n
\n }\n
\n {\n this.props.isMobile &&\n
\n }\n
\n )\n }\n {\n isDeliveryTimeAvailable &&\n
\n {this.renderAvailableFilter()}\n
\n }\n
this.tabNav.setGroupAtt(el)}>\n
this.tabNav.setFocusAtt(el)}\n data-testid='cancel_button'\n onClick={this.removeAllFilters}\n onKeyDown={this.tabNav.handleKeyDown}>\n
{helper.decodeHTML(this.props.l10n.clearAllFilters)}
\n
\n {this.getMatchingArticleCounter()}\n
\n
\n }\n
\n );\n }\n\n public handleFilterIsAvailableChange(): void {\n if (!this.state.filterModel.filters)\n return;\n\n const filter: IFilterProperty | undefined\n = this.state.filterModel.filters.find((x) => x.name === Constants.DELIVERY_TIME);\n const isSelected: boolean = this.isAvailableFilterActive();\n if (typeof filter !== 'undefined')\n if (isSelected) {\n const selectedFilter = {\n dimension: filter,\n value: filter.filterPropertyValues[0],\n } as ISelectedFilter;\n this.removeSelectedFilter(selectedFilter);\n } else\n this.addSelectedFilter(filter, filter.filterPropertyValues[0]);\n else {\n // eslint-disable-next-line no-console\n console.warn('Data inconsistent! delivery_time filter should always available base on design');\n }\n }\n\n public updateTooltip(visibility: boolean, text: string, imageName?: string) {\n this.setState({\n selectedTooltipText: text,\n showTooltip: visibility,\n tooltipImageName: imageName,\n });\n }\n\n private toggleSelectedFiltersView(): void {\n const selectedFiltersContainer: HTMLDivElement = this.selectedFiltersRef.current;\n if (selectedFiltersContainer) {\n const filtersList: HTMLDivElement = selectedFiltersContainer.querySelector('.fas_selected_filters');\n if (filtersList) {\n this.setState({ selectedFiltersExpanded: !this.state.selectedFiltersExpanded });\n }\n }\n }\n\n private selectedFilterName(filter: ISelectedFilter): string {\n const isDeliveryTime: boolean = filter.dimension.name === Constants.DELIVERY_TIME;\n if (isDeliveryTime)\n return filter.value.title.substring(0,\n filter.value.title.indexOf(','));\n else\n if (filter.dimension.applyDimensionTitleInActiveFilter)\n return filter.dimension.title + ': ' + filter.value.title;\n else\n return filter.value.title;\n }\n\n private globalyRemoveSelectedFilter(filter: ISelectedFilter) {\n this.selectedFilterState.globalRemoveFilter(filter);\n }\n\n private removeGlovesSort(gSort: IFilterPropertyValue): void {\n const sortContext: ISortContext = this.context;\n sortContext.setGloveRating(gSort, 0, !this.props.isMobile, !this.props.isMobile,\n !this.props.isMobile);\n }\n\n private getMatchingArticleCounter() {\n const result: string = this.state.filterModel.matchingArticleCount > 1 ?\n this.props.l10n.showItems :\n this.props.l10n.showItem;\n\n const strings = result.split('{0}');\n let surfix = '';\n let prefix = '';\n if (strings.length > 1) {\n prefix = strings[0];\n surfix = strings[1];\n } else\n surfix = strings[0];\n\n return (\n this.tabNav.setFocusAtt(el)} \n className={styles.apply_button}\n data-testid='apply_button'\n onClick={this.applyFilters}\n onKeyDown={this.tabNav.handleKeyDown}>\n {prefix} {this.state.filterModel.matchingArticleCount} {surfix}\n
\n );\n }\n\n private renderAllAvailableFilters(showSelectedFiltersWrapper: boolean) {\n if (this.state.filterModel !== null && this.state.filterModel.filters\n && this.state.componentMounted) {\n const displayFilters = this.state.filterModel.filters.filter((x) => x.name !== Constants.DELIVERY_TIME);\n return (\n \n
}\n renderThumbVertical={() =>
}>\n this.tabNav.setGroupAtt(el)}>\n {displayFilters.map((filter) => this.renderFilterProperty(filter))}\n
\n \n
\n );\n }\n\n return (\n \n no filters found\n
\n );\n }\n\n private renderFilterProperty(filter: IFilterProperty) {\n const sortContext: ISortContext = this.context;\n const isCollapsed: boolean = this.state.collapsedFilter === filter.name;\n return (\n \n
this.tabNav.setFocusAtt(el)} \n className={classNames(\n styles.filter_header,\n {\n [styles.disabled_filter]: filter.allDisabled,\n [styles.collapsed]: isCollapsed,\n })}\n data-testid='filter_header'\n onClick={() => { this.onHeaderClick(filter); }}\n onKeyDown={this.tabNav.handleKeyDown}\n >\n
\n {filter.title}\n
\n {\n filter.name === Constants.GLOVES ? (\n
\n {\n sortContext.activeGlovesSortPropValues.map((gSort) => {\n return gSort.title;\n }).join(', ')\n }\n
\n )\n : (\n this.state.selectedFilters.filter((f) => f.dimension.name === filter.name) &&\n
\n {this.state.selectedFilters.filter(\n (f) => f.dimension.name.split(':')[0] === filter.name)\n .map((value) => {\n return value.value.title;\n }).join(', ')}\n
\n )\n }\n
\n
\n {\n isCollapsed &&\n
\n \n
\n }\n
\n );\n }\n\n private resetState() {\n this.updateTooltip(false, '');\n const originalLastSelectedFilterName = this.getLastSelectedFilterName(this.state.selectedFilters);\n this.setState({ collapsedFilter: originalLastSelectedFilterName });\n }\n\n private updateGlovesSort(): void {\n const sortContext: ISortContext = this.context;\n sortContext.applyContextChanges();\n }\n\n private renderAvailableFilter() {\n if (!this.state.filterModel.filters)\n return null;\n\n const isAvailable = this.isAvailableFilterActive();\n const tooltipText = this.props.l10n.deliveryTime;\n return (\n \n );\n }\n\n private isAvailableFilterActive(): boolean {\n const deliveryTimeFilterIndex = this.state.selectedFilters.findIndex(\n (x) => x.dimension.name === Constants.DELIVERY_TIME);\n return deliveryTimeFilterIndex >= 0;\n }\n\n private onHeaderClick(filter: IFilterProperty) {\n if (!filter.allDisabled)\n if (this.state.collapsedFilter === filter.name)\n this.setState({ collapsedFilter: null });\n else\n this.setState({ collapsedFilter: filter.name });\n }\n\n private applyFilters() {\n publish('ESPP.MainSidePanel.ShouldClose', null);\n\n if (this.props.isMobile) {\n history.scrollRestoration = 'manual'; // mainly for iphone scroll feature\n const sortContext: ISortContext = this.context;\n sortContext.applyContextChanges(null, true, false, true);\n this.availableFiltersState.updateGlobalState();\n }\n }\n\n private removeAllFilters() {\n const sortContext: ISortContext = this.context;\n sortContext.removeAllGloveSortValues(true, !this.props.isMobile, true); // skip articlelist update on mobile\n\n if (this.props.isMobile) {\n history.scrollRestoration = 'manual'; // mainly for iphone scroll feature\n }\n\n this.selectedFilterState.globalRemoveAllFilters();\n }\n\n private addSelectedFilter(dimension: IFilterProperty, value: IFilterPropertyValue) {\n // activate loading spinner:\n this.selectedFilterState.addFilter({ dimension, value } as ISelectedFilter);\n }\n\n private removeSelectedFilter(filter: ISelectedFilter) {\n // activate loading spinner:\n this.selectedFilterState.removeFilter(filter);\n }\n\n private onSelectedFiltersChanged: Handler = (event: IFilterSelectionChangedEvent) => {\n this.setState({ selectedFilters: event.selectedFilters });\n\n if (event.type === FilterSelectionChangedType.GlobalUpdate) {\n const collapsedFilterName: string = this.getLastSelectedFilterName(event.selectedFilters);\n this.setState({ collapsedFilter: collapsedFilterName });\n }\n\n if (event.type === FilterSelectionChangedType.FilterAdded ||\n event.type === FilterSelectionChangedType.FilterRemoved ||\n event.type === FilterSelectionChangedType.GlobalFilterRemoved) {\n setTimeout(() => { this.setSelectedFilterView(); }, 500);\n }\n\n if (event.type === FilterSelectionChangedType.GlobalAllFilterRemoved)\n publish('ESPP.MainSidePanel.ShouldClose', null);\n }\n\n private getLastSelectedFilterName(selectedFilters: ISelectedFilter[]): string {\n let result: string = null;\n\n if (selectedFilters.length > 0) {\n // Set last filter name:\n result = selectedFilters[selectedFilters.length - 1].dimension.name.split(':')[0];\n\n // filters in side panel might not be rendered at the very beginning:\n if (this.filterContentRef && this.filterContentRef.current) {\n // Set last sub filter name:\n const selectedValue = selectedFilters.filter((value) =>\n value.dimension.name.split(':')[0] === result);\n const lastSelectedSubFilterName: string =\n selectedValue.length > 0 ? selectedValue.pop().dimension.name : null;\n this.filterContentRef.current.setState({ collapsedSubFilter: lastSelectedSubFilterName });\n }\n }\n\n return result;\n }\n\n private onAvailableFiltersChanged: Handler = (event: IFilterModelChangedEvent) => {\n this.setState({ filterModel: event.filterModel });\n }\n}\n","import * as React from 'react';\nimport classNames from 'classnames';\nimport styles from './collection-line-value.scss';\nimport { IFilterValueProps } from '../filterValue/filter-value.d';\nimport InfoIcon from '../../../Assets/svg/info';\nimport TabNavigationHelper from '../../../Common/tabNavigationHelper';\n\nexport default class CollectionLineValue extends React.Component {\n private tabNav: TabNavigationHelper;\n\n constructor(props) {\n super(props);\n this.state = {};\n this.tabNav = TabNavigationHelper.instance;\n this.onValueClicked = this.onValueClicked.bind(this);\n this.onTooltipClicked = this.onTooltipClicked.bind(this);\n }\n\n public render() {\n return (\n this.tabNav.setFocusAtt(el)}\n onClick={this.onValueClicked}\n onKeyDown={this.tabNav.handleKeyDown}\n >\n {this.props.filterPropertyValue.iconSrc ?\n (\n
\n
\n
\n )\n : null\n }\n
\n
\n {this.props.filterPropertyValue.title}\n
\n
\n {this.props.filterPropertyValue.articlesAmount}\n this.tabNav.setFocusAtt(el)}>\n \n \n
\n
\n );\n }\n\n private onValueClicked() {\n if (this.props.isSelected)\n this.props.removeSelectedFilter(this.props.filter);\n else\n this.props.addSelectedFilter(this.props.dimension, this.props.filterPropertyValue);\n }\n\n private onTooltipClicked(event: React.MouseEvent) {\n event.preventDefault();\n event.stopPropagation();\n this.props.updateTooltip(true, this.props.filterPropertyValue.tooltip, this.getTooltipImageName());\n }\n\n private getTooltipImageName(): string {\n let collectionName: string;\n switch (this.props.filterPropertyValue.id) {\n case '0':\n collectionName = 'classic';\n break;\n case '1':\n collectionName = 'image';\n break;\n case '2':\n collectionName = 'active';\n break;\n case '3':\n collectionName = 'prestige';\n break;\n case '4':\n collectionName = 'roughtough';\n break;\n case '5':\n collectionName = 'motion';\n break;\n case '6':\n collectionName = 'motion2020';\n break;\n case '7':\n collectionName = 'vision';\n break;\n case '8':\n collectionName = 'fusion';\n break;\n case '9':\n collectionName = 'avida';\n break;\n case '10':\n collectionName = 'basics';\n break;\n case '11':\n collectionName = 'dynashield';\n break;\n case '19':\n collectionName = 'industry';\n break;\n case '21':\n collectionName = 'motionten';\n break;\n case '23':\n collectionName = 'concrete';\n break;\n case '24':\n collectionName = 'vintage';\n break;\n case '26':\n collectionName = 'trail';\n break;\n case '29':\n collectionName = 'iconic';\n break;\n case '30':\n collectionName = 'ambition';\n break;\n case '31':\n collectionName = 'motion247';\n break;\n default:\n return null;\n }\n\n return 'fas_collection-' + collectionName;\n }\n}\n","import * as React from 'react';\nimport classNames from 'classnames';\nimport styles from './colour-value.scss';\nimport { IFilterValueProps } from '../filterValue/filter-value.d';\nimport * as helper from '../../../Common/html-helper';\nimport TabNavigationHelper from '../../../Common/tabNavigationHelper';\n\nexport default class ColourValue extends React.Component {\n private tabNav: TabNavigationHelper;\n \n constructor(props) {\n super(props);\n this.state = {};\n\n this.tabNav = TabNavigationHelper.instance;\n this.onValueClicked = this.onValueClicked.bind(this);\n }\n\n public render() {\n return (\n this.tabNav.setFocusAtt(el)}\n onClick={this.onValueClicked}\n onKeyDown={this.tabNav.handleKeyDown}\n >\n {this.props.filterPropertyValue.iconSrc ?\n (\n
\n
\n
\n )\n : null\n }\n
\n
\n {helper.decodeHTML(this.props.filterTitleMlt)}\n
\n
{this.props.filterPropertyValue.articlesAmount}
\n
\n );\n }\n\n private onValueClicked() {\n if (this.props.isSelected)\n this.props.removeSelectedFilter(this.props.filter);\n else\n this.props.addSelectedFilter(this.props.dimension, this.props.filterPropertyValue);\n }\n}\n","import * as React from 'react';\nimport { IFilterContentProp, IFilterContentState } from './filterContent.d';\nimport styles from './filterContent.scss';\nimport classNames from 'classnames';\nimport FilterValuesGroup from './filterValuesGroup/filterValuesGroup';\nimport * as Constants from '../../Common/constants';\nimport PlusIcn from '../../Assets/svg/plus';\nimport MinusIcn from '../../Assets/svg/minus';\nimport { IFilterProperty } from '../fas-bar/filter-property.d';\nimport TabNavigationHelper from '../../Common/tabNavigationHelper';\n\nexport default class FilterContent extends React.Component {\n private tabNav: TabNavigationHelper;\n\n constructor(props) {\n super(props);\n this.state = {\n collapsedSubFilter: this.lastSelectedSubFilterName(),\n };\n\n this.tabNav = TabNavigationHelper.instance;\n\n this.onHeaderClick = this.onHeaderClick.bind(this);\n }\n\n public componentDidMount() {\n if (this.props.dimension.subProperties && this.props.dimension.subProperties.length > 0)\n if (this.state.collapsedSubFilter !== null) {\n const collapsedProperty =\n this.props.dimension.subProperties.find((property) =>\n property.name === this.state.collapsedSubFilter);\n this.handleContentCollapse(collapsedProperty);\n } else if (this.props.dimension.subProperties.length === 1)\n this.handleContentCollapse(this.props.dimension.subProperties[0]);\n }\n\n public componentWillUnmount() {\n this.props.updateTooltip(false, '');\n }\n\n // private handleKeyDown(event, subProperty: IFilterProperty): void {\n // // prevent page jump down on Space btn., click\n // if (event.key === ' ') {//'Space'\n // event.preventDefault();\n // this.onHeaderClick(subProperty);\n // }\n // }\n\n public render() {\n // sub filters\n if (this.props.dimension.subProperties && this.props.dimension.subProperties.length > 0)\n return (\n {\n this.props.dimension.subProperties.map((subProperty, index) => {\n return (\n
\n
{ this.onHeaderClick(subProperty); }} \n ref={(el)=> this.tabNav.setFocusAtt(el)}\n onKeyDown={this.tabNav.handleKeyDown}>\n
\n {this.state.collapsedSubFilter === subProperty.name ||\n this.props.dimension.subProperties.length === 1\n ?
:
}\n
\n
\n {subProperty.title}\n
\n
\n\n
\n \n
\n
\n );\n })\n }\n
\n );\n else // regular filter\n return (\n \n \n
\n );\n }\n\n private onHeaderClick(subProperty: IFilterProperty) {\n if (this.state.collapsedSubFilter === subProperty.name || subProperty.allDisabled === true)\n this.setState({ collapsedSubFilter: null });\n else\n this.handleContentCollapse(subProperty);\n }\n\n private handleContentCollapse(subProperty: IFilterProperty) {\n this.setState({ collapsedSubFilter: subProperty.name });\n }\n\n private lastSelectedSubFilterName(): string {\n if (this.props.selectedFilters.length > 0) {\n const selectedValue = this.props.selectedFilters.filter((value) =>\n value.dimension.name.split(':')[0] === this.props.dimension.name);\n return selectedValue.length > 0 ? selectedValue.pop().dimension.name : null;\n } else\n return null;\n }\n}\n","import * as React from 'react';\nimport classNames from 'classnames';\nimport { IFilterValueProps } from './filter-value.d';\nimport styles from './filter-value.scss';\nimport InfoIcon from '../../../Assets/svg/info';\nimport TabNavigationHelper from '../../../Common/tabNavigationHelper';\n\nexport default class FilterValue extends React.Component {\n private tabNav: TabNavigationHelper;\n\n constructor(props) {\n super(props);\n this.state = {};\n this.tabNav = TabNavigationHelper.instance;\n this.onValueClicked = this.onValueClicked.bind(this);\n this.onTooltipClicked = this.onTooltipClicked.bind(this);\n }\n\n public render() {\n if (this.props.filterPropertyValue.iconSrc)\n return (\n this.tabNav.setFocusAtt(el)}\n onKeyDown={this.tabNav.handleKeyDown}\n >\n
\n
\n
\n
\n {this.props.filterPropertyValue.title}\n
\n
{this.props.filterPropertyValue.articlesAmount}
\n
\n );\n else\n return (\n this.tabNav.setFocusAtt(el)}\n onKeyDown={this.tabNav.handleKeyDown}\n >\n {this.props.filterPropertyValue.tooltip &&\n
\n \n
\n }\n
\n {this.props.filterPropertyValue.title}\n
\n
{this.props.filterPropertyValue.articlesAmount}
\n
\n );\n }\n\n private onValueClicked() {\n if (this.props.isSelected)\n this.props.removeSelectedFilter(this.props.filter);\n else\n this.props.addSelectedFilter(this.props.dimension, this.props.filterPropertyValue);\n }\n\n private onTooltipClicked(event: React.MouseEvent) {\n event.preventDefault();\n event.stopPropagation();\n this.props.updateTooltip(true, this.props.filterPropertyValue.tooltip);\n }\n}\n","import * as React from 'react';\nimport classNames from 'classnames';\nimport styles from './filterValuesGroup.scss';\nimport { IFilterValuesGroupProp, IFilterValuesGroupState } from './filterValuesGroup.d';\nimport { IFilterProperty } from '../../fas-bar/filter-property.d';\nimport FilterValue from '../filterValue/filter-value';\nimport ColourValue from '../colourValue/colour-value';\nimport CollectionLineValue from '../collectionLineValue/collection-line-value';\nimport GenderValue from '../genderValue/gender-value';\nimport { IFilterPropertyValue } from '../../fas-bar/filter-property-value.d';\nimport SizeValue from '../sizeValue/size-value';\nimport GlovesRecommendationSort from '../glovesRecommendationSort/glovesRecommendation-sort';\nimport * as Constants from '../../../Common/constants';\nimport { IFilterPropertyValuesGroup } from '../../fas-bar/filter-property-values-group.d';\nimport TabNavigationHelper from '../../../Common/tabNavigationHelper';\n\nexport default class FilterValuesGroup extends React.Component {\n private fvgElementRef = React.createRef();\n private nextActiveGroup: string;\n private colorsLocalizationArray: { id: string; localization: string }[];\n private tabNav: TabNavigationHelper;\n\n constructor(props: IFilterValuesGroupProp) {\n super(props);\n this.state = {\n activeFilterGroup: this.lastSelectedGroupName(),\n };\n\n this.tabNav = TabNavigationHelper.instance;\n }\n\n private handleKeyDown(event, groupTitle: string): void {\n event.stopPropagation(); // avoid triggering parent:\n\n // prevent page jump down on Space btn., click\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault();\n this.handleFiltersGroupSelection(groupTitle);\n }\n }\n\n public render() {\n return (\n \n {this.renderFilterGroupsHeaders(this.props.filterProperty)}\n {this.renderFilterGroups(this.props.filterProperty)}\n
\n );\n }\n\n private renderFilterGroupsHeaders(filterProperty: IFilterProperty): JSX.Element {\n return (\n filterProperty.filterPropertyValuesGroups &&\n filterProperty.filterPropertyValuesGroups.length > 1 ?\n \n {\n filterProperty.filterPropertyValuesGroups.map((valuesGroup, gindex) => {\n return (\n
this.tabNav.setFocusAtt(el)}\n onClick={this.handleFiltersGroupSelection.bind(this, valuesGroup.groupTitle)}\n onKeyDown={this.handleKeyDown.bind(this, valuesGroup.groupTitle)}\n className={classNames(styles.fvg_title,\n this.isGroupActive(gindex, valuesGroup) ? styles.fvg_active : '')}\n data-testid='fvg_title'>\n {valuesGroup.groupTitle}\n
\n );\n })\n }\n
\n :\n null\n );\n }\n\n private renderFilterGroups(filterProperty: IFilterProperty): JSX.Element {\n // prepare color localizations:\n if (filterProperty.name === Constants.COLOUR)\n this.prepareColorsLocalizationArray();\n\n return (\n {\n filterProperty.name !== Constants.GLOVES ?\n filterProperty.filterPropertyValuesGroups.map((valuesGroup, gindex) => {\n const isSpecialLayout =\n this.getSpecialLayout(valuesGroup.filterPropertyValues);\n return (\n
this.tabNav.setGroupAtt(el)}>\n
this.tabNav.setGroupAtt(el)}>\n {\n valuesGroup.filterPropertyValues.map((value, sindex) => {\n return (this.renderFilterValue(filterProperty, value, sindex,\n isSpecialLayout));\n })\n }\n
\n
\n );\n }) :
\n }\n
\n );\n }\n\n private prepareColorsLocalizationArray(): void {\n this.colorsLocalizationArray = [];\n\n this.colorsLocalizationArray.push({ id: '1', localization: this.props.l10n.shadesOfRed });\n this.colorsLocalizationArray.push({ id: '2', localization: this.props.l10n.shadesOfBlue });\n this.colorsLocalizationArray.push({ id: '3', localization: this.props.l10n.shadesOfOrange });\n this.colorsLocalizationArray.push({ id: '5', localization: this.props.l10n.shadesOfYellow });\n this.colorsLocalizationArray.push({ id: '6', localization: this.props.l10n.shadesOfGrey });\n this.colorsLocalizationArray.push({ id: '7', localization: this.props.l10n.shadesOfGreen });\n this.colorsLocalizationArray.push({ id: '8', localization: this.props.l10n.shadesOfWhite });\n this.colorsLocalizationArray.push({ id: '9', localization: this.props.l10n.shadesOfBrown });\n this.colorsLocalizationArray.push({ id: '10', localization: this.props.l10n.shadesOfViolet });\n }\n\n private getSpecialLayout(filterPropertyValues: IFilterPropertyValue[]): boolean {\n return filterPropertyValues.some((value) => value.title.trim().length > 3);\n }\n\n private isGroupActive(gindex: number, valuesGroup: IFilterPropertyValuesGroup): boolean {\n let result = false;\n\n if (this.state.activeFilterGroup === null && gindex === 0\n || this.state.activeFilterGroup === valuesGroup.groupTitle) {\n if (!(valuesGroup.allDisabled && this.nextActiveGroup !== valuesGroup.groupTitle)) {\n result = true;\n this.nextActiveGroup = valuesGroup.groupTitle;\n }\n } else if (!this.nextActiveGroup || this.nextActiveGroup === valuesGroup.groupTitle) {\n this.nextActiveGroup = valuesGroup.groupTitle;\n result = true;\n }\n\n return result;\n }\n\n private handleFiltersGroupSelection(groupTitle: string): void {\n this.nextActiveGroup = groupTitle;\n this.setState({ activeFilterGroup: groupTitle });\n }\n\n private renderFilterValue(dimension: IFilterProperty, value: IFilterPropertyValue, index: number,\n isSpecialLayout: boolean) {\n const indexOfFilter = this.props.selectedFilters.findIndex((x) =>\n x.dimension.name === dimension.name\n && x.value.id === value.id);\n const isSelected = indexOfFilter >= 0;\n const filter = isSelected ? this.props.selectedFilters[indexOfFilter] : null;\n\n if (dimension.name.includes(Constants.SIZE))\n return (\n \n );\n if (dimension.name === Constants.COLOUR) {\n const colorTitleMlt = this.colorsLocalizationArray.find((elm) => elm.id === value.id);\n let localization = '';\n\n if (colorTitleMlt)\n localization = colorTitleMlt.localization;\n\n return (\n \n );\n }\n if (dimension.name === Constants.COLLECTION_LINE)\n return (\n \n );\n if (dimension.name === Constants.GENDER)\n return (\n \n );\n else\n return (\n \n );\n }\n\n private lastSelectedGroupName(): string {\n if (this.props.selectedFilters.length > 0) {\n const selectedValue = this.props.selectedFilters.filter((value) =>\n value.dimension.name === this.props.filterProperty.name);\n return selectedValue.length > 0 ? selectedValue.pop().value.groupTitle : null;\n } else\n return null;\n }\n}\n","import * as React from 'react';\nimport classNames from 'classnames';\nimport styles from './gender-value.scss';\nimport { IFilterValueProps } from '../filterValue/filter-value.d';\nimport ManIcn from '../../../Assets/svg/icon_herren';\nimport WomanIcn from '../../../Assets/svg/icon_damen';\nimport ChildIcn from '../../../Assets/svg/icon_kinder';\nimport TabNavigationHelper from '../../../Common/tabNavigationHelper';\n\nexport default class GenderValue extends React.Component {\n private tabNav: TabNavigationHelper;\n constructor(props) {\n super(props);\n this.state = {};\n this.tabNav = TabNavigationHelper.instance;\n this.onValueClicked = this.onValueClicked.bind(this);\n }\n\n public render() {\n return (\n this.tabNav.setFocusAtt(el)}\n onKeyDown={this.tabNav.handleKeyDown}\n >\n {this.props.filterPropertyValue.iconSrc ?\n (\n
\n
\n
\n )\n : null\n }\n
\n {\n this.props.filterPropertyValue.id === '2' ? : null\n }\n {\n this.props.filterPropertyValue.id === '3' ? : null\n }\n {\n this.props.filterPropertyValue.id === '4' ? : null\n }\n
\n
\n {this.props.filterPropertyValue.title}\n
\n
{this.props.filterPropertyValue.articlesAmount}
\n
\n );\n }\n\n private onValueClicked() {\n if (this.props.filterPropertyValue.articlesAmount === 0)\n return;\n\n if (this.props.isSelected)\n this.props.removeSelectedFilter(this.props.filter);\n else\n this.props.addSelectedFilter(this.props.dimension, this.props.filterPropertyValue);\n }\n}\n","import * as React from 'react';\nimport classNames from 'classnames';\nimport styles from './glovesRecommendation-sort.scss';\nimport { IGlovesRecommendationProps } from './glovesRecommendation-sort.d';\nimport Rate from './rate/rate';\nimport * as helper from '../../../Common/html-helper';\nimport ReactHtmlParser from 'react-html-parser';\nimport { SortContext } from '../../globalState/sortContextProvider';\n\nexport default class GlovesRecommendationSort extends React.Component {\n static contextType = SortContext;\n\n constructor(props, context) {\n super(props, context);\n\n this.state = {\n mobileTouchStarted: false\n };\n\n this.onTouchStart = this.onTouchStart.bind(this);\n this.onTouchEnd = this.onTouchEnd.bind(this);\n }\n\n private onTouchStart(): void {\n this.setState({mobileTouchStarted: true});\n }\n\n private onTouchEnd(): void {\n this.setState({mobileTouchStarted: false});\n }\n\n public render() {\n return (\n \n {(sortContext) => (\n \n
\n
\n
\n
\n {helper.decodeHTML(this.props.rateLessImportantTitle)}
\n
\n {helper.decodeHTML(this.props.rateVeryImportantTitle)}
\n
\n {\n sortContext.glovesSortPropValues.map((gloveFilterSort, index) => {\n return (
\n );\n })}\n
\n
\n )}\n \n );\n }\n}\n","import * as React from 'react';\nimport Rating from 'react-rating';\nimport classNames from 'classnames';\nimport styles from './rate.scss';\nimport { IRateProperty } from './rate.d';\nimport XIcon from '../../../../Assets/svg/x';\nimport { SortContext } from '../../../globalState/sortContextProvider';\nimport { ISortContext } from '../../../globalState/sortContextProvider.d';\n\nexport default class Rate extends React.Component {\n static contextType = SortContext;\n\n private emptySymbols: JSX.Element[];\n private fullSymbols: JSX.Element[];\n\n constructor(props, context) {\n super(props, context);\n\n this.clearSelectedRate = this.clearSelectedRate.bind(this);\n this.onRateChange = this.onRateChange.bind(this);\n\n this.init();\n }\n\n public render() {\n return (\n \n
\n \n {this.props.glovesFilterSort.title}\n \n
\n
\n {this.renderClearSelectedRatingBtn()}\n \n
\n
\n );\n }\n\n private renderClearSelectedRatingBtn(): JSX.Element {\n return (\n this.props.glovesFilterSort.glovesRate > 0 ? :\n \n );\n }\n\n private init(): void {\n this.emptySymbols = [];\n this.fullSymbols = [];\n\n this.emptySymbols.push();\n this.emptySymbols.push();\n this.emptySymbols.push();\n this.emptySymbols.push();\n this.emptySymbols.push();\n\n this.fullSymbols.push();\n this.fullSymbols.push();\n this.fullSymbols.push();\n this.fullSymbols.push();\n this.fullSymbols.push();\n }\n\n private clearSelectedRate(): void {\n const sortContext: ISortContext = this.context;\n sortContext.setGloveRating(this.props.glovesFilterSort, 0, !this.props.isMobile,\n !this.props.isMobile, !this.props.isMobile);\n }\n\n private onRateChange(rateParam: number): void {\n const sortContext: ISortContext = this.context;\n sortContext.setGloveRating(this.props.glovesFilterSort, rateParam, !this.props.isMobile,\n !this.props.isMobile, !this.props.isMobile);\n }\n}\n","import * as React from 'react';\nimport classNames from 'classnames';\nimport styles from './size-value.scss';\nimport f_styles from '../filterValue/filter-value.scss';\nimport { IFilterValueProps } from '../filterValue/filter-value.d';\nimport TabNavigationHelper from '../../../Common/tabNavigationHelper';\n\nexport interface ISizeFilterProps extends IFilterValueProps {\n isSpecialLayout: boolean;\n}\n\nexport default class SizeValue extends React.Component {\n private tabNav: TabNavigationHelper;\n\n constructor(props) {\n super(props);\n this.state = {};\n this.tabNav = TabNavigationHelper.instance;\n this.onValueClicked = this.onValueClicked.bind(this);\n }\n\n public render() {\n return (\n this.tabNav.setFocusAtt(el)}\n onClick={this.onValueClicked}\n onKeyDown={this.tabNav.handleKeyDown}>\n
\n {this.props.filterPropertyValue.title}\n
\n
\n );\n }\n\n private onValueClicked() {\n if (this.props.isSelected)\n this.props.removeSelectedFilter(this.props.filter);\n else\n this.props.addSelectedFilter(this.props.dimension, this.props.filterPropertyValue);\n }\n}\n","import * as React from 'react';\nimport FilterContent from '../filterContent/filterContent';\nimport styles from './filterDropDown.scss';\nimport ArrowDownIcn from '../../Assets/svg/arrow_down';\nimport { IFilterDropDownProp, IFilterDropDownState } from './filterDropDownProp';\nimport classNames from 'classnames';\nimport AvailabilityFilter from '../availabilityFilter/availabilityFilter';\nimport SpinnerComponent from '../spinnerComponent/spinnerComponent';\nimport TooltipComponent from '../tooltipComponent/tooltipComponent';\nimport { ScrollLock } from '../../../Helper/scrollLock';\nimport { isIPad13, isTablet } from 'react-device-detect';\nimport { SortContext } from '../globalState/sortContextProvider';\nimport { ISortContext } from '../globalState/sortContextProvider.d';\nimport TabNavigationHelper from '../../Common/tabNavigationHelper';\n\nexport default class FilterDropDown extends React.Component {\n static contextType = SortContext;\n private dropDownRef = React.createRef();\n private dropDownContentRef = React.createRef();\n private scrollLock: ScrollLock;\n private mouseYPosition: number;\n private tabNav: TabNavigationHelper;\n\n constructor(props: IFilterDropDownProp, context) {\n super(props, context);\n this.state = {\n dropDownMinHeightStyle: {\n height: 'auto',\n minHeight: 0,\n },\n selectedTooltipText: null,\n showTooltip: false,\n tooltipImageName: null,\n };\n\n this.tabNav = TabNavigationHelper.instance;\n\n this.scrollLock = ScrollLock.instance;\n this.toggleFilterDropdown = this.toggleFilterDropdown.bind(this);\n this.handleClickOutside = this.handleClickOutside.bind(this);\n this.updateTooltip = this.updateTooltip.bind(this);\n this.handleKeyDown = this.handleKeyDown.bind(this);\n }\n\n public componentDidMount() {\n document.addEventListener('mousedown', this.handleClickOutside, false);\n\n if(this.dropDownContentRef.current) {\n this.tabNav.setGroupAtt(this.dropDownContentRef.current);\n }\n }\n\n public componentWillUnmount() {\n document.removeEventListener('mousedown', this.handleClickOutside, false);\n }\n\n public componentDidUpdate() {\n // for screen auto scroll, tablet only:\n if (this.dropDownContentRef.current && (isTablet || isIPad13)) {\n const dropDownContentHeightBottom: number =\n this.dropDownContentRef.current.clientHeight + this.mouseYPosition + 30;\n\n if (window.innerHeight < dropDownContentHeightBottom) {\n const goUp = dropDownContentHeightBottom - window.innerHeight;\n window.scrollBy({\n behavior: 'smooth',\n top: goUp,\n });\n }\n }\n }\n\n private handleKeyDown(event): void {\n // prevent page jump down on Space btn., click\n if (event.key === ' ') {//'Space'\n event.preventDefault();\n this.toggleFilterDropdown();\n }\n\n if (event.key === 'Enter') {\n this.toggleFilterDropdown();\n }\n }\n\n public render() {\n const sortContext: ISortContext = this.context;\n let filterCount: number;\n if (this.props.filterProperty.name === 'GlovesRecommendedUses') {\n filterCount = sortContext.activeGlovesSortPropValues.length\n } else {\n filterCount = this.props.selectedFilters.filter((filter =>\n filter.dimension.name.indexOf(this.props.filterProperty.name) > -1)).length;\n }\n const dropDownClasses = classNames(styles.dropdown_close,\n this.props.filterProperty.name === this.props.activeDropDownFilterName ?\n styles.dropdown_open : '',\n filterCount > 0 ? styles.dropdown_active : '');\n\n return (\n \n
this.tabNav.setFocusAtt(el)}\n data-testid='filterDropdown'\n onClick={this.toggleFilterDropdown}>\n {filterCount > 0 && (\n
\n )}\n
\n {this.props.filterProperty.title}\n
\n\n
\n
\n {\n this.props.filterProperty.name === this.props.activeDropDownFilterName &&\n (\n this.dropDownContent()\n )\n }\n
\n );\n }\n\n public updateTooltip(visibility: boolean, text: string, imageName?: string) {\n this.setState({\n selectedTooltipText: text,\n showTooltip: visibility,\n tooltipImageName: imageName,\n }, this.adjustDropDownMinHeight);\n }\n\n private adjustDropDownMinHeight() {\n if (this.state.showTooltip) {\n const dropDownContent: Element =\n this.dropDownRef.current.querySelector('.fas_dropdown_content');\n const tooltipCloseButton: Element =\n this.dropDownRef.current.querySelector('.fas_tooltip_overlay .fas_close_button');\n\n if (!dropDownContent || !tooltipCloseButton)\n return;\n\n const requiredMarginTop = 14;\n const dropdownOffset: number = dropDownContent.getBoundingClientRect().top;\n const closeButtonOffset: number = tooltipCloseButton.getBoundingClientRect().top;\n const missingSpace: number = dropdownOffset + requiredMarginTop - closeButtonOffset;\n\n if (missingSpace > 0)\n this.setState({\n dropDownMinHeightStyle: {\n height: 0,\n minHeight: (dropDownContent.scrollHeight + 2 * missingSpace),\n },\n });\n\n } else\n this.setState({\n dropDownMinHeightStyle: {\n height: 'auto',\n minHeight: 0,\n },\n });\n }\n\n private handleClickOutside(event: Event) {\n if (this.props.filterProperty.name !== this.props.activeDropDownFilterName)\n return;\n\n let eventObject = event.target as Element;\n while (eventObject.className.length <= 0 || !(eventObject instanceof HTMLElement)) {\n if (!eventObject.parentElement)\n break;\n eventObject = eventObject.parentElement;\n }\n\n let isContainEventObject = false;\n let isDropDownButton = false;\n let isDropDownTitle = false;\n\n if (eventObject) {\n isContainEventObject = this.dropDownContentRef.current.contains(eventObject);\n isDropDownButton = eventObject.classList.contains('fas_dropdown_close');\n isDropDownTitle = eventObject.classList.contains('fas_filter_property_title');\n }\n\n if (this.dropDownRef.current)\n if (!isContainEventObject && !isDropDownButton && !isDropDownTitle)\n this.toggleFilterDropdown();\n }\n\n private dropDownContent() {\n return (\n \n
\n
\n
\n
\n
this.tabNav.setFocusAtt(el)}>\n {this.getmatchingArticleCounter()}\n
\n
\n );\n }\n\n private getmatchingArticleCounter() {\n const result: string = this.props.articleCount > 1 ?\n this.props.l10n.showItems :\n this.props.l10n.showItem;\n\n const strings = result.split('{0}');\n let surfix = '';\n let prefix = '';\n if (strings.length > 1) {\n prefix = strings[0];\n surfix = strings[1];\n } else\n surfix = strings[0];\n\n return (\n \n {prefix} {this.props.articleCount} {surfix}\n
\n );\n }\n\n private toggleFilterDropdown(event: React.MouseEvent = null) {\n if(window.shell && window.shell.tabNav) // when modal is destroyed, tanNav loses focus, need to give him a hint\n window.shell.tabNav.focus(this.dropDownRef.current);\n\n // for screen auto scroll, tablet only:\n if (event && (isTablet || isIPad13))\n this.mouseYPosition = event.clientY;\n\n if (this.props.filterProperty.name === this.props.activeDropDownFilterName) {\n this.scrollLock.unlock();\n // Empty string means no selected dropdown\n this.props.activeDropDownFilterChange('');\n } else {\n // delay is needed only for tablets.\n // for screen auto scroll, tablet only:\n if (isTablet || isIPad13)\n setTimeout(() => {\n this.scrollLock.lock();\n }, 500);\n\n this.props.activeDropDownFilterChange(this.props.filterProperty.name);\n }\n }\n}\n","import { ISelectedFilter } from './selectedFilters.d';\nimport { ArticleTilesAjaxCaller } from '../articleTileGrid/ArticleTilesAjaxCaller';\nimport { IKioskInfo } from '../categorypage/categorypage.d';\nimport { IFilteringResult } from '../articleTileGrid/article-tile/article-tile.d';\nimport TouchPointFactory from '../../Common/MPCTouchPointFactory';\nimport { ViewType } from '../../Common/enums';\n\nexport class ArticleListController {\n private touchPointFactory: TouchPointFactory;\n\n constructor(\n private articleTilesAjaxCaller: ArticleTilesAjaxCaller,\n private newArticlesLoadedCallBack: (result: IFilteringResult) => void) {\n this.touchPointFactory = TouchPointFactory.instance;\n }\n\n public updateArticleList(\n lastAddedFilter: ISelectedFilter,\n lastAddedFilterSource: string,\n shouldForceScrollUp: boolean,\n kiosk: IKioskInfo,\n viewType: ViewType\n ) {\n const ajaxStartTime: number = performance.now();\n this.articleTilesAjaxCaller\n .loadArticlesAfterFilterChange(kiosk, lastAddedFilter, lastAddedFilterSource, viewType)\n .then(\n (result) => {\n this.newArticlesLoadedCallBack(result);\n this.touchPointFactory.createTouchPointsBatch(result.articles);\n const ajaxEndTime: number = performance.now();\n const timeoutDelay = Math.max(300 - (ajaxEndTime - ajaxStartTime), 0);\n setTimeout(() => {\n if (shouldForceScrollUp) {\n window.shell.publishTo('ESPP.MainSidePanel.Background.ForceScrollToTop',\n 'ESPP.MainSidePanel.Background.ForceScrollToTop');\n\n window.shell.publishTo('footerToTopButtonAnimationFinished', null);\n }\n }, timeoutDelay);\n },\n // Note: it's important to handle errors here\n // instead of a catch() block so that we don't swallow\n // exceptions from actual bugs in components.\n // (error) => {},\n );\n }\n\n public static AddLastAddedForTracing(\n ajaxUrl: string,\n lastAddedFilter: ISelectedFilter,\n lastAddedFilterSource: string): string {\n const url = new URL(ajaxUrl);\n url.searchParams.delete('lastAddedFilter');\n url.searchParams.delete('lastAddedFilterSource');\n if (!lastAddedFilter || !lastAddedFilterSource)\n return url.toString();\n\n url.searchParams.set('lastAddedFilter', lastAddedFilter.dimension.slug + ':' + lastAddedFilter.value.slug);\n url.searchParams.set('lastAddedFilterSource', lastAddedFilterSource);\n url.searchParams.sort();\n return url.toString();\n }\n}\n","import { IFilterModel } from './filterModel.d';\nimport { FilterAjaxUpdateController } from './FilterAjaxUpdateController';\nimport { SelectedFiltersState } from './SelectedFiltersState';\nimport { ISelectedFilter } from './selectedFilters.d';\nimport { IGlobalStateChangedEvent, Handler, GlobalState } from './globalState';\nimport { IKioskInfo } from '../categorypage/categorypage.d';\nimport { ViewType } from '../../Common/enums';\n\nexport interface IFilterModelChangedEvent {\n filterModel: IFilterModel;\n}\n\nexport interface IErrorWhileUpdatingAvailableFilterEvent {\n error: Error;\n}\n\nexport interface ICategoryData {\n navigationKey: string;\n categoryPath: string;\n seoSlug: string;\n}\n\nexport class AvailableFiltersState {\n public selectedFilterState: SelectedFiltersState;\n\n private filterModel: IFilterModel;\n private handlers: Array> = [];\n private errorHandlers: Array> = [];\n private ajaxController: FilterAjaxUpdateController;\n private readonly triggerName: string;\n private viewType: ViewType;\n\n constructor(\n private globalState: GlobalState,\n categoryData: ICategoryData,\n triggerName: string,\n initFilterModel: IFilterModel,\n initSelectedFilters: ISelectedFilter[],\n searchTerm: string,\n kiosk: IKioskInfo,\n ) {\n this.selectedFilterState = new SelectedFiltersState(initSelectedFilters);\n this.ajaxController = new FilterAjaxUpdateController(this.globalState, categoryData.navigationKey,\n this, this.selectedFilterState, searchTerm, kiosk);\n this.triggerName = triggerName;\n this.filterModel = initFilterModel;\n this.viewType = searchTerm ? ViewType.Search : ViewType.Category;\n this.globalState.registerOnStateChanged(this.onGlobalStateChanged.bind(this));\n }\n\n public updateGlobalState() {\n this.globalState.updateState(\n this.selectedFilterState.selectedFilters,\n this.filterModel,\n this.triggerName,\n this.selectedFilterState.lastAddedFilter,\n this.triggerName, \n this.ajaxController.kiosk, \n this.viewType);\n }\n\n public restoreToGlobalState() {\n const globalStateFilterModel = { ...this.globalState.filterModel };\n this.onNewFilterModel(globalStateFilterModel);\n const globalStateSelectedFilters = [...this.globalState.selectedFilters];\n const kioskInfo = {...this.globalState.kioskInfo};\n this.selectedFilterState.onGlobalStateChanged(\n globalStateSelectedFilters, \n kioskInfo ? kioskInfo.retailStoreFilterMode : null);\n }\n\n public getFilterModel(): IFilterModel {\n return this.filterModel;\n }\n\n public registerOnStateChanged(handler: Handler) {\n this.handlers.push(handler);\n }\n\n public registerErrorHandler(handler: Handler) {\n this.errorHandlers.push(handler);\n }\n\n public onNewFilterModel(filterModel: IFilterModel) {\n this.filterModel = filterModel;\n this.selectedFilterState.updateSelectedStateWithNewFitlerModel(filterModel);\n this.stateChanged({filterModel} as IFilterModelChangedEvent);\n }\n\n public onErrorWhileUpdating(error: Error) {\n for (const h of this.errorHandlers)\n h({error} as IErrorWhileUpdatingAvailableFilterEvent);\n }\n\n private stateChanged(event: IFilterModelChangedEvent) {\n for (const h of this.handlers)\n h(event);\n }\n\n private onGlobalStateChanged(event: IGlobalStateChangedEvent) {\n if (event.originalTrigger !== this.triggerName) {\n this.onNewFilterModel({ ...event.filterModel });\n this.selectedFilterState.onGlobalStateChanged(\n event.selectedFilters,\n event.retailStoreFilterMode\n );\n }\n if (this.ajaxController.kiosk)\n this.ajaxController.kiosk.retailStoreFilterMode = event.retailStoreFilterMode;\n }\n}\n","import { IFilteringResult, ISalesDesignationView } from '../articleTileGrid/article-tile/article-tile.d';\nimport { GlobalState } from '../globalState/globalState';\n\nexport class BookmarkController {\n private _articleList: ISalesDesignationView[];\n\n constructor(articleList: ISalesDesignationView[], globalState: GlobalState) {\n this._articleList = articleList;\n if (typeof window !== 'undefined')\n this.subscribeToBookmark();\n\n // for tests\n if(globalState)\n globalState.registerOnArticlesChanged(this.onNewArticles.bind(this));\n }\n\n public onNewArticles(result: IFilteringResult) {\n this._articleList = result.articles;\n }\n\n public updateArticleList(articles: ISalesDesignationView[]): void {\n this._articleList = this._articleList.concat(articles);\n }\n\n public getPayloadById(id: string) {\n let article: ISalesDesignationView;\n\n const isTeaserView = \n document.getElementsByClassName('fas_teaser_suggestion_container').length > 0;\n if (isTeaserView) {\n const slicedTeaserId = id.split('_')[0]; // removes grid number \"_n\"\n article = this._articleList.find(article => article.defaultSalesArticleNo === slicedTeaserId);\n } else {\n article = this._articleList.find(article => article.defaultSalesArticleNo === id);\n }\n \n const element = document.getElementById(`ats_${id}`);\n const fullPath = article.salesArticleVariantColors\n .find(color => color.color.name === element.dataset.color).image.fullPath;\n const path = new URL(fullPath).pathname;\n const imageId = path.substring(path.indexOf('/product') + 1);\n return {\n requester: id,\n productName: article.description,\n colorName: element.dataset.color,\n imageUrl: imageId,\n savKey: element.dataset.savkey,\n categoryName: element.dataset.categoryname\n }\n }\n\n private subscribeToBookmark() {\n if (window && window.shell) {\n window.shell.subscribeTo('ESCID.ESPP.Bookmark.RequestArticleDataInjection',\n (requestData) => {\n const payload = this.getPayloadById(requestData.requester);\n window.shell.publishTo('ESCID.ESPP.Bookmark.ArticleDataInjection', payload);\n },\n 'ESCID.ESPP.ArticleTileService');\n }\n }\n}\n","import { IFilterSelectionChangedEvent, SelectedFiltersState, FilterSelectionChangedType } from './SelectedFiltersState';\nimport { AvailableFiltersState } from './AvailableFiltersState';\nimport { Handler, GlobalState } from './globalState';\nimport { convertFromReactToServiceWorld } from './ServiceSelectedFilterConverter';\nimport { IFilterModel } from './filterModel.d';\nimport * as helper from '../../Common/html-helper';\nimport { IKioskInfo } from '../categorypage/categorypage.d';\n\nexport class FilterAjaxUpdateController {\n\n public readonly kiosk: IKioskInfo;\n\n private readonly navigationKey: string;\n private readonly availableFiltersState: AvailableFiltersState;\n private readonly searchTerm: string;\n public static IsRequestRunning = false;\n\n constructor(\n private globalState: GlobalState,\n navigationKey: string,\n availableFiltersState: AvailableFiltersState,\n selectedFilterState: SelectedFiltersState,\n searchTerm: string,\n kiosk: IKioskInfo,\n ) {\n selectedFilterState.registerOnStateChanged(this.onFiltersChanged.bind(this));\n\n this.navigationKey = navigationKey;\n this.availableFiltersState = availableFiltersState;\n this.searchTerm = searchTerm;\n this.kiosk = kiosk;\n }\n\n private onFiltersChanged: Handler = (event: IFilterSelectionChangedEvent) => {\n if (typeof window === 'undefined')\n return;\n\n // For GlobalUpdate, no need to do the ajax call\n if (event.type === FilterSelectionChangedType.GlobalUpdate)\n return;\n\n if (event.type === FilterSelectionChangedType.RetailStoreFilterModeChanged)\n this.kiosk.retailStoreFilterMode = event.retailStoreFilterMode;\n\n const apiUrl = this.urlBuilder().toString();\n const serviceSelectedFilters = convertFromReactToServiceWorld(event.selectedFilters);\n this.globalState.Spinner.startSpinner();\n FilterAjaxUpdateController.IsRequestRunning = true;\n fetch(apiUrl,\n {\n body: JSON.stringify(serviceSelectedFilters),\n credentials: 'include',\n headers: {\n 'Accept': 'application/json',\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n },\n ).then((res) => res.json())\n .then(\n (result: IFilterModel) => {\n FilterAjaxUpdateController.IsRequestRunning = false;\n this.availableFiltersState.onNewFilterModel(result);\n this.globalState.Spinner.stopSpinner();\n // global filters has to be updated on every filter value select/deselect:\n // WARNING!!!! make an if condition, for mobile, we don't do global update\n const shouldNotGlobalUpdate: boolean = helper.isMobileShop_DontUseFromReact() &&\n event.type !== FilterSelectionChangedType.GlobalFilterRemoved &&\n event.type !== FilterSelectionChangedType.GlobalAllFilterRemoved;\n if (!shouldNotGlobalUpdate)\n this.availableFiltersState.updateGlobalState();\n },\n // Note: it's important to handle errors here\n // instead of a catch() block so that we don't swallow\n // exceptions from actual bugs in components.\n (error) => {\n FilterAjaxUpdateController.IsRequestRunning = false;\n this.availableFiltersState.onErrorWhileUpdating(error);\n this.availableFiltersState.restoreToGlobalState();\n this.globalState.Spinner.stopSpinner();\n },\n );\n }\n\n // Url builder\n private urlBuilder(): URL {\n const apiUrl = this.globalState.getMagicLink\n + 'api/articletileservice/calculateFilters';\n const url = new URL(apiUrl);\n if (this.searchTerm !== null && (typeof this.searchTerm !== 'undefined'))\n url.searchParams.set('query', this.searchTerm);\n else\n url.searchParams.set('navigationKey', this.navigationKey);\n\n if (this.kiosk && (typeof this.kiosk !== 'undefined')) {\n url.searchParams.set('wwsID', this.kiosk.retailStoreNo.toString());\n if (this.kiosk.retailStoreFilterMode)\n url.searchParams.set('kioskType', this.kiosk.retailStoreFilterMode);\n }\n return url;\n }\n}\n","import React from 'react';\nimport { SortContext } from './sortContextProvider';\nimport { ArticleTileContext } from './articleTileContext';\n\nexport const MultipleContext = React.createContext({ context1: {}, context2: {} });\n\nexport default function MultipleContextProvider(props: React.PropsWithChildren<{}>) {\n return (\n \n {context1 => (\n \n {context2 => (\n \n {props.children}\n \n )}\n \n )}\n \n );\n}\n\n","import { ISelectedFilter } from './selectedFilters.d';\nimport { Handler } from './globalState';\nimport { IFilterModel } from './filterModel.d';\nimport { IFilterProperty } from '../fas-bar/filter-property.d';\n\nexport enum FilterSelectionChangedType {\n FilterAdded,\n FilterRemoved,\n AllFiltersRemoved,\n GlobalUpdate,\n GlobalFilterRemoved,\n GlobalAllFilterRemoved,\n RetailStoreFilterModeChanged,\n}\n\nexport interface IFilterSelectionChangedEvent {\n type: FilterSelectionChangedType;\n selectedFilters: ISelectedFilter[];\n changedFilter: ISelectedFilter;\n retailStoreFilterMode?: string;\n}\n\nexport class SelectedFiltersState {\n private _selectedFilters: ISelectedFilter[] = [];\n get selectedFilters() {\n return this._selectedFilters;\n }\n private _lastAddedFilter: ISelectedFilter;\n get lastAddedFilter() {\n return this._lastAddedFilter;\n }\n private handlers: Array> = [];\n\n constructor(initselectedFilters: ISelectedFilter[]) {\n this._selectedFilters = initselectedFilters;\n }\n\n public registerOnStateChanged(handler: Handler) {\n this.handlers.push(handler);\n }\n\n public onGlobalStateChanged(\n newSelectedFilters: ISelectedFilter[],\n retailStoreFilterMode: string) {\n this._selectedFilters = [...newSelectedFilters];\n this.stateChanged(\n {\n selectedFilters: newSelectedFilters,\n type: FilterSelectionChangedType.GlobalUpdate,\n retailStoreFilterMode: retailStoreFilterMode\n } as IFilterSelectionChangedEvent);\n }\n\n public removeAllFilters() {\n this._selectedFilters = [];\n this._lastAddedFilter = null;\n this.stateChanged(\n {\n selectedFilters: this._selectedFilters,\n type: FilterSelectionChangedType.AllFiltersRemoved,\n } as IFilterSelectionChangedEvent);\n }\n\n public globalRemoveAllFilters() {\n this._selectedFilters = [];\n this._lastAddedFilter = null;\n this.stateChanged(\n {\n selectedFilters: this._selectedFilters,\n type: FilterSelectionChangedType.GlobalAllFilterRemoved,\n } as IFilterSelectionChangedEvent);\n }\n\n public addFilter(filter: ISelectedFilter) {\n // don't push, create new one, for change detection\n if (filter !== null) {\n this._selectedFilters = this._selectedFilters.concat([filter]);\n this._lastAddedFilter = filter;\n }\n this.stateChanged(\n {\n changedFilter: filter,\n selectedFilters: this._selectedFilters,\n type: FilterSelectionChangedType.FilterAdded,\n } as IFilterSelectionChangedEvent);\n }\n\n public changeKiosk(retailStoreFilterMode: string) {\n this.stateChanged(\n {\n changedFilter: null,\n retailStoreFilterMode,\n selectedFilters: this._selectedFilters,\n type: FilterSelectionChangedType.RetailStoreFilterModeChanged,\n } as IFilterSelectionChangedEvent);\n }\n\n public removeFilter(filter: ISelectedFilter) {\n // ESPP-6123\n const isRestValueValid = this._selectedFilters.find((sf) => {\n const isSameDimesion = sf.dimension.name === filter.dimension.name;\n const hasArticle = sf.value.articlesAmount > 0;\n const notRemovedFilter = sf.value.id !== filter.value.id;\n return isSameDimesion && hasArticle && notRemovedFilter;\n });\n\n if (isRestValueValid !== undefined)\n this._selectedFilters.splice(\n this._selectedFilters.findIndex((x) => {\n const sameDimesion = x.dimension.name === filter.dimension.name;\n const sameId = x.value.id === filter.value.id;\n return sameDimesion && sameId;\n })\n , 1);\n else\n this._selectedFilters = this._selectedFilters.filter((x) => {\n return x.dimension.name !== filter.dimension.name;\n });\n\n this._lastAddedFilter = null;\n this.stateChanged(\n {\n changedFilter: filter,\n selectedFilters: this._selectedFilters,\n type: FilterSelectionChangedType.FilterRemoved,\n } as IFilterSelectionChangedEvent);\n }\n\n public globalRemoveFilter(filter: ISelectedFilter) {\n // ESPP-6123\n const isRestValueValid = this._selectedFilters.find((sf) => {\n const isSameDimesion = sf.dimension.name === filter.dimension.name;\n const hasArticle = sf.value.articlesAmount > 0;\n const notRemovedFilter = sf.value.id !== filter.value.id;\n return isSameDimesion && hasArticle && notRemovedFilter;\n });\n\n if (isRestValueValid !== undefined)\n this._selectedFilters.splice(\n this._selectedFilters.findIndex((x) => {\n const sameDimesion = x.dimension.name === filter.dimension.name;\n const sameId = x.value.id === filter.value.id;\n return sameDimesion && sameId;\n })\n , 1);\n else\n this._selectedFilters = this._selectedFilters.filter((x) => {\n return x.dimension.name !== filter.dimension.name;\n });\n\n this._lastAddedFilter = null;\n this.stateChanged(\n {\n changedFilter: filter,\n selectedFilters: this._selectedFilters,\n type: FilterSelectionChangedType.GlobalFilterRemoved,\n } as IFilterSelectionChangedEvent);\n }\n\n public anyFiltersSelected(): boolean {\n return this._selectedFilters.length > 0;\n }\n\n public sortSelectedFilters(): ISelectedFilter[] {\n const dict: { [key: string]: ISelectedFilter[] } = {};\n this._selectedFilters.forEach((filter) => {\n const dimensionName = filter.dimension.name.split(':')[0];\n if (!dict[dimensionName])\n dict[dimensionName] = new Array();\n dict[dimensionName].push(filter);\n });\n let newOrder: Array = [];\n for (const key in dict)\n if (dict.hasOwnProperty(key)) { // eslint-disable-line no-prototype-builtins\n const value = dict[key];\n newOrder = newOrder.concat(value);\n }\n return newOrder;\n }\n\n public updateSelectedStateWithNewFitlerModel(filterModel: IFilterModel) {\n this._selectedFilters.forEach((selectedFilter, index, arr) => {\n const foundDimension = this.findFilterdimension(filterModel, selectedFilter.dimension.name);\n if (foundDimension) {\n const foundValue = foundDimension.filterPropertyValues.find((filterValue) =>\n filterValue.id === selectedFilter.value.id);\n if (foundValue)\n arr[index] = { dimension: foundDimension, value: foundValue } as ISelectedFilter;\n else\n selectedFilter.value.articlesAmount = 0;\n } else {\n selectedFilter.value.articlesAmount = 0;\n }\n });\n }\n\n private findFilterdimension(filterModel: IFilterModel, dimesionName: string): IFilterProperty {\n for (const filter of filterModel.filters) {\n const candidate = this.findFilterdimensionSingle(filter, dimesionName);\n if (candidate)\n return candidate;\n }\n return null;\n }\n\n private findFilterdimensionSingle(filter: IFilterProperty, dimesionName: string): IFilterProperty {\n let result: IFilterProperty;\n if (filter.name === dimesionName)\n result = filter;\n else\n if (filter.subProperties) {\n let tmp: IFilterProperty;\n for (const subFilter of filter.subProperties) {\n tmp = this.findFilterdimensionSingle(subFilter, dimesionName);\n if (tmp) {\n result = tmp;\n break;\n }\n }\n }\n return result;\n }\n\n private stateChanged(event: IFilterSelectionChangedEvent) {\n for (const h of this.handlers)\n h(event);\n }\n}\n","import { ISelectedFilter, IServiceSelectedFilter } from '../globalState/selectedFilters.d';\nimport { IFilterModel } from '../globalState/filterModel.d';\nimport { IFilterProperty } from '../fas-bar/filter-property.d';\n\nexport function convertFromServiceToReactWorld(\n fromService: IServiceSelectedFilter[],\n filterModel: IFilterModel,\n): ISelectedFilter[] {\n if (!fromService) return [];\n return fromService.map((filterSelected) => {\n if (!filterModel.filters)\n return null;\n let filterProperty = filterModel.filters.find(\n (filter) => filter.slug === filterSelected.dimensionSlug ,\n );\n if (!filterProperty)\n // try to look inside sub filters:\n filterProperty = getSubFilter(filterModel, filterSelected);\n\n if (!filterProperty)\n return null;\n\n return filterSelected.valueSlugs.map((valueSelected) => {\n let filterPropertyValue = filterProperty.filterPropertyValues.find(\n (value) => value.slug === valueSelected,\n );\n\n if (!filterPropertyValue) {\n // go throw sub filters:\n filterProperty.subProperties.forEach((subProperty) => {\n subProperty.filterPropertyValues.forEach((fpv) => {\n if (fpv.slug === valueSelected)\n filterPropertyValue = fpv;\n });\n },\n );\n\n if (!filterPropertyValue)\n return null;\n }\n\n return {\n dimension: filterProperty,\n value: filterPropertyValue,\n } as ISelectedFilter;\n });\n }).reduce((prev, cur) => prev.concat(cur), [])\n .filter((filter) => filter != null);\n}\n\nfunction getSubFilter(filterModel: IFilterModel, filterSelected: IServiceSelectedFilter): IFilterProperty {\n let result: IFilterProperty = null;\n if (!filterModel.filters)\n return result;\n\n for (const filter of filterModel.filters) {\n if (filter.subProperties && filter.subProperties.length > 0)\n result = filter.subProperties.find(\n (subFilter) => subFilter.slug === filterSelected.dimensionSlug,\n );\n\n if (result)\n break;\n }\n\n return result;\n}\n\nexport function convertFromReactToServiceWorld(\n fromReact: ISelectedFilter[],\n): IServiceSelectedFilter[] {\n return fromReact.reduce(\n (rv: IServiceSelectedFilter[], x: ISelectedFilter) => {\n const serviceSelectedFilter = rv.find((y) => y.dimensionSlug === x.dimension.slug);\n if (serviceSelectedFilter)\n serviceSelectedFilter.valueSlugs.push(x.value.slug);\n else\n rv.push({\n dimensionSlug: x.dimension.slug,\n valueSlugs: [x.value.slug],\n } as IServiceSelectedFilter);\n\n return rv;\n },\n []);\n}\n","import { IServiceSelectedFilter, ISelectedFilter } from '../globalState/selectedFilters.d';\nimport { ArticleListController } from './ArticleListController';\nimport { convertFromReactToServiceWorld } from './ServiceSelectedFilterConverter';\nimport * as helper from '../../Common/html-helper';\nimport { ISortProperty } from '../sort-side-content/sort-side-content.d';\n\nexport interface IUrlController {\n updateUrlForPaging(pageNo: number, tileId: string): void;\n updateUrlForFiltering(\n selectedFilters: ISelectedFilter[],\n lastAddedFilter: ISelectedFilter,\n lastAddedFilterSource: string): void;\n}\nexport class UrlController implements IUrlController {\n private seoSlug: string;\n private viewType: string;\n\n constructor(seoSlug: string,\n viewType: string,\n private magicLink: string) {\n this.seoSlug = seoSlug;\n this.viewType = viewType;\n }\n\n public updateUrlForPaging(pageNo: number, tileId: string): void\n {\n if (typeof window === 'undefined')\n return;\n \n const url = new URL(window.location.href);\n if(pageNo <= 1) \n url.searchParams.delete('page'); \n else\n url.searchParams.set('page', pageNo.toString());\n\n if(tileId)\n url.hash = tileId;\n history.replaceState(null, '', url);\n }\n\n public updateUrlForFiltering(\n selectedFilters: ISelectedFilter[],\n lastAddedFilter: ISelectedFilter,\n lastAddedFilterSource: string)\n {\n if (typeof window === 'undefined')\n return;\n\n const url = new URL(window.location.href);\n url.searchParams.delete('page');\n\n let urlNoFilter = this.magicLink;\n\n const serviceSelectedFilters = convertFromReactToServiceWorld(selectedFilters).sort((a, b) => {\n return a.dimensionSlug.localeCompare(b.dimensionSlug);\n });\n\n if (this.viewType !== 'Search') {\n if (selectedFilters.length > 0)\n urlNoFilter += 'f/';\n urlNoFilter += this.seoSlug + '/';\n urlNoFilter += this.toUrl(serviceSelectedFilters);\n urlNoFilter += url.search;\n } else {\n if (selectedFilters.length > 0) {\n const queryFilters = this.toUrl(serviceSelectedFilters).slice(0, -1);\n url.searchParams.set('filterParams', queryFilters);\n } else\n url.searchParams.delete('filterParams');\n\n url.searchParams.sort();\n\n urlNoFilter = url.toString();\n // ESPP-4200\n urlNoFilter = urlNoFilter.replace(/%3A/g, ':');\n urlNoFilter = urlNoFilter.replace(/%2C/g, ',');\n }\n\n if (helper.isMobileShop_DontUseFromReact())\n urlNoFilter = ArticleListController.AddLastAddedForTracing(\n urlNoFilter,\n lastAddedFilter,\n lastAddedFilterSource);\n\n history.replaceState(selectedFilters, '', urlNoFilter);\n }\n\n private toUrl(selectedFilters: IServiceSelectedFilter[]) {\n let joined = selectedFilters.map((x) => x.dimensionSlug + ':' + x.valueSlugs.sort().join(',')).join('/');\n if (selectedFilters.length > 0)\n joined += '/';\n\n return joined;\n }\n\n public updateSortUrl(sortUrl: string) {\n if (typeof window === 'undefined')\n return;\n\n const query = new URLSearchParams(window.location.search);\n query.delete('page');\n\n if (!sortUrl)\n query.delete('sort');\n else\n if (!query.get('sort'))\n query.append('sort', sortUrl);\n else\n query.set('sort', sortUrl);\n\n query.sort();\n let newPathQuery = window.location.pathname;\n if (query.toString().length > 0)\n newPathQuery += '?' + query.toString();\n\n history.pushState(null, '', newPathQuery);\n }\n\n public updateGloveSortUrl(sortPropertyList: ISortProperty[], gloveSortUrlValue: string) {\n const query: URLSearchParams = new URLSearchParams(window.location.search);\n const sortValue: string = query.get('sort');\n const isNormalSort = this.isNormalSortUrlValue(sortValue, sortPropertyList);\n if (!isNormalSort || gloveSortUrlValue.length > 0)\n this.updateSortUrl(gloveSortUrlValue);\n }\n\n private isNormalSortUrlValue(urlValue: string, sortPropertyList: ISortProperty[]): boolean {\n const sortValue = sortPropertyList.find((sortProperty) => sortProperty.urlValue === urlValue);\n return typeof sortValue !== 'undefined';\n }\n}\n","/* eslint-disable @typescript-eslint/no-empty-function */\n/* eslint-disable @typescript-eslint/no-unused-vars */\n\nimport React, { createContext, useReducer } from 'react';\nimport { IL10N } from '../../Common/l10n-keys';\nimport { ArticleTileContextActionType } from '../../Common/enums';\n\nexport const ArticleTileContext = createContext({\n l10n: {} as IL10N,\n mspOrigin: '',\n setMspOrigin: (origin: string) => {},\n});\n\nfunction catStateReducer(state, action) {\n if (action.type === ArticleTileContextActionType.SetMspOrigin){\n return {\n mspOrigin: action.payload.mspOrigin,\n };\n }\n\n return {\n mspOrigin: '',\n };\n}\n\nexport default function ArticleTileContextProvider(\n props: React.PropsWithChildren<{l10n: IL10N}>) {\n const [catState, catStateDispatch ] = useReducer(catStateReducer, {mspOrigin: ''});\n\n function setMspOrigin(origin: string) {\n catStateDispatch({type: ArticleTileContextActionType.SetMspOrigin, payload: {mspOrigin: origin}});\n }\n\n const ctxValue = {\n l10n: props.l10n,\n setMspOrigin: setMspOrigin,\n mspOrigin: catState.mspOrigin\n };\n\n return (\n \n {props.children}\n \n );\n}\n","import { ISelectedFilter } from './selectedFilters.d';\nimport { IFilterModel } from './filterModel.d';\nimport { IUrlController, UrlController } from './UrlController';\nimport { ArticleListController } from './ArticleListController';\nimport { IKioskInfo } from '../categorypage/categorypage.d';\nimport { IFilteringResult } from '../articleTileGrid/article-tile/article-tile.d';\nimport { SelectedFiltersState } from './SelectedFiltersState';\nimport { ViewType } from '../../Common/enums';\n\nexport type Handler = (event: E) => void;\n\nexport interface IGlobalStateChangedEvent {\n originalTrigger: string;\n selectedFilters: ISelectedFilter[];\n filterModel: IFilterModel;\n lastAddedFilter: ISelectedFilter;\n lastAddedFilterSource: string;\n retailStoreFilterMode: string;\n}\n\nexport class GlobalState {\n private _selectedFilters: ISelectedFilter[] = [];\n private _kiosk: IKioskInfo = null;\n private _urlController: IUrlController;\n private _articleListController: ArticleListController;\n private _selectedFiltersState: SelectedFiltersState;\n private _lastAddedFilter: ISelectedFilter;\n private _viewType: ViewType;\n private _magicLink: string;\n public onNewArticlesLoaded: (articles)=>void\n\n get urlController(): IUrlController {\n return this._urlController;\n }\n get lastAddedFilter() {\n return this._lastAddedFilter;\n }\n private _lastAddedFilterSource: string;\n get lastAddedFilterSource() {\n return this._lastAddedFilterSource;\n }\n\n get selectedFilters() {\n return this._selectedFilters;\n }\n\n private _filterModel: IFilterModel;\n get filterModel() {\n return this._filterModel;\n }\n\n constructor(filterModel: IFilterModel,\n selectedFilters: ISelectedFilter[],\n seoSlug: string,\n searchTerm: string,\n kiosk: IKioskInfo,\n magicLink: string) {\n\n this._selectedFilters = [...selectedFilters];\n this._filterModel = { ...filterModel };\n this._viewType = searchTerm ? ViewType.Search : ViewType.Category;\n this._urlController = new UrlController(seoSlug, this._viewType, magicLink);\n this._kiosk = kiosk;\n this.onNewArticlesLoaded = this._onNewArticlesLoaded.bind(this);\n this._magicLink = magicLink;\n }\n\n public get getMagicLink(): string {\n return this._magicLink;\n }\n\n set articleListController(articleListController: ArticleListController) {\n this._articleListController = articleListController;\n }\n\n set selectedFiltersState(selectedFiltersState: SelectedFiltersState) {\n this._selectedFiltersState = selectedFiltersState;\n }\n\n get kioskInfo() {\n return this._kiosk;\n }\n\n private _onNewArticlesLoaded(articles: IFilteringResult) {\n for (const h of this.articlesChangedHandlers)\n h(articles);\n }\n\n\n public removeAllFilters() {\n this.updateState(\n [], this.filterModel, 'removeAllFilters', null, null, this._kiosk, this._viewType\n );\n // ESPP-9504: selectedFilterState need here to trigger ajax update for matchingArticleCount\n this._selectedFiltersState.globalRemoveAllFilters();\n }\n\n public updateState(\n selectedFilters: ISelectedFilter[],\n filterModel: IFilterModel,\n originalTrigger: string,\n lastAddedFilter: ISelectedFilter,\n lastAddedFilterSource: string,\n kiosk: IKioskInfo,\n viewType: ViewType) {\n // use clone instead of copy to make sure reference update\n this._selectedFilters = [...selectedFilters];\n this._filterModel = { ...filterModel };\n this._lastAddedFilter = lastAddedFilter;\n this._lastAddedFilterSource = lastAddedFilterSource;\n this._kiosk = kiosk;\n this._viewType = viewType;\n const event: IGlobalStateChangedEvent = {\n filterModel: this._filterModel,\n retailStoreFilterMode: kiosk?.retailStoreFilterMode,\n lastAddedFilter,\n lastAddedFilterSource,\n originalTrigger,\n selectedFilters: this._selectedFilters,\n };\n\n this._urlController.updateUrlForFiltering(\n this._selectedFilters,\n this._lastAddedFilter,\n this._lastAddedFilterSource);\n\n const shouldForceScrollUp: boolean = originalTrigger === 'fas-side-content';\n\n this.updateArticleList(shouldForceScrollUp);\n\n for (const h of this.filterModelChangeHandlers)\n h(event);\n }\n\n public updateArticleList(shouldForceScroll: boolean) {\n this._articleListController.updateArticleList(\n this._lastAddedFilter, this._lastAddedFilterSource,\n shouldForceScroll, this._kiosk, this._viewType);\n }\n\n private filterModelChangeHandlers: Array> = [];\n private articlesChangedHandlers: Array> = [];\n public registerOnStateChanged(handler: Handler) {\n this.filterModelChangeHandlers.push(handler);\n }\n public registerOnArticlesChanged(handler: Handler) {\n this.articlesChangedHandlers.push(handler);\n }\n\n public Spinner = new GlobalSpinnerState();\n}\n\nexport class GlobalSpinnerState {\n\n private _spinnerStack = 0;\n private spinnerStateChangedHandlers: { [id: string]: Handler } = {};\n public registerSpinnerChanged(id: string, handler: Handler) {\n this.spinnerStateChangedHandlers[id] = handler;\n }\n public unregisterSpinnerChanged(id: string) {\n delete this.spinnerStateChangedHandlers[id];\n }\n private sendSpinnerStateEvent(newState: boolean) {\n const entries = Object.entries(this.spinnerStateChangedHandlers);\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n entries.forEach(([_, handler]) => {\n handler(newState);\n })\n }\n\n public stopSpinner() {\n this._spinnerStack--;\n if (this._spinnerStack > 0) return;\n\n const now = new Date().getTime();\n const spinnerShownDuration = now - this._spinnerStartActualTime;\n if (spinnerShownDuration < GlobalSpinnerState.SpinnerMinimumTime) {\n setTimeout(() => {\n this.sendSpinnerStateEvent(false);\n }, GlobalSpinnerState.SpinnerMinimumTime - spinnerShownDuration);\n } else {\n this.sendSpinnerStateEvent(false);\n }\n }\n private _spinnerStartActualTime: number;\n static readonly SpinnerMinimumTime: number = 1000;\n public startSpinner() {\n this._spinnerStack++;\n setTimeout(() => {\n if (this._spinnerStack > 0) {\n this.sendSpinnerStateEvent(true);\n this._spinnerStartActualTime = new Date().getTime();\n }\n }, 200);\n }\n}\n","import React from 'react';\r\nimport sortContextReducer, {initState} from './sortContextReducer';\r\nimport { SortType, ISortProperty } from '../sort-side-content/sort-side-content.d';\r\nimport { ISortContextProviderState, ISortContextProviderProps, ISortContext } from './sortContextProvider.d';\r\nimport { IFilterPropertyValue } from '../fas-bar/filter-property-value.d';\r\nimport { SortContextActionType } from '../../Common/enums';\r\nimport { UrlController } from './UrlController';\r\n\r\nexport const SortContext = React.createContext(null);\r\n\r\nfunction SortContextProvider(props: React.PropsWithChildren) {\r\n const { viewType, l10n, glovesFilter, initialSort, isMobile, magicLink } = props;\r\n const [state, dispatch] = React.useReducer(sortContextReducer,\r\n {l10n, viewType, glovesFilter, initialSortUrlParam: initialSort}, initState);\r\n \r\n const onChangeSideEffects = React.useRef([]);\r\n const urlController = React.useMemo(() => new UrlController(null, viewType, magicLink),[]);\r\n\r\n React.useEffect(() => {\r\n if (!isMobile) {\r\n updateUrlParams(state);\r\n }\r\n });\r\n\r\n const updateUrlParams = React.useCallback((contextState: ISortContextProviderState) => {\r\n const selectedSort = contextState.sortPropertyList.find(item => item.selected);\r\n if (selectedSort) {\r\n urlController.updateSortUrl(selectedSort.urlValue);\r\n }\r\n\r\n const hasGloveSortProps = contextState.glovesSortPropValues.length > 0;\r\n if (hasGloveSortProps) {\r\n let gloveSortUrlValue = '';\r\n\r\n // Collect ratings:\r\n contextState.glovesSortPropValues.forEach((gsProperty) => {\r\n if (gsProperty.glovesRate > 0) {\r\n if (gloveSortUrlValue.length === 0)\r\n gloveSortUrlValue = gsProperty.slug.concat(':', gsProperty.glovesRate.toString());\r\n else\r\n gloveSortUrlValue = gloveSortUrlValue.concat(',',\r\n gsProperty.slug, ':', gsProperty.glovesRate.toString());\r\n }\r\n });\r\n\r\n urlController.updateGloveSortUrl(contextState.sortPropertyList, gloveSortUrlValue);\r\n }\r\n }, []);\r\n\r\n const applyContextChanges = React.useCallback((\r\n selectedSort?: ISortProperty,\r\n updateUrl = false,\r\n updateArticleList = true,\r\n applySideEffects = true,\r\n newState?: ISortContextProviderState,\r\n ) => {\r\n if (updateUrl) {\r\n updateUrlParams(newState || state);\r\n }\r\n\r\n if (updateArticleList) {\r\n props.updateArticleList(false);\r\n }\r\n\r\n if (applySideEffects) {\r\n useSideEffects();\r\n }\r\n }, [state]);\r\n\r\n const addOnChangeSideEffect = React.useCallback((cb: Function) => {\r\n onChangeSideEffects.current.push(cb);\r\n }, []);\r\n\r\n const removeOnChangeSideEffect = React.useCallback((cb: Function) => {\r\n const index = onChangeSideEffects.current.findIndex(item => item === cb);\r\n if (index > -1) {\r\n onChangeSideEffects.current.splice(index, 1);\r\n }\r\n }, []);\r\n\r\n const useSideEffects = React.useCallback(() => {\r\n onChangeSideEffects.current.forEach(cb => typeof cb === 'function' && cb());\r\n }, []);\r\n\r\n const activeGlovesSortPropValues = React.useMemo((): IFilterPropertyValue[] => {\r\n return state.glovesSortPropValues.filter((prop) => prop.glovesRate > 0);\r\n }, [state.glovesSortPropValues]);\r\n\r\n const setSortSelected = React.useCallback((selectedSort: ISortProperty, updateUrl = false,\r\n updateArticleList = true, applySideEffects = true) => {\r\n dispatch({type: SortContextActionType.SetSortSelected, sortType: selectedSort.sortType});\r\n const newState = sortContextReducer(state, {type: SortContextActionType.SetSortSelected,\r\n sortType: selectedSort.sortType});\r\n applyContextChanges(selectedSort, updateUrl, updateArticleList, applySideEffects, newState);\r\n }, [state.sortPropertyList, state.glovesSortPropValues]);\r\n\r\n const setGloveRating = React.useCallback((\r\n gloveProp: IFilterPropertyValue,\r\n newRating: number,\r\n updateUrl = false,\r\n updateArticleList = true,\r\n applySideEffects = true\r\n ) => {\r\n dispatch({type: SortContextActionType.SetGloveRating, gloveSlug: gloveProp.slug, gloveRating: newRating});\r\n\r\n // force to update active articles grid:\r\n const newState = sortContextReducer(state, \r\n {type: SortContextActionType.SetGloveRating, \r\n gloveSlug: gloveProp.slug, \r\n gloveRating: newRating\r\n });\r\n\r\n applyContextChanges(null, updateUrl, updateArticleList, applySideEffects, newState);\r\n }, [state.sortPropertyList, state.glovesSortPropValues]);\r\n\r\n const removeRegularSortValues = React.useCallback((\r\n updateUrl = false,\r\n updateArticleList = true,\r\n applySideEffects = true\r\n ) => {\r\n dispatch({type: SortContextActionType.ResetRegularSortValues});\r\n const popSort = state.sortPropertyList.find(item => item.sortType === SortType.Popularity);\r\n applyContextChanges(popSort, updateUrl, updateArticleList, applySideEffects);\r\n }, [state.sortPropertyList, state.glovesSortPropValues]);\r\n\r\n const removeAllGloveSortValues = React.useCallback((\r\n updateUrl = true,\r\n updateArticleList = true,\r\n applySideEffects = true\r\n ) => {\r\n dispatch({type: SortContextActionType.ResetGloveSortValues});\r\n const newState = sortContextReducer(state, {type: SortContextActionType.ResetGloveSortValues});\r\n applyContextChanges(null, updateUrl, updateArticleList, applySideEffects, newState);\r\n }, [state.sortPropertyList, state.glovesSortPropValues]);\r\n\r\n const providerValue: ISortContext = {\r\n ...state, setSortSelected, setGloveRating, removeRegularSortValues, removeAllGloveSortValues,\r\n applyContextChanges, addOnChangeSideEffect, removeOnChangeSideEffect, activeGlovesSortPropValues\r\n };\r\n\r\n return (\r\n \r\n {props.children}\r\n \r\n );\r\n}\r\n\r\nexport default SortContextProvider;\r\n","import { ISortContextProviderState } from './sortContextProvider.d';\r\nimport { ISortContextAction } from './sortContextReducer.d';\r\nimport { SortType } from '../sort-side-content/sort-side-content.d';\r\nimport { ViewType, SortContextActionType } from '../../Common/enums';\r\nimport { IL10N } from '../../Common/l10n-keys';\r\nimport { IFilterProperty } from '../fas-bar/filter-property.d';\r\n\r\nexport function initState({ viewType, l10n, glovesFilter, initialSortUrlParam }: {\r\n l10n: IL10N,\r\n viewType: ViewType,\r\n glovesFilter: IFilterProperty,\r\n initialSortUrlParam: string }\r\n): ISortContextProviderState {\r\n const initialState = {\r\n sortPropertyList: [\r\n {\r\n selected: false,\r\n sortType: SortType.Popularity,\r\n title: l10n.popularity,\r\n urlValue: viewType !== 'Search' ? null : 'popularity',\r\n },\r\n {\r\n selected: false,\r\n sortType: SortType.NewestFirst,\r\n title: l10n.newestFirst,\r\n urlValue: 'newest',\r\n },\r\n {\r\n selected: false,\r\n sortType: SortType.PriceDescending,\r\n title: l10n.priceDescending,\r\n urlValue: '-price',\r\n },\r\n {\r\n selected: false,\r\n sortType: SortType.PriceAscending,\r\n title: l10n.priceAscending,\r\n urlValue: 'price',\r\n },\r\n ],\r\n glovesSortPropValues: glovesFilter ? glovesFilter.filterPropertyValues : [],\r\n };\r\n\r\n if (!initialSortUrlParam && viewType !== 'Search') {\r\n const popSort = initialState.sortPropertyList.find(item => item.sortType === SortType.Popularity);\r\n if (popSort) {\r\n popSort.selected = true;\r\n }\r\n } else {\r\n const idx = initialState.sortPropertyList.findIndex(sortProperty => sortProperty.urlValue === initialSortUrlParam);\r\n if (initialState.sortPropertyList[idx]) {\r\n initialState.sortPropertyList[idx].selected = true;\r\n }\r\n\r\n if (initialState.glovesSortPropValues && initialState.glovesSortPropValues.length > 0) {\r\n initialState.glovesSortPropValues.forEach((gsProperty) => {\r\n gsProperty.glovesRate = 0;\r\n // if there is gloves sort parameters, check url sort parameters:\r\n if (initialSortUrlParam) {\r\n initialSortUrlParam.split(new RegExp(',|%2c')).forEach((sortParam) => {\r\n if (sortParam.indexOf(gsProperty.slug) >= 0) {\r\n gsProperty.glovesRate = Number(sortParam.substr(-1));\r\n }\r\n });\r\n }\r\n });\r\n }\r\n }\r\n\r\n return initialState;\r\n}\r\n\r\nfunction sortContextReducer(\r\n state: ISortContextProviderState,\r\n action: ISortContextAction\r\n): ISortContextProviderState {\r\n switch (action.type) {\r\n case SortContextActionType.SetSortSelected: {\r\n const newSortPropertyList = state.sortPropertyList.map((item) => {\r\n item.selected = item.sortType === action.sortType;\r\n return item;\r\n });\r\n\r\n const newGlovesSortPropValues = state.glovesSortPropValues.map((gsProperty) => {\r\n return { ...gsProperty, glovesRate: 0 };\r\n });\r\n\r\n return { ...state, sortPropertyList: newSortPropertyList, glovesSortPropValues: newGlovesSortPropValues };\r\n }\r\n\r\n case SortContextActionType.SetGloveRating: {\r\n const newGlovesSortPropValues = state.glovesSortPropValues.map((prop) => {\r\n let result = prop;\r\n if (prop.slug === action.gloveSlug) {\r\n result = {...prop, glovesRate: action.gloveRating };\r\n }\r\n return result;\r\n });\r\n\r\n const newSortPropertyList = state.sortPropertyList.map((item) => {\r\n item.selected = false;\r\n return item;\r\n });\r\n\r\n return { ...state, sortPropertyList: newSortPropertyList, glovesSortPropValues: newGlovesSortPropValues };\r\n }\r\n \r\n \r\n case SortContextActionType.ResetRegularSortValues: {\r\n const newSortPropertyList = state.sortPropertyList.map(prop => (\r\n { ...prop, selected: prop.sortType === SortType.Popularity })\r\n );\r\n\r\n return { ...state, sortPropertyList: newSortPropertyList };\r\n }\r\n\r\n case SortContextActionType.ResetGloveSortValues: {\r\n const newGlovesSortPropValues = state.glovesSortPropValues.map((gsProperty) => {\r\n return {...gsProperty, glovesRate: 0};\r\n });\r\n\r\n return { ...state, glovesSortPropValues: newGlovesSortPropValues };\r\n }\r\n\r\n default: {\r\n throw Error('Unknown SortContextReducer action: ' + action.type);\r\n }\r\n }\r\n\r\n}\r\n\r\nexport default sortContextReducer;\r\n","import { IFilterModel } from '../globalState/filterModel';\nimport { ISelectedFilter, IServiceSelectedFilter } from '../globalState/selectedFilters';\nimport { IL10N } from '../../Common/l10n-keys';\nimport { IKioskInfo } from '../categorypage/categorypage.d';\nimport { AvailableFiltersState } from '../globalState/AvailableFiltersState';\n\nexport interface IKioskFilterComponentProperty {\n viewType: string;\n culture: string;\n culturePrefix: string;\n navigationKey: string;\n seoSlug: string;\n categoryPath: string;\n filterModel: IFilterModel;\n userSelectedFilters: IServiceSelectedFilter[];\n l10n: IL10N;\n searchTerm: string;\n isMobile: boolean;\n isAlternative: boolean;\n portal: string;\n kiosk: IKioskInfo;\n availableFilterState: AvailableFiltersState;\n}\n\nexport interface IKioskFilterComponentState {\n userSelectedFilters: ISelectedFilter[];\n filterModel: IFilterModel;\n showLoadingSpinner: boolean;\n kioskFilterState: KioskFilterType;\n}\n\nexport enum KioskFilterType {\n None,\n WorkWearStoreOnly,\n OnlineShopOnly,\n}\n","import * as React from 'react';\nimport classNames from 'classnames';\nimport styles from './kiosk-filter.scss';\nimport { Handler } from '../globalState/globalState';\nimport {\n AvailableFiltersState,\n IFilterModelChangedEvent\n} from '../globalState/AvailableFiltersState';\nimport { IFilterSelectionChangedEvent, SelectedFiltersState } from '../globalState/SelectedFiltersState';\nimport { convertFromServiceToReactWorld } from '../globalState/ServiceSelectedFilterConverter';\nimport { IKioskFilterComponentProperty, IKioskFilterComponentState, KioskFilterType } from './kiosk-filter.d';\n\nexport default class KioskFilter extends React.Component<\n IKioskFilterComponentProperty, IKioskFilterComponentState> {\n private readonly availableFilterState: AvailableFiltersState;\n private readonly selectedFilterState: SelectedFiltersState;\n\n constructor(props: IKioskFilterComponentProperty) {\n super(props);\n this.state = {\n filterModel: props.filterModel,\n kioskFilterState: this.parseKioskType(this.props.kiosk.retailStoreFilterMode),\n userSelectedFilters: convertFromServiceToReactWorld(\n props.userSelectedFilters,\n props.filterModel),\n showLoadingSpinner: false,\n };\n\n this.availableFilterState = this.props.availableFilterState;\n this.selectedFilterState = this.availableFilterState.selectedFilterState;\n this.availableFilterState.registerOnStateChanged(this.onAvailableFiltersChanged.bind(this));\n this.selectedFilterState.registerOnStateChanged(this.onSelectedFiltersChanged.bind(this));\n this.toggleCheckboxChange = this.toggleCheckboxChange.bind(this);\n }\n\n public render() {\n\n const onlyOnlineString = {\n __html: this.props.l10n.kioskOnline + ' (' + this.state.filterModel.onlyOnlineCount + ')',\n };\n const onlyStoreString = {\n __html: this.props.l10n.kioskInStore + ' (' + this.state.filterModel.onlyStoreCount + ')',\n };\n return (\n \n );\n }\n\n private parseKioskType(kioskType: string): KioskFilterType {\n switch (kioskType) {\n case 'OnlyOnline':\n return KioskFilterType.OnlineShopOnly;\n case 'OnlyStore':\n return KioskFilterType.WorkWearStoreOnly;\n default:\n return KioskFilterType.None;\n }\n }\n\n private toggleCheckboxChange(event) {\n const newStats = this.parseKioskType(event.target.value);\n let newValueString = '';\n\n if (this.state.kioskFilterState === KioskFilterType.None) {\n this.setState({ kioskFilterState: newStats });\n newValueString = event.target.value;\n } else if (newStats === this.state.kioskFilterState)\n this.setState({ kioskFilterState: KioskFilterType.None });\n else\n console.error('lost state: kiosk type'); // eslint-disable-line no-console\n this.availableFilterState.selectedFilterState.changeKiosk(newValueString);\n }\n\n private onSelectedFiltersChanged: Handler = (event: IFilterSelectionChangedEvent) => {\n this.setState({ \n userSelectedFilters: event.selectedFilters,\n kioskFilterState: this.parseKioskType(event.retailStoreFilterMode)\n });\n }\n\n private onAvailableFiltersChanged: Handler = (event: IFilterModelChangedEvent) => {\n this.setState({filterModel: event.filterModel});\n }\n}\n","import * as React from 'react';\nimport styles from './content.scss';\nimport classnames from 'classnames';\nimport { IContentProps } from './content.d';\nimport { ContentType } from '../sidePanel/contentType';\nimport * as helper from '../../../Common/html-helper';\nimport FragmentPlaceholder from '../../../Common/fragment-placeholder/fragment-placeholder';\nimport FasSideContent from '../../fas-side-content/fas-side-content';\nimport SortSideComponent from '../../sort-side-content/sort-side-content';\nimport TabNavigationHelper from '../../../Common/tabNavigationHelper';\n\nexport default class Content extends React.Component {\n private tabNav: TabNavigationHelper;\n\n constructor(props) {\n super(props);\n this.tabNav = TabNavigationHelper.instance;\n }\n\n public render() {\n return (\n this.tabNav.setGroupAtt(el)}>\n {this.props.filterContentViewModel &&\n
this.tabNav.setGroupAtt(el)}\n className={classnames(styles.sort_content,\n {[styles.hidden]: !this.isSortContent()})}>\n\n \n
\n }\n {this.props.filterContentViewModel &&\n
\n }\n {\n this.props.shouldShowProductFinder && this.props.productFinderServicesUpAndRunning ?\n (\n
\n \n
\n ) : (\n
\n
{\n helper.decodeHTML(this.props.localizations.finderNotAvailableText)}\n
\n
\n )\n }\n
\n );\n }\n\n public isFilterContent(): boolean {\n return this.props.initialState === ContentType.FilterContent;\n }\n\n public isSortContent(): boolean {\n return this.props.initialState === ContentType.SortContent;\n }\n\n public showProductFinder(): string {\n if (this.props.initialState !== ContentType.FilterContent &&\n this.props.initialState !== ContentType.SortContent)\n return '';\n return styles.hidden;\n }\n}\n","export class IEsAppService {\n public navigationWheel: NavigationWheel;\n constructor() {\n this.navigationWheel = new NavigationWheel();\n }\n}\n\nclass NavigationWheel {\n private visible: boolean;\n constructor() {\n this.visible = true;\n }\n\n public isVisible() {\n return this.visible;\n }\n\n public show() {\n this.visible = true;\n document.dispatchEvent(new CustomEvent('esapp.navigationwheel', {\n detail: {\n visible: true,\n },\n }));\n }\n public hide() {\n this.visible = false;\n document.dispatchEvent(new CustomEvent('esapp.navigationwheel', {\n detail: {\n visible: false,\n },\n }));\n }\n}\n","import * as React from 'react';\nimport styles from './header.scss';\nimport classnames from 'classnames';\nimport FilterIcn2021 from '../../../Assets/svg/filter_2021';\nimport ProductFinderIcn2021 from '../../../Assets/svg/productfinder_2021';\nimport CrossIcn2021 from '../../../Assets/svg/cross_2021';\nimport SpinningArrow2021 from '../../../Assets/svg/spinning_arrow_2021';\nimport Sort2021 from '../../../Assets/svg/sort_2021';\nimport * as helper from '../../../Common/html-helper';\nimport { ContentType } from '../sidePanel/contentType';\nimport TabNavigationHelper from '../../../Common/tabNavigationHelper';\nimport { ArticleTileContext } from '../../globalState/articleTileContext';\n\nexport default class Header extends React.Component<{\n onStateChange; onClose; onReset; localizations; selectedTab: ContentType; isShow;\n shouldShowProductFinder; productFinderKey; isSortContent; shouldShowFilters;\n},\n { showResetButton: boolean }> {\n static contextType = ArticleTileContext;\n private filterButtonRef = React.createRef();\n private finderButtonRef = React.createRef();\n private heighlightRef = React.createRef();\n private inited = false;\n private tabNav: TabNavigationHelper;\n\n constructor(props) {\n super(props);\n this.tabNav = TabNavigationHelper.instance;\n this.state = { showResetButton: false };\n if (typeof window !== 'undefined') {\n window.addEventListener('handleResetButton', \n ((e: CustomEvent) => { this.setState({ showResetButton: e.detail }); }) as EventListener);\n window.addEventListener('resize', helper.throttle(this.forceUpdate.bind(this), 300));\n }\n\n this.handleCloseBtnClick = this.handleCloseBtnClick.bind(this);\n }\n\n componentDidMount(): void {\n if(this.filterButtonRef.current)\n this.tabNav.setFocusAtt(this.filterButtonRef.current);\n\n if(this.finderButtonRef.current)\n this.tabNav.setFocusAtt(this.finderButtonRef.current);\n }\n\n public componentDidUpdate() {\n if (this.props.isShow) {\n if (this.inited) \n this.updateSwitchAnimation();\n else {\n this.inited = true;\n window.setTimeout(() => { this.updateSwitchAnimation(); }, 180);\n // retrigger for slow browsers (IE11)\n window.setTimeout(() => { this.updateSwitchAnimation(); }, 500);\n }\n }\n else\n this.inited = false;\n }\n\n private handleCloseBtnClick(event): void {\n this.tabNav.handleKeyDown(event, this.context.mspOrigin);\n }\n\n public render() {\n return (\n this.tabNav.setGroupAtt(el)}>\n {this.renderProperHeader()}\n {this.props.selectedTab !== ContentType.FilterContent && (\n
this.tabNav.setFocusAtt(el)}>\n \n
\n )}\n
this.tabNav.setFocusAtt(el)}\n onKeyDown={this.handleCloseBtnClick}>\n \n
\n
\n );\n }\n\n private renderProperHeader() {\n if(this.props.isSortContent)\n return this.renderSortTitle();\n\n if (this.props.shouldShowFilters)\n return this.props.shouldShowProductFinder ? this.renderTabSwitch() : this.renderFilterTitle();\n\n return this.props.shouldShowProductFinder ? this.renderProductFinderOnly() : null;\n }\n\n private renderProductFinderOnly() {\n return (\n \n
\n
\n {this.props.productFinderKey === ContentType.JacketFinderContent &&\n helper.decodeHTML(this.props.localizations.jacketFinder)}\n {this.props.productFinderKey === ContentType.TrouserFinderContent &&\n helper.decodeHTML(this.props.localizations.productFinder)}\n {this.props.productFinderKey === ContentType.ShoeFinderContent &&\n helper.decodeHTML(this.props.localizations.shoeFinder)}\n
\n
\n );\n }\n\n private renderTabSwitch() {\n return (\n this.tabNav.setGroupAtt(el)}>\n
\n
\n \n {helper.decodeHTML(this.props.localizations.filter)}\n
\n
\n
\n {this.props.productFinderKey === ContentType.JacketFinderContent &&\n helper.decodeHTML(this.props.localizations.jacketFinder)}\n {this.props.productFinderKey === ContentType.TrouserFinderContent &&\n helper.decodeHTML(this.props.localizations.productFinder)}\n {this.props.productFinderKey === ContentType.ShoeFinderContent &&\n helper.decodeHTML(this.props.localizations.shoeFinder)}\n
\n
\n );\n }\n\n private renderFilterTitle() {\n return (\n \n
\n
\n {helper.decodeHTML(this.props.localizations.filter)}\n
\n
\n );\n }\n\n private renderSortTitle() {\n return (\n \n
\n
\n {helper.decodeHTML(this.props.localizations.sort)}\n
\n
\n );\n }\n\n private updateSwitchAnimation() {\n if (this.filterButtonRef.current && this.finderButtonRef.current) {\n const filterButtonWidth = this.filterButtonRef.current.offsetWidth;\n const finderButtonWidth = this.finderButtonRef.current.offsetWidth;\n if (this.props.selectedTab === ContentType.FilterContent) {\n this.heighlightRef.current.style.width = filterButtonWidth.toString() + 'px';\n this.heighlightRef.current.style.left = '0px';\n this.heighlightRef.current.style.right = finderButtonWidth.toString() + 'px';\n } else {\n this.heighlightRef.current.style.width = finderButtonWidth.toString() + 'px';\n this.heighlightRef.current.style.right = '0px';\n const finialValue = filterButtonWidth + 10;\n this.heighlightRef.current.style.left = finialValue.toString() + 'px';\n }\n }\n }\n}\n","import React, { Component } from 'react';\nimport SidePanel from './sidePanel/sidePanel';\nimport { IMainSidePanelProps } from './mainSidePanel.d';\nimport { ScrollLock } from '../../../Helper/scrollLock';\nimport { ContentType } from './sidePanel/contentType';\nimport { IEsAppService } from './esAppService';\nimport classnames from 'classnames';\nimport styles from './mainSidePanel.scss';\nimport { publish, subscribe, unsubscribe } from '../../Common/customEventHelper';\n\nexport default class MainSidePanel extends Component {\n private scrollLock: ScrollLock;\n private esAppService: IEsAppService = new IEsAppService();\n\n constructor(props) {\n super(props);\n this.state = { showSidebar: false, currentTab: ContentType.FilterContent };\n this.scrollLock = ScrollLock.instance;\n this.onCloseRequestedBySubComponent = this.onCloseRequestedBySubComponent.bind(this);\n this.onResetRequestedBySubComponent = this.onResetRequestedBySubComponent.bind(this);\n this.handleContentChange = this.handleContentChange.bind(this);\n this.bindEvent();\n }\n\n public render() {\n return (\n \n );\n }\n\n public componentDidMount() {\n if (typeof window !== 'undefined' && window.shell) {\n if (new URL(window.location.href).searchParams.get('trouserfinder') === 'open') {\n this.updatePageUrl('trouserfinder');\n setTimeout(() => { this.open(ContentType.TrouserFinderContent); }, 300); // give some time for PF to react\n }\n\n if (new URL(window.location.href).searchParams.get('jacketfinder') === 'open') {\n this.updatePageUrl('jacketfinder');\n setTimeout(() => { this.open(ContentType.JacketFinderContent); }, 300); // give some time for PF to react\n }\n\n if (new URL(window.location.href).searchParams.get('shoefinder') === 'open') {\n this.updatePageUrl('shoefinder');\n setTimeout(() => { this.open(ContentType.ShoeFinderContent); }, 300); // give some time for PF to react\n }\n }\n }\n\n public componentWillUnmount() {\n unsubscribe('ESPP.MainSidePanel.ShouldOpen', (payload: CustomEvent) => {\n this.open(payload.detail);\n });\n\n unsubscribe('ESPP.MainSidePanel.ShouldClose', () => {\n this.close();\n });\n }\n\n public handleContentChange(tab: ContentType) {\n let payload: string = null;\n\n switch (this.state.currentTab) {\n case ContentType.FilterContent: {\n payload = ContentType.FilterContent;\n break;\n }\n case ContentType.TrouserFinderContent: {\n payload = ContentType.TrouserFinderContent;\n break;\n }\n case ContentType.JacketFinderContent: {\n payload = ContentType.JacketFinderContent;\n break;\n }\n case ContentType.ShoeFinderContent: {\n payload = ContentType.ShoeFinderContent;\n break;\n }\n }\n\n publish('ESPP.MainSidePanel.TabSwitchOff', payload);\n\n this.setState({ currentTab: tab }, this.finishOpening.bind(this, tab));\n }\n\n public open(payload: ContentType) {\n switch (payload) {\n case ContentType.TrouserFinderContent: {\n this.setState({ showSidebar: true, currentTab: ContentType.TrouserFinderContent },\n this.finishOpening.bind(this, payload));\n break;\n }\n case ContentType.JacketFinderContent: {\n this.setState({ showSidebar: true, currentTab: ContentType.JacketFinderContent },\n this.finishOpening.bind(this, payload));\n break;\n }\n case ContentType.ShoeFinderContent: {\n this.setState({ showSidebar: true, currentTab: ContentType.ShoeFinderContent },\n this.finishOpening.bind(this, payload));\n break;\n }\n case ContentType.SortContent: {\n this.setState({ showSidebar: true, currentTab: ContentType.SortContent },\n this.finishOpening.bind(this, payload));\n break;\n }\n default: {\n this.setState({ showSidebar: true, currentTab: ContentType.FilterContent },\n this.finishOpening.bind(this, payload));\n break;\n }\n }\n\n if(window.shell.tabNav) {\n if (document.getElementsByClassName('fas_msp_close_button').length > 0) {\n const mspCloseBtn = document.querySelector('.fas_msp_close_button')\n window.shell.tabNav.focus(mspCloseBtn);\n }\n }\n }\n\n private onCloseRequestedBySubComponent() {\n if (document.getElementsByClassName('fas_tooltip_overlay').length > 0)\n document.getElementsByClassName('fas_tooltip_overlay')[0].classList.add('hide');\n\n this.close();\n }\n\n private onResetRequestedBySubComponent() {\n this.reset();\n }\n\n private bindEvent() {\n if (typeof window !== 'undefined' && typeof window.shell !== 'undefined') {\n window.esApp = this.esAppService;\n\n // for breadcrumb, because it is not in ATS scope\n window.shell.subscribeTo('ESPP.MainSidePanel.ShouldOpen', (payload) => {\n this.open(payload);\n }, 'ESPP.MainSidePanel.ShouldOpen');\n }\n\n subscribe('ESPP.MainSidePanel.ShouldOpen', (payload: CustomEvent) => {\n this.open(payload.detail);\n });\n \n subscribe('ESPP.MainSidePanel.ShouldClose', () => {\n this.close();\n });\n }\n\n private close() {\n if (typeof window !== 'undefined' && typeof window.shell !== 'undefined')\n window.shell.publishTo('ESPP.ProductFinder.LogCancelStep', this.state.currentTab);\n\n const sidebar = document.getElementsByClassName(styles.side_panel_app)[0];\n sidebar.classList.add(styles.slideout);\n this.esAppService.navigationWheel.show();\n this.scrollLock.unlock();\n setTimeout(() => {\n sidebar.classList.remove(styles.slideout);\n this.setState({ showSidebar: false }, () => {\n publish('ESPP.MainSidePanel.Closed', null);\n });\n }, 300);\n }\n\n private reset() {\n if (typeof window !== 'undefined' && typeof window.shell !== 'undefined')\n window.shell.publishTo('ProductFinder.Reset', this.props.productFinderKey);\n }\n\n private finishOpening(payload: ContentType) {\n // ping finder to prepare for render:\n window.shell.publishTo('ProductFinder.Open', payload);\n\n if (typeof window !== 'undefined' && typeof window.shell !== 'undefined') {\n publish('ESPP.MainSidePanel.Opened', null);\n\n this.esAppService.navigationWheel.hide();\n if (!this.scrollLock.getLockState)\n this.scrollLock.lock();\n }\n }\n\n private updatePageUrl(scopeUrlParam: string): void {\n // This method removes trouserFinder paramter from url without page reload\n // It happens when customer comes to trousers category, \n // and side bar opens automatically without any additional actions\n // Such a link is placed in a mobile main menu. Or also can be placed in any newsLetter\n\n // https://www-preview-dev.engelbert-strauss.de/en/trousers/?trouserfinder=open\n let currentUrl: string = window.location.href;\n\n // if tf is the only one parameter:\n currentUrl = currentUrl.replace('?' + scopeUrlParam + '=open', '');\n\n // if tf is combined with other parameters:\n currentUrl = currentUrl.replace('&' + scopeUrlParam + '=open', '');\n\n window.history.replaceState(null, '', currentUrl);\n }\n}\n","export enum ContentType {\n FilterContent = 'Filter', // = 0,\n TrouserFinderContent = 'ESPP1-TrouserFinderWeb',\n JacketFinderContent = 'ESPP1-JacketFinderWeb',\n ShoeFinderContent = 'ESPP1-ShoeFinderWeb',\n SortContent = 'Sort',\n}\n","import * as React from 'react';\nimport styles from './sidePanel.scss';\nimport { ContentType } from './contentType';\nimport Header from '../header/header';\nimport Content from '../content/content';\nimport { IFasBarData } from '../../fas-bar/fas-bar.d';\nimport classNames from 'classnames';\nimport { GlobalState } from '../../globalState/globalState';\nimport {IL10N} from '../../../Common/l10n-keys';\nimport TabNavigationHelper from '../../../Common/tabNavigationHelper';\n\nexport default class SidePanel extends React.Component<{onClose, onReset, \n filterContentViewModel: IFasBarData, currentTab: ContentType, \n onTabChange, isShow, shouldShowProductFinder, productFinderServicesUpAndRunning, globalState: GlobalState,\n l10n: IL10N, productFinderKey: string\n}> {\n private tabNav: TabNavigationHelper;\n\n constructor(props) {\n super(props);\n this.tabNav = TabNavigationHelper.instance;\n this.state = { currentState: ContentType.FilterContent };\n }\n\n public handleContentChange = (prop) => {\n this.props.onTabChange(prop.target.dataset.content as ContentType);\n }\n\n public render() {\n return (\n <>\n
\n \n
{this.tabNav.setGroupAtt(el); this.tabNav.setModalAtt(el);}}>\n
\n
\n >\n\n );\n }\n}\n","import React, { Component } from 'react';\nimport FragmentPlaceholder from '../../Common/fragment-placeholder/fragment-placeholder';\nimport styles from './newsLetterPromotion.scss';\nimport { INewsLetterPromotionProps } from './newsLetterPromotion.d';\nimport classNames from 'classnames';\n\nconst defaultProps: INewsLetterPromotionProps = {\n isVisible: false\n};\n\nclass NewsLetter extends Component {\n\n static defaultProps = defaultProps;\n\n public render() {\n const { forwardRef, isVisible } = this.props;\n\n return (\n \n \n
\n );\n }\n}\nconst NewsLetterPromotion = React.forwardRef((props, ref) => {\n return \n});\n\nexport default NewsLetterPromotion;\n","import React from 'react';\r\nimport styles from './noResult.scss';\r\nimport * as helper from '../../Common/html-helper';\r\nimport classNames from 'classnames';\r\nimport FilterIcn2021 from '../../Assets/svg/filter_2021';\r\nimport {IL10N} from '../../Common/l10n-keys';\r\nimport { GlobalState } from '../globalState/globalState';\r\nimport { SortContext } from '../globalState/sortContextProvider';\r\nimport { ISortContext } from '../globalState/sortContextProvider.d';\r\nimport { ViewType } from '../../Common/enums';\r\nimport { publish } from '../../Common/customEventHelper';\r\n\r\n\r\nfunction NoResult({ l10n, globalState }:\r\n { l10n: IL10N; viewType: ViewType; isMobile: boolean; globalState: GlobalState }) {\r\n const sortContext = React.useContext(SortContext) as ISortContext;\r\n\r\n return (\r\n \r\n
\r\n
\r\n
{\r\n sortContext.removeAllGloveSortValues();\r\n globalState.removeAllFilters();\r\n }}>{helper.decodeHTML(l10n.clearAllFilters)}
\r\n
{\r\n publish('ESPP.MainSidePanel.ShouldOpen', 'Filter');\r\n }}>\r\n \r\n {helper.decodeHTML(l10n.editFilter)}\r\n
\r\n
\r\n
\r\n );\r\n}\r\n\r\nexport default NoResult;\r\n","import classNames from 'classnames';\nimport React, { Component } from 'react';\nimport { IL10N } from '../../Common/l10n-keys';\nimport ProgressBar from '../progressBar/progressBar';\nimport styles from './paging.scss';\nimport * as helper from '../../Common/html-helper';\n\nexport default class Paging extends Component<\n { viewedArticleNumber: number, \n totalArticleCount: number, \n waitingForResponse: boolean, \n onLoadMore,\n l10n: IL10N,\n totalPagesNumber: number,\n currentPageNumber: number,\n showHiddenLinks: boolean,\n url?: string\n }, \n { mounted: boolean }> {\n\n constructor(props) {\n super(props);\n\n this.state =\n {\n mounted: false\n };\n\n this.handleLoadMoreClick = this.handleLoadMoreClick.bind(this);\n }\n\n componentDidMount(): void {\n this.setState({mounted: true})\n }\n\n private handleLoadMoreClick() {\n this.props.onLoadMore();\n }\n\n private hiddenLink() {\n const params = (new URL(this.props.url));\n const urlStart = params.protocol + '//' + params.host + params.pathname;\n const listItem = [];\n for (let i = 2; i <= this.props.totalPagesNumber; i++) {\n listItem.push(< a href={`${urlStart}?page=${i}`} key={i}>);\n }\n return listItem;\n }\n\n public render() {\n //TODO find a SSR safe way to do this\n let pagingString = this.props.totalArticleCount === 1 ? this.props.l10n.lookedArticle :\n this.props.l10n.lookedArticles;\n pagingString = pagingString.replace('{0}', (this.props.viewedArticleNumber).toString());\n pagingString = pagingString.replace('{1}', this.props.totalArticleCount.toString());\n return (\n \n
\n {pagingString}\n
\n {this.props.totalArticleCount > 0 && \n
\n }\n {this.props.currentPageNumber < this.props.totalPagesNumber &&\n
\n \n {helper.decodeHTML(this.props.l10n.viewMoreProducts)}\n \n \n }\n {this.props.url && this.props.showHiddenLinks &&\n
\n {this.hiddenLink()}\n
\n }\n
\n );\n }\n}\n","import * as React from 'react';\nimport classNames from 'classnames';\nimport styles from './progressBar.scss';\nimport { IProgressBar } from './progressBar.d';\n\nexport default class ProgressBar extends React.Component {\n\n constructor(props: IProgressBar) {\n super(props);\n }\n\n public render() {\n const value = Math.min(1, this.props.currentValue / this.props.maxValue) * 100;\n return (\n \n );\n }\n}\n","import * as React from 'react';\nimport styles from './progressBarAnimation.scss';\nimport { IProgressBarAnimationProps, IProgressBarAnimationState } from './progressBarAnimation.d';\nimport * as helper from '../../Common/html-helper';\n\nexport default class ProgressBarAnimation extends React.Component {\n constructor(props: IProgressBarAnimationProps) {\n super(props);\n\n this.state = {\n width: 0,\n }\n\n this.handleScroll = this.handleScroll.bind(this);\n this.getShouldDoInfiniteScroll = this.getShouldDoInfiniteScroll.bind(this);\n }\n\n componentDidMount(): void {\n document.addEventListener('scroll', this.handleScroll);\n\n if(this.props.totalResultCount > 0 && this.props.totalResultCount < 5) {\n this.setState({\n width: 100\n });\n }\n }\n\n getDocScrollRatio(){\n const domElement = document.documentElement;\n const ratio = domElement.scrollTop / (domElement.scrollHeight - domElement.clientHeight);\n return ratio;\n }\n\n getShouldDoInfiniteScroll() {\n const hasPreviousPage = this.props.pageEndOffset - this.props.salesDesignationViews.length > 0;\n const isAtTop = this.getDocScrollRatio() < .1;\n\n return hasPreviousPage && isAtTop;\n }\n\n componentWillUnmount(): void {\n if (typeof document !== 'undefined') {\n document.removeEventListener('scroll', this.handleScroll);\n }\n }\n\n componentDidUpdate(prevProps: Readonly) {\n if (prevProps.scrollPosition !== this.props.scrollPosition) {\n this.setState({ width: this.props.scrollPosition });\n }\n }\n\n private handleScroll(): void {\n const setter = newScrollPosition => this.setState({\n width: newScrollPosition\n });\n\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n const infiniteScrollFunc = this.getShouldDoInfiniteScroll() ? this.props.infiniteScroll : () => {};\n helper.onScroll(this.props.pageEndOffset, this.props.salesDesignationViews, setter, infiniteScrollFunc);\n }\n\n public render() {\n const barWidth = this.state.width > 100 ? 100 : this.state.width;\n\n return (\n \n );\n }\n}\n","export class SearchTelemetryAjaxCaller {\n\n private static blockTelemetryEvent: boolean;\n private static headersOfTelemetryRequest = {\n type: 'application/json'\n };\n\n public static postData(data: SearchTelemetryData) {\n \n if (SearchTelemetryAjaxCaller.blockTelemetryEvent) return;\n\n SearchTelemetryAjaxCaller.blockTelemetryEvent = true;\n setTimeout(() => (this.blockTelemetryEvent = false), 1000);\n let route = '';\n switch (data.eventType) {\n case SearchTelemetryEventType.HymUsed:\n route = 'api/shopsearch-web/telemetry/hym/events';\n break;\n\n case SearchTelemetryEventType.SearchUsed:\n route = 'api/shopsearch-web/telemetry/search/events';\n break;\n\n default:\n throw new RangeError(\n `Event type ${data.eventType} is not valid.`\n );\n }\n const apiUrl = data.magicLink + route;\n\n const requestUrl = new URL(apiUrl);\n \n if (navigator.sendBeacon) {\n const payload = new Blob(\n [JSON.stringify(data)],\n SearchTelemetryAjaxCaller.headersOfTelemetryRequest\n );\n navigator.sendBeacon(requestUrl, payload);\n return;\n }\n\n setTimeout(() => {\n fetch(requestUrl, {\n body: JSON.stringify(data),\n headers: {\n 'Content-Type': 'application/json'\n },\n method: 'POST'\n });\n }, 0);\n }\n}\n\nexport interface ISearchTelemetryData {\n readonly eventType: SearchTelemetryEventType;\n term: string;\n}\n\nexport enum SearchTelemetryEventType {\n Unknown,\n HymUsed,\n SearchUsed\n}\n\nexport class SearchTelemetryData implements ISearchTelemetryData\n{\n public term: string;\n public magicLink: string;\n private origin: string;\n private contentType: string;\n private salesArticleNo: string;\n private position: number;\n private pageViewLogDataId: string;\n private portal: string;\n private culture: string;\n public eventTypeField: SearchTelemetryEventType =\n SearchTelemetryEventType.Unknown;\n\n get eventType(): SearchTelemetryEventType {\n return this.eventTypeField;\n }\n\n constructor(\n stm: string,\n pageViewLogDataId: string,\n portal: string,\n culture: string,\n magicLink: string\n ) {\n this.pageViewLogDataId = pageViewLogDataId;\n this.portal = portal;\n this.culture = culture;\n this.setData(stm);\n this.magicLink = magicLink;\n }\n\n\n private setData(stm: string) {\n const keyValue: string[] = stm.split(';');\n keyValue.forEach((element: string) => {\n const split: string[] = element.split('=');\n const key: string = split[0];\n const value: string = split[1];\n\n switch (key) {\n case 'Term':\n // term can contain semicolon which is escaped via some similar unicode char\n this.term = value.replace('ï¹”', ';');\n break;\n case 'Origin':\n this.origin = value;\n break;\n case 'ContentType':\n this.contentType = value;\n break;\n case 'Position':\n this.position = parseInt(value, 10);\n break;\n case 'SalesArticleNo':\n this.salesArticleNo = value;\n break;\n default:\n // ignore new keys\n break;\n }\n });\n\n if (this.salesArticleNo)\n this.eventTypeField = SearchTelemetryEventType.SearchUsed;\n else if (this.origin)\n this.eventTypeField = SearchTelemetryEventType.HymUsed;\n }\n}\n\nexport class PageViewLogDataId\n{\n private static instance: PageViewLogDataId;\n\n private pageViewLogDataId: string;\n\n constructor() {\n if (PageViewLogDataId.instance)\n throw new Error(\n `Instantiation of GetPageViewLogDataId failed:\n Use GetPageViewLogDataId.getInstance()\n instead of new GetPageViewLogDataId()`\n );\n }\n\n public static getInstance(): PageViewLogDataId {\n if (!PageViewLogDataId.instance)\n PageViewLogDataId.instance = new PageViewLogDataId();\n\n return PageViewLogDataId.instance;\n }\n\n public get(): string {\n if (!this.pageViewLogDataId) this.set();\n\n return this.pageViewLogDataId;\n }\n\n private set(): void {\n this.pageViewLogDataId =\n document.documentElement.dataset.pageViewLogDataId;\n }\n}\n","import React, { Component } from 'react';\nimport * as helper from '../../../Common/html-helper';\nimport FragmentPlaceholder from '../../../Common/fragment-placeholder/fragment-placeholder';\nimport { IL10N } from '../../../Common/l10n-keys';\nimport { ILanguageSwitcherFragmentProps } from '../searchresultpage.d';\nimport styles from './language-switcher.scss';\nimport classNames from 'classnames';\nimport { PageViewLogDataId, SearchTelemetryAjaxCaller, SearchTelemetryData } from '../SearchTelemetryAjaxCaller';\n\nexport default class LanguageSwitcher extends Component<{\n languageSwitcherProps: ILanguageSwitcherFragmentProps, \n l10n: IL10N\n magicLink: string\n}, {}> {\n switcherFragmentRef: React.RefObject;\n \n constructor(props) {\n super(props);\n this.switcherFragmentRef = React.createRef();\n this.onLanguageSwitchClicked.bind(this);\n }\n\n componentDidMount(): void {\n const buttons = this.switcherFragmentRef.current.getElementsByClassName('mkt-switcher-button');\n for (let index = 0; index < buttons.length; index++) {\n const button = buttons[index];\n button.addEventListener('click', this.onLanguageSwitchClicked.bind(this));\n }\n }\n\n private onLanguageSwitchClicked(ev): void {\n const stm = this.props.languageSwitcherProps.telemetryDataViewModel.stm;\n if (!stm) return;\n const data = new SearchTelemetryData(\n stm,\n PageViewLogDataId.getInstance().get(), \n this.props.languageSwitcherProps.portal, \n this.props.languageSwitcherProps.culture,\n this.props.magicLink);\n const switchedToCulture = ev.currentTarget.dataset.culture;\n data.term = switchedToCulture;\n SearchTelemetryAjaxCaller.postData(data);\n }\n\n public render() {\n return (\n \n
\n {helper.decodeHTML(this.props.l10n.searchInOtherLanguage)}\n
\n\n {this.props.languageSwitcherProps.fragment != null && (\n
\n )}\n
\n );\n }\n}","import React, { Component } from 'react';\nimport * as helper from '../../../Common/html-helper';\nimport FragmentPlaceholder from '../../../Common/fragment-placeholder/fragment-placeholder';\nimport { IL10N } from '../../../Common/l10n-keys';\nimport styles from './searchbox.scss';\nimport classNames from 'classnames';\n\nexport default class BottomSearchbox extends Component<{style: string, l10n: IL10N}, {}> {\n \n constructor(props) {\n super(props);\n }\n\n public render() {\n return (\n \n
\n
\n {helper.decodeHTML(this.props.l10n.stillDidntFindTheRightOne)}\n
\n\n
\n
\n )\n }\n}","import React, { Component } from 'react';\nimport FragmentPlaceholder from '../../../Common/fragment-placeholder/fragment-placeholder';\nimport { ISearchInsteadBoxProps, ISearchInsteadBoxState, ISoldOutArticle } from '../searchresultpage.d';\nimport styles from './searchbox.scss';\nimport classNames from 'classnames';\n\nexport default class TopSearchbox extends Component {\n constructor(props: ISearchInsteadBoxProps) {\n super(props);\n this.state = {\n showTopSearchbox: this.props.topPageSearchBoxFragment != null || \n this.props.htmlEncodedAppropriateResultNotFoundText != '' || \n this.props.soldOutArticles?.length > 0,\n showSingleAltHeadline: this.props.htmlEncodedAppropriateResultNotFoundText != '' || \n this.props.htmlEncodedSearchInsteadText != '',\n };\n }\n\n public render() {\n return (\n <>\n {this.props.style === 'single-alternative-search-result' ?\n this.state.showSingleAltHeadline && (\n \n {this.getTextElement(this.getHtmlEncodedText())}\n
\n )\n : <>\n {this.state.showTopSearchbox && (\n \n {this.props.htmlEncodedAppropriateResultNotFoundText && (\n this.getTextElement(this.props.htmlEncodedAppropriateResultNotFoundText)\n )}\n\n {this.props.soldOutArticles?.length > 0 && (\n
\n \n {this.props.l10n.alreadySoldOut}\n \n {this.props.soldOutArticles.map((article, index) => {\n return (\n \n \n );\n })}\n
\n )}\n\n {this.props.topPageSearchBoxFragment != null && (\n
\n )}\n
\n )}\n\n {this.props.htmlEncodedSearchInsteadText && (\n \n {this.getTextElement(this.props.htmlEncodedSearchInsteadText)}\n
\n )}\n >\n }\n >\n );\n }\n\n private getHtmlEncodedText(): string {\n let htmlEncodedText = '';\n if (this.props.htmlEncodedAppropriateResultNotFoundText) {\n htmlEncodedText = this.props.htmlEncodedAppropriateResultNotFoundText;\n }\n if (this.props.htmlEncodedSearchInsteadText) {\n htmlEncodedText += ' ' + this.props.htmlEncodedSearchInsteadText;\n }\n return htmlEncodedText;\n }\n\n private getTextElement(htmlEncodedText: string) {\n return (\n
\n );\n }\n\n private getSoldOutArticleInfo(article: ISoldOutArticle): string {\n const articleInfo = \n this.props.l10n.startQuotationMark\n + article.articleName\n + this.props.l10n.endQuotationMark\n + ' - '\n + this.props.l10n.catalogNoLesserAcronym\n + this.props.l10n.colon\n + ' '\n + article.salesArticleNo;\n return articleInfo;\n }\n}","import { IL10N } from '../../Common/l10n-keys';\nimport {\n ISalesDesignationView,\n ICurrencyInformation,\n ITelemetryData\n} from '../articleTileGrid/article-tile/article-tile.d';\nimport { IKioskInfo } from '../categorypage/categorypage.d';\nimport { IFasBarData } from '../fas-bar/fas-bar.d';\nimport { GlobalState } from '../globalState/globalState';\nimport { ArticleTilesAjaxCaller } from '../articleTileGrid/ArticleTilesAjaxCaller';\n\nexport interface ISearchResultPageProps {\n l10n: IL10N;\n searchResultPageProps: ISearchResultPage;\n}\n\nexport interface ISearchResultPageState {\n salesDesignationViews: ISalesDesignationView[];\n pageNo: number;\n totalResultCount: number;\n pageEndOffset: number;\n totalPageCount: number;\n waitingForResponse: boolean;\n isBreadcrumbOnly: boolean;\n componentMounted: boolean;\n scrollPosition: number;\n activeDropDownFilterName: string;\n stickyHeaderDropDownFilterName: string;\n isStickyHeaderVisible: boolean;\n toggleToUpdateFasBar: boolean;\n isTeaserViewResultPage: boolean;\n isEmptySearchResultPage: boolean;\n infiniteScrollUpMode: boolean;\n infiniteScrollUpMinPageNo: number;\n isSearchBottomReached: boolean;\n}\n\nexport interface ISearchResultPage {\n articleLists: IArticleList[];\n bottomPageSearchBoxFragment: IFragmentPlaceholder;\n bottomSuggestionLinks: ISuggestionLinks;\n currencyInformation?: ICurrencyInformation;\n filterContentViewModel: IFasBarData;\n languageSwitcherFragment: ILanguageSwitcherFragmentProps;\n originalRootPath: string;\n originalSearchTerm: string;\n pagingInformation: IPagingInformation;\n style: string;\n topPageSearchBoxFragment: ISearchInsteadBoxProps;\n topSuggestionLinks: ISuggestionLinks;\n type: SearchResultPageType;\n url: string;\n magicLink: string;\n}\n\nexport interface IArticleList {\n alternativeTarget: string;\n articles: ISalesDesignationView[];\n filterAndSortBar: IFilterAndSortForSearch;\n isAlternative: boolean;\n paging: IPagingInformation;\n searchString: string;\n style: string;\n telemetryDataViewModel: ITelemetryData;\n totalResultCount: number;\n types: string[];\n}\n\nexport interface ITeaserSuggestionsProps {\n articleLists: IArticleList[];\n currencyInformation?: ICurrencyInformation;\n kiosk: IKioskInfo;\n l10n: IL10N;\n isMobile: boolean;\n portal: string;\n culture: string;\n isGlobal: boolean;\n globalState: GlobalState;\n articleTilesAjaxCaller: ArticleTilesAjaxCaller;\n magicLink: string;\n}\n\nexport interface ITeaserSuggestionsState {\n salesDesignationViewList: ISalesDesignationView[][];\n isDesktop: boolean;\n}\n\nexport interface ISearchInsteadBoxProps {\n htmlEncodedAppropriateResultNotFoundText: string;\n htmlEncodedSearchInsteadText: string;\n originalSearchTerm: string;\n soldOutArticles: ISoldOutArticle[];\n style: string;\n topPageSearchBoxFragment: IFragmentPlaceholder;\n l10n: IL10N;\n}\n\nexport interface ISearchInsteadBoxState {\n showTopSearchbox: boolean;\n showSingleAltHeadline: boolean;\n}\n\nexport interface ISuggestionLinks {\n labelLocalizationKey: string;\n style: string;\n suggestionLinks: ISuggestionLink[];\n}\n\nexport interface ISuggestionLinksState {\n isScrollable: boolean;\n showLeftChevron: boolean;\n showRightChevron: boolean;\n isDesktop: boolean;\n}\n\nexport interface ISuggestionLink {\n searchTerm: string;\n portal: string;\n culture: string;\n target: string;\n telemetryDataViewModel: ITelemetryData;\n}\n\nexport interface ILanguageSwitcherFragmentProps {\n culture: string;\n portal: string;\n fragment: IFragmentPlaceholder;\n telemetryDataViewModel: ITelemetryData;\n style: string;\n magicLink: string;\n}\n\nexport interface ISoldOutArticle {\n articleName: string;\n salesArticleNo: string;\n}\n\nexport interface IFilterAndSortForSearch {\n breadcrumbViewModel: IFragmentPlaceholder;\n filterAndSortViewModel: IFragmentPlaceholder;\n mainSidePanelViewModel: IFragmentPlaceholder;\n}\n\nexport interface IFragmentPlaceholder {\n dClass: string;\n fallbackSource: string;\n features: string;\n ignoreErrors?: boolean;\n source: string;\n timeout?: number;\n}\n\nexport interface IPagingInformation {\n pageNumber: number;\n totalPageCount: number;\n totalResultCount: number;\n pageEndOffset: number;\n}\n\nexport enum SearchResultPageType {\n Empty,\n Regular,\n FullAlternativeView,\n TeaserAlternativeView,\n}\n\nexport enum OriginType {\n LanguageSwitcher,\n RelatedTerm,\n TypoCorrection,\n UmbrellaTerm,\n}\n\nexport enum ContentTypes {\n ArticleTile,\n LanguageSwitcher,\n ShowMoreArticles,\n SuggestionPillBottom,\n SuggestionPillTop,\n}\n\nexport function ArticleListsToSdvListMapper(\n lists: IArticleList[]\n): ISalesDesignationView[][] {\n return lists.map(x=>x.articles);\n}\n\nexport function SearchResultPagePropsToSdvMapper(\n props: ISearchResultPageProps\n): ISalesDesignationView[] {\n const salesDesignationViews: ISalesDesignationView[] = [];\n\n props.searchResultPageProps.articleLists.map((list) => {\n list.articles.map((article) => salesDesignationViews.push(article))\n });\n\n return salesDesignationViews;\n}\n\nexport interface IMetaEventForSearchResult {\n search_string: string;\n content_ids: string[];\n}\n","/* eslint-disable max-len */\nimport React, { Component } from 'react';\nimport ArticleTileGrid from '../articleTileGrid/article-tile-grid';\nimport classNames from 'classnames';\nimport BottomSearchbox from './searchbox/bottom-searchbox';\nimport FasBar from '../fas-bar/fas-bar';\nimport LanguageSwitcher from './language-switcher/language-switcher';\nimport SuggestionLinks from './suggestion-links/suggestion-links';\nimport TeaserSuggestions from './teaser-suggestions/teaser-suggestions';\nimport TopSearchbox from './searchbox/top-searchbox';\nimport '../../Common/customStyles.scss';\nimport styles from './searchresultpage.scss';\nimport StickyHeader from '../stickyHeader/stickyHeader';\nimport Paging from '../paging/paging';\nimport { ArticleTilesAjaxCaller } from '../articleTileGrid/ArticleTilesAjaxCaller';\nimport { IFilteringResult } from '../articleTileGrid/article-tile/article-tile.d';\nimport {\n IMetaEventForSearchResult,\n ISearchResultPageProps,\n ISearchResultPageState,\n SearchResultPagePropsToSdvMapper,\n SearchResultPageType,\n} from './searchresultpage.d';\nimport { GlobalState } from '../globalState/globalState';\nimport { ArticleListController } from '../globalState/ArticleListController';\nimport { convertFromServiceToReactWorld } from '../globalState/ServiceSelectedFilterConverter';\nimport MainSidePanel from '../mainSidePanel/mainSidePanel';\nimport * as helper from '../../Common/html-helper';\nimport ProgressBarAnimation from '../progressBarAnimation/progressBarAnimation';\nimport TouchPointFactory from '../../Common/MPCTouchPointFactory';\nimport { BookmarkController } from '../globalState/BookmarkController';\nimport { ScrollLock } from '../../../Helper/scrollLock';\nimport SortContextProvider from '../globalState/sortContextProvider';\nimport * as Constants from '../../Common/constants';\nimport * as MetaEvents from './metaEventForSearch.js'\nimport ArticleTileContextProvider from '../globalState/articleTileContext';\nimport MultipleContextProvider from '../globalState/MultipleContext';\nimport { subscribe } from '../../Common/customEventHelper';\n\nexport default class SearchResultPage extends Component {\n private articleTilesAjaxCaller: ArticleTilesAjaxCaller;\n private gridRef = React.createRef();\n private touchPointFactory: TouchPointFactory;\n private globalState: GlobalState;\n private bookmarkController: BookmarkController;\n infinityScrollingIsLoading: boolean;\n private searchRef = React.createRef();\n private historyTile;\n\n constructor(props: ISearchResultPageProps) {\n super(props);\n this.state = {\n salesDesignationViews: SearchResultPagePropsToSdvMapper(this.props),\n pageNo: this.props.searchResultPageProps.pagingInformation?.pageNumber ?? 1,\n waitingForResponse: false,\n totalResultCount: this.props.searchResultPageProps.pagingInformation?.totalResultCount ?? 0,\n totalPageCount: this.props.searchResultPageProps.pagingInformation?.totalPageCount ?? 1,\n isBreadcrumbOnly: false,\n componentMounted: false,\n scrollPosition: 0,\n activeDropDownFilterName: '',\n stickyHeaderDropDownFilterName: '',\n isStickyHeaderVisible: false,\n toggleToUpdateFasBar: false,\n isTeaserViewResultPage: this.props.searchResultPageProps.articleLists?.length >= 1 && \n this.props.searchResultPageProps.type === SearchResultPageType.TeaserAlternativeView,\n isEmptySearchResultPage: this.props.searchResultPageProps.type === SearchResultPageType.Empty,\n infiniteScrollUpMode: helper.isUrlWithPageQuery(this.props.searchResultPageProps.url),\n pageEndOffset: this.props.searchResultPageProps.pagingInformation?.pageEndOffset ?? 0,\n infiniteScrollUpMinPageNo: this.props.searchResultPageProps.pagingInformation?.pageNumber ?? 1,\n isSearchBottomReached: false\n };\n\n // init global state\n const initSelectedFilters = convertFromServiceToReactWorld(\n props.searchResultPageProps.filterContentViewModel.userSelectedFilters,\n props.searchResultPageProps.filterContentViewModel.filterModel);\n this.globalState = new GlobalState(\n this.props.searchResultPageProps.filterContentViewModel.filterModel,\n initSelectedFilters,\n this.props.searchResultPageProps.filterContentViewModel.seoSlug,\n this.props.searchResultPageProps.filterContentViewModel.searchTerm,\n this.props.searchResultPageProps.filterContentViewModel.kiosk,\n this.props.searchResultPageProps.magicLink);\n this.articleTilesAjaxCaller = new ArticleTilesAjaxCaller(this.globalState.Spinner, this.props.searchResultPageProps.magicLink);\n const articleListController = new ArticleListController(\n this.articleTilesAjaxCaller, this.globalState.onNewArticlesLoaded);\n this.globalState.articleListController = articleListController;\n this.onNewArticles = this.onNewArticles.bind(this);\n this.globalState.registerOnArticlesChanged(this.onNewArticles);\n\n const salesDesignationViews = SearchResultPagePropsToSdvMapper(this.props);\n if (salesDesignationViews.length > 0)\n this.bookmarkController = new BookmarkController(salesDesignationViews, this.globalState);\n\n this.touchPointFactory = TouchPointFactory.instance;\n this.props.searchResultPageProps.filterContentViewModel.l10n = this.props.l10n;\n this.props.searchResultPageProps.filterContentViewModel.originalSearchTerm =\n this.props.searchResultPageProps.originalSearchTerm;\n if (this.props.searchResultPageProps.type === SearchResultPageType.FullAlternativeView) {\n this.props.searchResultPageProps.filterContentViewModel.searchTerm =\n this.props.searchResultPageProps.articleLists[0].searchString;\n }\n\n this.handleLoadArticles = this.handleLoadArticles.bind(this);\n this.stickyHeaderVisibleChange = this.stickyHeaderVisibleChange.bind(this);\n this.handleActiveDropDownFilterChange = this.handleActiveDropDownFilterChange.bind(this);\n this.handleSortContextChange = this.handleSortContextChange.bind(this);\n this.infiniteScroll = this.infiniteScroll.bind(this);\n this.createTouchpoints = this.createTouchpoints.bind(this);\n\n if (typeof document !== 'undefined') {\n const firstTileFocuslistener = () => {\n if (this.gridRef.current) {\n const firstChild = this.gridRef.current.firstChild as Element;\n if (firstChild)\n window.shell.tabNav.focus(firstChild);\n }\n }\n document.addEventListener('DOMContentLoaded',\n () => {\n this.createTouchpoints();\n firstTileFocuslistener();\n\n if (window && window.location) {\n if (this.historyTile) {\n this.historyTile.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });\n\n this.historyTile = undefined;\n }\n }\n });\n\n subscribe('ESPP.MainSidePanel.Closed', firstTileFocuslistener);\n }\n }\n\n private createTouchpoints(): void{\n // first iteration for mpc tp on after page is loaded\n this.touchPointFactory.createTouchPointsBatch(this.state.salesDesignationViews);\n }\n\n componentDidMount(): void {\n this.setState({ isBreadcrumbOnly: false, componentMounted: true });\n\n if(window && window.location && window.location.hash.startsWith('#ats-')) {\n this.historyTile = window.document.body.querySelector(window.location.hash);\n\n //remove hash\n history.replaceState(null, null, window.location.pathname + window.location.search);\n }\n\n if(window && window.location && window.location.search.indexOf('page=') >= 0) {\n const params = new URL(window.location.href).searchParams;\n params.delete('page');\n let newUrl = window.location.pathname;\n if (params.size > 0) {\n newUrl += '?' + params.toString();\n }\n history.replaceState(null, null, newUrl);\n }\n this.handleMetaEvent();\n }\n\n private infiniteScroll() {\n if(\n this.state.infiniteScrollUpMode && // we have returned from somwhere to ATS\n this.state.infiniteScrollUpMinPageNo > 1 && // there are missing pages still\n !this.infinityScrollingIsLoading // we dont currently wait for an older load\n ) {\n const lastScrollHeight = window.document.body.scrollHeight;\n this.infinityScrollingIsLoading = true;\n this.articleTilesAjaxCaller.loadMoreArticles(\n this.state.infiniteScrollUpMinPageNo - 1, \n this.props.searchResultPageProps.filterContentViewModel.kiosk, \n this.props.searchResultPageProps.filterContentViewModel.viewType,\n false\n ).then((data) => {\n this.infinityScrollingIsLoading = false;\n this.setState({\n salesDesignationViews: data.articles.concat(this.state.salesDesignationViews), //prepend\n infiniteScrollUpMinPageNo: this.state.infiniteScrollUpMinPageNo - 1,\n }, () => {\n this.touchPointFactory.createTouchPointsBatch(data.articles);\n\n // we want to keep the scroll position\n setTimeout(()=>{\n const heightDifference = window.document.body.scrollHeight - lastScrollHeight;\n window.scrollBy({top: heightDifference});\n }, 100);\n });\n }).catch(()=>{\n this.infinityScrollingIsLoading = false;\n });\n }\n }\n\n componentWillUnmount(): void {\n if (typeof document !== 'undefined') {\n document.removeEventListener('DOMContentLoaded', this.createTouchpoints);\n }\n }\n\n public onNewArticles(result: IFilteringResult) {\n this.setState({\n salesDesignationViews: result.articles,\n totalResultCount: result.totalResultCount,\n totalPageCount: result.totalPageCount,\n pageEndOffset: result.pageEndOffset,\n pageNo: 1,\n waitingForResponse: false,\n toggleToUpdateFasBar: !this.state.toggleToUpdateFasBar,\n infiniteScrollUpMode: false // after filtering, page url parameter related element should be removed\n });\n ScrollLock.instance.checkSliderPosition();\n }\n\n public stickyHeaderVisibleChange(visible: boolean) {\n if (this.state.isStickyHeaderVisible !== visible) {\n const activeFilterDropdownName = this.state.isStickyHeaderVisible ?\n this.state.stickyHeaderDropDownFilterName : this.state.activeDropDownFilterName;\n this.setState({ stickyHeaderDropDownFilterName: visible ? activeFilterDropdownName : '' });\n this.setState({ activeDropDownFilterName: visible ? '' : activeFilterDropdownName });\n }\n this.setState({ isStickyHeaderVisible: visible });\n }\n\n public handleActiveDropDownFilterChange = (prActiveDropDownFilter: string) => {\n this.setState({\n activeDropDownFilterName: this.state.isStickyHeaderVisible ? '' : prActiveDropDownFilter,\n stickyHeaderDropDownFilterName: this.state.isStickyHeaderVisible ? prActiveDropDownFilter : ''\n });\n }\n \n\n private handleSortContextChange(shouldForceScroll) {\n this.globalState.updateArticleList(shouldForceScroll);\n }\n\n public render() {\n const isMspNeeded = this.props.searchResultPageProps.articleLists?.length === 1 &&\n this.props.searchResultPageProps.type !== SearchResultPageType.TeaserAlternativeView &&\n this.state.componentMounted;\n const shouldHaveFasBar = this.props.searchResultPageProps.articleLists?.length === 1 && (\n this.props.searchResultPageProps.filterContentViewModel?.filterModel &&\n this.state.totalResultCount > 0);\n const isNewsLetterVisible = !(this.props.searchResultPageProps.filterContentViewModel.kiosk ||\n this.props.searchResultPageProps.filterContentViewModel.isMobile) \n && this.state.isStickyHeaderVisible === true && this.state.salesDesignationViews.length > 4;\n const renderNLP = !this.props.searchResultPageProps.filterContentViewModel.isMobile &&\n this.props.searchResultPageProps.type !== SearchResultPageType.Empty &&\n this.props.searchResultPageProps.type !== SearchResultPageType.TeaserAlternativeView;\n\n return (\n x.name === Constants.GLOVES)}\n initialSort={this.props.searchResultPageProps.filterContentViewModel.initialSort}\n updateArticleList={this.handleSortContextChange}\n magicLink={this.props.searchResultPageProps.magicLink}\n >\n \n \n {isMspNeeded &&\n
\n }\n\n {(shouldHaveFasBar &&\n <>\n
\n\n
\n \n \n >\n )}\n\n {this.props.searchResultPageProps.topPageSearchBoxFragment &&\n
\n }\n\n {this.props.searchResultPageProps.topSuggestionLinks?.suggestionLinks.length > 0 &&\n
\n }\n \n {this.props.searchResultPageProps.articleLists?.length === 1\n && this.props.searchResultPageProps.type !== SearchResultPageType.TeaserAlternativeView && (\n <>\n
\n
\n >\n )\n }\n\n {this.state.isTeaserViewResultPage &&\n
\n }\n\n {this.props.searchResultPageProps.bottomSuggestionLinks?.suggestionLinks.length > 0 ? (\n
\n ) : this.props.searchResultPageProps.topSuggestionLinks?.suggestionLinks.length > 0 && (\n
\n )}\n\n {!this.state.isEmptySearchResultPage &&\n
\n }\n\n {this.props.searchResultPageProps.languageSwitcherFragment &&\n
\n }\n
\n \n \n );\n }\n\n private handleLoadArticles() {\n this.setState({ waitingForResponse: true })\n this.articleTilesAjaxCaller\n .loadMoreArticles(\n this.state.pageNo + 1,\n this.props.searchResultPageProps.filterContentViewModel.kiosk,\n this.props.searchResultPageProps.filterContentViewModel.viewType,\n true\n )\n .then((data) => {\n if (data && Array.isArray(data.articles)) {\n this.setState({\n pageNo: this.state.pageNo + 1,\n pageEndOffset: data.pageEndOffset,\n salesDesignationViews: this.state.salesDesignationViews.concat(data.articles)\n }, () => {\n const scrollPos = helper.calculateScrollDistance(\n this.state.pageEndOffset, \n this.state.salesDesignationViews);\n this.setState({ scrollPosition: scrollPos });\n this.bookmarkController.updateArticleList(data.articles);\n\n // call mpc for tp upgrade\n this.touchPointFactory.createTouchPointsBatch(data.articles);\n });\n }\n this.setState({ waitingForResponse: false })\n });\n }\n\n private handleMetaEvent() {\n if (this.props.searchResultPageProps.type === SearchResultPageType.TeaserAlternativeView) {\n const contents: IMetaEventForSearchResult[] = [];\n this.props.searchResultPageProps.articleLists.map(list => {\n contents.push({\n search_string: list.searchString.toLowerCase(),\n content_ids: list.articles.map(\n (view) => view.defaultSalesArticleNo\n ),\n });\n });\n if (contents.length > 1) MetaEvents.sendEventForTeaserSearchResult(contents);\n } else if (!this.state.isEmptySearchResultPage) {\n const salesArticleNos = this.state.salesDesignationViews.map(view => view.defaultSalesArticleNo);\n const searchString = this.props.searchResultPageProps.articleLists[0].searchString.toLowerCase();\n MetaEvents.sendEventForSingleSearchResult(searchString, salesArticleNos);\n }\n }\n}\n","import React, { Component, KeyboardEvent } from 'react';\nimport { ISuggestionLink } from '../searchresultpage.d';\nimport styles from './suggestion-link.scss';\nimport {\n PageViewLogDataId,\n SearchTelemetryAjaxCaller,\n SearchTelemetryData,\n} from '../SearchTelemetryAjaxCaller';\n\nexport default class SuggestionLinks extends Component<{\n suggestionLink: ISuggestionLink;\n magicLink: string;\n isDesktop: boolean;\n labelText: string;\n}> {\n constructor(props) {\n super(props);\n }\n\n public render() {\n const tabNavAttribute = this.props.isDesktop ? { focusable: true } : {};\n const labelText = `${this.props.suggestionLink.searchTerm.toLowerCase()}, ${this.props.labelText}`;\n const stm = this.props.suggestionLink.telemetryDataViewModel\n ? this.props.suggestionLink.telemetryDataViewModel.stm\n : null;\n\n return (\n \n );\n }\n\n private handleTabNavigation(event: KeyboardEvent): void {\n if (event.key === ' ') {\n event.preventDefault(); // prevent page jump\n event.currentTarget?.click();\n }\n }\n\n private handleTelemetry(stm: string): void {\n if (!stm) return;\n const data = new SearchTelemetryData(\n stm,\n PageViewLogDataId.getInstance().get(),\n this.props.suggestionLink.portal,\n this.props.suggestionLink.culture,\n this.props.magicLink\n );\n SearchTelemetryAjaxCaller.postData(data);\n }\n}\n","import React, { Component } from 'react';\nimport ArrowDownIcn from '../../../Assets/svg/arrow_down';\nimport classNames from 'classnames';\nimport styles from './suggestion-links.scss';\nimport { ISuggestionLinks, ISuggestionLinksState } from '../searchresultpage.d';\nimport SuggestionLink from './suggestion-link';\nimport * as helper from '../../../Common/html-helper';\n\nexport default class SuggestionLinks extends Component<{\n suggestionLink: ISuggestionLinks, isMobile: boolean, isTablet: boolean, \n position: string, portal: string, culture: string, magicLink: string, seeAllResultsL10n: string\n}, ISuggestionLinksState> {\n\n constructor(props) {\n super(props);\n this.state = {\n isScrollable: false,\n showLeftChevron: false,\n showRightChevron: true,\n isDesktop: false\n };\n this.handleScroll = this.handleScroll.bind(this);\n }\n\n public componentDidMount(): void {\n if (this.props.isMobile || this.props.isTablet) {\n const columnElement = document.querySelector(`.fas_suggestion_links.fas_single_row.fas_${this.props.position}`);\n if (columnElement) {\n const linkContainer = columnElement.querySelector('.fas_link_container');\n if (linkContainer.scrollWidth > linkContainer.clientWidth) this.setState({isScrollable: true});\n }\n }\n if (document.documentElement.classList.contains('desktop')) {\n this.setState({ isDesktop: true });\n }\n }\n\n public render() {\n const seeAllResults = helper.decodeHTML(this.props.seeAllResultsL10n);\n\n return (\n \n
\n
\n\n
\n {this.state.isScrollable &&\n
\n }\n {this.props.suggestionLink.suggestionLinks.map((link, index) => {\n link.portal = this.props.portal;\n link.culture = this.props.culture;\n return
\n })}\n {this.state.isScrollable &&\n \n }\n \n
\n )\n }\n\n private handleScroll(event): void {\n const target = event.target as HTMLDivElement;\n if (target.scrollLeft === 0 ) {\n if (this.state.showLeftChevron) this.setState({showLeftChevron: false});\n if (!this.state.showRightChevron) this.setState({showRightChevron: true});\n } else if (target.scrollWidth - target.scrollLeft === target.clientWidth) {\n if (!this.state.showLeftChevron) this.setState({showLeftChevron: true});\n if (this.state.showRightChevron) this.setState({showRightChevron: false});\n } else if (target.scrollLeft > 0) {\n if (!this.state.showLeftChevron) this.setState({showLeftChevron: true});\n if (!this.state.showRightChevron) this.setState({showRightChevron: true});\n }\n }\n}","import React, { Component, KeyboardEvent } from 'react';\nimport ArrowDownIcn from '../../../Assets/svg/arrow_down';\nimport ArticleTileGrid from '../../articleTileGrid/article-tile-grid';\nimport { \n ArticleListsToSdvListMapper, \n ITeaserSuggestionsState,\n ITeaserSuggestionsProps\n} from '../searchresultpage.d';\nimport { PageViewLogDataId, SearchTelemetryAjaxCaller, SearchTelemetryData } from '../SearchTelemetryAjaxCaller';\nimport * as helper from '../../../Common/html-helper';\nimport styles from './teaser-suggestions.scss';\nimport { ViewType } from '../../../Common/enums';\nimport NewsLetterPromotion from '../../newsLetterPromotion/newsLetterPromotion';\n\nexport default class TeaserSuggestions extends Component {\n\n constructor(props) {\n super(props);\n this.state = {\n salesDesignationViewList: ArticleListsToSdvListMapper(this.props.articleLists),\n isDesktop: false\n };\n }\n\n public componentDidMount(): void {\n if (document.documentElement.classList.contains('desktop')) {\n this.setState({ isDesktop: true });\n }\n }\n\n public render() {\n const renderNewsLetter = !(this.props.kiosk || this.props.isMobile);\n const tabNavAttribute = this.state.isDesktop ? { focusable: true } : {};\n const buttonText = helper.decodeHTML(this.props.l10n.seeAllResults);\n\n return (\n \n {this.state.salesDesignationViewList.map((list, index) => {\n const stm = this.props.articleLists[index].telemetryDataViewModel \n ? this.props.articleLists[index].telemetryDataViewModel.stm \n : null;\n return (\n
\n
\n \n {this.props.articleLists[index].searchString}:\n \n \n {this.props.articleLists[index].totalResultCount + ' '}\n {this.props.articleLists[index].totalResultCount === 1\n ? helper.decodeHTML(this.props.l10n.result)\n : helper.decodeHTML(this.props.l10n.results)}\n \n
\n \n {(index===0 && renderNewsLetter) &&
}\n
\n\n {this.props.articleLists[index].alternativeTarget != null &&\n
\n }\n
\n );\n })}\n
\n );\n }\n\n private getLabelText(index: number, buttonText: string): string {\n return `${this.props.articleLists[index].searchString.toLowerCase()}, ${buttonText}`;\n }\n\n private handleTabNavigation(event: KeyboardEvent): void {\n if (event.key === ' ') {\n event.preventDefault(); // prevent page jump\n event.currentTarget?.click();\n }\n }\n\n private handleTelemetry(stm: string): void {\n if (!stm) return;\n const data = new SearchTelemetryData(\n stm,\n PageViewLogDataId.getInstance().get(), \n this.props.portal,\n this.props.culture,\n this.props.magicLink\n );\n SearchTelemetryAjaxCaller.postData(data);\n }\n}\n","export interface ISortComponent {\n isDisabled: boolean;\n titleMlt: string;\n sortingProperties: ISortProperty[];\n viewType: string;\n updateSort(sort: ISortProperty);\n}\n\nexport enum SortType {\n Popularity = 1,\n NewestFirst,\n PriceDescending,\n PriceAscending,\n GlovesRecommendation,\n}\n\nexport interface ISortProperty {\n title?: string;\n selected: boolean;\n sortType: SortType;\n urlValue: string;\n}\n\nexport interface ISortComponentState {\n isOpen: boolean;\n headerTitle: string;\n selectedValue: ISortProperty;\n}\nexport interface ISortSideComponentState{\n componentMounted: boolean;\n}\n","import * as React from 'react';\nimport classNames from 'classnames';\nimport { IFasSideContentProps } from '../fas-side-content/fas-side-content.d';\nimport styles from './sort-side-content.scss';\nimport * as helper from '../../Common/html-helper';\nimport { ISortProperty, ISortSideComponentState } from './sort-side-content.d';\nimport { SortContext } from '../globalState/sortContextProvider';\nimport { ISortContext } from '../globalState/sortContextProvider.d';\nimport { publish } from '../../Common/customEventHelper';\nimport TabNavigationHelper from '../../Common/tabNavigationHelper';\n\nexport default class SortSideComponent extends React.Component {\n static contextType = SortContext;\n private tabNav: TabNavigationHelper;\n\n constructor(props: IFasSideContentProps) {\n super(props);\n this.tabNav = TabNavigationHelper.instance;\n\n this.state = {\n componentMounted: false\n };\n }\n\n componentDidMount(): void {\n this.setState({\n componentMounted: true\n });\n }\n\n public render() {\n return (\n \n {(sortContext) => (\n this.tabNav.setGroupAtt(el)}>\n {sortContext.sortPropertyList.map((property, index) => {\n return (
{ this.updateSortSelection(property); }}\n data-property={helper.decodeHTML(property.title)}\n ref={(el)=> this.tabNav.setFocusAtt(el)}\n onKeyDown={this.tabNav.handleKeyDown}>\n {helper.decodeHTML(property.title)}\n
);\n })}\n
\n )}\n \n );\n }\n\n private updateSortSelection(property: ISortProperty) {\n const sortContext: ISortContext = this.context;\n sortContext.setSortSelected(property, true);\n\n publish('ESPP.MainSidePanel.ShouldClose', null);\n }\n}\n","import * as React from 'react';\nimport classNames from 'classnames';\nimport styles from './spinnerComponent.scss';\nimport { ISpinnerComponentProps, ISpinnerComponentState } from './spinnerComponent.d';\n\nexport default class SpinnerComponent extends React.Component {\n\n constructor(props: ISpinnerComponentProps) {\n super(props);\n this.state = {showLoadingSpinner: false};\n }\n \n public componentDidMount() {\n this.props.globalState.Spinner.registerSpinnerChanged(this.props.context, this.changeSpinnerState.bind(this));\n }\n public componentWillUnmount() {\n this.props.globalState.Spinner.unregisterSpinnerChanged(this.props.context);\n }\n\n private changeSpinnerState(isVisible: boolean) {\n if (this.state.showLoadingSpinner == isVisible) return;\n this.setState({ showLoadingSpinner: isVisible });\n }\n\n public render() {\n return (\n \n );\n }\n}\n","import React, {PropsWithChildren, RefObject, useRef} from 'react';\r\nimport styles from './stickyHeader.scss';\r\nimport classnames from 'classnames';\r\nimport * as helper from '../../Common/html-helper';\r\nimport { isIOS } from 'react-device-detect';\r\nimport { ScrollLock } from '../../../Helper/scrollLock';\r\nimport NewsLetterPromotion from '../newsLetterPromotion/newsLetterPromotion';\r\n\r\nconst breakpoint1024 = 1024;\r\n\r\nconst placeholderRef = {\r\n current: {\r\n clientHeight: 0,\r\n getBoundingClientRect() {\r\n return { top: 0 };\r\n },\r\n },\r\n};\r\n\r\nfunction StickyHeader({ children, isBreadcrumbOnly, isEsApp, isGlobal, gridRef, visibleCallBack, \r\n renderNLP, nlpIsVisibleByParentRules }:\r\n PropsWithChildren<{\r\n isBreadcrumbOnly: boolean, isEsApp: boolean, isGlobal: boolean,\r\n gridRef: RefObject, visibleCallBack: (visible: boolean) => void,\r\n renderNLP: boolean, nlpIsVisibleByParentRules: boolean\r\n }>) {\r\n const scrollLock = ScrollLock.instance;\r\n const [contentVisible, setContentVisible] = React.useState(false);\r\n const [newsLetterVisible, setNewsLetterVisible] = React.useState(false);\r\n const nlpHoverInProgressRef = useRef(false);\r\n\r\n const anchorRef = React.useRef();\r\n let lastKnownScrollPosition = 0;\r\n let newsLetterPromotionTimer: ReturnType;\r\n\r\n const hideNewsLetterPromotion = ()=> {\r\n // avoid new timer during hover and scroll:\r\n if(nlpHoverInProgressRef.current === false) {\r\n newsLetterPromotionTimer = setTimeout(() => {\r\n // only if we are not scrolling but hovering on nlp:\r\n if(nlpHoverInProgressRef.current === false)\r\n setNewsLetterVisible(false);\r\n }, 2000);\r\n }\r\n };\r\n\r\n const handleScrollOrResize = React.useCallback(() => {\r\n if (scrollLock.getLockState) {\r\n setContentVisible(false);\r\n visibleCallBack(false);\r\n return;\r\n }\r\n\r\n const scrollingUp = lastKnownScrollPosition > window.scrollY;\r\n const isDesktop = window.innerWidth > breakpoint1024;\r\n lastKnownScrollPosition = window.scrollY;\r\n\r\n const outerRef = anchorRef?.current ? anchorRef : placeholderRef;\r\n const outerRefTop = outerRef.current?.getBoundingClientRect().top ?\r\n outerRef.current.getBoundingClientRect().top : 0;\r\n\r\n const articleGridBottom = gridRef.current?.getBoundingClientRect().bottom || 0;\r\n const visible = (scrollingUp || isDesktop) && outerRefTop <= 50 && articleGridBottom >= 50;\r\n setContentVisible(visible);\r\n visibleCallBack(visible);\r\n\r\n // sticky header is visible, but some time nlp has to be hidden:\r\n if (articleGridBottom <= 750) {\r\n setNewsLetterVisible(false);\r\n } else {\r\n if (renderNLP) {\r\n clearTimeout(newsLetterPromotionTimer);\r\n setNewsLetterVisible(true);\r\n\r\n hideNewsLetterPromotion();\r\n }\r\n }\r\n\r\n }, []);\r\n\r\n const throttledHandleScrollOrResize = helper.throttle(handleScrollOrResize, 300);\r\n\r\n React.useEffect(() => {\r\n window.addEventListener('scroll', throttledHandleScrollOrResize);\r\n\r\n if (!isIOS) {\r\n window.addEventListener('resize', throttledHandleScrollOrResize);\r\n }\r\n\r\n return () => {\r\n window.removeEventListener('scroll', () => {\r\n throttledHandleScrollOrResize;\r\n });\r\n\r\n if (!isIOS) {\r\n window.removeEventListener('resize', throttledHandleScrollOrResize);\r\n }\r\n };\r\n }, []);\r\n\r\n const handleHoverOnNewsLetterFloater = () => {\r\n nlpHoverInProgressRef.current = true;\r\n };\r\n\r\n const handleEndHoverOnNewsLetterFloater = () => {\r\n nlpHoverInProgressRef.current = false;\r\n hideNewsLetterPromotion();\r\n }\r\n\r\n return (\r\n <>\r\n } />\r\n
\r\n
\r\n {children}\r\n
\r\n {renderNLP &&\r\n
\r\n \r\n
\r\n }\r\n
\r\n >\r\n\r\n\r\n );\r\n}\r\n\r\nexport default StickyHeader;\r\n","import * as React from 'react';\nimport * as helper from '../../Common/html-helper';\nimport classNames from 'classnames';\nimport styles from './tooltipComponent.scss';\nimport { ITooltipComponent, ITooltipComponentState } from './tooltipComponent.d';\nimport TabNavigationHelper from '../../Common/tabNavigationHelper';\n\nexport default class TooltipComponent extends React.Component
{\n private tabNav: TabNavigationHelper;\n private closeButtonRef = React.createRef();\n private previouslySelectedElement: Element;\n\n constructor(props: ITooltipComponent) {\n super(props);\n this.state = {};\n this.tabNav = TabNavigationHelper.instance;\n\n this.closeButtonClicked = this.closeButtonClicked.bind(this);\n }\n\n public componentDidMount() {\n if(this.closeButtonRef.current) {\n this.tabNav.setFocusAtt(this.closeButtonRef.current);\n }\n }\n\n componentDidUpdate(): void {\n if(this.props.showTooltip) {\n this.previouslySelectedElement = document.activeElement;\n window.shell.tabNav.focus(this.closeButtonRef.current);\n }\n }\n\n public render() {\n return (\n {this.tabNav.setGroupAtt(el); this.tabNav.setModalAtt(el)}}>\n
\n
\n {!this.props.imageName ? null :
}\n
{this.toolTipText()}
\n
\n );\n }\n\n private closeButtonClicked(event: React.MouseEvent) {\n window.shell.tabNav.focus(this.previouslySelectedElement);\n event.preventDefault();\n event.stopPropagation();\n this.props.updateTooltip(false, this.props.text, this.props.imageName);\n }\n\n private toolTipText(): string {\n return this.props.text ? helper.decodeHTML(this.props.text) : '';\n }\n}\n","export class ScrollLock {\n // tslint:disable-next-line: variable-name\n private static _instance: ScrollLock;\n private bodyScrollPosition = 0;\n private isLocked = false;\n private timer = null;\n static get instance() {\n return this._instance || (this._instance = new this());\n }\n\n public lock(): void {\n const webContainer = document.querySelector('.container');\n const mobileContainer = document.querySelector('.page-container');\n const lockAlreadyActived = document.body.getAttribute('style') ?\n document.body.getAttribute('style').length > 0 : false;\n if ((webContainer || mobileContainer) && !lockAlreadyActived) {\n this.bodyScrollPosition = window.scrollY;\n document.body.setAttribute(\n 'style',\n 'overflow: hidden !important; position: fixed !important; top: -' +\n this.bodyScrollPosition +\n 'px;',\n );\n this.isLocked = true;\n }\n }\n\n public unlock(): void {\n const webContainer = document.querySelector('.container');\n const mobileContainer = document.querySelector('.page-container');\n\n if (webContainer || mobileContainer) {\n document.body.removeAttribute('style');\n window.scrollTo(0, window.scrollY > 0 ? window.scrollY : this.bodyScrollPosition);\n this.isLocked = false;\n }\n }\n\n public checkSliderPosition() {\n const isLocked = this.getLockState;\n if (document.getElementsByClassName('fas_article_tile_grid_root')[0]?.getBoundingClientRect().top < -100) {\n if (isLocked) this.unlock();\n\n let header : Element = document.getElementById('ats_scope-fas-bar');\n const parent = header.closest('.fas_header');\n if(parent){\n header = parent;\n }\n if (!header) {\n header = document.getElementsByClassName('fas_scope-fas')[0];\n } \n\n window.scrollTo({\n behavior: 'smooth',\n top: header.getBoundingClientRect().top\n - document.body.getBoundingClientRect().top - 50\n })\n if (isLocked) {\n this.timer = null;\n addEventListener('scroll', this.scrollHandler)\n }\n }\n }\n\n public get getLockState() {\n return this.isLocked;\n }\n\n private scrollHandler = () => {\n if (this.timer !== null) {\n clearTimeout(this.timer)\n }\n this.timer = setTimeout(() => {\n const mspHidden = document.getElementsByClassName('fas_msp_hidden').length > 0;\n if (!mspHidden) {\n this.lock();\n }\n removeEventListener('scroll', this.scrollHandler)\n }, 500);\n }\n}\n","var prefix = require('prefix-style')\nvar toCamelCase = require('to-camel-case')\nvar cache = { 'float': 'cssFloat' }\nvar addPxToStyle = require('add-px-to-style')\n\nfunction style (element, property, value) {\n var camel = cache[property]\n if (typeof camel === 'undefined') {\n camel = detect(property)\n }\n\n // may be false if CSS prop is unsupported\n if (camel) {\n if (value === undefined) {\n return element.style[camel]\n }\n\n element.style[camel] = addPxToStyle(camel, value)\n }\n}\n\nfunction each (element, properties) {\n for (var k in properties) {\n if (properties.hasOwnProperty(k)) {\n style(element, k, properties[k])\n }\n }\n}\n\nfunction detect (cssProp) {\n var camel = toCamelCase(cssProp)\n var result = prefix(camel)\n cache[camel] = cache[cssProp] = cache[result] = result\n return result\n}\n\nfunction set () {\n if (arguments.length === 2) {\n if (typeof arguments[1] === 'string') {\n arguments[0].style.cssText = arguments[1]\n } else {\n each(arguments[0], arguments[1])\n }\n } else {\n style(arguments[0], arguments[1], arguments[2])\n }\n}\n\nmodule.exports = set\nmodule.exports.set = set\n\nmodule.exports.get = function (element, properties) {\n if (Array.isArray(properties)) {\n return properties.reduce(function (obj, prop) {\n obj[prop] = style(element, prop || '')\n return obj\n }, {})\n } else {\n return style(element, properties || '')\n }\n}\n","/*\n Module dependencies\n*/\nvar ElementType = require('domelementtype');\nvar entities = require('entities');\n\n/* mixed-case SVG and MathML tags & attributes\n recognized by the HTML parser, see\n https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inforeign\n*/\nvar foreignNames = require('./foreignNames.json');\nforeignNames.elementNames.__proto__ = null; /* use as a simple dictionary */\nforeignNames.attributeNames.__proto__ = null;\n\nvar unencodedElements = {\n __proto__: null,\n style: true,\n script: true,\n xmp: true,\n iframe: true,\n noembed: true,\n noframes: true,\n plaintext: true,\n noscript: true\n};\n\n/*\n Format attributes\n*/\nfunction formatAttrs(attributes, opts) {\n if (!attributes) return;\n\n var output = '';\n var value;\n\n // Loop through the attributes\n for (var key in attributes) {\n value = attributes[key];\n if (output) {\n output += ' ';\n }\n\n if (opts.xmlMode === 'foreign') {\n /* fix up mixed-case attribute names */\n key = foreignNames.attributeNames[key] || key;\n }\n output += key;\n if ((value !== null && value !== '') || opts.xmlMode) {\n output +=\n '=\"' +\n (opts.decodeEntities\n ? entities.encodeXML(value)\n : value.replace(/\\\"/g, '"')) +\n '\"';\n }\n }\n\n return output;\n}\n\n/*\n Self-enclosing tags (stolen from node-htmlparser)\n*/\nvar singleTag = {\n __proto__: null,\n area: true,\n base: true,\n basefont: true,\n br: true,\n col: true,\n command: true,\n embed: true,\n frame: true,\n hr: true,\n img: true,\n input: true,\n isindex: true,\n keygen: true,\n link: true,\n meta: true,\n param: true,\n source: true,\n track: true,\n wbr: true\n};\n\nvar render = (module.exports = function(dom, opts) {\n if (!Array.isArray(dom) && !dom.cheerio) dom = [dom];\n opts = opts || {};\n\n var output = '';\n\n for (var i = 0; i < dom.length; i++) {\n var elem = dom[i];\n\n if (elem.type === 'root') output += render(elem.children, opts);\n else if (ElementType.isTag(elem)) output += renderTag(elem, opts);\n else if (elem.type === ElementType.Directive)\n output += renderDirective(elem);\n else if (elem.type === ElementType.Comment) output += renderComment(elem);\n else if (elem.type === ElementType.CDATA) output += renderCdata(elem);\n else output += renderText(elem, opts);\n }\n\n return output;\n});\n\nvar foreignModeIntegrationPoints = [\n 'mi',\n 'mo',\n 'mn',\n 'ms',\n 'mtext',\n 'annotation-xml',\n 'foreignObject',\n 'desc',\n 'title'\n];\n\nfunction renderTag(elem, opts) {\n // Handle SVG / MathML in HTML\n if (opts.xmlMode === 'foreign') {\n /* fix up mixed-case element names */\n elem.name = foreignNames.elementNames[elem.name] || elem.name;\n /* exit foreign mode at integration points */\n if (\n elem.parent &&\n foreignModeIntegrationPoints.indexOf(elem.parent.name) >= 0\n )\n opts = Object.assign({}, opts, { xmlMode: false });\n }\n if (!opts.xmlMode && ['svg', 'math'].indexOf(elem.name) >= 0) {\n opts = Object.assign({}, opts, { xmlMode: 'foreign' });\n }\n\n var tag = '<' + elem.name;\n var attribs = formatAttrs(elem.attribs, opts);\n\n if (attribs) {\n tag += ' ' + attribs;\n }\n\n if (opts.xmlMode && (!elem.children || elem.children.length === 0)) {\n tag += '/>';\n } else {\n tag += '>';\n if (elem.children) {\n tag += render(elem.children, opts);\n }\n\n if (!singleTag[elem.name] || opts.xmlMode) {\n tag += '' + elem.name + '>';\n }\n }\n\n return tag;\n}\n\nfunction renderDirective(elem) {\n return '<' + elem.data + '>';\n}\n\nfunction renderText(elem, opts) {\n var data = elem.data || '';\n\n // if entities weren't decoded, no need to encode them back\n if (\n opts.decodeEntities &&\n !(elem.parent && elem.parent.name in unencodedElements)\n ) {\n data = entities.encodeXML(data);\n }\n\n return data;\n}\n\nfunction renderCdata(elem) {\n return '';\n}\n\nfunction renderComment(elem) {\n return '';\n}\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", { value: true });\nexports.Doctype = exports.CDATA = exports.Tag = exports.Style = exports.Script = exports.Comment = exports.Directive = exports.Text = exports.Root = exports.isTag = exports.ElementType = void 0;\n/** Types of elements found in htmlparser2's DOM */\nvar ElementType;\n(function (ElementType) {\n /** Type for the root element of a document */\n ElementType[\"Root\"] = \"root\";\n /** Type for Text */\n ElementType[\"Text\"] = \"text\";\n /** Type for ... ?> */\n ElementType[\"Directive\"] = \"directive\";\n /** Type for */\n ElementType[\"Comment\"] = \"comment\";\n /** Type for