@teipublisher/pb-components 2.26.1-next.2 → 3.0.0-next-4.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 (262) hide show
  1. package/.github/workflows/docker-cypress.yml +54 -0
  2. package/.github/workflows/main.yml +6 -4
  3. package/.github/workflows/node.js.yml +56 -21
  4. package/.github/workflows/release.js.yml +19 -17
  5. package/.releaserc.json +1 -1
  6. package/CHANGELOG.md +351 -9
  7. package/Dockerfile +78 -70
  8. package/README.md +112 -4
  9. package/css/components.css +5 -5
  10. package/css/gridjs/mermaid.min.css +1 -1
  11. package/css/leaflet/Control.Geocoder.css +1 -126
  12. package/css/leaflet/images/layers.png +0 -0
  13. package/css/tify/tify.css +6 -5
  14. package/css/tom-select/tom-select.bootstrap4.min.css +1 -1
  15. package/css/tom-select/tom-select.bootstrap5.min.css +1 -1
  16. package/css/tom-select/tom-select.default.min.css +1 -1
  17. package/css/tom-select/tom-select.default.min.css.map +1 -0
  18. package/css/tom-select/tom-select.min.css +1 -1
  19. package/cypress.config.js +84 -0
  20. package/dist/api.html +1 -1
  21. package/dist/css/design-system.css +607 -0
  22. package/dist/demo/bundle-test.html +4 -3
  23. package/dist/demo/components.css +46 -1
  24. package/dist/demo/design-system.html +710 -0
  25. package/dist/demo/dts-client.html +2 -2
  26. package/dist/demo/pb-autocomplete.html +23 -11
  27. package/dist/demo/pb-autocomplete2.html +66 -55
  28. package/dist/demo/pb-autocomplete3.html +17 -8
  29. package/dist/demo/pb-blacklab-highlight.html +28 -11
  30. package/dist/demo/pb-blacklab-results.html +3 -2
  31. package/dist/demo/pb-browse-docs.html +24 -24
  32. package/dist/demo/pb-browse-docs2.html +3 -3
  33. package/dist/demo/pb-clipboard.html +32 -28
  34. package/dist/demo/pb-code-editor.html +6 -6
  35. package/dist/demo/pb-code-highlight.html +63 -63
  36. package/dist/demo/pb-codepen.html +1 -1
  37. package/dist/demo/pb-collapse.html +1 -1
  38. package/dist/demo/pb-collapse2.html +2 -2
  39. package/dist/demo/pb-combo-box.html +135 -130
  40. package/dist/demo/pb-custom-form.html +64 -55
  41. package/dist/demo/pb-dialog.html +12 -6
  42. package/dist/demo/pb-document.html +1 -1
  43. package/dist/demo/pb-download.html +68 -59
  44. package/dist/demo/pb-drawer.html +67 -46
  45. package/dist/demo/pb-drawer2.html +65 -58
  46. package/dist/demo/pb-edit-app.html +2 -2
  47. package/dist/demo/pb-edit-xml.html +1 -1
  48. package/dist/demo/pb-facsimile-2.html +26 -11
  49. package/dist/demo/pb-facsimile-3.html +25 -10
  50. package/dist/demo/pb-facsimile-dedup-test-2.html +48 -0
  51. package/dist/demo/pb-facsimile-dedup-test.html +48 -0
  52. package/dist/demo/pb-facsimile.html +4 -4
  53. package/dist/demo/pb-formula.html +1 -1
  54. package/dist/demo/pb-grid.html +22 -8
  55. package/dist/demo/pb-highlight.html +2 -2
  56. package/dist/demo/pb-i18n-simple.html +1 -0
  57. package/dist/demo/pb-i18n.html +15 -5
  58. package/dist/demo/pb-image-strip-standalone.html +2 -2
  59. package/dist/demo/pb-image-strip-view.html +2 -2
  60. package/dist/demo/pb-leaflet-map.html +3 -3
  61. package/dist/demo/pb-leaflet-map2.html +2 -2
  62. package/dist/demo/pb-leaflet-map3.html +3 -3
  63. package/dist/demo/pb-link.html +1 -1
  64. package/dist/demo/pb-load.html +2 -6
  65. package/dist/demo/pb-login.html +1 -3
  66. package/dist/demo/pb-manage-odds.html +9 -4
  67. package/dist/demo/pb-markdown.html +1 -1
  68. package/dist/demo/pb-media-query.html +2 -2
  69. package/dist/demo/pb-mei.html +2 -2
  70. package/dist/demo/pb-mei2.html +2 -2
  71. package/dist/demo/pb-message.html +2 -3
  72. package/dist/demo/pb-odd-editor.html +54 -52
  73. package/dist/demo/pb-page-header.html +27 -0
  74. package/dist/demo/pb-popover.html +1 -1
  75. package/dist/demo/pb-print-preview.html +2 -2
  76. package/dist/demo/pb-progress.html +4 -4
  77. package/dist/demo/pb-repeat.html +32 -36
  78. package/dist/demo/pb-search.html +16 -5
  79. package/dist/demo/pb-search2.html +4 -4
  80. package/dist/demo/pb-search3.html +3 -3
  81. package/dist/demo/pb-search4.html +3 -3
  82. package/dist/demo/pb-select-feature.html +4 -4
  83. package/dist/demo/pb-select-feature2.html +4 -4
  84. package/dist/demo/pb-select-feature3.html +2 -2
  85. package/dist/demo/pb-select-i18n.html +58 -53
  86. package/dist/demo/pb-select-odd.html +1 -1
  87. package/dist/demo/pb-select.html +190 -75
  88. package/dist/demo/pb-select2.html +91 -37
  89. package/dist/demo/pb-select3.html +109 -41
  90. package/dist/demo/pb-svg.html +1 -1
  91. package/dist/demo/pb-table-grid.html +26 -15
  92. package/dist/demo/pb-tabs.html +15 -7
  93. package/dist/demo/pb-tify.html +7 -7
  94. package/dist/demo/pb-timeline.html +1 -1
  95. package/dist/demo/pb-timeline2.html +1 -1
  96. package/dist/demo/pb-toggle-feature.html +26 -23
  97. package/dist/demo/pb-toggle-feature2.html +4 -4
  98. package/dist/demo/pb-toggle-feature3.html +2 -2
  99. package/dist/demo/pb-toggle-feature4.html +56 -54
  100. package/dist/demo/pb-version.html +2 -2
  101. package/dist/demo/pb-view.html +78 -40
  102. package/dist/demo/pb-view2.html +69 -46
  103. package/dist/demo/pb-view3.html +53 -48
  104. package/dist/demo/pb-view4.html +70 -49
  105. package/dist/demo/pb-zoom.html +2 -2
  106. package/dist/{es-global-bridge-d8ce175d.js → es-global-bridge-D8ZcUcx_.js} +0 -4
  107. package/dist/focus-mixin-VCsFap6b.js +768 -0
  108. package/dist/images/icons.svg +217 -0
  109. package/dist/jinn-codemirror-DETLdm08.js +1 -0
  110. package/dist/lib/openseadragon.min.js +80 -0
  111. package/dist/lib/openseadragon.min.js.map +1 -0
  112. package/dist/pb-code-editor.js +25 -20
  113. package/dist/pb-component-docs.js +414 -3225
  114. package/dist/pb-components-bundle.js +3046 -4402
  115. package/dist/pb-dialog-tklYGWfc.js +121 -0
  116. package/dist/pb-edit-app.js +208 -107
  117. package/dist/pb-elements.json +716 -249
  118. package/dist/pb-facsimile.js +46 -0
  119. package/dist/pb-i18n-C0NDma4h.js +1 -0
  120. package/dist/pb-leaflet-map.js +23 -23
  121. package/dist/pb-mei.js +152 -134
  122. package/dist/pb-mixin-DHoWQheB.js +1 -0
  123. package/dist/pb-odd-editor.js +1671 -1231
  124. package/dist/pb-tify.js +1 -27
  125. package/dist/unsafe-html-D5VGo9Oq.js +1 -0
  126. package/dist/urls-BEONu_g4.js +1 -0
  127. package/eslint.config.mjs +92 -0
  128. package/gh-pages.js +5 -3
  129. package/i18n/common/en.json +6 -0
  130. package/i18n/common/pl.json +2 -2
  131. package/images/icons.svg +217 -0
  132. package/index.html +0 -5
  133. package/lib/leaflet-src.js.map +1 -0
  134. package/lib/leaflet.markercluster-src.js.map +1 -0
  135. package/lib/openseadragon.min.js +6 -6
  136. package/package.json +56 -81
  137. package/pb-elements.json +716 -249
  138. package/rollup.config.mjs +312 -0
  139. package/src/assets/components.css +5 -5
  140. package/src/assets/design-system.css +607 -0
  141. package/src/authority/airtable.js +20 -21
  142. package/src/authority/anton.js +129 -129
  143. package/src/authority/custom.js +70 -27
  144. package/src/authority/geonames.js +38 -32
  145. package/src/authority/gnd.js +50 -42
  146. package/src/authority/kbga.js +136 -134
  147. package/src/authority/metagrid.js +44 -46
  148. package/src/authority/reconciliation.js +66 -68
  149. package/src/authority/registry.js +4 -4
  150. package/src/docs/demo-utils.js +91 -0
  151. package/src/docs/pb-component-docs.js +287 -147
  152. package/src/docs/pb-component-view.js +380 -273
  153. package/src/docs/pb-components-list.js +115 -51
  154. package/src/docs/pb-demo-snippet.js +199 -174
  155. package/src/dts-client.js +306 -303
  156. package/src/dts-select-endpoint.js +125 -85
  157. package/src/parse-date-service.js +184 -135
  158. package/src/pb-ajax.js +175 -173
  159. package/src/pb-authority-lookup.js +198 -158
  160. package/src/pb-autocomplete.js +731 -313
  161. package/src/pb-blacklab-highlight.js +266 -260
  162. package/src/pb-blacklab-results.js +230 -225
  163. package/src/pb-browse-docs.js +601 -484
  164. package/src/pb-browse.js +68 -65
  165. package/src/pb-clipboard.js +97 -76
  166. package/src/pb-code-editor.js +111 -103
  167. package/src/pb-code-highlight.js +234 -204
  168. package/src/pb-codepen.js +81 -73
  169. package/src/pb-collapse.js +265 -152
  170. package/src/pb-combo-box.js +191 -191
  171. package/src/pb-components-bundle.js +1 -7
  172. package/src/pb-components.js +2 -6
  173. package/src/pb-custom-form.js +230 -141
  174. package/src/pb-dialog.js +99 -63
  175. package/src/pb-document.js +118 -91
  176. package/src/pb-download.js +214 -198
  177. package/src/pb-drawer.js +146 -149
  178. package/src/pb-edit-app.js +471 -240
  179. package/src/pb-edit-xml.js +101 -98
  180. package/src/pb-events.js +126 -107
  181. package/src/pb-facs-link.js +130 -101
  182. package/src/pb-facsimile.js +494 -410
  183. package/src/pb-fetch.js +389 -0
  184. package/src/pb-formula.js +152 -154
  185. package/src/pb-geolocation.js +130 -132
  186. package/src/pb-grid-action.js +59 -56
  187. package/src/pb-grid.js +388 -228
  188. package/src/pb-highlight.js +142 -142
  189. package/src/pb-hotkeys.js +40 -42
  190. package/src/pb-i18n.js +115 -127
  191. package/src/pb-icon-button.js +108 -0
  192. package/src/pb-icon.js +283 -0
  193. package/src/pb-image-strip.js +85 -79
  194. package/src/pb-lang.js +142 -57
  195. package/src/pb-leaflet-map.js +551 -483
  196. package/src/pb-link.js +132 -126
  197. package/src/pb-load.js +495 -428
  198. package/src/pb-login.js +303 -248
  199. package/src/pb-manage-odds.js +384 -338
  200. package/src/pb-map-icon.js +90 -90
  201. package/src/pb-map-layer.js +86 -86
  202. package/src/pb-markdown.js +107 -110
  203. package/src/pb-media-query.js +75 -73
  204. package/src/pb-mei.js +523 -303
  205. package/src/pb-message.js +144 -98
  206. package/src/pb-mixin.js +268 -265
  207. package/src/pb-navigation.js +83 -96
  208. package/src/pb-observable.js +39 -39
  209. package/src/pb-odd-editor.js +1209 -948
  210. package/src/pb-odd-elementspec-editor.js +375 -310
  211. package/src/pb-odd-model-editor.js +1189 -941
  212. package/src/pb-odd-parameter-editor.js +269 -170
  213. package/src/pb-odd-rendition-editor.js +184 -131
  214. package/src/pb-page.js +451 -422
  215. package/src/pb-paginate.js +260 -178
  216. package/src/pb-panel.js +217 -183
  217. package/src/pb-popover-themes.js +16 -9
  218. package/src/pb-popover.js +297 -288
  219. package/src/pb-print-preview.js +128 -128
  220. package/src/pb-progress.js +52 -52
  221. package/src/pb-repeat.js +141 -108
  222. package/src/pb-restricted.js +85 -78
  223. package/src/pb-search.js +258 -230
  224. package/src/pb-select-feature.js +210 -126
  225. package/src/pb-select-odd.js +184 -118
  226. package/src/pb-select-template.js +113 -78
  227. package/src/pb-select.js +330 -229
  228. package/src/pb-split-list.js +181 -176
  229. package/src/pb-svg.js +81 -80
  230. package/src/pb-table-column.js +55 -55
  231. package/src/pb-table-grid.js +334 -205
  232. package/src/pb-tabs.js +238 -61
  233. package/src/pb-tify.js +3331 -126
  234. package/src/pb-timeline.js +394 -255
  235. package/src/pb-toggle-feature.js +196 -188
  236. package/src/pb-upload.js +201 -176
  237. package/src/pb-version.js +22 -34
  238. package/src/pb-view-annotate.js +138 -102
  239. package/src/pb-view.js +1722 -1272
  240. package/src/pb-zoom.js +144 -46
  241. package/src/search-result-service.js +256 -223
  242. package/src/seed-element.js +14 -22
  243. package/src/settings.js +4 -4
  244. package/src/theming.js +98 -91
  245. package/src/urls.js +403 -289
  246. package/src/utils.js +53 -51
  247. package/vite.config.js +86 -0
  248. package/css/pb-styles.css +0 -51
  249. package/dist/iron-form-3b8dcaa7.js +0 -210
  250. package/dist/jinn-codemirror-da0e2d1f.js +0 -1
  251. package/dist/paper-checkbox-515a5284.js +0 -1597
  252. package/dist/paper-icon-button-b1d31571.js +0 -398
  253. package/dist/paper-listbox-a3b7175c.js +0 -1265
  254. package/dist/pb-i18n-0611135a.js +0 -1
  255. package/dist/pb-mixin-b1caa22e.js +0 -158
  256. package/dist/polymer-hack.js +0 -1
  257. package/dist/vaadin-element-mixin-6e4cee3a.js +0 -527
  258. package/lib/Control.Geocoder.min.js +0 -2
  259. package/lib/Control.Geocoder.min.js.map +0 -1
  260. package/src/assets/pb-styles.css +0 -51
  261. package/src/pb-light-dom.js +0 -40
  262. package/src/polymer-hack.js +0 -6
