@internetarchive/collection-browser 0.4.16-alpha.9 → 0.4.17

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 (74) hide show
  1. package/dist/src/app-root.js +12 -0
  2. package/dist/src/app-root.js.map +1 -1
  3. package/dist/src/collection-browser.d.ts +53 -14
  4. package/dist/src/collection-browser.js +279 -113
  5. package/dist/src/collection-browser.js.map +1 -1
  6. package/dist/src/collection-facets/facets-template.d.ts +3 -0
  7. package/dist/src/collection-facets/facets-template.js +20 -1
  8. package/dist/src/collection-facets/facets-template.js.map +1 -1
  9. package/dist/src/collection-facets/more-facets-content.js +7 -4
  10. package/dist/src/collection-facets/more-facets-content.js.map +1 -1
  11. package/dist/src/collection-facets.d.ts +0 -2
  12. package/dist/src/collection-facets.js +1 -4
  13. package/dist/src/collection-facets.js.map +1 -1
  14. package/dist/src/empty-placeholder.js +1 -0
  15. package/dist/src/empty-placeholder.js.map +1 -1
  16. package/dist/src/models.d.ts +5 -0
  17. package/dist/src/models.js.map +1 -1
  18. package/dist/src/tiles/grid/item-tile.js +10 -3
  19. package/dist/src/tiles/grid/item-tile.js.map +1 -1
  20. package/dist/src/tiles/list/tile-list-compact.d.ts +1 -0
  21. package/dist/src/tiles/list/tile-list-compact.js +13 -1
  22. package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
  23. package/dist/src/tiles/list/tile-list.js +10 -1
  24. package/dist/src/tiles/list/tile-list.js.map +1 -1
  25. package/dist/src/utils/format-date.d.ts +1 -1
  26. package/dist/src/utils/format-date.js +3 -0
  27. package/dist/src/utils/format-date.js.map +1 -1
  28. package/dist/src/utils/local-date-from-utc.d.ts +9 -0
  29. package/dist/src/utils/local-date-from-utc.js +16 -0
  30. package/dist/src/utils/local-date-from-utc.js.map +1 -0
  31. package/dist/test/collection-browser.test.js +73 -10
  32. package/dist/test/collection-browser.test.js.map +1 -1
  33. package/dist/test/collection-facets/facets-template.test.js +80 -0
  34. package/dist/test/collection-facets/facets-template.test.js.map +1 -1
  35. package/dist/test/mocks/mock-collection-name-cache.d.ts +2 -0
  36. package/dist/test/mocks/mock-collection-name-cache.js +4 -0
  37. package/dist/test/mocks/mock-collection-name-cache.js.map +1 -1
  38. package/dist/test/mocks/mock-search-responses.d.ts +2 -0
  39. package/dist/test/mocks/mock-search-responses.js +81 -0
  40. package/dist/test/mocks/mock-search-responses.js.map +1 -1
  41. package/dist/test/mocks/mock-search-service.js +3 -1
  42. package/dist/test/mocks/mock-search-service.js.map +1 -1
  43. package/dist/test/tiles/grid/item-tile.test.js +124 -3
  44. package/dist/test/tiles/grid/item-tile.test.js.map +1 -1
  45. package/dist/test/tiles/list/tile-list-compact.test.js +65 -20
  46. package/dist/test/tiles/list/tile-list-compact.test.js.map +1 -1
  47. package/dist/test/tiles/list/tile-list.test.js +106 -4
  48. package/dist/test/tiles/list/tile-list.test.js.map +1 -1
  49. package/dist/test/utils/local-date-from-utc.test.d.ts +1 -0
  50. package/dist/test/utils/local-date-from-utc.test.js +27 -0
  51. package/dist/test/utils/local-date-from-utc.test.js.map +1 -0
  52. package/index.html +1 -0
  53. package/package.json +3 -3
  54. package/src/app-root.ts +12 -0
  55. package/src/collection-browser.ts +311 -114
  56. package/src/collection-facets/facets-template.ts +32 -1
  57. package/src/collection-facets/more-facets-content.ts +4 -1
  58. package/src/collection-facets.ts +1 -8
  59. package/src/empty-placeholder.ts +1 -0
  60. package/src/models.ts +6 -0
  61. package/src/tiles/grid/item-tile.ts +11 -4
  62. package/src/tiles/list/tile-list-compact.ts +16 -2
  63. package/src/tiles/list/tile-list.ts +12 -5
  64. package/src/utils/format-date.ts +4 -0
  65. package/src/utils/local-date-from-utc.ts +15 -0
  66. package/test/collection-browser.test.ts +101 -12
  67. package/test/collection-facets/facets-template.test.ts +98 -0
  68. package/test/mocks/mock-collection-name-cache.ts +8 -0
  69. package/test/mocks/mock-search-responses.ts +89 -0
  70. package/test/mocks/mock-search-service.ts +4 -0
  71. package/test/tiles/grid/item-tile.test.ts +145 -3
  72. package/test/tiles/list/tile-list-compact.test.ts +70 -19
  73. package/test/tiles/list/tile-list.test.ts +118 -4
  74. package/test/utils/local-date-from-utc.test.ts +37 -0
