@internetarchive/collection-browser 4.4.1 → 4.5.1-alpha-webdev8221.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 (85) hide show
  1. package/.editorconfig +29 -29
  2. package/.github/workflows/ci.yml +27 -27
  3. package/.github/workflows/gh-pages-main.yml +39 -39
  4. package/.github/workflows/npm-publish.yml +39 -39
  5. package/.github/workflows/pr-preview.yml +38 -38
  6. package/.husky/pre-commit +1 -1
  7. package/.prettierignore +1 -1
  8. package/LICENSE +661 -661
  9. package/README.md +83 -83
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/src/app-root.d.ts +8 -0
  13. package/dist/src/app-root.js +26 -0
  14. package/dist/src/app-root.js.map +1 -1
  15. package/dist/src/collection-browser.d.ts +8 -4
  16. package/dist/src/collection-browser.js +20 -11
  17. package/dist/src/collection-browser.js.map +1 -1
  18. package/dist/src/collection-facets/facet-row.d.ts +6 -0
  19. package/dist/src/collection-facets/facet-row.js +19 -1
  20. package/dist/src/collection-facets/facet-row.js.map +1 -1
  21. package/dist/src/collection-facets/facets-template.js +2 -0
  22. package/dist/src/collection-facets/facets-template.js.map +1 -1
  23. package/dist/src/data-source/collection-browser-data-source-interface.d.ts +5 -0
  24. package/dist/src/data-source/collection-browser-data-source-interface.js.map +1 -1
  25. package/dist/src/data-source/collection-browser-data-source.d.ts +1 -1
  26. package/dist/src/data-source/collection-browser-data-source.js +8 -6
  27. package/dist/src/data-source/collection-browser-data-source.js.map +1 -1
  28. package/dist/src/restoration-state-handler.js +2 -4
  29. package/dist/src/restoration-state-handler.js.map +1 -1
  30. package/dist/src/styles/tile-action-styles.d.ts +14 -0
  31. package/dist/src/styles/tile-action-styles.js +59 -0
  32. package/dist/src/styles/tile-action-styles.js.map +1 -0
  33. package/dist/src/tiles/base-tile-component.d.ts +17 -1
  34. package/dist/src/tiles/base-tile-component.js +50 -1
  35. package/dist/src/tiles/base-tile-component.js.map +1 -1
  36. package/dist/src/tiles/grid/item-tile.js +1 -0
  37. package/dist/src/tiles/grid/item-tile.js.map +1 -1
  38. package/dist/src/tiles/list/tile-list-compact-header.js +27 -2
  39. package/dist/src/tiles/list/tile-list-compact-header.js.map +1 -1
  40. package/dist/src/tiles/list/tile-list-compact.d.ts +1 -1
  41. package/dist/src/tiles/list/tile-list-compact.js +113 -75
  42. package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
  43. package/dist/src/tiles/list/tile-list.d.ts +1 -1
  44. package/dist/src/tiles/list/tile-list.js +164 -146
  45. package/dist/src/tiles/list/tile-list.js.map +1 -1
  46. package/dist/src/tiles/models.d.ts +11 -0
  47. package/dist/src/tiles/models.js.map +1 -1
  48. package/dist/src/tiles/tile-dispatcher.d.ts +14 -0
  49. package/dist/src/tiles/tile-dispatcher.js +107 -4
  50. package/dist/src/tiles/tile-dispatcher.js.map +1 -1
  51. package/dist/src/utils/date-filter-field.d.ts +6 -0
  52. package/dist/src/utils/date-filter-field.js +9 -0
  53. package/dist/src/utils/date-filter-field.js.map +1 -0
  54. package/dist/test/collection-browser.test.js +81 -0
  55. package/dist/test/collection-browser.test.js.map +1 -1
  56. package/dist/test/collection-facets/facet-row.test.js +32 -0
  57. package/dist/test/collection-facets/facet-row.test.js.map +1 -1
  58. package/eslint.config.mjs +53 -53
  59. package/index.html +24 -24
  60. package/index.ts +1 -0
  61. package/local.archive.org.cert +86 -86
  62. package/local.archive.org.key +27 -27
  63. package/package.json +120 -120
  64. package/renovate.json +6 -6
  65. package/src/app-root.ts +30 -0
  66. package/src/collection-browser.ts +17 -13
  67. package/src/collection-facets/facet-row.ts +11 -1
  68. package/src/collection-facets/facets-template.ts +2 -0
  69. package/src/data-source/collection-browser-data-source-interface.ts +6 -0
  70. package/src/data-source/collection-browser-data-source.ts +16 -7
  71. package/src/restoration-state-handler.ts +5 -5
  72. package/src/styles/tile-action-styles.ts +59 -0
  73. package/src/tiles/base-tile-component.ts +60 -1
  74. package/src/tiles/grid/item-tile.ts +1 -0
  75. package/src/tiles/list/tile-list-compact-header.ts +28 -2
  76. package/src/tiles/list/tile-list-compact.ts +114 -75
  77. package/src/tiles/list/tile-list.ts +190 -172
  78. package/src/tiles/models.ts +13 -0
  79. package/src/tiles/tile-dispatcher.ts +114 -4
  80. package/src/utils/date-filter-field.ts +11 -0
  81. package/test/collection-browser.test.ts +132 -0
  82. package/test/collection-facets/facet-row.test.ts +46 -0
  83. package/tsconfig.json +25 -25
  84. package/web-dev-server.config.mjs +30 -30
  85. package/web-test-runner.config.mjs +52 -52
