@internetarchive/collection-browser 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/src/app-root.js +1 -1
  2. package/dist/src/app-root.js.map +1 -1
  3. package/dist/src/collection-browser.d.ts +40 -3
  4. package/dist/src/collection-browser.js +214 -58
  5. package/dist/src/collection-browser.js.map +1 -1
  6. package/dist/src/collection-facets/more-facets-content.d.ts +3 -2
  7. package/dist/src/collection-facets/more-facets-content.js +6 -2
  8. package/dist/src/collection-facets/more-facets-content.js.map +1 -1
  9. package/dist/src/collection-facets.d.ts +3 -2
  10. package/dist/src/collection-facets.js +6 -2
  11. package/dist/src/collection-facets.js.map +1 -1
  12. package/dist/src/models.d.ts +9 -0
  13. package/dist/src/models.js +8 -0
  14. package/dist/src/models.js.map +1 -1
  15. package/dist/src/restoration-state-handler.d.ts +0 -1
  16. package/dist/src/restoration-state-handler.js +4 -4
  17. package/dist/src/restoration-state-handler.js.map +1 -1
  18. package/dist/src/sort-filter-bar/alpha-bar.d.ts +3 -0
  19. package/dist/src/sort-filter-bar/alpha-bar.js +32 -13
  20. package/dist/src/sort-filter-bar/alpha-bar.js.map +1 -1
  21. package/dist/src/sort-filter-bar/sort-filter-bar.d.ts +2 -1
  22. package/dist/src/sort-filter-bar/sort-filter-bar.js +7 -0
  23. package/dist/src/sort-filter-bar/sort-filter-bar.js.map +1 -1
  24. package/dist/src/tiles/image-block.js +0 -1
  25. package/dist/src/tiles/image-block.js.map +1 -1
  26. package/dist/test/collection-browser.test.js +81 -9
  27. package/dist/test/collection-browser.test.js.map +1 -1
  28. package/dist/test/collection-facets/more-facets-content.test.js +2 -2
  29. package/dist/test/collection-facets/more-facets-content.test.js.map +1 -1
  30. package/dist/test/mocks/mock-search-responses.d.ts +2 -0
  31. package/dist/test/mocks/mock-search-responses.js +70 -0
  32. package/dist/test/mocks/mock-search-responses.js.map +1 -1
  33. package/dist/test/mocks/mock-search-service.js +5 -1
  34. package/dist/test/mocks/mock-search-service.js.map +1 -1
  35. package/dist/test/restoration-state-handler.test.js +0 -1
  36. package/dist/test/restoration-state-handler.test.js.map +1 -1
  37. package/dist/test/sort-filter-bar/alpha-bar.test.d.ts +1 -0
  38. package/dist/test/sort-filter-bar/alpha-bar.test.js +44 -0
  39. package/dist/test/sort-filter-bar/alpha-bar.test.js.map +1 -0
  40. package/dist/test/sort-filter-bar/sort-filter-bar.test.js +32 -0
  41. package/dist/test/sort-filter-bar/sort-filter-bar.test.js.map +1 -1
  42. package/package.json +3 -3
  43. package/src/app-root.ts +1 -1
  44. package/src/collection-browser.ts +273 -57
  45. package/src/collection-facets/more-facets-content.ts +6 -2
  46. package/src/collection-facets.ts +6 -2
  47. package/src/models.ts +15 -0
  48. package/src/restoration-state-handler.ts +7 -5
  49. package/src/sort-filter-bar/alpha-bar.ts +26 -9
  50. package/src/sort-filter-bar/sort-filter-bar.ts +9 -0
  51. package/src/tiles/image-block.ts +0 -1
  52. package/test/collection-browser.test.ts +90 -10
  53. package/test/collection-facets/more-facets-content.test.ts +2 -2
  54. package/test/mocks/mock-search-responses.ts +78 -0
  55. package/test/mocks/mock-search-service.ts +6 -0
  56. package/test/restoration-state-handler.test.ts +0 -3
  57. package/test/sort-filter-bar/alpha-bar.test.ts +52 -0
  58. package/test/sort-filter-bar/sort-filter-bar.test.ts +44 -0