@@ -1,335 +1,753 @@
1
- import { LitElement, html, css } from 'lit-element';
2
- import { pbMixin, waitOnce } from './pb-mixin';
3
- import { translate } from './pb-i18n';
4
- import '@polymer/paper-input/paper-input.js';
5
- import '@polymer/iron-ajax';
6
- import '@polymer/iron-icon';
7
- import '@cwmr/paper-autocomplete/paper-autocomplete-suggestions.js';
8
-
9
- function _query(datasource, query) {
10
- const queryResult = [];
11
- datasource.forEach((item) => {
12
- let objText, objValue;
13
-
14
- if (typeof item === 'object') {
15
- objText = item.text;
16
- objValue = item.value;
1
+ import { LitElement, html, css, nothing } from 'lit';
2
+ import { classMap } from 'lit/directives/class-map.js';
3
+ import { ifDefined } from 'lit/directives/if-defined.js';
4
+ import { pbMixin, waitOnce } from './pb-mixin.js';
5
+ import { translate } from './pb-i18n.js';
6
+ import './pb-icon.js';
7
+
8
+ let autocompleteId = 0;
9
+
10
+ const KEY_CODES = {
11
+ ENTER: 'Enter',
12
+ ESC: 'Escape',
13
+ ARROW_UP: 'ArrowUp',
14
+ ARROW_DOWN: 'ArrowDown',
15
+ };
16
+
17
+ const FETCH_DEBOUNCE = 200;
18
+
19
+ function normalizeSuggestion(suggestion) {
20
+ if (typeof suggestion === 'object' && suggestion) {
21
+ const text = suggestion.text ?? suggestion.value ?? '';
22
+ const value = suggestion.value ?? suggestion.text ?? '';
23
+ return { text: String(text), value: String(value) };
24
+ }
25
+ const str = suggestion == null ? '' : String(suggestion);
26
+ return { text: str, value: str };
27
+ }
28
+
29
+ function normalizeList(list = []) {
30
+ return Array.isArray(list) ? list.map(normalizeSuggestion) : [];
31
+ }
32
+
33
+ export class PbAutocomplete extends pbMixin(LitElement) {
34
+ static get properties() {
35
+ return {
36
+ ...super.properties,
37
+ name: { type: String },
38
+ value: { type: String },
39
+ placeholder: { type: String, attribute: 'placeholder' },
40
+ label: { type: String },
41
+ source: { type: String },
42
+ preload: { type: Boolean },
43
+ suggestions: { type: Array },
44
+ substring: { type: Boolean },
45
+ icon: { type: String },
46
+ helperText: { type: String, attribute: 'helper-text' },
47
+ alwaysFloatLabel: { type: Boolean, attribute: 'always-float-label' },
48
+ disabled: { type: Boolean, reflect: true },
49
+ requestParams: { type: Object, attribute: false },
50
+ };
51
+ }
52
+
53
+ constructor() {
54
+ super();
55
+ this.placeholder = 'search.placeholder';
56
+ this.label = '';
57
+ this.suggestions = [];
58
+ this.substring = false;
59
+ this.preload = false;
60
+ this.icon = '';
61
+ this.helperText = '';
62
+ this.alwaysFloatLabel = false;
63
+ this.disabled = false;
64
+ this.lastSelected = null;
65
+ this.value = '';
66
+ this.name = '';
67
+ this._hiddenInput = null;
68
+ this._initialized = false;
69
+ this._menuOpen = false;
70
+ this._isFocused = false;
71
+ this._inputValue = '';
72
+ this._filteredSuggestions = [];
73
+ this._highlightedIndex = -1;
74
+ this._loading = false;
75
+ this._pointerSelecting = false;
76
+ this._fetchTimeout = null;
77
+ this._abortController = null;
78
+ this.requestParams = {};
79
+ this._inputId = `pb-autocomplete-input-${++autocompleteId}`;
80
+ }
81
+
82
+ _resolveLabelText() {
83
+ if (this.label) {
84
+ return translate(this.label);
85
+ }
86
+ if (this.placeholder) {
87
+ return translate(this.placeholder);
88
+ }
89
+ return '';
90
+ }
91
+
92
+ _resolveHelperText() {
93
+ return this.helperText ? translate(this.helperText) : '';
94
+ }
95
+
96
+ get _input() {
97
+ return this.shadowRoot?.getElementById(this._inputId) ?? null;
98
+ }
99
+
100
+ connectedCallback() {
101
+ super.connectedCallback();
102
+ this._inputValue = this.value || '';
103
+ }
104
+
105
+ firstUpdated() {
106
+ if (!this._hiddenInput && this.name) {
107
+ this._hiddenInput = document.createElement('input');
108
+ this._hiddenInput.type = 'hidden';
109
+ this._hiddenInput.name = this.name;
110
+ this._hiddenInput.value = this.value || '';
111
+ this.appendChild(this._hiddenInput);
112
+ }
113
+
114
+ waitOnce('pb-page-ready', () => {
115
+ if (this.preload && this.source) {
116
+ this._sendRequest();
117
+ } else if (this.value && this.source) {
118
+ this._sendRequest(this.value);
17
119
  } else {
18
- objText = item.toString();
19
- objValue = objText;
120
+ this._syncInputValueFromSuggestions();
121
+ this._filterSuggestions();
20
122
  }
123
+ });
21
124
 
22
- if (objText.toLowerCase().indexOf(query) > -1) {
23
- // NOTE: the structure of the result object matches with the current template. For custom templates, you
24
- // might need to return more data
25
- const resultItem = {};
26
- resultItem.text = objText;
27
- resultItem.value = objValue;
28
- queryResult.push(resultItem);
125
+ if (!this.source) {
126
+ this._syncInputValueFromSuggestions();
127
+ this._filterSuggestions();
128
+ }
129
+ }
130
+
131
+ disconnectedCallback() {
132
+ super.disconnectedCallback();
133
+ window.clearTimeout(this._fetchTimeout);
134
+ if (this._abortController) {
135
+ this._abortController.abort();
136
+ this._abortController = null;
137
+ }
138
+ }
139
+
140
+ updated(changed) {
141
+ if (changed.has('suggestions')) {
142
+ this._filterSuggestions();
143
+ this._syncInputValueFromSuggestions();
144
+ }
145
+ if (changed.has('value')) {
146
+ this._syncInputValueFromSuggestions();
147
+ if (this._hiddenInput) {
148
+ this._hiddenInput.value = this.value ?? '';
149
+ }
150
+ }
151
+ if (changed.has('name')) {
152
+ if (!this._hiddenInput && this.name) {
153
+ this._hiddenInput = document.createElement('input');
154
+ this._hiddenInput.type = 'hidden';
155
+ this._hiddenInput.name = this.name;
156
+ this._hiddenInput.value = this.value || '';
157
+ this._hiddenInput.disabled = this.disabled;
158
+ this.appendChild(this._hiddenInput);
159
+ } else if (this._hiddenInput) {
160
+ this._hiddenInput.name = this.name;
29
161
  }
162
+ }
163
+ if (changed.has('disabled')) {
164
+ if (this.disabled) {
165
+ this._closeMenu();
166
+ }
167
+ if (this._hiddenInput) {
168
+ this._hiddenInput.disabled = this.disabled;
169
+ }
170
+ }
171
+ }
172
+
173
+ render() {
174
+ const labelText = this._resolveLabelText();
175
+ const helperText = this._resolveHelperText();
176
+ const containerClasses = {
177
+ 'pb-input-container': true,
178
+ 'pb-input-container--focused': this._isFocused,
179
+ 'pb-input-container--filled': !!this._inputValue,
180
+ 'pb-input-container--has-prefix': !!this.icon,
181
+ 'pb-input-container--always-float': this.alwaysFloatLabel,
182
+ 'pb-input-container--disabled': this.disabled,
183
+ };
184
+ const activeId =
185
+ this._menuOpen && this._highlightedIndex >= 0
186
+ ? `pb-autocomplete-option-${this._highlightedIndex}`
187
+ : undefined;
188
+ const suggestions = this._filteredSuggestions;
189
+ const showDropdown = this._menuOpen && (suggestions.length > 0 || this._loading);
190
+ const labelId = labelText ? `${this._inputId}-label` : undefined;
191
+ const helperId = helperText ? `${this._inputId}-helper` : undefined;
192
+ const ariaLabel = !labelId && labelText ? labelText : undefined;
193
+ const placeholderText = labelId || !this.placeholder ? '' : translate(this.placeholder);
194
+
195
+ return html`
196
+ <div class="autocomplete">
197
+ <slot></slot>
198
+ <div class="${classMap(containerClasses)}">
199
+ ${this.icon
200
+ ? html`<pb-icon class="pb-input-prefix" icon="${this.icon}" decorative></pb-icon>`
201
+ : nothing}
202
+ <input
203
+ id="${this._inputId}"
204
+ class="pb-input"
205
+ type="search"
206
+ part="input"
207
+ name="query"
208
+ autocomplete="off"
209
+ .value=${this._inputValue}
210
+ placeholder="${placeholderText}"
211
+ role="combobox"
212
+ aria-autocomplete="list"
213
+ aria-expanded="${this._menuOpen ? 'true' : 'false'}"
214
+ aria-controls="pb-autocomplete-list"
215
+ aria-activedescendant=${ifDefined(activeId)}
216
+ aria-labelledby=${ifDefined(labelId)}
217
+ aria-describedby=${ifDefined(helperId)}
218
+ aria-label=${ifDefined(ariaLabel)}
219
+ ?disabled=${this.disabled}
220
+ @input=${this._handleInput}
221
+ @keydown=${this._handleKeydown}
222
+ @focus=${this._handleFocus}
223
+ @blur=${this._handleBlur}
224
+ />
225
+ ${labelText
226
+ ? html`<label class="pb-input-label" id="${labelId}" for="${this._inputId}">
227
+ ${labelText}
228
+ </label>`
229
+ : nothing}
230
+ </div>
231
+ ${helperText
232
+ ? html`<div class="pb-input-helper" id="${helperId}">${helperText}</div>`
233
+ : nothing}
234
+ ${showDropdown
235
+ ? html`
236
+ <ul
237
+ id="pb-autocomplete-list"
238
+ class="suggestions"
239
+ part="suggestions"
240
+ role="listbox"
241
+ @mousedown=${this._handleSuggestionsPointerDown}
242
+ @mouseup=${this._handleSuggestionsPointerUp}
243
+ >
244
+ ${this._loading
245
+ ? html`<li class="suggestion suggestion--status" role="status">Loading…</li>`
246
+ : nothing}
247
+ ${!this._loading && suggestions.length === 0
248
+ ? html`<li class="suggestion suggestion--status" role="status">No results</li>`
249
+ : suggestions.map((item, index) => this._renderSuggestion(item, index))}
250
+ </ul>
251
+ `
252
+ : nothing}
253
+ </div>
254
+ `;
255
+ }
256
+
257
+ _renderSuggestion(item, index) {
258
+ const isActive = index === this._highlightedIndex;
259
+ return html`
260
+ <li
261
+ id="pb-autocomplete-option-${index}"
262
+ class=${classMap({
263
+ suggestion: true,
264
+ 'suggestion--active': isActive,
265
+ })}
266
+ role="option"
267
+ aria-selected="${isActive ? 'true' : 'false'}"
268
+ @click=${() => this._selectSuggestion(item)}
269
+ @mouseenter=${() => this._setHighlightedIndex(index)}
270
+ >
271
+ ${item.text}
272
+ </li>
273
+ `;
274
+ }
275
+
276
+ _handleInput(event) {
277
+ if (this.disabled) {
278
+ event.preventDefault();
279
+ return;
280
+ }
281
+ const input = event.currentTarget;
282
+ this._inputValue = input.value;
283
+ this.value = input.value;
284
+ if (this._hiddenInput) {
285
+ this._hiddenInput.value = this.value;
286
+ }
287
+ this._filterSuggestions();
288
+ this._menuOpen = true;
289
+ this._highlightedIndex = this._filteredSuggestions.length ? 0 : -1;
290
+ this.requestUpdate();
291
+
292
+ if (this.source && !this.preload) {
293
+ this._scheduleFetch(this.value);
294
+ }
295
+
296
+ this.emitTo('pb-autocomplete-input', {
297
+ text: this._inputValue,
298
+ value: this.value,
30
299
  });
31
- return queryResult;
32
- }
300
+ }
33
301
 
34
- /**
35
- * Provides an input with attached autocomplete. The autocomplete suggestions can be read
36
- * either from a static list or a remote endpoint to which the current user input is sent.
37
- *
38
- * @cssprop --pb-search-label-color - Color of the label and underline
39
- * @cssprop --pb-search-input-color - Text color for input field
40
- * @cssprop --pb-search-focus-color - Color for label and underline if input has focus
41
- * @cssprop --pb-search-suggestions-color - Color for the labels shown in the suggestions dropdown
42
- * @cssprop --pb-search-suggestions-background - Background for the suggestions dropdown
43
- * @slot - default unnamed slot
44
- */
45
- export class PbAutocomplete extends pbMixin(LitElement) {
46
- static get properties() {
47
- return {
48
- ...super.properties,
49
- /**
50
- * Name of the form field which will be submitted
51
- */
52
- name: {
53
- type: String
54
- },
55
- /**
56
- * Value of the form field which will be submitted
57
- */
58
- value: {
59
- type: String
60
- },
61
- /**
62
- * Placeholder to display if field is empty
63
- */
64
- placeholder: {
65
- type: String,
66
- attribute: 'placeholder'
67
- },
68
- /**
69
- * Optional URL to query for suggestions. If relative, it is interpreted
70
- * relative to the endpoint defined on a surrounding `pb-page`.
71
- *
72
- * Upon autocomplete, the current input by the user will be sent with a query parameter
73
- * `query`. The name/values of form controls nested within `pb-autocomplete` will also be
74
- * appended to the request as parameters. This allows the server side code to distinguish
75
- * different states.
76
- */
77
- source: {
78
- type: String
79
- },
80
- /**
81
- * If set, the entire list of possible suggestions will be preloaded upon initialization of the
82
- * component.
83
- */
84
- preload: {
85
- type: Boolean
86
- },
87
- /**
88
- * A static list of suggestions. Use instead of `source`. May either be a flat array of strings,
89
- * or an array containing objects of the form `{"text": "", "value": ""}, in which case "value" denotes
90
- * the value to be used when the enclosing form is submitted, and "text" is the label to be displayed.
91
- */
92
- suggestions: {
93
- type: Array
94
- },
95
- /**
96
- * By default suggestions are filtered by prefix, i.e. only suggestions starting with the prefix
97
- * typed by the user are shown. Set this property to true to search for the user-provided string
98
- * anywhere within the suggestion text.
99
- */
100
- substring: {
101
- type: Boolean
102
- },
103
- /**
104
- * An icon to display next to the input.
105
- */
106
- icon: {
107
- type: String
108
- }
109
- };
110
- }
111
-
112
- constructor() {
113
- super();
114
- this.placeholder = 'search.placeholder';
115
- this.suggestions = [];
116
- this.lastSelected = null;
117
- this.preload = false;
118
- this.substring = false;
119
- this._hiddenInput = null;
120
- this._initialized = false;
121
- }
122
-
123
- connectedCallback() {
124
- super.connectedCallback();
125
- }
126
-
127
- firstUpdated() {
128
- const inIronForm = this.closest('iron-form,pb-search,pb-custom-form');
129
- if (!inIronForm) {
130
- this._hiddenInput = document.createElement('input');
131
- this._hiddenInput.type = 'hidden';
132
- this._hiddenInput.name = this.name;
133
- this.appendChild(this._hiddenInput);
134
- }
302
+ _handleFocus() {
303
+ if (this.disabled) {
304
+ return;
305
+ }
306
+ this._isFocused = true;
307
+ if (this._filteredSuggestions.length) {
308
+ this._menuOpen = true;
309
+ }
310
+ }
135
311
 
136
- const autocomplete = this.shadowRoot.getElementById('autocomplete');
137
- autocomplete.addEventListener('autocomplete-change', this._autocomplete.bind(this));
138
-
139
- if (this.preload && this.source) {
140
- if (this.substring) {
141
- autocomplete.queryFn = _query;
142
- }
143
- waitOnce('pb-page-ready', () => {
144
- this._sendRequest();
145
- });
146
- } else if (this.value) {
147
- if (this.source) {
148
- waitOnce('pb-page-ready', () => {
149
- //console.log('send autocomplete request for remote source %s on value %s', this.source, this.value);
150
- this._sendRequest(this.value);
151
- });
152
- } else {
153
- const input = this.shadowRoot.getElementById('search');
154
- const value = this.suggestions.find((suggestion) => {
155
- if (suggestion.text) {
156
- return suggestion.value === this.value;
157
- }
158
- return suggestion === this.value;
159
- });
160
- if (value) {
161
- input.value = value.text || value;
162
- if (this._hiddenInput) {
163
- this._hiddenInput.value = value.value || value;
164
- }
165
- }
166
- if (this._hiddenInput) {
167
- this._hiddenInput.value = this.value;
168
- }
169
- }
170
- }
312
+ _handleBlur() {
313
+ this._isFocused = false;
314
+ if (this._pointerSelecting) {
315
+ return;
171
316
  }
317
+ window.setTimeout(() => {
318
+ if (!this._isFocused && !this._pointerSelecting) {
319
+ this._closeMenu();
320
+ }
321
+ }, 100);
322
+ }
172
323
 
173
- render() {
174
- return html`
175
- <custom-style>
176
- <style>
177
- :host {
178
- --suggestions-item: {
179
- color: var(--pb-search-suggestions-color, black);
180
- };
181
- --suggestions-wrapper: {
182
- background: var(--pb-search-suggestions-background, white);
183
- };
184
- }
185
- </style>
186
- </custom-style>
187
- <slot></slot>
188
- <paper-input id="search" type="search" name="query" @keyup="${this._setInput}" label="${translate(this.placeholder)}"
189
- always-float-label>
190
- ${this.icon ? html`<iron-icon icon="${this.icon}" slot="prefix"></iron-icon>` : null}
191
- </paper-input>
192
- <paper-autocomplete-suggestions id="autocomplete" for="search" .source="${this.suggestions}" ?remote-source="${!this.preload && this.source}"
193
- @autocomplete-selected="${this._autocompleteSelected}"></paper-autocomplete-suggestions>
194
-
195
- <iron-ajax
196
- id="autocompleteLoader"
197
- verbose
198
- handle-as="json"
199
- method="get"
200
- with-credentials
201
- @response="${this._updateSuggestions}"></iron-ajax>
202
-
203
-
204
- `;
324
+ _handleSuggestionsPointerDown(event) {
325
+ if (this.disabled) {
326
+ return;
327
+ }
328
+ event.preventDefault();
329
+ this._pointerSelecting = true;
330
+ }
205
331
 
332
+ _handleSuggestionsPointerUp() {
333
+ if (this.disabled) {
334
+ return;
206
335
  }
336
+ this._pointerSelecting = false;
337
+ this._input?.focus();
338
+ }
207
339
 
208
- static get styles() {
209
- return css`
210
- :host {
211
- --paper-input-container-color: var(--pb-search-label-color, var(--paper-grey-500, #303030));
212
- --paper-input-container-input-color: var(--pb-search-input-color, var(--pb-color-primary, #000000));
213
- --paper-input-container-focus-color: var(--pb-search-focus-color, var(--paper-grey-500, #303030));
214
-
215
- display: flex;
216
- align-items: center;
217
- }
218
-
219
- ::slotted {
220
- display: block;
221
- margin-left: 10px;
222
- }
223
- `;
224
- }
225
-
226
- _autocomplete(ev) {
227
- const search = this.shadowRoot.getElementById('search');
228
- this._sendRequest(search.value);
229
- }
230
-
231
- _sendRequest(query) {
232
- const loader = this.shadowRoot.getElementById('autocompleteLoader');
233
- loader.url = this.toAbsoluteURL(this.source);
234
-
235
- const params = this._getParameters();
236
- params.query = query;
237
- loader.params = params;
238
- //console.log('send request for %s with %o', loaderId, params);
239
-
240
- loader.generateRequest();
241
- }
242
-
243
-
244
- _updateSuggestions() {
245
- const loader = this.shadowRoot.getElementById('autocompleteLoader');
246
- if (this._initialized) {
247
- const autocomplete = this.shadowRoot.getElementById('autocomplete');
248
- if (loader.lastResponse) {
249
- this.suggestions = loader.lastResponse;
250
- autocomplete.suggestions(this.suggestions);
251
- }
252
- } else if (loader.lastResponse) {
253
- const suggestions = loader.lastResponse;
254
-
255
- const input = this.shadowRoot.getElementById('search');
256
- const value = suggestions.find((suggestion) => {
257
- if (suggestion.text) {
258
- return suggestion.value === this.value;
259
- }
260
- return suggestion === this.value;
261
- });
262
- if (value) {
263
- input.value = value.text || value;
264
- if (this._hiddenInput) {
265
- this._hiddenInput.value = value.value || value;
266
- }
267
- } else if (this._hiddenInput) {
268
- this._hiddenInput.value = this.value;
269
- }
270
-
271
- if (this.preload) {
272
- this.suggestions = suggestions;
273
- }
340
+ _handleKeydown(event) {
341
+ if (this.disabled) {
342
+ return;
343
+ }
344
+ const { key } = event;
345
+ switch (key) {
346
+ case KEY_CODES.ARROW_DOWN:
347
+ event.preventDefault();
348
+ this._openMenu();
349
+ this._moveHighlight(1);
350
+ break;
351
+ case KEY_CODES.ARROW_UP:
352
+ event.preventDefault();
353
+ this._openMenu();
354
+ this._moveHighlight(-1);
355
+ break;
356
+ case KEY_CODES.ENTER:
357
+ if (this._menuOpen) {
358
+ event.preventDefault();
274
359
  }
275
- this._initialized = true;
360
+ this._handleEnter();
361
+ break;
362
+ case KEY_CODES.ESC:
363
+ this._closeMenu();
364
+ break;
365
+ default:
366
+ break;
367
+ }
368
+ }
276
369
 
370
+ _handleEnter() {
371
+ if (this.disabled) {
372
+ return;
373
+ }
374
+ if (this._menuOpen && this._highlightedIndex >= 0) {
375
+ const selected = this._filteredSuggestions[this._highlightedIndex];
376
+ if (selected) {
377
+ this._selectSuggestion(selected);
378
+ return;
379
+ }
277
380
  }
278
381
 
279
- _getParameters() {
280
- const params = {};
281
- const inputs = this.querySelectorAll('[name]');
282
- inputs.forEach((input) => {
283
- params[input.name] = input.value;
284
- });
285
- return params;
382
+ if (!this.value) {
383
+ return;
384
+ }
385
+ const match = this._findSuggestionByValue(this.value);
386
+ if (match) {
387
+ this._selectSuggestion(match);
286
388
  }
389
+ }
287
390
 
288
- _autocompleteSelected(ev) {
289
- const { text, value } = ev.detail;
290
- this.lastSelected = text;
291
- const input = this.shadowRoot.getElementById('search');
292
- input.value = text;
293
- this.value = value;
294
- if (this._hiddenInput) {
295
- this._hiddenInput.value = this.value;
296
- }
391
+ _openMenu() {
392
+ if (this.disabled) {
393
+ return;
394
+ }
395
+ if (!this._menuOpen) {
396
+ this._menuOpen = true;
397
+ this.requestUpdate();
398
+ }
399
+ }
297
400
 
298
- this.emitTo('pb-autocomplete-selected', { text, value });
299
- }
300
-
301
- _setInput(ev) {
302
- const input = this.shadowRoot.getElementById('search');
303
- this.value = input.value;
304
-
305
- if (this._hiddenInput) {
306
- this._hiddenInput.value = this.value;
307
- }
308
-
309
- if (ev.keyCode === 13) {
310
- const entry = this.suggestions.find((suggestion) => {
311
- if (suggestion.text) {
312
- return suggestion.value === this.value;
313
- }
314
- return suggestion === this.value;
315
- });
316
- if (!entry) {
317
- return;
318
- }
319
- if (entry.value) {
320
- this.emitTo('pb-autocomplete-selected', {
321
- text: entry.text,
322
- value: entry.value
323
- });
324
- } else {
325
- this.emitTo('pb-autocomplete-selected', {
326
- text: entry,
327
- value: entry
328
- });
329
- }
330
- }
401
+ _closeMenu() {
402
+ if (this._menuOpen) {
403
+ this._menuOpen = false;
404
+ this._highlightedIndex = -1;
405
+ this.requestUpdate();
406
+ }
407
+ }
408
+
409
+ _moveHighlight(delta) {
410
+ if (this.disabled) {
411
+ return;
412
+ }
413
+ const suggestions = this._filteredSuggestions;
414
+ if (!suggestions.length) {
415
+ this._highlightedIndex = -1;
416
+ this.requestUpdate();
417
+ return;
418
+ }
419
+ let next = this._highlightedIndex + delta;
420
+ if (next < 0) {
421
+ next = suggestions.length - 1;
422
+ } else if (next >= suggestions.length) {
423
+ next = 0;
424
+ }
425
+ this._setHighlightedIndex(next);
426
+ }
427
+
428
+ _setHighlightedIndex(index) {
429
+ this._highlightedIndex = index;
430
+ this.requestUpdate();
431
+ this.updateComplete.then(() => {
432
+ const option = this.shadowRoot?.getElementById(`pb-autocomplete-option-${index}`);
433
+ option?.scrollIntoView({ block: 'nearest' });
434
+ });
435
+ }
436
+
437
+ _selectSuggestion(suggestion) {
438
+ if (this.disabled) {
439
+ return;
440
+ }
441
+ const { text, value } = normalizeSuggestion(suggestion);
442
+ this.lastSelected = text;
443
+ this.value = value;
444
+ this._inputValue = text;
445
+ if (this._hiddenInput) {
446
+ this._hiddenInput.value = value;
447
+ }
448
+ const input = this._input;
449
+ if (input) {
450
+ input.value = text;
331
451
  }
452
+ this._closeMenu();
453
+ this.emitTo('pb-autocomplete-selected', { text, value });
454
+ this.emitTo('pb-autocomplete-input', { text, value });
455
+ }
456
+
457
+ _filterSuggestions() {
458
+ const source = normalizeList(this.suggestions);
459
+ if (!source.length) {
460
+ this._filteredSuggestions = [];
461
+ return;
462
+ }
463
+ const query = (this._inputValue || '').trim().toLowerCase();
464
+ if (!query) {
465
+ this._filteredSuggestions = source;
466
+ return;
467
+ }
468
+ const filterFn = this.substring
469
+ ? suggestion => suggestion.text.toLowerCase().includes(query)
470
+ : suggestion => suggestion.text.toLowerCase().startsWith(query);
471
+ this._filteredSuggestions = source.filter(filterFn);
472
+ }
473
+
474
+ _findSuggestionByValue(value) {
475
+ if (!value) {
476
+ return null;
477
+ }
478
+ const list = normalizeList(this.suggestions);
479
+ const lower = String(value).toLowerCase();
480
+ return (
481
+ list.find(item => item.value.toLowerCase() === lower || item.text.toLowerCase() === lower) ??
482
+ null
483
+ );
484
+ }
485
+
486
+ _syncInputValueFromSuggestions() {
487
+ if (!this.value) {
488
+ this._inputValue = '';
489
+ return;
490
+ }
491
+ const match = this._findSuggestionByValue(this.value);
492
+ if (match) {
493
+ this._inputValue = match.text;
494
+ } else {
495
+ this._inputValue = this.value;
496
+ }
497
+ }
498
+
499
+ _scheduleFetch(query) {
500
+ window.clearTimeout(this._fetchTimeout);
501
+ this._fetchTimeout = window.setTimeout(() => {
502
+ this._fetchTimeout = null;
503
+ this._sendRequest(query);
504
+ }, FETCH_DEBOUNCE);
505
+ }
506
+
507
+ async _sendRequest(query) {
508
+ if (!this.source) {
509
+ return;
510
+ }
511
+ const url = this.toAbsoluteURL(this.source);
512
+ let requestUrl;
513
+ try {
514
+ requestUrl = new URL(url);
515
+ } catch (_) {
516
+ requestUrl = new URL(url, window.location.href);
517
+ }
518
+ const params = {
519
+ ...(this.requestParams || {}),
520
+ ...this._getParameters(),
521
+ };
522
+ if (query !== undefined && query !== null) {
523
+ params.query = query;
524
+ }
525
+ Object.entries(params).forEach(([key, value]) => {
526
+ if (value == null) {
527
+ return;
528
+ }
529
+ if (Array.isArray(value)) {
530
+ value.forEach(item => requestUrl.searchParams.append(key, item));
531
+ } else {
532
+ requestUrl.searchParams.set(key, value);
533
+ }
534
+ });
535
+
536
+ if (this._abortController) {
537
+ this._abortController.abort();
538
+ }
539
+ this._abortController = new AbortController();
540
+ this._loading = true;
541
+ this._menuOpen = true;
542
+ this.requestUpdate();
543
+
544
+ try {
545
+ const response = await fetch(requestUrl.href, {
546
+ method: 'GET',
547
+ credentials: 'include',
548
+ signal: this._abortController.signal,
549
+ headers: {
550
+ Accept: 'application/json',
551
+ },
552
+ });
553
+ if (!response.ok) {
554
+ throw new Error(`Request failed with status ${response.status}`);
555
+ }
556
+ const data = await response.json();
557
+ this._applyRemoteSuggestions(data);
558
+ } catch (error) {
559
+ if (error.name !== 'AbortError') {
560
+ console.error('pb-autocomplete: request failed', error);
561
+ }
562
+ } finally {
563
+ if (this._abortController?.signal.aborted) {
564
+ // The current controller was aborted due to a subsequent request; keep loading state aligned.
565
+ this._loading = false;
566
+ return;
567
+ }
568
+ this._loading = false;
569
+ this._abortController = null;
570
+ this.requestUpdate();
571
+ }
572
+ }
573
+
574
+ _applyRemoteSuggestions(data) {
575
+ if (!Array.isArray(data)) {
576
+ this.suggestions = [];
577
+ this._filteredSuggestions = [];
578
+ this._highlightedIndex = -1;
579
+ this._menuOpen = false;
580
+ this.requestUpdate();
581
+ return;
582
+ }
583
+ this._initialized = true;
584
+ this.suggestions = [...data];
585
+ this._filteredSuggestions = normalizeList(data);
586
+ this._highlightedIndex = this._filteredSuggestions.length ? 0 : -1;
587
+ this._menuOpen = true;
588
+ this._syncInputValueFromSuggestions();
589
+ this.requestUpdate();
590
+ }
591
+
592
+ _getParameters() {
593
+ const params = {};
594
+ this.querySelectorAll('[name]').forEach(input => {
595
+ params[input.name] = input.value;
596
+ });
597
+ return params;
598
+ }
599
+
600
+ static get styles() {
601
+ return css`
602
+ :host {
603
+ display: inline-flex;
604
+ flex-direction: column;
605
+ position: relative;
606
+ min-width: 240px;
607
+ color: inherit;
608
+ }
609
+
610
+ .autocomplete {
611
+ position: relative;
612
+ width: 100%;
613
+ }
614
+
615
+ ::slotted(*) {
616
+ display: block;
617
+ margin-left: 10px;
618
+ }
619
+
620
+ .pb-input-container {
621
+ position: relative;
622
+ display: flex;
623
+ align-items: center;
624
+ gap: var(--pb-input-gap, 0.5rem);
625
+ min-height: var(--pb-input-height, 56px);
626
+ padding: 0 var(--pb-input-padding, 0.75rem);
627
+ border-radius: var(--pb-input-radius, 8px);
628
+ border: 1px solid var(--pb-input-border-color, rgba(0, 0, 0, 0.24));
629
+ background: var(--pb-input-background, #fff);
630
+ transition: border-color 120ms ease, box-shadow 120ms ease, background-color 120ms ease;
631
+ }
632
+
633
+ .pb-input-container--focused {
634
+ border-color: var(--pb-primary-color, #1976d2);
635
+ box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.16);
636
+ }
637
+
638
+ .pb-input-container--disabled {
639
+ background: var(--pb-input-disabled-background, rgba(0, 0, 0, 0.04));
640
+ border-color: var(--pb-input-disabled-border-color, rgba(0, 0, 0, 0.12));
641
+ color: rgba(0, 0, 0, 0.38);
642
+ }
643
+
644
+ .pb-input-container--disabled .pb-input {
645
+ cursor: not-allowed;
646
+ }
647
+
648
+ .pb-input-container--disabled .pb-input-label {
649
+ color: rgba(0, 0, 0, 0.38);
650
+ }
651
+
652
+ .pb-input-prefix {
653
+ color: rgba(0, 0, 0, 0.54);
654
+ }
655
+
656
+ .pb-input {
657
+ flex: 1 1 auto;
658
+ width: 100%;
659
+ height: 100%;
660
+ border: none;
661
+ background: transparent;
662
+ outline: none;
663
+ font: inherit;
664
+ color: inherit;
665
+ padding: 0;
666
+ }
667
+
668
+ .pb-input::placeholder {
669
+ color: var(--pb-input-placeholder-color, rgba(0, 0, 0, 0.4));
670
+ }
671
+
672
+ .pb-input-label {
673
+ position: absolute;
674
+ top: 50%;
675
+ left: var(--pb-input-label-left, 0.75rem);
676
+ transform: translateY(-50%);
677
+ font-size: 0.95rem;
678
+ color: var(--pb-input-label-color, rgba(0, 0, 0, 0.54));
679
+ pointer-events: none;
680
+ transition: transform 120ms ease, font-size 120ms ease, color 120ms ease, top 120ms ease;
681
+ padding: 0 4px;
682
+ background: var(--pb-input-background, #fff);
683
+ line-height: 1;
684
+ }
685
+
686
+ .pb-input-container--has-prefix .pb-input-label {
687
+ left: var(--pb-input-label-left-with-prefix, 2.5rem);
688
+ }
689
+
690
+ .pb-input-container--filled .pb-input-label,
691
+ .pb-input-container--focused .pb-input-label,
692
+ .pb-input-container--always-float .pb-input-label {
693
+ top: 0;
694
+ transform: translateY(-50%) scale(0.88);
695
+ font-size: 0.75rem;
696
+ color: var(--pb-primary-color, #1976d2);
697
+ }
698
+
699
+ .pb-input-helper {
700
+ margin-top: 0.35rem;
701
+ font-size: 0.8rem;
702
+ color: rgba(0, 0, 0, 0.6);
703
+ }
704
+
705
+ .suggestions {
706
+ position: absolute;
707
+ top: calc(100% + 4px);
708
+ left: 0;
709
+ right: 0;
710
+ z-index: 10;
711
+ margin: 0;
712
+ padding: 4px 0;
713
+ list-style: none;
714
+ max-height: 240px;
715
+ overflow-y: auto;
716
+ border-radius: 8px;
717
+ border: 1px solid rgba(0, 0, 0, 0.12);
718
+ background: var(--pb-search-suggestions-background, #fff);
719
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
720
+ }
721
+
722
+ .suggestion {
723
+ padding: 0.5rem 0.75rem;
724
+ cursor: pointer;
725
+ font-size: 0.95rem;
726
+ line-height: 1.2;
727
+ color: var(--pb-search-suggestions-color, rgba(0, 0, 0, 0.8));
728
+ transition: background-color 120ms ease, color 120ms ease;
729
+ }
730
+
731
+ .suggestion:hover,
732
+ .suggestion--active {
733
+ background-color: rgba(25, 118, 210, 0.12);
734
+ color: #0d47a1;
735
+ }
736
+
737
+ .suggestion--status {
738
+ cursor: default;
739
+ color: rgba(0, 0, 0, 0.54);
740
+ }
741
+
742
+ .suggestion--status:hover,
743
+ .suggestion--status.suggestion--active {
744
+ background-color: transparent;
745
+ color: rgba(0, 0, 0, 0.54);
746
+ }
747
+ `;
748
+ }
749
+ }
332
750
 
333
-
751
+ if (!customElements.get('pb-autocomplete')) {
752
+ customElements.define('pb-autocomplete', PbAutocomplete);
334
753
  }
335
- customElements.define('pb-autocomplete', PbAutocomplete);