@@ -82,6 +82,30 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
82
82
  * for the previous/next page, we'll fetch the next/previous page to populate it
83
83
  */
84
84
  this.dataSource = {};
85
+ /**
86
+ * Updates the height of the left column according to its position on the page.
87
+ * Arrow function ensures proper `this` binding.
88
+ */
89
+ this.updateLeftColumnHeight = () => {
90
+ var _a, _b, _c, _d, _e;
91
+ if (this.mobileView) {
92
+ (_b = (_a = this.leftColumn) === null || _a === void 0 ? void 0 : _a.style) === null || _b === void 0 ? void 0 : _b.removeProperty('height');
93
+ }
94
+ else {
95
+ const clientTop = (_c = this.leftColumn) === null || _c === void 0 ? void 0 : _c.getBoundingClientRect().top;
96
+ (_e = (_d = this.leftColumn) === null || _d === void 0 ? void 0 : _d.style) === null || _e === void 0 ? void 0 : _e.setProperty('height', `${window.innerHeight - (clientTop !== null && clientTop !== void 0 ? clientTop : 0) - 3}px`);
97
+ }
98
+ };
99
+ /**
100
+ * Toggles whether the fade-out is visible at the bottom of the facets.
101
+ * It should only be visible if the facets are not scrolled to the bottom.
102
+ * Arrow function ensures proper `this` binding.
103
+ */
104
+ this.updateFacetFadeOut = (entries) => {
105
+ var _a, _b;
106
+ const fadeElmt = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.getElementById('facets-bottom-fade');
107
+ fadeElmt === null || fadeElmt === void 0 ? void 0 : fadeElmt.classList.toggle('hidden', (_b = entries === null || entries === void 0 ? void 0 : entries[0]) === null || _b === void 0 ? void 0 : _b.isIntersecting);
108
+ };
85
109
  // we only want to scroll on the very first query change
86
110
  // so this keeps track of whether we've already set the initial query
87
111
  this.initialQueryChangeHappened = false;
@@ -150,8 +174,6 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
150
174
  if (letterFilters) {
151
175
  this.selectedTitleFilter = null;
152
176
  this.selectedCreatorFilter = null;
153
- this.titleQuery = undefined;
154
- this.creatorQuery = undefined;
155
177
  }
156
178
  if (sort) {
157
179
  this.sortParam = null;
@@ -193,8 +215,8 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
193
215
  if (!((_a = this.baseQuery) === null || _a === void 0 ? void 0 : _a.trim())) {
194
216
  this.placeholderType = 'empty-query';
195
217
  }
196
- if ((!this.searchResultsLoading && this.totalResults === 0) ||
197
- !this.searchService) {
218
+ else if (!this.searchResultsLoading &&
219
+ (this.totalResults === 0 || !this.searchService)) {
198
220
  this.placeholderType = 'null-result';
199
221
  }
200
222
  if (this.queryErrorMessage) {
@@ -218,7 +240,8 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
218
240
  const shouldShowSearching = this.searchResultsLoading || this.totalResults === undefined;
219
241
  const resultsCount = (_a = this.totalResults) === null || _a === void 0 ? void 0 : _a.toLocaleString();
220
242
  const resultsLabel = this.totalResults === 1 ? 'Result' : 'Results';
221
- return html `<div
243
+ return html ` <div id="left-column-scroll-sentinel"></div>
244
+ <div
222
245
  id="left-column"
223
246
  class="column${this.isResizeToMobile ? ' preload' : ''}"
224
247
  >
@@ -240,7 +263,9 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
240
263
  : ''}
241
264
  >
242
265
  ${this.facetsTemplate}
266
+ <div id="facets-scroll-sentinel"></div>
243
267
  </div>
268
+ ${this.mobileView ? nothing : html `<div id="facets-bottom-fade"></div>`}
244
269
  </div>
245
270
  <div id="right-column" class="column">
246
271
  ${this.sortFilterBarTemplate}
@@ -329,9 +354,28 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
329
354
  });
330
355
  }
331
356
  }
332
- /** Send Analytics when sorting by title's first letter
357
+ /**
358
+ * Returns a query clause identifying the currently selected title filter,
359
+ * e.g., `firstTitle:X`.
360
+ */
361
+ get titleQuery() {
362
+ return this.selectedTitleFilter
363
+ ? `firstTitle:${this.selectedTitleFilter}`
364
+ : undefined;
365
+ }
366
+ /**
367
+ * Returns a query clause identifying the currently selected creator filter,
368
+ * e.g., `firstCreator:X`.
369
+ */
370
+ get creatorQuery() {
371
+ return this.selectedCreatorFilter
372
+ ? `firstCreator:${this.selectedCreatorFilter}`
373
+ : undefined;
374
+ }
375
+ /**
376
+ * Send Analytics when sorting by title's first letter
333
377
  * labels: 'start-<ToLetter>' | 'clear-<FromLetter>' | '<FromLetter>-<ToLetter>'
334
- * */
378
+ */
335
379
  sendFilterByTitleAnalytics(prevSelectedLetter) {
336
380
  var _a;
337
381
  if (!prevSelectedLetter && !this.selectedTitleFilter) {
@@ -346,14 +390,10 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
346
390
  : `${prevSelectedLetter || 'start'}-${this.selectedTitleFilter}`,
347
391
  });
