@internetarchive/collection-browser 4.2.0-alpha-webdev8164.3 → 4.2.0

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 (84) hide show
  1. package/.claude/settings.local.json +11 -0
  2. package/.editorconfig +29 -29
  3. package/.github/workflows/ci.yml +27 -27
  4. package/.github/workflows/gh-pages-main.yml +39 -39
  5. package/.github/workflows/npm-publish.yml +39 -39
  6. package/.github/workflows/pr-preview.yml +38 -38
  7. package/.husky/pre-commit +1 -1
  8. package/.prettierignore +1 -1
  9. package/LICENSE +661 -661
  10. package/README.md +83 -83
  11. package/dist/src/app-root.js +4 -0
  12. package/dist/src/app-root.js.map +1 -1
  13. package/dist/src/collection-browser.js +32 -27
  14. package/dist/src/collection-browser.js.map +1 -1
  15. package/dist/src/collection-facets/facets-template.js +0 -5
  16. package/dist/src/collection-facets/facets-template.js.map +1 -1
  17. package/dist/src/collection-facets/more-facets-content.d.ts +8 -106
  18. package/dist/src/collection-facets/more-facets-content.js +103 -612
  19. package/dist/src/collection-facets/more-facets-content.js.map +1 -1
  20. package/dist/src/collection-facets/more-facets-pagination.d.ts +3 -12
  21. package/dist/src/collection-facets/more-facets-pagination.js +9 -71
  22. package/dist/src/collection-facets/more-facets-pagination.js.map +1 -1
  23. package/dist/src/collection-facets/toggle-switch.js +0 -1
  24. package/dist/src/collection-facets/toggle-switch.js.map +1 -1
  25. package/dist/src/collection-facets.js +9 -10
  26. package/dist/src/collection-facets.js.map +1 -1
  27. package/dist/src/data-source/collection-browser-data-source.js +3 -2
  28. package/dist/src/data-source/collection-browser-data-source.js.map +1 -1
  29. package/dist/src/mediatype/mediatype-config.js +1 -1
  30. package/dist/src/mediatype/mediatype-config.js.map +1 -1
  31. package/dist/src/models.d.ts +12 -2
  32. package/dist/src/models.js +13 -8
  33. package/dist/src/models.js.map +1 -1
  34. package/dist/src/restoration-state-handler.js +9 -3
  35. package/dist/src/restoration-state-handler.js.map +1 -1
  36. package/dist/src/tiles/hover/hover-pane-controller.js +2 -1
  37. package/dist/src/tiles/hover/hover-pane-controller.js.map +1 -1
  38. package/dist/src/tiles/tile-dispatcher.d.ts +6 -0
  39. package/dist/src/tiles/tile-dispatcher.js +11 -3
  40. package/dist/src/tiles/tile-dispatcher.js.map +1 -1
  41. package/dist/test/collection-browser.test.js +72 -0
  42. package/dist/test/collection-browser.test.js.map +1 -1
  43. package/dist/test/collection-facets/more-facets-content.test.js +3 -212
  44. package/dist/test/collection-facets/more-facets-content.test.js.map +1 -1
  45. package/dist/test/collection-facets/more-facets-pagination.test.js +3 -63
  46. package/dist/test/collection-facets/more-facets-pagination.test.js.map +1 -1
  47. package/dist/test/mocks/mock-search-responses.d.ts +0 -5
  48. package/dist/test/mocks/mock-search-responses.js +0 -44
  49. package/dist/test/mocks/mock-search-responses.js.map +1 -1
  50. package/dist/test/mocks/mock-search-service.js +1 -2
  51. package/dist/test/mocks/mock-search-service.js.map +1 -1
  52. package/dist/test/tiles/tile-dispatcher.test.js +14 -0
  53. package/dist/test/tiles/tile-dispatcher.test.js.map +1 -1
  54. package/dist/test/tiles/tile-mediatype-icon.test.js +4 -4
  55. package/dist/test/tiles/tile-mediatype-icon.test.js.map +1 -1
  56. package/eslint.config.mjs +53 -53
  57. package/index.html +24 -24
  58. package/local.archive.org.cert +86 -86
  59. package/local.archive.org.key +27 -27
  60. package/package.json +120 -121
  61. package/renovate.json +6 -6
  62. package/src/app-root.ts +4 -0
  63. package/src/collection-browser.ts +43 -36
  64. package/src/collection-facets/facets-template.ts +0 -5
  65. package/src/collection-facets/more-facets-content.ts +113 -662
  66. package/src/collection-facets/more-facets-pagination.ts +10 -84
  67. package/src/collection-facets/toggle-switch.ts +0 -1
  68. package/src/collection-facets.ts +13 -10
  69. package/src/data-source/collection-browser-data-source.ts +3 -2
  70. package/src/mediatype/mediatype-config.ts +1 -1
  71. package/src/models.ts +30 -8
  72. package/src/restoration-state-handler.ts +7 -3
  73. package/src/tiles/hover/hover-pane-controller.ts +2 -1
  74. package/src/tiles/tile-dispatcher.ts +12 -3
  75. package/test/collection-browser.test.ts +105 -0
  76. package/test/collection-facets/more-facets-content.test.ts +4 -326
  77. package/test/collection-facets/more-facets-pagination.test.ts +3 -87
  78. package/test/mocks/mock-search-responses.ts +0 -48
  79. package/test/mocks/mock-search-service.ts +0 -2
  80. package/test/tiles/tile-dispatcher.test.ts +17 -0
  81. package/test/tiles/tile-mediatype-icon.test.ts +4 -4
  82. package/tsconfig.json +25 -25
  83. package/web-dev-server.config.mjs +30 -30
  84. package/web-test-runner.config.mjs +52 -52
