@internetarchive/collection-browser 4.1.1-alpha-webdev8185.1 → 4.1.2-alpha-webdev8332.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 (38) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/src/collection-browser.d.ts +18 -9
  5. package/dist/src/collection-browser.js +77 -18
  6. package/dist/src/collection-browser.js.map +1 -1
  7. package/dist/src/data-source/collection-browser-data-source.js +3 -2
  8. package/dist/src/data-source/collection-browser-data-source.js.map +1 -1
  9. package/dist/src/data-source/collection-browser-query-state.d.ts +3 -5
  10. package/dist/src/data-source/collection-browser-query-state.js.map +1 -1
  11. package/dist/src/models.d.ts +2 -27
  12. package/dist/src/models.js +0 -36
  13. package/dist/src/models.js.map +1 -1
  14. package/dist/src/restoration-state-handler.js +9 -3
  15. package/dist/src/restoration-state-handler.js.map +1 -1
  16. package/dist/src/sort-filter-bar/sort-filter-bar.d.ts +2 -2
  17. package/dist/src/sort-filter-bar/sort-filter-bar.js.map +1 -1
  18. package/dist/src/tiles/hover/hover-pane-controller.js +2 -1
  19. package/dist/src/tiles/hover/hover-pane-controller.js.map +1 -1
  20. package/dist/src/tiles/tile-dispatcher.d.ts +6 -0
  21. package/dist/src/tiles/tile-dispatcher.js +11 -3
  22. package/dist/src/tiles/tile-dispatcher.js.map +1 -1
  23. package/dist/test/collection-browser.test.js +17 -15
  24. package/dist/test/collection-browser.test.js.map +1 -1
  25. package/dist/test/tiles/tile-dispatcher.test.js +14 -0
  26. package/dist/test/tiles/tile-dispatcher.test.js.map +1 -1
  27. package/index.ts +0 -5
  28. package/package.json +1 -1
  29. package/src/collection-browser.ts +94 -25
  30. package/src/data-source/collection-browser-data-source.ts +3 -2
  31. package/src/data-source/collection-browser-query-state.ts +3 -12
  32. package/src/models.ts +4 -53
  33. package/src/restoration-state-handler.ts +7 -3
  34. package/src/sort-filter-bar/sort-filter-bar.ts +4 -3
  35. package/src/tiles/hover/hover-pane-controller.ts +2 -1
  36. package/src/tiles/tile-dispatcher.ts +12 -3
  37. package/test/collection-browser.test.ts +17 -15
  38. package/test/tiles/tile-dispatcher.test.ts +17 -0
@@ -159,6 +159,20 @@ describe('Tile Dispatcher', () => {
159
159
  await el.updateComplete;
160
160
  expect(el.getHoverPane()).not.to.exist;
161
161
  });
162
+ it('should not show info button when hover pane is not enabled', async () => {
163
+ const el = await fixture(html `
164
+ <tile-dispatcher
165
+ .tileDisplayMode=${'grid'}
166
+ .model=${{ mediatype: 'texts' }}
167
+ .enableHoverPane=${false}
168
+ >
169
+ </tile-dispatcher>
170
+ `);
171
+ const itemTile = el.shadowRoot?.querySelector('item-tile');
172
+ expect(itemTile).to.exist;
173
+ const infoButton = itemTile.shadowRoot?.querySelector('.info-button');
174
+ expect(infoButton).to.not.exist;
175
+ });
162
176
  });