@@ -3,7 +3,7 @@ import { __decorate } from "tslib";
3
3
  import { html, css, LitElement, nothing, } from 'lit';
4
4
  import { customElement, property, query, state } from 'lit/decorators.js';
5
5
  import { ifDefined } from 'lit/directives/if-defined.js';
6
- import { SearchType, } from '@internetarchive/search-service';
6
+ import { FilterConstraint, FilterMapBuilder, SearchType, } from '@internetarchive/search-service';
7
7
  import '@internetarchive/infinite-scroller';
8
8
  import './tiles/tile-dispatcher';
9
9
  import './tiles/collection-browser-loading-tile';
@@ -11,7 +11,7 @@ import './sort-filter-bar/sort-filter-bar';
11
11
  import './collection-facets';
12
12
  import './circular-activity-indicator';
13
13
  import './sort-filter-bar/sort-filter-bar';
14
- import { SortField, SortFieldToMetadataField, defaultSelectedFacets, } from './models';
14
+ import { SortField, SortFieldToMetadataField, defaultSelectedFacets, prefixFilterAggregationKeys, } from './models';
15
15
  import { RestorationStateHandler, } from './restoration-state-handler';
16
16
  import chevronIcon from './assets/img/icons/chevron';
17
17
  import { LanguageCodeHandler } from './language-code-handler/language-code-handler';
@@ -59,6 +59,7 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
59
59
  this.mobileView = false;
60
60
  this.mobileFacetsVisible = false;
61
61
  this.placeholderType = null;
62
+ this.prefixFilterCountMap = {};
62
63
  this.languageCodeHandler = new LanguageCodeHandler();
