@internetarchive/collection-browser 3.4.1-alpha-webdev7761.2 → 3.4.1-alpha-webdev7761.4

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 (208) hide show
  1. package/dist/src/app-root.js +19 -28
  2. package/dist/src/app-root.js.map +1 -1
  3. package/dist/src/collection-browser.d.ts +14 -10
  4. package/dist/src/collection-browser.js +870 -886
  5. package/dist/src/collection-browser.js.map +1 -1
  6. package/dist/src/collection-facets/facet-row.js +3 -4
  7. package/dist/src/collection-facets/facet-row.js.map +1 -1
  8. package/dist/src/collection-facets/models.js.map +1 -1
  9. package/dist/src/collection-facets/more-facets-content.js +145 -156
  10. package/dist/src/collection-facets/more-facets-content.js.map +1 -1
  11. package/dist/src/collection-facets/more-facets-pagination.js +6 -10
  12. package/dist/src/collection-facets/more-facets-pagination.js.map +1 -1
  13. package/dist/src/collection-facets/smart-facets/heuristics/wikidata/wikidata-heuristic.js +16 -21
  14. package/dist/src/collection-facets/smart-facets/heuristics/wikidata/wikidata-heuristic.js.map +1 -1
  15. package/dist/src/collection-facets/smart-facets/smart-facet-bar.js +7 -10
  16. package/dist/src/collection-facets/smart-facets/smart-facet-bar.js.map +1 -1
  17. package/dist/src/collection-facets/smart-facets/smart-facet-button.js +3 -2
  18. package/dist/src/collection-facets/smart-facets/smart-facet-button.js.map +1 -1
  19. package/dist/src/collection-facets/smart-facets/smart-facet-dropdown.js +9 -11
  20. package/dist/src/collection-facets/smart-facets/smart-facet-dropdown.js.map +1 -1
  21. package/dist/src/collection-facets/smart-facets/smart-facet-heuristics.js +7 -7
  22. package/dist/src/collection-facets/smart-facets/smart-facet-heuristics.js.map +1 -1
  23. package/dist/src/collection-facets/toggle-switch.js +4 -6
  24. package/dist/src/collection-facets/toggle-switch.js.map +1 -1
  25. package/dist/src/collection-facets.js +34 -50
  26. package/dist/src/collection-facets.js.map +1 -1
  27. package/dist/src/combo-box/caret-closed.js +5 -11
  28. package/dist/src/combo-box/caret-closed.js.map +1 -1
  29. package/dist/src/combo-box/caret-open.js +5 -11
  30. package/dist/src/combo-box/caret-open.js.map +1 -1
  31. package/dist/src/combo-box/clear.d.ts +2 -0
  32. package/dist/src/combo-box/clear.js +11 -0
  33. package/dist/src/combo-box/clear.js.map +1 -0
  34. package/dist/src/combo-box/ia-combo-box.d.ts +40 -9
  35. package/dist/src/combo-box/ia-combo-box.js +363 -272
  36. package/dist/src/combo-box/ia-combo-box.js.map +1 -1
  37. package/dist/src/combo-box/models.d.ts +14 -0
  38. package/dist/src/combo-box/models.js +32 -1
  39. package/dist/src/combo-box/models.js.map +1 -1
  40. package/dist/src/data-source/collection-browser-data-source.js +35 -47
  41. package/dist/src/data-source/collection-browser-data-source.js.map +1 -1
  42. package/dist/src/empty-placeholder.js +19 -18
  43. package/dist/src/empty-placeholder.js.map +1 -1
  44. package/dist/src/expanded-date-picker.js +6 -10
  45. package/dist/src/expanded-date-picker.js.map +1 -1
  46. package/dist/src/language-code-handler/language-code-handler.js +2 -2
  47. package/dist/src/language-code-handler/language-code-handler.js.map +1 -1
  48. package/dist/src/manage/manage-bar.js +86 -92
  49. package/dist/src/manage/manage-bar.js.map +1 -1
  50. package/dist/src/manage/remove-items-modal-content.js +2 -2
  51. package/dist/src/manage/remove-items-modal-content.js.map +1 -1
  52. package/dist/src/models.js +36 -40
  53. package/dist/src/models.js.map +1 -1
  54. package/dist/src/restoration-state-handler.js +9 -10
  55. package/dist/src/restoration-state-handler.js.map +1 -1
  56. package/dist/src/sort-filter-bar/alpha-bar.js +9 -14
  57. package/dist/src/sort-filter-bar/alpha-bar.js.map +1 -1
  58. package/dist/src/sort-filter-bar/sort-filter-bar.js +14 -24
  59. package/dist/src/sort-filter-bar/sort-filter-bar.js.map +1 -1
  60. package/dist/src/tiles/base-tile-component.js +1 -2
  61. package/dist/src/tiles/base-tile-component.js.map +1 -1
  62. package/dist/src/tiles/grid/account-tile.js +36 -38
  63. package/dist/src/tiles/grid/account-tile.js.map +1 -1
  64. package/dist/src/tiles/grid/collection-tile.js +79 -82
  65. package/dist/src/tiles/grid/collection-tile.js.map +1 -1
  66. package/dist/src/tiles/grid/item-tile.js +154 -164
  67. package/dist/src/tiles/grid/item-tile.js.map +1 -1
  68. package/dist/src/tiles/grid/search-tile.js +42 -43
  69. package/dist/src/tiles/grid/search-tile.js.map +1 -1
  70. package/dist/src/tiles/grid/styles/tile-grid-shared-styles.js +119 -119
  71. package/dist/src/tiles/grid/styles/tile-grid-shared-styles.js.map +1 -1
  72. package/dist/src/tiles/grid/tile-stats.js +2 -3
  73. package/dist/src/tiles/grid/tile-stats.js.map +1 -1
  74. package/dist/src/tiles/hover/hover-pane-controller.js +42 -49
  75. package/dist/src/tiles/hover/hover-pane-controller.js.map +1 -1
  76. package/dist/src/tiles/hover/tile-hover-pane.js +113 -114
  77. package/dist/src/tiles/hover/tile-hover-pane.js.map +1 -1
  78. package/dist/src/tiles/image-block.js +5 -8
  79. package/dist/src/tiles/image-block.js.map +1 -1
  80. package/dist/src/tiles/item-image.js +12 -19
  81. package/dist/src/tiles/item-image.js.map +1 -1
  82. package/dist/src/tiles/list/tile-list-compact.js +114 -122
  83. package/dist/src/tiles/list/tile-list-compact.js.map +1 -1
  84. package/dist/src/tiles/list/tile-list.js +326 -347
  85. package/dist/src/tiles/list/tile-list.js.map +1 -1
  86. package/dist/src/tiles/overlay/icon-overlay.js +1 -2
  87. package/dist/src/tiles/overlay/icon-overlay.js.map +1 -1
  88. package/dist/src/tiles/overlay/text-overlay.js +2 -4
  89. package/dist/src/tiles/overlay/text-overlay.js.map +1 -1
  90. package/dist/src/tiles/text-snippet-block.js +2 -4
  91. package/dist/src/tiles/text-snippet-block.js.map +1 -1
  92. package/dist/src/tiles/tile-dispatcher.js +233 -241
  93. package/dist/src/tiles/tile-dispatcher.js.map +1 -1
  94. package/dist/src/tiles/tile-display-value-provider.js +5 -9
  95. package/dist/src/tiles/tile-display-value-provider.js.map +1 -1
  96. package/dist/src/tiles/tile-mediatype-icon.js +12 -19
  97. package/dist/src/tiles/tile-mediatype-icon.js.map +1 -1
  98. package/dist/src/utils/collapse-repeated-quotes.js +1 -1
  99. package/dist/src/utils/collapse-repeated-quotes.js.map +1 -1
  100. package/dist/src/utils/facet-utils.js +3 -5
  101. package/dist/src/utils/facet-utils.js.map +1 -1
  102. package/dist/src/utils/format-count.js +10 -10
  103. package/dist/src/utils/format-count.js.map +1 -1
  104. package/dist/src/utils/format-date.js.map +1 -1
  105. package/dist/src/utils/resolve-mediatype.js +2 -3
  106. package/dist/src/utils/resolve-mediatype.js.map +1 -1
  107. package/dist/test/collection-browser.test.js +131 -185
  108. package/dist/test/collection-browser.test.js.map +1 -1
  109. package/dist/test/collection-facets/facet-row.test.js +60 -75
  110. package/dist/test/collection-facets/facet-row.test.js.map +1 -1
  111. package/dist/test/collection-facets/facets-template.test.js +17 -23
  112. package/dist/test/collection-facets/facets-template.test.js.map +1 -1
  113. package/dist/test/collection-facets/more-facets-content.test.js +22 -32
  114. package/dist/test/collection-facets/more-facets-content.test.js.map +1 -1
  115. package/dist/test/collection-facets/more-facets-pagination.test.js +16 -22
  116. package/dist/test/collection-facets/more-facets-pagination.test.js.map +1 -1
  117. package/dist/test/collection-facets/toggle-switch.test.js +22 -19
  118. package/dist/test/collection-facets/toggle-switch.test.js.map +1 -1
  119. package/dist/test/collection-facets.test.js +80 -97
  120. package/dist/test/collection-facets.test.js.map +1 -1
  121. package/dist/test/empty-placeholder.test.js +11 -17
  122. package/dist/test/empty-placeholder.test.js.map +1 -1
  123. package/dist/test/expanded-date-picker.test.js +8 -14
  124. package/dist/test/expanded-date-picker.test.js.map +1 -1
  125. package/dist/test/icon-overlay.test.js +7 -6
  126. package/dist/test/icon-overlay.test.js.map +1 -1
  127. package/dist/test/image-block.test.js +16 -26
  128. package/dist/test/image-block.test.js.map +1 -1
  129. package/dist/test/item-image.test.js +23 -32
  130. package/dist/test/item-image.test.js.map +1 -1
  131. package/dist/test/manage/manage-bar.test.js +21 -33
  132. package/dist/test/manage/manage-bar.test.js.map +1 -1
  133. package/dist/test/manage/remove-items-modal-content.test.js +10 -15
  134. package/dist/test/manage/remove-items-modal-content.test.js.map +1 -1
  135. package/dist/test/mocks/mock-search-service.js +2 -3
  136. package/dist/test/mocks/mock-search-service.js.map +1 -1
  137. package/dist/test/restoration-state-handler.test.js +13 -21
  138. package/dist/test/restoration-state-handler.test.js.map +1 -1
  139. package/dist/test/review-block.test.js +16 -18
  140. package/dist/test/review-block.test.js.map +1 -1
  141. package/dist/test/sort-filter-bar/alpha-bar-tooltip.test.js +2 -3
  142. package/dist/test/sort-filter-bar/alpha-bar-tooltip.test.js.map +1 -1
  143. package/dist/test/sort-filter-bar/alpha-bar.test.js +18 -24
  144. package/dist/test/sort-filter-bar/alpha-bar.test.js.map +1 -1
  145. package/dist/test/sort-filter-bar/sort-filter-bar.test.js +178 -180
  146. package/dist/test/sort-filter-bar/sort-filter-bar.test.js.map +1 -1
  147. package/dist/test/text-overlay.test.js +16 -15
  148. package/dist/test/text-overlay.test.js.map +1 -1
  149. package/dist/test/text-snippet-block.test.js +14 -19
  150. package/dist/test/text-snippet-block.test.js.map +1 -1
  151. package/dist/test/tile-stats.test.js +73 -34
  152. package/dist/test/tile-stats.test.js.map +1 -1
  153. package/dist/test/tiles/grid/account-tile.test.js +25 -25
  154. package/dist/test/tiles/grid/account-tile.test.js.map +1 -1
  155. package/dist/test/tiles/grid/collection-tile.test.js +13 -19
  156. package/dist/test/tiles/grid/collection-tile.test.js.map +1 -1
  157. package/dist/test/tiles/grid/item-tile.test.js +141 -168
  158. package/dist/test/tiles/grid/item-tile.test.js.map +1 -1
  159. package/dist/test/tiles/grid/search-tile.test.js +9 -13
  160. package/dist/test/tiles/grid/search-tile.test.js.map +1 -1
  161. package/dist/test/tiles/hover/hover-pane-controller.test.js +50 -62
  162. package/dist/test/tiles/hover/hover-pane-controller.test.js.map +1 -1
  163. package/dist/test/tiles/hover/tile-hover-pane.test.js +12 -16
  164. package/dist/test/tiles/hover/tile-hover-pane.test.js.map +1 -1
  165. package/dist/test/tiles/list/tile-list-compact.test.js +104 -118
  166. package/dist/test/tiles/list/tile-list-compact.test.js.map +1 -1
  167. package/dist/test/tiles/list/tile-list.test.js +202 -231
  168. package/dist/test/tiles/list/tile-list.test.js.map +1 -1
  169. package/dist/test/tiles/tile-dispatcher.test.js +97 -110
  170. package/dist/test/tiles/tile-dispatcher.test.js.map +1 -1
  171. package/dist/test/tiles/tile-mediatype-icon.test.js +12 -24
  172. package/dist/test/tiles/tile-mediatype-icon.test.js.map +1 -1
  173. package/dist/test/utils/format-date.test.js.map +1 -1
  174. package/index.html +1 -1
  175. package/package.json +5 -3
  176. package/src/collection-browser.ts +3060 -3030
  177. package/src/collection-facets/models.ts +10 -10
  178. package/src/collection-facets/more-facets-content.ts +639 -639
  179. package/src/collection-facets.ts +1 -1
  180. package/src/combo-box/caret-closed.ts +5 -11
  181. package/src/combo-box/caret-open.ts +5 -11
  182. package/src/combo-box/clear.ts +11 -0
  183. package/src/combo-box/ia-combo-box.ts +1288 -1180
  184. package/src/combo-box/models.ts +31 -1
  185. package/src/manage/manage-bar.ts +247 -247
  186. package/src/restoration-state-handler.ts +5 -1
  187. package/src/tiles/base-tile-component.ts +65 -65
  188. package/src/tiles/grid/account-tile.ts +113 -113
  189. package/src/tiles/grid/collection-tile.ts +163 -163
  190. package/src/tiles/grid/item-tile.ts +340 -340
  191. package/src/tiles/grid/search-tile.ts +90 -90
  192. package/src/tiles/grid/styles/tile-grid-shared-styles.ts +130 -130
  193. package/src/tiles/hover/hover-pane-controller.ts +613 -613
  194. package/src/tiles/hover/tile-hover-pane.ts +184 -184
  195. package/src/tiles/list/tile-list-compact.ts +239 -239
  196. package/src/tiles/list/tile-list.ts +700 -700
  197. package/src/tiles/tile-dispatcher.ts +517 -517
  198. package/src/utils/format-date.ts +62 -62
  199. package/test/collection-facets/facet-row.test.ts +375 -375
  200. package/test/collection-facets.test.ts +928 -928
  201. package/test/tiles/grid/item-tile.test.ts +520 -520
  202. package/test/tiles/hover/hover-pane-controller.test.ts +418 -418
  203. package/test/tiles/list/tile-list-compact.test.ts +282 -282
  204. package/test/tiles/list/tile-list.test.ts +552 -552
  205. package/test/tiles/tile-dispatcher.test.ts +283 -283
  206. package/test/utils/format-date.test.ts +89 -89
  207. package/tsconfig.json +8 -3
  208. package/vite.config.ts +29 -22
