1
1
/** @jsx h */
2
2
3
- import { h , Component } from 'preact' ;
4
- import PropTypes from 'prop-types' ;
3
+ import { h , createRef , Component } from 'preact' ;
5
4
import cx from 'classnames' ;
6
5
import { isSpecialClick , isEqual } from '../../lib/utils' ;
6
+ import { PreparedTemplateProps } from '../../lib/utils/prepareTemplateProps' ;
7
7
import Template from '../Template/Template' ;
8
8
import RefinementListItem from './RefinementListItem' ;
9
9
import SearchBox from '../SearchBox/SearchBox' ;
10
+ import { RefinementListItem as TRefinementListItem } from '../../connectors/refinement-list/connectRefinementList' ;
11
+ import { SearchBoxTemplates } from '../../widgets/search-box/types' ;
12
+ import { CreateURL , Templates } from '../../types' ;
10
13
11
- class RefinementList extends Component {
12
- constructor ( props ) {
14
+ type CSSClasses = {
15
+ searchable ?: Record < string , string > ;
16
+ [ key : string ] : any ;
17
+ } ;
18
+
19
+ export type RefinementListProps <
20
+ TTemplates extends Templates ,
21
+ TCSSClasses extends CSSClasses
22
+ > = {
23
+ createURL : CreateURL < string > ;
24
+ cssClasses : TCSSClasses ;
25
+ depth ?: number ;
26
+ facetValues ?: TRefinementListItem [ ] ;
27
+ attribute ?: string ;
28
+ templateProps ?: PreparedTemplateProps < TTemplates > ;
29
+ searchBoxTemplateProps ?: PreparedTemplateProps < SearchBoxTemplates > ;
30
+ toggleRefinement : ( value : string ) => void ;
31
+ searchFacetValues ?: ( query : string ) => void ;
32
+ searchPlaceholder ?: string ;
33
+ isFromSearch ?: boolean ;
34
+ showMore ?: boolean ;
35
+ toggleShowMore ?: ( ) => void ;
36
+ isShowingMore ?: boolean ;
37
+ hasExhaustiveItems ?: boolean ;
38
+ canToggleShowMore ?: boolean ;
39
+ searchIsAlwaysActive ?: boolean ;
40
+ className ?: string ;
41
+ children ?: h . JSX . Element ;
42
+ } ;
43
+
44
+ const defaultProps = {
45
+ cssClasses : { } ,
46
+ depth : 0 ,
47
+ } ;
48
+
49
+ type RefinementListPropsWithDefaultProps <
50
+ TTemplates extends Templates ,
51
+ TCSSClasses extends CSSClasses
52
+ > = RefinementListProps < TTemplates , TCSSClasses > &
53
+ Readonly < typeof defaultProps > ;
54
+
55
+ type RefinementListItemTemplateData <
56
+ TTemplates extends Templates ,
57
+ TCSSClasses extends CSSClasses
58
+ > = TRefinementListItem & {
59
+ url : string ;
60
+ } & Pick <
61
+ RefinementListProps < TTemplates , TCSSClasses > ,
62
+ 'attribute' | 'cssClasses' | 'isFromSearch'
63
+ > ;
64
+
65
+ class RefinementList <
66
+ TTemplates extends Templates ,
67
+ TCSSClasses extends CSSClasses
68
+ > extends Component <
69
+ RefinementListPropsWithDefaultProps < TTemplates , TCSSClasses >
70
+ > {
71
+ public static defaultProps = defaultProps ;
72
+
73
+ private searchBox = createRef < SearchBox > ( ) ;
74
+
75
+ public constructor (
76
+ props : RefinementListPropsWithDefaultProps < TTemplates , TCSSClasses >
77
+ ) {
13
78
super ( props ) ;
14
79
this . handleItemClick = this . handleItemClick . bind ( this ) ;
15
80
}
16
81
17
- shouldComponentUpdate ( nextProps , nextState ) {
18
- const isStateDifferent = this . state !== nextState ;
82
+ public shouldComponentUpdate (
83
+ nextProps : RefinementListPropsWithDefaultProps < TTemplates , TCSSClasses >
84
+ ) {
19
85
const areFacetValuesDifferent = ! isEqual (
20
86
this . props . facetValues ,
21
87
nextProps . facetValues
22
88
) ;
23
89
24
- return isStateDifferent || areFacetValuesDifferent ;
90
+ return areFacetValuesDifferent ;
25
91
}
26
92
27
- refine ( facetValueToRefine , isRefined ) {
28
- this . props . toggleRefinement ( facetValueToRefine , isRefined ) ;
93
+ private refine ( facetValueToRefine : string ) {
94
+ this . props . toggleRefinement ( facetValueToRefine ) ;
29
95
}
30
96
31
- _generateFacetItem ( facetValue ) {
97
+ private _generateFacetItem ( facetValue : TRefinementListItem ) {
32
98
let subItems ;
33
- const hasChildren = facetValue . data && facetValue . data . length > 0 ;
34
- if ( hasChildren ) {
99
+ if ( facetValue . data && facetValue . data . length > 0 ) {
35
100
const { root, ...cssClasses } = this . props . cssClasses ;
36
101
subItems = (
37
102
< RefinementList
@@ -46,7 +111,10 @@ class RefinementList extends Component {
46
111
}
47
112
48
113
const url = this . props . createURL ( facetValue . value ) ;
49
- const templateData = {
114
+ const templateData : RefinementListItemTemplateData <
115
+ TTemplates ,
116
+ TCSSClasses
117
+ > = {
50
118
...facetValue ,
51
119
url,
52
120
attribute : this . props . attribute ,
@@ -63,18 +131,21 @@ class RefinementList extends Component {
63
131
key += `/${ facetValue . count } ` ;
64
132
}
65
133
134
+ const refinementListItemClassName = cx ( this . props . cssClasses . item , {
135
+ [ this . props . cssClasses . selectedItem ] : facetValue . isRefined ,
136
+ [ this . props . cssClasses . disabledItem ] : ! facetValue . count ,
137
+ [ this . props . cssClasses . parentItem ] :
138
+ facetValue . data && facetValue . data . length > 0 ,
139
+ } ) ;
140
+
66
141
return (
67
142
< RefinementListItem
68
143
templateKey = "item"
69
144
key = { key }
70
145
facetValueToRefine = { facetValue . value }
71
146
handleClick = { this . handleItemClick }
72
147
isRefined = { facetValue . isRefined }
73
- className = { cx ( this . props . cssClasses . item , {
74
- [ this . props . cssClasses . selectedItem ] : facetValue . isRefined ,
75
- [ this . props . cssClasses . disabledItem ] : ! facetValue . count ,
76
- [ this . props . cssClasses . parentItem ] : hasChildren ,
77
- } ) }
148
+ className = { refinementListItemClassName }
78
149
subItems = { subItems }
79
150
templateData = { templateData }
80
151
templateProps = { this . props . templateProps }
@@ -97,13 +168,28 @@ class RefinementList extends Component {
97
168
//
98
169
// Finally, we always stop propagation of the event to avoid multiple levels RefinementLists to fail: click
99
170
// on child would click on parent also
100
- handleItemClick ( { facetValueToRefine, originalEvent, isRefined } ) {
171
+ private handleItemClick ( {
172
+ facetValueToRefine,
173
+ isRefined,
174
+ originalEvent,
175
+ } : {
176
+ facetValueToRefine : string ;
177
+ isRefined : boolean ;
178
+ originalEvent : MouseEvent ;
179
+ } ) {
101
180
if ( isSpecialClick ( originalEvent ) ) {
102
181
// do not alter the default browser behavior
103
182
// if one special key is down
104
183
return ;
105
184
}
106
185
186
+ if (
187
+ ! ( originalEvent . target instanceof HTMLElement ) ||
188
+ ! ( originalEvent . target . parentNode instanceof HTMLElement )
189
+ ) {
190
+ return ;
191
+ }
192
+
107
193
if (
108
194
isRefined &&
109
195
originalEvent . target . parentNode . querySelector (
@@ -115,7 +201,7 @@ class RefinementList extends Component {
115
201
}
116
202
117
203
if ( originalEvent . target . tagName === 'INPUT' ) {
118
- this . refine ( facetValueToRefine , isRefined ) ;
204
+ this . refine ( facetValueToRefine ) ;
119
205
return ;
120
206
}
121
207
@@ -130,39 +216,35 @@ class RefinementList extends Component {
130
216
return ;
131
217
}
132
218
133
- if ( parent . tagName === 'A' && parent . href ) {
219
+ if ( parent . tagName === 'A' && ( parent as HTMLAnchorElement ) . href ) {
134
220
originalEvent . preventDefault ( ) ;
135
221
}
136
222
137
- parent = parent . parentNode ;
223
+ parent = parent . parentNode as HTMLElement ;
138
224
}
139
225
140
226
originalEvent . stopPropagation ( ) ;
141
227
142
- this . refine ( facetValueToRefine , isRefined ) ;
228
+ this . refine ( facetValueToRefine ) ;
143
229
}
144
230
145
- componentWillReceiveProps ( nextProps ) {
146
- if ( this . searchBox && ! nextProps . isFromSearch ) {
147
- this . searchBox . resetInput ( ) ;
231
+ public componentWillReceiveProps (
232
+ nextProps : RefinementListPropsWithDefaultProps < TTemplates , TCSSClasses >
233
+ ) {
234
+ if ( this . searchBox . current && ! nextProps . isFromSearch ) {
235
+ this . searchBox . current . resetInput ( ) ;
148
236
}
149
237
}
150
238
151
- refineFirstValue ( ) {
152
- const firstValue = this . props . facetValues [ 0 ] ;
239
+ private refineFirstValue ( ) {
240
+ const firstValue = this . props . facetValues && this . props . facetValues [ 0 ] ;
153
241
if ( firstValue ) {
154
242
const actualValue = firstValue . value ;
155
243
this . props . toggleRefinement ( actualValue ) ;
156
244
}
157
245
}
158
246
159
- render ( ) {
160
- // Adding `-lvl0` classes
161
- const cssClassList = cx ( this . props . cssClasses . list , {
162
- [ `${ this . props . cssClasses . depth } ${ this . props . depth } ` ] : this . props
163
- . cssClasses . depth ,
164
- } ) ;
165
-
247
+ public render ( ) {
166
248
const showMoreButtonClassName = cx ( this . props . cssClasses . showMore , {
167
249
[ this . props . cssClasses . disabledShowMore ] : ! (
168
250
this . props . showMore === true && this . props . canToggleShowMore
@@ -191,18 +273,22 @@ class RefinementList extends Component {
191
273
192
274
const templates = this . props . searchBoxTemplateProps
193
275
? this . props . searchBoxTemplateProps . templates
194
- : { } ;
276
+ : undefined ;
195
277
196
278
const searchBox = this . props . searchFacetValues && (
197
279
< div className = { this . props . cssClasses . searchBox } >
198
280
< SearchBox
199
- ref = { searchBoxRef => ( this . searchBox = searchBoxRef ) }
281
+ ref = { this . searchBox }
200
282
placeholder = { this . props . searchPlaceholder }
201
283
disabled = { shouldDisableSearchBox }
202
- cssClasses = { this . props . cssClasses . searchable }
284
+ cssClasses = { this . props . cssClasses . searchable ! }
203
285
templates = { templates }
204
- onChange = { event => this . props . searchFacetValues ( event . target . value ) }
205
- onReset = { ( ) => this . props . searchFacetValues ( '' ) }
286
+ onChange = { ( event : Event ) =>
287
+ this . props . searchFacetValues ! (
288
+ ( event . target as HTMLInputElement ) . value
289
+ )
290
+ }
291
+ onReset = { ( ) => this . props . searchFacetValues ! ( '' ) }
206
292
onSubmit = { ( ) => this . refineFirstValue ( ) }
207
293
// This sets the search box to a controlled state because
208
294
// we don't rely on the `refine` prop but on `onChange`.
@@ -213,32 +299,32 @@ class RefinementList extends Component {
213
299
214
300
const facetValues = this . props . facetValues &&
215
301
this . props . facetValues . length > 0 && (
216
- < ul className = { cssClassList } >
302
+ < ul className = { this . props . cssClasses . list } >
217
303
{ this . props . facetValues . map ( this . _generateFacetItem , this ) }
218
304
</ ul >
219
305
) ;
220
306
221
307
const noResults = this . props . searchFacetValues &&
222
308
this . props . isFromSearch &&
223
- this . props . facetValues . length === 0 && (
309
+ ( ! this . props . facetValues || this . props . facetValues . length === 0 ) && (
224
310
< Template
225
311
{ ...this . props . templateProps }
226
312
templateKey = "searchableNoResults"
227
313
rootProps = { { className : this . props . cssClasses . noResults } }
228
314
/>
229
315
) ;
230
316
317
+ const rootClassName = cx (
318
+ this . props . cssClasses . root ,
319
+ {
320
+ [ this . props . cssClasses . noRefinementRoot ] :
321
+ ! this . props . facetValues || this . props . facetValues . length === 0 ,
322
+ } ,
323
+ this . props . className
324
+ ) ;
325
+
231
326
return (
232
- < div
233
- className = { cx (
234
- this . props . cssClasses . root ,
235
- {
236
- [ this . props . cssClasses . noRefinementRoot ] :
237
- ! this . props . facetValues || this . props . facetValues . length === 0 ,
238
- } ,
239
- this . props . className
240
- ) }
241
- >
327
+ < div className = { rootClassName } >
242
328
{ this . props . children }
243
329
{ searchBox }
244
330
{ facetValues }
@@ -249,61 +335,4 @@ class RefinementList extends Component {
249
335
}
250
336
}
251
337
252
- RefinementList . propTypes = {
253
- Template : PropTypes . func ,
254
- createURL : PropTypes . func ,
255
- cssClasses : PropTypes . shape ( {
256
- depth : PropTypes . string ,
257
- root : PropTypes . string ,
258
- noRefinementRoot : PropTypes . string ,
259
- list : PropTypes . string ,
260
- item : PropTypes . string ,
261
- selectedItem : PropTypes . string ,
262
- parentItem : PropTypes . string ,
263
- childList : PropTypes . string ,
264
- searchBox : PropTypes . string ,
265
- label : PropTypes . string ,
266
- checkbox : PropTypes . string ,
267
- labelText : PropTypes . string ,
268
- count : PropTypes . string ,
269
- noResults : PropTypes . string ,
270
- showMore : PropTypes . string ,
271
- disabledShowMore : PropTypes . string ,
272
- disabledItem : PropTypes . string ,
273
- searchable : PropTypes . shape ( {
274
- root : PropTypes . string ,
275
- form : PropTypes . string ,
276
- input : PropTypes . string ,
277
- submit : PropTypes . string ,
278
- submitIcon : PropTypes . string ,
279
- reset : PropTypes . string ,
280
- resetIcon : PropTypes . string ,
281
- loadingIndicator : PropTypes . string ,
282
- loadingIcon : PropTypes . string ,
283
- } ) ,
284
- } ) . isRequired ,
285
- depth : PropTypes . number ,
286
- facetValues : PropTypes . array ,
287
- attribute : PropTypes . string ,
288
- templateProps : PropTypes . object . isRequired ,
289
- searchBoxTemplateProps : PropTypes . object ,
290
- toggleRefinement : PropTypes . func . isRequired ,
291
- searchFacetValues : PropTypes . func ,
292
- searchPlaceholder : PropTypes . string ,
293
- isFromSearch : PropTypes . bool ,
294
- showMore : PropTypes . bool ,
295
- toggleShowMore : PropTypes . func ,
296
- isShowingMore : PropTypes . bool ,
297
- hasExhaustiveItems : PropTypes . bool ,
298
- canToggleShowMore : PropTypes . bool ,
299
- searchIsAlwaysActive : PropTypes . bool ,
300
- className : PropTypes . string ,
301
- children : PropTypes . element ,
302
- } ;
303
-
304
- RefinementList . defaultProps = {
305
- cssClasses : { } ,
306
- depth : 0 ,
307
- } ;
308
-
309
338
export default RefinementList ;
0 commit comments