348
392
  }
349
- selectedTitleLetterChanged() {
350
- this.titleQuery = this.selectedTitleFilter
351
- ? `firstTitle:${this.selectedTitleFilter}`
352
- : undefined;
353
- }
354
- /** Send Analytics when filtering by creator's first letter
393
+ /**
394
+ * Send Analytics when filtering by creator's first letter
355
395
  * labels: 'start-<ToLetter>' | 'clear-<FromLetter>' | '<FromLetter>-<ToLetter>'
356
- * */
396
+ */
357
397
  sendFilterByCreatorAnalytics(prevSelectedLetter) {
358
398
  var _a;
359
399
  if (!prevSelectedLetter && !this.selectedCreatorFilter) {
@@ -368,20 +408,19 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
368
408
  : `${prevSelectedLetter || 'start'}-${this.selectedCreatorFilter}`,
369
409
  });
370
410
  }
371
- selectedCreatorLetterChanged() {
372
- this.creatorQuery = this.selectedCreatorFilter
373
- ? `firstCreator:${this.selectedCreatorFilter}`
374
- : undefined;
375
- }
411
+ /**
412
+ * Handler for changes to which letter is selected in the title alphabet bar.
413
+ */
376
414
  titleLetterSelected(e) {
377
415
  this.selectedCreatorFilter = null;
378
416
  this.selectedTitleFilter = e.detail.selectedLetter;
379
- this.selectedTitleLetterChanged();
380
417
  }
418
+ /**
419
+ * Handler for changes to which letter is selected in the creator alphabet bar.
420
+ */
381
421
  creatorLetterSelected(e) {
382
422
  this.selectedTitleFilter = null;
383
423
  this.selectedCreatorFilter = e.detail.selectedLetter;
384
- this.selectedCreatorLetterChanged();
385
424
  }