@@ -1,28 +1,17 @@
1
- var MoreFacetsContent_1;
2
1
  import { __decorate } from "tslib";
3
2
  import { css, html, LitElement, nothing, } from 'lit';
4
- import { customElement, property, query, state } from 'lit/decorators.js';
5
- import { classMap } from 'lit/directives/class-map.js';
6
- import { when } from 'lit/directives/when.js';
3
+ import { customElement, property, state } from 'lit/decorators.js';
7
4
  import { SearchType, AggregationSortType, } from '@internetarchive/search-service';
8
5
  import { msg } from '@lit/localize';
9
6
  import { facetTitles, suppressedCollections, valueFacetSort, defaultFacetSort, getDefaultSelectedFacets, tvMoreFacetSort, } from '../models';
10
7
  import '@internetarchive/elements/ia-status-indicator/ia-status-indicator';
8
+ import './more-facets-pagination';
11
9
  import './facets-template';
12
10
  import { analyticsActions, analyticsCategories, } from '../utils/analytics-events';
13
11
  import './toggle-switch';
14
- import './more-facets-pagination';
15
- import '@internetarchive/ia-clearable-text-input';
16
- import arrowLeftIcon from '../assets/img/icons/arrow-left';
17
- import arrowRightIcon from '../assets/img/icons/arrow-right';
18
12
  import { srOnlyStyle } from '../styles/sr-only';
19
13
  import { mergeSelectedFacets, sortBucketsBySelectionState, updateSelectedFacetBucket, } from '../utils/facet-utils';
20
14
  import { MORE_FACETS__DEFAULT_PAGE_SIZE, MORE_FACETS__MAX_AGGREGATIONS, } from './models';