@@ -1,43 +1,15 @@
1
1
  import { __decorate } from "tslib";
2
- import { html, LitElement, css, } from 'lit';
2
+ import { html, LitElement, nothing, css, } from 'lit';
3
3
  import { customElement, property, state, query } from 'lit/decorators.js';
4
4
  import { classMap } from 'lit/directives/class-map.js';
5
5
  import { ifDefined } from 'lit/directives/if-defined.js';
6
6
  import { live } from 'lit/directives/live.js';
7
7
  import { when } from 'lit/directives/when.js';
8
8
  import { msg } from '@lit/localize';
9
- import { hasAnyOf, } from './models';
10
- import caretClosed from './caret-closed';
11
- import caretOpen from './caret-open';
12
- /**
13
- * Tests whether the given `haystack` string has the given `needle` as a subsequence.
14
- * Returns `true` if the characters of `needle` appear in order within `haystack`,
15
- * regardless of whether they are contiguous. Returns `false` otherwise.
16
- *
17
- * E.g., `ace` is a subsequence of `archive` (but not a contiguous substring).
18
- *
19
- * Note: The empty string is a subsequence of any string, including itself.
20
- *
21
- * @param needle The potential subsequence to check for inside `haystack`.
22
- * @param haystack The string to be tested for containing the `needle` subsequence.
23
- * @returns Whether `haystack` has `needle` as a subsequence.
24
- */
25
- const isSubsequence = (needle, haystack) => {
26
- const needleLen = needle.length;
27
- const haystackLen = haystack.length;
28
- if (needleLen === 0)
29
- return true;
30
- let needleIdx = 0;
31
- let haystackIdx = 0;
32
- while (haystackIdx < haystackLen) {
33
- if (haystack[haystackIdx] === needle[needleIdx])
34
- needleIdx += 1;
35
- if (needleIdx >= needleLen)
36
- return true;
37
- haystackIdx += 1;
38
- }
39
- return false;
40
- };
9
+ import { hasAnyOf, isSubsequence, } from './models';
10
+ import caretClosedIcon from './caret-closed';
11
+ import caretOpenIcon from './caret-open';
12
+ import clearIcon from './clear';
41
13
  /**
42
14
  * Map from filter preset keys to their associated filtering function.
43
15
  * @see {@linkcode IAComboBox.filter}
@@ -59,6 +31,7 @@ const STRING_LOWER_CASE_FN = (str) => str.toLocaleLowerCase();
59
31
  * freeform text to filter down & find specific options.
60
32
  */
