@geogirafe/lib-geoportal 1.2.0-dev.2829697690 → 1.2.0-dev.2836809138

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 (96) hide show
  1. package/api/apigeogirafeapp.js +1 -1
  2. package/assets/i18n/de.json +10 -1
  3. package/assets/i18n/en.json +10 -1
  4. package/assets/i18n/fr.json +10 -1
  5. package/assets/i18n/it.json +10 -1
  6. package/components/about/component.js +1 -1
  7. package/components/addbookmark/component.js +1 -1
  8. package/components/advancedfilter/component.js +1 -1
  9. package/components/alignnorthbutton-mobile/component.js +1 -1
  10. package/components/auth/component.js +1 -1
  11. package/components/basemap/component.js +1 -1
  12. package/components/contact/component.js +1 -1
  13. package/components/context-menu/custom-context-menu/component.js +1 -1
  14. package/components/context-menu/default-context-menu/component.js +1 -1
  15. package/components/coordinate/component.js +1 -1
  16. package/components/cross-section/cross-section-settings/component.js +1 -1
  17. package/components/cross-section/cross-section-viewer/component.js +1 -1
  18. package/components/displaymenubutton-mobile/component.js +1 -1
  19. package/components/displayselectorbutton-mobile/component.js +1 -1
  20. package/components/drawing/component-mobile.js +1 -1
  21. package/components/drawing/component.js +1 -1
  22. package/components/drawing/fixed-dimension/component.js +1 -1
  23. package/components/drawing-container-mobile/component.js +1 -1
  24. package/components/drawing-toolbar/component.js +1 -1
  25. package/components/edit/component.js +1 -1
  26. package/components/edit/editform/component.js +1 -1
  27. package/components/extlayers/component.js +1 -1
  28. package/components/feedbackbutton/component.js +1 -1
  29. package/components/geolocation-mobile/component.js +1 -1
  30. package/components/getdirections/component.js +1 -1
  31. package/components/help/component.js +1 -1
  32. package/components/infobox/component.js +1 -1
  33. package/components/infowindow/component.js +1 -1
  34. package/components/layout/component.js +1 -1
  35. package/components/lr-panel/component.js +1 -1
  36. package/components/main.d.ts +1 -0
  37. package/components/main.js +1 -0
  38. package/components/map-2d/component.js +1 -1
  39. package/components/map-3d/component.js +1 -1
  40. package/components/menu-mobile/component.js +1 -1
  41. package/components/menubutton/component.js +1 -1
  42. package/components/modals/component.js +1 -1
  43. package/components/navigation/component.js +1 -1
  44. package/components/news/news-button/component.js +1 -1
  45. package/components/news/news-panel/component.js +1 -1
  46. package/components/offline/component.js +1 -1
  47. package/components/print/component.js +1 -1
  48. package/components/prototypebanner/component.js +1 -1
  49. package/components/querybuilder/component.js +1 -1
  50. package/components/scale/component.js +1 -1
  51. package/components/search/component.d.ts +9 -0
  52. package/components/search/component.js +94 -25
  53. package/components/search/style.css +14 -4
  54. package/components/search-filter/component.d.ts +76 -0
  55. package/components/search-filter/component.js +388 -0
  56. package/components/search-filter/style.css +226 -0
  57. package/components/selectiongrid/component.js +1 -1
  58. package/components/selectionpanel-mobile/component.js +1 -1
  59. package/components/selectiontool/component.js +1 -1
  60. package/components/selectionwindow/component.js +1 -1
  61. package/components/share/component.js +1 -1
  62. package/components/share-mobile/component.js +1 -1
  63. package/components/swipe-up-panel-mobile/component.js +1 -1
  64. package/components/themes/component.js +1 -1
  65. package/components/timerestriction/component.js +1 -1
  66. package/components/timerestriction/timepicker/component.js +1 -1
  67. package/components/timerestriction/timeslider/component.js +1 -1
  68. package/components/treeview/treeviewgroup/component.js +1 -1
  69. package/components/treeview/treeviewitem/component.js +1 -1
  70. package/components/treeview/treeviewroot/component.js +1 -1
  71. package/components/treeview/treeviewtheme/component.js +1 -1
  72. package/components/userpreferences/component.js +1 -1
  73. package/components/videorecord/component.js +1 -1
  74. package/package.json +1 -1
  75. package/styles/common.css +1 -0
  76. package/styles/index.css +7 -4
  77. package/templates/index.html +7 -1
  78. package/templates/public/about.json +1 -1
  79. package/tools/app/geogirafeapp.js +2 -0
  80. package/tools/configuration/girafeconfig.d.ts +3 -0
  81. package/tools/configuration/girafeconfig.js +3 -1
  82. package/tools/main.d.ts +5 -4
  83. package/tools/search/abstractsearchclient.d.ts +32 -4
  84. package/tools/search/abstractsearchclient.js +68 -13
  85. package/tools/search/geoadminchsearchclient.d.ts +13 -6
  86. package/tools/search/geoadminchsearchclient.js +25 -15
  87. package/tools/search/gmfsearchclient.d.ts +18 -6
  88. package/tools/search/gmfsearchclient.js +59 -1
  89. package/tools/search/searchmanager.d.ts +19 -3
  90. package/tools/search/searchmanager.js +73 -10
  91. package/tools/state/graphicalInterface.d.ts +1 -0
  92. package/tools/state/graphicalInterface.js +1 -0
  93. package/tools/state/state.d.ts +8 -0
  94. package/tools/state/state.js +5 -0
  95. package/tools/tests/mockconfig.d.ts +9 -2
  96. package/tools/tests/mockconfig.js +5 -1
