class FindWidget { findWidget : HTMLDivElement ; findWidgetStartSearch : HTMLSpanElement ; findWidgetInput : HTMLInputElement ; findWWidgetErrorMessage : HTMLDivElement ; findWidgetInfo : HTMLSpanElement ; findWidgetOutdatedSearch : HTMLSpanElement ; findWidgetCancelSearch : HTMLSpanElement ; findWidgetOptionMatchCase : HTMLDivElement ; findWidgetOptionWholeCell : HTMLDivElement ; findWidgetOptionWholeCellTrimmed : HTMLDivElement ; findWidgetOptionRegex : HTMLDivElement ; findWidgetPrevious : HTMLDivElement ; findWidgetNext : HTMLDivElement ; findWidgetGripperIsMouseDown : boolean ; findWidgetDownPointOffsetInPx : number ; findWidgetInputValueCache : string ; findOptionMatchCaseCache = false ; findOptionMatchWholeCellCache = false ; findOptionTrimCellCache = false ; findOptionUseRegexCache = false ; findOptionMatchCasePreviousCache = false ; findOptionMatchWholeCellPreviousCache = false ; findOptionTrimCellPreviousCache = false ; findOptionUseRegexPreviousCache = false ; /**
     * we need this to be sure if we need to show the outdated search indicator
     * this should be reset when we re-searched
     */ tableHasChangedAfterSearch = false ; findWidgetQueryCancellationToken : { isCancellationRequested : boolean ; } = { isCancellationRequested : false } ; findWidgetCurrRegex : RegExp | null = null ; findMatchCellClass = 'search-result-cell' ; findOldMatchCellClass = 'old-search-result-cell' ; onWindowResizeThrottled : ( ) => void ; onWindowResizeThrottledBound : ( ) => void ; onSearchInputPreDebounced : ( e : KeyboardEvent | null ) => void ; onSearchInputPreDebouncedBound : ( e : KeyboardEvent | null ) => void ; onDocumentRootKeyDownBound : ( e : ExtendedKeyboardEvent ) => void ; onFindWidgetDragEndBound : ( e : MouseEvent ) => void ; onFindWidgetDragBound : ( e : MouseEvent ) => void ; /**
     * stores the last find results
     */ lastFindResults : HandsontableSearchResult [ ] = [ ] ; currentFindIndex = 0 ; findWidgetProgressbar : Progressbar ; constructor ( ) { this . findWidget = _getById ( 'find-widget' ) as HTMLDivElement ; this . findWidgetStartSearch = _getById ( 'find-widget-start-search' ) as HTMLSpanElement ; this . findWidgetInput = _getById ( 'find-widget-input' ) as HTMLInputElement ; this . findWWidgetErrorMessage = _getById ( 'find-widget-error-message' ) as HTMLDivElement ; this . findWidgetInfo = _getById ( 'find-widget-info' ) as HTMLSpanElement ; this . findWidgetOutdatedSearch = _getById ( 'find-widget-outdated-search' ) as HTMLSpanElement ; this . findWidgetCancelSearch = _getById ( 'find-widget-cancel-search' ) as HTMLSpanElement ; this . findWidgetOptionMatchCase = _getById ( 'find-window-option-match-case' ) as HTMLDivElement ; this . findWidgetOptionWholeCell = _getById ( 'find-window-option-whole-cell' ) as HTMLDivElement ; this . findWidgetOptionWholeCellTrimmed = _getById ( 'find-window-option-whole-cell-trimmed' ) as HTMLDivElement ; this . findWidgetOptionRegex = _getById ( 'find-window-option-regex' ) as HTMLDivElement ; this . findWidgetPrevious = _getById ( 'find-widget-previous' ) as HTMLDivElement ; this . findWidgetNext = _getById ( 'find-widget-next' ) as HTMLDivElement ; this . findWidgetGripperIsMouseDown = false ; this . findWidgetDownPointOffsetInPx = 0 ; this . findWidgetInputValueCache = '' ; this . onWindowResizeThrottled = throttle ( this . onWindowResize , 200 ) ; this . onSearchInputPreDebounced = debounce ( this . onSearchInputPre , 200 ) ; this . onWindowResizeThrottledBound = this . onWindowResizeThrottled . bind ( this ) ; this . onSearchInputPreDebouncedBound = this . onSearchInputPre . bind ( this ) ; this . onDocumentRootKeyDownBound = this . onDocumentRootKeyDown . bind ( this ) ; this . onFindWidgetDragEndBound = this . onFindWidgetDragEnd . bind ( this ) ; this . onFindWidgetDragBound = this . onFindWidgetDrag . bind ( this ) ; this . findWidgetProgressbar = new Progressbar ( 'find-widget-progress-bar' ) ; } param show showOrHideWidget ( show ) { if ( ! hot ) return ; let currIsSown = this . isFindWidgetDisplayed ( ) ; if ( currIsSown === show ) { if ( show ) { setTimeout ( ( ) => { this . findWidgetInput . focus ( ) ; } , 0 ) ; } return ; } this . findWidget . style . display = show ? 'flex' : 'none' ; if ( show ) { if ( this . findWidgetInput . value === '' && this . findWidgetInputValueCache !== '' ) { this . findWidgetInput . value = this . findWidgetInputValueCache ; } if ( this . lastFindResults . length > 0 ) { hot . updateSettings ( { search : { isSuspended : false } } as any , false ) ; } setTimeout ( ( ) => { this . findWidgetInput . focus ( ) ; } , 0 ) ; } else { if ( this . getIsSearchCancelDisplayed ( ) ) { this . onCancelSearch ( ) ; this . findWidget . style . display = 'flex' ; } else { hot . updateSettings ( { search : { isSuspended : true } } as any , false ) ; } } } isFindWidgetDisplayed ( ) { return this . findWidget . style . display !== 'none' ; } /**
    * toggles the find widget visibility
    */ toggleFindWidgetVisibility ( ) { this . showOrHideWidget ( this . findWidget . style . display === 'none' ) ; } /**
     * prepares the find widget (re-setup all listeners...)
     */ setupFind ( ) { Mousetrap . unbind ( [ 'meta+f' , 'ctrl+f' ] ) ; Mousetrap . bindGlobal ( [ 'meta+f' , 'ctrl+f' ] , ( e ) => { e . preventDefault ( ) ; this . showOrHideWidget ( true ) ; } ) ; this . findWidgetInput . removeEventListener ( 'keyup' , this . onSearchInputPreDebouncedBound ) ; this . findWidgetInput . addEventListener ( 'keyup' , this . onSearchInputPreDebouncedBound ) ; document . documentElement . removeEventListener ( 'keydown' , this . onDocumentRootKeyDownBound ) ; document . documentElement . addEventListener ( 'keydown' , this . onDocumentRootKeyDownBound ) ; Mousetrap . unbind ( 'esc' ) ; Mousetrap . bindGlobal ( 'esc' , ( e ) => { this . showOrHideWidget ( false ) ; } ) ; Mousetrap . bindGlobal ( 'f3' , ( e ) => { if ( this . isFindWidgetDisplayed ( ) === false && this . lastFindResults . length === 0 ) return ; this . gotoNextFindMatch ( ) ; } ) ; Mousetrap . unbind ( 'shift+f3' ) ; Mousetrap . bindGlobal ( 'shift+f3' , ( e ) => { if ( this . isFindWidgetDisplayed ( ) === false && this . lastFindResults . length === 0 ) return ; this . gotoPreviousFindMatch ( ) ; } ) ; window . removeEventListener ( 'resize' , this . onWindowResizeThrottledBound ) ; window . addEventListener ( 'resize' , this . onWindowResizeThrottledBound ) ; } onDocumentRootKeyDown ( e ) { if ( this . isFindWidgetDisplayed ( ) && document . activeElement === this . findWidgetInput ) { if ( ( e . key === 'F1' ) || ( e . metaKey || e . ctrlKey ) ) { let xyz = 1 ; } else { e . stopImmediatePropagation ( ) ; } if ( e . key === 'Escape' ) { Mousetrap . trigger ( 'esc' ) ; } else if ( e . key === 'F3' ) { Mousetrap . trigger ( e . shiftKey ? 'shift+f3' : 'f3' ) ; } else if ( e . key === 'F2' ) { if ( hot && this . lastFindResults . length > 0 ) { let match = this . lastFindResults [ this . currentFindIndex ] ; hot . selectCell ( match . row , match . col ) ; hot . scrollViewportTo ( match . row ) ; } } else if ( e . key === 'Enter' ) { this . refreshCurrentSearch ( ) ; } } } /**
     * called on every keydown
     * currently not use because we don't start a new search on keydown
     */ onSearchInputPre ( e ) { if ( e && e . key !== 'Enter' ) return ; } param isOpeningFindWidget param jumpToResult param pretendedText async onSearchInput ( isOpeningFindWidget , jumpToResult , pretendedText ) { if ( ! hot ) return ; if ( isOpeningFindWidget === true && this . findWidgetInput . value === "" ) { this . findWidgetInput . focus ( ) ; return ; } this . showOrHideOutdatedSearchIndicator ( false ) ; if ( this . findOptionUseRegexCache ) { let regexIsValid = this . refreshFindWidgetRegex ( false ) ; if ( ! regexIsValid ) { this . findWidgetInput . focus ( ) ; return ; } } this . findOptionMatchCasePreviousCache = this . findOptionMatchCaseCache ; this . findOptionMatchWholeCellPreviousCache = this . findOptionMatchWholeCellCache ; this . findOptionTrimCellPreviousCache = this . findOptionTrimCellCache ; this . findOptionUseRegexPreviousCache = this . findOptionUseRegexCache ; this . tableHasChangedAfterSearch = false ; let searchPlugin = hot . getPlugin ( 'search' ) ; if ( pretendedText === null ) { this . findWidgetProgressbar . setValue ( 0 ) ; this . findWidgetProgressbar . show ( ) ; this . findWidgetQueryCancellationToken . isCancellationRequested = false ; this . enableOrDisableFindWidgetInput ( false ) ; this . showOrHideSearchCancel ( true ) ; this . _enabledOrDisableFindWidgetInteraction ( false ) ; this . lastFindResults = await searchPlugin . queryAsync ( this . findWidgetInput . value , this . findWidgetQueryCancellationToken , this . _onSearchProgress . bind ( this ) , 5 ) ; this . _getRealIndicesFromFindResult ( ) ; } else { this . lastFindResults = searchPlugin . query ( pretendedText ) ; this . _getRealIndicesFromFindResult ( ) ; } statusInfo . innerText = `Rendering table...` ; if ( this . lastFindResults . length === 0 ) { if ( this . findWidgetQueryCancellationToken . isCancellationRequested === false ) { this . findWidgetInfo . innerText = `No results` ; } else { this . findWidgetInfo . innerText = `Cancelled` ; } } if ( jumpToResult && this . lastFindResults . length > 0 ) { this . gotoFindMatchByIndex ( 0 ) ; } setTimeout ( ( ) => { hot ! . updateSettings ( { search : { isSuspended : this . lastFindResults . length === 0 } } as any , false ) ; } , 0 ) ; setTimeout ( ( ) => { this . findWidgetQueryCancellationToken . isCancellationRequested = false ; statusInfo . innerText = `` ; this . enableOrDisableFindWidgetInput ( true ) ; this . _enabledOrDisableFindWidgetInteraction ( true ) ; if ( this . lastFindResults . length === 0 ) { this . findWidgetNext . classList . add ( 'div-disabled' ) ; this . findWidgetPrevious . classList . add ( 'div-disabled' ) ; } this . findWidgetProgressbar . hide ( ) ; this . findWidgetInput . focus ( ) ; } , 0 ) ; } /**
     * sets the physical row indices for  lastFindResults * this is needed because when we use the next/previous btns we jump to the cell index and after sorting the
     * visual index might have changed
     *
     *  note _getRealIndicesFromFindResult ( ) { if ( ! hot ) return ; for ( let i = 0 ; i < this . lastFindResults . length ; i ++ ) { const findResult = this . lastFindResults [ i ] ; findResult . rowReal = hot . toPhysicalRow ( findResult . row ) ; findResult . colReal = hot . toPhysicalColumn ( findResult . col ) ; } } _onSearchProgress ( index , maxIndex , percentage ) { this . findWidgetProgressbar . setValue ( percentage ) ; if ( index >= maxIndex ) { this . _onSearchFinished ( ) ; } } _onSearchFinished ( ) { this . showOrHideSearchCancel ( false ) ; } onCancelSearch ( ) { this . findWidgetQueryCancellationToken . isCancellationRequested = true ; this . _onSearchFinished ( ) ; if ( ! hot ) return ; hot . updateSettings ( { search : { isSuspended : true } } as any , false ) ; } toggleFindWindowOptionMatchCase ( ) { this . setFindWindowOptionMatchCase ( this . findWidgetOptionMatchCase . classList . contains ( `active` ) ? false : true ) ; } setFindWindowOptionMatchCase ( enabled ) { if ( enabled ) { this . findWidgetOptionMatchCase . classList . add ( `active` ) ; } else { this . findWidgetOptionMatchCase . classList . remove ( `active` ) ; } this . findOptionMatchCaseCache = enabled ; if ( this . findWidgetInputValueCache !== '' ) { if ( this . _hasAnyFindOptionChanged ( ) ) { this . showOrHideOutdatedSearchIndicator ( true ) ; } else { if ( this . tableHasChangedAfterSearch ) { } else { this . showOrHideOutdatedSearchIndicator ( false ) ; } } } } toggleFindWindowOptionWholeCell ( ) { this . setFindWindowOptionWholeCell ( this . findWidgetOptionWholeCell . classList . contains ( `active` ) ? false : true ) ; } setFindWindowOptionWholeCell ( enabled ) { if ( enabled ) { this . findWidgetOptionWholeCell . classList . add ( `active` ) ; } else { this . findWidgetOptionWholeCell . classList . remove ( `active` ) ; } this . findOptionMatchWholeCellCache = enabled ; if ( this . findWidgetInputValueCache !== '' ) { if ( this . _hasAnyFindOptionChanged ( ) ) { this . showOrHideOutdatedSearchIndicator ( true ) ; } else { if ( this . tableHasChangedAfterSearch ) { } else { this . showOrHideOutdatedSearchIndicator ( false ) ; } } } } toggleFindWindowOptionMatchTrimmedCell ( ) { this . setFindWindowOptionMatchTrimmedCell ( this . findWidgetOptionWholeCellTrimmed . classList . contains ( `active` ) ? false : true ) ; } setFindWindowOptionMatchTrimmedCell ( enabled ) { if ( enabled ) { this . findWidgetOptionWholeCellTrimmed . classList . add ( `active` ) ; } else { this . findWidgetOptionWholeCellTrimmed . classList . remove ( `active` ) ; } this . findOptionTrimCellCache = enabled ; if ( this . findWidgetInputValueCache !== '' ) { if ( this . _hasAnyFindOptionChanged ( ) ) { this . showOrHideOutdatedSearchIndicator ( true ) ; } else { if ( this . tableHasChangedAfterSearch ) { } else { this . showOrHideOutdatedSearchIndicator ( false ) ; } } } } toggleFindWindowOptionRegex ( ) { this . setFindWindowOptionRegex ( this . findWidgetOptionRegex . classList . contains ( `active` ) ? false : true ) ; } /**
 * also refreshes the  findWidgetCurrRegex when enabled (in  onSearchInput )
 *  param enabled setFindWindowOptionRegex ( enabled ) { if ( enabled ) { this . findWidgetOptionRegex . classList . add ( `active` ) ; this . refreshFindWidgetRegex ( false ) ; } else { this . findWidgetOptionRegex . classList . remove ( `active` ) ; this . refreshFindWidgetRegex ( true ) ; } this . findOptionUseRegexCache = enabled ; if ( this . findWidgetInputValueCache !== '' ) { if ( this . _hasAnyFindOptionChanged ( ) ) { this . showOrHideOutdatedSearchIndicator ( true ) ; } else { if ( this . tableHasChangedAfterSearch ) { } else { this . showOrHideOutdatedSearchIndicator ( false ) ; } } } } /**
     * returns true if any find option was changed after the last search
     */ _hasAnyFindOptionChanged ( ) { if ( this . findOptionMatchCasePreviousCache !== this . findOptionMatchCaseCache ) return true ; if ( this . findOptionMatchWholeCellPreviousCache !== this . findOptionMatchWholeCellCache ) return true ; if ( this . findOptionTrimCellPreviousCache !== this . findOptionTrimCellCache ) return true ; if ( this . findOptionUseRegexPreviousCache !== this . findOptionUseRegexCache ) return true ; return false ; } /**
     * refreshes the  findWidgetCurrRegex from the  findWidgetInput returns refreshFindWidgetRegex ( forceReset ) { if ( forceReset ) { this . findWidgetCurrRegex = null ; this . findWWidgetErrorMessage . innerText = '' ; this . findWidgetInput . classList . remove ( 'error-input' ) ; return false ; } try { this . findWidgetCurrRegex = new RegExp ( this . findWidgetInput . value , '' ) ; this . findWWidgetErrorMessage . innerText = '' ; this . findWidgetInput . classList . remove ( 'error-input' ) ; return true ; } catch ( error ) { console . log ( `error:` , error . message ) ; this . findWidgetCurrRegex = null ; this . findWWidgetErrorMessage . innerText = error . message ; this . findWidgetInput . classList . add ( 'error-input' ) ; return false ; } } param enable _enabledOrDisableFindWidgetInteraction ( enable ) { const disabledClass = 'div-disabled' ; if ( enable ) { this . findWidgetOptionMatchCase . classList . remove ( disabledClass ) ; this . findWidgetOptionWholeCell . classList . remove ( disabledClass ) ; this . findWidgetOptionWholeCellTrimmed . classList . remove ( disabledClass ) ; this . findWidgetOptionRegex . classList . remove ( disabledClass ) ; this . findWidgetPrevious . classList . remove ( disabledClass ) ; this . findWidgetNext . classList . remove ( disabledClass ) ; } else { this . findWidgetOptionMatchCase . classList . add ( disabledClass ) ; this . findWidgetOptionWholeCell . classList . add ( disabledClass ) ; this . findWidgetOptionWholeCellTrimmed . classList . add ( disabledClass ) ; this . findWidgetOptionRegex . classList . add ( disabledClass ) ; this . findWidgetPrevious . classList . add ( disabledClass ) ; this . findWidgetNext . classList . add ( disabledClass ) ; } } param isEnable enableOrDisableFindWidgetInput ( isEnable ) { this . findWidgetInput . disabled = ! isEnable ; } param isOutdated showOrHideOutdatedSearchIndicator ( isOutdated ) { this . findWidgetOutdatedSearch . style . display = isOutdated ? 'block' : 'none' ; } /**
     * refreshes the current search
     * e.g. when the table has changed and we want to update the search
     */ refreshCurrentSearch ( ) { this . findWidgetInputValueCache = this . findWidgetInput . value ; if ( this . findWidgetInput . value === '' ) return ; this . onSearchInput ( false , true , null ) ; } param show showOrHideSearchCancel ( show ) { this . findWidgetCancelSearch . style . display = show ? 'block' : 'none' ; this . findWidgetInfo . style . display = show ? 'none' : 'block' ; this . findWidgetStartSearch . style . display = show ? 'none' : 'block' ; } /**
     * returns if the cancel btn is displayed
     */ getIsSearchCancelDisplayed ( ) { return this . findWidgetCancelSearch . style . display === 'block' ; } /**
     * moves to the previous match and selects that cell
     */ gotoPreviousFindMatch ( ) { this . gotoFindMatchByIndex ( this . currentFindIndex - 1 ) ; } /**
     * moves to the next match and selects that cell
     */ gotoNextFindMatch ( ) { this . gotoFindMatchByIndex ( this . currentFindIndex + 1 ) ; } /**
     * moves the table to the given find match by index
     * also sets the  currentFindIndex param matchIndex gotoFindMatchByIndex ( matchIndex ) { if ( ! hot ) return ; if ( matchIndex >= this . lastFindResults . length ) { this . gotoFindMatchByIndex ( 0 ) ; return ; } if ( matchIndex < 0 ) { this . gotoFindMatchByIndex ( this . lastFindResults . length - 1 ) ; return ; } let match = this . lastFindResults [ matchIndex ] ; { const autoColumnSizePlugin = hot . getPlugin ( 'autoColumnSize' ) ; const autoRowSizePlugin = hot . getPlugin ( 'autoRowSize' ) ; const visualRowIndex = hot . toVisualRow ( match . rowReal ) ; const visualColIndex = hot . toVisualColumn ( match . colReal ) ; const _firstVisualRow = autoRowSizePlugin . getFirstVisibleRow ( ) ; const _lastVisualRow = autoRowSizePlugin . getLastVisibleRow ( ) ; const _firstVisualCol = autoColumnSizePlugin . getFirstVisibleColumn ( ) ; const _lastVisualCol = autoColumnSizePlugin . getLastVisibleColumn ( ) ; let virtualColumnIndexToScrollTo = undefined ; let virtualRowIndexToScrollTo = undefined ; let useAutoScroll = true ; let useAutoScrollRow = false ; let useAutoScrollCol = false ; if ( visualRowIndex < _firstVisualRow || visualRowIndex > _lastVisualRow ) { useAutoScroll = false ; useAutoScrollRow = true ; } if ( visualColIndex < _firstVisualCol || visualColIndex > _lastVisualCol ) { useAutoScroll = false ; useAutoScrollCol = true ; } if ( getAreCommentsDisplayed ( ) === false ) { useAutoScroll = true ; useAutoScrollRow = false ; useAutoScrollCol = false ; } hot . selectCell ( visualRowIndex , visualColIndex , undefined , undefined , useAutoScroll ) ; if ( useAutoScroll === false ) { if ( useAutoScrollRow ) { const rowsToSubtract = Math . floor ( ( _lastVisualRow - _firstVisualRow ) / 2 ) ; const clampedVisualRowIndex = Math . max ( visualRowIndex - rowsToSubtract , 0 ) ; virtualRowIndexToScrollTo = clampedVisualRowIndex ; } if ( useAutoScrollCol ) { const rowsToSubtract = Math . floor ( ( _lastVisualCol - _firstVisualCol ) / 2 ) ; const clampedVisualRowIndex = Math . max ( visualColIndex - rowsToSubtract , 0 ) ; virtualColumnIndexToScrollTo = clampedVisualRowIndex ; } hot . scrollViewportTo ( virtualRowIndexToScrollTo , virtualColumnIndexToScrollTo ) ; } } this . findWidgetInfo . innerText = `${ matchIndex + 1 }/${ this . lastFindResults . length }` ; this . currentFindIndex = matchIndex ; setTimeout ( ( ) => { this . findWidgetInput . focus ( ) ; } , 0 ) ; } param e onFindWidgetGripperMouseDown ( e ) { e . preventDefault ( ) ; this . findWidgetGripperIsMouseDown = true ; let xFromRight = document . body . clientWidth - e . clientX ; if ( this . findWidget . style . right === null || this . findWidget . style . right === "" ) { return ; } let rightString = this . findWidget . style . right . substr ( 0 , this . findWidget . style . right . length - 2 ) ; this . findWidgetDownPointOffsetInPx = xFromRight - parseInt ( rightString ) ; document . addEventListener ( 'mouseup' , this . onFindWidgetDragEndBound ) ; document . addEventListener ( 'mousemove' , this . onFindWidgetDragBound ) ; } param e onFindWidgetDrag ( e ) { e . preventDefault ( ) ; let xFromRight = document . body . clientWidth - e . clientX ; let newRight = xFromRight - this . findWidgetDownPointOffsetInPx ; newRight = Math . max ( newRight , 0 ) ; newRight = Math . min ( newRight , document . body . clientWidth - this . findWidget . clientWidth ) ; this . findWidget . style . right = `${ newRight }px` ; } onFindWidgetDragEnd ( e ) { this . findWidgetGripperIsMouseDown = false ; document . removeEventListener ( 'mousemove' , this . onFindWidgetDragBound ) ; document . removeEventListener ( 'mouseup' , this . onFindWidgetDragEndBound ) ; } /**
     * ensures that the widget stays in window bounds
     */ onWindowResize ( ) { if ( this . findWidget . style . right === null || this . findWidget . style . right === "" ) { return ; } let rightString = this . findWidget . style . right . substr ( 0 , this . findWidget . style . right . length - 2 ) ; let currRight = parseInt ( rightString ) ; currRight = Math . max ( currRight , 0 ) ; currRight = Math . min ( currRight , document . body . clientWidth - this . findWidget . clientWidth ) ; this . findWidget . style . right = `${ currRight }px` ; } }