package/package.json CHANGED
@@ -1,120 +1,120 @@
1
- {
2
- "name": "@internetarchive/collection-browser",
3
- "description": "The Internet Archive Collection Browser.",
4
- "license": "AGPL-3.0-only",
5
- "author": "Internet Archive",
6
- "version": "4.4.1",
7
- "main": "dist/index.js",
8
- "module": "dist/index.js",
9
- "scripts": {
10
- "start": "vite",
11
- "prepare:ghpages": "rimraf ghpages && yarn run prepare && vite build",
12
- "prepare": "rimraf dist && tsc && husky install",
13
- "lint": "eslint . && prettier \"**/*.ts\" --check",
14
- "format": "eslint . --fix && prettier \"**/*.ts\" --write",
15
- "circular": "madge --circular --extensions ts .",
16
- "test": "tsc && yarn run lint && yarn run circular && wtr --coverage",
17
- "test:fast": "tsc && wtr --coverage",
18
- "test:watch": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wtr --watch\"",
19
- "deploy": "yarn run deploy:run -e $(git branch --show-current)",
20
- "deploy:run": "yarn run prepare:ghpages && touch ghpages/.nojekyll && yarn run deploy:gh",
21
- "deploy:gh": "gh-pages -t -d ghpages -m \"Build for $(git log --pretty=format:\"%h %an %ai %s\" -n1) [skip ci]\"",
22
- "typecheck": "yarn tsc --noEmit"
23
- },
24
- "types": "dist/index.d.ts",
25
- "dependencies": {
26
- "@a11y/focus-trap": "^1.0.5",
27
- "@internetarchive/analytics-manager": "^0.1.4",
28
- "@internetarchive/elements": "^0.2.2",
29
- "@internetarchive/feature-feedback": "^1.0.0",
30
- "@internetarchive/field-parsers": "^1.0.0",
31
- "@internetarchive/histogram-date-range": "^1.4.2",
32
- "@internetarchive/ia-dropdown": "^2.0.0",
33
- "@internetarchive/iaux-item-metadata": "^1.0.5",
34
- "@internetarchive/infinite-scroller": "^2.0.0",
35
- "@internetarchive/modal-manager": "^2.0.5",
36
- "@internetarchive/search-service": "2.7.2",
37
- "@internetarchive/shared-resize-observer": "^0.2.0",
38
- "@lit/localize": "^0.12.2",
39
- "dompurify": "^3.2.4",
40
- "lit": "^2.8.0 || ^3.3.2",
41
- "typescript-cookie": "^1.0.6"
42
- },
43
- "devDependencies": {
44
- "@internetarchive/result-type": "^0.0.1",
45
- "@open-wc/eslint-config": "^13.0.0",
46
- "@open-wc/testing": "^4.0.0",
47
- "@rollup/plugin-image": "^3.0.3",
48
- "@types/dompurify": "^3.2.0",
49
- "@types/mocha": "^10.0.10",
50
- "@typescript-eslint/eslint-plugin": "^8.27.0",
51
- "@typescript-eslint/parser": "^8.27.0",
52
- "@vitejs/plugin-basic-ssl": "^2.1.4",
53
- "@web/dev-server": "^0.4.6",
54
- "@web/dev-server-rollup": "^0.6.4",
55
- "@web/test-runner": "^0.20.0",
56
- "concurrently": "^9.1.2",
57
- "eslint": "^9.22.0",
58
- "eslint-config-prettier": "^10.1.1",
59
- "eslint-plugin-html": "^8.1.2",
60
- "eslint-plugin-import": "^2.31.0",
61
- "eslint-plugin-lit": "^2.0.0",
62
- "eslint-plugin-lit-a11y": "^5.0.0",
63
- "eslint-plugin-no-only-tests": "^3.3.0",
64
- "eslint-plugin-wc": "^3.0.0",
65
- "gh-pages": "^6.3.0",
66
- "husky": "^9.1.7",
67
- "madge": "^8.0.0",
68
- "prettier": "^3.5.3",
69
- "rimraf": "^6.0.1",
70
- "sinon": "^19.0.4",
71
- "tslib": "^2.8.1",
72
- "typescript": "^5.8.2",
73
- "vite": "^8.0.0"
74
- },
75
- "publishConfig": {
76
- "access": "public"
77
- },
78
- "eslintConfig": {
79
- "parser": "@typescript-eslint/parser",
80
- "extends": [
81
- "@open-wc",
82
- "prettier"
83
- ],
84
- "plugins": [
85
- "@typescript-eslint"
86
- ],
87
- "rules": {
88
- "no-unused-vars": "off",
89
- "@typescript-eslint/no-unused-vars": [
90
- "error"
91
- ],
92
- "no-shadow": "off",
93
- "@typescript-eslint/no-shadow": [
94
- "error"
95
- ],
96
- "class-methods-use-this": "off",
97
- "import/no-unresolved": "off",
98
- "import/extensions": [
99
- "off",
100
- "ignorePackages",
101
- {
102
- "js": "never",
103
- "ts": "never"
104
- }
105
- ],
106
- "no-unsafe-optional-chaining": "warn",
107
- "default-param-last": "warn"
108
- }
109
- },
110
- "prettier": {
111
- "singleQuote": true,
112
- "arrowParens": "avoid"
113
- },
114
- "lint-staged": {
115
- "*.ts": [
116
- "eslint --fix",
117
- "prettier --write"
118
- ]
119
- }
120
- }
1
+ {
2
+ "name": "@internetarchive/collection-browser",
3
+ "description": "The Internet Archive Collection Browser.",
4
+ "license": "AGPL-3.0-only",
5
+ "author": "Internet Archive",
6
+ "version": "4.5.1-alpha-webdev8221.0",
7
+ "main": "dist/index.js",
8
+ "module": "dist/index.js",
9
+ "scripts": {
10
+ "start": "vite",
11
+ "prepare:ghpages": "rimraf ghpages && yarn run prepare && vite build",
12
+ "prepare": "rimraf dist && tsc && husky install",
13
+ "lint": "eslint . && prettier \"**/*.ts\" --check",
14
+ "format": "eslint . --fix && prettier \"**/*.ts\" --write",
15
+ "circular": "madge --circular --extensions ts .",
16
+ "test": "tsc && yarn run lint && yarn run circular && wtr --coverage",
17
+ "test:fast": "tsc && wtr --coverage",
18
+ "test:watch": "tsc && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"wtr --watch\"",
19
+ "deploy": "yarn run deploy:run -e $(git branch --show-current)",
20
+ "deploy:run": "yarn run prepare:ghpages && touch ghpages/.nojekyll && yarn run deploy:gh",
21
+ "deploy:gh": "gh-pages -t -d ghpages -m \"Build for $(git log --pretty=format:\"%h %an %ai %s\" -n1) [skip ci]\"",
22
+ "typecheck": "yarn tsc --noEmit"
23
+ },
24
+ "types": "dist/index.d.ts",
25
+ "dependencies": {
26
+ "@a11y/focus-trap": "^1.0.5",
27
+ "@internetarchive/analytics-manager": "^0.1.4",
28
+ "@internetarchive/elements": "^0.2.2",
29
+ "@internetarchive/feature-feedback": "^1.0.0",
30
+ "@internetarchive/field-parsers": "^1.0.0",
31
+ "@internetarchive/histogram-date-range": "^1.4.2",
32
+ "@internetarchive/ia-dropdown": "^2.0.0",
33
+ "@internetarchive/iaux-item-metadata": "^1.0.5",
34
+ "@internetarchive/infinite-scroller": "^2.0.0",
35
+ "@internetarchive/modal-manager": "^2.0.5",
36
+ "@internetarchive/search-service": "^2.7.2",
37
+ "@internetarchive/shared-resize-observer": "^0.2.0",
38
+ "@lit/localize": "^0.12.2",
39
+ "dompurify": "^3.2.4",
40
+ "lit": "^2.8.0 || ^3.3.2",
41
+ "typescript-cookie": "^1.0.6"
42
+ },
43
+ "devDependencies": {
44
+ "@internetarchive/result-type": "^0.0.1",
45
+ "@open-wc/eslint-config": "^13.0.0",
46
+ "@open-wc/testing": "^4.0.0",
47
+ "@rollup/plugin-image": "^3.0.3",
48
+ "@types/dompurify": "^3.2.0",
49
+ "@types/mocha": "^10.0.10",
50
+ "@typescript-eslint/eslint-plugin": "^8.27.0",
51
+ "@typescript-eslint/parser": "^8.27.0",
52
+ "@vitejs/plugin-basic-ssl": "^2.1.4",
53
+ "@web/dev-server": "^0.4.6",
54
+ "@web/dev-server-rollup": "^0.6.4",
55
+ "@web/test-runner": "^0.20.0",
56
+ "concurrently": "^9.1.2",
57
+ "eslint": "^9.22.0",
58
+ "eslint-config-prettier": "^10.1.1",
59
+ "eslint-plugin-html": "^8.1.2",
60
+ "eslint-plugin-import": "^2.31.0",
61
+ "eslint-plugin-lit": "^2.0.0",
62
+ "eslint-plugin-lit-a11y": "^5.0.0",
63
+ "eslint-plugin-no-only-tests": "^3.3.0",
64
+ "eslint-plugin-wc": "^3.0.0",
65
+ "gh-pages": "^6.3.0",
66
+ "husky": "^9.1.7",
67
+ "madge": "^8.0.0",
68
+ "prettier": "^3.5.3",
69
+ "rimraf": "^6.0.1",
70
+ "sinon": "^19.0.4",
71
+ "tslib": "^2.8.1",
72
+ "typescript": "^5.8.2",
73
+ "vite": "^8.0.0"
74
+ },
75
+ "publishConfig": {
76
+ "access": "public"
77
+ },
78
+ "eslintConfig": {
79
+ "parser": "@typescript-eslint/parser",
80
+ "extends": [
81
+ "@open-wc",
82
+ "prettier"
83
+ ],
84
+ "plugins": [
85
+ "@typescript-eslint"
86
+ ],
87
+ "rules": {
88
+ "no-unused-vars": "off",
89
+ "@typescript-eslint/no-unused-vars": [
90
+ "error"
91
+ ],
92
+ "no-shadow": "off",
93
+ "@typescript-eslint/no-shadow": [
94
+ "error"
95
+ ],
96
+ "class-methods-use-this": "off",
97
+ "import/no-unresolved": "off",
98
+ "import/extensions": [
99
+ "off",
100
+ "ignorePackages",
101
+ {
102
+ "js": "never",
103
+ "ts": "never"
104
+ }
105
+ ],
106
+ "no-unsafe-optional-chaining": "warn",
107
+ "default-param-last": "warn"
108
+ }
109
+ },
110
+ "prettier": {
111
+ "singleQuote": true,
112
+ "arrowParens": "avoid"
113
+ },
114
+ "lint-staged": {
115
+ "*.ts": [
116
+ "eslint --fix",
117
+ "prettier --write"
118
+ ]
119
+ }
120
+ }
package/renovate.json CHANGED
@@ -1,6 +1,6 @@
1
- {
2
- "extends": [
3
- "config:base",
4
- ":preserveSemverRanges"
5
- ]
6
- }
1
+ {
2
+ "extends": [
3
+ "config:base",
4
+ ":preserveSemverRanges"
5
+ ]
6
+ }
package/src/app-root.ts CHANGED
@@ -384,6 +384,16 @@ export class AppRoot extends LitElement {
384
384
  />
385
385
  <label for="enable-smart-facet-bar">Enable smart facet bar</label>
386
386
  </div>
387
+ <div class="checkbox-control">
388
+ <input
389
+ type="checkbox"
390
+ id="enable-tile-actions"
391
+ @click=${this.tileActionsCheckboxChanged}
392
+ />
393
+ <label for="enable-tile-actions"
394
+ >Enable tile action buttons</label
395
+ >
396
+ </div>
387
397
  </fieldset>
388
398
 
389
399
  <fieldset class="cb-visual-appearance">
@@ -584,6 +594,7 @@ export class AppRoot extends LitElement {
584
594
  @manageModeChanged=${this.manageModeChanged}
585
595
  @itemRemovalRequested=${this.handleItemRemovalRequest}
586
596
  @itemManagerRequested=${this.handleItemManagerRequest}
597
+ @tileActionClicked=${this.handleTileActionClicked}
587
598
  >
588
599
  ${this.toggleSlots
589
600
  ? html`<div slot="sortbar-left-slot">Sort Slot</div>`
@@ -805,6 +816,25 @@ export class AppRoot extends LitElement {
805
816
  this.collectionBrowser.showSmartFacetBar = target.checked;
806
817
  }
807
818
 
819
+ /**
820
+ * Handler for when the dev panel's "Enable tile action buttons" checkbox is changed.
821
+ */
822
+ private tileActionsCheckboxChanged(e: Event) {
823
+ const target = e.target as HTMLInputElement;
824
+ this.collectionBrowser.tileActions = target.checked
825
+ ? [{ id: 'demo-action', label: 'Return' }]
826
+ : [];
827
+ }
828
+
829
+ /**
830
+ * Handler for tile action button clicks (logs to console for QA).
831
+ */
832
+ private handleTileActionClicked(
833
+ e: CustomEvent<{ actionId: string; model: unknown }>,
834
+ ) {
835
+ console.log('Tile action clicked:', e.detail.actionId, e.detail.model);
836
+ }
837
+
808
838
  /**
809
839
  * Handler for when the dev panel's "Show facet top slot" checkbox is changed.
810
840
  */
@@ -78,6 +78,7 @@ import { sha1 } from './utils/sha1';
78
78
  import type { PlaceholderType } from './empty-placeholder';
79
79
  import type { ManageBar } from './manage/manage-bar';
80
80
  import type { SmartFacetBar } from './collection-facets/smart-facets/smart-facet-bar';
81
+ import type { LayoutType, TileAction } from './tiles/models';
81
82
 
82
83
  import '@internetarchive/elements/ia-combo-box/ia-combo-box';
83
84
  import './empty-placeholder';
@@ -297,6 +298,15 @@ export class CollectionBrowser
297
298
  */
298
299
  @property({ type: Boolean }) isManageView = false;
299
300
 
301
+ /** Action buttons to display on each tile */
302
+ @property({ type: Array }) tileActions: TileAction[] = [];
303
+
304
+ /**
305
+ * The simplified layout to apply to grid-mode tiles, if any. See
306
+ * `LayoutType` for available options. Has no effect on list display modes.
307
+ */
308
+ @property({ type: String }) tileLayoutType: LayoutType = 'default';
309
+
300
310
  @property({ type: String }) manageViewLabel = 'Select items to remove';
301
311
 
302
312
  /** Whether to replace the default sort options with a slot for customization (default: false) */
@@ -1652,6 +1662,7 @@ export class CollectionBrowser
1652
1662
  .mobileBreakpoint=${this.mobileBreakpoint}
1653
1663
  .loggedIn=${this.loggedIn}
1654
1664
  .suppressBlurring=${this.shouldSuppressTileBlurring}
1665
+ .tileActions=${this.tileActions}
1655
1666
  >
1656
1667
  </tile-dispatcher>
1657
1668
  </div>
@@ -1673,21 +1684,10 @@ export class CollectionBrowser
1673
1684
  this.analyticsHandler?.sendEvent({
1674
1685
  category: this.searchContext,
1675
1686
  action: analyticsActions.histogramChanged,
1676
- label: this.dateRangeQueryClause,
1687
+ label: this.dataSource.dateRangeQueryClause,
1677
1688
  });
1678
1689
  }
1679
1690
 
1680
- /**
1681
- * The Lucene query corresponding to the current date range.
1682
- */
1683
- private get dateRangeQueryClause() {
1684
- if (!this.minSelectedDate || !this.maxSelectedDate) {
1685
- return undefined;
1686
- }
1687
-
1688
- return `year:[${this.minSelectedDate} TO ${this.maxSelectedDate}]`;
1689
- }
1690
-
1691
1691
  /**
1692
1692
  * Emits an event indicating a change in whether the manage mode is shown.
1693
1693
  */
@@ -1801,7 +1801,9 @@ export class CollectionBrowser
1801
1801
  changed.has('displayMode') ||
1802
1802
  changed.has('baseNavigationUrl') ||
1803
1803
  changed.has('baseImageUrl') ||
1804
- changed.has('loggedIn')
1804
+ changed.has('loggedIn') ||
1805
+ changed.has('tileActions') ||
1806
+ changed.has('tileLayoutType')
1805
1807
  ) {
1806
1808
  this.infiniteScroller?.reload();
1807
1809
  }
@@ -2635,6 +2637,8 @@ export class CollectionBrowser
2635
2637
  .loggedIn=${this.loggedIn}
2636
2638
  .suppressBlurring=${this.shouldSuppressTileBlurring}
2637
2639
  .isManageView=${this.isManageView}
2640
+ .tileActions=${this.tileActions}
2641
+ .layoutType=${this.tileLayoutType}
2638
2642
  ?showTvClips=${isTVSearch || isTVCollection}
2639
2643
  ?enableHoverPane=${true}
2640
2644
  ?useLocalTime=${shouldUseLocalTime}
@@ -30,6 +30,13 @@ export class FacetRow extends LitElement {
30
30
  /** The facet bucket containing details about the state, count, and key for this row */
31
31
  @property({ type: Object }) bucket?: FacetBucket;
32
32
 
33
+ /**
34
+ * Whether to omit the negative/hide button from this facet row if possible.
35
+ * Has no effect if the facet bucket is itself hidden, in which case the hide
36
+ * button *must* be shown to represent the state accurately.
37
+ */
38
+ @property({ type: Boolean, reflect: true }) omitHideButton = false;
39
+
33
40
  /** The collection name cache for converting collection identifiers to titles */
34
41
  @property({ type: Object })
35
42
  collectionTitles?: CollectionTitles;
@@ -102,7 +109,10 @@ export class FacetRow extends LitElement {
102
109
  id=${showOnlyCheckboxId}
103
110
  data-testid=${showOnlyCheckboxId}
104
111
  />
105
- <div class="hide-facet-container">
112
+ <div
113
+ class="hide-facet-container"
114
+ ?hidden=${this.omitHideButton && !facetHidden}
115
+ >
106
116
  <input
107
117
  type="checkbox"
108
118
  id=${negativeCheckboxId}
@@ -36,6 +36,7 @@ export class FacetsTemplate extends LitElement {
36
36
  if (!facetGroup) return nothing;
37
37
 
38
38
  const facetBuckets = facetGroup.buckets as FacetBucket[];
39
+ const isLoneBucket = facetBuckets.length === 1;
39
40
 
40
41
  // Added data-testid for Playwright testing
41
42
  // Using className and aria-labels is not ideal for Playwright locator
@@ -49,6 +50,7 @@ export class FacetsTemplate extends LitElement {
49
50
  .facetType=${facetGroup.key}
50
51
  .bucket=${bucket}
51
52
  .collectionTitles=${this.collectionTitles}
53
+ ?omitHideButton=${isLoneBucket}
52
54
  @facetClick=${this.facetClicked}
53
55
  ></facet-row>`,
54
56
  )}
@@ -71,6 +71,12 @@ export interface CollectionBrowserDataSourceInterface
71
71
  */
72
72
  readonly pageSpecifierParams: PageSpecifierParams | null;
73
73
 
74
+ /**
75
+ * The Lucene query clause corresponding to the current date range,
76
+ * e.g. `year:[2000 TO 2020]` or `date:[2020-01 TO 2020-12]`.
77
+ */
78
+ readonly dateRangeQueryClause: string | undefined;
79
+
74
80
  /**
75
81
  * A FilterMap object representing all filters applied to the current search,
76
82
  * including any facets, letter filters, and date ranges.
@@ -33,6 +33,7 @@ import type { CollectionBrowserDataSourceInterface } from './collection-browser-
33
33
  import type { CollectionBrowserSearchInterface } from './collection-browser-query-state';
34
34
  import { sha1 } from '../utils/sha1';
35
35
  import { mergeSelectedFacets } from '../utils/facet-utils';
36
+ import { dateFilterField } from '../utils/date-filter-field';
36
37
 
37
38
  export class CollectionBrowserDataSource
38
39
  implements CollectionBrowserDataSourceInterface
@@ -746,7 +747,10 @@ export class CollectionBrowserDataSource
746
747
  selectedCreatorFilter,
747
748
  } = this.host;
748
749
 
749
- const dateField = this.host.searchType === SearchType.TV ? 'date' : 'year';
750
+ const dateField = dateFilterField(
751
+ this.host.minSelectedDate,
752
+ this.host.maxSelectedDate,
753
+ );
750
754
 
751
755
  if (minSelectedDate) {
752
756
  builder.addFilter(
@@ -901,12 +905,16 @@ export class CollectionBrowserDataSource
901
905
  return this.joinFacetClauses(facetClauses)?.trim();
902
906
  }
903
907
 
904
- private get dateRangeQueryClause(): string | undefined {
908
+ get dateRangeQueryClause(): string | undefined {
905
909
  if (!this.host.minSelectedDate || !this.host.maxSelectedDate) {
906
910
  return undefined;
907
911
  }
908
912
 
909
- return `year:[${this.host.minSelectedDate} TO ${this.host.maxSelectedDate}]`;
913
+ const dateField = dateFilterField(
914
+ this.host.minSelectedDate,
915
+ this.host.maxSelectedDate,
916
+ );
917
+ return `${dateField}:[${this.host.minSelectedDate} TO ${this.host.maxSelectedDate}]`;
910
918
  }
911
919
 
912
920
  private get sortFilterQueries(): string {
@@ -1069,10 +1077,11 @@ export class CollectionBrowserDataSource
1069
1077
  success.response;
1070
1078
  this.aggregations = aggregations;
1071
1079
 
1072
- this.histogramAggregation =
1073
- this.host.searchType === SearchType.TV
1074
- ? aggregations?.date_histogram
1075
- : aggregations?.year_histogram;
1080
+ const useDateHistogram =
1081
+ this.host.searchType === SearchType.TV || this.host.isTVCollection;
1082
+ this.histogramAggregation = useDateHistogram
1083
+ ? (aggregations?.date_histogram ?? aggregations?.year_histogram)
1084
+ : aggregations?.year_histogram;
1076
1085
 
1077
1086
  if (collectionTitles) {
1078
1087
  for (const [id, title] of Object.entries(collectionTitles)) {
@@ -14,6 +14,7 @@ import {
14
14
  tvClipURLParamsToFilters,
15
15
  } from './models';
16
16
  import { arrayEquals } from './utils/array-equals';
17
+ import { dateFilterField } from './utils/date-filter-field';
17
18
 
18
19
  export interface RestorationState {
19
20
  displayMode?: CollectionDisplayMode;
@@ -190,11 +191,10 @@ export class RestorationStateHandler
190
191
  }
191
192
  }
192
193
 
193
- const dateField =
194
- state.minSelectedDate?.includes('-') ||
195
- state.maxSelectedDate?.includes('-')
196
- ? 'date'
197
- : 'year';
194
+ const dateField = dateFilterField(
195
+ state.minSelectedDate,
196
+ state.maxSelectedDate,
197
+ );
198
198
 
199
199
  if (state.minSelectedDate && state.maxSelectedDate) {
200
200
  newParams.append(
@@ -0,0 +1,59 @@
1
+ import { css } from 'lit';
2
+
3
+ /**
4
+ * Shared styles for tile action buttons rendered in grid, list-detail, and
5
+ * list-compact display modes. Layout positioning is handled by each tile
6
+ * component; these styles cover only the button appearance and the row
7
+ * container's default flex behavior.
8
+ *
9
+ * Customizable via CSS custom properties:
10
+ * - `--tileActionColor` (defaults to --primaryErrorCTAFill, then #d9534f)
11
+ * - `--tileActionBg` (default: #fff)
12
+ * - `--tileActionBorderColor` (defaults to --primaryErrorCTAFill, then #d9534f)
13
+ * - `--tileActionHoverBg` (defaults to --primaryErrorCTAFillRGB at 20% alpha)
14
+ * - `--tileActionHoverColor` (defaults to --tileActionColor)
15
+ */
16
+ export const tileActionStyles = css`
17
+ .tile-actions {
18
+ flex-shrink: 0;
19
+ display: flex;
20
+ gap: var(--tileActionGap, 0);
21
+ }
22
+
23
+ .tile-action-btn {
24
+ flex: 1;
25
+ padding: 6px 5px;
26
+ border: 2px solid
27
+ var(--tileActionBorderColor, var(--primaryErrorCTAFill, #d9534f));
28
+ border-radius: var(--tileActionBorderRadius, 0);
29
+ /* Inherit from the surrounding tile rather than the UA default for <button> */
30
+ font-family: inherit;
31
+ font-size: 1.2rem;
32
+ font-weight: bold;
33
+ cursor: pointer;
34
+ color: var(--tileActionColor, var(--primaryErrorCTAFill, #d9534f));
35
+ background: var(--tileActionBg, #fff);
36
+ transition:
37
+ background 0.15s,
38
+ color 0.15s;
39
+ }
40
+
41
+ /*
42
+ * When buttons are flush against each other (no gap), overlap their shared
43
+ * edge by 1px so adjacent borders don't double up.
44
+ */
45
+ .tile-action-btn + .tile-action-btn {
46
+ margin-left: -1px;
47
+ }
48
+
49
+ .tile-action-btn:hover {
50
+ background: var(
51
+ --tileActionHoverBg,
52
+ rgba(var(--primaryErrorCTAFillRGB, 229, 28, 38), 0.2)
53
+ );
54
+ color: var(
55
+ --tileActionHoverColor,
56
+ var(--tileActionColor, var(--primaryErrorCTAFill, #d9534f))
57
+ );
58
+ }
59
+ `;
@@ -1,13 +1,18 @@
1
- import { LitElement, PropertyValues } from 'lit';
1
+ import { html, LitElement, nothing, PropertyValues, TemplateResult } from 'lit';
2
2
  import { property } from 'lit/decorators.js';
3
+ import { classMap } from 'lit/directives/class-map.js';
3
4
  import type { SortParam } from '@internetarchive/search-service';
4
5
  import { TileDisplayValueProvider } from './tile-display-value-provider';
5
6
  import type { TileModel } from '../models';
6
7
  import { DateFormat, formatDate } from '../utils/format-date';
8
+ import type { TileAction } from './models';
7
9
 
8
10
  export abstract class BaseTileComponent extends LitElement {
9
11
  @property({ type: Object }) model?: TileModel;
10
12
 
13
+ /** Action buttons to display on this tile (rendered by subclasses) */
14
+ @property({ type: Array }) tileActions: TileAction[] = [];
15
+
11
16
  @property({ type: Number }) currentWidth?: number;
12
17
 
13
18
  @property({ type: Number }) currentHeight?: number;
@@ -62,4 +67,58 @@ export abstract class BaseTileComponent extends LitElement {
62
67
  const { useLocalTime } = this;
63
68
  return formatDate(date, format, { useLocalTime });
64
69
  }
70
+
71
+ /**
72
+ * Renders the action buttons configured for this tile, or `nothing` if
73
+ * there are none. Optional `extraClass` is appended to the container's
74
+ * class list so subclasses can target their own layout tweaks.
75
+ */
76
+ protected renderTileActions(
77
+ extraClass: string = '',
78
+ ): TemplateResult | typeof nothing {
79
+ if (!this.tileActions.length) return nothing;
80
+
81
+ const containerClasses = classMap({
82
+ 'tile-actions': true,
83
+ ...(extraClass && { [extraClass]: true }),
84
+ });
85
+
86
+ return html`
87
+ <div class=${containerClasses}>
88
+ ${this.tileActions.map(
89
+ action => html`
90
+ <button
91
+ class="tile-action-btn"
92
+ @click=${(e: Event) => this.handleTileActionClick(e, action)}
93
+ >
94
+ ${action.label}
95
+ </button>
96
+ `,
97
+ )}
98
+ </div>
99
+ `;
100
+ }
101
+
102
+ /**
103
+ * Click handler for tile action buttons. Stops propagation so the click
104
+ * doesn't activate a wrapping tile link, and dispatches a
105
+ * `tileActionClicked` event (bubbling + composed) carrying the action ID
106
+ * and the tile model.
107
+ */
108
+ protected handleTileActionClick(e: Event, action: TileAction): void {
109
+ e.preventDefault();
110
+ e.stopPropagation();
111
+ // Pre-set the hover pane controller's clicking flag so that focus
112
+ // restoration after a consumer-opened modal won't trigger the hover pane.
113
+ this.dispatchEvent(
114
+ new PointerEvent('pointerdown', { bubbles: true, composed: true }),
115
+ );
116
+ this.dispatchEvent(
117
+ new CustomEvent('tileActionClicked', {
118
+ detail: { actionId: action.id, model: this.model },
119
+ bubbles: true,
120
+ composed: true,
121
+ }),
122
+ );
123
+ }
65
124
  }
@@ -24,6 +24,7 @@ export class ItemTile extends BaseTileComponent {
24
24
  /*
25
25
  * Reactive properties inherited from BaseTileComponent:
26
26
  * - model?: TileModel;
27
+ * - tileActions: TileAction[] = [];
27
28
  * - currentWidth?: number;
28
29
  * - currentHeight?: number;
29
30
  * - baseNavigationUrl?: string;