@@ -9,6 +9,8 @@
9
9
  width: 100%;
10
10
  display: flex;
11
11
  flex-direction: row;
12
+ align-items: center;
13
+ min-width: 0;
12
14
  border: 1px solid #cfd6dd;
13
15
  border-radius: 4px;
14
16
 
@@ -17,11 +19,16 @@
17
19
  }
18
20
  }
19
21
 
22
+ #search-filter {
23
+ flex: 0 0 auto;
24
+ min-width: 0;
25
+ }
26
+
20
27
  #search {
21
28
  font-size: 1rem;
22
29
  outline: none;
23
- margin: 0 0 0 0.5rem;
24
- padding-left: 0.8rem;
30
+ margin: 0 0 0 0.25rem;
31
+ padding-left: 0.25rem;
25
32
  border: 0;
26
33
  height: 2.25rem;
27
34
  width: auto;
@@ -39,7 +46,7 @@
39
46
  .select-color-icon {
40
47
  width: 14px;
41
48
  color: var(--text-color);
42
- padding: 0 1rem;
49
+ padding: 0 0.75rem;
43
50
  }
44
51
 
45
52
  .close-button,
@@ -56,11 +63,13 @@
56
63
  margin-bottom: 0.5rem;
57
64
  position: absolute;
58
65
  top: 3.5rem;
59
- width: 36rem;
66
+ width: min(36rem, 50vw);
60
67
  max-height: 31rem;
61
68
  background-color: var(--bkg-color);
62
69
  overflow-x: hidden;
63
70
  scrollbar-width: thin;
71
+ border: var(--app-standard-border);
72
+ border-radius: 4px;
64
73
  }
65
74
 
66
75
  .entry {
@@ -116,6 +125,7 @@
116
125
  color: var(--text-color);
117
126
  }
118
127
 
