@fluid-topics/ft-search-bar 0.2.16 → 0.2.19

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.
@@ -34,8 +34,8 @@ export const DEFAULT_LABELS = {
34
34
  clearInputButton: "Clear",
35
35
  clearFilterButton: "Clear",
36
36
  displayMoreFilterValuesButton: "More",
37
+ noFilterValuesAvailable: "No values available",
37
38
  searchButton: "Search",
38
- noSuggestions: "No results found…",
39
39
  clearFilters: "Clear filters",
40
40
  contentLocaleSelector: "Lang",
41
41
  presetsSelector: "Preset",
@@ -57,12 +57,15 @@ export class SearchStateChangeEvent extends CustomEvent {
57
57
  super("change", { detail: request });
58
58
  }
59
59
  }
60
+ const doNothing = () => {
61
+ };
60
62
  export class FtSearchBar extends FtLitElement {
61
63
  constructor() {
62
64
  super(...arguments);
63
65
  this.dense = false;
64
66
  this.mode = "auto";
65
67
  this.forceMobileMenuOpen = false;
68
+ this.forceMenuOpen = false;
66
69
  this.baseUrl = "";
67
70
  this.apiIntegrationIdentifier = "ft-search-bar";
68
71
  this.availableContentLocales = [];
@@ -79,7 +82,7 @@ export class FtSearchBar extends FtLitElement {
79
82
  this.mobileMenuOpen = false;
80
83
  this.facets = [];
81
84
  this.facetsInitialized = false;
82
- this.knownFacetLabels = {};
85
+ this.knownFacetLabels = new Map();
83
86
  this.query = "";
84
87
  this.suggestions = [];
85
88
  this.suggestionsLoaded = true;
@@ -101,10 +104,7 @@ export class FtSearchBar extends FtLitElement {
101
104
  && a.filters.every(fa => b.filters.some(fb => this.compareFilters(fa, fb)));
102
105
  }
103
106
  get isMobileMenuOpen() {
104
- return this.forceMobileMenuOpen || this.mobileMenuOpen;
105
- }
106
- get recentSearchesStorageKey() {
107
- return this.baseUrl + ":ft:recent-search-queries";
107
+ return this.isMobile && (this.forceMobileMenuOpen || this.forceMenuOpen || this.mobileMenuOpen);
108
108
  }
109
109
  get request() {
110
110
  return {
@@ -119,7 +119,8 @@ export class FtSearchBar extends FtLitElement {
119
119
  };
120
120
  }
121
121
  get facetsRequest() {
122
- const fromFilters = this.searchFilters.filter(f => !this.displayedFilters.includes(f.key)).map(f => ({ id: f.key }));
122
+ const fromFilters = this.searchFilters.filter(f => f.values.length > 0 && !this.displayedFilters.includes(f.key))
123
+ .map(f => ({ id: f.key }));
123
124
  return [...this.displayedFilters.map(id => ({ id })), ...fromFilters];
124
125
  }
125
126
  get suggestRequest() {
@@ -156,6 +157,14 @@ export class FtSearchBar extends FtLitElement {
156
157
  var _a;
157
158
  (_a = this.container) === null || _a === void 0 ? void 0 : _a.focus();
158
159
  }
160
+ focusInput() {
161
+ if (this.input) {
162
+ this.input.focus();
163
+ }
164
+ else {
165
+ setTimeout(() => this.focusInput(), 50);
166
+ }
167
+ }
159
168
  clear() {
160
169
  this.query = "";
161
170
  this.searchFilters = [];
@@ -177,15 +186,16 @@ export class FtSearchBar extends FtLitElement {
177
186
  "ft-search-bar--dense": !this.isMobile && this.dense,
178
187
  "ft-search-bar--mobile": this.isMobile,
179
188
  "ft-search-bar--desktop": !this.isMobile,
180
- "ft-search-bar--floating-panel-open": !this.isMobile && this.displayFacets,
189
+ "ft-search-bar--floating-panel-open": !this.isMobile && this.displayFacets && !this.forceMenuOpen,
181
190
  "ft-search-bar--mobile-menu-open": this.isMobileMenuOpen,
191
+ "ft-search-bar--forced-open": this.forceMenuOpen || this.forceMobileMenuOpen,
182
192
  };
183
193
  return this.facetsInitialized && this.availableContentLocalesInitialized ? html `
184
194
  <div class="${classMap(rootClasses)}" part="container" tabindex="-1">
185
195
  ${this.isMobile ? this.renderMobileSearchBar() : this.renderDesktopSearchBar()}
186
196
  </div>
187
197
  ` : html `
188
- <ft-skeleton class="ft-search-bar--skeleton" part="loader"></ft-skeleton>
198
+ <ft-skeleton class="ft-search-bar--container ft-search-bar--skeleton" part="loader" tabindex="-1"></ft-skeleton>
189
199
  `;
190
200
  }
191
201
  renderMobileSearchBar() {
@@ -268,7 +278,7 @@ export class FtSearchBar extends FtLitElement {
268
278
  </div>
269
279
  <ft-filter
270
280
  part="filters filter-ft-locale"
271
- exportparts=${this.getFilterExportParts("ft-locale")}
281
+ .exportpartsPrefixes=${["filter", "filter-ft-locale"]}
272
282
  class="ft-search-bar--content-locale"
273
283
  filterPlaceHolder="${this.labelResolver.resolve("filterInputPlaceHolder", this.labelResolver.resolve("contentLocaleSelector"))}"
274
284
  .options=${(this.contentLocalesAsFilterOptions())}
@@ -279,7 +289,7 @@ export class FtSearchBar extends FtLitElement {
279
289
  ${repeat(this.facets, facet => facet.key, facet => {
280
290
  const filter = facetToFilter(facet);
281
291
  const keyWithNoColumn = facet.key.replace(":", "-");
282
- return html `
292
+ return facet.rootNodes.length > 0 ? html `
283
293
  <ft-accordion-item data-facet-key="${facet.key}">
284
294
  <div class="ft-search-bar--filter-label" slot="toggle">
285
295
  <ft-typography variant="button">${facet.label}</ft-typography>
@@ -288,12 +298,13 @@ export class FtSearchBar extends FtLitElement {
288
298
  </ft-typography>
289
299
  </div>
290
300
  <ft-filter
291
- part="filters filter-${keyWithNoColumn}"
292
- exportparts=${this.getFilterExportParts(keyWithNoColumn)}
301
+ part="filters filter filter-${keyWithNoColumn}"
302
+ .exportpartsPrefixes=${["filter", "filter-" + keyWithNoColumn]}
293
303
  id="${filter.id}"
294
304
  filterPlaceHolder="${this.labelResolver.resolve("filterInputPlaceHolder", filter.label)}"
295
305
  clearButtonLabel="${this.labelResolver.resolve("clearFilterButton")}"
296
306
  moreValuesButtonLabel="${this.labelResolver.resolve("displayMoreFilterValuesButton")}"
307
+ noValuesLabel="${this.labelResolver.resolve("noFilterValuesAvailable")}"
297
308
  ?multivalued=${filter.multivalued}
298
309
  raiseSelectedOptions
299
310
  .options=${filter.options}
@@ -301,7 +312,7 @@ export class FtSearchBar extends FtLitElement {
301
312
  @change=${(e) => this.setFilter(filter.id, e.detail)}
302
313
  ></ft-filter>
303
314
  </ft-accordion-item>
304
- `;
315
+ ` : nothing;
305
316
  })}
306
317
  </ft-accordion>
307
318
  `;
@@ -317,7 +328,7 @@ export class FtSearchBar extends FtLitElement {
317
328
  return html `
318
329
  <div class="ft-search-bar" part="search-bar">
319
330
  ${(this.renderSearchBarLeftAction())}
320
- <div class="ft-search-bar--input-container" part="input-container">
331
+ <div class="ft-search-bar--input-container" part="input-container" tabindex="-1">
321
332
  <div class="ft-search-bar--input-outline" part="input-outline">
322
333
  ${this.dense ? this.renderSelectedFacets() : nothing}
323
334
  <input class="ft-search-bar--input ft-typography--body2"
@@ -331,25 +342,36 @@ export class FtSearchBar extends FtLitElement {
331
342
  ${this.renderSuggestions()}
332
343
  </div>
333
344
  ${this.renderDesktopSearchBarButtons()}
334
- <div class="ft-search-bar--floating-panel" @keyup=${this.onFloatingContainerKeyUp} tabindex="-1">
335
- ${this.renderFacetsActions()}
336
- ${this.renderDesktopFacets()}
337
- <div class="ft-search-bar--facets-actions">
338
- <ft-button class="ft-search-bar--launch-search"
339
- part="launch-search-in-panel"
340
- icon="search"
341
- @click=${this.launchSearch}>
342
- ${this.labelResolver.resolve("searchButton")}
343
- </ft-button>
344
- </div>
345
- </div>
345
+ ${this.forceMenuOpen ? nothing : html `
346
+ <div class="ft-search-bar--floating-panel" @keyup=${this.onFloatingContainerKeyUp}
347
+ part="floating-panel"
348
+ tabindex="-1">
349
+ ${this.renderDesktopMenu()}
350
+ </div>`}
346
351
  </div>
347
- ${this.dense ? nothing : this.renderSelectedFacets()}
352
+ ${this.dense || this.forceMenuOpen ? (this.forceMenuOpen ? this.renderDesktopMenu() : nothing) : this.renderSelectedFacets()}
348
353
  `;
349
354
  }
355
+ renderDesktopMenu() {
356
+ return this.hasFacets ? html `
357
+ <div class="ft-search-bar--desktop-menu">
358
+ ${this.renderFacetsActions()}
359
+ ${this.renderDesktopFacets()}
360
+ <div class="ft-search-bar--facets-actions">
361
+ ${this.forceMenuOpen ? this.renderDesktopClearFilters() : nothing}
362
+ <ft-button class="ft-search-bar--launch-search"
363
+ part="launch-search-in-panel"
364
+ icon="search"
365
+ @click=${this.launchSearch}>
366
+ ${this.labelResolver.resolve("searchButton")}
367
+ </ft-button>
368
+ </div>
369
+ </div>
370
+ ` : nothing;
371
+ }
350
372
  renderSearchBarLeftAction() {
351
373
  if (this.hasFacets) {
352
- return html `
374
+ return this.forceMenuOpen ? nothing : html `
353
375
  <ft-button class="ft-search-bar--filters-opener ft-search-bar--left-action"
354
376
  part="filters-opener"
355
377
  trailingIcon
@@ -363,11 +385,12 @@ export class FtSearchBar extends FtLitElement {
363
385
  </ft-button>
364
386
  `;
365
387
  }
366
- if (this.hasLocaleSelector) {
388
+ else if (this.hasLocaleSelector) {
367
389
  return html `
368
390
  <ft-select outlined
369
391
  class="ft-search-bar--content-locale ft-search-bar--left-action"
370
- part="content-locale"
392
+ part="content-locale select-ft-locale"
393
+ .exportpartsPrefixes=${["content-locale", "select-ft-locale"]}
371
394
  @change=${(e) => this.contentLocale = e.detail == null ? undefined : e.detail}>
372
395
  ${repeat(this.availableContentLocales, l => l.lang, l => html `
373
396
  <ft-select-option .value=${l.lang}
@@ -423,16 +446,19 @@ export class FtSearchBar extends FtLitElement {
423
446
  `)}
424
447
  </ft-select>
425
448
  ` : nothing}
426
- ${this.searchFilters.length > 0 ? html `
427
- <ft-button part="facets-actions"
428
- @click=${this.clearFilters}>
429
- ${this.labelResolver.resolve("clearFilters")}
430
- </ft-button>
431
- ` : nothing}
432
449
  <slot name="facets-actions"></slot>
450
+ ${this.forceMenuOpen && !this.isMobile ? nothing : this.renderDesktopClearFilters()}
433
451
  </div>
434
452
  `;
435
453
  }
454
+ renderDesktopClearFilters() {
455
+ return this.searchFilters.length > 0 ? html `
456
+ <ft-button part="facets-actions"
457
+ @click=${this.clearFilters}>
458
+ ${this.labelResolver.resolve("clearFilters")}
459
+ </ft-button>
460
+ ` : nothing;
461
+ }
436
462
  renderDesktopFacets() {
437
463
  if (!this.hasFacets) {
438
464
  return nothing;
@@ -441,11 +467,11 @@ export class FtSearchBar extends FtLitElement {
441
467
  <ft-snap-scroll horizontal limitSize controls
442
468
  class="ft-search-bar--filters-container"
443
469
  part="filters-container"
444
- exportparts="controls:snap-scroll-controls">
470
+ exportpartsPrefix="filters-container">
445
471
  ${this.hasLocaleSelector ? html `
446
472
  <ft-filter class="ft-search-bar--content-locale"
447
473
  part="filters filter-ft-locale"
448
- exportparts=${this.getFilterExportParts("ft-locale")}
474
+ .exportpartsPrefixes=${["filter", "filter-ft-locale"]}
449
475
  label="${this.labelResolver.resolve("contentLocaleSelector")}"
450
476
  filterPlaceHolder="${this.labelResolver.resolve("filterInputPlaceHolder", this.labelResolver.resolve("contentLocaleSelector"))}"
451
477
  .options=${(this.contentLocalesAsFilterOptions())}
@@ -459,13 +485,14 @@ export class FtSearchBar extends FtLitElement {
459
485
  return html `
460
486
  <ft-filter
461
487
  class="${hierachical ? "ft-search-bar--hierarchical-filter" : ""}"
462
- part="filters filter-${keyWithNoColumn}"
463
- exportparts=${this.getFilterExportParts(keyWithNoColumn)}
488
+ part="filters filter filter-${keyWithNoColumn}"
489
+ .exportpartsPrefixes=${["filter", "filter-" + keyWithNoColumn]}
464
490
  id="${filter.id}"
465
491
  label="${filter.label}"
466
492
  filterPlaceHolder="${this.labelResolver.resolve("filterInputPlaceHolder", filter.label)}"
467
493
  clearButtonLabel="${this.labelResolver.resolve("clearFilterButton")}"
468
494
  moreValuesButtonLabel="${this.labelResolver.resolve("displayMoreFilterValuesButton")}"
495
+ noValuesLabel="${this.labelResolver.resolve("noFilterValuesAvailable")}"
469
496
  ?multivalued=${filter.multivalued}
470
497
  raiseSelectedOptions
471
498
  .options=${filter.options}
@@ -484,7 +511,7 @@ export class FtSearchBar extends FtLitElement {
484
511
  const useSnapScroll = (!this.isMobile && this.dense) || (this.isMobile && this.isMobileMenuOpen);
485
512
  const filters = html `
486
513
  ${this.hasLocaleSelector && (this.hasFacets || this.isMobile) ? html `
487
- <ft-chip part="selected-filters"
514
+ <ft-chip part="selected-filters selected-filter-ft-locale"
488
515
  ?dense=${this.dense && !this.isMobile}
489
516
  ?clickable=${this.isMobile}
490
517
  @click=${() => this.openMobileFilters("ft:contentLocale")}>
@@ -495,9 +522,10 @@ export class FtSearchBar extends FtLitElement {
495
522
  const values = getSelectedValues(facet);
496
523
  return repeat(values, value => {
497
524
  let label = facet.label + ": " + getBreadcrumbFromValue(value);
525
+ const keyWithNoColumn = facet.key.replace(":", "-");
498
526
  const chip = html `
499
527
  <ft-chip
500
- part="selected-filters"
528
+ part="selected-filters selected-filter-${keyWithNoColumn}"
501
529
  ?dense=${this.dense && !this.isMobile}
502
530
  ?clickable=${this.isMobile}
503
531
  ?removable=${!this.isMobile}
@@ -517,7 +545,7 @@ export class FtSearchBar extends FtLitElement {
517
545
  });
518
546
  })}
519
547
  ${this.isMobile ? html `
520
- <ft-chip part="selected-filters"
548
+ <ft-chip part="selected-filters mobile-filters-opener"
521
549
  icon="add"
522
550
  clickable
523
551
  @click=${() => {
@@ -533,7 +561,7 @@ export class FtSearchBar extends FtLitElement {
533
561
  <ft-snap-scroll horizontal controls hideScrollbar limitSize
534
562
  class="ft-search-bar--selected-filters"
535
563
  part="selected-filters-container"
536
- exportparts="controls:snap-scroll-controls">
564
+ exportpartsPrefix="selected-filters-container">
537
565
  ${filters}
538
566
  </ft-snap-scroll>
539
567
  `
@@ -545,9 +573,10 @@ export class FtSearchBar extends FtLitElement {
545
573
  }
546
574
  renderSuggestions() {
547
575
  const filteredRecentSearches = this.recentSearches.filter(q => q.toLowerCase().includes(this.query.toLowerCase()));
548
- const shouldDisplaySuggestions = this.query.length > 2 || filteredRecentSearches.length > 0;
576
+ const shouldDisplaySuggestions = this.suggestions.length > 0 || filteredRecentSearches.length > 0;
549
577
  return html `
550
578
  <div class="ft-search-bar--suggestions ${shouldDisplaySuggestions ? "ft-search-bar--suggestions-not-empty" : ""}"
579
+ part="suggestions-container"
551
580
  @keydown=${this.onSuggestKeyDown}>
552
581
  ${repeat(filteredRecentSearches.slice(0, 5), query => query, query => html `
553
582
  <a href="${this.searchRequestSerializer({ ...this.request, query: query })}"
@@ -578,13 +607,6 @@ export class FtSearchBar extends FtLitElement {
578
607
  <ft-typography variant="body1">${suggest.value}</ft-typography>
579
608
  </a>
580
609
  `)}
581
- ${filteredRecentSearches.length === 0 && this.suggestions.length === 0 && this.query.length > 2 && this.suggestionsLoaded
582
- ? html `
583
- <ft-typography class="ft-search-bar--no-suggestions" element="p"
584
- variant="body2">
585
- ${this.labelResolver.resolve("noSuggestions")}
586
- </ft-typography>
587
- ` : null}
588
610
  </div>
589
611
  `;
590
612
  }
@@ -618,9 +640,14 @@ export class FtSearchBar extends FtLitElement {
618
640
  async firstUpdated(props) {
619
641
  super.firstUpdated(props);
620
642
  this.initApi();
643
+ window.addEventListener("storage", (e) => {
644
+ if (e.key === this.recentSearchesStorageKey) {
645
+ this.initRecentSearches();
646
+ }
647
+ });
621
648
  }
622
649
  update(props) {
623
- var _a, _b, _c, _d, _e, _f;
650
+ var _a, _b, _c, _d, _e;
624
651
  if (props.has("labels")) {
625
652
  this.labelResolver = new ParametrizedLabelResolver(DEFAULT_LABELS, this.labels);
626
653
  }
@@ -639,19 +666,22 @@ export class FtSearchBar extends FtLitElement {
639
666
  if (this.baseUrl.endsWith("/")) {
640
667
  this.baseUrl = this.baseUrl.replace(/\/$/, "");
641
668
  }
642
- this.recentSearches = JSON.parse((_b = window.localStorage.getItem(this.recentSearchesStorageKey)) !== null && _b !== void 0 ? _b : "[]");
669
+ this.initRecentSearches();
643
670
  }
644
671
  if (props.has("presets")) {
645
- ((_c = this.presets) !== null && _c !== void 0 ? _c : []).forEach(preset => preset.filters.forEach(filter => filter.values = filter.values.map(v => unquote(v))));
672
+ ((_b = this.presets) !== null && _b !== void 0 ? _b : []).forEach(preset => preset.filters.forEach(filter => filter.values = filter.values.map(v => unquote(v))));
646
673
  }
647
674
  if (props.has("selectedPreset")) {
648
- const currentPreset = ((_d = this.presets) !== null && _d !== void 0 ? _d : []).find(p => p.name === this.selectedPreset);
675
+ const currentPreset = ((_c = this.presets) !== null && _c !== void 0 ? _c : []).find(p => p.name === this.selectedPreset);
649
676
  if (currentPreset && !this.compareRequests(this.request, currentPreset)) {
650
677
  this.setFiltersFromPreset(currentPreset);
651
678
  }
652
679
  }
680
+ if (props.has("contentLocale") && this.contentLocale != null) {
681
+ this.knownFacetLabels = new Map();
682
+ }
653
683
  if (["contentLocale", "searchFilters"].some(p => props.has(p))) {
654
- this.selectedPreset = (_f = ((_e = this.presets) !== null && _e !== void 0 ? _e : []).find(p => this.compareRequests(p, this.request))) === null || _f === void 0 ? void 0 : _f.name;
684
+ this.selectedPreset = (_e = ((_d = this.presets) !== null && _d !== void 0 ? _d : []).find(p => this.compareRequests(p, this.request))) === null || _e === void 0 ? void 0 : _e.name;
655
685
  }
656
686
  if (["baseUrl", "apiIntegrationIdentifier"].some(p => props.has(p))) {
657
687
  this.api = undefined;
@@ -704,9 +734,27 @@ export class FtSearchBar extends FtLitElement {
704
734
  if (this.facetsRequest.length > 0) {
705
735
  this.facetsLoaded = false;
706
736
  this.updateFacetsDebouncer.run(async () => {
707
- var _a, _b;
708
- this.facets = (_b = await ((_a = this.api) === null || _a === void 0 ? void 0 : _a.search({ ...this.request, query: "" }).then(r => r.facets).catch(() => []))) !== null && _b !== void 0 ? _b : [];
709
- this.facets.forEach(f => this.knownFacetLabels[f.key] = f.label);
737
+ var _a;
738
+ const retrievedFacets = new Map();
739
+ await ((_a = this.api) === null || _a === void 0 ? void 0 : _a.search({ ...this.request, query: "" }).then(r => r.facets.forEach(f => {
740
+ this.knownFacetLabels.set(f.key, f.label);
741
+ retrievedFacets.set(f.key, f);
742
+ })).catch(doNothing));
743
+ this.facets = [];
744
+ for (let r of this.facetsRequest) {
745
+ if (retrievedFacets.has(r.id)) {
746
+ this.facets.push(retrievedFacets.get(r.id));
747
+ }
748
+ else if (this.knownFacetLabels.has(r.id)) {
749
+ this.facets.push({
750
+ key: r.id,
751
+ label: this.knownFacetLabels.get(r.id),
752
+ rootNodes: [],
753
+ multiSelectionable: true,
754
+ hierarchical: false
755
+ });
756
+ }
757
+ }
710
758
  this.facetsLoaded = true;
711
759
  this.facetsInitialized = true;
712
760
  });
@@ -786,8 +834,22 @@ export class FtSearchBar extends FtLitElement {
786
834
  this.displayFacets = false;
787
835
  this.focus();
788
836
  }
837
+ get recentSearchesStorageKey() {
838
+ return this.baseUrl + ":ft:recent-search-queries";
839
+ }
840
+ initRecentSearches() {
841
+ var _a;
842
+ this.recentSearches = JSON.parse((_a = window.localStorage.getItem(this.recentSearchesStorageKey)) !== null && _a !== void 0 ? _a : "[]");
843
+ }
789
844
  saveRecentSearches() {
790
- window.localStorage.setItem(this.recentSearchesStorageKey, JSON.stringify(this.recentSearches));
845
+ const newValue = JSON.stringify(this.recentSearches);
846
+ window.localStorage.setItem(this.recentSearchesStorageKey, newValue);
847
+ window.dispatchEvent(new StorageEvent("storage", {
848
+ key: this.recentSearchesStorageKey,
849
+ newValue,
850
+ storageArea: window.localStorage,
851
+ url: window.location.href
852
+ }));
791
853
  }
792
854
  connectedCallback() {
793
855
  super.connectedCallback();
@@ -863,12 +925,6 @@ export class FtSearchBar extends FtLitElement {
863
925
  break;
864
926
  }
865
927
  }
866
- getFilterExportParts(metadataKey) {
867
- return ["container", "header", "clear-button", "input"].map(part => this.getFilterSubPartExportAttribute(part, metadataKey)).join(",");
868
- }
869
- getFilterSubPartExportAttribute(part, metadataKey) {
870
- return `${part}:filters-${part},${part}:filter-${metadataKey}-${part}`;
871
- }
872
928
  }
873
929
  FtSearchBar.elementDefinitions = {
874
930
  "ft-accordion": FtAccordion,
@@ -903,6 +959,9 @@ __decorate([
903
959
  __decorate([
904
960
  property({ type: Boolean })
905
961
  ], FtSearchBar.prototype, "forceMobileMenuOpen", void 0);
962
+ __decorate([
963
+ property({ type: Boolean })
964
+ ], FtSearchBar.prototype, "forceMenuOpen", void 0);
906
965
  __decorate([
907
966
  property()
908
967
  ], FtSearchBar.prototype, "baseUrl", void 0);