@@ -9,10 +9,14 @@ import {
99} from 'lit' ;
1010import { repeat } from 'lit/directives/repeat.js' ;
1111import { customElement , property , state } from 'lit/decorators.js' ;
12- import { msg } from '@lit/localize' ;
1312import type { Aggregation , Bucket } from '@internetarchive/search-service' ;
1413import type { CollectionTitles } from '../../data-source/models' ;
15- import type { FacetOption , SelectedFacets } from '../../models' ;
14+ import type {
15+ FacetEventDetails ,
16+ FacetOption ,
17+ FacetState ,
18+ SelectedFacets ,
19+ } from '../../models' ;
1620import { updateSelectedFacetBucket } from '../../utils/facet-utils' ;
1721import { SmartQueryHeuristicGroup } from './smart-facet-heuristics' ;
1822import type { SmartFacetDropdown } from './smart-facet-dropdown' ;
@@ -47,8 +51,12 @@ export class SmartFacetBar extends LitElement {
4751 @property ( { type : Object } )
4852 collectionTitles ?: CollectionTitles ;
4953
54+ @property ( { type : Boolean } ) filterToggleShown = false ;
55+
5056 @property ( { type : Boolean } ) filterToggleActive = false ;
5157
58+ @property ( { type : String } ) label ?: string ;
59+
5260 @state ( ) private heuristicRecs : SmartFacet [ ] = [ ] ;
5361
5462 @state ( ) private smartFacets : SmartFacet [ ] [ ] = [ ] ;
@@ -62,9 +70,13 @@ export class SmartFacetBar extends LitElement {
6270 render ( ) {
6371 if ( ! this . query ) return nothing ;
6472
73+ const shouldShowLabel = ! ! this . label && this . smartFacets . length > 0 ;
6574 return html `
6675 <div id= "smart-facets-container" >
6776 ${ this . filtersToggleTemplate }
77+ ${ shouldShowLabel
78+ ? html `<p id= "filters-label" > ${ this . label } </ p> `
79+ : nothing }
6880 ${ repeat (
6981 this . smartFacets ,
7082 f =>
@@ -106,6 +118,15 @@ export class SmartFacetBar extends LitElement {
106118 this . updateSmartFacets ( ) ;
107119 }
108120
121+ deselectAll ( ) : void {
122+ for ( const sf of this . smartFacets ) {
123+ for ( const facet of sf ) {
124+ facet . selected = false ;
125+ }
126+ }
127+ this . requestUpdate ( ) ;
128+ }
129+
109130 private async updateSmartFacets ( ) : Promise < void > {
110131 log ( 'updating smart facets' ) ;
111132 if ( this . query ) {
@@ -148,13 +169,15 @@ export class SmartFacetBar extends LitElement {
148169 .facetInfo = ${ facets }
149170 .labelPrefix = ${ fieldPrefixes [ facets [ 0 ] . facets [ 0 ] . facetType ] }
150171 .activeFacetRef = ${ facets [ 0 ] . facets [ 0 ] }
151- @facetClick = ${ this . facetDropdownClicked }
152- @dropdownClick = ${ this . onDropdownClick }
172+ @facetClick = ${ this . dropdownOptionClicked }
173+ @dropdownClick = ${ this . dropdownClicked }
153174 > </ smart- facet- dropdown>
154175 ` ;
155176 }
156177
157- private get filtersToggleTemplate ( ) : TemplateResult {
178+ private get filtersToggleTemplate ( ) : TemplateResult | typeof nothing {
179+ if ( ! this . filterToggleShown ) return nothing ;
180+
158181 return html `
159182 <butto n
160183 id= "filters-toggle"
@@ -255,15 +278,48 @@ export class SmartFacetBar extends LitElement {
255278 } as SmartFacet ;
256279 }
257280
258- private facetClicked ( e : CustomEvent < SmartFacetEvent > ) : void {
259- if ( ! e . detail . smartFacet . selected ) {
281+ /**
282+ * Toggles the state of the given smart facet, and updates the selected facets accordingly.
283+ */
284+ private toggleSmartFacet (
285+ facet : SmartFacet ,
286+ details : FacetEventDetails [ ] ,
287+ ) : void {
288+ let newState : FacetState ;
289+ if ( facet . selected ) {
290+ // When deselected, leave the smart facet where it is
291+ newState = 'none' ;
292+ this . smartFacets = this . smartFacets . map ( f => {
293+ if ( f [ 0 ] === facet ) return [ { ...facet , selected : false } ] ;
294+ return f ;
295+ } ) ;
296+ } else {
297+ // When selected, move the toggled smart facet to the front of the list
298+ newState = 'selected' ;
260299 this . smartFacets = [
261- [ { ...e . detail . smartFacet , selected : true } ] ,
262- ...this . smartFacets . filter ( f => f [ 0 ] !== e . detail . smartFacet ) ,
300+ [ { ...facet , selected : true } ] ,
301+ ...this . smartFacets . filter ( f => f [ 0 ] !== facet ) ,
263302 ] ;
264303 }
265304
266- for ( const facet of e . detail . details ) {
305+ this . updateSelectedFacets (
306+ details . map ( facet => ( {
307+ ...facet ,
308+ bucket : {
309+ ...facet . bucket ,
310+ state : newState ,
311+ } ,
312+ } ) ) ,
313+ ) ;
314+ }
315+
316+ /**
317+ * Updates the selected facet buckets for each of the given facets,
318+ * and emits a `facetsChanged` event to notify parent components of
319+ * the new state.
320+ */
321+ private updateSelectedFacets ( facets : FacetEventDetails [ ] ) : void {
322+ for ( const facet of facets ) {
267323 this . selectedFacets = updateSelectedFacetBucket (
268324 this . selectedFacets ,
269325 facet . facetType ,
@@ -278,34 +334,39 @@ export class SmartFacetBar extends LitElement {
278334 this . dispatchEvent ( event ) ;
279335 }
280336
281- private facetDropdownClicked ( e : CustomEvent < SmartFacetEvent > ) : void {
282- if (
283- this . smartFacets . find ( sf => smartFacetEquals ( sf [ 0 ] , e . detail . smartFacet ) )
284- ) {
337+ /**
338+ * Handler for when a smart facet button is clicked
339+ */
340+ private facetClicked ( e : CustomEvent < SmartFacetEvent > ) : void {
341+ this . toggleSmartFacet ( e . detail . smartFacet , e . detail . details ) ;
342+ }
343+
344+ /**
345+ * Handler for when an option in a smart facet dropdown menu is selected
346+ */
347+ private dropdownOptionClicked ( e : CustomEvent < SmartFacetEvent > ) : void {
348+ const existingFacet = this . smartFacets . find (
349+ sf => sf . length === 1 && smartFacetEquals ( sf [ 0 ] , e . detail . smartFacet ) ,
350+ ) ;
351+ if ( existingFacet ) {
352+ // The facet already exists outside the dropdown, so just select it there
353+ this . toggleSmartFacet ( existingFacet [ 0 ] , e . detail . details ) ;
285354 return ;
286355 }
287356
357+ // Otherwise, prepend a new smart facet for the selected option
288358 this . smartFacets = [
289359 [ { ...e . detail . smartFacet , selected : true } ] ,
290360 ...this . smartFacets ,
291361 ] ;
292362
293- for ( const facet of e . detail . details ) {
294- this . selectedFacets = updateSelectedFacetBucket (
295- this . selectedFacets ,
296- facet . facetType ,
297- facet . bucket ,
298- true ,
299- ) ;
300- }
301-
302- const event = new CustomEvent < SelectedFacets > ( 'facetsChanged' , {
303- detail : this . selectedFacets ,
304- } ) ;
305- this . dispatchEvent ( event ) ;
363+ this . updateSelectedFacets ( e . detail . details ) ;
306364 }
307365
308- private onDropdownClick ( e : CustomEvent < SmartFacetDropdown > ) : void {
366+ /**
367+ * Handler for when any dropdown is clicked (whether button, caret, or menu item)
368+ */
369+ private dropdownClicked ( e : CustomEvent < SmartFacetDropdown > ) : void {
309370 log ( 'smart bar: onDropdownClick' , e . detail ) ;
310371 this . shadowRoot
311372 ?. querySelectorAll ( 'smart-facet-dropdown' )
@@ -330,9 +391,11 @@ export class SmartFacetBar extends LitElement {
330391 # smart-facets-container {
331392 display : flex;
332393 align-items : center;
333- flex-wrap : wrap;
334394 gap : 5px 10px ;
335395 padding : 10px 0 ;
396+ white-space : nowrap;
397+ overflow : scroll hidden;
398+ scrollbar-width : none;
336399 }
337400
338401 # filters-toggle {
@@ -363,6 +426,12 @@ export class SmartFacetBar extends LitElement {
363426 # filters-toggle .active > svg {
364427 filter : invert (1 );
365428 }
429+
430+ # filters-label {
431+ font-size : 1.4rem ;
432+ font-weight : var (--smartFacetLabelFontWeight , normal);
433+ margin : 0 -5px 0 0 ;
434+ }
366435 ` ;
367436 }
368437}
0 commit comments