163
177
  describe('Accessibility', () => {
164
178
  it('should have proper aria-label on tile link', async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"tile-dispatcher.test.js","sourceRoot":"","sources":["../../../test/tiles/tile-dispatcher.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { aTimeout, expect, fixture } from '@open-wc/testing';\nimport { html } from 'lit';\nimport sinon from 'sinon';\nimport type { TileDispatcher } from '../../src/tiles/tile-dispatcher';\n\nimport '../../src/tiles/tile-dispatcher';\nimport type { ItemTile } from '../../src/tiles/grid/item-tile';\nimport { TileHoverPane } from '../../src/tiles/hover/tile-hover-pane';\nimport type { HoverPaneProperties } from '../../src/tiles/hover/hover-pane-controller';\n\ndescribe('Tile Dispatcher', () => {\n it('should render item-tile for grid mode by default', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{ mediatype: 'texts' }}\n >\n </tile-dispatcher>\n `);\n\n const itemTile = el.shadowRoot?.querySelector('item-tile');\n expect(itemTile).to.exist;\n });\n\n it('should render collection-tile for grid mode and collection mediatype', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{ mediatype: 'collection' }}\n >\n </tile-dispatcher>\n `);\n\n const collectionTile = el.shadowRoot?.querySelector('collection-tile');\n expect(collectionTile).to.exist;\n });\n\n it('should render account-tile for grid mode and account mediatype', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{ mediatype: 'account' }}\n >\n </tile-dispatcher>\n `);\n\n const accountTile = el.shadowRoot?.querySelector('account-tile');\n expect(accountTile).to.exist;\n });\n\n it('should render search-tile for grid mode and search mediatype', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{ mediatype: 'search' }}\n >\n </tile-dispatcher>\n `);\n\n const searchTile = el.shadowRoot?.querySelector('search-tile');\n expect(searchTile).to.exist;\n });\n\n it('should render tile-list for extended list mode', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher .tileDisplayMode=${'list-detail'} .model=${{}}>\n </tile-dispatcher>\n `);\n\n const listTile = el.shadowRoot?.querySelector('tile-list');\n expect(listTile).to.exist;\n });\n\n it('should render tile-list-compact for compact list mode', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher .tileDisplayMode=${'list-compact'} .model=${{}}>\n </tile-dispatcher>\n `);\n\n const compactListTile = el.shadowRoot?.querySelector('tile-list-compact');\n expect(compactListTile).to.exist;\n });\n\n it('should open item in new tab when right-clicked in manage mode', async () => {\n const oldWindowOpen = window.open;\n const spy = sinon.spy();\n window.open = spy;\n\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n isManageView\n .model=${{ identifier: 'foo', href: '/foo' }}\n .baseNavigationUrl=${''}\n >\n </tile-dispatcher>\n `);\n\n const tileLink = el.shadowRoot?.querySelector(\n 'a[href]',\n ) as HTMLAnchorElement;\n expect(tileLink).to.exist;\n\n tileLink.dispatchEvent(new Event('contextmenu'));\n await el.updateComplete;\n\n expect(spy.callCount).to.equal(1);\n expect(spy.args[0][0]).to.equal('/foo');\n expect(spy.args[0][1]).to.equal('_blank');\n\n window.open = oldWindowOpen;\n });\n\n it('should toggle model checked state when manage check clicked', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n isManageView\n .model=${{ identifier: 'foo', href: '/foo' }}\n .tileDisplayMode=${'grid'}\n ></tile-dispatcher>\n `);\n\n const manageCheck = el.shadowRoot?.querySelector(\n '.manage-check > input[type=\"checkbox\"]',\n ) as HTMLButtonElement;\n\n manageCheck.click();\n await el.updateComplete;\n expect(el.model?.checked).to.be.true;\n\n manageCheck.click();\n await el.updateComplete;\n expect(el.model?.checked).to.be.false;\n });\n\n it('should return hover pane props', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher .model=${{ identifier: 'foo' }}> </tile-dispatcher>\n `);\n\n expect(el.getHoverPaneProps()).to.satisfy(\n (props: HoverPaneProperties) => props?.model?.identifier === 'foo',\n );\n });\n\n it('should focus the tile link when requested', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher .tileDisplayMode=${'grid'}> </tile-dispatcher>\n `);\n\n const tileLink = el.shadowRoot?.querySelector(\n '.tile-link',\n ) as HTMLAnchorElement;\n expect(tileLink).to.exist;\n\n const spyFocus = sinon.spy(tileLink, 'focus');\n el.acquireFocus();\n expect(spyFocus.callCount).to.equal(1);\n });\n\n it('should blur the tile link when requested', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher .tileDisplayMode=${'grid'}> </tile-dispatcher>\n `);\n\n const tileLink = el.shadowRoot?.querySelector(\n '.tile-link',\n ) as HTMLAnchorElement;\n expect(tileLink).to.exist;\n\n const spyBlur = sinon.spy(tileLink, 'blur');\n el.releaseFocus();\n expect(spyBlur.callCount).to.equal(1);\n });\n\n describe('Hover pane info button behavior', () => {\n let oldMatchMedia: typeof window.matchMedia;\n\n before(() => {\n oldMatchMedia = window.matchMedia;\n // Pretend that there is no hover-capable input device\n window.matchMedia = () => ({ matches: false }) as MediaQueryList;\n });\n\n after(() => {\n window.matchMedia = oldMatchMedia;\n });\n\n it('should toggle hover pane when tile info button is pressed', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{ mediatype: 'texts' }}\n .enableHoverPane=${true}\n >\n </tile-dispatcher>\n `);\n\n const itemTile = el.shadowRoot?.querySelector('item-tile') as ItemTile;\n expect(itemTile).to.exist;\n\n const infoButton = itemTile.shadowRoot?.querySelector(\n '.info-button',\n ) as HTMLButtonElement;\n expect(infoButton).to.exist;\n\n infoButton.click();\n await aTimeout(500);\n await el.updateComplete;\n expect(el.getHoverPane()).to.be.instanceOf(TileHoverPane);\n\n infoButton.click();\n await aTimeout(500);\n await el.updateComplete;\n expect(el.getHoverPane()).not.to.exist;\n });\n });\n\n describe('Accessibility', () => {\n it('should have proper aria-label on tile link', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{\n title: 'Example Title',\n mediatype: 'texts',\n }}\n >\n </tile-dispatcher>\n `);\n\n const tileLink = el.shadowRoot?.querySelector(\n '.tile-link',\n ) as HTMLAnchorElement;\n expect(tileLink).to.exist;\n expect(tileLink.getAttribute('aria-label')).to.equal('Example Title');\n });\n\n it('should fallback to untitled aria-label on tile link when no title', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{\n mediatype: 'texts',\n }}\n >\n </tile-dispatcher>\n `);\n\n const tileLink = el.shadowRoot?.querySelector(\n '.tile-link',\n ) as HTMLAnchorElement;\n expect(tileLink).to.exist;\n expect(tileLink.getAttribute('aria-label')).to.equal('Untitled item');\n });\n\n it('should have no accessibility violations in grid mode', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{\n title: 'Example Title',\n mediatype: 'texts',\n }}\n >\n </tile-dispatcher>\n `);\n\n await expect(el).to.be.accessible();\n });\n\n it('should have no accessibility violations in list mode', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'list-detail'}\n .model=${{ mediatype: 'texts' }}\n >\n </tile-dispatcher>\n `);\n\n await expect(el).to.be.accessible();\n });\n });\n});\n"]}
1
+ {"version":3,"file":"tile-dispatcher.test.js","sourceRoot":"","sources":["../../../test/tiles/tile-dispatcher.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { aTimeout, expect, fixture } from '@open-wc/testing';\nimport { html } from 'lit';\nimport sinon from 'sinon';\nimport type { TileDispatcher } from '../../src/tiles/tile-dispatcher';\n\nimport '../../src/tiles/tile-dispatcher';\nimport type { ItemTile } from '../../src/tiles/grid/item-tile';\nimport { TileHoverPane } from '../../src/tiles/hover/tile-hover-pane';\nimport type { HoverPaneProperties } from '../../src/tiles/hover/hover-pane-controller';\n\ndescribe('Tile Dispatcher', () => {\n it('should render item-tile for grid mode by default', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{ mediatype: 'texts' }}\n >\n </tile-dispatcher>\n `);\n\n const itemTile = el.shadowRoot?.querySelector('item-tile');\n expect(itemTile).to.exist;\n });\n\n it('should render collection-tile for grid mode and collection mediatype', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{ mediatype: 'collection' }}\n >\n </tile-dispatcher>\n `);\n\n const collectionTile = el.shadowRoot?.querySelector('collection-tile');\n expect(collectionTile).to.exist;\n });\n\n it('should render account-tile for grid mode and account mediatype', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{ mediatype: 'account' }}\n >\n </tile-dispatcher>\n `);\n\n const accountTile = el.shadowRoot?.querySelector('account-tile');\n expect(accountTile).to.exist;\n });\n\n it('should render search-tile for grid mode and search mediatype', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{ mediatype: 'search' }}\n >\n </tile-dispatcher>\n `);\n\n const searchTile = el.shadowRoot?.querySelector('search-tile');\n expect(searchTile).to.exist;\n });\n\n it('should render tile-list for extended list mode', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher .tileDisplayMode=${'list-detail'} .model=${{}}>\n </tile-dispatcher>\n `);\n\n const listTile = el.shadowRoot?.querySelector('tile-list');\n expect(listTile).to.exist;\n });\n\n it('should render tile-list-compact for compact list mode', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher .tileDisplayMode=${'list-compact'} .model=${{}}>\n </tile-dispatcher>\n `);\n\n const compactListTile = el.shadowRoot?.querySelector('tile-list-compact');\n expect(compactListTile).to.exist;\n });\n\n it('should open item in new tab when right-clicked in manage mode', async () => {\n const oldWindowOpen = window.open;\n const spy = sinon.spy();\n window.open = spy;\n\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n isManageView\n .model=${{ identifier: 'foo', href: '/foo' }}\n .baseNavigationUrl=${''}\n >\n </tile-dispatcher>\n `);\n\n const tileLink = el.shadowRoot?.querySelector(\n 'a[href]',\n ) as HTMLAnchorElement;\n expect(tileLink).to.exist;\n\n tileLink.dispatchEvent(new Event('contextmenu'));\n await el.updateComplete;\n\n expect(spy.callCount).to.equal(1);\n expect(spy.args[0][0]).to.equal('/foo');\n expect(spy.args[0][1]).to.equal('_blank');\n\n window.open = oldWindowOpen;\n });\n\n it('should toggle model checked state when manage check clicked', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n isManageView\n .model=${{ identifier: 'foo', href: '/foo' }}\n .tileDisplayMode=${'grid'}\n ></tile-dispatcher>\n `);\n\n const manageCheck = el.shadowRoot?.querySelector(\n '.manage-check > input[type=\"checkbox\"]',\n ) as HTMLButtonElement;\n\n manageCheck.click();\n await el.updateComplete;\n expect(el.model?.checked).to.be.true;\n\n manageCheck.click();\n await el.updateComplete;\n expect(el.model?.checked).to.be.false;\n });\n\n it('should return hover pane props', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher .model=${{ identifier: 'foo' }}> </tile-dispatcher>\n `);\n\n expect(el.getHoverPaneProps()).to.satisfy(\n (props: HoverPaneProperties) => props?.model?.identifier === 'foo',\n );\n });\n\n it('should focus the tile link when requested', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher .tileDisplayMode=${'grid'}> </tile-dispatcher>\n `);\n\n const tileLink = el.shadowRoot?.querySelector(\n '.tile-link',\n ) as HTMLAnchorElement;\n expect(tileLink).to.exist;\n\n const spyFocus = sinon.spy(tileLink, 'focus');\n el.acquireFocus();\n expect(spyFocus.callCount).to.equal(1);\n });\n\n it('should blur the tile link when requested', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher .tileDisplayMode=${'grid'}> </tile-dispatcher>\n `);\n\n const tileLink = el.shadowRoot?.querySelector(\n '.tile-link',\n ) as HTMLAnchorElement;\n expect(tileLink).to.exist;\n\n const spyBlur = sinon.spy(tileLink, 'blur');\n el.releaseFocus();\n expect(spyBlur.callCount).to.equal(1);\n });\n\n describe('Hover pane info button behavior', () => {\n let oldMatchMedia: typeof window.matchMedia;\n\n before(() => {\n oldMatchMedia = window.matchMedia;\n // Pretend that there is no hover-capable input device\n window.matchMedia = () => ({ matches: false }) as MediaQueryList;\n });\n\n after(() => {\n window.matchMedia = oldMatchMedia;\n });\n\n it('should toggle hover pane when tile info button is pressed', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{ mediatype: 'texts' }}\n .enableHoverPane=${true}\n >\n </tile-dispatcher>\n `);\n\n const itemTile = el.shadowRoot?.querySelector('item-tile') as ItemTile;\n expect(itemTile).to.exist;\n\n const infoButton = itemTile.shadowRoot?.querySelector(\n '.info-button',\n ) as HTMLButtonElement;\n expect(infoButton).to.exist;\n\n infoButton.click();\n await aTimeout(500);\n await el.updateComplete;\n expect(el.getHoverPane()).to.be.instanceOf(TileHoverPane);\n\n infoButton.click();\n await aTimeout(500);\n await el.updateComplete;\n expect(el.getHoverPane()).not.to.exist;\n });\n\n it('should not show info button when hover pane is not enabled', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{ mediatype: 'texts' }}\n .enableHoverPane=${false}\n >\n </tile-dispatcher>\n `);\n\n const itemTile = el.shadowRoot?.querySelector('item-tile') as ItemTile;\n expect(itemTile).to.exist;\n\n const infoButton = itemTile.shadowRoot?.querySelector('.info-button');\n expect(infoButton).to.not.exist;\n });\n });\n\n describe('Accessibility', () => {\n it('should have proper aria-label on tile link', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{\n title: 'Example Title',\n mediatype: 'texts',\n }}\n >\n </tile-dispatcher>\n `);\n\n const tileLink = el.shadowRoot?.querySelector(\n '.tile-link',\n ) as HTMLAnchorElement;\n expect(tileLink).to.exist;\n expect(tileLink.getAttribute('aria-label')).to.equal('Example Title');\n });\n\n it('should fallback to untitled aria-label on tile link when no title', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{\n mediatype: 'texts',\n }}\n >\n </tile-dispatcher>\n `);\n\n const tileLink = el.shadowRoot?.querySelector(\n '.tile-link',\n ) as HTMLAnchorElement;\n expect(tileLink).to.exist;\n expect(tileLink.getAttribute('aria-label')).to.equal('Untitled item');\n });\n\n it('should have no accessibility violations in grid mode', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'grid'}\n .model=${{\n title: 'Example Title',\n mediatype: 'texts',\n }}\n >\n </tile-dispatcher>\n `);\n\n await expect(el).to.be.accessible();\n });\n\n it('should have no accessibility violations in list mode', async () => {\n const el = await fixture<TileDispatcher>(html`\n <tile-dispatcher\n .tileDisplayMode=${'list-detail'}\n .model=${{ mediatype: 'texts' }}\n >\n </tile-dispatcher>\n `);\n\n await expect(el).to.be.accessible();\n });\n });\n});\n"]}
package/index.ts CHANGED
@@ -6,15 +6,10 @@ export { SortFilterBar } from './src/sort-filter-bar/sort-filter-bar';
6
6
  export {
7
7
  CollectionDisplayMode,
8
8
  SortField,
9
- ExplicitSortField,
10
9
  TileModel,
11
10
  FacetOption,
12
11
  SelectedFacets,
13
12
  getDefaultSelectedFacets,
14
- sortOptionFromAPIString,
15
- resolveCollectionDefaultSort,
16
- SORT_OPTIONS,
17
- defaultProfileElementSorts,
18
13
  } from './src/models';
19
14
  export { CollectionBrowserLoadingTile } from './src/tiles/collection-browser-loading-tile';
20
15
  export { CollectionTile } from './src/tiles/grid/collection-tile';
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "The Internet Archive Collection Browser.",
4
4
  "license": "AGPL-3.0-only",
5
5
  "author": "Internet Archive",
6
- "version": "4.1.1-alpha-webdev8185.1",
6
+ "version": "4.1.2-alpha-webdev8332.0",
7
7
  "main": "dist/index.js",
8
8
  "module": "dist/index.js",
9
9
  "scripts": {
@@ -36,13 +36,14 @@ import type { IAComboBox } from '@internetarchive/elements/ia-combo-box/ia-combo
36
36
  import {
37
37
  SelectedFacets,
38
38
  SortField,
39
- type ExplicitSortField,
40
39
  CollectionBrowserContext,
41
40
  getDefaultSelectedFacets,
42
41
  TileModel,
43
42
  CollectionDisplayMode,
44
43
  FacetEventDetails,
44
+ sortOptionFromAPIString,
45
45
  SORT_OPTIONS,
46
+ defaultProfileElementSorts,
46
47
  FacetLoadStrategy,
47
48
  defaultFacetDisplayOrder,
48
49
  tvFacetDisplayOrder,
@@ -150,8 +151,10 @@ export class CollectionBrowser
150
151
 
151
152
  @property({ type: String }) sortDirection: SortDirection | null = null;
152
153
 
153
- @property({ type: String }) defaultSortField: ExplicitSortField =
154
- SortField.relevance;
154
+ @property({ type: String }) defaultSortField: Exclude<
155
+ SortField,
156
+ SortField.default
157
+ > = SortField.relevance;
155
158
 
156
159
  @property({ type: String }) defaultSortDirection: SortDirection | null = null;
157
160
 
@@ -598,6 +601,10 @@ export class CollectionBrowser
598
601
 
599
602
  willUpdate(changed: PropertyValues): void {
600
603
  this.setPlaceholderType();
604
+
605
+ if (changed.has('searchType') && this.searchType === SearchType.TV) {
606
+ this.applyDefaultTVSearchSort();
607
+ }
601
608
  }
602
609
 
603
610
  render() {
@@ -1639,12 +1646,6 @@ export class CollectionBrowser
1639
1646
  this.maxSelectedDate = queryState.maxSelectedDate;
1640
1647
  this.selectedSort = queryState.selectedSort ?? SortField.default;
1641
1648
  this.sortDirection = queryState.sortDirection;
1642
- if (queryState.defaultSortField) {
1643
- this.defaultSortField = queryState.defaultSortField;
1644
- }
1645
- if (queryState.defaultSortDirection !== undefined) {
1646
- this.defaultSortDirection = queryState.defaultSortDirection;
1647
- }
1648
1649
  this.selectedTitleFilter = queryState.selectedTitleFilter;
1649
1650
  this.selectedCreatorFilter = queryState.selectedCreatorFilter;
1650
1651
 
@@ -1755,6 +1756,21 @@ export class CollectionBrowser
1755
1756
  }
1756
1757
  }
1757
1758
 
1759
+ if (changed.has('profileElement')) {
1760
+ this.applyDefaultProfileSort();
1761
+ }
1762
+
1763
+ if (changed.has('withinCollection') && this.withinCollection) {
1764
+ // Set a sensible default collection sort while we load results, which we will later
1765
+ // adjust based on any sort-by metadata once the response arrives.
1766
+ if (!this.baseQuery) {
1767
+ this.defaultSortField = this.withinCollection.startsWith('fav-')
1768
+ ? SortField.datefavorited
1769
+ : SortField.weeklyview;
1770
+ this.defaultSortDirection = 'desc';
1771
+ }
1772
+ }
1773
+
1758
1774
  if (changed.has('baseQuery')) {
1759
1775
  this.emitBaseQueryChanged();
1760
1776
  }
@@ -2067,22 +2083,6 @@ export class CollectionBrowser
2067
2083
  );
2068
2084
  }
2069
2085
 
2070
- /**
2071
- * Emits a `collectionExtraInfoLoaded` event when the data source has received
2072
- * collection metadata from the backend. This allows parent components to react
2073
- * to the metadata (e.g., to update their default sort based on the collection's
2074
- * `sort-by` metadata field).
2075
- */
2076
- emitCollectionExtraInfoLoaded(
2077
- collectionExtraInfo?: CollectionExtraInfo,
2078
- ): void {
2079
- this.dispatchEvent(
2080
- new CustomEvent('collectionExtraInfoLoaded', {
2081
- detail: collectionExtraInfo,
2082
- }),
2083
- );
2084
- }
2085
-
2086
2086
  /**
2087
2087
  * Emits a `queryStateChanged` event indicating that one or more of this component's
2088
2088
  * properties have changed in a way that could affect the set of search results.
@@ -2382,6 +2382,75 @@ export class CollectionBrowser
2382
2382
  }
2383
2383
  }
2384
2384
 
2385
+ /**
2386
+ * Applies the default sort options for the TV search results page
2387
+ */
2388
+ applyDefaultTVSearchSort(): void {
2389
+ this.defaultSortField = SortField.datearchived;
2390
+ this.defaultSortDirection = 'desc';
2391
+ }
2392
+
2393
+ /**
2394
+ * Applies any default sort option for the current collection, by checking
2395
+ * for one in the collection's metadata. If none is found, defaults to sorting
2396
+ * descending by:
2397
+ * - Date Favorited for fav-* collections
2398
+ * - Weekly views for all other collections
2399
+ */
2400
+ applyDefaultCollectionSort(collectionInfo?: CollectionExtraInfo): void {
2401
+ if (this.baseQuery) {
2402
+ // If there's a query set, then we default to relevance sorting regardless of
2403
+ // the collection metadata-specified sort.
2404
+ this.defaultSortField = SortField.relevance;
2405
+ this.defaultSortDirection = null;
2406
+ return;
2407
+ }
2408
+
2409
+ // Favorite collections sort on Date Favorited by default.
2410
+ // Other collections fall back to sorting on weekly views.
2411
+ const baseDefaultSort: string =
2412
+ collectionInfo?.public_metadata?.identifier?.startsWith('fav-')
2413
+ ? '-favoritedate'
2414
+ : '-week';
2415
+
2416
+ // The collection metadata may override the default sorting with something else
2417
+ const metadataSort: string | undefined =
2418
+ collectionInfo?.public_metadata?.['sort-by'];
2419
+
2420
+ // Prefer the metadata-specified sort if one exists
2421
+ const defaultSortToApply = metadataSort ?? baseDefaultSort;
2422
+
2423
+ // Account for both -field and field:dir formats
2424
+ let [field, dir] = defaultSortToApply.split(':');
2425
+ if (field.startsWith('-')) {
2426
+ field = field.slice(1);
2427
+ dir = 'desc';
2428
+ } else if (!['asc', 'desc'].includes(dir)) {
2429
+ dir = 'asc';
2430
+ }
2431
+
2432
+ const sortOption = sortOptionFromAPIString(field);
2433
+ const sortField = sortOption.field;
2434
+ if (sortField && sortField !== SortField.default) {
2435
+ this.defaultSortField = sortField;
2436
+ this.defaultSortDirection = dir as SortDirection;
2437
+ }
2438
+ }
2439
+
2440
+ /**
2441
+ * Applies the default sort option for the current profile element
2442
+ */
2443
+ applyDefaultProfileSort(): void {
2444
+ if (this.profileElement) {
2445
+ const defaultSortField = defaultProfileElementSorts[this.profileElement];
2446
+ this.defaultSortField = defaultSortField ?? SortField.weeklyview;
2447
+ } else {
2448
+ this.defaultSortField = SortField.weeklyview;
2449
+ }
2450
+
2451
+ this.defaultSortDirection = 'desc';
2452
+ }
2453
+
2385
2454
  /**
2386
2455
  * This is useful for determining whether we need to reload the scroller.
2387
2456
  *
@@ -1194,9 +1194,10 @@ export class CollectionBrowserDataSource
1194
1194
  if (withinCollection) {
1195
1195
  this.collectionExtraInfo = success.response.collectionExtraInfo;
1196
1196
 
1197
- // Emit the collection metadata so the parent page can set default sort
1197
+ // For collections, we want the UI to respect the default sort option
1198
+ // which can be specified in metadata, or otherwise assumed to be week:desc
1198
1199
  if (this.activeOnHost) {
1199
- this.host.emitCollectionExtraInfoLoaded(this.collectionExtraInfo);
1200
+ this.host.applyDefaultCollectionSort(this.collectionExtraInfo);
1200
1201
  }
1201
1202
 
1202
1203
  if (this.collectionExtraInfo) {
@@ -6,12 +6,7 @@ import type {
6
6
  SortDirection,
7
7
  SortParam,
8
8
  } from '@internetarchive/search-service';
9
- import type {
10
- ExplicitSortField,
11
- FacetLoadStrategy,
12
- SelectedFacets,
13
- SortField,
14
- } from '../models';
9
+ import type { FacetLoadStrategy, SelectedFacets, SortField } from '../models';
15
10
  import type { CollectionBrowserDataSourceInterface } from './collection-browser-data-source-interface';
16
11
 
17
12
  /**
@@ -32,8 +27,6 @@ export interface CollectionBrowserQueryState {
32
27
  selectedCreatorFilter: string | null;
33
28
  selectedSort?: SortField;
34
29
  sortDirection: SortDirection | null;
35
- defaultSortField?: ExplicitSortField;
36
- defaultSortDirection?: SortDirection | null;
37
30
  }
38
31
 
39
32
  /**
@@ -45,7 +38,7 @@ export interface CollectionBrowserSearchInterface
45
38
  searchService?: SearchServiceInterface;
46
39
  isTVCollection: boolean;
47
40
  readonly sortParam: SortParam | null;
48
- readonly defaultSortField: ExplicitSortField;
41
+ readonly defaultSortField: SortField | null;
49
42
  readonly defaultSortDirection: SortDirection | null;
50
43
  readonly facetLoadStrategy: FacetLoadStrategy;
51
44
  readonly initialPageNumber: number;
@@ -59,9 +52,7 @@ export interface CollectionBrowserSearchInterface
59
52
  setFacetsLoading(loading: boolean): void;
60
53
  setTotalResultCount(count: number): void;
61
54
  setTileCount(count: number): void;
62
- emitCollectionExtraInfoLoaded(
63
- collectionExtraInfo?: CollectionExtraInfo,
64
- ): void;
55
+ applyDefaultCollectionSort(collectionInfo?: CollectionExtraInfo): void;
65
56
  emitEmptyResults(): void;
66
57
  emitSearchError(): void;
67
58
  emitQueryStateChanged(): void;
package/src/models.ts CHANGED
@@ -3,7 +3,6 @@ import { msg } from '@lit/localize';
3
3
  import type { MediaType } from '@internetarchive/field-parsers';
4
4
  import {
5
5
  AggregationSortType,
6
- CollectionExtraInfo,
7
6
  HitType,
8
7
  SearchReview,
9
8
  SearchResult,
@@ -323,16 +322,6 @@ export enum SortField {
323
322
  'creator' = 'creator',
324
323
  }
325
324
 
326
- /**
327
- * A sort field other than the abstract "default" placeholder.
328
- * This is useful because the "default" sort field is just an indicator to
329
- * revert to an explicitly defined fallback value. So when defining default
330
- * sort logic, this type should be preferred to avoid accidentally permitting
331
- * that fallback to itself equal the "default" placeholder (which would be
332
- * rather circular/ill-defined).
333
- */
334
- export type ExplicitSortField = Exclude<SortField, SortField.default>;
335
-
336
325
  /**
337
326
  * Views-related sort fields
338
327
  */
@@ -564,47 +553,6 @@ export function sortOptionFromAPIString(sortName?: string | null): SortOption {
564
553
  );
565
554
  }