386
425
  get mobileFacetsTemplate() {
387
426
  return html `
@@ -422,13 +461,13 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
422
461
  .selectedFacets=${this.selectedFacets}
423
462
  .collectionNameCache=${this.collectionNameCache}
424
463
  .showHistogramDatePicker=${this.showHistogramDatePicker}
425
- .query=${this.filteredQuery}
464
+ .query=${this.baseQuery}
426
465
  .filterMap=${this.filterMap}
427
466
  .modalManager=${this.modalManager}
428
467
  ?collapsableFacets=${this.mobileView}
429
468
  ?facetsLoading=${this.facetsLoading}
430
469
  ?fullYearAggregationLoading=${this.facetsLoading}
431
- .onFacetClick=${this.facetClickHandler}
470
+ @facetClick=${this.facetClickHandler}
432
471
  .analyticsHandler=${this.analyticsHandler}
433
472
  >
434
473
  </collection-facets>
@@ -477,6 +516,15 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
477
516
  }
478
517
  updated(changed) {
479
518
  var _a;
519
+ if (changed.has('placeholderType') && this.placeholderType === null) {
520
+ if (!this.leftColIntersectionObserver) {
521
+ this.setupLeftColumnScrollListeners();
522
+ }
523
+ if (!this.facetsIntersectionObserver) {
524
+ this.setupFacetsScrollListeners();
525
+ }
526
+ this.updateLeftColumnHeight();
527
+ }
480
528
  if (changed.has('displayMode') ||
481
529
  changed.has('baseNavigationUrl') ||
482
530
  changed.has('baseImageUrl') ||
@@ -521,11 +569,9 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
521
569
  }
522
570
  if (changed.has('selectedTitleFilter')) {
523
571
  this.sendFilterByTitleAnalytics(changed.get('selectedTitleFilter'));
524
- this.selectedTitleLetterChanged();
525
572
  }
526
573
  if (changed.has('selectedCreatorFilter')) {
527
574
  this.sendFilterByCreatorAnalytics(changed.get('selectedCreatorFilter'));
528
- this.selectedCreatorLetterChanged();
529
575
  }
530
576
  if (changed.has('baseQuery') ||
531
577
  changed.has('searchType') ||
@@ -551,12 +597,16 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
551
597
  }
552
598
  }
553
599
  disconnectedCallback() {
600
+ var _a, _b;
554
601
  if (this.resizeObserver) {
555
602
  this.disconnectResizeObserver(this.resizeObserver);
556
603
  }
557
604
  if (this.boundNavigationHandler) {
558
605
  window.removeEventListener('popstate', this.boundNavigationHandler);
559
606
  }
607
+ (_a = this.leftColIntersectionObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
608
+ (_b = this.facetsIntersectionObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
609
+ window.removeEventListener('resize', this.updateLeftColumnHeight);
560
610
  }
561
611
  handleResize(entry) {
562
612
  const previousView = this.mobileView;
@@ -567,6 +617,41 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
567
617
  this.isResizeToMobile = true;
568
618
  }
569
619
  }
620
+ // Ensure the facet sidebar remains sized correctly
621
+ this.updateLeftColumnHeight();
622
+ }
623
+ /**
624
+ * Sets up listeners for events that may require updating the left column height.
625
+ */
626
+ setupLeftColumnScrollListeners() {
627
+ var _a;
628
+ // We observe intersections between the left column's scroll sentinel and
629
+ // the viewport, so that we can ensure the left column is always sized to
630
+ // match the _available_ viewport height. This should generally be more
631
+ // performant than listening to scroll events on the page or column.
632
+ const leftColumnSentinel = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('#left-column-scroll-sentinel');
633
+ if (leftColumnSentinel) {
634
+ this.leftColIntersectionObserver = new IntersectionObserver(this.updateLeftColumnHeight, {
635
+ threshold: [...Array(101).keys()].map(n => n / 100), // Threshold every 1%
636
+ });
637
+ this.leftColIntersectionObserver.observe(leftColumnSentinel);
638
+ }
639
+ // We also listen for window resize events, as they are not always captured
640
+ // by the resize observer and can affect the desired height of the left column.
641
+ window.addEventListener('resize', this.updateLeftColumnHeight);
642
+ }
643
+ /**
644
+ * Sets up listeners to control whether the facet sidebar shows its bottom fade-out.
645
+ * Note this uses a separate IntersectionObserver from the left column, because we
646
+ * don't need granular intersection thresholds for this.
647
+ */
648
+ setupFacetsScrollListeners() {
649
+ var _a;
650
+ const facetsSentinel = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('#facets-scroll-sentinel');
651
+ if (facetsSentinel) {
652
+ this.facetsIntersectionObserver = new IntersectionObserver(this.updateFacetFadeOut);
653
+ this.facetsIntersectionObserver.observe(facetsSentinel);
654
+ }
570
655
  }
571
656
  emitBaseQueryChanged() {
572
657
  this.dispatchEvent(new CustomEvent('baseQueryChanged', {
@@ -703,6 +788,11 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
703
788
  await this.fetchPage(this.initialPageNumber);
704
789
  this.searchResultsLoading = false;
705
790
  }
791
+ /**
792
+ * Constructs a search service FilterMap object from the combination of
793
+ * all the currently-applied filters. This includes any facets, letter
794
+ * filters, and date range.
795
+ */
706
796
  get filterMap() {
707
797
  const builder = new FilterMapBuilder();
708
798
  // Add the date range, if applicable
@@ -730,20 +820,16 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
730
820
  }
731
821
  }
732
822
  }
823
+ // Add any letter filters
824
+ if (this.selectedTitleFilter) {
825
+ builder.addFilter('firstTitle', this.selectedTitleFilter, FilterConstraint.INCLUDE);
826
+ }
827
+ if (this.selectedCreatorFilter) {
828
+ builder.addFilter('firstCreator', this.selectedCreatorFilter, FilterConstraint.INCLUDE);
829
+ }
733
830
  const filterMap = builder.build();
734
831
  return filterMap;
735
832
  }
736
- /** The base query joined with any title/creator letter filters */
737
- get filteredQuery() {
738
- if (!this.baseQuery)
739
- return undefined;
740
- let filteredQuery = this.baseQuery.trim();
741
- const { sortFilterQueries } = this;
742
- if (sortFilterQueries) {
743
- filteredQuery += ` AND ${sortFilterQueries}`;
744
- }
745
- return filteredQuery.trim();
746
- }
747
833
  /** The full query, including year facets and date range clauses */
748
834
  get fullQuery() {
749
835
  if (!this.baseQuery)
@@ -761,20 +847,6 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
761
847
  }
762
848
  return fullQuery.trim();
763
849
  }
764
- /** The full query without any title/creator letter filters */
765
- get fullQueryWithoutAlphaFilters() {
766
- if (!this.baseQuery)
767
- return undefined;
768
- let fullQuery = this.baseQuery.trim();
769
- const { facetQuery, dateRangeQueryClause } = this;
770
- if (facetQuery) {
771
- fullQuery += ` AND ${facetQuery}`;
772
- }
773
- if (dateRangeQueryClause) {
774
- fullQuery += ` AND ${dateRangeQueryClause}`;
775
- }
776
- return fullQuery.trim();
777
- }
778
850
  /**
779
851
  * Generates a query string for the given facets
780
852
  *
@@ -846,35 +918,37 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
846
918
  facetsChanged(e) {
847
919
  this.selectedFacets = e.detail;
848
920
  }
849
- facetClickHandler(name, facetSelected, negative) {
921
+ facetClickHandler({ detail: { key, state: facetState, negative }, }) {
850
922
  var _a, _b;
851
923
  if (negative) {
852
924
  (_a = this.analyticsHandler) === null || _a === void 0 ? void 0 : _a.sendEvent({
853
925
  category: this.searchContext,
854
- action: facetSelected
926
+ action: facetState !== 'none'
855
927
  ? analyticsActions.facetNegativeSelected
856
928
  : analyticsActions.facetNegativeDeselected,
857
- label: name,
929
+ label: key,
858
930
  });
859
931
  }
860
932
  else {
861
933
  (_b = this.analyticsHandler) === null || _b === void 0 ? void 0 : _b.sendEvent({
862
934
  category: this.searchContext,
863
- action: facetSelected
935
+ action: facetState !== 'none'
864
936
  ? analyticsActions.facetSelected
865
937
  : analyticsActions.facetDeselected,
866
- label: name,
938
+ label: key,
867
939
  });
868
940
  }
869
941
  }
870
942
  async fetchFacets() {
871
- var _a, _b, _c, _d, _e, _f, _g;
872
- if (!this.filteredQuery)
943
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
944
+ const trimmedQuery = (_a = this.baseQuery) === null || _a === void 0 ? void 0 : _a.trim();
945
+ if (!trimmedQuery)
873
946
  return;
874
947
  if (!this.searchService)
875
948
  return;
949
+ const { facetFetchQueryKey } = this;
876
950
  const params = {
877
- query: this.filteredQuery,
951
+ query: trimmedQuery,
878
952
  rows: 0,
879
953
  filters: this.filterMap,
880
954
  // Fetch a few extra buckets beyond the 6 we show, in case some get suppressed
@@ -886,26 +960,32 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
886
960
  this.facetsLoading = true;
887
961
  const searchResponse = await this.searchService.search(params, this.searchType);
888
962
  const success = searchResponse === null || searchResponse === void 0 ? void 0 : searchResponse.success;
889
- this.facetsLoading = false;
963
+ // This is checking to see if the query has changed since the data was fetched.
964
+ // If so, we just want to discard this set of aggregations because they are
965
+ // likely no longer valid for the newer query.
966
+ const queryChangedSinceFetch = facetFetchQueryKey !== this.facetFetchQueryKey;
967
+ if (queryChangedSinceFetch)
968
+ return;
890
969
  if (!success) {
891
- const errorMsg = (_a = searchResponse === null || searchResponse === void 0 ? void 0 : searchResponse.error) === null || _a === void 0 ? void 0 : _a.message;
892
- const detailMsg = (_c = (_b = searchResponse === null || searchResponse === void 0 ? void 0 : searchResponse.error) === null || _b === void 0 ? void 0 : _b.details) === null || _c === void 0 ? void 0 : _c.message;
970
+ const errorMsg = (_b = searchResponse === null || searchResponse === void 0 ? void 0 : searchResponse.error) === null || _b === void 0 ? void 0 : _b.message;
971
+ const detailMsg = (_d = (_c = searchResponse === null || searchResponse === void 0 ? void 0 : searchResponse.error) === null || _c === void 0 ? void 0 : _c.details) === null || _d === void 0 ? void 0 : _d.message;
893
972
  if (!errorMsg && !detailMsg) {
894
973
  // @ts-ignore: Property 'Sentry' does not exist on type 'Window & typeof globalThis'
895
- (_e = (_d = window === null || window === void 0 ? void 0 : window.Sentry) === null || _d === void 0 ? void 0 : _d.captureMessage) === null || _e === void 0 ? void 0 : _e.call(_d, 'Missing or malformed facet response from backend', 'error');
974
+ (_f = (_e = window === null || window === void 0 ? void 0 : window.Sentry) === null || _e === void 0 ? void 0 : _e.captureMessage) === null || _f === void 0 ? void 0 : _f.call(_e, 'Missing or malformed facet response from backend', 'error');
896
975
  }
897
976
  return;
898
977
  }
899
- // This is checking to see if the query has changed since the data was fetched.
900
- // If so, we just want to discard this set of aggregations because they are
901
- // likely no longer valid for the newer query.
902
- const returnedUid = success.request.clientParameters.uid;
903
- const queryChangedSinceFetch = returnedUid !== this.facetFetchQueryKey;
904
- if (queryChangedSinceFetch)
905
- return;
906
- this.aggregations = success === null || success === void 0 ? void 0 : success.response.aggregations;
978
+ const { aggregations, collectionTitles } = success.response;
979
+ this.aggregations = aggregations;
980
+ if (collectionTitles) {
981
+ (_g = this.collectionNameCache) === null || _g === void 0 ? void 0 : _g.addKnownTitles(collectionTitles);
982
+ }
983
+ else if ((_h = this.aggregations) === null || _h === void 0 ? void 0 : _h.collection) {
984
+ (_j = this.collectionNameCache) === null || _j === void 0 ? void 0 : _j.preloadIdentifiers(this.aggregations.collection.buckets.map(bucket => { var _a; return (_a = bucket.key) === null || _a === void 0 ? void 0 : _a.toString(); }));
985
+ }
907
986
  this.fullYearsHistogramAggregation =
908
- (_g = (_f = success === null || success === void 0 ? void 0 : success.response) === null || _f === void 0 ? void 0 : _f.aggregations) === null || _g === void 0 ? void 0 : _g.year_histogram;
987
+ (_l = (_k = success === null || success === void 0 ? void 0 : success.response) === null || _k === void 0 ? void 0 : _k.aggregations) === null || _l === void 0 ? void 0 : _l.year_histogram;
988
+ this.facetsLoading = false;
909
989
  }
910
990
  scrollToPage(pageNumber) {
911
991
  return new Promise(resolve => {
@@ -956,8 +1036,9 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
956
1036
  return `${this.fullQuery}-${this.searchType}`;
957
1037
  }
958
1038
  async fetchPage(pageNumber) {
959
- var _a, _b, _c, _d, _e, _f, _g;
960
- if (!this.filteredQuery)
1039
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
1040
+ const trimmedQuery = (_a = this.baseQuery) === null || _a === void 0 ? void 0 : _a.trim();
1041
+ if (!trimmedQuery)
961
1042
  return;
962
1043
  if (!this.searchService)
963
1044
  return;
@@ -968,14 +1049,14 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
968
1049
  return;
969
1050
  // if a fetch is already in progress for this query and page, don't fetch again
970
1051
  const { pageFetchQueryKey } = this;
971
- const pageFetches = (_a = this.pageFetchesInProgress[pageFetchQueryKey]) !== null && _a !== void 0 ? _a : new Set();
1052
+ const pageFetches = (_b = this.pageFetchesInProgress[pageFetchQueryKey]) !== null && _b !== void 0 ? _b : new Set();
972
1053
  if (pageFetches.has(pageNumber))
973
1054
  return;
974
1055
  pageFetches.add(pageNumber);
975
1056
  this.pageFetchesInProgress[pageFetchQueryKey] = pageFetches;
976
1057
  const sortParams = this.sortParam ? [this.sortParam] : [];
977
1058
  const params = {
978
- query: this.filteredQuery,
1059
+ query: trimmedQuery,
979
1060
  page: pageNumber,
980
1061
  rows: this.pageSize,
981
1062
  sort: sortParams,
@@ -985,28 +1066,34 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
985
1066
  };
986
1067
  const searchResponse = await this.searchService.search(params, this.searchType);
987
1068
  const success = searchResponse === null || searchResponse === void 0 ? void 0 : searchResponse.success;
1069
+ // This is checking to see if the query has changed since the data was fetched.
1070
+ // If so, we just want to discard the data since there should be a new query
1071
+ // right behind it.
1072
+ const queryChangedSinceFetch = pageFetchQueryKey !== this.pageFetchQueryKey;
1073
+ if (queryChangedSinceFetch)
1074
+ return;
988
1075
  if (!success) {
989
- const errorMsg = (_b = searchResponse === null || searchResponse === void 0 ? void 0 : searchResponse.error) === null || _b === void 0 ? void 0 : _b.message;
990
- const detailMsg = (_d = (_c = searchResponse === null || searchResponse === void 0 ? void 0 : searchResponse.error) === null || _c === void 0 ? void 0 : _c.details) === null || _d === void 0 ? void 0 : _d.message;
1076
+ const errorMsg = (_c = searchResponse === null || searchResponse === void 0 ? void 0 : searchResponse.error) === null || _c === void 0 ? void 0 : _c.message;
1077
+ const detailMsg = (_e = (_d = searchResponse === null || searchResponse === void 0 ? void 0 : searchResponse.error) === null || _d === void 0 ? void 0 : _d.details) === null || _e === void 0 ? void 0 : _e.message;
991
1078
  this.queryErrorMessage = `${errorMsg !== null && errorMsg !== void 0 ? errorMsg : ''}${detailMsg ? `; ${detailMsg}` : ''}`;
992
1079
  if (!this.queryErrorMessage) {
993
1080
  this.queryErrorMessage = 'Missing or malformed response from backend';
994
1081
  // @ts-ignore: Property 'Sentry' does not exist on type 'Window & typeof globalThis'
995
- (_f = (_e = window === null || window === void 0 ? void 0 : window.Sentry) === null || _e === void 0 ? void 0 : _e.captureMessage) === null || _f === void 0 ? void 0 : _f.call(_e, this.queryErrorMessage, 'error');
1082
+ (_g = (_f = window === null || window === void 0 ? void 0 : window.Sentry) === null || _f === void 0 ? void 0 : _f.captureMessage) === null || _g === void 0 ? void 0 : _g.call(_f, this.queryErrorMessage, 'error');
996
1083
  }
1084
+ (_h = this.pageFetchesInProgress[pageFetchQueryKey]) === null || _h === void 0 ? void 0 : _h.delete(pageNumber);
1085
+ this.searchResultsLoading = false;
997
1086
  return;
998
1087
  }
999
- // This is checking to see if the query has changed since the data was fetched.
1000
- // If so, we just want to discard the data since there should be a new query
1001
- // right behind it.
1002
- const returnedUid = success.request.clientParameters.uid;
1003
- const queryChangedSinceFetch = returnedUid !== this.pageFetchQueryKey;
1004
- if (queryChangedSinceFetch)
1005
- return;
1006
1088
  this.totalResults = success.response.totalResults;
1007
- const { results } = success.response;
1089
+ const { results, collectionTitles } = success.response;
1008
1090
  if (results && results.length > 0) {
1009
- this.preloadCollectionNames(results);
1091
+ if (collectionTitles) {
1092
+ (_j = this.collectionNameCache) === null || _j === void 0 ? void 0 : _j.addKnownTitles(collectionTitles);
1093
+ }
1094
+ else {
1095
+ this.preloadCollectionNames(results);
1096
+ }
1010
1097
  this.updateDataSource(pageNumber, results);
1011
1098
  }
1012
1099
  // When we reach the end of the data, we can set the infinite scroller's
@@ -1018,7 +1105,7 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
1018
1105
  this.infiniteScroller.itemCount = this.totalResults;
1019
1106
  }
1020
1107
  }
1021
- (_g = this.pageFetchesInProgress[pageFetchQueryKey]) === null || _g === void 0 ? void 0 : _g.delete(pageNumber);
1108
+ (_k = this.pageFetchesInProgress[pageFetchQueryKey]) === null || _k === void 0 ? void 0 : _k.delete(pageNumber);
1022
1109
  this.searchResultsLoading = false;
1023
1110
  }
1024
1111
  preloadCollectionNames(results) {
@@ -1120,24 +1207,32 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
1120
1207
  }
1121
1208
  /** Fetches the aggregation buckets for the given prefix filter type. */
1122
1209
  async fetchPrefixFilterBuckets(filterType) {
1123
- var _a, _b, _c, _d, _e, _f;
1124
- if (!this.fullQueryWithoutAlphaFilters)
1210
+ var _a, _b, _c, _d, _e, _f, _g;
1211
+ const trimmedQuery = (_a = this.baseQuery) === null || _a === void 0 ? void 0 : _a.trim();
1212
+ if (!trimmedQuery)
1125
1213
  return [];
1126
1214
  const filterAggregationKey = prefixFilterAggregationKeys[filterType];
1127
1215
  const params = {
1128
- query: this.fullQueryWithoutAlphaFilters,
1216
+ query: trimmedQuery,
1129
1217
  rows: 0,
1218
+ filters: this.filterMap,
1130
1219
  // Only fetch the firstTitle or firstCreator aggregation
1131
1220
  aggregations: { simpleParams: [filterAggregationKey] },
1132
1221
  // Fetch all 26 letter buckets
1133
1222
  aggregationsSize: 26,
1134
1223
  };
1135
- const searchResponse = await ((_a = this.searchService) === null || _a === void 0 ? void 0 : _a.search(params, this.searchType));
1136
- 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 : []);
1224
+ const searchResponse = await ((_b = this.searchService) === null || _b === void 0 ? void 0 : _b.search(params, this.searchType));
1225
+ return ((_g = (_f = (_e = (_d = (_c = searchResponse === null || searchResponse === void 0 ? void 0 : searchResponse.success) === null || _c === void 0 ? void 0 : _c.response) === null || _d === void 0 ? void 0 : _d.aggregations) === null || _e === void 0 ? void 0 : _e[filterAggregationKey]) === null || _f === void 0 ? void 0 : _f.buckets) !== null && _g !== void 0 ? _g : []);
1137
1226
  }
1138
1227
  /** Fetches and caches the prefix filter counts for the given filter type. */
1139
1228
  async updatePrefixFilterCounts(filterType) {
1229
+ const { facetFetchQueryKey } = this;
1140
1230
  const buckets = await this.fetchPrefixFilterBuckets(filterType);
1231
+ // Don't update the filter counts for an outdated query (if it has been changed
1232
+ // since we sent the request)
1233
+ const queryChangedSinceFetch = facetFetchQueryKey !== this.facetFetchQueryKey;
1234
+ if (queryChangedSinceFetch)
1235
+ return;
1141
1236
  // Unpack the aggregation buckets into a simple map like { 'A': 50, 'B': 25, ... }
1142
1237
  this.prefixFilterCountMap = { ...this.prefixFilterCountMap }; // Clone the object to trigger an update
1143
1238
  this.prefixFilterCountMap[filterType] = buckets.reduce((acc, bucket) => {
@@ -1221,6 +1316,9 @@ let CollectionBrowser = class CollectionBrowser extends LitElement {
1221
1316
  CollectionBrowser.styles = css `
1222
1317
  :host {
1223
1318
  display: block;
1319
+
1320
+ --leftColumnWidth: 18rem;
1321
+ --leftColumnPaddingRight: 2.5rem;
1224
1322
  }
1225
1323
 
1226
1324
  /**
@@ -1281,15 +1379,91 @@ CollectionBrowser.styles = css `
1281
1379
  }
1282
1380
 
1283
1381
  #left-column {
1284
- width: 18rem;
1285
- min-width: 18rem; /* Prevents Safari from shrinking col at first draw */
1286
- padding-right: 12px;
1287
- padding-right: 2.5rem;
1382
+ width: var(--leftColumnWidth, 18rem);
1383
+ /* Prevents Safari from shrinking col at first draw */
1384
+ min-width: var(--leftColumnWidth, 18rem);
1385
+ padding-top: 0;
1386
+ /* Reduced padding by 0.2rem to add the invisible border in the rule below */
1387
+ padding-right: calc(var(--leftColumnPaddingRight, 2.5rem) - 0.2rem);
1388
+ border-right: 0.2rem solid transparent; /* Pads to the right of the scrollbar a bit */
1288
1389
  z-index: 1;
1289
1390
  }
