@internetarchive/collection-browser 0.3.0 → 0.3.1-alpha.1

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 (212) hide show
  1. package/.editorconfig +29 -29
  2. package/.github/workflows/ci.yml +26 -26
  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 +4 -4
  7. package/LICENSE +661 -661
  8. package/README.md +83 -83
  9. package/dist/index.d.ts +9 -9
  10. package/dist/index.js +9 -9
  11. package/dist/src/app-root.d.ts +43 -43
  12. package/dist/src/app-root.js +233 -233
  13. package/dist/src/assets/img/icons/arrow-left.d.ts +2 -2
  14. package/dist/src/assets/img/icons/arrow-left.js +2 -2
  15. package/dist/src/assets/img/icons/arrow-right.d.ts +2 -2
  16. package/dist/src/assets/img/icons/arrow-right.js +2 -2
  17. package/dist/src/assets/img/icons/chevron.d.ts +2 -2
  18. package/dist/src/assets/img/icons/chevron.js +2 -2
  19. package/dist/src/assets/img/icons/empty-query.d.ts +2 -2
  20. package/dist/src/assets/img/icons/empty-query.js +2 -2
  21. package/dist/src/assets/img/icons/eye-closed.d.ts +2 -2
  22. package/dist/src/assets/img/icons/eye-closed.js +2 -2
  23. package/dist/src/assets/img/icons/eye.d.ts +2 -2
  24. package/dist/src/assets/img/icons/eye.js +2 -2
  25. package/dist/src/assets/img/icons/favorite-filled.d.ts +1 -1
  26. package/dist/src/assets/img/icons/favorite-filled.js +2 -2
  27. package/dist/src/assets/img/icons/login-required.d.ts +1 -1
  28. package/dist/src/assets/img/icons/login-required.js +2 -2
  29. package/dist/src/assets/img/icons/mediatype/account.d.ts +1 -1
  30. package/dist/src/assets/img/icons/mediatype/account.js +2 -2
  31. package/dist/src/assets/img/icons/mediatype/audio.d.ts +1 -1
  32. package/dist/src/assets/img/icons/mediatype/audio.js +2 -2
  33. package/dist/src/assets/img/icons/mediatype/collection.d.ts +1 -1
  34. package/dist/src/assets/img/icons/mediatype/collection.js +2 -2
  35. package/dist/src/assets/img/icons/mediatype/data.d.ts +1 -1
  36. package/dist/src/assets/img/icons/mediatype/data.js +2 -2
  37. package/dist/src/assets/img/icons/mediatype/etree.d.ts +1 -1
  38. package/dist/src/assets/img/icons/mediatype/etree.js +2 -2
  39. package/dist/src/assets/img/icons/mediatype/film.d.ts +1 -1
  40. package/dist/src/assets/img/icons/mediatype/film.js +2 -2
  41. package/dist/src/assets/img/icons/mediatype/images.d.ts +1 -1
  42. package/dist/src/assets/img/icons/mediatype/images.js +2 -2
  43. package/dist/src/assets/img/icons/mediatype/radio.d.ts +1 -1
  44. package/dist/src/assets/img/icons/mediatype/radio.js +2 -2
  45. package/dist/src/assets/img/icons/mediatype/software.d.ts +1 -1
  46. package/dist/src/assets/img/icons/mediatype/software.js +2 -2
  47. package/dist/src/assets/img/icons/mediatype/texts.d.ts +1 -1
  48. package/dist/src/assets/img/icons/mediatype/texts.js +2 -2
  49. package/dist/src/assets/img/icons/mediatype/tv.d.ts +1 -1
  50. package/dist/src/assets/img/icons/mediatype/tv.js +2 -2
  51. package/dist/src/assets/img/icons/mediatype/video.d.ts +1 -1
  52. package/dist/src/assets/img/icons/mediatype/video.js +2 -2
  53. package/dist/src/assets/img/icons/mediatype/web.d.ts +1 -1
  54. package/dist/src/assets/img/icons/mediatype/web.js +2 -2
  55. package/dist/src/assets/img/icons/null-result.d.ts +2 -2
  56. package/dist/src/assets/img/icons/null-result.js +2 -2
  57. package/dist/src/assets/img/icons/restricted.d.ts +1 -1
  58. package/dist/src/assets/img/icons/restricted.js +2 -2
  59. package/dist/src/assets/img/icons/reviews.d.ts +1 -1
  60. package/dist/src/assets/img/icons/reviews.js +2 -2
  61. package/dist/src/assets/img/icons/upload.d.ts +1 -1
  62. package/dist/src/assets/img/icons/upload.js +2 -2
  63. package/dist/src/assets/img/icons/views.d.ts +1 -1
  64. package/dist/src/assets/img/icons/views.js +2 -2
  65. package/dist/src/circular-activity-indicator.d.ts +5 -5
  66. package/dist/src/circular-activity-indicator.js +17 -17
  67. package/dist/src/collection-browser.d.ts +234 -234
  68. package/dist/src/collection-browser.js +1368 -1367
  69. package/dist/src/collection-browser.js.map +1 -1
  70. package/dist/src/collection-facets/facets-template.d.ts +16 -16
  71. package/dist/src/collection-facets/facets-template.js +125 -125
  72. package/dist/src/collection-facets/facets-util.d.ts +10 -10
  73. package/dist/src/collection-facets/facets-util.js +19 -19
  74. package/dist/src/collection-facets/facets-util.js.map +1 -1
  75. package/dist/src/collection-facets/more-facets-content.d.ts +74 -74
  76. package/dist/src/collection-facets/more-facets-content.js +330 -330
  77. package/dist/src/collection-facets/more-facets-pagination.d.ts +36 -36
  78. package/dist/src/collection-facets/more-facets-pagination.js +192 -192
  79. package/dist/src/collection-facets.d.ts +71 -71
  80. package/dist/src/collection-facets.js +476 -476
  81. package/dist/src/collection-facets.js.map +1 -1
  82. package/dist/src/empty-placeholder.d.ts +11 -11
  83. package/dist/src/empty-placeholder.js +42 -42
  84. package/dist/src/language-code-handler/language-code-handler.d.ts +37 -37
  85. package/dist/src/language-code-handler/language-code-handler.js +26 -26
  86. package/dist/src/language-code-handler/language-code-mapping.d.ts +1 -1
  87. package/dist/src/language-code-handler/language-code-mapping.js +562 -562
  88. package/dist/src/mediatype/mediatype-config.d.ts +3 -3
  89. package/dist/src/mediatype/mediatype-config.js +85 -85
  90. package/dist/src/models.d.ts +89 -89
  91. package/dist/src/models.js +85 -85
  92. package/dist/src/models.js.map +1 -1
  93. package/dist/src/restoration-state-handler.d.ts +46 -45
  94. package/dist/src/restoration-state-handler.js +230 -219
  95. package/dist/src/restoration-state-handler.js.map +1 -1
  96. package/dist/src/sort-filter-bar/alpha-bar.d.ts +9 -9
  97. package/dist/src/sort-filter-bar/alpha-bar.js +41 -41
  98. package/dist/src/sort-filter-bar/img/compact.d.ts +1 -1
  99. package/dist/src/sort-filter-bar/img/compact.js +2 -2
  100. package/dist/src/sort-filter-bar/img/list.d.ts +1 -1
  101. package/dist/src/sort-filter-bar/img/list.js +2 -2
  102. package/dist/src/sort-filter-bar/img/sort-triangle.d.ts +1 -1
  103. package/dist/src/sort-filter-bar/img/sort-triangle.js +2 -2
  104. package/dist/src/sort-filter-bar/img/tile.d.ts +1 -1
  105. package/dist/src/sort-filter-bar/img/tile.js +2 -2
  106. package/dist/src/sort-filter-bar/sort-filter-bar.d.ts +107 -107
  107. package/dist/src/sort-filter-bar/sort-filter-bar.js +423 -423
  108. package/dist/src/styles/item-image-styles.d.ts +8 -8
  109. package/dist/src/styles/item-image-styles.js +9 -9
  110. package/dist/src/tiles/collection-browser-loading-tile.d.ts +5 -5
  111. package/dist/src/tiles/collection-browser-loading-tile.js +15 -15
  112. package/dist/src/tiles/grid/account-tile.d.ts +8 -8
  113. package/dist/src/tiles/grid/account-tile.js +20 -20
  114. package/dist/src/tiles/grid/collection-tile.d.ts +7 -7
  115. package/dist/src/tiles/grid/collection-tile.js +23 -23
  116. package/dist/src/tiles/grid/item-tile.d.ts +24 -24
  117. package/dist/src/tiles/grid/item-tile.js +87 -87
  118. package/dist/src/tiles/grid/tile-stats.d.ts +10 -10
  119. package/dist/src/tiles/grid/tile-stats.js +35 -35
  120. package/dist/src/tiles/image-block.d.ts +17 -17
  121. package/dist/src/tiles/image-block.js +73 -73
  122. package/dist/src/tiles/item-image.d.ts +31 -31
  123. package/dist/src/tiles/item-image.js +103 -103
  124. package/dist/src/tiles/list/account-label.d.ts +1 -1
  125. package/dist/src/tiles/list/account-label.js +6 -6
  126. package/dist/src/tiles/list/date-label.d.ts +1 -1
  127. package/dist/src/tiles/list/date-label.js +12 -12
  128. package/dist/src/tiles/list/tile-list-compact-header.d.ts +12 -12
  129. package/dist/src/tiles/list/tile-list-compact-header.js +41 -41
  130. package/dist/src/tiles/list/tile-list-compact.d.ts +20 -20
  131. package/dist/src/tiles/list/tile-list-compact.js +87 -87
  132. package/dist/src/tiles/list/tile-list.d.ts +50 -50
  133. package/dist/src/tiles/list/tile-list.js +263 -263
  134. package/dist/src/tiles/mediatype-icon.d.ts +9 -9
  135. package/dist/src/tiles/mediatype-icon.js +47 -47
  136. package/dist/src/tiles/overlay/icon-overlay.d.ts +7 -7
  137. package/dist/src/tiles/overlay/icon-overlay.js +31 -31
  138. package/dist/src/tiles/overlay/text-overlay.d.ts +8 -8
  139. package/dist/src/tiles/overlay/text-overlay.js +31 -31
  140. package/dist/src/tiles/text-snippet-block.d.ts +29 -29
  141. package/dist/src/tiles/text-snippet-block.js +81 -81
  142. package/dist/src/tiles/tile-dispatcher.d.ts +36 -36
  143. package/dist/src/tiles/tile-dispatcher.js +128 -128
  144. package/dist/src/utils/analytics-events.d.ts +18 -18
  145. package/dist/src/utils/analytics-events.js +20 -20
  146. package/dist/src/utils/format-count.d.ts +7 -7
  147. package/dist/src/utils/format-count.js +75 -75
  148. package/dist/src/utils/format-date.d.ts +2 -2
  149. package/dist/src/utils/format-date.js +23 -23
  150. package/dist/test/collection-browser.test.d.ts +1 -1
  151. package/dist/test/collection-browser.test.js +342 -342
  152. package/dist/test/collection-browser.test.js.map +1 -1
  153. package/dist/test/collection-facets/facets-template.test.d.ts +1 -1
  154. package/dist/test/collection-facets/facets-template.test.js +62 -62
  155. package/dist/test/collection-facets/facets-util.test.d.ts +1 -1
  156. package/dist/test/collection-facets/facets-util.test.js +12 -12
  157. package/dist/test/collection-facets/facets-util.test.js.map +1 -1
  158. package/dist/test/collection-facets/more-facets-content.test.d.ts +1 -1
  159. package/dist/test/collection-facets/more-facets-content.test.js +91 -91
  160. package/dist/test/collection-facets/more-facets-pagination.test.d.ts +1 -1
  161. package/dist/test/collection-facets/more-facets-pagination.test.js +117 -117
  162. package/dist/test/collection-facets.test.d.ts +2 -2
  163. package/dist/test/collection-facets.test.js +182 -182
  164. package/dist/test/empty-placeholder.test.d.ts +1 -1
  165. package/dist/test/empty-placeholder.test.js +33 -33
  166. package/dist/test/icon-overlay.test.d.ts +1 -1
  167. package/dist/test/icon-overlay.test.js +24 -24
  168. package/dist/test/item-image.test.d.ts +1 -1
  169. package/dist/test/item-image.test.js +56 -56
  170. package/dist/test/mediatype-config.test.d.ts +1 -1
  171. package/dist/test/mediatype-config.test.js +16 -16
  172. package/dist/test/mocks/mock-analytics-handler.d.ts +10 -10
  173. package/dist/test/mocks/mock-analytics-handler.js +15 -15
  174. package/dist/test/mocks/mock-collection-name-cache.d.ts +7 -7
  175. package/dist/test/mocks/mock-collection-name-cache.js +13 -13
  176. package/dist/test/mocks/mock-search-responses.d.ts +5 -5
  177. package/dist/test/mocks/mock-search-responses.js +103 -103
  178. package/dist/test/mocks/mock-search-service.d.ts +13 -13
  179. package/dist/test/mocks/mock-search-service.js +25 -25
  180. package/dist/test/restoration-state-handler.test.d.ts +1 -1
  181. package/dist/test/restoration-state-handler.test.js +117 -117
  182. package/dist/test/sort-filter-bar/sort-filter-bar.test.d.ts +1 -1
  183. package/dist/test/sort-filter-bar/sort-filter-bar.test.js +113 -113
  184. package/dist/test/text-overlay.test.d.ts +1 -1
  185. package/dist/test/text-overlay.test.js +41 -41
  186. package/dist/test/text-snippet-block.test.d.ts +1 -1
  187. package/dist/test/text-snippet-block.test.js +57 -57
  188. package/dist/test/tile-stats.test.d.ts +1 -1
  189. package/dist/test/tile-stats.test.js +33 -33
  190. package/dist/test/tiles/grid/item-tile.test.d.ts +1 -1
  191. package/dist/test/tiles/grid/item-tile.test.js +107 -107
  192. package/dist/test/tiles/list/tile-list.test.d.ts +1 -1
  193. package/dist/test/tiles/list/tile-list.test.js +36 -36
  194. package/dist/test/utils/format-count.test.d.ts +1 -1
  195. package/dist/test/utils/format-count.test.js +23 -23
  196. package/dist/test/utils/format-date.test.d.ts +1 -1
  197. package/dist/test/utils/format-date.test.js +17 -17
  198. package/index.html +24 -24
  199. package/local.archive.org.cert +86 -86
  200. package/local.archive.org.key +27 -27
  201. package/package.json +115 -115
  202. package/renovate.json +6 -6
  203. package/src/collection-browser.ts +1490 -1489
  204. package/src/collection-facets/facets-util.ts +22 -22
  205. package/src/collection-facets.ts +541 -541
  206. package/src/models.ts +188 -188
  207. package/src/restoration-state-handler.ts +313 -301
  208. package/test/collection-browser.test.ts +488 -488
  209. package/test/collection-facets/facets-util.test.ts +18 -18
  210. package/tsconfig.json +21 -21
  211. package/web-dev-server.config.mjs +30 -30
  212. package/web-test-runner.config.mjs +41 -41
