1- import React , { useCallback , useMemo , useRef , useState } from 'react' ;
1+ import React , { useCallback , useMemo , useRef , useState , useEffect } from 'react' ;
22import { DocSearchButton , useDocSearchKeyboardEvents } from '@docsearch/react' ;
33import Head from '@docusaurus/Head' ;
44import { useHistory } from '@docusaurus/router' ;
99import useDocusaurusContext from '@docusaurus/useDocusaurusContext' ;
1010import { createPortal } from 'react-dom' ;
1111import translations from '@theme/SearchTranslations' ;
12- import { useEffect } from 'react' ;
1312import { useAskAI } from '@site/src/hooks/useAskAI'
1413import { shouldPreventSearchAction , handleSearchKeyboardConflict } from './utils/aiConflictHandler' ;
1514import { initializeSearchAnalytics , createEnhancedSearchClient } from './utils/searchAnalytics' ;
@@ -25,17 +24,17 @@ import { DocTypeSelector } from './docTypeSelector';
2524
2625function DocSearch ( { contextualSearch, externalUrlRegex, ...props } ) {
2726 const queryIDRef = useRef ( null ) ;
27+ const lastQueryRef = useRef ( '' ) ;
2828 const { siteMetadata, i18n : { currentLocale } } = useDocusaurusContext ( ) ;
2929 const processSearchResultUrl = useSearchResultUrlProcessor ( ) ;
3030 const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters ( ) ;
31- const { isAskAIOpen, currentMode } = useAskAI ( ) ;
31+ const { isAskAIOpen } = useAskAI ( ) ;
3232 const history = useHistory ( ) ;
3333 const searchButtonRef = useRef ( null ) ;
3434
35- // Doc type filtering state
3635 const [ selectedDocTypes , setSelectedDocTypes ] = useState ( null ) ;
36+ const searchParametersRef = useRef ( null ) ;
3737
38- // Use the modal management hook
3938 const {
4039 isOpen,
4140 initialQuery,
@@ -47,26 +46,63 @@ function DocSearch({ contextualSearch, externalUrlRegex, ...props }) {
4746 importDocSearchModalIfNeeded
4847 } = useDocSearchModal ( ) ;
4948
50- // Configure search parameters with doc_type filter
51- const searchParameters = createSearchParameters (
52- props ,
53- contextualSearch ,
54- contextualSearchFacetFilters ,
55- selectedDocTypes
56- ) ;
49+ // Update searchParameters ref instead of creating new object
50+ useEffect ( ( ) => {
51+ const newParams = createSearchParameters (
52+ props ,
53+ contextualSearch ,
54+ contextualSearchFacetFilters ,
55+ selectedDocTypes
56+ ) ;
57+
58+ if ( ! searchParametersRef . current ) {
59+ searchParametersRef . current = newParams ;
60+ } else {
61+ Object . keys ( newParams ) . forEach ( key => {
62+ searchParametersRef . current [ key ] = newParams [ key ] ;
63+ } ) ;
64+ }
65+ } , [ props , contextualSearch , contextualSearchFacetFilters , selectedDocTypes ] ) ;
66+
67+ // Initialize on mount
68+ if ( ! searchParametersRef . current ) {
69+ searchParametersRef . current = createSearchParameters (
70+ props ,
71+ contextualSearch ,
72+ contextualSearchFacetFilters ,
73+ selectedDocTypes
74+ ) ;
75+ }
76+
77+ // Track input changes to capture the query
78+ useEffect ( ( ) => {
79+ if ( ! isOpen ) return ;
80+
81+ const handleInput = ( e ) => {
82+ const input = e . target ;
83+ if ( input . classList . contains ( 'DocSearch-Input' ) ) {
84+ lastQueryRef . current = input . value ;
85+ }
86+ } ;
87+
88+ document . addEventListener ( 'input' , handleInput , true ) ;
89+ return ( ) => document . removeEventListener ( 'input' , handleInput , true ) ;
90+ } , [ isOpen ] ) ;
5791
5892 useEffect ( ( ) => {
5993 initializeSearchAnalytics ( props . appId , props . apiKey ) ;
6094 } , [ props . appId , props . apiKey ] ) ;
6195
62- // Create navigator for handling result clicks
6396 const navigator = useMemo (
6497 ( ) => createSearchNavigator ( history , externalUrlRegex ) ,
6598 [ history , externalUrlRegex ]
6699 ) ;
67100
68- // Transform search items with metadata
69101 const transformItems = useCallback ( ( items , state ) => {
102+ if ( state ?. query ) {
103+ lastQueryRef . current = state . query ;
104+ }
105+
70106 return transformSearchItems ( items , {
71107 transformItems : props . transformItems ,
72108 processSearchResultUrl,
@@ -75,36 +111,74 @@ function DocSearch({ contextualSearch, externalUrlRegex, ...props }) {
75111 } ) ;
76112 } , [ props . transformItems , processSearchResultUrl , currentLocale ] ) ;
77113
78- const handleDocTypeChange = useCallback ( ( docTypes ) => {
79- setSelectedDocTypes ( docTypes ) ;
80- } , [ ] ) ;
114+ const handleDocTypeChange = useCallback ( ( docTypes ) => {
115+ setSelectedDocTypes ( docTypes ) ;
116+
117+ // Re-trigger search with updated filters after state update completes
118+ setTimeout ( ( ) => {
119+ const input = document . querySelector ( '.DocSearch-Input' ) ;
120+ const query = lastQueryRef . current ;
121+
122+ if ( input && query ) {
123+ // Access React's internal value setter to bypass readonly property
124+ const nativeInputValueSetter = Object . getOwnPropertyDescriptor (
125+ window . HTMLInputElement . prototype ,
126+ 'value'
127+ ) . set ;
128+
129+ // Clear input to trigger change detection
130+ nativeInputValueSetter . call ( input , '' ) ;
131+ input . dispatchEvent ( new Event ( 'input' , { bubbles : true } ) ) ;
132+
133+ // Restore original query to execute search with new filters
134+ setTimeout ( ( ) => {
135+ nativeInputValueSetter . call ( input , query ) ;
136+ input . dispatchEvent ( new Event ( 'input' , { bubbles : true } ) ) ;
137+ input . focus ( ) ;
138+ } , 0 ) ;
139+ }
140+ } , 100 ) ;
141+ } , [ ] ) ;
81142
82143 const resultsFooterComponent = useMemo (
83- ( ) =>
84- // eslint-disable-next-line react/no-unstable-nested-components
85- ( footerProps ) =>
86- < SearchResultsFooter { ...footerProps } onClose = { onClose } /> ,
87- [ onClose ] ,
144+ ( ) => ( footerProps ) => < SearchResultsFooter { ...footerProps } onClose = { onClose } /> ,
145+ [ onClose ]
88146 ) ;
89147
90148 const transformSearchClient = useCallback ( ( searchClient ) => {
91- return createEnhancedSearchClient ( searchClient , siteMetadata . docusaurusVersion , queryIDRef ) ;
149+ const enhancedClient = createEnhancedSearchClient (
150+ searchClient ,
151+ siteMetadata . docusaurusVersion ,
152+ queryIDRef
153+ ) ;
154+
155+ const originalSearch = enhancedClient . search . bind ( enhancedClient ) ;
156+
157+ let debounceTimeout ;
158+ enhancedClient . search = ( ...args ) => {
159+ return new Promise ( ( resolve , reject ) => {
160+ clearTimeout ( debounceTimeout ) ;
161+ debounceTimeout = setTimeout ( ( ) => {
162+ originalSearch ( ...args )
163+ . then ( resolve )
164+ . catch ( reject ) ;
165+ } , 200 ) ;
166+ } ) ;
167+ } ;
168+
169+ return enhancedClient ;
92170 } , [ siteMetadata . docusaurusVersion ] ) ;
93171
94172 const handleOnOpen = useCallback ( ( ) => {
95- console . log ( 'handleOnOpen called' , { isAskAIOpen } ) ;
96-
97173 if ( shouldPreventSearchAction ( isAskAIOpen ) ) {
98- console . log ( 'handleOnOpen - preventing search modal' ) ;
99174 return ;
100175 }
101-
102176 onOpen ( ) ;
103177 } , [ isAskAIOpen , onOpen ] ) ;
104178
105179 const handleOnInput = useCallback ( ( event ) => {
106180 if ( shouldPreventSearchAction ( isAskAIOpen ) ) {
107- return ; // Prevent search input handling
181+ return ;
108182 }
109183 onInput ( event ) ;
110184 } , [ isAskAIOpen , onInput ] ) ;
@@ -118,68 +192,64 @@ function DocSearch({ contextualSearch, externalUrlRegex, ...props }) {
118192 } ) ;
119193
120194 return (
121- < >
122- < Head >
123- { /* This hints the browser that the website will load data from Algolia,
124- and allows it to preconnect to the DocSearch cluster. It makes the first
125- query faster, especially on mobile. */ }
126- < link
127- rel = "preconnect"
128- href = { `https://${ props . appId } -dsn.algolia.net` }
129- crossOrigin = "anonymous"
130- />
131- </ Head >
132-
133- < DocSearchButton
134- onTouchStart = { importDocSearchModalIfNeeded }
135- onFocus = { importDocSearchModalIfNeeded }
136- onMouseOver = { importDocSearchModalIfNeeded }
137- onClick = { onOpen }
138- ref = { searchButtonRef }
139- translations = { translations . button }
195+ < >
196+ < Head >
197+ < link
198+ rel = "preconnect"
199+ href = { `https://${ props . appId } -dsn.algolia.net` }
200+ crossOrigin = "anonymous"
140201 />
202+ </ Head >
141203
142- { isOpen &&
143- DocSearchModal &&
144- searchContainer &&
145- createPortal (
146- < >
147- < DocSearchModal
148- onClose = { onClose }
149- initialScrollY = { window . scrollY }
150- initialQuery = { initialQuery }
151- navigator = { navigator }
152- transformItems = { transformItems }
153- hitComponent = { SearchHit }
154- transformSearchClient = { transformSearchClient }
155- { ...( props . searchPagePath && {
156- resultsFooterComponent,
157- } ) }
158- { ...props }
159- insights = { true }
160- searchParameters = { searchParameters }
161- placeholder = { translations . placeholder }
162- translations = { translations . modal }
163- />
164-
165- { /* Selector positioned as overlay */ }
166- < div style = { {
167- position : 'fixed' ,
168- top : window . innerWidth < 768 ? '55px' : '120px' ,
169- right : window . innerWidth < 768 ? 'calc(50% - 185px)' : 'calc(50% - 255px)' ,
170- zIndex : 10000 ,
171- backgroundColor : 'var(--docsearch-modal-background)' ,
172- boxShadow : '0 2px 8px rgba(0,0,0,0.1)'
173- } } >
174- < DocTypeSelector
175- selectedDocTypes = { selectedDocTypes }
176- onSelectionChange = { handleDocTypeChange }
177- />
178- </ div >
179- </ > ,
180- searchContainer ,
181- ) }
182- </ >
204+ < DocSearchButton
205+ onTouchStart = { importDocSearchModalIfNeeded }
206+ onFocus = { importDocSearchModalIfNeeded }
207+ onMouseOver = { importDocSearchModalIfNeeded }
208+ onClick = { onOpen }
209+ ref = { searchButtonRef }
210+ translations = { translations . button }
211+ />
212+
213+ { isOpen &&
214+ DocSearchModal &&
215+ searchContainer &&
216+ createPortal (
217+ < >
218+ < DocSearchModal
219+ onClose = { onClose }
220+ initialScrollY = { window . scrollY }
221+ initialQuery = { initialQuery }
222+ navigator = { navigator }
223+ transformItems = { transformItems }
224+ hitComponent = { SearchHit }
225+ transformSearchClient = { transformSearchClient }
226+ { ...( props . searchPagePath && {
227+ resultsFooterComponent,
228+ } ) }
229+ { ...props }
230+ insights = { true }
231+ searchParameters = { searchParametersRef . current }
232+ placeholder = { translations . placeholder }
233+ translations = { translations . modal }
234+ />
235+
236+ < div style = { {
237+ position : 'fixed' ,
238+ top : window . innerWidth < 768 ? '55px' : '120px' ,
239+ right : window . innerWidth < 768 ? 'calc(50% - 185px)' : 'calc(50% - 255px)' ,
240+ zIndex : 10000 ,
241+ backgroundColor : 'var(--docsearch-modal-background)' ,
242+ boxShadow : '0 2px 8px rgba(0,0,0,0.1)'
243+ } } >
244+ < DocTypeSelector
245+ selectedDocTypes = { selectedDocTypes }
246+ onSelectionChange = { handleDocTypeChange }
247+ />
248+ </ div >
249+ </ > ,
250+ searchContainer
251+ ) }
252+ </ >
183253 ) ;
184254}
185255
@@ -192,12 +262,8 @@ export default function SearchBar() {
192262 handleSearchKeyboardConflict ( event , isAskAIOpen ) ;
193263 } ;
194264
195- // Add listener with capture phase to intercept before DocSearch
196265 document . addEventListener ( 'keydown' , handleKeyDown , true ) ;
197-
198- return ( ) => {
199- document . removeEventListener ( 'keydown' , handleKeyDown , true ) ;
200- } ;
266+ return ( ) => document . removeEventListener ( 'keydown' , handleKeyDown , true ) ;
201267 } , [ isAskAIOpen ] ) ;
202268
203269 return < DocSearch { ...siteConfig . themeConfig . algolia } /> ;
0 commit comments