1290
1391
 
1392
+ .desktop #left-column {
1393
+ top: 0;
1394
+ position: sticky;
1395
+ height: calc(100vh - 2rem);
1396
+ max-height: calc(100vh - 2rem);
1397
+ overflow-x: hidden;
1398
+ overflow-y: scroll;
1399
+
1400
+ /*
1401
+ * Firefox doesn't support any of the -webkit-scrollbar stuff below, but
1402
+ * does at least give us a tiny bit of control over width & color.
1403
+ */
1404
+ scrollbar-width: thin;
1405
+ scrollbar-color: transparent transparent;
1406
+ }
1407
+ .desktop #left-column:hover {
1408
+ scrollbar-color: auto;
1409
+ }
1291
1410
  .desktop #left-column::-webkit-scrollbar {
1292
- display: none;
1411
+ appearance: none;
1412
+ width: 6px;
1413
+ }
1414
+ .desktop #left-column::-webkit-scrollbar-button {
1415
+ height: 3px;
1416
+ background: transparent;
1417
+ }
1418
+ .desktop #left-column::-webkit-scrollbar-corner {
1419
+ background: transparent;
1420
+ }
1421
+ .desktop #left-column::-webkit-scrollbar-thumb {
1422
+ border-radius: 4px;
1423
+ }
1424
+ .desktop #left-column:hover::-webkit-scrollbar-thumb {
1425
+ background: rgba(0, 0, 0, 0.15);
1426
+ }
1427
+ .desktop #left-column:hover::-webkit-scrollbar-thumb:hover {
1428
+ background: rgba(0, 0, 0, 0.2);
1429
+ }
1430
+ .desktop #left-column:hover::-webkit-scrollbar-thumb:active {
1431
+ background: rgba(0, 0, 0, 0.3);
1432
+ }
1433
+
1434
+ #facets-bottom-fade {
1435
+ background: linear-gradient(
1436
+ to bottom,
1437
+ #f5f5f700 0%,
1438
+ #f5f5f7c0 50%,
1439
+ #f5f5f7 80%,
1440
+ #f5f5f7 100%
1441
+ );
1442
+ position: fixed;
1443
+ bottom: 0;
1444
+ height: 50px;
1445
+ /* Wide enough to cover the content, but leave the scrollbar uncovered */
1446
+ width: calc(
1447
+ var(--leftColumnWidth) + var(--leftColumnPaddingRight) - 10px
1448
+ );
1449
+ z-index: 2;
1450
+ pointer-events: none;
1451
+ transition: height 0.1s ease;
1452
+ }
1453
+ #facets-bottom-fade.hidden {
1454
+ height: 0;
1455
+ }
1456
+
1457
+ .desktop #left-column-scroll-sentinel {
1458
+ width: 1px;
1459
+ height: 100vh;
1460
+ background: transparent;
1461
+ }
1462
+
1463
+ .desktop #facets-scroll-sentinel {
1464
+ width: 1px;
1465
+ height: 1px;
1466
+ background: transparent;
1293
1467
  }