21
- /**
22
- * Threshold for switching from horizontal scroll to pagination.
23
- * If facet count >= this value, use pagination. Otherwise use horizontal scroll.
24
- */
25
- const PAGINATION_THRESHOLD = 1000;
26
15
  let MoreFacetsContent = class MoreFacetsContent extends LitElement {
27
16
  constructor() {
28
17
  super(...arguments);
@@ -45,43 +34,9 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
45
34
  */
46
35
  this.unappliedFacetChanges = getDefaultSelectedFacets();
47
36
  /**
48
- * Text entered by the user to filter facet buckets.
49
- * Applied to bucket.key for case-insensitive matching.
50
- */
51
- this.filterText = '';
52
- /**
53
- * Current page number for pagination (when facet count >= PAGINATION_THRESHOLD).
37
+ * Which page of facets we are showing.
54
38
  */
55
39
  this.pageNumber = 1;
56
- /**
57
- * Whether the component is narrow enough to warrant compact pagination.
58
- * Updated via a ResizeObserver-based container query approach.
59
- */
60
- this.isCompactView = false;
61
- /**
62
- * Whether the horizontal scroll is at the leftmost position.
63
- */
64
- this.atScrollStart = true;
65
- /**
66
- * Whether the horizontal scroll is at the rightmost position.
67
- */
68
- this.atScrollEnd = true;
69
- this.scrollHandler = () => this.updateScrollState();
70
- this.scrollListenerAttached = false;
71
- /**
72
- * Close more facets modal on Escape click
73
- */
74
- this.escapeHandler = (e) => {
75
- if (e.key === 'Escape')
76
- this.modalManager?.closeModal();
77
- };
78
- }
79
- static { MoreFacetsContent_1 = this; }
80
- /** Column gap in px — matches the --facetsColumnGap default (never overridden). */
81
- static { this.COLUMN_GAP = 15; }
82
- /** Column count derived from the same breakpoint as the CSS media query. */
83
- get columnCount() {
84
- return this.isCompactView ? 1 : 3;
85
40
  }
86
41
  willUpdate(changed) {
87
42
  if (changed.has('aggregations') ||
@@ -93,12 +48,6 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
93
48
  // store it for reuse across pages.
94
49
  this.facetGroup = this.mergedFacets;
95
50
  }
96
- // Reset to page 1 when filter text changes (only matters for pagination mode)
97
- if (changed.has('filterText')) {
98
- this.pageNumber = 1;
99
- }
100
- }
101
- updated(changed) {
102
51
  // If any of the search properties change, it triggers a facet fetch
103
52
  if (changed.has('facetKey') ||
104
53
  changed.has('query') ||
@@ -112,170 +61,23 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
112
61
  : defaultFacetSort[this.facetKey];
113
62
  this.updateSpecificFacets();
114
63
  }
115
- // Reset horizontal scroll when filter text changes (e.g., switching from
116
- // horizontal-scroll mode back to pagination mode)
117
- if (changed.has('filterText')) {
118
- const facetsContent = this.shadowRoot?.querySelector('.facets-content');
119
- if (facetsContent) {
120
- facetsContent.scrollLeft = 0;
121
- }
122
- }
123
- // Manage scroll listener for horizontal scroll mode arrows.
124
- // Only re-evaluate when properties that affect the displayed content change.
125
- if (changed.has('filterText') ||
126
- changed.has('aggregations') ||
127
- changed.has('facetKey') ||
128
- changed.has('sortedBy') ||
129
- changed.has('selectedFacets') ||
130
- changed.has('unappliedFacetChanges')) {
131
- if (!this.usePagination) {
132
- this.attachScrollListener();
133
- // Refresh scroll state whenever content may have changed (e.g., filtering)
134
- requestAnimationFrame(() => this.updateScrollState());
135
- }
136
- else {
137
- this.removeScrollListener();
138
- }
139
- }
140
64
  }
141
65
  firstUpdated() {
142
66
  this.setupEscapeListeners();
143
- this.setupCompactViewObserver();
144
- this.constrainToScrollContainer();
145
- }
146
- disconnectedCallback() {
147
- super.disconnectedCallback();
148
- this.resizeObserver?.disconnect();
149
- this.removeScrollListener();
150
- document.removeEventListener('keydown', this.escapeHandler);
151
- }
152
- /**
153
- * Attaches a scroll event listener to the facets content element
154
- * to track horizontal scroll position for arrow button states.
155
- */
156
- attachScrollListener() {
157
- if (this.scrollListenerAttached || !this.facetsContentEl)
158
- return;
159
- this.scrollListenerTarget = this.facetsContentEl;
160
- this.scrollListenerTarget.addEventListener('scroll', this.scrollHandler, {
161
- passive: true,
162
- });
163
- this.scrollListenerAttached = true;
164
- // Defer initial state check until after browser layout, so scrollWidth
165
- // reflects the actual content dimensions.
166
- requestAnimationFrame(() => this.updateScrollState());
167
- }
168
- removeScrollListener() {
169
- if (!this.scrollListenerAttached || !this.scrollListenerTarget)
170
- return;
171
- this.scrollListenerTarget.removeEventListener('scroll', this.scrollHandler);
172
- this.scrollListenerTarget = undefined;
173
- this.scrollListenerAttached = false;
174
- }
175
- /**
176
- * Updates the scroll arrow disabled states based on current scroll position.
177
- */
178
- updateScrollState() {
179
- const el = this.facetsContentEl;
180
- if (!el)
181
- return;
182
- this.atScrollStart = el.scrollLeft <= 0;
183
- this.atScrollEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 1;
184
- }
185
- /**
186
- * Calculates the width of one column step (column width + gap) based on
187
- * the CSS multi-column layout of the scroll container.
188
- */
189
- getColumnStep() {
190
- const el = this.facetsContentEl;
191
- if (!el)
192
- return 0;
193
- // Column step = column width + gap = (visible width + gap) / column count
194
- return (el.clientWidth + MoreFacetsContent_1.COLUMN_GAP) / this.columnCount;
195
- }
196
- /**
197
- * Snaps a scroll target to the nearest column boundary.
198
- */
199
- snapToColumn(target) {
200
- const step = this.getColumnStep();
201
- if (step <= 0)
202
- return target;
203
- return Math.round(target / step) * step;
204
- }
205
- /**
206
- * Scrolls the facet content left by approximately one page, snapping to
207
- * the nearest column boundary.
208
- */
209
- onScrollLeft() {
210
- const el = this.facetsContentEl;
211
- if (!el)
212
- return;
213
- const rawTarget = el.scrollLeft - el.clientWidth;
214
- const snapped = Math.max(0, this.snapToColumn(rawTarget));
215
- el.scrollTo({ left: snapped, behavior: 'smooth' });
216
- }
217
- /**
218
- * Scrolls the facet content right by approximately one page, snapping to
219
- * the nearest column boundary.
220
- */
221
- onScrollRight() {
222
- const el = this.facetsContentEl;
223
- if (!el)
224
- return;
225
- const maxScroll = el.scrollWidth - el.clientWidth;
226
- const rawTarget = el.scrollLeft + el.clientWidth;
227
- const snapped = Math.min(maxScroll, this.snapToColumn(rawTarget));
228
- el.scrollTo({ left: snapped, behavior: 'smooth' });
229
67
  }
230
68
  /**
231
- * Sets up a ResizeObserver to toggle compact pagination based on component width.
69
+ * Close more facets modal on Escape click
232
70
  */
233
- setupCompactViewObserver() {
234
- this.resizeObserver = new ResizeObserver(entries => {
235
- for (const entry of entries) {
236
- const compact = entry.contentRect.width <= 560;
237
- if (this.isCompactView !== compact)
238
- this.isCompactView = compact;
239
- }
240
- });
241
- this.resizeObserver.observe(this);
242
- }
243
- /**
244
- * Constrains the section's max-height to fit within the nearest
245
- * scroll-container ancestor (e.g., the modal's content area).
246
- * This is a safety net for cases where the CSS max-height calculation
247
- * doesn't perfectly match the container's available space.
248
- */
249
- constrainToScrollContainer() {
250
- requestAnimationFrame(() => {
251
- const section = this.shadowRoot?.querySelector('section#more-facets');
252
- if (!section)
253
- return;
254
- // Walk up from the assigned slot to find the nearest overflow container
255
- let el = this.assignedSlot?.parentElement;
256
- while (el) {
257
- const cs = getComputedStyle(el);
258
- if (cs.overflowY === 'auto' ||
259
- cs.overflowY === 'scroll' ||
260
- cs.overflowY === 'hidden') {
261
- const containerBottom = el.getBoundingClientRect().bottom;
262
- const sectionTop = section.getBoundingClientRect().top;
263
- const available = containerBottom - sectionTop;
264
- // Compare against the CSS max-height rather than actual height,
265
- // since content may not have loaded yet at firstUpdated time
266
- const computedMax = parseFloat(getComputedStyle(section).maxHeight);
267
- if (available > 0 && available < computedMax) {
268
- section.style.maxHeight = `${available}px`;
269
- }
270
- return;
271
- }
272
- el = el.parentElement;
273
- }
274
- });
275
- }
276
71
  setupEscapeListeners() {
277
72
  if (this.modalManager) {
278
- document.addEventListener('keydown', this.escapeHandler);
73
+ document.addEventListener('keydown', (e) => {
74
+ if (e.key === 'Escape') {
75
+ this.modalManager?.closeModal();
76
+ }
77
+ });
78
+ }
79
+ else {
80
+ document.removeEventListener('keydown', () => { });
279
81
  }
280
82
  }
281
83
  /**
@@ -310,19 +112,29 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
310
112
  aggregationsSize,
311
113
  rows: 0, // todo - do we want server-side pagination with offset/page/limit flag?
312
114
  };
313
- try {
314
- const results = await this.searchService?.search(params, this.searchType);
315
- this.aggregations = results?.success?.response.aggregations;
316
- const collectionTitles = results?.success?.response?.collectionTitles;
317
- if (collectionTitles) {
318
- for (const [id, title] of Object.entries(collectionTitles)) {
319
- this.collectionTitles?.set(id, title);
320
- }
115
+ const results = await this.searchService?.search(params, this.searchType);
116
+ this.aggregations = results?.success?.response.aggregations;
117
+ this.facetsLoading = false;
118
+ const collectionTitles = results?.success?.response?.collectionTitles;
119
+ if (collectionTitles) {
120
+ for (const [id, title] of Object.entries(collectionTitles)) {
121
+ this.collectionTitles?.set(id, title);
321
122
  }
322
123
  }
323
- finally {
324
- this.facetsLoading = false;
124
+ }
125
+ /**
126
+ * Handler for page number changes from the pagination widget.
127
+ */
128
+ pageNumberClicked(e) {
129
+ const page = e?.detail?.page;
130
+ if (page) {
131
+ this.pageNumber = Number(page);
325
132
  }
133
+ this.analyticsHandler?.sendEvent({
134
+ category: analyticsCategories.default,
135
+ action: analyticsActions.moreFacetsPageChange,
136
+ label: `${this.pageNumber}`,
137
+ });
326
138
  }
327
139
  /**
328
140
  * Combines the selected facets with the aggregations to create a single list of facets
@@ -390,9 +202,7 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
390
202
  return undefined;
391
203
  const facetGroupTitle = facetTitles[this.facetKey];
392
204
  const buckets = Object.entries(selectedFacetsForKey).map(([value, data]) => {
393
- const displayText = (this.facetKey === 'collection'
394
- ? this.collectionTitles?.get(value)
395
- : undefined) ?? value;
205
+ const displayText = value;
396
206
  return {
397
207
  displayText,
398
208
  key: value,
@@ -426,26 +236,16 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
426
236
  return (!suppressedCollections[bucketKey] && !bucketKey?.startsWith('fav-'));
427
237
  });
428
238
  }
429
- // Construct the array of facet buckets from the aggregation buckets,
430
- // using collection display titles where available.
239
+ // Construct the array of facet buckets from the aggregation buckets
431
240
  const facetBuckets = sortedBuckets.map(bucket => {
432
241
  const bucketKeyStr = `${bucket.key}`;
433
- const displayText = (this.facetKey === 'collection'
434
- ? this.collectionTitles?.get(bucketKeyStr)
435
- : undefined) ?? bucketKeyStr;
436
242
  return {
437
- displayText,
243
+ displayText: `${bucketKeyStr}`,
438
244
  key: `${bucketKeyStr}`,
439
245
  count: bucket.doc_count,
440
246
  state: 'none',
441
247
  };
442
248
  });
443
- // For collection facets sorted alphabetically, re-sort by display title
444
- // instead of the raw identifier used by getSortedBuckets.
445
- if (this.facetKey === 'collection' &&
446
- this.sortedBy === AggregationSortType.ALPHABETICAL) {
447
- facetBuckets.sort((a, b) => (a.displayText ?? a.key).localeCompare(b.displayText ?? b.key));
448
- }
449
249
  return {
450
250
  title: facetGroupTitle,
451
251
  key: this.facetKey,
@@ -453,70 +253,24 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
453
253
  };
454
254
  }
455
255
  /**
456
- * Returns the facet group with buckets filtered by the current filter text.
457
- * Filters are applied to the full bucket list before pagination.
256
+ * Returns a FacetGroup representing only the current page of facet buckets to show.
458
257
  */
459
- get filteredFacetGroup() {
460
- const { facetGroup, filterText } = this;
258
+ get facetGroupForCurrentPage() {
259
+ const { facetGroup } = this;
461
260
  if (!facetGroup)
462
261
  return undefined;
463
- // If no filter text, return the full group
464
- if (!filterText.trim()) {
465
- return facetGroup;
466
- }
467
- // Filter buckets by the text the user actually sees.
468
- // For collections, match against the displayed collection title (not the identifier).
469
- // For other facet types, match against the bucket key (which is also the display text).
470
- const lowerFilter = filterText.toLowerCase().trim();
471
- const filteredBuckets = facetGroup.buckets.filter(bucket => {
472
- const displayText = this.collectionTitles?.get(bucket.key) ?? bucket.key;
473
- return displayText.toLowerCase().includes(lowerFilter);
474
- });
262
+ // Slice out only the current page of facet buckets
263
+ const firstBucketIndexOnPage = (this.pageNumber - 1) * this.facetsPerPage;
264
+ const truncatedBuckets = facetGroup.buckets.slice(firstBucketIndexOnPage, firstBucketIndexOnPage + this.facetsPerPage);
475
265
  return {
476
266
  ...facetGroup,
477
- buckets: filteredBuckets,
478
- };
479
- }
480
- /**
481
- * Determines whether to use pagination based on the number of filtered facets.
482
- * Returns true if facet count >= PAGINATION_THRESHOLD, false otherwise.
483
- */
484
- get usePagination() {
485
- const facetCount = this.filteredFacetGroup?.buckets.length ?? 0;
486
- return facetCount >= PAGINATION_THRESHOLD;
487
- }
488
- /**
489
- * Returns the facet group for the current page.
490
- * If using pagination (>= 1000 facets), slices to show only the current page.
491
- * Otherwise, returns all facets for horizontal scrolling.
492
- */
493
- get facetGroupForCurrentPage() {
494
- const filteredGroup = this.filteredFacetGroup;
495
- if (!filteredGroup)
496
- return undefined;
497
- // If facet count is below threshold, show all facets with horizontal scroll
498
- if (!this.usePagination) {
499
- return filteredGroup;
500
- }
501
- // Otherwise, use pagination - slice to current page
502
- const startIndex = (this.pageNumber - 1) * this.facetsPerPage;
503
- const endIndex = startIndex + this.facetsPerPage;
504
- const slicedBuckets = filteredGroup.buckets.slice(startIndex, endIndex);
505
- return {
506
- ...filteredGroup,
507
- buckets: slicedBuckets,
267
+ buckets: truncatedBuckets,
508
268
  };
509
269
  }
510
270
  get moreFacetsTemplate() {
511
- const facetGroup = this.facetGroupForCurrentPage;
512
- // Show empty state if filtering returned no results
513
- if (this.filterText.trim() &&
514
- (!facetGroup || facetGroup.buckets.length === 0)) {
515
- return this.emptyFilterResultsTemplate;
516
- }
517
271
  return html `
518
272
  <facets-template
519
- .facetGroup=${facetGroup}
273
+ .facetGroup=${this.facetGroupForCurrentPage}
520
274
  .selectedFacets=${this.selectedFacets}
521
275
  .collectionTitles=${this.collectionTitles}
522
276
  @facetClick=${(e) => {
@@ -535,171 +289,83 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
535
289
  ></ia-status-indicator>
536
290
  `;
537
291
  }
538
- get emptyFilterResultsTemplate() {
539
- return html `
540
- <div class="empty-results">
541
- <p>${msg('No matching values found.')}</p>
542
- <p class="hint">${msg('Try a different search term.')}</p>
543
- </div>
544
- `;
545
- }
546
292
  /**
547
- * Number of pages for pagination (only used when facet count >= PAGINATION_THRESHOLD).
293
+ * How many pages of facets to show in the modal pagination widget
548
294
  */
549
295
  get paginationSize() {
550
- const filteredBuckets = this.filteredFacetGroup?.buckets ?? [];
551
- return Math.ceil(filteredBuckets.length / this.facetsPerPage);
296
+ if (!this.aggregations || !this.facetKey)
297
+ return 0;
298
+ // Calculate the appropriate number of pages to show in the modal pagination widget
299
+ const length = this.aggregations[this.facetKey]?.buckets.length;
300
+ return Math.ceil(length / this.facetsPerPage);
552
301
  }
553
- /**
554
- * Template for pagination component.
555
- */
302
+ // render pagination if more then 1 page
556
303
  get facetsPaginationTemplate() {
557
- return html `<more-facets-pagination
558
- .size=${this.paginationSize}
559
- .currentPage=${this.pageNumber}
560
- .compact=${this.isCompactView}
561
- @pageNumberClicked=${this.pageNumberClicked}
562
- ></more-facets-pagination>`;
304
+ return this.paginationSize > 1
305
+ ? html `<more-facets-pagination
306
+ .size=${this.paginationSize}
307
+ .currentPage=${1}
308
+ @pageNumberClicked=${this.pageNumberClicked}
309
+ ></more-facets-pagination>`
310
+ : nothing;
563
311
  }
564
312
  get footerTemplate() {
565
- return html `
566
- ${when(this.usePagination, () => this.facetsPaginationTemplate)}
567
- <div class="footer">
568
- <button class="btn btn-cancel" type="button" @click=${this.cancelClick}>
569
- Cancel
570
- </button>
571
- <button
572
- class="btn btn-submit"
573
- type="button"
574
- @click=${this.applySearchFacetsClicked}
575
- >
576
- Apply filters
577
- </button>
578
- </div>
579
- `;
313
+ if (this.paginationSize > 0) {
314
+ return html `${this.facetsPaginationTemplate}
315
+ <div class="footer">
316
+ <button
317
+ class="btn btn-cancel"
318
+ type="button"
319
+ @click=${this.cancelClick}
320
+ >
321
+ Cancel
322
+ </button>
323
+ <button
324
+ class="btn btn-submit"
325
+ type="button"
326
+ @click=${this.applySearchFacetsClicked}
327
+ >
328
+ Apply filters
329
+ </button>
330
+ </div> `;
331
+ }
332
+ return nothing;
580
333
  }
581
334
  sortFacetAggregation(facetSortType) {
582
335
  this.sortedBy = facetSortType;
583
336
  this.dispatchEvent(new CustomEvent('sortedFacets', { detail: this.sortedBy }));
584
337
  }
585
- /**
586
- * Handler for filter input changes. Updates the filter text and triggers re-render.
587
- */
588
- handleFilterInput(e) {
589
- const input = e.target;
590
- this.filterText = input.value;
591
- }
592
- /**
593
- * Handler for when the filter input is cleared via the clear button.
594
- */
595
- handleFilterClear() {
596
- this.filterText = '';
597
- }
598
- /**
599
- * Handler for pagination page number clicks.
600
- * Only used when facet count >= PAGINATION_THRESHOLD.
601
- */
602
- pageNumberClicked(e) {
603
- this.pageNumber = e.detail.page;
604
- // Track page navigation in analytics
605
- this.analyticsHandler?.sendEvent({
606
- category: analyticsCategories.default,
607
- action: analyticsActions.moreFacetsPageChange,
608
- label: `${this.pageNumber}`,
609
- });
610
- this.dispatchEvent(new CustomEvent('pageChanged', {
611
- detail: this.pageNumber,
612
- bubbles: true,
613
- composed: true,
614
- }));
615
- }
616
338
  get modalHeaderTemplate() {
617
339
  const facetSort = this.sortedBy ?? defaultFacetSort[this.facetKey];
618
340
  const defaultSwitchSide = facetSort === AggregationSortType.COUNT ? 'left' : 'right';
619
341
  return html `<span class="sr-only">${msg('More facets for:')}</span>
620
- <span class="title"> ${this.facetGroup?.title} </span>
621
- <span class="header-controls">
622
- <span class="sort-controls">
623
- <label class="sort-label">${msg('Sort by:')}</label>
624
- ${this.facetKey
342
+ <span class="title">
343
+ ${this.facetGroup?.title}
344
+
345
+ <label class="sort-label">${msg('Sort by:')}</label>
346
+ ${this.facetKey
625
347
  ? html `<toggle-switch
626
- class="sort-toggle"
627
- leftValue=${AggregationSortType.COUNT}
628
- leftLabel="Count"
629
- rightValue=${valueFacetSort[this.facetKey]}
630
- .rightLabel=${this.facetGroup?.title}
631
- side=${defaultSwitchSide}
632
- @change=${(e) => {
348
+ class="sort-toggle"
349
+ leftValue=${AggregationSortType.COUNT}
350
+ leftLabel="Count"
351
+ rightValue=${valueFacetSort[this.facetKey]}
352
+ .rightLabel=${this.facetGroup?.title}
353
+ side=${defaultSwitchSide}
354
+ @change=${(e) => {
633
355
  this.sortFacetAggregation(Number(e.detail));
634
356
  }}
635
- ></toggle-switch>`
357
+ ></toggle-switch>`
636
358
  : nothing}
637
- </span>
638
-
639
- <span class="filter-controls">
640
- <label class="filter-label">${msg('Filter by:')}</label>
641
- <ia-clearable-text-input
642
- class="filter-input"
643
- .value=${this.filterText}
644
- .placeholder=${msg('Search...')}
645
- .screenReaderLabel=${msg('Filter facets')}
646
- .clearButtonScreenReaderLabel=${msg('Clear filter')}
647
- @input=${this.handleFilterInput}
648
- @clear=${this.handleFilterClear}
649
- ></ia-clearable-text-input>
650
- </span>
651
359
  </span>`;
652
360
  }
653
- get horizontalScrollTemplate() {
654
- const contentClasses = classMap({
655
- 'facets-content': true,
656
- 'horizontal-scroll-mode': true,
657
- });
658
- const showArrows = !this.atScrollStart || !this.atScrollEnd;
659
- return html `<div class="scroll-nav-container">
660
- ${when(showArrows, () => html `<button
661
- class="scroll-arrow scroll-left"
662
- @click=${this.onScrollLeft}
663
- ?disabled=${this.atScrollStart}
664
- aria-label="Scroll facets left"
665
- >
666
- ${arrowLeftIcon}
667
- </button>`)}
668
- <div class=${contentClasses}>
669
- <div class="facets-horizontal-container">
670
- ${this.moreFacetsTemplate}
671
- </div>
672
- </div>
673
- ${when(showArrows, () => html `<button
674
- class="scroll-arrow scroll-right"
675
- @click=${this.onScrollRight}
676
- ?disabled=${this.atScrollEnd}
677
- aria-label="Scroll facets right"
678
- >
679
- ${arrowRightIcon}
680
- </button>`)}
681
- </div>`;
682
- }
683
361
  render() {
684
- const sectionClasses = classMap({
685
- 'pagination-mode': this.usePagination,
686
- 'horizontal-scroll-mode': !this.usePagination,
687
- });
688
- const contentClasses = classMap({
689
- 'facets-content': true,
690
- 'pagination-mode': this.usePagination,
691
- });
692
362
  return html `
693
363
  ${this.facetsLoading
694
364
  ? this.loaderTemplate
695
365
  : html `
696
- <section id="more-facets" class=${sectionClasses}>
366
+ <section id="more-facets">
697
367
  <div class="header-content">${this.modalHeaderTemplate}</div>
698
- ${this.usePagination
699
- ? html `<div class=${contentClasses}>
700
- ${this.moreFacetsTemplate}
701
- </div>`
702
- : this.horizontalScrollTemplate}
368
+ <div class="facets-content">${this.moreFacetsTemplate}</div>
703
369
  ${this.footerTemplate}
704
370
  </section>
705
371
  `}
@@ -715,8 +381,6 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
715
381
  this.dispatchEvent(event);
716
382
  // Reset the unapplied changes back to default, now that they have been applied
717
383
  this.unappliedFacetChanges = getDefaultSelectedFacets();
718
- // Reset filter text
719
- this.filterText = '';
720
384
  this.modalManager?.closeModal();
721
385
  this.analyticsHandler?.sendEvent({
722
386
  category: analyticsCategories.default,
@@ -727,8 +391,6 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
727
391
  cancelClick() {
728
392
  // Reset the unapplied changes back to default
729
393
  this.unappliedFacetChanges = getDefaultSelectedFacets();
730
- // Reset filter text
731
- this.filterText = '';
732
394
  this.modalManager?.closeModal();
733
395
  this.analyticsHandler?.sendEvent({
734
396
  category: analyticsCategories.default,
@@ -742,26 +404,10 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
742
404
  srOnlyStyle,
743
405
  css `
744
406
  section#more-facets {
745
- display: flex;
746
- flex-direction: column;
747
- max-height: calc(100vh - 16.5rem - var(--modalBottomMargin, 2.5rem));
748
- padding: 10px;
749
- box-sizing: border-box;
407
+ overflow: auto;
408
+ padding: 10px; /* leaves room for scroll bar to appear without overlaying on content */
750
409
  --facetsColumnCount: 3;
751
410
  }
752
-
753
- /* Both modes need a height constraint for proper column flow */
754
- section#more-facets.horizontal-scroll-mode,
755
- section#more-facets.pagination-mode {
756
- --facetsMaxHeight: 280px;
757
- }
758
- .header-content {
759
- flex-shrink: 0;
760
- position: relative;
761
- z-index: 1;
762
- background: #fff;
763
- }
764
-
765
411
  .header-content .title {
766
412
  display: block;
767
413
  text-align: left;
@@ -770,22 +416,8 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
770
416
  font-weight: bold;
771
417
  }
772
418
 
773
- .header-controls {
774
- display: flex;
775
- flex-wrap: wrap;
776
- align-items: center;
777
- gap: 8px 20px;
778
- padding: 0 10px 8px;
779
- }
780
-
781
- .sort-controls {
782
- display: inline-flex;
783
- align-items: center;
784
- white-space: nowrap;
785
- gap: 5px;
786
- }
787
-
788
419
  .sort-label {
420
+ margin-left: 20px;
789
421
  font-size: 1.3rem;
790
422
  }
791
423
 
@@ -793,115 +425,11 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
793
425
  font-weight: normal;
794
426
  }
795
427
 
796
- .filter-controls {
797
- display: inline-flex;
798
- align-items: center;
799
- white-space: nowrap;
800
- }
801
-
802
- .filter-label {
803
- font-size: 1.3rem;
804
- }
805
-
806
- .filter-input {
807
- --input-height: 2.5rem;
808
- --input-font-size: 1.3rem;
809
- --input-border-radius: 4px;
810
- --input-padding: 4px 8px;
811
- --input-focused-border-color: ${modalSubmitButton};
812
- width: 150px;
813
- margin-left: 5px;
814
- }
815
-
816
- .empty-results {
817
- text-align: center;
818
- padding: 40px 20px;
819
- color: #666;
820
- }
821
-
822
- .empty-results .hint {
823
- margin-top: 10px;
824
- }
825
-
826
428
  .facets-content {
827
429
  font-size: 1.2rem;
828
- flex: 1 1 auto;
829
- min-height: 0;
830
- overflow-y: auto;
831
- overflow-x: hidden;
430
+ max-height: 300px;
431
+ overflow: auto;
832
432
  padding: 10px;
833
- /* Force scrollbar to always be visible */
834
- scrollbar-width: thin; /* Firefox */
835
- scrollbar-color: #888 #f1f1f1; /* Firefox - thumb and track colors */
836
- }
837
-
838
- /* Horizontal scroll mode: horizontal scrolling only */
839
- .facets-content.horizontal-scroll-mode {
840
- overflow-x: auto;
841
- overflow-y: hidden;
842
- }
843
-
844
- /* Webkit browsers scrollbar styling - always visible */
845
- .facets-content::-webkit-scrollbar {
846
- width: 12px; /* Vertical scrollbar width */
847
- height: 12px; /* Horizontal scrollbar height */
848
- }
849
-
850
- .facets-content::-webkit-scrollbar-track {
851
- background: #f1f1f1;
852
- border-radius: 6px;
853
- }
854
-
855
- .facets-content::-webkit-scrollbar-thumb {
856
- background: #888;
857
- border-radius: 6px;
858
- min-height: 30px; /* Ensure thumb is always visible when scrolling is possible */
859
- }
860
-
861
- .facets-content::-webkit-scrollbar-thumb:hover {
862
- background: #555;
863
- }
864
-
865
- /* Force corner to match track color */
866
- .facets-content::-webkit-scrollbar-corner {
867
- background: #f1f1f1;
868
- }
869
-
870
- .facets-horizontal-container {
871
- display: inline-block;
872
- min-width: 100%;
873
- /* Allow natural width expansion based on content */
874
- width: fit-content;
875
- }
876
-
877
- .scroll-nav-container {
878
- display: flex;
879
- align-items: center;
880
- flex: 1 1 auto;
881
- min-height: 0;
882
- }
883
-
884
- .scroll-nav-container .facets-content {
885
- flex: 1 1 auto;
886
- min-width: 0;
887
- }
888
-
889
- .scroll-arrow {
890
- background: none;
891
- border: none;
892
- cursor: pointer;
893
- padding: 5px;
894
- flex-shrink: 0;
895
- }
896
-
897
- .scroll-arrow svg {
898
- height: 14px;
899
- fill: #2c2c2c;
900
- }
901
-
902
- .scroll-arrow:disabled {
903
- opacity: 0.3;
904
- cursor: default;
905
433
  }
906
434
  .facets-loader {
907
435
  --icon-width: 70px;
@@ -917,7 +445,6 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
917
445
  width: auto;
918
446
  border-radius: 4px;
919
447
  cursor: pointer;
920
- font-family: inherit;
921
448
  }
922
449
  .btn-cancel {
923
450
  background-color: #2c2c2c;
@@ -927,37 +454,19 @@ let MoreFacetsContent = class MoreFacetsContent extends LitElement {
927
454
  background-color: ${modalSubmitButton};
928
455
  color: white;
929
456
  }
930
- more-facets-pagination {
931
- flex-shrink: 0;
932
- }
933
-
934
457
  .footer {
935
458
  text-align: center;
936
459
  margin-top: 10px;
937
- flex-shrink: 0;
938
460
  }
939
461
 
940
462
  @media (max-width: 560px) {
941
- section#more-facets.horizontal-scroll-mode,
942
- section#more-facets.pagination-mode {
943
- --facetsColumnCount: 1; /* Single column on mobile */
944
- --facetsMaxHeight: none; /* Remove fixed height for vertical scrolling */
463
+ section#more-facets {
464
+ max-height: 450px;
465
+ --facetsColumnCount: 1;
945
466
  }
946
- /* On mobile, always use vertical scrolling regardless of mode */
947
- .facets-content,
948
- .facets-content.horizontal-scroll-mode {
467
+ .facets-content {
949
468
  overflow-y: auto;
950
- overflow-x: hidden;
951
- }
952
- .scroll-nav-container {
953
- display: contents; /* Remove wrapper from layout so section flex-column works */
954
- }
955
- .scroll-arrow {
956
- display: none;
957
- }
958
- .filter-input {
959
- width: 120px;
960
- --input-font-size: 1.2rem;
469
+ height: 300px;
961
470
  }
962
471
  }
963
472
  `,
@@ -1021,28 +530,10 @@ __decorate([
1021
530
  __decorate([
1022
531
  state()
1023
532
  ], MoreFacetsContent.prototype, "unappliedFacetChanges", void 0);
1024
- __decorate([
1025
- state()
1026
- ], MoreFacetsContent.prototype, "filterText", void 0);
1027
533
  __decorate([
1028
534
  state()
1029
535
  ], MoreFacetsContent.prototype, "pageNumber", void 0);
1030
- __decorate([
1031
- state()
1032
- ], MoreFacetsContent.prototype, "isCompactView", void 0);
1033
- __decorate([
1034
- state()
1035
- ], MoreFacetsContent.prototype, "atScrollStart", void 0);
1036
- __decorate([
1037
- state()
1038
- ], MoreFacetsContent.prototype, "atScrollEnd", void 0);
1039
- __decorate([
1040
- query('ia-clearable-text-input')
1041
- ], MoreFacetsContent.prototype, "filterInput", void 0);
1042
- __decorate([
1043
- query('.facets-content')
1044
- ], MoreFacetsContent.prototype, "facetsContentEl", void 0);
1045
- MoreFacetsContent = MoreFacetsContent_1 = __decorate([
536
+ MoreFacetsContent = __decorate([
1046
537
  customElement('more-facets-content')
1047
538
  ], MoreFacetsContent);
1048
539
  export { MoreFacetsContent };