@@ -1,541 +1,541 @@
1
- /* eslint-disable import/no-duplicates */
2
- import {
3
- css,
4
- html,
5
- LitElement,
6
- PropertyValues,
7
- nothing,
8
- TemplateResult,
9
- } from 'lit';
10
- import { customElement, property, state } from 'lit/decorators.js';
11
- import {
12
- Aggregation,
13
- Bucket,
14
- SearchServiceInterface,
15
- SearchType,
16
- } from '@internetarchive/search-service';
17
- import '@internetarchive/histogram-date-range';
18
- import '@internetarchive/feature-feedback';
19
- import '@internetarchive/collection-name-cache';
20
- import type { CollectionNameCacheInterface } from '@internetarchive/collection-name-cache';
21
- import {
22
- ModalConfig,
23
- ModalManagerInterface,
24
- } from '@internetarchive/modal-manager';
25
- import chevronIcon from './assets/img/icons/chevron';
26
- import {
27
- FacetOption,
28
- SelectedFacets,
29
- FacetGroup,
30
- FacetBucket,
31
- facetDisplayOrder,
32
- facetTitles,
33
- } from './models';
34
- import type { LanguageCodeHandlerInterface } from './language-code-handler/language-code-handler';
35
- import './collection-facets/more-facets-content';
36
- import './collection-facets/facets-template';
37
-
38
- @customElement('collection-facets')
39
- export class CollectionFacets extends LitElement {
40
- @property({ type: Object }) searchService?: SearchServiceInterface;
41
-
42
- @property({ type: String }) searchType?: SearchType;
43
-
44
- @property({ type: Object }) aggregations?: Record<string, Aggregation>;
45
-
46
- @property({ type: Object }) fullYearsHistogramAggregation?: Aggregation;
47
-
48
- @property({ type: String }) minSelectedDate?: string;
49
-
50
- @property({ type: String }) maxSelectedDate?: string;
51
-
52
- @property({ type: Boolean }) facetsLoading = false;
53
-
54
- @property({ type: Boolean }) fullYearAggregationLoading = false;
55
-
56
- @property({ type: Object }) selectedFacets?: SelectedFacets;
57
-
58
- @property({ type: Boolean }) collapsableFacets = false;
59
-
60
- @property({ type: Boolean }) showHistogramDatePicker = false;
61
-
62
- @property({ type: String }) fullQuery?: string;
63
-
64
- @property({ type: Object }) modalManager?: ModalManagerInterface;
65
-
66
- @property({ type: Object })
67
- languageCodeHandler?: LanguageCodeHandlerInterface;
68
-
69
- @property({ type: Object })
70
- collectionNameCache?: CollectionNameCacheInterface;
71
-
72
- /** Fires when a facet is clicked */
73
- @property({ type: Function }) onFacetClick?: (
74
- name: FacetOption,
75
- facetChecked: boolean,
76
- negative: boolean
77
- ) => void;
78
-
79
- @state() openFacets: Record<FacetOption, boolean> = {
80
- subject: false,
81
- mediatype: false,
82
- language: false,
83
- creator: false,
84
- collection: false,
85
- year: false,
86
- };
87
-
88
- @property({ type: Object, attribute: false })
89
-
90
- /**
91
- * render number of facet items
92
- */
93
- private allowedFacetCount = 6;
94
-
95
- render() {
96
- return html`
97
- <div id="container" class="${this.facetsLoading ? 'loading' : ''}">
98
- ${this.showHistogramDatePicker && this.fullYearsHistogramAggregation
99
- ? html`
100
- <div class="facet-group">
101
- <h1>Year Published <feature-feedback></feature-feedback></h1>
102
- ${this.histogramTemplate}
103
- </div>
104
- `
105
- : nothing}
106
- ${this.mergedFacets.map(facetGroup =>
107
- this.getFacetGroupTemplate(facetGroup)
108
- )}
109
- </div>
110
- `;
111
- }
112
-
113
- updated(changed: PropertyValues) {
114
- if (changed.has('selectedFacets')) {
115
- this.dispatchFacetsChangedEvent();
116
- }
117
- }
118
-
119
- // TODO: want to fire analytics?
120
- private dispatchFacetsChangedEvent() {
121
- const event = new CustomEvent<SelectedFacets>('facetsChanged', {
122
- detail: this.selectedFacets,
123
- });
124
- this.dispatchEvent(event);
125
- }
126
-
127
- private get currentYearsHistogramAggregation(): Aggregation | undefined {
128
- return this.aggregations?.year_histogram;
129
- }
130
-
131
- private get histogramTemplate() {
132
- const { fullYearsHistogramAggregation } = this;
133
- return html`
134
- <histogram-date-range
135
- .minDate=${fullYearsHistogramAggregation?.first_bucket_key}
136
- .maxDate=${fullYearsHistogramAggregation?.last_bucket_key}
137
- .minSelectedDate=${this.minSelectedDate}
138
- .maxSelectedDate=${this.maxSelectedDate}
139
- .updateDelay=${100}
140
- missingDataMessage="..."
141
- .width=${180}
142
- .bins=${fullYearsHistogramAggregation?.buckets as number[]}
143
- @histogramDateRangeUpdated=${this.histogramDateRangeUpdated}
144
- ></histogram-date-range>
145
- `;
146
- }
147
-
148
- private histogramDateRangeUpdated(
149
- e: CustomEvent<{
150
- minDate: string;
151
- maxDate: string;
152
- }>
153
- ) {
154
- const { minDate, maxDate } = e.detail;
155
- const event = new CustomEvent('histogramDateRangeUpdated', {
156
- detail: { minDate, maxDate },
157
- });
158
- this.dispatchEvent(event);
159
- }
160
-
161
- /**
162
- * Combines the selected facets with the aggregations to create a single list of facets
163
- */
164
- private get mergedFacets(): FacetGroup[] {
165
- const facetGroups: FacetGroup[] = [];
166
-
167
- facetDisplayOrder.forEach(facetKey => {
168
- const selectedFacetGroup = this.selectedFacetGroups.find(
169
- group => group.key === facetKey
170
- );
171
- const aggregateFacetGroup = this.aggregationFacetGroups.find(
172
- group => group.key === facetKey
173
- );
174
-
175
- // if the user selected a facet, but it's not in the aggregation, we add it as-is
176
- if (selectedFacetGroup && !aggregateFacetGroup) {
177
- facetGroups.push(selectedFacetGroup);
178
- return;
179
- }
180
-
181
- // if we don't have an aggregate facet group, don't add this to the list
182
- if (!aggregateFacetGroup) return;
183
-
184
- // start with either the selected group if we have one, or the aggregate group
185
- const facetGroup = selectedFacetGroup ?? aggregateFacetGroup;
186
-
187
- // attach the counts to the selected buckets
188
- const bucketsWithCount =
189
- selectedFacetGroup?.buckets.map(bucket => {
190
- const selectedBucket = aggregateFacetGroup.buckets.find(
191
- b => b.key === bucket.key
192
- );
193
- return selectedBucket
194
- ? {
195
- ...bucket,
196
- count: selectedBucket.count,
197
- }
198
- : bucket;
199
- }) ?? [];
200
-
201
- // append any additional buckets that were not selected
202
- aggregateFacetGroup.buckets.forEach(bucket => {
203
- const existingBucket = bucketsWithCount.find(b => b.key === bucket.key);
204
- if (existingBucket) return;
205
- bucketsWithCount.push(bucket);
206
- });
207
-
208
- /**
209
- * render limited facet items on page facet area
210
- *
211
- * - by-default we are showing 6 items
212
- * - additionally want to show all items (selected/suppressed) in page facet area
213
- */
214
- let allowedFacetCount = Object.keys(
215
- (selectedFacetGroup?.buckets as []) || []
216
- )?.length;
217
- if (allowedFacetCount < this.allowedFacetCount) {
218
- allowedFacetCount = this.allowedFacetCount; // splice start index from 0th
219
- }
220
-
221
- // splice how many items we want to show in page facet area
222
- facetGroup.buckets = bucketsWithCount.splice(0, allowedFacetCount);
223
-
224
- facetGroups.push(facetGroup);
225
- });
226
-
227
- return facetGroups;
228
- }
229
-
230
- /**
231
- * Converts the selected facets to a `FacetGroup` array,
232
- * which is easier to work with
233
- */
234
- private get selectedFacetGroups(): FacetGroup[] {
235
- if (!this.selectedFacets) return [];
236
-
237
- const facetGroups: FacetGroup[] = Object.entries(this.selectedFacets).map(
238
- ([key, selectedFacets]) => {
239
- const option = key as FacetOption;
240
- const title = facetTitles[option];
241
-
242
- const buckets: FacetBucket[] = Object.entries(selectedFacets).map(
243
- ([value, facetData]) => {
244
- let displayText = value;
245
- // for selected languages, we store the language code instead of the
246
- // display name, so look up the name from the mapping
247
- if (option === 'language') {
248
- displayText =
249
- this.languageCodeHandler?.getLanguageNameFromCodeString(
250
- value
251
- ) ?? value;
252
- }
253
- return {
254
- displayText,
255
- key: value,
256
- count: facetData.count,
257
- state: facetData.state,
258
- };
259
- }
260
- );
261
-
262
- return {
263
- title,
264
- key: option,
265
- buckets,
266
- };
267
- }
268
- );
269
-
270
- return facetGroups;
271
- }
272
-
273
- /**
274
- * Converts the raw `aggregations` to `FacetGroups`, which are easier to use
275
- */
276
- private get aggregationFacetGroups(): FacetGroup[] {
277
- const facetGroups: FacetGroup[] = [];
278
- Object.entries(this.aggregations ?? []).forEach(([key, buckets]) => {
279
- // the year_histogram data is in a different format so can't be handled here
280
- if (key === 'year_histogram') return;
281
-
282
- const option = key as FacetOption;
283
- const title = facetTitles[option];
284
- if (!title) return;
285
-
286
- const castedBuckets = buckets.buckets as Bucket[];
287
-
288
- // we are not showing fav- items in facets
289
- castedBuckets?.filter(
290
- bucket => bucket?.key?.toString()?.startsWith('fav-') === false
291
- );
292
-
293
- const facetBuckets: FacetBucket[] = castedBuckets.map(bucket => {
294
- let bucketKey = bucket.key;
295
- // for languages, we need to search by language code instead of the
296
- // display name, which is what we get from the search engine result
297
- if (option === 'language') {
298
- // const languageCodeKey = languageToCodeMap[bucket.key];
299
- bucketKey =
300
- this.languageCodeHandler?.getCodeStringFromLanguageName(
301
- `${bucket.key}`
302
- ) ?? bucket.key;
303
- // bucketKey = languageCodeKey ?? bucket.key;
304
- }
305
- return {
306
- displayText: `${bucket.key}`,
307
- key: `${bucketKey}`,
308
- count: bucket.doc_count,
309
- state: 'none',
310
- };
311
- });
312
- const group: FacetGroup = {
313
- title,
314
- key: option,
315
- buckets: facetBuckets,
316
- };
317
- facetGroups.push(group);
318
- });
319
- return facetGroups;
320
- }
321
-
322
- /**
323
- * Generate the template for a facet group with a header and the collapsible
324
- * chevron for the mobile view
325
- */
326
- private getFacetGroupTemplate(
327
- facetGroup: FacetGroup
328
- ): TemplateResult | typeof nothing {
329
- if (facetGroup.buckets.length === 0) return nothing;
330
- const { key } = facetGroup;
331
- const isOpen = this.openFacets[key];
332
- const collapser = html`
333
- <span class="collapser ${isOpen ? 'open' : ''}"> ${chevronIcon} </span>
334
- `;
335
-
336
- return html`
337
- <div class="facet-group ${this.collapsableFacets ? 'mobile' : ''}">
338
- <div class="facet-group-header">
339
- <h1
340
- @click=${() => {
341
- const newOpenFacets = { ...this.openFacets };
342
- newOpenFacets[key] = !isOpen;
343
- this.openFacets = newOpenFacets;
344
- }}
345
- @keyup=${() => {
346
- const newOpenFacets = { ...this.openFacets };
347
- newOpenFacets[key] = !isOpen;
348
- this.openFacets = newOpenFacets;
349
- }}
350
- >
351
- ${this.collapsableFacets ? collapser : nothing} ${facetGroup.title}
352
- </h1>
353
- <input
354
- class="sorting-icon"
355
- type="image"
356
- @click=${() => this.showMoreFacetsModal(facetGroup, 'alpha')}
357
- src="https://archive.org/images/filter-count.png"
358
- alt="Sort by alphabetically"
359
- />
360
- </div>
361
- <div class="facet-group-content ${isOpen ? 'open' : ''}">
362
- ${this.getFacetTemplate(facetGroup)}
363
- ${this.searchMoreFacetsLink(facetGroup)}
364
- </div>
365
- </div>
366
- `;
367
- }
368
-
369
- /**
370
- * Generate the More... link button just below the facets group
371
- *
372
- * TODO: want to fire analytics?
373
- */
374
- private searchMoreFacetsLink(
375
- facetGroup: FacetGroup
376
- ): TemplateResult | typeof nothing {
377
- // Don't render More... links for FTS searches
378
- if (this.searchType === SearchType.FULLTEXT) {
379
- return nothing;
380
- }
381
-
382
- // Don't render More... link if the number of facets < this.allowedFacetCount
383
- if (Object.keys(facetGroup.buckets).length < this.allowedFacetCount) {
384
- return nothing;
385
- }
386
-
387
- return html`<button
388
- class="more-link"
389
- @click=${() => {
390
- this.showMoreFacetsModal(facetGroup, 'count');
391
- this.dispatchEvent(
392
- new CustomEvent('showMoreFacets', { detail: facetGroup.key })
393
- );
394
- }}
395
- >
396
- More...
397
- </button>`;
398
- }
399
-
400
- async showMoreFacetsModal(
401
- facetGroup: FacetGroup,
402
- sortedBy: string
403
- ): Promise<void> {
404
- const facetAggrKey = facetGroup.key;
405
-
406
- const customModalContent = html`
407
- <more-facets-content
408
- .facetKey=${facetGroup.key}
409
- .facetAggregationKey=${facetAggrKey}
410
- .fullQuery=${this.fullQuery}
411
- .modalManager=${this.modalManager}
412
- .searchService=${this.searchService}
413
- .searchType=${this.searchType}
414
- .collectionNameCache=${this.collectionNameCache}
415
- .languageCodeHandler=${this.languageCodeHandler}
416
- .selectedFacets=${this.selectedFacets}
417
- .sortedBy=${sortedBy}
418
- @facetsChanged=${(e: CustomEvent) => {
419
- const event = new CustomEvent<SelectedFacets>('facetsChanged', {
420
- detail: e.detail,
421
- bubbles: true,
422
- composed: true,
423
- });
424
- this.dispatchEvent(event);
425
- }}
426
- >
427
- </more-facets-content>
428
- `;
429
-
430
- const config = new ModalConfig({
431
- bodyColor: '#fff',
432
- headerColor: '#194880',
433
- showHeaderLogo: false,
434
- closeOnBackdropClick: true, // TODO: want to fire analytics
435
- title: html`Select filters`,
436
- });
437
- this.modalManager?.classList.add('more-search-facets');
438
- this.modalManager?.showModal({
439
- config,
440
- customModalContent,
441
- });
442
- }
443
-
444
- /**
445
- * Generate the list template for each bucket in a facet group
446
- */
447
- private getFacetTemplate(facetGroup: FacetGroup): TemplateResult {
448
- return html`
449
- <facets-template
450
- .facetGroup=${facetGroup}
451
- .selectedFacets=${this.selectedFacets}
452
- .renderOn=${'page'}
453
- .collectionNameCache=${this.collectionNameCache}
454
- @selectedFacetsChanged=${(e: CustomEvent) => {
455
- const event = new CustomEvent<SelectedFacets>('facetsChanged', {
456
- detail: e.detail,
457
- bubbles: true,
458
- composed: true,
459
- });
460
- this.dispatchEvent(event);
461
- }}
462
- ></facets-template>
463
- `;
464
- }
465
-
466
- static get styles() {
467
- return css`
468
- #container.loading {
469
- opacity: 0.5;
470
- }
471
-
472
- .collapser {
473
- display: inline-block;
474
- cursor: pointer;
475
- width: 10px;
476
- height: 10px;
477
- }
478
-
479
- .collapser svg {
480
- transition: transform 0.2s ease-in-out;
481
- }
482
-
483
- .collapser.open svg {
484
- transform: rotate(90deg);
485
- }
486
-
487
- .facet-group {
488
- margin-bottom: 2rem;
489
- }
490
-
491
- .facet-group h1 {
492
- margin-bottom: 0.7rem;
493
- }
494
-
495
- .facet-group.mobile h1 {
496
- cursor: pointer;
497
- }
498
-
499
- .facet-group-header {
500
- display: flex;
501
- margin-bottom: 0.7rem;
502
- justify-content: space-between;
503
- border-bottom: 1px solid rgb(232, 232, 232);
504
- }
505
-
506
- .facet-group-content {
507
- transition: max-height 0.2s ease-in-out;
508
- }
509
-
510
- .facet-group.mobile .facet-group-content {
511
- max-height: 0;
512
- overflow: hidden;
513
- }
514
-
515
- .facet-group.mobile .facet-group-content.open {
516
- max-height: 2000px;
517
- }
518
-
519
- h1 {
520
- font-size: 1.4rem;
521
- font-weight: 200
522
- padding-bottom: 3px;
523
- margin: 0;
524
- }
525
-
526
- .more-link {
527
- font-size: 1.2rem;
528
- text-decoration: none;
529
- padding: 0;
530
- background: inherit;
531
- border: 0;
532
- color: blue;
533
- cursor: pointer;
534
- }
535
- .sorting-icon {
536
- height: 15px;
537
- cursor: pointer;
538
- }
539
- `;
540
- }
541
- }
1
+ /* eslint-disable import/no-duplicates */
2
+ import {
3
+ css,
4
+ html,
5
+ LitElement,
6
+ PropertyValues,
7
+ nothing,
8
+ TemplateResult,
9
+ } from 'lit';
10
+ import { customElement, property, state } from 'lit/decorators.js';
11
+ import {
12
+ Aggregation,
13
+ Bucket,
14
+ SearchServiceInterface,
15
+ SearchType,
16
+ } from '@internetarchive/search-service';
17
+ import '@internetarchive/histogram-date-range';
18
+ import '@internetarchive/feature-feedback';
19
+ import '@internetarchive/collection-name-cache';
20
+ import type { CollectionNameCacheInterface } from '@internetarchive/collection-name-cache';
21
+ import {
22
+ ModalConfig,
23
+ ModalManagerInterface,
24
+ } from '@internetarchive/modal-manager';
25
+ import chevronIcon from './assets/img/icons/chevron';
26
+ import {
27
+ FacetOption,
28
+ SelectedFacets,
29
+ FacetGroup,
30
+ FacetBucket,
31
+ facetDisplayOrder,
32
+ facetTitles,
33
+ } from './models';
34
+ import type { LanguageCodeHandlerInterface } from './language-code-handler/language-code-handler';
35
+ import './collection-facets/more-facets-content';
36
+ import './collection-facets/facets-template';
37
+
38
+ @customElement('collection-facets')
39
+ export class CollectionFacets extends LitElement {
40
+ @property({ type: Object }) searchService?: SearchServiceInterface;
41
+
42
+ @property({ type: String }) searchType?: SearchType;
43
+
44
+ @property({ type: Object }) aggregations?: Record<string, Aggregation>;
45
+
46
+ @property({ type: Object }) fullYearsHistogramAggregation?: Aggregation;
47
+
48
+ @property({ type: String }) minSelectedDate?: string;
49
+
50
+ @property({ type: String }) maxSelectedDate?: string;
51
+
52
+ @property({ type: Boolean }) facetsLoading = false;
53
+
54
+ @property({ type: Boolean }) fullYearAggregationLoading = false;
55
+
56
+ @property({ type: Object }) selectedFacets?: SelectedFacets;
57
+
58
+ @property({ type: Boolean }) collapsableFacets = false;
59
+
60
+ @property({ type: Boolean }) showHistogramDatePicker = false;
61
+
62
+ @property({ type: String }) fullQuery?: string;
63
+
64
+ @property({ type: Object }) modalManager?: ModalManagerInterface;
65
+
66
+ @property({ type: Object })
67
+ languageCodeHandler?: LanguageCodeHandlerInterface;
68
+
69
+ @property({ type: Object })
70
+ collectionNameCache?: CollectionNameCacheInterface;
71
+
72
+ /** Fires when a facet is clicked */
73
+ @property({ type: Function }) onFacetClick?: (
74
+ name: FacetOption,
75
+ facetChecked: boolean,
76
+ negative: boolean
77
+ ) => void;
78
+
79
+ @state() openFacets: Record<FacetOption, boolean> = {
80
+ subject: false,
81
+ mediatype: false,
82
+ language: false,
83
+ creator: false,
84
+ collection: false,
85
+ year: false,
86
+ };
87
+
88
+ @property({ type: Object, attribute: false })
89
+
90
+ /**
91
+ * render number of facet items
92
+ */
93
+ private allowedFacetCount = 6;
94
+
95
+ render() {
96
+ return html`
97
+ <div id="container" class="${this.facetsLoading ? 'loading' : ''}">
98
+ ${this.showHistogramDatePicker && this.fullYearsHistogramAggregation
99
+ ? html`
100
+ <div class="facet-group">
101
+ <h1>Year Published <feature-feedback></feature-feedback></h1>
102
+ ${this.histogramTemplate}
103
+ </div>
104
+ `
105
+ : nothing}
106
+ ${this.mergedFacets.map(facetGroup =>
107
+ this.getFacetGroupTemplate(facetGroup)
108
+ )}
109
+ </div>
110
+ `;
111
+ }
112
+
113
+ updated(changed: PropertyValues) {
114
+ if (changed.has('selectedFacets')) {
115
+ this.dispatchFacetsChangedEvent();
116
+ }
117
+ }
118
+
119
+ // TODO: want to fire analytics?
120
+ private dispatchFacetsChangedEvent() {
121
+ const event = new CustomEvent<SelectedFacets>('facetsChanged', {
122
+ detail: this.selectedFacets,
123
+ });
124
+ this.dispatchEvent(event);
125
+ }
126
+
127
+ private get currentYearsHistogramAggregation(): Aggregation | undefined {
128
+ return this.aggregations?.year_histogram;
129
+ }
130
+
131
+ private get histogramTemplate() {
132
+ const { fullYearsHistogramAggregation } = this;
133
+ return html`
134
+ <histogram-date-range
135
+ .minDate=${fullYearsHistogramAggregation?.first_bucket_key}
136
+ .maxDate=${fullYearsHistogramAggregation?.last_bucket_key}
137
+ .minSelectedDate=${this.minSelectedDate}
138
+ .maxSelectedDate=${this.maxSelectedDate}
139
+ .updateDelay=${100}
140
+ missingDataMessage="..."
141
+ .width=${180}
142
+ .bins=${fullYearsHistogramAggregation?.buckets as number[]}
143
+ @histogramDateRangeUpdated=${this.histogramDateRangeUpdated}
144
+ ></histogram-date-range>
145
+ `;
146
+ }
147
+
148
+ private histogramDateRangeUpdated(
149
+ e: CustomEvent<{
150
+ minDate: string;
151
+ maxDate: string;
152
+ }>
153
+ ) {
154
+ const { minDate, maxDate } = e.detail;
155
+ const event = new CustomEvent('histogramDateRangeUpdated', {
156
+ detail: { minDate, maxDate },
157
+ });
158
+ this.dispatchEvent(event);
159
+ }
160
+
161
+ /**
162
+ * Combines the selected facets with the aggregations to create a single list of facets
163
+ */
164
+ private get mergedFacets(): FacetGroup[] {
165
+ const facetGroups: FacetGroup[] = [];
166
+
167
+ facetDisplayOrder.forEach(facetKey => {
168
+ const selectedFacetGroup = this.selectedFacetGroups.find(
169
+ group => group.key === facetKey
170
+ );
171
+ const aggregateFacetGroup = this.aggregationFacetGroups.find(
172
+ group => group.key === facetKey
173
+ );
174
+
175
+ // if the user selected a facet, but it's not in the aggregation, we add it as-is
176
+ if (selectedFacetGroup && !aggregateFacetGroup) {
177
+ facetGroups.push(selectedFacetGroup);
178
+ return;
179
+ }
180
+
181
+ // if we don't have an aggregate facet group, don't add this to the list
182
+ if (!aggregateFacetGroup) return;
183
+
184
+ // start with either the selected group if we have one, or the aggregate group
185
+ const facetGroup = selectedFacetGroup ?? aggregateFacetGroup;
186
+
187
+ // attach the counts to the selected buckets
188
+ const bucketsWithCount =
189
+ selectedFacetGroup?.buckets.map(bucket => {
190
+ const selectedBucket = aggregateFacetGroup.buckets.find(
191
+ b => b.key === bucket.key
192
+ );
193
+ return selectedBucket
194
+ ? {
195
+ ...bucket,
196
+ count: selectedBucket.count,
197
+ }
198
+ : bucket;
199
+ }) ?? [];
200
+
201
+ // append any additional buckets that were not selected
202
+ aggregateFacetGroup.buckets.forEach(bucket => {
203
+ const existingBucket = bucketsWithCount.find(b => b.key === bucket.key);
204
+ if (existingBucket) return;
205
+ bucketsWithCount.push(bucket);
206
+ });
207
+
208
+ /**
209
+ * render limited facet items on page facet area
210
+ *
211
+ * - by-default we are showing 6 items
212
+ * - additionally want to show all items (selected/suppressed) in page facet area
213
+ */
214
+ let allowedFacetCount = Object.keys(
215
+ (selectedFacetGroup?.buckets as []) || []
216
+ )?.length;
217
+ if (allowedFacetCount < this.allowedFacetCount) {
218
+ allowedFacetCount = this.allowedFacetCount; // splice start index from 0th
219
+ }
220
+
221
+ // splice how many items we want to show in page facet area
222
+ facetGroup.buckets = bucketsWithCount.splice(0, allowedFacetCount);
223
+
224
+ facetGroups.push(facetGroup);
225
+ });
226
+
227
+ return facetGroups;
228
+ }
229
+
230
+ /**
231
+ * Converts the selected facets to a `FacetGroup` array,
232
+ * which is easier to work with
233
+ */
234
+ private get selectedFacetGroups(): FacetGroup[] {
235
+ if (!this.selectedFacets) return [];
236
+
237
+ const facetGroups: FacetGroup[] = Object.entries(this.selectedFacets).map(
238
+ ([key, selectedFacets]) => {
239
+ const option = key as FacetOption;
240
+ const title = facetTitles[option];
241
+
242
+ const buckets: FacetBucket[] = Object.entries(selectedFacets).map(
243
+ ([value, facetData]) => {
244
+ let displayText = value;
245
+ // for selected languages, we store the language code instead of the
246
+ // display name, so look up the name from the mapping
247
+ if (option === 'language') {
248
+ displayText =
249
+ this.languageCodeHandler?.getLanguageNameFromCodeString(
250
+ value
251
+ ) ?? value;
252
+ }
253
+ return {
254
+ displayText,
255
+ key: value,
256
+ count: facetData.count,
257
+ state: facetData.state,
258
+ };
259
+ }
260
+ );
261
+
262
+ return {
263
+ title,
264
+ key: option,
265
+ buckets,
266
+ };
267
+ }
268
+ );
269
+
270
+ return facetGroups;
271
+ }
272
+
273
+ /**
274
+ * Converts the raw `aggregations` to `FacetGroups`, which are easier to use
275
+ */
276
+ private get aggregationFacetGroups(): FacetGroup[] {
277
+ const facetGroups: FacetGroup[] = [];
278
+ Object.entries(this.aggregations ?? []).forEach(([key, buckets]) => {
279
+ // the year_histogram data is in a different format so can't be handled here
280
+ if (key === 'year_histogram') return;
281
+
282
+ const option = key as FacetOption;
283
+ const title = facetTitles[option];
284
+ if (!title) return;
285
+
286
+ const castedBuckets = buckets.buckets as Bucket[];
287
+
288
+ // we are not showing fav- items in facets
289
+ castedBuckets?.filter(
290
+ bucket => bucket?.key?.toString()?.startsWith('fav-') === false
291
+ );
292
+
293
+ const facetBuckets: FacetBucket[] = castedBuckets.map(bucket => {
294
+ let bucketKey = bucket.key;
295
+ // for languages, we need to search by language code instead of the
296
+ // display name, which is what we get from the search engine result
297
+ if (option === 'language') {
298
+ // const languageCodeKey = languageToCodeMap[bucket.key];
299
+ bucketKey =
300
+ this.languageCodeHandler?.getCodeStringFromLanguageName(
301
+ `${bucket.key}`
302
+ ) ?? bucket.key;
303
+ // bucketKey = languageCodeKey ?? bucket.key;
304
+ }
305
+ return {
306
+ displayText: `${bucket.key}`,
307
+ key: `${bucketKey}`,
308
+ count: bucket.doc_count,
309
+ state: 'none',
310
+ };
311
+ });
312
+ const group: FacetGroup = {
313
+ title,
314
+ key: option,
315
+ buckets: facetBuckets,
316
+ };
317
+ facetGroups.push(group);
318
+ });
319
+ return facetGroups;
320
+ }
321
+
322
+ /**
323
+ * Generate the template for a facet group with a header and the collapsible
324
+ * chevron for the mobile view
325
+ */
326
+ private getFacetGroupTemplate(
327
+ facetGroup: FacetGroup
328
+ ): TemplateResult | typeof nothing {
329
+ if (facetGroup.buckets.length === 0) return nothing;
330
+ const { key } = facetGroup;
331
+ const isOpen = this.openFacets[key];
332
+ const collapser = html`
333
+ <span class="collapser ${isOpen ? 'open' : ''}"> ${chevronIcon} </span>
334
+ `;
335
+
336
+ return html`
337
+ <div class="facet-group ${this.collapsableFacets ? 'mobile' : ''}">
338
+ <div class="facet-group-header">
339
+ <h1
340
+ @click=${() => {
341
+ const newOpenFacets = { ...this.openFacets };
342
+ newOpenFacets[key] = !isOpen;
343
+ this.openFacets = newOpenFacets;
344
+ }}
345
+ @keyup=${() => {
346
+ const newOpenFacets = { ...this.openFacets };
347
+ newOpenFacets[key] = !isOpen;
348
+ this.openFacets = newOpenFacets;
349
+ }}
350
+ >
351
+ ${this.collapsableFacets ? collapser : nothing} ${facetGroup.title}
352
+ </h1>
353
+ <input
354
+ class="sorting-icon"
355
+ type="image"
356
+ @click=${() => this.showMoreFacetsModal(facetGroup, 'alpha')}
357
+ src="https://archive.org/images/filter-count.png"
358
+ alt="Sort by alphabetically"
359
+ />
360
+ </div>
361
+ <div class="facet-group-content ${isOpen ? 'open' : ''}">
362
+ ${this.getFacetTemplate(facetGroup)}
363
+ ${this.searchMoreFacetsLink(facetGroup)}
364
+ </div>
365
+ </div>
366
+ `;
367
+ }
368
+
369
+ /**
370
+ * Generate the More... link button just below the facets group
371
+ *
372
+ * TODO: want to fire analytics?
373
+ */
374
+ private searchMoreFacetsLink(
375
+ facetGroup: FacetGroup
376
+ ): TemplateResult | typeof nothing {
377
+ // Don't render More... links for FTS searches
378
+ if (this.searchType === SearchType.FULLTEXT) {
379
+ return nothing;
380
+ }
381
+
382
+ // Don't render More... link if the number of facets < this.allowedFacetCount
383
+ if (Object.keys(facetGroup.buckets).length < this.allowedFacetCount) {
384
+ return nothing;
385
+ }
386
+
387
+ return html`<button
388
+ class="more-link"
389
+ @click=${() => {
390
+ this.showMoreFacetsModal(facetGroup, 'count');
391
+ this.dispatchEvent(
392
+ new CustomEvent('showMoreFacets', { detail: facetGroup.key })
393
+ );
394
+ }}
395
+ >
396
+ More...
397
+ </button>`;
398
+ }
399
+
400
+ async showMoreFacetsModal(
401
+ facetGroup: FacetGroup,
402
+ sortedBy: string
403
+ ): Promise<void> {
404
+ const facetAggrKey = facetGroup.key;
405
+
406
+ const customModalContent = html`
407
+ <more-facets-content
408
+ .facetKey=${facetGroup.key}
409
+ .facetAggregationKey=${facetAggrKey}
410
+ .fullQuery=${this.fullQuery}
411
+ .modalManager=${this.modalManager}
412
+ .searchService=${this.searchService}
413
+ .searchType=${this.searchType}
414
+ .collectionNameCache=${this.collectionNameCache}
415
+ .languageCodeHandler=${this.languageCodeHandler}
416
+ .selectedFacets=${this.selectedFacets}
417
+ .sortedBy=${sortedBy}
418
+ @facetsChanged=${(e: CustomEvent) => {
419
+ const event = new CustomEvent<SelectedFacets>('facetsChanged', {
420
+ detail: e.detail,
421
+ bubbles: true,
422
+ composed: true,
423
+ });
424
+ this.dispatchEvent(event);
425
+ }}
426
+ >
427
+ </more-facets-content>
428
+ `;
429
+
430
+ const config = new ModalConfig({
431
+ bodyColor: '#fff',
432
+ headerColor: '#194880',
433
+ showHeaderLogo: false,
434
+ closeOnBackdropClick: true, // TODO: want to fire analytics
435
+ title: html`Select filters`,
436
+ });
437
+ this.modalManager?.classList.add('more-search-facets');
438
+ this.modalManager?.showModal({
439
+ config,
440
+ customModalContent,
441
+ });
442
+ }
443
+
444
+ /**
445
+ * Generate the list template for each bucket in a facet group
446
+ */
447
+ private getFacetTemplate(facetGroup: FacetGroup): TemplateResult {
448
+ return html`
449
+ <facets-template
450
+ .facetGroup=${facetGroup}
451
+ .selectedFacets=${this.selectedFacets}
452
+ .renderOn=${'page'}
453
+ .collectionNameCache=${this.collectionNameCache}
454
+ @selectedFacetsChanged=${(e: CustomEvent) => {
455
+ const event = new CustomEvent<SelectedFacets>('facetsChanged', {
456
+ detail: e.detail,
457
+ bubbles: true,
458
+ composed: true,
459
+ });
460
+ this.dispatchEvent(event);
461
+ }}
462
+ ></facets-template>
463
+ `;
464
+ }
465
+
466
+ static get styles() {
467
+ return css`
468
+ #container.loading {
469
+ opacity: 0.5;
470
+ }
471
+
472
+ .collapser {
473
+ display: inline-block;
474
+ cursor: pointer;
475
+ width: 10px;
476
+ height: 10px;
477
+ }
478
+
479
+ .collapser svg {
480
+ transition: transform 0.2s ease-in-out;
481
+ }
482
+
483
+ .collapser.open svg {
484
+ transform: rotate(90deg);
485
+ }
486
+
487
+ .facet-group {
488
+ margin-bottom: 2rem;
489
+ }
490
+
491
+ .facet-group h1 {
492
+ margin-bottom: 0.7rem;
493
+ }
494
+
495
+ .facet-group.mobile h1 {
496
+ cursor: pointer;
497
+ }
498
+
499
+ .facet-group-header {
500
+ display: flex;
501
+ margin-bottom: 0.7rem;
502
+ justify-content: space-between;
503
+ border-bottom: 1px solid rgb(232, 232, 232);
504
+ }
505
+
506
+ .facet-group-content {
507
+ transition: max-height 0.2s ease-in-out;
508
+ }
509
+
510
+ .facet-group.mobile .facet-group-content {
511
+ max-height: 0;
512
+ overflow: hidden;
513
+ }
514
+
515
+ .facet-group.mobile .facet-group-content.open {
516
+ max-height: 2000px;
517
+ }
518
+
519
+ h1 {
520
+ font-size: 1.4rem;
521
+ font-weight: 200
522
+ padding-bottom: 3px;
523
+ margin: 0;
524
+ }
525
+
526
+ .more-link {
527
+ font-size: 1.2rem;
528
+ text-decoration: none;
529
+ padding: 0;
530
+ background: inherit;
531
+ border: 0;
532
+ color: blue;
533
+ cursor: pointer;
534
+ }
535
+ .sorting-icon {
536
+ height: 15px;
537
+ cursor: pointer;
538
+ }
539
+ `;
540
+ }
541
+ }