63
64
  /**
64
65
  * When we're animated scrolling to the page, we don't want to fetch
@@ -183,6 +184,10 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
183
184
  `;
184
185
  }
185
186
  get collectionBrowserTemplate() {
187
+ var _a;
188
+ const shouldShowSearching = this.searchResultsLoading || this.totalResults === undefined;
189
+ const resultsCount = (_a = this.totalResults) === null || _a === void 0 ? void 0 : _a.toLocaleString();
190
+ const resultsLabel = this.totalResults === 1 ? 'Result' : 'Results';
186
191
  return html `<div
187
192
  id="left-column"
188
193
  class="column${this.isResizeToMobile ? ' preload' : ''}"
@@ -191,12 +196,10 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
191
196
  ${this.mobileView ? this.mobileFacetsTemplate : nothing}
192
197
  <div id="results-total">
193
198
  <span id="big-results-count">
194
- ${this.totalResults !== undefined
195
- ? this.totalResults.toLocaleString()
196
- : '-'}
199
+ ${shouldShowSearching ? html `Searching&hellip;` : resultsCount}
197
200
  </span>
198
201
  <span id="big-results-label">
199
- ${this.totalResults === 1 ? 'Result' : 'Results'}
202
+ ${shouldShowSearching ? nothing : resultsLabel}
200
203
  </span>
201
204
  </div>
202
205
  </div>
@@ -234,6 +237,7 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
234
237
  .displayMode=${this.displayMode}
235
238
  .selectedTitleFilter=${this.selectedTitleFilter}
236
239
  .selectedCreatorFilter=${this.selectedCreatorFilter}
240
+ .prefixFilterCountMap=${this.prefixFilterCountMap}
237
241
  .resizeObserver=${this.resizeObserver}
238
242
  @sortChanged=${this.userChangedSort}
239
243
  @displayModeChanged=${this.displayModeChanged}
@@ -271,6 +275,8 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
271
275
  if (!sortField)
272
276
  return;
273
277
  this.sortParam = { field: sortField, direction: this.sortDirection };
278
+ // Lazy-load the alphabet counts for title/creator sort bar as needed
279
+ this.updatePrefixFiltersForCurrentSort();
274
280
  }
275
281
  displayModeChanged(e) {
276
282
  var _a;
@@ -375,7 +381,8 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
375
381
  .collectionNameCache=${this.collectionNameCache}
376
382
  .languageCodeHandler=${this.languageCodeHandler}
377
383
  .showHistogramDatePicker=${this.showHistogramDatePicker}
378
- .fullQuery=${this.fullQuery}
384
+ .query=${this.filteredQuery}
385
+ .filterMap=${this.filterMap}
379
386
  .modalManager=${this.modalManager}
380
387
  ?collapsableFacets=${this.mobileView}
381
388
  ?facetsLoading=${this.facetDataLoading}
@@ -407,33 +414,21 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
407
414
  </div>
408
415
  `;
409
416
  }
410
- get queryDebuggingTemplate() {
411
- var _a, _b;
412
- return html `
413
- <div>
414
- <ul>
415
- <li>Base Query: ${this.baseQuery}</li>
416
- <li>Facet Query: ${this.facetQuery}</li>
417
- <li>Sort Filter Query: ${this.sortFilterQueries}</li>
418
- <li>Date Range Query: ${this.dateRangeQueryClause}</li>
419
- <li>Sort: ${(_a = this.sortParam) === null || _a === void 0 ? void 0 : _a.field} ${(_b = this.sortParam) === null || _b === void 0 ? void 0 : _b.direction}</li>
420
- <li>Full Query: ${this.fullQuery}</li>
421
- </ul>
422
- </div>
423
- `;
424
- }
425
417
  histogramDateRangeUpdated(e) {
426
418
  var _a;
427
419
  const { minDate, maxDate } = e.detail;
428
420
  [this.minSelectedDate, this.maxSelectedDate] = [minDate, maxDate];
429
- this.dateRangeQueryClause = `year:[${minDate} TO ${maxDate}]`;
430
- if (this.dateRangeQueryClause) {
431
- (_a = this.analyticsHandler) === null || _a === void 0 ? void 0 : _a.sendEvent({
432
- category: this.searchContext,
433
- action: analyticsActions.histogramChanged,
434
- label: this.dateRangeQueryClause,
435
- });
421
+ (_a = this.analyticsHandler) === null || _a === void 0 ? void 0 : _a.sendEvent({
422
+ category: this.searchContext,
423
+ action: analyticsActions.histogramChanged,
424
+ label: this.dateRangeQueryClause,
425
+ });
426
+ }
427
+ get dateRangeQueryClause() {
428
+ if (!this.minSelectedDate || !this.maxSelectedDate) {
429
+ return undefined;
436
430
  }
431
+ return `year:[${this.minSelectedDate} TO ${this.maxSelectedDate}]`;
437
432
  }
438
433
  firstUpdated() {
439
434
  this.setupStateRestorationObserver();
@@ -459,12 +454,19 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
459
454
  if (changed.has('baseQuery') ||
460
455
  changed.has('titleQuery') ||
461
456
  changed.has('creatorQuery') ||
462
- changed.has('dateRangeQueryClause') ||
457
+ changed.has('minSelectedDate') ||
458
+ changed.has('maxSelectedDate') ||
463
459
  changed.has('sortParam') ||
464
460
  changed.has('selectedFacets') ||
465
461
  changed.has('searchService')) {
466
462
  this.handleQueryChange();
467
463
  }
464
+ if (changed.has('baseQuery') ||
465
+ changed.has('minSelectedDate') ||
466
+ changed.has('maxSelectedDate') ||
467
+ changed.has('selectedFacets')) {
468
+ this.refreshLetterCounts();
469
+ }
468
470
  if (changed.has('selectedSort') || changed.has('sortDirection')) {
469
471
  const prevSortDirection = changed.get('sortDirection');
470
472
  this.sendSortByAnalytics(prevSortDirection);
@@ -566,6 +568,9 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
566
568
  return;
567
569
  this.previousQueryKey = this.pageFetchQueryKey;
568
570
  this.dataSource = {};
571
+ this.totalResults = undefined;
572
+ this.aggregations = undefined;
573
+ this.fullYearsHistogramAggregation = undefined;
569
574
  this.pageFetchesInProgress = {};
570
575
  this.endOfDataReached = false;
571
576
  this.pagesToRender = this.initialPageNumber;
@@ -614,7 +619,6 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
614
619
  this.baseQuery = restorationState.baseQuery;
615
620
  this.titleQuery = restorationState.titleQuery;
616
621
  this.creatorQuery = restorationState.creatorQuery;
617
- this.dateRangeQueryClause = restorationState.dateRangeQueryClause;
618
622
  this.sortParam = (_e = restorationState.sortParam) !== null && _e !== void 0 ? _e : null;
619
623
  this.currentPage = (_f = restorationState.currentPage) !== null && _f !== void 0 ? _f : 1;
620
624
  this.minSelectedDate = restorationState.minSelectedDate;
@@ -634,7 +638,6 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
634
638
  selectedFacets: (_c = this.selectedFacets) !== null && _c !== void 0 ? _c : defaultSelectedFacets,
635
639
  baseQuery: this.baseQuery,
636
640
  currentPage: this.currentPage,
637
- dateRangeQueryClause: this.dateRangeQueryClause,
638
641
  titleQuery: this.titleQuery,
639
642
  creatorQuery: this.creatorQuery,
640
643
  minSelectedDate: this.minSelectedDate,
@@ -649,6 +652,64 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
649
652
  await this.fetchPage(this.initialPageNumber);
650
653
  this.searchResultsLoading = false;
651
654
  }
655
+ get filterMap() {
656
+ const builder = new FilterMapBuilder();
657
+ // Add the date range, if applicable
658
+ if (this.minSelectedDate) {
659
+ builder.addFilter('year', this.minSelectedDate, FilterConstraint.GREATER_OR_EQUAL);
660
+ }
661
+ if (this.maxSelectedDate) {
662
+ builder.addFilter('year', this.maxSelectedDate, FilterConstraint.LESS_OR_EQUAL);
663
+ }
664
+ // Add any selected facets
665
+ if (this.selectedFacets) {
666
+ for (const [facetName, facetValues] of Object.entries(this.selectedFacets)) {
667
+ const { name, values } = this.prepareFacetForFetch(facetName, facetValues);
668
+ for (const [value, bucket] of Object.entries(values)) {
669
+ let constraint;
670
+ if (bucket.state === 'selected') {
671
+ constraint = FilterConstraint.INCLUDE;
672
+ }
673
+ else if (bucket.state === 'hidden') {
674
+ constraint = FilterConstraint.EXCLUDE;
675
+ }
676
+ if (constraint) {
677
+ builder.addFilter(name, value, constraint);
678
+ }
679
+ }
680
+ }
681
+ }
682
+ const filterMap = builder.build();
683
+ // TEMP: At present, the backend search engine incorrectly returns 0 results if
684
+ // the _first_ language filter contains a space, so let's try to avoid that if possible.
685
+ if (filterMap.language) {
686
+ for (const [value, constraint] of Object.entries(filterMap.language)) {
687
+ if (value.includes(' ')) {
688
+ // Delete and re-add this filter to make it the last one on the parent object
689
+ // (Technically this isn't in the standard, but most browser impls output
690
+ // object keys in the order they were added.)
691
+ delete filterMap.language[value];
692
+ filterMap.language[value] = constraint;
693
+ }
694
+ else {
695
+ // As soon as we reach one without a space, we're done
696
+ break;
697
+ }
698
+ }
699
+ }
700
+ return filterMap;
701
+ }
702
+ /** The base query joined with any title/creator letter filters */
703
+ get filteredQuery() {
704
+ if (!this.baseQuery)
705
+ return undefined;
706
+ let filteredQuery = this.baseQuery;
707
+ const { sortFilterQueries } = this;
708
+ if (sortFilterQueries) {
709
+ filteredQuery += ` AND ${sortFilterQueries}`;
710
+ }
711
+ return filteredQuery;
712
+ }
652
713
  /** The full query, including year facets and date range clauses */
