@internetarchive/collection-browser 4.2.0-alpha-webdev8164.0 → 4.2.0-alpha-webdev8164.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.
- package/dist/src/app-root.js +0 -4
- package/dist/src/app-root.js.map +1 -1
- package/dist/src/collection-facets/more-facets-content.d.ts +10 -0
- package/dist/src/collection-facets/more-facets-content.js +118 -52
- package/dist/src/collection-facets/more-facets-content.js.map +1 -1
- package/dist/src/collection-facets/more-facets-pagination.js +2 -1
- package/dist/src/collection-facets/more-facets-pagination.js.map +1 -1
- package/dist/test/collection-facets/more-facets-content.test.js +50 -0
- package/dist/test/collection-facets/more-facets-content.test.js.map +1 -1
- package/package.json +1 -1
- package/src/app-root.ts +0 -4
- package/src/collection-facets/more-facets-content.ts +139 -58
- package/src/collection-facets/more-facets-pagination.ts +4 -1
- package/test/collection-facets/more-facets-content.test.ts +75 -0
package/src/app-root.ts
CHANGED
|
@@ -950,10 +950,6 @@ export class AppRoot extends LitElement {
|
|
|
950
950
|
--modalBorder: 2px solid var(--primaryButtonBGColor, #194880);
|
|
951
951
|
--modalTitleLineHeight: 4rem;
|
|
952
952
|
--modalTitleFontSize: 1.8rem;
|
|
953
|
-
--modalCornerRadius: 0;
|
|
954
|
-
--modalBottomPadding: 0;
|
|
955
|
-
--modalBottomMargin: 0;
|
|
956
|
-
--modalScrollOffset: 0;
|
|
957
953
|
--modalCornerRadius: 0.5rem;
|
|
958
954
|
}
|
|
959
955
|
modal-manager.expanded-date-picker {
|
|
@@ -161,7 +161,7 @@ export class MoreFacetsContent extends LitElement {
|
|
|
161
161
|
/**
|
|
162
162
|
* Whether the horizontal scroll is at the rightmost position.
|
|
163
163
|
*/
|
|
164
|
-
@state() private atScrollEnd =
|
|
164
|
+
@state() private atScrollEnd = true;
|
|
165
165
|
|
|
166
166
|
@query('ia-clearable-text-input')
|
|
167
167
|
private filterInput!: HTMLElement;
|
|
@@ -240,25 +240,30 @@ export class MoreFacetsContent extends LitElement {
|
|
|
240
240
|
firstUpdated(): void {
|
|
241
241
|
this.setupEscapeListeners();
|
|
242
242
|
this.setupCompactViewObserver();
|
|
243
|
+
this.constrainToScrollContainer();
|
|
243
244
|
}
|
|
244
245
|
|
|
245
246
|
disconnectedCallback(): void {
|
|
246
247
|
super.disconnectedCallback();
|
|
247
248
|
this.resizeObserver?.disconnect();
|
|
248
249
|
this.removeScrollListener();
|
|
250
|
+
document.removeEventListener('keydown', this.escapeHandler);
|
|
249
251
|
}
|
|
250
252
|
|
|
251
253
|
private scrollHandler = () => this.updateScrollState();
|
|
252
254
|
|
|
253
255
|
private scrollListenerAttached = false;
|
|
254
256
|
|
|
257
|
+
private scrollListenerTarget?: HTMLElement;
|
|
258
|
+
|
|
255
259
|
/**
|
|
256
260
|
* Attaches a scroll event listener to the facets content element
|
|
257
261
|
* to track horizontal scroll position for arrow button states.
|
|
258
262
|
*/
|
|
259
263
|
private attachScrollListener(): void {
|
|
260
264
|
if (this.scrollListenerAttached || !this.facetsContentEl) return;
|
|
261
|
-
this.
|
|
265
|
+
this.scrollListenerTarget = this.facetsContentEl;
|
|
266
|
+
this.scrollListenerTarget.addEventListener('scroll', this.scrollHandler, {
|
|
262
267
|
passive: true,
|
|
263
268
|
});
|
|
264
269
|
this.scrollListenerAttached = true;
|
|
@@ -268,8 +273,9 @@ export class MoreFacetsContent extends LitElement {
|
|
|
268
273
|
}
|
|
269
274
|
|
|
270
275
|
private removeScrollListener(): void {
|
|
271
|
-
if (!this.scrollListenerAttached || !this.
|
|
272
|
-
this.
|
|
276
|
+
if (!this.scrollListenerAttached || !this.scrollListenerTarget) return;
|
|
277
|
+
this.scrollListenerTarget.removeEventListener('scroll', this.scrollHandler);
|
|
278
|
+
this.scrollListenerTarget = undefined;
|
|
273
279
|
this.scrollListenerAttached = false;
|
|
274
280
|
}
|
|
275
281
|
|
|
@@ -344,24 +350,61 @@ export class MoreFacetsContent extends LitElement {
|
|
|
344
350
|
private setupCompactViewObserver(): void {
|
|
345
351
|
this.resizeObserver = new ResizeObserver(entries => {
|
|
346
352
|
for (const entry of entries) {
|
|
347
|
-
|
|
353
|
+
const compact = entry.contentRect.width <= 560;
|
|
354
|
+
if (this.isCompactView !== compact) this.isCompactView = compact;
|
|
348
355
|
}
|
|
349
356
|
});
|
|
350
357
|
this.resizeObserver.observe(this);
|
|
351
358
|
}
|
|
352
359
|
|
|
360
|
+
/**
|
|
361
|
+
* Constrains the section's max-height to fit within the nearest
|
|
362
|
+
* scroll-container ancestor (e.g., the modal's content area).
|
|
363
|
+
* This is a safety net for cases where the CSS max-height calculation
|
|
364
|
+
* doesn't perfectly match the container's available space.
|
|
365
|
+
*/
|
|
366
|
+
private constrainToScrollContainer(): void {
|
|
367
|
+
requestAnimationFrame(() => {
|
|
368
|
+
const section = this.shadowRoot?.querySelector(
|
|
369
|
+
'section#more-facets',
|
|
370
|
+
) as HTMLElement;
|
|
371
|
+
if (!section) return;
|
|
372
|
+
|
|
373
|
+
// Walk up from the assigned slot to find the nearest overflow container
|
|
374
|
+
let el = this.assignedSlot?.parentElement;
|
|
375
|
+
while (el) {
|
|
376
|
+
const cs = getComputedStyle(el);
|
|
377
|
+
if (
|
|
378
|
+
cs.overflowY === 'auto' ||
|
|
379
|
+
cs.overflowY === 'scroll' ||
|
|
380
|
+
cs.overflowY === 'hidden'
|
|
381
|
+
) {
|
|
382
|
+
const containerBottom = el.getBoundingClientRect().bottom;
|
|
383
|
+
const sectionTop = section.getBoundingClientRect().top;
|
|
384
|
+
const available = containerBottom - sectionTop;
|
|
385
|
+
// Compare against the CSS max-height rather than actual height,
|
|
386
|
+
// since content may not have loaded yet at firstUpdated time
|
|
387
|
+
const computedMax = parseFloat(getComputedStyle(section).maxHeight);
|
|
388
|
+
if (available > 0 && available < computedMax) {
|
|
389
|
+
section.style.maxHeight = `${available}px`;
|
|
390
|
+
}
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
el = el.parentElement;
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
353
398
|
/**
|
|
354
399
|
* Close more facets modal on Escape click
|
|
355
400
|
*/
|
|
401
|
+
private escapeHandler = (e: KeyboardEvent) => {
|
|
402
|
+
if (e.key === 'Escape') this.modalManager?.closeModal();
|
|
403
|
+
};
|
|
404
|
+
|
|
356
405
|
private setupEscapeListeners() {
|
|
357
406
|
if (this.modalManager) {
|
|
358
|
-
document.addEventListener('keydown',
|
|
359
|
-
if (e.key === 'Escape') {
|
|
360
|
-
this.modalManager?.closeModal();
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
} else {
|
|
364
|
-
document.removeEventListener('keydown', () => {});
|
|
407
|
+
document.addEventListener('keydown', this.escapeHandler);
|
|
365
408
|
}
|
|
366
409
|
}
|
|
367
410
|
|
|
@@ -400,15 +443,18 @@ export class MoreFacetsContent extends LitElement {
|
|
|
400
443
|
rows: 0, // todo - do we want server-side pagination with offset/page/limit flag?
|
|
401
444
|
};
|
|
402
445
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
446
|
+
try {
|
|
447
|
+
const results = await this.searchService?.search(params, this.searchType);
|
|
448
|
+
this.aggregations = results?.success?.response.aggregations;
|
|
406
449
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
450
|
+
const collectionTitles = results?.success?.response?.collectionTitles;
|
|
451
|
+
if (collectionTitles) {
|
|
452
|
+
for (const [id, title] of Object.entries(collectionTitles)) {
|
|
453
|
+
this.collectionTitles?.set(id, title);
|
|
454
|
+
}
|
|
411
455
|
}
|
|
456
|
+
} finally {
|
|
457
|
+
this.facetsLoading = false;
|
|
412
458
|
}
|
|
413
459
|
}
|
|
414
460
|
|
|
@@ -493,7 +539,10 @@ export class MoreFacetsContent extends LitElement {
|
|
|
493
539
|
|
|
494
540
|
const buckets: FacetBucket[] = Object.entries(selectedFacetsForKey).map(
|
|
495
541
|
([value, data]) => {
|
|
496
|
-
const displayText
|
|
542
|
+
const displayText =
|
|
543
|
+
(this.facetKey === 'collection'
|
|
544
|
+
? this.collectionTitles?.get(value)
|
|
545
|
+
: undefined) ?? value;
|
|
497
546
|
return {
|
|
498
547
|
displayText,
|
|
499
548
|
key: value,
|
|
@@ -537,17 +586,33 @@ export class MoreFacetsContent extends LitElement {
|
|
|
537
586
|
});
|
|
538
587
|
}
|
|
539
588
|
|
|
540
|
-
// Construct the array of facet buckets from the aggregation buckets
|
|
589
|
+
// Construct the array of facet buckets from the aggregation buckets,
|
|
590
|
+
// using collection display titles where available.
|
|
541
591
|
const facetBuckets: FacetBucket[] = sortedBuckets.map(bucket => {
|
|
542
592
|
const bucketKeyStr = `${bucket.key}`;
|
|
593
|
+
const displayText =
|
|
594
|
+
(this.facetKey === 'collection'
|
|
595
|
+
? this.collectionTitles?.get(bucketKeyStr)
|
|
596
|
+
: undefined) ?? bucketKeyStr;
|
|
543
597
|
return {
|
|
544
|
-
displayText
|
|
598
|
+
displayText,
|
|
545
599
|
key: `${bucketKeyStr}`,
|
|
546
600
|
count: bucket.doc_count,
|
|
547
601
|
state: 'none',
|
|
548
602
|
};
|
|
549
603
|
});
|
|
550
604
|
|
|
605
|
+
// For collection facets sorted alphabetically, re-sort by display title
|
|
606
|
+
// instead of the raw identifier used by getSortedBuckets.
|
|
607
|
+
if (
|
|
608
|
+
this.facetKey === 'collection' &&
|
|
609
|
+
this.sortedBy === AggregationSortType.ALPHABETICAL
|
|
610
|
+
) {
|
|
611
|
+
facetBuckets.sort((a, b) =>
|
|
612
|
+
(a.displayText ?? a.key).localeCompare(b.displayText ?? b.key),
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
|
|
551
616
|
return {
|
|
552
617
|
title: facetGroupTitle,
|
|
553
618
|
key: this.facetKey,
|
|
@@ -739,7 +804,11 @@ export class MoreFacetsContent extends LitElement {
|
|
|
739
804
|
});
|
|
740
805
|
|
|
741
806
|
this.dispatchEvent(
|
|
742
|
-
new CustomEvent('pageChanged', {
|
|
807
|
+
new CustomEvent('pageChanged', {
|
|
808
|
+
detail: this.pageNumber,
|
|
809
|
+
bubbles: true,
|
|
810
|
+
composed: true,
|
|
811
|
+
}),
|
|
743
812
|
);
|
|
744
813
|
}
|
|
745
814
|
|
|
@@ -786,6 +855,46 @@ export class MoreFacetsContent extends LitElement {
|
|
|
786
855
|
</span>`;
|
|
787
856
|
}
|
|
788
857
|
|
|
858
|
+
private get horizontalScrollTemplate(): TemplateResult {
|
|
859
|
+
const contentClasses = classMap({
|
|
860
|
+
'facets-content': true,
|
|
861
|
+
'horizontal-scroll-mode': true,
|
|
862
|
+
});
|
|
863
|
+
const showArrows = !this.atScrollStart || !this.atScrollEnd;
|
|
864
|
+
|
|
865
|
+
return html`<div class="scroll-nav-container">
|
|
866
|
+
${when(
|
|
867
|
+
showArrows,
|
|
868
|
+
() =>
|
|
869
|
+
html`<button
|
|
870
|
+
class="scroll-arrow scroll-left"
|
|
871
|
+
@click=${this.onScrollLeft}
|
|
872
|
+
?disabled=${this.atScrollStart}
|
|
873
|
+
aria-label="Scroll facets left"
|
|
874
|
+
>
|
|
875
|
+
${arrowLeftIcon}
|
|
876
|
+
</button>`,
|
|
877
|
+
)}
|
|
878
|
+
<div class=${contentClasses}>
|
|
879
|
+
<div class="facets-horizontal-container">
|
|
880
|
+
${this.moreFacetsTemplate}
|
|
881
|
+
</div>
|
|
882
|
+
</div>
|
|
883
|
+
${when(
|
|
884
|
+
showArrows,
|
|
885
|
+
() =>
|
|
886
|
+
html`<button
|
|
887
|
+
class="scroll-arrow scroll-right"
|
|
888
|
+
@click=${this.onScrollRight}
|
|
889
|
+
?disabled=${this.atScrollEnd}
|
|
890
|
+
aria-label="Scroll facets right"
|
|
891
|
+
>
|
|
892
|
+
${arrowRightIcon}
|
|
893
|
+
</button>`,
|
|
894
|
+
)}
|
|
895
|
+
</div>`;
|
|
896
|
+
}
|
|
897
|
+
|
|
789
898
|
render() {
|
|
790
899
|
const sectionClasses = classMap({
|
|
791
900
|
'pagination-mode': this.usePagination,
|
|
@@ -794,7 +903,6 @@ export class MoreFacetsContent extends LitElement {
|
|
|
794
903
|
const contentClasses = classMap({
|
|
795
904
|
'facets-content': true,
|
|
796
905
|
'pagination-mode': this.usePagination,
|
|
797
|
-
'horizontal-scroll-mode': !this.usePagination,
|
|
798
906
|
});
|
|
799
907
|
|
|
800
908
|
return html`
|
|
@@ -807,37 +915,7 @@ export class MoreFacetsContent extends LitElement {
|
|
|
807
915
|
? html`<div class=${contentClasses}>
|
|
808
916
|
${this.moreFacetsTemplate}
|
|
809
917
|
</div>`
|
|
810
|
-
:
|
|
811
|
-
${when(
|
|
812
|
-
!this.atScrollStart || !this.atScrollEnd,
|
|
813
|
-
() =>
|
|
814
|
-
html`<button
|
|
815
|
-
class="scroll-arrow scroll-left"
|
|
816
|
-
@click=${this.onScrollLeft}
|
|
817
|
-
?disabled=${this.atScrollStart}
|
|
818
|
-
aria-label="Scroll facets left"
|
|
819
|
-
>
|
|
820
|
-
${arrowLeftIcon}
|
|
821
|
-
</button>`,
|
|
822
|
-
)}
|
|
823
|
-
<div class=${contentClasses}>
|
|
824
|
-
<div class="facets-horizontal-container">
|
|
825
|
-
${this.moreFacetsTemplate}
|
|
826
|
-
</div>
|
|
827
|
-
</div>
|
|
828
|
-
${when(
|
|
829
|
-
!this.atScrollStart || !this.atScrollEnd,
|
|
830
|
-
() =>
|
|
831
|
-
html`<button
|
|
832
|
-
class="scroll-arrow scroll-right"
|
|
833
|
-
@click=${this.onScrollRight}
|
|
834
|
-
?disabled=${this.atScrollEnd}
|
|
835
|
-
aria-label="Scroll facets right"
|
|
836
|
-
>
|
|
837
|
-
${arrowRightIcon}
|
|
838
|
-
</button>`,
|
|
839
|
-
)}
|
|
840
|
-
</div>`}
|
|
918
|
+
: this.horizontalScrollTemplate}
|
|
841
919
|
${this.footerTemplate}
|
|
842
920
|
</section>
|
|
843
921
|
`}
|
|
@@ -895,7 +973,7 @@ export class MoreFacetsContent extends LitElement {
|
|
|
895
973
|
section#more-facets {
|
|
896
974
|
display: flex;
|
|
897
975
|
flex-direction: column;
|
|
898
|
-
max-height: calc(100vh - 16.5rem);
|
|
976
|
+
max-height: calc(100vh - 16.5rem - var(--modalBottomMargin, 2.5rem));
|
|
899
977
|
padding: 10px;
|
|
900
978
|
box-sizing: border-box;
|
|
901
979
|
--facetsColumnCount: 3;
|
|
@@ -908,6 +986,9 @@ export class MoreFacetsContent extends LitElement {
|
|
|
908
986
|
}
|
|
909
987
|
.header-content {
|
|
910
988
|
flex-shrink: 0;
|
|
989
|
+
position: relative;
|
|
990
|
+
z-index: 1;
|
|
991
|
+
background: #fff;
|
|
911
992
|
}
|
|
912
993
|
|
|
913
994
|
.header-content .title {
|
|
@@ -922,8 +1003,8 @@ export class MoreFacetsContent extends LitElement {
|
|
|
922
1003
|
display: flex;
|
|
923
1004
|
flex-wrap: wrap;
|
|
924
1005
|
align-items: center;
|
|
925
|
-
gap:
|
|
926
|
-
padding: 0 10px;
|
|
1006
|
+
gap: 8px 20px;
|
|
1007
|
+
padding: 0 10px 8px;
|
|
927
1008
|
}
|
|
928
1009
|
|
|
929
1010
|
.sort-controls {
|
|
@@ -42,7 +42,10 @@ export class MoreFacetsPagination extends LitElement {
|
|
|
42
42
|
) {
|
|
43
43
|
this.updatePages();
|
|
44
44
|
}
|
|
45
|
-
if (
|
|
45
|
+
if (
|
|
46
|
+
changed.has('currentPage') &&
|
|
47
|
+
changed.get('currentPage') !== undefined
|
|
48
|
+
) {
|
|
46
49
|
this.emitPageClick();
|
|
47
50
|
}
|
|
48
51
|
}
|
|
@@ -404,6 +404,81 @@ describe('More facets content', () => {
|
|
|
404
404
|
expect(clearableInput.value).to.equal('');
|
|
405
405
|
});
|
|
406
406
|
|
|
407
|
+
describe('Modal container height constraint', () => {
|
|
408
|
+
// Register a test wrapper element to simulate the modal's scroll container
|
|
409
|
+
if (!customElements.get('test-scroll-wrapper')) {
|
|
410
|
+
customElements.define(
|
|
411
|
+
'test-scroll-wrapper',
|
|
412
|
+
class extends HTMLElement {
|
|
413
|
+
constructor() {
|
|
414
|
+
super();
|
|
415
|
+
this.attachShadow({ mode: 'open' });
|
|
416
|
+
this.shadowRoot!.innerHTML = `
|
|
417
|
+
<style>
|
|
418
|
+
:host { display: block; }
|
|
419
|
+
.content { overflow-y: auto; max-height: 300px; }
|
|
420
|
+
</style>
|
|
421
|
+
<div class="content"><slot></slot></div>
|
|
422
|
+
`;
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
it('should constrain section height when inside a scroll container', async () => {
|
|
429
|
+
const el = await fixture<MoreFacetsContent>(html`
|
|
430
|
+
<test-scroll-wrapper>
|
|
431
|
+
<more-facets-content></more-facets-content>
|
|
432
|
+
</test-scroll-wrapper>
|
|
433
|
+
`);
|
|
434
|
+
|
|
435
|
+
const mfc = el.querySelector('more-facets-content') as MoreFacetsContent;
|
|
436
|
+
mfc.facetsLoading = false;
|
|
437
|
+
await mfc.updateComplete;
|
|
438
|
+
|
|
439
|
+
// Wait for the constrainToScrollContainer rAF callback
|
|
440
|
+
await new Promise(r =>
|
|
441
|
+
requestAnimationFrame(() => requestAnimationFrame(r)),
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
const section = mfc.shadowRoot?.querySelector(
|
|
445
|
+
'section#more-facets',
|
|
446
|
+
) as HTMLElement;
|
|
447
|
+
|
|
448
|
+
// The section's inline max-height should be set when it would
|
|
449
|
+
// overflow the 300px scroll container
|
|
450
|
+
const sectionHeight = section.getBoundingClientRect().height;
|
|
451
|
+
const wrapper = el.shadowRoot?.querySelector('.content') as HTMLElement;
|
|
452
|
+
const wrapperBottom = wrapper.getBoundingClientRect().bottom;
|
|
453
|
+
const sectionTop = section.getBoundingClientRect().top;
|
|
454
|
+
const availableSpace = wrapperBottom - sectionTop;
|
|
455
|
+
|
|
456
|
+
// The section should not exceed the available space in the container
|
|
457
|
+
expect(sectionHeight).to.be.at.most(availableSpace + 1); // +1 for rounding
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('should not constrain section when no scroll container exists', async () => {
|
|
461
|
+
const el = await fixture<MoreFacetsContent>(
|
|
462
|
+
html`<more-facets-content></more-facets-content>`,
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
el.facetsLoading = false;
|
|
466
|
+
await el.updateComplete;
|
|
467
|
+
|
|
468
|
+
// Wait for the constrainToScrollContainer rAF callback
|
|
469
|
+
await new Promise(r =>
|
|
470
|
+
requestAnimationFrame(() => requestAnimationFrame(r)),
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
const section = el.shadowRoot?.querySelector(
|
|
474
|
+
'section#more-facets',
|
|
475
|
+
) as HTMLElement;
|
|
476
|
+
|
|
477
|
+
// No inline max-height should be set when there's no scroll container
|
|
478
|
+
expect(section.style.maxHeight).to.equal('');
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
407
482
|
describe('Horizontal scroll navigation arrows', () => {
|
|
408
483
|
it('should use scroll-nav-container in horizontal scroll mode', async () => {
|
|
409
484
|
const searchService = new MockSearchService();
|