61
33
  let IAComboBox = class IAComboBox extends LitElement {
34
+ static { this.formAssociated = true; }
62
35
  constructor() {
63
36
  super();
64
37
  /**
@@ -119,7 +92,8 @@ let IAComboBox = class IAComboBox extends LitElement {
119
92
  */
120
93
  this.caseSensitive = false;
121
94
  /**
122
- * Whether the filtered options should be listed in lexicographically-sorted order.
95
+ * Whether the filtered options should be listed in lexicographically-sorted order,
96
+ * respecting the current `caseSensitive` setting.
123
97
  * Default is `false`, displaying them in the same order as the provided options array.
124
98
  */
125
99
  this.sort = false;
@@ -141,6 +115,11 @@ let IAComboBox = class IAComboBox extends LitElement {
141
115
  * Default is `false`, closing the options list when a selection is made.
142
116
  */
143
117
  this.stayOpen = false;
118
+ /**
119
+ * Whether the combo box shows a clear button when a value is selected.
120
+ * Default is `false`.
121
+ */
122
+ this.clearable = false;
144
123
  /**
145
124
  * Whether the combo box's option menu is currently expanded. Default is `false`.
146
125
  */
@@ -213,22 +192,20 @@ let IAComboBox = class IAComboBox extends LitElement {
213
192
  this.internals = this.attachInternals();
214
193
  }
215
194
  render() {
216
- return html `
217
- <div
218
- id="container"
219
- class=${classMap({ focused: this.hasFocus })}
220
- part="container"
221
- >
222
- ${this.labelTemplate}
223
- <div
224
- id="main-widget-row"
225
- class=${classMap({ disabled: this.disabled })}
226
- part="combo-box"
227
- >
228
- ${this.textInputTemplate} ${this.caretButtonTemplate}
229
- </div>
230
- ${this.optionsListTemplate}
231
- </div>
195
+ const mainWidgetClasses = classMap({
196
+ disabled: this.disabled,
197
+ focused: this.hasFocus,
198
+ });
199
+ return html `
200
+ <div id="container" part="container">
201
+ ${this.labelTemplate}
202
+ <div id="main-widget-row" class=${mainWidgetClasses} part="combo-box">
203
+ ${this.textInputTemplate}
204
+ ${this.clearable ? this.clearButtonTemplate : nothing}
205
+ ${this.caretButtonTemplate}
206
+ </div>
207
+ ${this.optionsListTemplate}
208
+ </div>
232
209
  `;
233
210
  }
234
211
  willUpdate(changed) {
@@ -276,15 +253,14 @@ let IAComboBox = class IAComboBox extends LitElement {
276
253
  }
277
254
  }
278
255
  updated(changed) {
279
- var _a, _b, _c, _d;
280
256
  if (changed.has('open')) {
281
257
  if (this.open) {
282
258
  this.positionOptionsMenu();
283
- (_b = (_a = this.optionsList).showPopover) === null || _b === void 0 ? void 0 : _b.call(_a);
259
+ this.optionsList.showPopover?.();
284
260
  this.optionsList.classList.add('visible');
285
261
  }
286
262
  else {
287
- (_d = (_c = this.optionsList).hidePopover) === null || _d === void 0 ? void 0 : _d.call(_c);
263
+ this.optionsList.hidePopover?.();
288
264
  this.optionsList.classList.remove('visible');
289
265
  }
290
266
  }
@@ -295,42 +271,65 @@ let IAComboBox = class IAComboBox extends LitElement {
295
271
  /**
296
272
  * Template for the main label for the combo box.
297
273
  *
298
- * Uses the contents of the default (unnamed) slot as the label text.
274
+ * Uses the contents of the `label` named slot as the label text.
299
275
  */
300
276
  get labelTemplate() {
301
- return html `<label id="label" for="text-input"><slot></slot></label>`;
277
+ return html `
278
+ <label id="label" for="text-input">
279
+ <slot name="label"></slot>
280
+ </label>
281
+ `;
302
282
  }
303
283
  /**
304
284
  * Template for the text input field that users can edit to filter the available
305
285
  * options or (if freeform behavior) to enter a custom value.
306
286
  */
307
287
  get textInputTemplate() {
308
- var _a;
309
288
  const textInputClasses = classMap({
310
- editable: this.behavior !== 'select-only',
289
+ 'clear-padding': this.clearable && !this.shouldShowClearButton,
311
290
  });
312
- return html `
313
- <input
314
- type="text"
315
- id="text-input"
316
- class=${textInputClasses}
317
- .value=${live(this.enteredText)}
318
- placeholder=${ifDefined(this.placeholder)}
319
- part="text-input"
320
- role="combobox"
321
- autocomplete="off"
322
- aria-autocomplete="list"
323
- aria-controls="options-list"
324
- aria-expanded=${this.open}
325
- aria-activedescendant=${ifDefined((_a = this.highlightedOption) === null || _a === void 0 ? void 0 : _a.id)}
326
- ?disabled=${this.disabled}
327
- ?required=${this.required}
328
- @click=${this.handleComboBoxClick}
329
- @keydown=${this.handleComboBoxKeyDown}
330
- @input=${this.handleTextBoxInput}
331
- @focus=${this.handleFocus}
332
- @blur=${this.handleBlur}
333
- />
291
+ return html `
292
+ <input
293
+ type="text"
294
+ id="text-input"
295
+ class=${textInputClasses}
296
+ .value=${live(this.enteredText)}
297
+ placeholder=${ifDefined(this.placeholder)}
298
+ part="text-input"
299
+ role="combobox"
300
+ autocomplete="off"
301
+ aria-autocomplete="list"
302
+ aria-controls="options-list"
303
+ aria-expanded=${this.open}
304
+ aria-activedescendant=${ifDefined(this.highlightedOption?.id)}
305
+ ?readonly=${this.behavior === 'select-only'}
306
+ ?disabled=${this.disabled}
307
+ ?required=${this.required}
308
+ @click=${this.handleComboBoxClick}
309
+ @keydown=${this.handleComboBoxKeyDown}
310
+ @input=${this.handleTextBoxInput}
311
+ @focus=${this.handleFocus}
312
+ @blur=${this.handleBlur}
313
+ />
314
+ `;
315
+ }
316
+ /**
317
+ * Template for the clear button that is shown when the `clearable` property
318
+ * is true.
319
+ */
320
+ get clearButtonTemplate() {
321
+ return html `
322
+ <button
323
+ type="button"
324
+ id="clear-button"
325
+ part="clear-button"
326
+ tabindex="-1"
327
+ ?hidden=${!this.shouldShowClearButton}
328
+ @click=${this.handleClearButtonClick}
329
+ >
330
+ <span class="sr-only">${msg('Clear')}</span>
331
+ <slot name="clear-button"> ${clearIcon} </slot>
332
+ </button>
334
333
  `;
335
334
  }
336
335
  /**
@@ -338,51 +337,53 @@ let IAComboBox = class IAComboBox extends LitElement {
338
337
  * The icons are wrapped in named slots to allow consumers to override them.
339
338
  */
340
339
  get caretTemplate() {
341
- return html `
342
- <slot name="caret-closed" ?hidden=${this.open}> ${caretClosed} </slot>
343
- <slot name="caret-open" ?hidden=${!this.open}> ${caretOpen} </slot>
340
+ return html `
341
+ <slot name="caret-closed" ?hidden=${this.open}> ${caretClosedIcon} </slot>
342
+ <slot name="caret-open" ?hidden=${!this.open}> ${caretOpenIcon} </slot>
344
343
  `;
345
344
  }
346
345
  /**
347
346
  * Template for the caret button to be shown beside the text input.
348
347
  */
349
348
  get caretButtonTemplate() {
350
- return html `
351
- <button
352
- type="button"
353
- id="caret-button"
354
- part="caret-button"
355
- tabindex="-1"
356
- aria-controls="options-list"
357
- aria-expanded=${this.open}
358
- ?disabled=${this.disabled}
359
- @click=${this.handleComboBoxClick}
360
- @keydown=${this.handleComboBoxKeyDown}
361
- @focus=${this.handleFocus}
362
- @blur=${this.handleBlur}
363
- >
364
- ${this.caretTemplate}
365
- </button>
349
+ return html `
350
+ <button
351
+ type="button"
352
+ id="caret-button"
353
+ part="caret-button"
354
+ tabindex="-1"
355
+ aria-controls="options-list"
356
+ aria-expanded=${this.open}
357
+ ?disabled=${this.disabled}
358
+ @click=${this.handleComboBoxClick}
359
+ @keydown=${this.handleComboBoxKeyDown}
360
+ @focus=${this.handleFocus}
361
+ @blur=${this.handleBlur}
362
+ >
363
+ <span class="sr-only">${msg('Toggle options')}</span>
364
+ ${this.caretTemplate}
365
+ </button>
366
366
  `;
367
367
  }
368
368
  /**
369
369
  * Template for the options list that is displayed when the combo box is open.
370
370
  */
371
371
  get optionsListTemplate() {
372
- return html `
373
- <ul
374
- id="options-list"
375
- part="options-list"
376
- role="listbox"
377
- popover
378
- ?hidden=${!this.open}
379
- @focus=${this.handleFocus}
380
- @blur=${this.handleBlur}
381
- >
382
- <slot name="options-list-top"></slot>
383
- ${when(this.open, () => this.optionTemplates)}
384
- <slot name="options-list-bottom"></slot>
385
- </ul>
372
+ return html `
373
+ <ul
374
+ id="options-list"
375
+ part="options-list"
376
+ role="listbox"
377
+ tabindex="-1"
378
+ popover
379
+ ?hidden=${!this.open}
380
+ @focus=${this.handleFocus}
381
+ @blur=${this.handleBlur}
382
+ >
383
+ <slot name="options-list-top"></slot>
384
+ ${when(this.open, () => this.optionTemplates)}
385
+ <slot name="options-list-bottom"></slot>
386
+ </ul>
386
387
  `;
387
388
  }
388
389
  /**
@@ -394,26 +395,26 @@ let IAComboBox = class IAComboBox extends LitElement {
394
395
  return [this.emptyOptionsTemplate];
395
396
  }
396
397
  // Otherwise build a list item for each filtered option
397
- return this.filteredOptions.map((opt) => {
398
- var _a;
398
+ return this.filteredOptions.map(opt => {
399
399
  const optionClasses = classMap({
400
400
  option: true,
401
401
  highlight: opt === this.highlightedOption,
402
402
  });
403
- return html `
404
- <li
405
- id=${opt.id}
406
- class=${optionClasses}
407
- part="option"
408
- tabindex="-1"
409
- @pointerenter=${this.handleOptionPointerEnter}
410
- @pointermove=${this.handleOptionPointerMove}
411
- @click=${this.handleOptionClick}
412
- @focus=${this.handleFocus}
413
- @blur=${this.handleBlur}
414
- >
415
- ${(_a = opt.content) !== null && _a !== void 0 ? _a : opt.text}
416
- </li>
403
+ return html `
404
+ <li
405
+ id=${opt.id}
406
+ class=${optionClasses}
407
+ part="option"
408
+ role="option"
409
+ tabindex="-1"
410
+ @pointerenter=${this.handleOptionPointerEnter}
411
+ @pointermove=${this.handleOptionPointerMove}
412
+ @click=${this.handleOptionClick}
413
+ @focus=${this.handleFocus}
414
+ @blur=${this.handleBlur}
415
+ >
416
+ ${opt.content ?? opt.text}
417
+ </li>
417
418
  `;
418
419
  });
419
420
  }
@@ -422,10 +423,10 @@ let IAComboBox = class IAComboBox extends LitElement {
422
423
  * Renders within an `empty-options` named slot so that its content can be customized.
423
424
  */
424
425
  get emptyOptionsTemplate() {
425
- return html `
426
- <li id="empty-options" part="empty-options">
427
- <slot name="empty-options">${msg('No matching options')}</slot>
428
- </li>
426
+ return html `
427
+ <li id="empty-options" part="empty-options">
428
+ <slot name="empty-options">${msg('No matching options')}</slot>
429
+ </li>
429
430
  `;
430
431
  }
431
432
  //
@@ -441,7 +442,7 @@ let IAComboBox = class IAComboBox extends LitElement {
441
442
  * Handler for when the pointer device is moved within an option in the dropdown.
442
443
  */
443
444
  handleOptionPointerMove(e) {
444
- const target = e.target;
445
+ const target = e.currentTarget;
445
446
  const option = this.getOptionFor(target.id);
446
447
  if (option)
447
448
  this.setHighlightedOption(option);
@@ -450,7 +451,7 @@ let IAComboBox = class IAComboBox extends LitElement {
450
451
  * Handler for when the user clicks on an option in the dropdown.
451
452
  */
452
453
  handleOptionClick(e) {
453
- const target = e.target;
454
+ const target = e.currentTarget;
454
455
  const option = this.getOptionFor(target.id);
455
456
  if (option) {
456
457
  this.setSelectedOption(option.id);
@@ -485,10 +486,16 @@ let IAComboBox = class IAComboBox extends LitElement {
485
486
  }
486
487
  break;
487
488
  case 'Tab':
488
- this.textInput.focus();
489
- return;
489
+ this.handleTabPressed();
490
+ return; // Never cancel the default behavior for Tab
491
+ case ' ':
492
+ this.handleSpacePressed();
493
+ // In the specific case of picking an option in select-only, we skip the defaults
494
+ if (this.behavior === 'select-only' && this.highlightedOption)
495
+ break;
496
+ return; // Otherwise, don't cancel the default behavior
490
497
  default:
491
- // Do nothing and allow propagation otherwise
498
+ // Do nothing and allow propagation for all other keys
492
499
  return;
493
500
  }
494
501
  e.stopPropagation();
@@ -567,20 +574,46 @@ let IAComboBox = class IAComboBox extends LitElement {
567
574
  handleAltDownArrowPressed() {
568
575
  this.openOptionsMenu();
569
576
  }
577
+ /**
578
+ * Handler for when the Tab key is pressed
579
+ */
580
+ handleTabPressed() {
581
+ if (this.highlightedOption) {
582
+ this.setSelectedOption(this.highlightedOption.id);
583
+ if (!this.stayOpen)
584
+ this.open = false;
585
+ }
586
+ }
587
+ /**
588
+ * Handler for when the Space key is pressed
589
+ */
590
+ handleSpacePressed() {
591
+ if (this.behavior === 'select-only' && this.highlightedOption) {
592
+ this.setSelectedOption(this.highlightedOption.id);
593
+ if (!this.stayOpen)
594
+ this.open = false;
595
+ }
596
+ }
570
597
  /**
571
598
  * Handler for clicks on the combo box input field or caret button.
572
599
  */
573
600
  handleComboBoxClick() {
574
601
  this.toggleOptionsMenu();
575
602
  }
603
+ /**
604
+ * Handler for when the clear button is clicked.
605
+ */
606
+ handleClearButtonClick() {
607
+ this.clearSelectedOption();
608
+ this.textInput.focus();
609
+ this.openOptionsMenu();
610
+ }
576
611
  /**
577
612
  * Handler for when any part of the combo box receives focus.
578
613
  */
579
614
  handleFocus() {
580
- if (this.behavior === 'select-only') {
581
- this.caretButton.focus();
582
- }
583
- else {
615
+ if (this.behavior !== 'select-only') {
616
+ // Always keep focus on the text input if it's editable
584
617
  this.textInput.focus();
585
618
  }
586
619
  this.hasFocus = true;
@@ -592,13 +625,20 @@ let IAComboBox = class IAComboBox extends LitElement {
592
625
  handleBlur() {
593
626
  this.hasFocus = false;
594
627
  this.losingFocus = true;
628
+ // On the next tick, check whether we've actually lost focus to some other element,
629
+ // or just had a momentary internal focus switch. If it's the former, we should
630
+ // close the menu and possibly make a selection (depending on desired behavior).
595
631
  setTimeout(() => {
596
- var _a;
597
- if (this.losingFocus && !((_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.activeElement)) {
632
+ if (this.losingFocus && !this.shadowRoot?.activeElement) {
598
633
  this.losingFocus = false;
599
634
  this.closeOptionsMenu();
600
- if (this.behavior === 'freeform')
635
+ if (this.behavior === 'list') {
636
+ this.setTextValue(this.selectedOption?.text ?? '', false);
637
+ }
638
+ else if (this.behavior === 'freeform' &&
639
+ (this.enteredText || this.value)) {
601
640
  this.setValue(this.enteredText);
641
+ }
602
642
  }
603
643
  }, 0);
604
644
  }
@@ -686,18 +726,18 @@ let IAComboBox = class IAComboBox extends LitElement {
686
726
  * held by this combo box.
687
727
  */
688
728
  setSelectedOption(id) {
689
- var _a;
690
729
  const option = this.getOptionFor(id);
691
730
  if (!option)
692
731
  throw new RangeError('Unknown option ID');
693
732
  const prevValue = this.value;
694
733
  this.value = option.id;
695
734
  this.internals.setFormValue(this.value);
696
- this.setTextValue(option.text);
735
+ this.setTextValue(option.text, false);
736
+ this.setFilterText('');
697
737
  if (this.value !== prevValue)
698
738
  this.emitChangeEvent();
699
739
  // Invoke the option's select callback if defined
700
- (_a = option.onSelected) === null || _a === void 0 ? void 0 : _a.call(option, option);
740
+ option.onSelected?.(option);
701
741
  }
702
742
  /**
703
743
  * Clears any currently selected option from this combo box, setting it to null
@@ -738,10 +778,20 @@ let IAComboBox = class IAComboBox extends LitElement {
738
778
  /**
739
779
  * Changes the value of the text input box, and updates the filter accordingly.
740
780
  */
741
- setTextValue(value) {
781
+ setTextValue(value, setFilter = true) {
742
782
  this.textInput.value = value;
743
783
  this.enteredText = value;
744
- this.setFilterText(value);
784
+ if (setFilter)
785
+ this.setFilterText(value);
786
+ }
787
+ /**
788
+ * Sets the current filter text based on the provided string. The resulting filter
789
+ * text might not exactly match the provided value, depending on the current case
790
+ * sensitivity.
791
+ */
792
+ setFilterText(baseFilterText) {
793
+ const { caseTransform } = this;
794
+ this.filterText = caseTransform(baseFilterText);
745
795
  }
746
796
  openOptionsMenu() {
747
797
  this.open = true;
@@ -777,6 +827,19 @@ let IAComboBox = class IAComboBox extends LitElement {
777
827
  //
778
828
  // HELPERS
779
829
  //
830
+ /**
831
+ * True iff no selection has been made and no text has been entered.
832
+ */
833
+ get isEmpty() {
834
+ return !this.selectedOption && !this.enteredText;
835
+ }
836
+ /**
837
+ * We only show the clear button when the `clearable` property is set
838
+ * and the combo box is neither empty nor disabled.
839
+ */
840
+ get shouldShowClearButton() {
841
+ return this.clearable && !this.disabled && !this.isEmpty;
842
+ }
780
843
  /**
781
844
  * Sets the size and position of the options menu to match the size and position of
782
845
  * the combo box widget. Prefers to position below the main widget, but will flip
@@ -789,11 +852,11 @@ let IAComboBox = class IAComboBox extends LitElement {
789
852
  const usableHeightAbove = mainWidgetRect.top;
790
853
  const usableHeightBelow = innerHeight - mainWidgetRect.bottom;
791
854
  // We still want to respect any CSS var specified by the consumer
792
- const maxHeightVar = 'var(--comboBoxListMaxHeight, 250px)';
855
+ const maxHeightVar = 'var(--combo-box-list-max-height, 250px)';
793
856
  const optionsListStyles = {
794
857
  top: `${mainWidgetRect.bottom + scrollY}px`,
795
858
  left: `${mainWidgetRect.left + scrollX}px`,
796
- width: `var(--comboBoxListWidth, ${mainWidgetRect.width}px)`,
859
+ width: `var(--combo-box-list-width, ${mainWidgetRect.width}px)`,
797
860
  maxHeight: `min(${usableHeightBelow}px, ${maxHeightVar})`,
798
861
  };
799
862
  Object.assign(optionsList.style, optionsListStyles);
@@ -816,21 +879,11 @@ let IAComboBox = class IAComboBox extends LitElement {
816
879
  get caseTransform() {
817
880
  return this.caseSensitive ? STRING_IDENTITY_FN : STRING_LOWER_CASE_FN;
818
881
  }
819
- /**
820
- * Sets the current filter text based on the provided string. The resulting filter
821
- * text might not exactly match the provided value, depending on the current case
822
- * sensitivity.
823
- */
824
- setFilterText(baseFilterText) {
825
- const { caseTransform } = this;
826
- this.filterText = caseTransform(baseFilterText);
827
- }
828
882
  /**
829
883
  * Returns the combo box option having the given ID, or null if none exists.
830
884
  */
831
885
  getOptionFor(id) {
832
- var _a;
833
- return (_a = this.optionsByID.get(id)) !== null && _a !== void 0 ? _a : null;
886
+ return this.optionsByID.get(id) ?? null;
834
887
  }
835
888
  /**
836
889
  * Clears any previously-cached mapping of IDs to options, and rebuilds the
@@ -881,7 +934,7 @@ let IAComboBox = class IAComboBox extends LitElement {
881
934
  ? FILTER_PRESETS[resolvedFilterOption]
882
935
  : resolvedFilterOption;
883
936
  const filtered = this.optionsRespectingSortFlag
884
- .filter((opt) => {
937
+ .filter(opt => {
885
938
  const optionFilteringValue = this.optionFilteringValues.get(opt);
886
939
  if (!optionFilteringValue)
887
940
  return false;
@@ -894,15 +947,13 @@ let IAComboBox = class IAComboBox extends LitElement {
894
947
  * The first option appearing in the filtered list, or null if there are none.
895
948
  */
896
949
  get firstFilteredOption() {
897
- var _a;
898
- return (_a = this.filteredOptions[0]) !== null && _a !== void 0 ? _a : null;
950
+ return this.filteredOptions[0] ?? null;
899
951
  }
900
952
  /**
901
953
  * The last option appearing in the filtered list, or null if there are none.
902
954
  */
903
955
  get lastFilteredOption() {
904
- var _a;
905
- return (_a = this.filteredOptions[this.lastFilteredIndex]) !== null && _a !== void 0 ? _a : null;
956
+ return this.filteredOptions[this.lastFilteredIndex] ?? null;
906
957
  }
907
958
  /**
908
959
  * The index of the last filtered option, or -1 if no options match the filter.
@@ -937,111 +988,151 @@ let IAComboBox = class IAComboBox extends LitElement {
937
988
  return this.shadowRoot.getElementById(this.highlightedOption.id);
938
989
  }
939
990
  static get styles() {
940
- return css `
941
- #label {
942
- display: block;
943
- width: fit-content;
944
- }
945
-
946
- #main-widget-row {
947
- display: inline-flex;
948
- align-items: stretch;
949
- flex-wrap: nowrap;
950
- background: white;
951
- border: 1px solid black;
952
- }
953
-
954
- .focused #main-widget-row {
955
- outline: black auto 1px;
956
- outline-offset: 3px;
957
- }
958
-
959
- #text-input {
960
- appearance: none;
961
- background: transparent;
962
- border: none;
963
- padding: var(--comboBoxPadding, 5px);
964
- width: 100%;
965
- font-size: inherit;
966
- outline: none;
967
- }
968
-
969
- #text-input:not(.editable) {
970
- cursor: pointer;
971
- }
972
-
973
- #caret-button {
974
- display: inline-flex;
975
- align-items: center;
976
- appearance: none;
977
- background: transparent;
978
- border: none;
979
- padding: var(--comboBoxPadding, 5px);
980
- outline: none;
981
- cursor: pointer;
982
- }
983
-
984
- #options-list {
985
- position: absolute;
986
- list-style-type: none;
987
- margin: 1px 0 0;
988
- border: none;
989
- padding: 0;
990
- background: white;
991
- max-height: 400px;
992
- box-shadow: 0 0 1px 1px #ddd;
993
- opacity: 0;
994
- transition: opacity 0.125s ease;
995
- }
996
-
997
- #options-list.visible {
998
- opacity: 1;
999
- }
1000
-
1001
- #empty-options {
1002
- padding: 5px;
1003
- color: #606060;
1004
- font-style: italic;
1005
- text-align: center;
1006
- }
1007
-
1008
- .caret {
1009
- width: 14px;
1010
- height: 14px;
1011
- }
1012
-
1013
- .option {
1014
- padding: 5px;
1015
- cursor: pointer;
1016
- }
1017
-
1018
- .highlight {
1019
- background-color: #dbe0ff;
1020
- }
1021
-
1022
- .disabled,
1023
- .disabled * {
1024
- cursor: not-allowed !important;
1025
- }
1026
-
1027
- .sr-only {
1028
- position: absolute !important;
1029
- width: 1px !important;
1030
- height: 1px !important;
1031
- margin: -1px !important;
1032
- padding: 0 !important;
1033
- border: 0 !important;
1034
- overflow: hidden !important;
1035
- white-space: nowrap !important;
1036
- clip: rect(1px, 1px, 1px, 1px) !important;
1037
- -webkit-clip-path: inset(50%) !important;
1038
- clip-path: inset(50%) !important;
1039
- user-select: none !important;
1040
- }
991
+ return css `
992
+ #container {
993
+ display: inline-block;
994
+ width: var(--combo-box-width, auto);
995
+ }
996
+
997
+ #label {
998
+ display: block;
999
+ width: fit-content;
1000
+ }
1001
+
1002
+ #main-widget-row {
1003
+ display: inline-flex;
1004
+ align-items: stretch;
1005
+ flex-wrap: nowrap;
1006
+ background: white;
1007
+ border: 1px solid black;
1008
+ width: 100%;
1009
+ }
1010
+
1011
+ #main-widget-row:not(.focused):hover,
1012
+ #main-widget-row:not(.focused):active {
1013
+ background: #fafafa;
1014
+ }
1015
+
1016
+ #main-widget-row.focused {
1017
+ outline: black auto 1px;
1018
+ outline-offset: 3px;
1019
+ }
1020
+
1021
+ #text-input {
1022
+ appearance: none;
1023
+ background: transparent;
1024
+ border: none;
1025
+ padding: var(--combo-box-padding, 5px);
1026
+ padding-right: 0;
1027
+ width: 100%;
1028
+ font-size: inherit;
1029
+ color: inherit;
1030
+ outline: none;
1031
+ text-overflow: ellipsis;
1032
+ }
1033
+
1034
+ #text-input.clear-padding {
1035
+ padding-right: 30px;
1036
+ }
1037
+
1038
+ #text-input:read-only {
1039
+ cursor: pointer;
1040
+ }
1041
+
1042
+ #clear-button,
1043
+ #caret-button {
1044
+ display: inline-flex;
1045
+ align-items: center;
1046
+ appearance: none;
1047
+ background: transparent;
1048
+ border: none;
1049
+ padding: var(--combo-box-padding, 5px) 5px;
1050
+ outline: none;
1051
+ cursor: pointer;
1052
+ }
1053
+
1054
+ #clear-button {
1055
+ flex: 0 0 30px;
1056
+ }
1057
+
1058
+ #clear-button[hidden] {
1059
+ display: none;
1060
+ }
1061
+
1062
+ #caret-button {
1063
+ padding-right: var(--combo-box-padding, 5px);
1064
+ }
1065
+
1066
+ #options-list {
1067
+ position: absolute;
1068
+ list-style-type: none;
1069
+ margin: 1px 0 0;
1070
+ border: none;
1071
+ padding: 0;
1072
+ background: white;
1073
+ max-height: 400px;
1074
+ box-shadow: 0 0 1px 1px #ddd;
1075
+ opacity: 0;
1076
+ transition: opacity 0.125s ease;
1077
+ }
1078
+
1079
+ #options-list.visible {
1080
+ opacity: 1;
1081
+ }
1082
+
1083
+ #empty-options {
1084
+ padding: 5px;
1085
+ color: #606060;
1086
+ font-style: italic;
1087
+ text-align: center;
1088
+ }
1089
+
1090
+ #caret-button svg {
1091
+ width: 14px;
1092
+ height: 14px;
1093
+ }
1094
+
1095
+ #clear-button svg {
1096
+ width: var(--combo-box-clear-icon-size, 16px);
1097
+ height: var(--combo-box-clear-icon-size, 16px);
1098
+ }
1099
+
1100
+ .option {
1101
+ padding: 7px 5px;
1102
+ width: 100%;
1103
+ box-sizing: border-box;
1104
+ line-height: 1.1;
1105
+ text-overflow: ellipsis;
1106
+ overflow: hidden;
1107
+ cursor: pointer;
1108
+ }
1109
+
1110
+ .highlight {
1111
+ background-color: #dbe0ff;
1112
+ }
1113
+
1114
+ .disabled,
1115
+ .disabled * {
1116
+ cursor: not-allowed !important;
1117
+ }
1118
+
1119
+ .sr-only {
1120
+ position: absolute !important;
1121
+ width: 1px !important;
1122
+ height: 1px !important;
1123
+ margin: -1px !important;
1124
+ padding: 0 !important;
1125
+ border: 0 !important;
1126
+ overflow: hidden !important;
1127
+ white-space: nowrap !important;
1128
+ clip: rect(1px, 1px, 1px, 1px) !important;
1129
+ -webkit-clip-path: inset(50%) !important;
1130
+ clip-path: inset(50%) !important;
1131
+ user-select: none !important;
1132
+ }
1041
1133
  `;