566
555
 
567
- /**
568
- * Resolves the default sort option for a collection based on its metadata.
569
- *
570
- * - Favorite collections (`fav-*`) default to Date Favorited descending.
571
- * - Other collections default to Weekly Views descending.
572
- * - If the collection metadata specifies a `sort-by` field, that overrides the above.
573
- *
574
- * Supports both `-field` (dash prefix = desc) and `field:dir` metadata formats.
575
- *
576
- * Note: This does NOT handle the "relevance when a query is present" rule,
577
- * which is managed separately by collection-browser itself.
578
- */
579
- export function resolveCollectionDefaultSort(
580
- collectionInfo?: CollectionExtraInfo,
581
- ): { field: ExplicitSortField; direction: SortDirection } {
582
- const isFav = collectionInfo?.public_metadata?.identifier?.startsWith('fav-');
583
- const baseDefaultSort: string = isFav ? '-favoritedate' : '-week';
584
- const metadataSort: string | undefined =
585
- collectionInfo?.public_metadata?.['sort-by'];
586
- const defaultSortToApply = metadataSort ?? baseDefaultSort;
587
-
588
- // Account for both -field and field:dir formats
589
- let [field, dir] = defaultSortToApply.split(':');
590
- if (field.startsWith('-')) {
591
- field = field.slice(1);
592
- dir = 'desc';
593
- } else if (!['asc', 'desc'].includes(dir)) {
594
- dir = 'asc';
595
- }
596
-
597
- const sortOption = sortOptionFromAPIString(field);
598
- const sortField = sortOption.field;
599
- if (sortField && sortField !== SortField.default) {
600
- return {
601
- field: sortField as ExplicitSortField,
602
- direction: dir as SortDirection,
603
- };
604
- }
605
- return { field: SortField.weeklyview, direction: 'desc' };
606
- }
607
-
608
556
  export const defaultSortAvailability: Record<SortField, boolean> = {
609
557
  [SortField.relevance]: true,
610
558
  [SortField.weeklyview]: true,
@@ -632,7 +580,10 @@ export const tvSortAvailability: Record<SortField, boolean> = {
632
580
  [SortField.dateadded]: false,
633
581
  };
634
582
 
635
- export const defaultProfileElementSorts: Record<string, ExplicitSortField> = {
583
+ export const defaultProfileElementSorts: Record<
584
+ string,
585
+ Exclude<SortField, SortField.default>
586
+ > = {
636
587
  uploads: SortField.datearchived,
637
588
  reviews: SortField.datereviewed,
638
589
  collections: SortField.datearchived,
@@ -337,7 +337,9 @@ export class RestorationStateHandler
337
337
  if (facetAnds) {
338
338
  facetAnds.forEach(and => {
339
339
  // eslint-disable-next-line prefer-const
340
- let [field, value] = and.split(':');
340
+ let [field, ...valueParts] = and.split(':');
341
+ if (!valueParts.length) return;
342
+ const value = valueParts.join(':');
341
343
 
342
344
  // Legacy search allowed and[] fields like 'creatorSorter', 'languageSorter', etc.
343
345
  // which we want to normalize to 'creator', 'language', etc. if redirected here.
@@ -396,7 +398,9 @@ export class RestorationStateHandler
396
398
 
397
399
  if (facetNots) {
398
400
  facetNots.forEach(not => {
399
- const [field, value] = not.split(':');
401
+ const [field, ...valueParts] = not.split(':');
402
+ if (!valueParts.length) return;
403
+ const value = valueParts.join(':');
400
404
  this.setSelectedFacetState(
401
405
  restorationState.selectedFacets,
402
406
  field as FacetOption,
@@ -531,7 +535,7 @@ export class RestorationStateHandler
531
535
  if (!facet) return; // Unrecognized facet group, ignore it.
532
536
 
533
537
  const unQuotedValue = this.stripQuotes(value);
534
- facet[unQuotedValue] ??= this.getDefaultBucket(value);
538
+ facet[unQuotedValue] ??= this.getDefaultBucket(unQuotedValue);
535
539
  facet[unQuotedValue].state = state;
536
540
  }
537
541
 
@@ -13,7 +13,6 @@ import type { SortDirection } from '@internetarchive/search-service';
13
13
  import {
14
14
  CollectionDisplayMode,
15
15
  defaultSortAvailability,
16
- type ExplicitSortField,
17
16
  PrefixFilterCounts,
18
17
  PrefixFilterType,
19
18
  SORT_OPTIONS,
@@ -43,8 +42,10 @@ export class SortFilterBar extends LitElement {
43
42
  @property({ type: String }) defaultSortDirection: SortDirection | null = null;
44
43
 
45
44
  /** The default sort field to use if none is set */
46
- @property({ type: String }) defaultSortField: ExplicitSortField =
47
- SortField.relevance;
45
+ @property({ type: String }) defaultSortField: Exclude<
46
+ SortField,
47
+ SortField.default
48
+ > = SortField.relevance;
48
49
 
49
50
  /** The current sort direction (asc/desc), or null if none is set */
50
51
  @property({ type: String }) sortDirection: SortDirection | null = null;
@@ -227,7 +227,7 @@ export class HoverPaneController implements HoverPaneControllerInterface {
227
227
 
228
228
  /** @inheritdoc */
229
229
  toggleHoverPane(options: ToggleHoverPaneOptions): void {
230
- if (this.hoverPaneState === 'shown') {
230
+ if (this.hoverPaneState !== 'hidden') {
231
231
  this.fadeOutHoverPane();
232
232
  this.forceTouchBackdrop = false;
233
233
  } else {
@@ -546,6 +546,7 @@ export class HoverPaneController implements HoverPaneControllerInterface {
546
546
  if (this.hoverPaneState !== 'hidden') {
547
547
  this.fadeOutHoverPane();
548
548
  }
549
+ e.preventDefault();
549
550
  e.stopPropagation();
550
551
  };
551
552
 
@@ -218,6 +218,15 @@ export class TileDispatcher
218
218
  return window.matchMedia('(hover: hover)').matches;
219
219
  }
220
220
 
221
+ /**
222
+ * Whether the info button should be shown on this tile.
223
+ * Only shown on touch/non-hover devices where a hover pane is available,
224
+ * so the button always has something to toggle.
225
+ */
226
+ private get shouldShowInfoButton(): boolean {
227
+ return !this.isHoverEnabled && this.shouldPrepareHoverPane;
228
+ }
229
+
221
230
  /** @inheritdoc */
222
231
  getHoverPane(): TileHoverPane | undefined {
223
232
  return this.hoverPane;
@@ -326,7 +335,7 @@ export class TileDispatcher
326
335
  .suppressBlurring=${this.suppressBlurring}
327
336
  .isManageView=${this.isManageView}
328
337
  .layoutType=${this.layoutType}
329
- ?showInfoButton=${!this.isHoverEnabled}
338
+ ?showInfoButton=${this.shouldShowInfoButton}
330
339
  @infoButtonPressed=${this.tileInfoButtonPressed}
331
340
  >
332
341
  </collection-tile>`;
@@ -340,7 +349,7 @@ export class TileDispatcher
340
349
  .creatorFilter=${creatorFilter}
341
350
  .suppressBlurring=${this.suppressBlurring}
342
351
  .isManageView=${this.isManageView}
343
- ?showInfoButton=${!this.isHoverEnabled}
352
+ ?showInfoButton=${this.shouldShowInfoButton}
344
353
  @infoButtonPressed=${this.tileInfoButtonPressed}
345
354
  >
346
355
  </account-tile>`;
@@ -373,7 +382,7 @@ export class TileDispatcher
373
382
  .isManageView=${this.isManageView}
374
383
  .layoutType=${this.layoutType}
375
384
  ?showTvClips=${this.showTvClips}
376
- ?showInfoButton=${!this.isHoverEnabled}
385
+ ?showInfoButton=${this.shouldShowInfoButton}
377
386
  ?useLocalTime=${this.useLocalTime}
378
387
  @infoButtonPressed=${this.tileInfoButtonPressed}
379
388
  >
@@ -1313,19 +1313,20 @@ describe('Collection Browser', () => {
1313
1313
  );
1314
1314
  });
1315
1315
 
1316
- it('sets default sort from externally-provided values', async () => {
1316
+ it('sets default sort from collection metadata', async () => {
1317
1317
  const searchService = new MockSearchService();
1318
1318
  const el = await fixture<CollectionBrowser>(
1319
1319
  html`<collection-browser
1320
1320
  .searchService=${searchService}
1321
1321
  .baseNavigationUrl=${''}
1322
- .withinCollection=${'test-collection'}
1323
- .defaultSortField=${SortField.title}
1324
- .defaultSortDirection=${'asc'}
1325
1322
  ></collection-browser>`,
1326
1323
  );
1327
1324
 
1325
+ el.withinCollection = 'default-sort';
1326
+ await el.updateComplete;
1327
+ await el.initialSearchComplete;
1328
1328
  await el.updateComplete;
1329
+ await aTimeout(50);
1329
1330
 
1330
1331
  const sortBar = el.shadowRoot?.querySelector(
1331
1332
  'sort-filter-bar',
@@ -1337,19 +1338,20 @@ describe('Collection Browser', () => {
1337
1338
  expect(sortBar.sortDirection).to.be.null;
1338
1339
  });
1339
1340
 
1340
- it('reflects updated default sort field/direction on sort bar', async () => {
1341
+ it('sets default sort from collection metadata in "-field" format', async () => {
1341
1342
  const searchService = new MockSearchService();
1342
1343
  const el = await fixture<CollectionBrowser>(
1343
1344
  html`<collection-browser
1344
1345
  .searchService=${searchService}
1345
1346
  .baseNavigationUrl=${''}
1346
- .withinCollection=${'test-collection'}
1347
- .defaultSortField=${SortField.dateadded}
1348
- .defaultSortDirection=${'desc'}
1349
1347
  ></collection-browser>`,
1350
1348
  );
1351
1349
 
1350
+ el.withinCollection = 'default-sort-concise';
1351
+ await el.updateComplete;
1352
+ await el.initialSearchComplete;
1352
1353
  await el.updateComplete;
1354
+ await aTimeout(50);
1353
1355
 
1354
1356
  const sortBar = el.shadowRoot?.querySelector(
1355
1357
  'sort-filter-bar',
@@ -1361,15 +1363,14 @@ describe('Collection Browser', () => {
1361
1363
  expect(sortBar.sortDirection).to.be.null;
1362
1364
  });
1363
1365
 
1364
- it('uses weekly views as default sort when set externally for profiles', async () => {
1366
+ it('falls back to weekly views default sorting on profiles when tab not set', async () => {
1365
1367
  const el = await fixture<CollectionBrowser>(
1366
1368
  html`<collection-browser
1367
1369
  .withinProfile=${'@foobar'}
1368
- .defaultSortField=${SortField.weeklyview}
1369
- .defaultSortDirection=${'desc'}
1370
1370
  ></collection-browser>`,
1371
1371
  );
1372
1372
 
1373
+ el.applyDefaultProfileSort();
1373
1374
  expect(el.defaultSortParam).to.deep.equal({
1374
1375
  field: 'week',
1375
1376
  direction: 'desc',
@@ -1402,19 +1403,20 @@ describe('Collection Browser', () => {
1402
1403
  expect(sortBar.sortDirection).to.be.null;
1403
1404
  });
1404
1405
 
1405
- it('uses date favorited sort as default when set externally for fav- collection', async () => {
1406
+ it('uses date favorited sort as default when targeting fav- collection', async () => {
1406
1407
  const searchService = new MockSearchService();
1407
1408
  const el = await fixture<CollectionBrowser>(
1408
1409
  html`<collection-browser
1409
1410
  .searchService=${searchService}
1410
1411
  .baseNavigationUrl=${''}
1411
- .withinCollection=${'fav-sort'}
1412
- .defaultSortField=${SortField.datefavorited}
1413
- .defaultSortDirection=${'desc'}
1414
1412
  ></collection-browser>`,
1415
1413
  );
1416
1414
 
1415
+ el.withinCollection = 'fav-sort';
1417
1416
  await el.updateComplete;
1417
+ await el.initialSearchComplete;
1418
+ await el.updateComplete;
1419
+ await aTimeout(50);
1418
1420
 
1419
1421
  const sortBar = el.shadowRoot?.querySelector(
1420
1422
  'sort-filter-bar',