653
714
  get fullQuery() {
654
715
  if (!this.baseQuery)
@@ -666,6 +727,20 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
666
727
  }
667
728
  return fullQuery;
668
729
  }
730
+ /** The full query without any title/creator letter filters */
731
+ get fullQueryWithoutAlphaFilters() {
732
+ if (!this.baseQuery)
733
+ return undefined;
734
+ let fullQuery = this.baseQuery;
735
+ const { facetQuery, dateRangeQueryClause } = this;
736
+ if (facetQuery) {
737
+ fullQuery += ` AND ${facetQuery}`;
738
+ }
739
+ if (dateRangeQueryClause) {
740
+ fullQuery += ` AND ${dateRangeQueryClause}`;
741
+ }
742
+ return fullQuery;
743
+ }
669
744
  /** The full query without any year facets or date range clauses */
670
745
  get fullQueryWithoutDates() {
671
746
  if (!this.baseQuery)
@@ -722,26 +797,46 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
722
797
  * @param facetValues The facet buckets, mapped by their keys
723
798
  */
724
799
  buildFacetClause(facetName, facetValues) {
725
- const facetEntries = Object.entries(facetValues);
800
+ const { name: facetQueryName, values } = this.prepareFacetForFetch(facetName, facetValues);
801
+ const facetEntries = Object.entries(values);
726
802
  if (facetEntries.length === 0)
727
803
  return '';
728
- const facetQueryName = facetName === 'lending' ? 'lending___status' : facetName;
729
804
  const facetValuesArray = [];
730
805
  for (const [key, facetData] of facetEntries) {
731
806
  const plusMinusPrefix = facetData.state === 'hidden' ? '-' : '';
732
- if (facetName === 'language') {
733
- const languages = this.languageCodeHandler.getCodeArrayFromCodeString(key);
734
- for (const language of languages) {
735
- facetValuesArray.push(`${plusMinusPrefix}"${language}"`);
736
- }
737
- }
738
- else {
739
- facetValuesArray.push(`${plusMinusPrefix}"${key}"`);
740
- }
807
+ facetValuesArray.push(`${plusMinusPrefix}"${key}"`);
741
808
  }
742
809
  const valueQuery = facetValuesArray.join(` OR `);
743
810
  return `${facetQueryName}:(${valueQuery})`;
744
811
  }
812
+ /**
813
+ * Handles some special pre-request normalization steps for certain facet types
814
+ * that require them.
815
+ *
816
+ * @param facetName The name of the facet type (e.g., 'language')
817
+ * @param facetValues An array of values for that facet type
818
+ */
819
+ prepareFacetForFetch(facetName, facetValues) {
820
+ let [normalizedName, normalizedValues] = [facetName, facetValues];
821
+ // The full "search engine" name of the lending field is "lending___status"
822
+ if (facetName === 'lending') {
823
+ normalizedName = 'lending___status';
824
+ }
825
+ // Language codes like "en-US|en-GB|en" need to be broken apart into individual values
826
+ if (facetName === 'language') {
827
+ normalizedValues = {};
828
+ for (const [facetValue, facetData] of Object.entries(facetValues)) {
829
+ const languages = this.languageCodeHandler.getCodeArrayFromCodeString(facetValue);
830
+ for (const lang of languages) {
831
+ normalizedValues[lang] = { ...facetData };
832
+ }
833
+ }
834
+ }
835
+ return {
836
+ name: normalizedName,
837
+ values: normalizedValues,
838
+ };
839
+ }
745
840
  /**
746
841
  * Takes an array of facet clauses, and combines them into a
747
842
  * full AND-joined facet query string. Empty clauses are ignored.
@@ -778,11 +873,12 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
778
873
  }
779
874
  async fetchFacets() {
780
875
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
781
- if (!this.fullQuery)
876
+ if (!this.filteredQuery)
782
877
  return;
783
878
  const params = {
784
- query: this.fullQuery,
879
+ query: this.filteredQuery,
785
880
  rows: 0,
881
+ filters: this.filterMap,
786
882
  // Fetch a few extra buckets beyond the 6 we show, in case some get suppressed
787
883
  aggregationsSize: 10,
788
884
  // Note: we don't need an aggregations param to fetch the default aggregations from the PPS.
@@ -869,7 +965,14 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
869
965
  }, 0);
870
966
  }
871
967
  /**
872
- * The query key is a string that uniquely identifies the current query
968
+ * The query key is a string that uniquely identifies the current search.
969
+ * It consists of:
970
+ * - The current base query
971
+ * - The current search type
972
+ * - Any currently-applied facets
973
+ * - Any currently-applied date range
974
+ * - Any currently-applied prefix filters
975
+ * - The current sort options
873
976
  *
874
977
  * This lets us keep track of queries so we don't persist data that's
875
978
  * no longer relevant.
@@ -880,7 +983,7 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
880
983
  }
881
984
  async fetchPage(pageNumber) {
882
985
  var _a, _b, _c, _d, _e;
883
- if (!this.fullQuery)
986
+ if (!this.filteredQuery)
884
987
  return;
885
988
  // if we already have data, don't fetch again
886
989
  if (this.dataSource[pageNumber])
@@ -896,10 +999,11 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
896
999
  this.pageFetchesInProgress[pageFetchQueryKey] = pageFetches;
897
1000
  const sortParams = this.sortParam ? [this.sortParam] : [];
898
1001
  const params = {
899
- query: this.fullQuery,
1002
+ query: this.filteredQuery,
900
1003
  page: pageNumber,
901
1004
  rows: this.pageSize,
902
1005
  sort: sortParams,
1006
+ filters: this.filterMap,
903
1007
  aggregations: { omit: true },
904
1008
  };
905
1009
  const searchResponse = await ((_b = this.searchService) === null || _b === void 0 ? void 0 : _b.search(params, this.searchType));
@@ -930,7 +1034,7 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
930
1034
  }
931
1035
  }
932
1036
  }
933
- const queryChangedSinceFetch = searchQuery !== this.fullQuery || sortChanged;
1037
+ const queryChangedSinceFetch = searchQuery !== this.filteredQuery || sortChanged;
934
1038
  if (queryChangedSinceFetch)
935
1039
  return;
936
1040
  const { results } = success.response;
@@ -938,12 +1042,12 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
938
1042
  this.preloadCollectionNames(results);
939
1043
  this.updateDataSource(pageNumber, results);
940
1044
  }
1045
+ // When we reach the end of the data, we can set the infinite scroller's
1046
+ // item count to the real total number of results (rather than the
1047
+ // temporary estimates based on pages rendered so far).
941
1048
  if (results.length < this.pageSize) {
942
1049
  this.endOfDataReached = true;
943
- // this updates the infinite scroller to show the actual size
944
- if (this.infiniteScroller) {
945
- this.infiniteScroller.itemCount = this.actualTileCount;
946
- }
1050
+ this.infiniteScroller.itemCount = this.totalResults;
947
1051
  }
948
1052
  (_e = this.pageFetchesInProgress[pageFetchQueryKey]) === null || _e === void 0 ? void 0 : _e.delete(pageNumber);
949
1053
  this.searchResultsLoading = false;
@@ -1041,6 +1145,56 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
1041
1145
  this.infiniteScroller.reload();
1042
1146
  }
1043
1147
  }
1148
+ /** Fetches the aggregation buckets for the given prefix filter type. */
1149
+ async fetchPrefixFilterBuckets(filterType) {
1150
+ var _a, _b, _c, _d, _e, _f;
1151
+ if (!this.fullQueryWithoutAlphaFilters)
1152
+ return [];
1153
+ const filterAggregationKey = prefixFilterAggregationKeys[filterType];
1154
+ const params = {
1155
+ query: this.fullQueryWithoutAlphaFilters,
1156
+ rows: 0,
1157
+ // Only fetch the firstTitle or firstCreator aggregation
1158
+ aggregations: { simpleParams: [filterAggregationKey] },
1159
+ // Fetch all 26 letter buckets
1160
+ aggregationsSize: 26,
1161
+ };
1162
+ const searchResponse = await ((_a = this.searchService) === null || _a === void 0 ? void 0 : _a.search(params, this.searchType));
1163
+ return ((_f = (_e = (_d = (_c = (_b = searchResponse === null || searchResponse === void 0 ? void 0 : searchResponse.success) === null || _b === void 0 ? void 0 : _b.response) === null || _c === void 0 ? void 0 : _c.aggregations) === null || _d === void 0 ? void 0 : _d[filterAggregationKey]) === null || _e === void 0 ? void 0 : _e.buckets) !== null && _f !== void 0 ? _f : []);
1164
+ }
1165
+ /** Fetches and caches the prefix filter counts for the given filter type. */
1166
+ async updatePrefixFilterCounts(filterType) {
1167
+ const buckets = await this.fetchPrefixFilterBuckets(filterType);
1168
+ // Unpack the aggregation buckets into a simple map like { 'A': 50, 'B': 25, ... }
1169
+ this.prefixFilterCountMap = { ...this.prefixFilterCountMap }; // Clone the object to trigger an update
1170
+ this.prefixFilterCountMap[filterType] = buckets.reduce((acc, bucket) => {
1171
+ acc[bucket.key.toUpperCase()] = bucket.doc_count;
1172
+ return acc;
1173
+ }, {});
1174
+ }
1175
+ /**
1176
+ * Fetches and caches the prefix filter counts for the current sort type,
1177
+ * provided it is one that permits prefix filtering. (If not, this does nothing).
1178
+ */
1179
+ async updatePrefixFiltersForCurrentSort() {
1180
+ if (['title', 'creator'].includes(this.selectedSort)) {
1181
+ const filterType = this.selectedSort;
1182
+ if (!this.prefixFilterCountMap[filterType]) {
1183
+ this.updatePrefixFilterCounts(filterType);
1184
+ }
1185
+ }
1186
+ }
1187
+ /**
1188
+ * Clears the cached letter counts for both title and creator, and
1189
+ * fetches a new set of counts for whichever of them is the currently
1190
+ * selected sort option (which may be neither).
1191
+ *
1192
+ * Call this whenever the counts are invalidated (e.g., by a query change).
1193
+ */
1194
+ refreshLetterCounts() {
1195
+ this.prefixFilterCountMap = {};
1196
+ this.updatePrefixFiltersForCurrentSort();
1197
+ }
1044
1198
  /*
1045
1199
  * Convert etree titles
1046
1200
  * "[Creator] Live at [Place] on [Date]" => "[Date]: [Place]"
@@ -1099,8 +1253,10 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
1099
1253
  * increase the number of pages to render and start fetching data for the new page
1100
1254
  */
1101
1255
  scrollThresholdReached() {
1102
- this.pagesToRender += 1;
1103
- this.fetchPage(this.pagesToRender);
1256
+ if (!this.endOfDataReached) {
1257
+ this.pagesToRender += 1;
1258
+ this.fetchPage(this.pagesToRender);
1259
+ }
1104
1260
  }
1105
1261
  };
1106
1262
  CollectionBrowser.styles = css `
@@ -1342,9 +1498,6 @@ __decorate([
1342
1498
  __decorate([
1343
1499
  property({ type: String })
1344
1500
  ], CollectionBrowser.prototype, "sortDirection", void 0);
1345
- __decorate([
1346
- property({ type: String })
1347
- ], CollectionBrowser.prototype, "dateRangeQueryClause", void 0);
1348
1501
  __decorate([
1349
1502
  property({ type: Number })
1350
1503
  ], CollectionBrowser.prototype, "pageSize", void 0);
@@ -1429,6 +1582,9 @@ __decorate([
1429
1582
  __decorate([
1430
1583
  state()
1431
1584
  ], CollectionBrowser.prototype, "placeholderType", void 0);
1585
+ __decorate([
1586
+ state()
1587
+ ], CollectionBrowser.prototype, "prefixFilterCountMap", void 0);
1432
1588
  __decorate([
1433
1589
  query('#content-container')
1434
1590
  ], CollectionBrowser.prototype, "contentContainer", void 0);