1042
1134
  }
1043
1135
  };
1044
- IAComboBox.formAssociated = true;
1045
1136
  __decorate([
1046
1137
  property({ type: Array })
1047
1138
  ], IAComboBox.prototype, "options", void 0);
@@ -1069,6 +1160,9 @@ __decorate([
1069
1160
  __decorate([
1070
1161
  property({ type: Boolean, reflect: true, attribute: 'stay-open' })
1071
1162
  ], IAComboBox.prototype, "stayOpen", void 0);
1163
+ __decorate([
1164
+ property({ type: Boolean, reflect: true })
1165
+ ], IAComboBox.prototype, "clearable", void 0);
1072
1166
  __decorate([
1073
1167
  property({ type: Boolean, reflect: true })
1074
1168
  ], IAComboBox.prototype, "open", void 0);
@@ -1099,9 +1193,6 @@ __decorate([
1099
1193
  __decorate([
1100
1194
  query('#text-input')
1101
1195
  ], IAComboBox.prototype, "textInput", void 0);
1102
- __decorate([
1103
- query('#caret-button')
1104
- ], IAComboBox.prototype, "caretButton", void 0);
1105
1196
  __decorate([
1106
1197
  query('#options-list')
1107
1198
  ], IAComboBox.prototype, "optionsList", void 0);