1294
1468
 
1295
1469
  .mobile #left-column {
@@ -1297,21 +1471,16 @@ CollectionBrowser.styles = css `
1297
1471
  padding: 0;
1298
1472
  }
1299
1473
 
1300
- .desktop #left-column {
1301
- top: 0;
1302
- position: sticky;
1303
- max-height: 100vh;
1304
- overflow: scroll;
1305
- -ms-overflow-style: none; /* hide scrollbar IE and Edge */
1306
- scrollbar-width: none; /* hide scrollbar Firefox */
1307
- }
1308
-
1309
1474
  #mobile-header-container {
1310
1475
  display: flex;
1311
1476
  justify-content: space-between;
1312
1477
  align-items: center;
1313
1478
  }
1314
1479
 
1480
+ .desktop #mobile-header-container {
1481
+ padding-top: 2rem;
1482
+ }
1483
+
1315
1484
  #facets-container {
1316
1485
  position: relative;
1317
1486
  max-height: 0;
@@ -1485,12 +1654,6 @@ __decorate([
1485
1654
  __decorate([
1486
1655
  property({ type: Object })
1487
1656
  ], CollectionBrowser.prototype, "resizeObserver", void 0);
1488
- __decorate([
1489
- property({ type: String })
1490
- ], CollectionBrowser.prototype, "titleQuery", void 0);
1491
- __decorate([
1492
- property({ type: String })
1493
- ], CollectionBrowser.prototype, "creatorQuery", void 0);
1494
1657
  __decorate([
1495
1658
  property({ type: Number })
1496
1659
  ], CollectionBrowser.prototype, "currentPage", void 0);
@@ -1575,6 +1738,9 @@ __decorate([
1575
1738
  __decorate([
1576
1739
  query('#content-container')
1577
1740
  ], CollectionBrowser.prototype, "contentContainer", void 0);
1741
+ __decorate([
1742
+ query('#left-column')
1743
+ ], CollectionBrowser.prototype, "leftColumn", void 0);
1578
1744
  __decorate([
1579
1745
  property({ type: Object, attribute: false })
1580
1746
  ], CollectionBrowser.prototype, "analyticsHandler", void 0);