128
+ .result.selected,
119
129
  .result button:hover {
120
130
  background-color: var(--bkg-color-grad2) !important;
121
131
  color: var(--text-color);
@@ -0,0 +1,76 @@
1
+ import GirafeHTMLElement from '../../base/GirafeHTMLElement.js';
2
+ interface ShortcutEntry {
3
+ shortcut: string;
4
+ categoriesList: string;
5
+ }
6
+ interface CategoryEntry {
7
+ key: string;
8
+ provider: string;
9
+ category: string;
10
+ label: string;
11
+ }
12
+ declare class SearchFilterComponent extends GirafeHTMLElement {
13
+ protected templateUrl: string | null;
14
+ protected styleUrls: string[] | null;
15
+ template: () => import("uhtml").Hole;
16
+ enabled: boolean;
17
+ currentPrefixSymbol: string | null;
18
+ private prefixSymbols;
19
+ private categories;
20
+ private availableShortcuts;
21
+ private selectedCategories;
22
+ private fsmState;
23
+ private focusedShortcutIndex;
24
+ private readonly numberOfChipsToShow;
25
+ constructor();
26
+ private registerEvents;
27
+ private initSearchFilter;
28
+ private loadSearchCategories;
29
+ private sanitizeShortcuts;
30
+ getSelectedCategoryEntries(shortened?: boolean): CategoryEntry[];
31
+ getNumberOfOverflowChips(): number;
32
+ getNumberOfOverflowChipsAsLabel(): string;
33
+ isMenuOpen(): boolean;
34
+ isCategoriesOpen(): boolean;
35
+ isShortcutsOpen(): boolean;
36
+ isCategorySelected(categoryEntry: CategoryEntry): boolean;
37
+ hasShortcutsConfigured(): boolean;
38
+ isFilterActive(): boolean;
39
+ getSelectionCount(): number;
40
+ onFilterButtonClick(event: Event): void;
41
+ onCategoryToggle(categoryEntry: CategoryEntry, e: Event): void;
42
+ onSelectAll(event: Event): void;
43
+ onClearSelection(event: Event): void;
44
+ onChipClick(event: Event): void;
45
+ onRemoveChip(categoryEntry: CategoryEntry, event: Event): void;
46
+ private closeMenu;
47
+ private transitionTo;
48
+ getAllCategoriesFlattened(): CategoryEntry[];
49
+ private createCategoryEntry;
50
+ private categoryEntriesToSearchCategories;
51
+ private applyFiltersChange;
52
+ getShortcutEntries(): ShortcutEntry[];
53
+ private translateCategory;
54
+ getShortcutForCategory(categoryEntry: CategoryEntry): string;
55
+ private onPrefixUsed;
56
+ private readonly parentKeyDownHandler;
57
+ /**
58
+ * Triggered on a keydown event from the parent search input field.
59
+ */
60
+ onParentKeyDown(e: KeyboardEvent): void;
61
+ onShortcutListDown(e: KeyboardEvent): void;
62
+ private navigateToResult;
63
+ private focusResultFromIndex;
64
+ private applyShortcut;
65
+ onShortcutClick(shortcut: string): void;
66
+ private readonly outsideClickHandler;
67
+ private onOutsideClick;
68
+ private readonly insideClickHandler;
69
+ private onInsideClick;
70
+ private removeClickHandlers;
71
+ private addParentKeyDownEventListener;
72
+ private removeParentKeyDownHandler;
73
+ protected connectedCallback(): void;
74
+ protected disconnectedCallback(): void;
75
+ }
76
+ export default SearchFilterComponent;
@@ -0,0 +1,388 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { html as uHtml } from 'uhtml';
8
+ // SPDX-License-Identifier: Apache-2.0
9
+ import GirafeHTMLElement from '../../base/GirafeHTMLElement.js';
10
+ import { UsedInTemplateOnly } from '../../decorators.js';
11
+ class SearchFilterComponent extends GirafeHTMLElement {
12
+ templateUrl = null;
13
+ styleUrls = null;
14
+ template = () => {
15
+ return uHtml `<style>
16
+ .hidden{display:none!important}.gg-rotate90{transform:rotate(90deg)}.gg-rotate180{transform:rotate(180deg)}.gg-rotate270{transform:rotate(270deg)}img{filter:var(--svg-filter)}img.legend-image{filter:var(--svg-map-filter);background:var(--svg-legend-bkg)}div{scrollbar-width:thin}a,a:visited{color:var(--link-color)}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes spin-wait{0%{transform:rotate(0)}7%{transform:rotate(360deg)}to{transform:rotate(360deg)}}.gg-spin{animation-name:spin;animation-duration:2s;animation-timing-function:linear;animation-iteration-count:infinite}.gg-spin-wait{animation-name:spin-wait;animation-duration:10s;animation-timing-function:linear;animation-iteration-count:infinite}::-webkit-scrollbar{width:5px}::-webkit-scrollbar-thumb{background:#999}button,input,select,textarea{font:inherit}.gg-button,.gg-select,.gg-input,.gg-textarea{background-color:var(--bkg-color);color:var(--text-color);border:var(--app-standard-border);box-sizing:border-box;cursor:pointer;border-radius:3px;outline:0;margin:0;padding:0 0 0 .5rem;display:inline-block}.gg-label{background-color:var(--bkg-color);color:var(--text-color);border:none;align-items:center;margin:0;padding:0;display:flex}.gg-button,.gg-select,.gg-input,.gg-label{min-height:calc(var(--app-standard-height) / 1.5)}.gg-textarea{max-height:initial;resize:vertical;height:6rem;padding:.5rem;line-height:1.3rem}.gg-input{cursor:text}.gg-checkbox{accent-color:var(--text-color);cursor:pointer;width:1.2rem}.gg-range{accent-color:var(--text-color)}.gg-button{padding:0 .5rem}.gg-button.active{border:solid 1px var(--text-color-grad1);background-color:var(--bkg-color-grad2)}.gg-button:disabled{background-color:var(--bkg-color-grad1);color:var(--text-color-grad2);cursor:not-allowed;border:none}.gg-button:disabled img{filter:opacity(.6)}.gg-input:disabled,.gg-select:disabled,.gg-textarea:disabled{background-color:var(--bkg-color-grad1);color:var(--text-color-grad2);cursor:not-allowed}.gg-button>img{vertical-align:middle}.gg-icon-button{color:var(--text-color);cursor:pointer;background-color:#0000;border:none;flex-direction:column;justify-content:center;align-items:center;padding:0;display:flex}.gg-icon{justify-content:center;align-items:center;display:flex}.gg-big,.gg-big-withtext{min-width:var(--app-standard-height);min-height:var(--app-standard-height);max-height:var(--app-standard-height)}.gg-big img,.gg-big-withtext img{width:calc(var(--app-standard-height) - 1.5rem);margin:0}.gg-big-withtext span{font-variant:small-caps;padding:0 1rem;font-size:.9rem}.gg-medium,.gg-medium-withtext{min-width:calc(var(--app-standard-height) / 1.2);min-height:calc(var(--app-standard-height) / 1.2);max-height:calc(var(--app-standard-height) / 1.2);flex-direction:row}.gg-medium img{width:calc(var(--app-standard-height) / 2.4);margin:0}.gg-medium-withtext img{width:calc(var(--app-standard-height) / 2.4);margin-left:.5rem}.gg-medium-withtext span{padding:0 1rem 0 .5rem;font-size:.9rem}.gg-small,.gg-small-withtext{min-width:calc(var(--app-standard-height) / 2);min-height:calc(var(--app-standard-height) / 2);max-height:calc(var(--app-standard-height) / 2);flex-direction:row}.gg-small img{width:calc(var(--app-standard-height) / 3);margin:0}.gg-small-withtext img{width:calc(var(--app-standard-height) / 3);margin-left:.5rem}.gg-small-withtext span{padding:0 .5rem 0 .3rem;font-size:.9rem}.gg-button:hover:not(:disabled),.gg-select:hover:not(:disabled),.gg-input:hover:not(:disabled),.gg-textarea:hover:not(:disabled),.gg-icon-button:hover:not(:disabled){background-color:var(--bkg-color-grad1)}.gg-opacity{opacity:.5}.gg-opacity:hover{opacity:1;background-color:#0000}.gg-tabs{cursor:pointer;grid-auto-flow:column;padding-bottom:1rem;font-size:1rem;display:grid}.gg-tab{border:none;border-bottom:var(--app-standard-border);cursor:pointer;color:var(--text-color);background:0 0;padding:.5rem}.gg-tab.active{border-bottom:solid 1px var(--text-color)}.girafe-button-big,.girafe-button-large,.girafe-button-small,.girafe-button-tiny{color:var(--text-color);background-color:#0000;border:none;flex-direction:column;display:flex}.girafe-button-big:hover,.girafe-button-large:hover,.girafe-button-small:hover,.girafe-button-tiny:hover{background-color:var(--bkg-color-grad1);cursor:pointer}.girafe-button-big.dark,.girafe-button-large.dark,.girafe-button-small.dark,.girafe-button-tiny.dark{background-color:var(--bkg-color);filter:invert()}.girafe-button-big{width:var(--app-standard-height);height:var(--app-standard-height);align-items:center;padding:1rem}.girafe-button-big img{overflow:hidden}.girafe-button-large{flex-direction:row}.girafe-button-large img{height:2rem;margin:.3rem}.girafe-button-large span{height:2rem;margin:.3rem;line-height:2rem}.girafe-button-small{min-width:calc(var(--app-standard-height) / 2);height:calc(var(--app-standard-height) / 2);align-items:center;padding:.5rem}.girafe-button-small img{overflow:hidden}.girafe-button-small span{text-align:left;text-overflow:ellipsis;width:100%;overflow:hidden}.girafe-button-tiny{align-items:center;width:1rem;height:1rem;padding:0}.girafe-button-tiny img{overflow:hidden}.girafe-onboarding-theme{background-color:var(--bkg-color)!important;color:var(--text-color)!important}.girafe-onboarding-theme button{background-color:var(--bkg-color)!important;color:var(--text-color)!important;text-shadow:none!important}.girafe-onboarding-theme button.driver-popover-close-btn{z-index:10000}.tippy-box{background-color:var(--bkg-color)!important;border:var(--app-standard-border)!important}.tippy-content{color:var(--text-color)!important}.tippy-arrow{color:var(--bkg-color)!important}.tippy-box[data-theme~=error]{background-color:var(--error-color);white-space:pre-line}div.tippy-box[data-theme=image-popup]{border:var(--app-standard-border)!important;& div.tippy-content{background-color:var(--bkg-color)!important;& img{max-width:20rem;max-height:20rem}}& div.tippy-arrow{color:var(--bkg-color)!important}}
17
+ </style><style>
18
+ #search-filter-root{--active-color:#2563eb;--chip-text-color:#102e91;--chip-bg-color:#dbeafe;--chip-width:7rem;width:max-content;max-width:100%;color:var(--text-color);align-items:center;gap:.4rem;display:inline-flex;position:relative}#search-filter-button{background:var(--bkg-color);cursor:pointer;border:1px solid #0000;border-radius:4px;justify-content:center;align-items:center;min-width:2.25rem;height:2.25rem;display:inline-flex;position:relative}#search-filter-button.active{border-color:var(--active-color);background:var(--bkg-color);box-shadow:inset 0 0 0 2px #2563eb33}#search-filter-button.active .filter-icon{filter:invert(35%)sepia()saturate(400%)hue-rotate(190deg)}.filter-icon{width:20px;height:20px}.badge{background:var(--active-color);color:#fff;text-align:center;border-radius:5rem;min-width:1rem;height:1rem;padding:0 .15rem;font-size:.7rem;line-height:1rem;position:absolute;top:-.25rem;right:-.5rem}#search-filter-chips{align-items:center;gap:.35rem;min-width:0;display:flex}.chip{background:var(--chip-bg-color);color:var(--chip-text-color);border:1px solid var(--chip-text-color);max-width:var(--chip-width);cursor:pointer;border-radius:5rem;flex:none;align-items:center;height:1.1rem;padding:.1rem .5rem;font-size:.8rem;display:inline-flex}.chip-label{color:inherit;cursor:pointer;background:0 0;border:none;outline:none;width:100%;min-width:0;padding:0}.chip-remove{color:var(--chip-text-color);cursor:pointer;background:0 0;border:none;width:1rem;height:1rem;padding:0;font-size:1rem;line-height:1}#search-filter-menu{width:100%;min-width:28rem;max-width:min(36rem,50vw);color:var(--text-color);background:var(--bkg-color);border:var(--app-standard-border);z-index:1001;border-radius:4px;flex-direction:column;min-height:0;max-height:min(31rem,60vh);display:flex;position:absolute;top:3.15rem;left:0;overflow:hidden}.menu-header{background:var(--bkg-color);border-bottom:var(--app-standard-border);z-index:1002;padding:1rem 1rem .5rem;position:sticky;top:0}.category-title{padding-bottom:.75rem;font-size:1rem;font-weight:700}.menu-actions{gap:.35rem;display:flex}.menu-action{border:var(--app-standard-border);background:var(--bkg-color);cursor:pointer;border-radius:4px;padding:.15rem .5rem}.shortcut-title{align-self:center;margin:0 .5rem 0 auto;font-size:.85rem}.menu-list{scrollbar-width:thin;flex-direction:column;padding:.5rem;display:flex;overflow:auto}.category-row{cursor:pointer;border-radius:4px;align-items:center;gap:.4rem;padding:.25rem;display:flex}.category-row:hover{background-color:var(--bkg-color-grad1)}.category-label{min-width:0;margin-right:auto}.shortcut{color:var(--text-color-grad1);font-family:monospace;font-size:.85rem}.shortcut-header{padding:.5rem;font-style:italic}.shortcut-row{background:var(--bkg-color);color:var(--text-color);cursor:pointer;text-align:left;border:0 solid #0000;border-radius:4px;align-items:center;padding:.5rem;display:flex}.shortcut-row:hover,.shortcut-row:focus,.shortcut-row.active{background-color:var(--bkg-color-grad1);outline:none}.shortcut-row>.shortcut{min-width:4rem}.text-ellipsis{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}
19
+ </style>
20
+ <style>${this.customStyle}</style>
21
+ <div id="search-filter-root" class="${this.enabled ? '' : 'hidden'}"><button id="search-filter-button" class="${this.isFilterActive() ? 'active' : ''}" type="button" title="Filter categories" aria-label="Filter categories" aria-expanded="${this.isMenuOpen()}" aria-haspopup="dialog" onclick="${(e) => this.onFilterButtonClick(e)}"><img class="filter-icon" alt="filter icon" src="icons/filter.svg"> <span class="${this.getSelectionCount() > 0 ? 'badge' : 'hidden'}">${this.getSelectionCount()}</span></button><div id="search-filter-chips" class="${this.getSelectedCategoryEntries().length > 0 ? '' : 'hidden'}">${this.getSelectedCategoryEntries(true).map((entry) => uHtml `<div class="chip"><button class="chip-label text-ellipsis" i18n="${entry.label}" onclick="${(e) => this.onChipClick(e)}">${entry.label}</button> <button class="chip-remove" type="button" title="Remove category" aria-label="Remove category" onclick="${(e) => this.onRemoveChip(entry, e)}">×</button></div>`)} ${this.getNumberOfOverflowChips() ? uHtml `<div class="chip"><button class="chip-label text-ellipsis" onclick="${(e) => this.onChipClick(e)}">${this.getNumberOfOverflowChipsAsLabel()}</button></div>` : ''}</div>${this.isMenuOpen() ? uHtml `<div id="search-filter-menu">${this.isCategoriesOpen() ? uHtml `<div class="menu-header"><div i18n="Filter categories" class="category-title">Search categories</div><div class="menu-actions"><button class="gg-button gg-medium menu-action" type="button" i18n="Select all" onclick="${(e) => this.onSelectAll(e)}">Select all</button> <button class="gg-button gg-medium menu-action" type="button" i18n="Reset" onclick="${(e) => this.onClearSelection(e)}">Reset</button><div class="${this.hasShortcutsConfigured() ? 'shortcut-title' : 'hidden'}"><span i18n="Shortcut">Shortcut</span></div></div></div><div class="menu-list">${this.getAllCategoriesFlattened().map((entry) => uHtml ` <label class="category-row"><input type="checkbox" value="${entry.key}" class="gg-checkbox" .checked="${this.isCategorySelected(entry)}" onchange="${(e) => this.onCategoryToggle(entry, e)}"> <span class="category-label text-ellipsis" i18n="${entry.label}">${entry.label}</span> <span class="shortcut">${this.getShortcutForCategory(entry)}</span></label> `)}</div>` : ''} ${this.isShortcutsOpen() ? uHtml `<div class="menu-header shortcut-header" i18n="Type or select a shortcut from the list to quickly filter search results.">Type or select a shortcut from the list to quickly filter search results.</div><div id="shortcut-list" class="menu-list">${this.getShortcutEntries().map((shEntry) => uHtml ` <button type="button" class="shortcut-row" data-shortcut="${shEntry.shortcut}" onkeydown="${(e) => this.onShortcutListDown(e)}" onclick="${() => this.onShortcutClick(shEntry.shortcut)}"><span class="shortcut">${this.currentPrefixSymbol}${shEntry.shortcut}</span> <span class="text-ellipsis">${shEntry.categoriesList}</span></button> `)} ` : ''}</div></div>` : ''}</div>
22
+ ${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
23
+ };
24
+ enabled = false;
25
+ currentPrefixSymbol = null;
26
+ prefixSymbols = [];
27
+ categories = {};
28
+ availableShortcuts = {};
29
+ selectedCategories = new Map();
30
+ fsmState = 'Idle';
31
+ focusedShortcutIndex = -1;
32
+ numberOfChipsToShow = 1;
33
+ constructor() {
34
+ super('search-filter');
35
+ }
36
+ registerEvents() {
37
+ // Event listeners to close menu when clicking outside
38
+ document.addEventListener('click', this.outsideClickHandler);
39
+ this.addEventListener('click', this.insideClickHandler);
40
+ // Event listeners for when shortcuts are entered directly in the search bar instead of selected in the menu
41
+ this.subscribe('search.filterShortcutPrefix', (_old, prefix) => this.onPrefixUsed(prefix));
42
+ this.subscribe('search.filterShortcut', (_old, shortcut) => this.applyShortcut(shortcut));
43
+ }
44
+ async initSearchFilter() {
45
+ const categories = await this.loadSearchCategories();
46
+ if (!categories || !Object.values(categories).length) {
47
+ this.enabled = false;
48
+ this.refreshRender();
49
+ return;
50
+ }
51
+ this.enabled = true;
52
+ this.categories = categories;
53
+ const searchConfig = this.context.configManager.Config.search;
54
+ this.prefixSymbols = searchConfig?.filterShortcutPrefix ?? ['#'];
55
+ this.currentPrefixSymbol = this.prefixSymbols[0];
56
+ this.availableShortcuts = searchConfig?.filterShortcuts ?? {};
57
+ this.sanitizeShortcuts();
58
+ this.refreshRender();
59
+ }
60
+ async loadSearchCategories() {
61
+ try {
62
+ return (await this.context.searchManager.getSearchCategories()) ?? null;
63
+ }
64
+ catch (error) {
65
+ console.error('Unable to load search categories:', error);
66
+ }
67
+ return null;
68
+ }
69
+ sanitizeShortcuts() {
70
+ // Remove categories that are not known to the search clients
71
+ const availableCategories = this.getAllCategoriesFlattened();
72
+ this.availableShortcuts = Object.fromEntries(Object.entries(this.availableShortcuts)
73
+ .map(([shortcut, categories]) => [
74
+ shortcut,
75
+ categories.filter((category) => availableCategories.find((cat) => cat.category === category))
76
+ ])
77
+ .filter(([_shortcut, categories]) => categories.length > 0));
78
+ }
79
+ getSelectedCategoryEntries(shortened = false) {
80
+ if (!this.isFilterActive()) {
81
+ return [];
82
+ }
83
+ const selectedCategories = Array.from(this.selectedCategories.values());
84
+ const hiddenLabelsCount = this.getNumberOfOverflowChips();
85
+ if (!shortened || hiddenLabelsCount === 0) {
86
+ return selectedCategories;
87
+ }
88
+ return selectedCategories.slice(0, this.numberOfChipsToShow);
89
+ }
90
+ getNumberOfOverflowChips() {
91
+ return Math.max(this.getSelectionCount() - this.numberOfChipsToShow, 0);
92
+ }
93
+ getNumberOfOverflowChipsAsLabel() {
94
+ return this.context.i18nManager
95
+ .getTranslation('+{number} more')
96
+ .replace('{number}', String(this.getNumberOfOverflowChips()));
97
+ }
98
+ isMenuOpen() {
99
+ return this.fsmState !== 'Idle';
100
+ }
101
+ isCategoriesOpen() {
102
+ return this.fsmState === 'CategoriesOpen';
103
+ }
104
+ isShortcutsOpen() {
105
+ return this.fsmState === 'ShortcutsOpen';
106
+ }
107
+ isCategorySelected(categoryEntry) {
108
+ return this.selectedCategories.has(categoryEntry.key);
109
+ }
110
+ hasShortcutsConfigured() {
111
+ return Object.keys(this.availableShortcuts).length > 0;
112
+ }
113
+ isFilterActive() {
114
+ const totalCategories = this.getAllCategoriesFlattened().length;
115
+ return totalCategories > 0 && this.selectedCategories.size > 0 && this.selectedCategories.size < totalCategories;
116
+ }
117
+ getSelectionCount() {
118
+ return this.isFilterActive() ? this.selectedCategories.size : 0;
119
+ }
120
+ onFilterButtonClick(event) {
121
+ event.preventDefault();
122
+ event.stopPropagation();
123
+ if (this.fsmState === 'CategoriesOpen') {
124
+ this.closeMenu();
125
+ return;
126
+ }
127
+ this.transitionTo('CategoriesOpen');
128
+ this.refreshRender();
129
+ }
130
+ onCategoryToggle(categoryEntry, e) {
131
+ const input = e.target;
132
+ if (!input) {
133
+ return;
134
+ }
135
+ if (input.checked) {
136
+ this.selectedCategories.set(categoryEntry.key, categoryEntry);
137
+ }
138
+ else {
139
+ this.selectedCategories.delete(categoryEntry.key);
140
+ }
141
+ this.applyFiltersChange();
142
+ this.refreshRender();
143
+ }
144
+ onSelectAll(event) {
145
+ event.preventDefault();
146
+ this.selectedCategories = new Map(this.getAllCategoriesFlattened().map((categoryEntry) => [categoryEntry.key, categoryEntry]));
147
+ this.applyFiltersChange();
148
+ this.refreshRender();
149
+ }
150
+ onClearSelection(event) {
151
+ event.preventDefault();
152
+ this.selectedCategories.clear();
153
+ this.applyFiltersChange();
154
+ this.refreshRender();
155
+ }
156
+ onChipClick(event) {
157
+ event.preventDefault();
158
+ event.stopPropagation();
159
+ if (this.fsmState === 'CategoriesOpen') {
160
+ this.closeMenu();
161
+ }
162
+ else {
163
+ this.transitionTo('CategoriesOpen');
164
+ }
165
+ this.refreshRender();
166
+ }
167
+ onRemoveChip(categoryEntry, event) {
168
+ event.preventDefault();
169
+ event.stopPropagation();
170
+ this.selectedCategories.delete(categoryEntry.key);
171
+ this.applyFiltersChange();
172
+ this.refreshRender();
173
+ }
174
+ closeMenu() {
175
+ this.transitionTo('Idle');
176
+ this.removeParentKeyDownHandler();
177
+ this.focusedShortcutIndex = -1;
178
+ this.refreshRender();
179
+ }
180
+ transitionTo(state) {
181
+ this.fsmState = state;
182
+ // Update the state
183
+ this.state.interface.searchFilterComponentVisible = this.fsmState !== 'Idle';
184
+ }
185
+ getAllCategoriesFlattened() {
186
+ return Object.entries(this.categories).flatMap(([provider, categoryList], _) => categoryList.map((category) => {
187
+ return this.createCategoryEntry(provider, category);
188
+ }));
189
+ }
190
+ createCategoryEntry(provider, category) {
191
+ return {
192
+ key: `${provider}-${category}`,
193
+ provider,
194
+ category: category,
195
+ label: this.context.searchManager.getI18nKeyForCategory(provider, category)
196
+ };
197
+ }
198
+ categoryEntriesToSearchCategories() {
199
+ return Array.from(this.selectedCategories.values()).reduce((categoriesPerProvider, entry) => {
200
+ if (!categoriesPerProvider[entry.provider]) {
201
+ categoriesPerProvider[entry.provider] = [];
202
+ }
203
+ categoriesPerProvider[entry.provider].push(entry.category);
204
+ return categoriesPerProvider;
205
+ }, {});
206
+ }
207
+ applyFiltersChange() {
208
+ this.state.search.filter = this.categoryEntriesToSearchCategories();
209
+ }
210
+ getShortcutEntries() {
211
+ return (Object.entries(this.availableShortcuts).map(([shortcut, categories]) => ({
212
+ shortcut,
213
+ categoriesList: categories.map((category) => this.translateCategory(category)).join(', ')
214
+ })) ?? []);
215
+ }
216
+ translateCategory(category) {
217
+ const provider = Object.keys(this.categories).find((provider) => this.categories[provider].includes(category));
218
+ return this.context.i18nManager.getTranslation(provider ? this.context.searchManager.getI18nKeyForCategory(provider, category) : category);
219
+ }
220
+ getShortcutForCategory(categoryEntry) {
221
+ const shortcut = Object.keys(this.availableShortcuts).find((shortcut) => this.availableShortcuts[shortcut].includes(categoryEntry.category)) ?? '';
222
+ if (shortcut) {
223
+ return `${this.currentPrefixSymbol}${shortcut}`;
224
+ }
225
+ return '';
226
+ }
227
+ onPrefixUsed(prefix) {
228
+ if (!this.hasShortcutsConfigured()) {
229
+ return;
230
+ }
231
+ if (prefix === null) {
232
+ // Prefix was removed from the search input
233
+ this.closeMenu();
234
+ }
235
+ else {
236
+ this.currentPrefixSymbol = prefix;
237
+ this.transitionTo('ShortcutsOpen');
238
+ this.addParentKeyDownEventListener();
239
+ }
240
+ this.refreshRender();
241
+ }
242
+ parentKeyDownHandler = (event) => this.onParentKeyDown(event);
243
+ /**
244
+ * Triggered on a keydown event from the parent search input field.
245
+ */
246
+ onParentKeyDown(e) {
247
+ if (e.key === 'Escape') {
248
+ this.closeMenu();
249
+ return;
250
+ }
251
+ if (!this.isShortcutsOpen()) {
252
+ return;
253
+ }
254
+ if (e.key === 'ArrowDown' && this.focusedShortcutIndex === -1) {
255
+ // Focus the first shortcut
256
+ this.focusedShortcutIndex = 0;
257
+ this.focusResultFromIndex();
258
+ this.removeParentKeyDownHandler();
259
+ }
260
+ }
261
+ onShortcutListDown(e) {
262
+ this.navigateToResult(e);
263
+ }
264
+ navigateToResult(e) {
265
+ const elem = e.target;
266
+ if (!elem) {
267
+ return;
268
+ }
269
+ switch (e.key) {
270
+ case 'Escape':
271
+ this.closeMenu();
272
+ break;
273
+ case 'ArrowDown':
274
+ if (this.focusedShortcutIndex < Object.keys(this.availableShortcuts).length - 1) {
275
+ this.focusedShortcutIndex += 1;
276
+ this.focusResultFromIndex();
277
+ }
278
+ break;
279
+ case 'ArrowUp':
280
+ if (this.focusedShortcutIndex > 0) {
281
+ this.focusedShortcutIndex -= 1;
282
+ this.focusResultFromIndex();
283
+ }
284
+ break;
285
+ case 'Enter':
286
+ this.applyShortcut(elem.dataset.shortcut ?? null);
287
+ break;
288
+ }
289
+ }
290
+ focusResultFromIndex() {
291
+ const shortcutList = this.getById('shortcut-list');
292
+ if (!shortcutList) {
293
+ return;
294
+ }
295
+ const shortcutBtn = [...shortcutList.children][this.focusedShortcutIndex];
296
+ if (shortcutBtn) {
297
+ shortcutBtn.focus();
298
+ }
299
+ }
300
+ applyShortcut(shortcut) {
301
+ if (!shortcut) {
302
+ return;
303
+ }
304
+ const categoriesToActivate = this.availableShortcuts[shortcut] ?? [];
305
+ categoriesToActivate.forEach((category) => {
306
+ const entries = this.getAllCategoriesFlattened().filter((entry) => entry.category.includes(category));
307
+ entries.forEach((entry) => this.selectedCategories.set(entry.key, entry));
308
+ });
309
+ if (categoriesToActivate) {
310
+ this.closeMenu();
311
+ this.applyFiltersChange();
312
+ }
313
+ this.refreshRender();
314
+ }
315
+ onShortcutClick(shortcut) {
316
+ this.applyShortcut(shortcut);
317
+ }
318
+ outsideClickHandler = (event) => this.onOutsideClick(event);
319
+ onOutsideClick(_event) {
320
+ if (this.isMenuOpen()) {
321
+ this.closeMenu();
322
+ }
323
+ }
324
+ insideClickHandler = (event) => this.onInsideClick(event);
325
+ onInsideClick(event) {
326
+ // Stop propagation to prevent closing menu when clicking inside the menu
327
+ event.stopPropagation();
328
+ }
329
+ removeClickHandlers() {
330
+ document.removeEventListener('click', this.outsideClickHandler);
331
+ this.removeEventListener('click', this.insideClickHandler);
332
+ }
333
+ addParentKeyDownEventListener() {
334
+ document.addEventListener('keydown', this.parentKeyDownHandler);
335
+ }
336
+ removeParentKeyDownHandler() {
337
+ document.removeEventListener('keydown', this.parentKeyDownHandler);
338
+ }
339
+ connectedCallback() {
340
+ super.connectedCallback();
341
+ this.render();
342
+ this.girafeTranslate();
343
+ if (this.context.configManager.Config.search?.filterEnabled) {
344
+ void this.initSearchFilter();
345
+ this.registerEvents();
346
+ }
347
+ else {
348
+ this.hide();
349
+ }
350
+ }
351
+ disconnectedCallback() {
352
+ this.removeClickHandlers();
353
+ this.removeParentKeyDownHandler();
354
+ this.enabled = false;
355
+ super.disconnectedCallback();
356
+ }
357
+ }
358
+ __decorate([
359
+ UsedInTemplateOnly()
360
+ ], SearchFilterComponent.prototype, "getSelectedCategoryEntries", null);
361
+ __decorate([
362
+ UsedInTemplateOnly()
363
+ ], SearchFilterComponent.prototype, "getNumberOfOverflowChipsAsLabel", null);
364
+ __decorate([
365
+ UsedInTemplateOnly()
366
+ ], SearchFilterComponent.prototype, "isCategoriesOpen", null);
367
+ __decorate([
368
+ UsedInTemplateOnly()
369
+ ], SearchFilterComponent.prototype, "isCategorySelected", null);
370
+ __decorate([
371
+ UsedInTemplateOnly()
372
+ ], SearchFilterComponent.prototype, "onSelectAll", null);
373
+ __decorate([
374
+ UsedInTemplateOnly()
375
+ ], SearchFilterComponent.prototype, "onClearSelection", null);
376
+ __decorate([
377
+ UsedInTemplateOnly()
378
+ ], SearchFilterComponent.prototype, "onChipClick", null);
379
+ __decorate([
380
+ UsedInTemplateOnly()
381
+ ], SearchFilterComponent.prototype, "onRemoveChip", null);
382
+ __decorate([
383
+ UsedInTemplateOnly()
384
+ ], SearchFilterComponent.prototype, "getShortcutEntries", null);
385
+ __decorate([
386
+ UsedInTemplateOnly()
387
+ ], SearchFilterComponent.prototype, "getShortcutForCategory", null);
388
+ export default SearchFilterComponent;