@lion/ui 0.24.0 → 0.26.0

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 (789) hide show
  1. package/README.md +25 -7
  2. package/accordion-test-suites.js +2 -0
  3. package/collapsible-test-suites.js +1 -0
  4. package/combobox-test-suites.js +2 -0
  5. package/components/accordion/test/lion-accordion.test.js +2 -565
  6. package/components/accordion/test-suites/lion-accordion.suite.js +596 -0
  7. package/components/button/test-suites/LionButton.suite.js +13 -11
  8. package/components/button/test-suites/LionButtonReset.suite.js +11 -3
  9. package/components/button/test-suites/LionButtonSubmit.suite.js +11 -3
  10. package/components/calendar/test/lion-calendar.test.js +1 -1570
  11. package/components/calendar/test-suites/LionCalendar.suite.js +1617 -5
  12. package/components/checkbox-group/test/lion-checkbox-group-integrations.test.js +5 -2
  13. package/components/checkbox-group/test/lion-checkbox-indeterminate-integrations.test.js +13 -4
  14. package/components/checkbox-group/test/lion-checkbox-integrations.test.js +4 -1
  15. package/components/checkbox-group/test-suites/CheckboxIndeterminate.suite.js +54 -54
  16. package/components/collapsible/test/lion-collapsible.test.js +2 -126
  17. package/components/collapsible/test-suites/LionCollapsible.suite.js +147 -0
  18. package/components/combobox/test/lion-combobox-integrations.test.js +11 -4
  19. package/components/combobox/test/lion-combobox.test.js +2 -3622
  20. package/components/combobox/test-suites/lion-combobox.suite.js +3665 -0
  21. package/components/core/src/ScopedElementsMixin.js +16 -1
  22. package/components/core/test-helpers/isElementVisible.js +21 -0
  23. package/components/core/test-helpers/resolveSuiteTag.js +97 -0
  24. package/components/core/types/TestSuiteTypes.ts +43 -0
  25. package/components/dialog/test/lion-dialog.integration.test.js +9 -10
  26. package/components/dialog/test/lion-dialog.test.js +2 -399
  27. package/components/dialog/test-suites/lion-dialog.suite.js +412 -0
  28. package/components/drawer/test/lion-drawer.test.js +2 -84
  29. package/components/drawer/test-suites/LionDrawer.suite.js +108 -0
  30. package/components/fieldset/test/lion-fieldset.test.js +7 -2
  31. package/components/form/test/lion-form.test.js +2 -348
  32. package/components/form/test-suites/lion-form.suite.js +398 -0
  33. package/components/form-core/test/FormatMixin.test.js +4 -1
  34. package/components/form-core/test/choice-group/CustomChoiceGroupMixin.integrations.test.js +13 -0
  35. package/components/form-core/test-suites/FormRegistrationMixins.suite.js +21 -26
  36. package/components/form-core/test-suites/FormatMixin.suite.js +45 -43
  37. package/components/form-core/test-suites/InteractionStateMixin.suite.js +19 -15
  38. package/components/form-core/test-suites/NativeTextFieldMixin.suite.js +8 -9
  39. package/components/form-core/test-suites/ValidateMixin.suite.js +14 -10
  40. package/components/form-core/test-suites/ValidateMixinFeedbackPart.suite.js +15 -2
  41. package/components/form-core/test-suites/choice-group/ChoiceGroupMixin.suite.js +55 -33
  42. package/components/form-core/test-suites/choice-group/ChoiceInputMixin.suite.js +7 -6
  43. package/components/form-core/test-suites/choice-group/CustomChoiceGroupMixin.suite.js +19 -22
  44. package/components/form-core/test-suites/form-group/FormGroupMixin-input.suite.js +14 -5
  45. package/components/form-core/test-suites/form-group/FormGroupMixin.suite.js +13 -4
  46. package/components/icon/test/lion-icon.test.js +2 -188
  47. package/components/icon/test-suites/lion-icon.suite.js +206 -0
  48. package/components/input/test/input-integrations.test.js +18 -15
  49. package/components/input/test/lion-input-integrations.test.js +7 -3
  50. package/components/input/test/lion-input.test.js +2 -198
  51. package/components/input/test-suites/lion-input.suite.js +214 -0
  52. package/components/input-amount/src/parsers.js +5 -1
  53. package/components/input-amount/test/lion-input-amount-integrations.test.js +7 -3
  54. package/components/input-amount/test/lion-input-amount.test.js +2 -421
  55. package/components/input-amount/test/parsers.test.js +12 -0
  56. package/components/input-amount/test-suites/lion-input-amount.suite.js +418 -0
  57. package/components/input-amount-dropdown/test/LionInputAmountDropdown.test.js +3 -2
  58. package/components/input-amount-dropdown/test/lion-input-amount-dropdown-integrations.test.js +4 -2
  59. package/components/input-amount-dropdown/test-suites/LionInputAmountDropdown.suite.js +6 -4
  60. package/components/input-date/test/lion-input-date-integrations.test.js +7 -3
  61. package/components/input-date/test/lion-input-date.test.js +2 -177
  62. package/components/input-date/test-suites/lion-input-date.suite.js +195 -0
  63. package/components/input-datepicker/test/lion-input-datepicker-integrations.test.js +7 -3
  64. package/components/input-datepicker/test/lion-input-datepicker.test.js +1 -844
  65. package/components/input-datepicker/test-helpers/DatepickerInputObject.js +26 -0
  66. package/components/input-datepicker/test-suites/LionInputDatepicker.suite.js +899 -14
  67. package/components/input-email/test/lion-input-email-integrations.test.js +4 -2
  68. package/components/input-email/test/lion-input-email.test.js +2 -46
  69. package/components/input-email/test-suites/lion-input-email.suite.js +63 -0
  70. package/components/input-file/test/lion-input-file.test.js +2 -1274
  71. package/components/input-file/test-suites/lion-input-file.suite.js +1505 -0
  72. package/components/input-iban/test/lion-input-iban-integrations.test.js +4 -2
  73. package/components/input-iban/test/lion-input-iban.test.js +2 -73
  74. package/components/input-iban/test-suites/lion-input-iban.suite.js +88 -0
  75. package/components/input-range/test/lion-input-range.test.js +2 -176
  76. package/components/input-range/test-suites/lion-input-range.suite.js +195 -0
  77. package/components/input-stepper/test-suites/lion-input-stepper.integration.suite.js +14 -3
  78. package/components/input-stepper/test-suites/lion-input-stepper.suite.js +14 -10
  79. package/components/input-tel/test/LionInputTel.test.js +3 -1
  80. package/components/input-tel/test/parsers.test.js +87 -0
  81. package/components/input-tel/test-helpers/mockPhoneUtilManager.js +14 -3
  82. package/components/input-tel/test-suites/LionInputTel.suite.js +9 -4
  83. package/components/input-tel-dropdown/test/LionInputTelDropdown.test.js +6 -4
  84. package/components/input-tel-dropdown/test-suites/LionInputTelDropdown--parentheses.suite.js +10 -13
  85. package/components/input-tel-dropdown/test-suites/LionInputTelDropdown.suite.js +6 -5
  86. package/components/listbox/src/LionListbox.js +1 -1
  87. package/components/listbox/test-suites/ListboxMixin.suite.js +19 -19
  88. package/components/menu/test-suites/InteractiveListMixin.suite.js +6 -9
  89. package/components/menu/test-suites/LionMenu.suite.js +6 -10
  90. package/components/menu/test-suites/LionMenuInteractions.suite.js +13 -4
  91. package/components/menu/test-suites/MoreButtonMenuMixin.suite.js +6 -9
  92. package/components/menu/test-suites/MultiLevelListMixin.suite.js +15 -11
  93. package/components/overlays/src/OverlayController.js +7 -2
  94. package/components/overlays/src/OverlayMixin.js +3 -6
  95. package/components/overlays/test/OverlayController.test.js +37 -1
  96. package/components/overlays/test/invoker-compatibility.test.js +37 -0
  97. package/components/overlays/test-suites/OverlayMixin.suite.js +3 -5
  98. package/components/pagination/test/lion-pagination.test.js +2 -176
  99. package/components/pagination/test-suites/LionPagination.suite.js +201 -0
  100. package/components/progress-indicator/test/lion-progress-indicator.test.js +2 -203
  101. package/components/progress-indicator/test-suites/lion-progress-indicator.suite.js +214 -0
  102. package/components/radio-group/test/lion-radio-group.test.js +2 -162
  103. package/components/radio-group/test/lion-radio.test.js +2 -54
  104. package/components/radio-group/test-suites/lion-radio-group.suite.js +173 -0
  105. package/components/radio-group/test-suites/lion-radio.suite.js +70 -0
  106. package/components/select/test/lion-select.test.js +2 -79
  107. package/components/select/test-suites/LionSelect.suite.js +94 -0
  108. package/components/select-rich/src/LionSelectRich.js +2 -1
  109. package/components/select-rich/test/lion-select-invoker.test.js +2 -86
  110. package/components/select-rich/test/lion-select-rich-interaction.test.js +2 -430
  111. package/components/select-rich/test/lion-select-rich.test.js +2 -1193
  112. package/components/select-rich/test-suites/lion-select-invoker.suite.js +98 -0
  113. package/components/select-rich/test-suites/lion-select-rich-interaction.suite.js +452 -0
  114. package/components/select-rich/test-suites/lion-select-rich.suite.js +1236 -0
  115. package/components/steps/test/lion-step.test.js +2 -97
  116. package/components/steps/test/lion-steps.test.js +2 -433
  117. package/components/steps/test-suites/lion-step.suite.js +113 -0
  118. package/components/steps/test-suites/lion-steps.suite.js +445 -0
  119. package/components/switch/test/lion-switch.test.js +2 -232
  120. package/components/switch/test-suites/lion-switch.suite.js +450 -0
  121. package/components/tabs/test/lion-tabs.test.js +2 -596
  122. package/components/tabs/test-suites/lion-tabs.suite.js +617 -0
  123. package/components/textarea/src/LionTextarea.js +5 -2
  124. package/components/textarea/test/lion-textarea.test.js +2 -159
  125. package/components/textarea/test-suites/lion-textarea.suite.js +192 -0
  126. package/components/tooltip/test/lion-tooltip.test.js +2 -393
  127. package/components/tooltip/test-suites/lion-tooltip.suite.js +409 -0
  128. package/custom-elements.json +14890 -10187
  129. package/dialog-test-suites.js +2 -0
  130. package/dist-types/components/accordion/test/lion-accordion.test.d.ts +1 -2
  131. package/dist-types/components/accordion/test/lion-accordion.test.d.ts.map +1 -1
  132. package/dist-types/components/accordion/test-suites/lion-accordion.suite.d.ts +18 -0
  133. package/dist-types/components/accordion/test-suites/lion-accordion.suite.d.ts.map +1 -0
  134. package/dist-types/components/button/test-suites/LionButton.suite.d.ts +10 -4
  135. package/dist-types/components/button/test-suites/LionButton.suite.d.ts.map +1 -1
  136. package/dist-types/components/button/test-suites/LionButtonReset.suite.d.ts +10 -4
  137. package/dist-types/components/button/test-suites/LionButtonReset.suite.d.ts.map +1 -1
  138. package/dist-types/components/button/test-suites/LionButtonSubmit.suite.d.ts +10 -4
  139. package/dist-types/components/button/test-suites/LionButtonSubmit.suite.d.ts.map +1 -1
  140. package/dist-types/components/calendar/test/lion-calendar.test.d.ts +1 -3
  141. package/dist-types/components/calendar/test/lion-calendar.test.d.ts.map +1 -1
  142. package/dist-types/components/calendar/test-suites/LionCalendar.suite.d.ts +13 -4
  143. package/dist-types/components/calendar/test-suites/LionCalendar.suite.d.ts.map +1 -1
  144. package/dist-types/components/checkbox-group/test/lion-checkbox-group-integrations.test.d.ts.map +1 -1
  145. package/dist-types/components/checkbox-group/test/lion-checkbox-integrations.test.d.ts.map +1 -1
  146. package/dist-types/components/checkbox-group/test-suites/CheckboxIndeterminate.suite.d.ts +12 -9
  147. package/dist-types/components/checkbox-group/test-suites/CheckboxIndeterminate.suite.d.ts.map +1 -1
  148. package/dist-types/components/collapsible/test/lion-collapsible.test.d.ts +1 -3
  149. package/dist-types/components/collapsible/test/lion-collapsible.test.d.ts.map +1 -1
  150. package/dist-types/components/collapsible/test-suites/LionCollapsible.suite.d.ts +18 -0
  151. package/dist-types/components/collapsible/test-suites/LionCollapsible.suite.d.ts.map +1 -0
  152. package/dist-types/components/combobox/test/lion-combobox.test.d.ts +1 -5
  153. package/dist-types/components/combobox/test/lion-combobox.test.d.ts.map +1 -1
  154. package/dist-types/components/combobox/test-suites/lion-combobox.suite.d.ts +17 -0
  155. package/dist-types/components/combobox/test-suites/lion-combobox.suite.d.ts.map +1 -0
  156. package/dist-types/components/core/src/ScopedElementsMixin.d.ts.map +1 -1
  157. package/dist-types/components/core/test-helpers/isElementVisible.d.ts +12 -0
  158. package/dist-types/components/core/test-helpers/isElementVisible.d.ts.map +1 -0
  159. package/dist-types/components/core/test-helpers/resolveSuiteTag.d.ts +29 -0
  160. package/dist-types/components/core/test-helpers/resolveSuiteTag.d.ts.map +1 -0
  161. package/dist-types/components/core/types/TestSuiteTypes.d.ts +40 -0
  162. package/dist-types/components/core/types/TestSuiteTypes.d.ts.map +1 -0
  163. package/dist-types/components/dialog/test/lion-dialog.integration.test.d.ts.map +1 -1
  164. package/dist-types/components/dialog/test/lion-dialog.test.d.ts +1 -5
  165. package/dist-types/components/dialog/test/lion-dialog.test.d.ts.map +1 -1
  166. package/dist-types/components/dialog/test-suites/lion-dialog.suite.d.ts +19 -0
  167. package/dist-types/components/dialog/test-suites/lion-dialog.suite.d.ts.map +1 -0
  168. package/dist-types/components/drawer/test/lion-drawer.test.d.ts +1 -3
  169. package/dist-types/components/drawer/test/lion-drawer.test.d.ts.map +1 -1
  170. package/dist-types/components/drawer/test-suites/LionDrawer.suite.d.ts +20 -0
  171. package/dist-types/components/drawer/test-suites/LionDrawer.suite.d.ts.map +1 -0
  172. package/dist-types/components/fieldset/test/lion-fieldset.test.d.ts.map +1 -1
  173. package/dist-types/components/form/test/lion-form.test.d.ts +1 -12
  174. package/dist-types/components/form/test/lion-form.test.d.ts.map +1 -1
  175. package/dist-types/components/form/test-suites/lion-form.suite.d.ts +39 -0
  176. package/dist-types/components/form/test-suites/lion-form.suite.d.ts.map +1 -0
  177. package/dist-types/components/form-core/test-suites/FormRegistrationMixins.suite.d.ts +14 -17
  178. package/dist-types/components/form-core/test-suites/FormRegistrationMixins.suite.d.ts.map +1 -1
  179. package/dist-types/components/form-core/test-suites/FormatMixin.suite.d.ts +22 -38
  180. package/dist-types/components/form-core/test-suites/FormatMixin.suite.d.ts.map +1 -1
  181. package/dist-types/components/form-core/test-suites/InteractionStateMixin.suite.d.ts +11 -5
  182. package/dist-types/components/form-core/test-suites/InteractionStateMixin.suite.d.ts.map +1 -1
  183. package/dist-types/components/form-core/test-suites/NativeTextFieldMixin.suite.d.ts +9 -4
  184. package/dist-types/components/form-core/test-suites/NativeTextFieldMixin.suite.d.ts.map +1 -1
  185. package/dist-types/components/form-core/test-suites/ValidateMixin.suite.d.ts +9 -4
  186. package/dist-types/components/form-core/test-suites/ValidateMixin.suite.d.ts.map +1 -1
  187. package/dist-types/components/form-core/test-suites/ValidateMixinFeedbackPart.suite.d.ts +10 -1
  188. package/dist-types/components/form-core/test-suites/ValidateMixinFeedbackPart.suite.d.ts.map +1 -1
  189. package/dist-types/components/form-core/test-suites/choice-group/ChoiceGroupMixin.suite.d.ts +17 -6
  190. package/dist-types/components/form-core/test-suites/choice-group/ChoiceGroupMixin.suite.d.ts.map +1 -1
  191. package/dist-types/components/form-core/test-suites/choice-group/ChoiceInputMixin.suite.d.ts +5 -2
  192. package/dist-types/components/form-core/test-suites/choice-group/ChoiceInputMixin.suite.d.ts.map +1 -1
  193. package/dist-types/components/form-core/test-suites/choice-group/CustomChoiceGroupMixin.suite.d.ts +10 -5
  194. package/dist-types/components/form-core/test-suites/choice-group/CustomChoiceGroupMixin.suite.d.ts.map +1 -1
  195. package/dist-types/components/form-core/test-suites/form-group/FormGroupMixin-input.suite.d.ts +10 -5
  196. package/dist-types/components/form-core/test-suites/form-group/FormGroupMixin-input.suite.d.ts.map +1 -1
  197. package/dist-types/components/form-core/test-suites/form-group/FormGroupMixin.suite.d.ts +10 -5
  198. package/dist-types/components/form-core/test-suites/form-group/FormGroupMixin.suite.d.ts.map +1 -1
  199. package/dist-types/components/icon/test/lion-icon.test.d.ts +1 -4
  200. package/dist-types/components/icon/test/lion-icon.test.d.ts.map +1 -1
  201. package/dist-types/components/icon/test-suites/lion-icon.suite.d.ts +16 -0
  202. package/dist-types/components/icon/test-suites/lion-icon.suite.d.ts.map +1 -0
  203. package/dist-types/components/input/test/lion-input-integrations.test.d.ts.map +1 -1
  204. package/dist-types/components/input/test/lion-input.test.d.ts +1 -2
  205. package/dist-types/components/input/test/lion-input.test.d.ts.map +1 -1
  206. package/dist-types/components/input/test-suites/lion-input.suite.d.ts +17 -0
  207. package/dist-types/components/input/test-suites/lion-input.suite.d.ts.map +1 -0
  208. package/dist-types/components/input-amount/src/parsers.d.ts.map +1 -1
  209. package/dist-types/components/input-amount/test/lion-input-amount-integrations.test.d.ts.map +1 -1
  210. package/dist-types/components/input-amount/test/lion-input-amount.test.d.ts +1 -2
  211. package/dist-types/components/input-amount/test/lion-input-amount.test.d.ts.map +1 -1
  212. package/dist-types/components/input-amount/test-suites/lion-input-amount.suite.d.ts +19 -0
  213. package/dist-types/components/input-amount/test-suites/lion-input-amount.suite.d.ts.map +1 -0
  214. package/dist-types/components/input-amount-dropdown/test/lion-input-amount-dropdown-integrations.test.d.ts.map +1 -1
  215. package/dist-types/components/input-amount-dropdown/test-suites/LionInputAmountDropdown.suite.d.ts +7 -3
  216. package/dist-types/components/input-amount-dropdown/test-suites/LionInputAmountDropdown.suite.d.ts.map +1 -1
  217. package/dist-types/components/input-date/test/lion-input-date-integrations.test.d.ts.map +1 -1
  218. package/dist-types/components/input-date/test/lion-input-date.test.d.ts +1 -3
  219. package/dist-types/components/input-date/test/lion-input-date.test.d.ts.map +1 -1
  220. package/dist-types/components/input-date/test-suites/lion-input-date.suite.d.ts +15 -0
  221. package/dist-types/components/input-date/test-suites/lion-input-date.suite.d.ts.map +1 -0
  222. package/dist-types/components/input-datepicker/test/lion-input-datepicker-integrations.test.d.ts.map +1 -1
  223. package/dist-types/components/input-datepicker/test/lion-input-datepicker.test.d.ts +1 -2
  224. package/dist-types/components/input-datepicker/test/lion-input-datepicker.test.d.ts.map +1 -1
  225. package/dist-types/components/input-datepicker/test-helpers/DatepickerInputObject.d.ts +8 -0
  226. package/dist-types/components/input-datepicker/test-helpers/DatepickerInputObject.d.ts.map +1 -1
  227. package/dist-types/components/input-datepicker/test-suites/LionInputDatepicker.suite.d.ts +15 -5
  228. package/dist-types/components/input-datepicker/test-suites/LionInputDatepicker.suite.d.ts.map +1 -1
  229. package/dist-types/components/input-email/test/lion-input-email-integrations.test.d.ts.map +1 -1
  230. package/dist-types/components/input-email/test/lion-input-email.test.d.ts +1 -3
  231. package/dist-types/components/input-email/test/lion-input-email.test.d.ts.map +1 -1
  232. package/dist-types/components/input-email/test-suites/lion-input-email.suite.d.ts +15 -0
  233. package/dist-types/components/input-email/test-suites/lion-input-email.suite.d.ts.map +1 -0
  234. package/dist-types/components/input-file/test/lion-input-file.test.d.ts +1 -5
  235. package/dist-types/components/input-file/test/lion-input-file.test.d.ts.map +1 -1
  236. package/dist-types/components/input-file/test-suites/lion-input-file.suite.d.ts +19 -0
  237. package/dist-types/components/input-file/test-suites/lion-input-file.suite.d.ts.map +1 -0
  238. package/dist-types/components/input-iban/test/lion-input-iban-integrations.test.d.ts.map +1 -1
  239. package/dist-types/components/input-iban/test/lion-input-iban.test.d.ts +1 -3
  240. package/dist-types/components/input-iban/test/lion-input-iban.test.d.ts.map +1 -1
  241. package/dist-types/components/input-iban/test-suites/lion-input-iban.suite.d.ts +15 -0
  242. package/dist-types/components/input-iban/test-suites/lion-input-iban.suite.d.ts.map +1 -0
  243. package/dist-types/components/input-range/test/lion-input-range.test.d.ts +1 -3
  244. package/dist-types/components/input-range/test/lion-input-range.test.d.ts.map +1 -1
  245. package/dist-types/components/input-range/test-suites/lion-input-range.suite.d.ts +15 -0
  246. package/dist-types/components/input-range/test-suites/lion-input-range.suite.d.ts.map +1 -0
  247. package/dist-types/components/input-stepper/test-suites/lion-input-stepper.integration.suite.d.ts +10 -2
  248. package/dist-types/components/input-stepper/test-suites/lion-input-stepper.integration.suite.d.ts.map +1 -1
  249. package/dist-types/components/input-stepper/test-suites/lion-input-stepper.suite.d.ts +10 -2
  250. package/dist-types/components/input-stepper/test-suites/lion-input-stepper.suite.d.ts.map +1 -1
  251. package/dist-types/components/input-tel/test-helpers/mockPhoneUtilManager.d.ts +5 -0
  252. package/dist-types/components/input-tel/test-helpers/mockPhoneUtilManager.d.ts.map +1 -1
  253. package/dist-types/components/input-tel/test-suites/LionInputTel.suite.d.ts +6 -4
  254. package/dist-types/components/input-tel/test-suites/LionInputTel.suite.d.ts.map +1 -1
  255. package/dist-types/components/input-tel-dropdown/test-suites/LionInputTelDropdown--parentheses.suite.d.ts +7 -3
  256. package/dist-types/components/input-tel-dropdown/test-suites/LionInputTelDropdown--parentheses.suite.d.ts.map +1 -1
  257. package/dist-types/components/input-tel-dropdown/test-suites/LionInputTelDropdown.suite.d.ts +7 -3
  258. package/dist-types/components/input-tel-dropdown/test-suites/LionInputTelDropdown.suite.d.ts.map +1 -1
  259. package/dist-types/components/listbox/src/LionListbox.d.ts +2 -2
  260. package/dist-types/components/listbox/src/LionListbox.d.ts.map +1 -1
  261. package/dist-types/components/listbox/test-suites/ListboxMixin.suite.d.ts +3 -4
  262. package/dist-types/components/listbox/test-suites/ListboxMixin.suite.d.ts.map +1 -1
  263. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts +6 -5
  264. package/dist-types/components/menu/test-suites/InteractiveListMixin.suite.d.ts.map +1 -1
  265. package/dist-types/components/menu/test-suites/LionMenu.suite.d.ts +6 -5
  266. package/dist-types/components/menu/test-suites/LionMenu.suite.d.ts.map +1 -1
  267. package/dist-types/components/menu/test-suites/LionMenuInteractions.suite.d.ts +10 -4
  268. package/dist-types/components/menu/test-suites/LionMenuInteractions.suite.d.ts.map +1 -1
  269. package/dist-types/components/menu/test-suites/MoreButtonMenuMixin.suite.d.ts +6 -5
  270. package/dist-types/components/menu/test-suites/MoreButtonMenuMixin.suite.d.ts.map +1 -1
  271. package/dist-types/components/menu/test-suites/MultiLevelListMixin.suite.d.ts +7 -6
  272. package/dist-types/components/menu/test-suites/MultiLevelListMixin.suite.d.ts.map +1 -1
  273. package/dist-types/components/overlays/src/OverlayController.d.ts +3 -1
  274. package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
  275. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  276. package/dist-types/components/overlays/test/invoker-compatibility.test.d.ts +3 -0
  277. package/dist-types/components/overlays/test/invoker-compatibility.test.d.ts.map +1 -0
  278. package/dist-types/components/overlays/test-suites/OverlayMixin.suite.d.ts.map +1 -1
  279. package/dist-types/components/pagination/test/lion-pagination.test.d.ts +1 -3
  280. package/dist-types/components/pagination/test/lion-pagination.test.d.ts.map +1 -1
  281. package/dist-types/components/pagination/test-suites/LionPagination.suite.d.ts +18 -0
  282. package/dist-types/components/pagination/test-suites/LionPagination.suite.d.ts.map +1 -0
  283. package/dist-types/components/progress-indicator/test/lion-progress-indicator.test.d.ts +1 -3
  284. package/dist-types/components/progress-indicator/test/lion-progress-indicator.test.d.ts.map +1 -1
  285. package/dist-types/components/progress-indicator/test-suites/lion-progress-indicator.suite.d.ts +15 -0
  286. package/dist-types/components/progress-indicator/test-suites/lion-progress-indicator.suite.d.ts.map +1 -0
  287. package/dist-types/components/radio-group/test/lion-radio-group.test.d.ts +1 -5
  288. package/dist-types/components/radio-group/test/lion-radio-group.test.d.ts.map +1 -1
  289. package/dist-types/components/radio-group/test/lion-radio.test.d.ts +1 -2
  290. package/dist-types/components/radio-group/test/lion-radio.test.d.ts.map +1 -1
  291. package/dist-types/components/radio-group/test-suites/lion-radio-group.suite.d.ts +19 -0
  292. package/dist-types/components/radio-group/test-suites/lion-radio-group.suite.d.ts.map +1 -0
  293. package/dist-types/components/radio-group/test-suites/lion-radio.suite.d.ts +17 -0
  294. package/dist-types/components/radio-group/test-suites/lion-radio.suite.d.ts.map +1 -0
  295. package/dist-types/components/select/test/lion-select.test.d.ts +1 -2
  296. package/dist-types/components/select/test/lion-select.test.d.ts.map +1 -1
  297. package/dist-types/components/select/test-suites/LionSelect.suite.d.ts +16 -0
  298. package/dist-types/components/select/test-suites/LionSelect.suite.d.ts.map +1 -0
  299. package/dist-types/components/select-rich/src/LionSelectRich.d.ts +5 -4
  300. package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
  301. package/dist-types/components/select-rich/test/lion-select-invoker.test.d.ts +1 -2
  302. package/dist-types/components/select-rich/test/lion-select-invoker.test.d.ts.map +1 -1
  303. package/dist-types/components/select-rich/test/lion-select-rich-interaction.test.d.ts +1 -5
  304. package/dist-types/components/select-rich/test/lion-select-rich-interaction.test.d.ts.map +1 -1
  305. package/dist-types/components/select-rich/test/lion-select-rich.test.d.ts +1 -8
  306. package/dist-types/components/select-rich/test/lion-select-rich.test.d.ts.map +1 -1
  307. package/dist-types/components/select-rich/test-suites/lion-select-invoker.suite.d.ts +19 -0
  308. package/dist-types/components/select-rich/test-suites/lion-select-invoker.suite.d.ts.map +1 -0
  309. package/dist-types/components/select-rich/test-suites/lion-select-rich-interaction.suite.d.ts +19 -0
  310. package/dist-types/components/select-rich/test-suites/lion-select-rich-interaction.suite.d.ts.map +1 -0
  311. package/dist-types/components/select-rich/test-suites/lion-select-rich.suite.d.ts +21 -0
  312. package/dist-types/components/select-rich/test-suites/lion-select-rich.suite.d.ts.map +1 -0
  313. package/dist-types/components/steps/test/lion-step.test.d.ts +1 -2
  314. package/dist-types/components/steps/test/lion-step.test.d.ts.map +1 -1
  315. package/dist-types/components/steps/test/lion-steps.test.d.ts +1 -8
  316. package/dist-types/components/steps/test/lion-steps.test.d.ts.map +1 -1
  317. package/dist-types/components/steps/test-suites/lion-step.suite.d.ts +17 -0
  318. package/dist-types/components/steps/test-suites/lion-step.suite.d.ts.map +1 -0
  319. package/dist-types/components/steps/test-suites/lion-steps.suite.d.ts +22 -0
  320. package/dist-types/components/steps/test-suites/lion-steps.suite.d.ts.map +1 -0
  321. package/dist-types/components/switch/test/lion-switch.test.d.ts +1 -4
  322. package/dist-types/components/switch/test/lion-switch.test.d.ts.map +1 -1
  323. package/dist-types/components/switch/test-suites/lion-switch.suite.d.ts +17 -0
  324. package/dist-types/components/switch/test-suites/lion-switch.suite.d.ts.map +1 -0
  325. package/dist-types/components/tabs/test/lion-tabs.test.d.ts +1 -5
  326. package/dist-types/components/tabs/test/lion-tabs.test.d.ts.map +1 -1
  327. package/dist-types/components/tabs/test-suites/lion-tabs.suite.d.ts +17 -0
  328. package/dist-types/components/tabs/test-suites/lion-tabs.suite.d.ts.map +1 -0
  329. package/dist-types/components/textarea/src/LionTextarea.d.ts.map +1 -1
  330. package/dist-types/components/textarea/test/lion-textarea.test.d.ts +1 -3
  331. package/dist-types/components/textarea/test/lion-textarea.test.d.ts.map +1 -1
  332. package/dist-types/components/textarea/test-suites/lion-textarea.suite.d.ts +15 -0
  333. package/dist-types/components/textarea/test-suites/lion-textarea.suite.d.ts.map +1 -0
  334. package/dist-types/components/tooltip/test/lion-tooltip.test.d.ts +1 -3
  335. package/dist-types/components/tooltip/test/lion-tooltip.test.d.ts.map +1 -1
  336. package/dist-types/components/tooltip/test-suites/lion-tooltip.suite.d.ts +19 -0
  337. package/dist-types/components/tooltip/test-suites/lion-tooltip.suite.d.ts.map +1 -0
  338. package/dist-types/exports/accordion-test-suites.d.ts +2 -0
  339. package/dist-types/exports/accordion-test-suites.d.ts.map +1 -0
  340. package/dist-types/exports/collapsible-test-suites.d.ts +2 -0
  341. package/dist-types/exports/collapsible-test-suites.d.ts.map +1 -0
  342. package/dist-types/exports/combobox-test-suites.d.ts +2 -0
  343. package/dist-types/exports/combobox-test-suites.d.ts.map +1 -0
  344. package/dist-types/exports/dialog-test-suites.d.ts +2 -0
  345. package/dist-types/exports/dialog-test-suites.d.ts.map +1 -0
  346. package/dist-types/exports/drawer-test-suites.d.ts +2 -0
  347. package/dist-types/exports/drawer-test-suites.d.ts.map +1 -0
  348. package/dist-types/exports/form-test-suites.d.ts +2 -0
  349. package/dist-types/exports/form-test-suites.d.ts.map +1 -0
  350. package/dist-types/exports/icon-test-suites.d.ts +2 -0
  351. package/dist-types/exports/icon-test-suites.d.ts.map +1 -0
  352. package/dist-types/exports/input-amount-test-suites.d.ts +2 -0
  353. package/dist-types/exports/input-amount-test-suites.d.ts.map +1 -0
  354. package/dist-types/exports/input-date-test-suites.d.ts +2 -0
  355. package/dist-types/exports/input-date-test-suites.d.ts.map +1 -0
  356. package/dist-types/exports/input-email-test-suites.d.ts +2 -0
  357. package/dist-types/exports/input-email-test-suites.d.ts.map +1 -0
  358. package/dist-types/exports/input-file-test-suites.d.ts +2 -0
  359. package/dist-types/exports/input-file-test-suites.d.ts.map +1 -0
  360. package/dist-types/exports/input-iban-test-suites.d.ts +2 -0
  361. package/dist-types/exports/input-iban-test-suites.d.ts.map +1 -0
  362. package/dist-types/exports/input-range-test-suites.d.ts +2 -0
  363. package/dist-types/exports/input-range-test-suites.d.ts.map +1 -0
  364. package/dist-types/exports/input-stepper-suites.d.ts +7 -2
  365. package/dist-types/exports/input-stepper-suites.d.ts.map +1 -1
  366. package/dist-types/exports/input-test-suites.d.ts +2 -0
  367. package/dist-types/exports/input-test-suites.d.ts.map +1 -0
  368. package/dist-types/exports/menu-test-suites.d.ts.map +1 -1
  369. package/dist-types/exports/pagination-test-suites.d.ts +2 -0
  370. package/dist-types/exports/pagination-test-suites.d.ts.map +1 -0
  371. package/dist-types/exports/progress-indicator-test-suites.d.ts +2 -0
  372. package/dist-types/exports/progress-indicator-test-suites.d.ts.map +1 -0
  373. package/dist-types/exports/radio-group-test-suites.d.ts +3 -0
  374. package/dist-types/exports/radio-group-test-suites.d.ts.map +1 -0
  375. package/dist-types/exports/select-rich-test-suites.d.ts +4 -0
  376. package/dist-types/exports/select-rich-test-suites.d.ts.map +1 -0
  377. package/dist-types/exports/select-test-suites.d.ts +2 -0
  378. package/dist-types/exports/select-test-suites.d.ts.map +1 -0
  379. package/dist-types/exports/steps-test-suites.d.ts +3 -0
  380. package/dist-types/exports/steps-test-suites.d.ts.map +1 -0
  381. package/dist-types/exports/switch-test-suites.d.ts +2 -0
  382. package/dist-types/exports/switch-test-suites.d.ts.map +1 -0
  383. package/dist-types/exports/tabs-test-suites.d.ts +2 -0
  384. package/dist-types/exports/tabs-test-suites.d.ts.map +1 -0
  385. package/dist-types/exports/textarea-test-suites.d.ts +2 -0
  386. package/dist-types/exports/textarea-test-suites.d.ts.map +1 -0
  387. package/dist-types/exports/tooltip-test-suites.d.ts +2 -0
  388. package/dist-types/exports/tooltip-test-suites.d.ts.map +1 -0
  389. package/dist-types/exports/types/test-suites.d.ts +4 -0
  390. package/dist-types/exports/types/test-suites.d.ts.map +1 -0
  391. package/dist-types/tsconfig.tsbuildinfo +1 -1
  392. package/drawer-test-suites.js +1 -0
  393. package/exports/accordion-test-suites.js +2 -0
  394. package/exports/collapsible-test-suites.js +1 -0
  395. package/exports/combobox-test-suites.js +2 -0
  396. package/exports/dialog-test-suites.js +2 -0
  397. package/exports/drawer-test-suites.js +1 -0
  398. package/exports/form-test-suites.js +2 -0
  399. package/exports/icon-test-suites.js +2 -0
  400. package/exports/input-amount-test-suites.js +2 -0
  401. package/exports/input-date-test-suites.js +2 -0
  402. package/exports/input-email-test-suites.js +2 -0
  403. package/exports/input-file-test-suites.js +2 -0
  404. package/exports/input-iban-test-suites.js +2 -0
  405. package/exports/input-range-test-suites.js +2 -0
  406. package/exports/input-stepper-suites.js +18 -4
  407. package/exports/input-test-suites.js +2 -0
  408. package/exports/menu-test-suites.js +3 -0
  409. package/exports/pagination-test-suites.js +1 -0
  410. package/exports/progress-indicator-test-suites.js +2 -0
  411. package/exports/radio-group-test-suites.js +3 -0
  412. package/exports/select-rich-test-suites.js +4 -0
  413. package/exports/select-test-suites.js +1 -0
  414. package/exports/steps-test-suites.js +3 -0
  415. package/exports/switch-test-suites.js +2 -0
  416. package/exports/tabs-test-suites.js +2 -0
  417. package/exports/textarea-test-suites.js +2 -0
  418. package/exports/tooltip-test-suites.js +2 -0
  419. package/exports/types/test-suites.ts +3 -0
  420. package/form-test-suites.js +2 -0
  421. package/generated/awesome-phonenumber/provenance.json +1 -1
  422. package/icon-test-suites.js +2 -0
  423. package/input-amount-test-suites.js +2 -0
  424. package/input-date-test-suites.js +2 -0
  425. package/input-email-test-suites.js +2 -0
  426. package/input-file-test-suites.js +2 -0
  427. package/input-iban-test-suites.js +2 -0
  428. package/input-range-test-suites.js +2 -0
  429. package/input-stepper-suites.js +18 -4
  430. package/input-test-suites.js +2 -0
  431. package/menu-test-suites.js +3 -0
  432. package/package.json +11 -7
  433. package/pagination-test-suites.js +1 -0
  434. package/progress-indicator-test-suites.js +2 -0
  435. package/radio-group-test-suites.js +3 -0
  436. package/select-rich-test-suites.js +4 -0
  437. package/select-test-suites.js +1 -0
  438. package/steps-test-suites.js +3 -0
  439. package/switch-test-suites.js +2 -0
  440. package/tabs-test-suites.js +2 -0
  441. package/textarea-test-suites.js +2 -0
  442. package/tooltip-test-suites.js +2 -0
  443. package/vscode.html-custom-data.json +60 -5
  444. package/web-types.json +219 -1
  445. package/components/button/test/demos.screenshots-test.js +0 -100
  446. package/components/calendar/test/demos.screenshots-test.js +0 -118
  447. package/components/input-file/test/lion-selected-file-list.test.js +0 -199
  448. package/components/select-rich/test/demos.screenshots-test.js +0 -168
  449. package/components/switch/test/lion-switch-button.test.js +0 -204
  450. package/dist-types/components/button/test/demos.screenshots-test.d.ts +0 -2
  451. package/dist-types/components/button/test/demos.screenshots-test.d.ts.map +0 -1
  452. package/dist-types/components/calendar/test/demos.screenshots-test.d.ts +0 -2
  453. package/dist-types/components/calendar/test/demos.screenshots-test.d.ts.map +0 -1
  454. package/dist-types/components/input-file/test/lion-selected-file-list.test.d.ts +0 -5
  455. package/dist-types/components/input-file/test/lion-selected-file-list.test.d.ts.map +0 -1
  456. package/dist-types/components/select-rich/test/demos.screenshots-test.d.ts +0 -2
  457. package/dist-types/components/select-rich/test/demos.screenshots-test.d.ts.map +0 -1
  458. package/dist-types/components/switch/test/lion-switch-button.test.d.ts +0 -4
  459. package/dist-types/components/switch/test/lion-switch-button.test.d.ts.map +0 -1
  460. package/docs/components/accordion/api-table.md +0 -53
  461. package/docs/components/accordion/index.md +0 -13
  462. package/docs/components/accordion/overview.md +0 -141
  463. package/docs/components/accordion/reference.md +0 -59
  464. package/docs/components/accordion/use-cases.md +0 -303
  465. package/docs/components/button/api-table.md +0 -84
  466. package/docs/components/button/assets/bootstrap-button.js +0 -245
  467. package/docs/components/button/examples.md +0 -42
  468. package/docs/components/button/extensions.md +0 -42
  469. package/docs/components/button/index.md +0 -13
  470. package/docs/components/button/overview.md +0 -42
  471. package/docs/components/button/src/icon.svg.js +0 -5
  472. package/docs/components/button/use-cases.md +0 -121
  473. package/docs/components/calendar/api-table.md +0 -56
  474. package/docs/components/calendar/index.md +0 -13
  475. package/docs/components/calendar/overview.md +0 -61
  476. package/docs/components/calendar/use-cases.md +0 -243
  477. package/docs/components/checkbox-group/api-table.md +0 -117
  478. package/docs/components/checkbox-group/index.md +0 -13
  479. package/docs/components/checkbox-group/overview.md +0 -51
  480. package/docs/components/checkbox-group/use-cases.md +0 -192
  481. package/docs/components/collapsible/api-table.md +0 -55
  482. package/docs/components/collapsible/assets/CustomCollapsible.js +0 -117
  483. package/docs/components/collapsible/assets/applyDemoCollapsibleStyles.js +0 -41
  484. package/docs/components/collapsible/examples.md +0 -95
  485. package/docs/components/collapsible/index.md +0 -13
  486. package/docs/components/collapsible/overview.md +0 -51
  487. package/docs/components/collapsible/use-cases.md +0 -91
  488. package/docs/components/combobox/api-table.md +0 -122
  489. package/docs/components/combobox/extensions.md +0 -174
  490. package/docs/components/combobox/index.md +0 -13
  491. package/docs/components/combobox/overview.md +0 -74
  492. package/docs/components/combobox/src/LinkMixin.js +0 -60
  493. package/docs/components/combobox/src/demo-selection-display.js +0 -164
  494. package/docs/components/combobox/src/gh-combobox/gh-button.js +0 -71
  495. package/docs/components/combobox/src/gh-combobox/gh-combobox.js +0 -515
  496. package/docs/components/combobox/src/google-combobox/assets/google-clear-icon.js +0 -9
  497. package/docs/components/combobox/src/google-combobox/assets/google-search-icon.js +0 -9
  498. package/docs/components/combobox/src/google-combobox/assets/google-voice-search-icon.js +0 -19
  499. package/docs/components/combobox/src/google-combobox/google-combobox.js +0 -461
  500. package/docs/components/combobox/src/lazyRender.js +0 -29
  501. package/docs/components/combobox/src/levenshtein.js +0 -95
  502. package/docs/components/combobox/src/md-combobox/MdFieldMixin.js +0 -345
  503. package/docs/components/combobox/src/md-combobox/md-combobox.js +0 -91
  504. package/docs/components/combobox/src/md-combobox/md-input.js +0 -5
  505. package/docs/components/combobox/src/md-combobox/style/load-roboto.js +0 -6
  506. package/docs/components/combobox/src/md-combobox/style/md-ripple.js +0 -82
  507. package/docs/components/combobox/src/wa-combobox/wa-combobox.js +0 -375
  508. package/docs/components/combobox/use-cases.md +0 -561
  509. package/docs/components/dialog/api-table.md +0 -61
  510. package/docs/components/dialog/index.md +0 -13
  511. package/docs/components/dialog/overview.md +0 -71
  512. package/docs/components/dialog/src/demoStyle.js +0 -57
  513. package/docs/components/dialog/src/external-dialog.js +0 -32
  514. package/docs/components/dialog/src/slots-dialog-content.js +0 -32
  515. package/docs/components/dialog/src/styled-dialog-content.js +0 -38
  516. package/docs/components/dialog/use-cases.md +0 -199
  517. package/docs/components/drawer/api-table.md +0 -64
  518. package/docs/components/drawer/index.md +0 -13
  519. package/docs/components/drawer/overview.md +0 -80
  520. package/docs/components/drawer/src/demoStyle.js +0 -71
  521. package/docs/components/drawer/use-cases.md +0 -206
  522. package/docs/components/fieldset/api-table.md +0 -24
  523. package/docs/components/fieldset/index.md +0 -13
  524. package/docs/components/fieldset/overview.md +0 -61
  525. package/docs/components/fieldset/use-cases.md +0 -203
  526. package/docs/components/form/api-table.md +0 -12
  527. package/docs/components/form/index.md +0 -13
  528. package/docs/components/form/overview.md +0 -66
  529. package/docs/components/form/use-cases.md +0 -230
  530. package/docs/components/icon/api-table.md +0 -41
  531. package/docs/components/icon/assets/bugs/bug01.svg.js +0 -2
  532. package/docs/components/icon/assets/bugs/bug02.svg.js +0 -2
  533. package/docs/components/icon/assets/bugs/bug05.svg.js +0 -2
  534. package/docs/components/icon/assets/bugs/bug06.svg.js +0 -2
  535. package/docs/components/icon/assets/bugs/bug08.svg.js +0 -2
  536. package/docs/components/icon/assets/bugs/bug12.svg.js +0 -2
  537. package/docs/components/icon/assets/bugs/bug19.svg.js +0 -2
  538. package/docs/components/icon/assets/bugs/bug23.svg.js +0 -2
  539. package/docs/components/icon/assets/bugs/bug24.svg.js +0 -2
  540. package/docs/components/icon/assets/iconset-bugs.js +0 -11
  541. package/docs/components/icon/assets/iconset-misc.js +0 -3
  542. package/docs/components/icon/assets/iconset-space.js +0 -31
  543. package/docs/components/icon/assets/misc/arrowLeft.svg.js +0 -6
  544. package/docs/components/icon/assets/space/aliens-spaceship.svg.js +0 -2
  545. package/docs/components/icon/assets/space/meteor.svg.js +0 -2
  546. package/docs/components/icon/assets/space/moon-flag.svg.js +0 -2
  547. package/docs/components/icon/assets/space/moon.svg.js +0 -2
  548. package/docs/components/icon/assets/space/night.svg.js +0 -2
  549. package/docs/components/icon/assets/space/orbit.svg.js +0 -2
  550. package/docs/components/icon/assets/space/planet.svg.js +0 -2
  551. package/docs/components/icon/assets/space/robot.svg.js +0 -2
  552. package/docs/components/icon/assets/space/rocket.svg.js +0 -2
  553. package/docs/components/icon/assets/space/satellite.svg.js +0 -2
  554. package/docs/components/icon/assets/space/signal.svg.js +0 -2
  555. package/docs/components/icon/assets/space/space-helmet.svg.js +0 -2
  556. package/docs/components/icon/assets/space/sun.svg.js +0 -2
  557. package/docs/components/icon/assets/space/telescope.svg.js +0 -2
  558. package/docs/components/icon/index.md +0 -13
  559. package/docs/components/icon/overview.md +0 -52
  560. package/docs/components/icon/use-cases.md +0 -92
  561. package/docs/components/index.md +0 -17
  562. package/docs/components/input/api-table.md +0 -52
  563. package/docs/components/input/index.md +0 -13
  564. package/docs/components/input/overview.md +0 -48
  565. package/docs/components/input/use-cases.md +0 -90
  566. package/docs/components/input-amount/api-table.md +0 -66
  567. package/docs/components/input-amount/index.md +0 -13
  568. package/docs/components/input-amount/overview.md +0 -61
  569. package/docs/components/input-amount/use-cases.md +0 -147
  570. package/docs/components/input-amount-dropdown/api-table.md +0 -87
  571. package/docs/components/input-amount-dropdown/index.md +0 -13
  572. package/docs/components/input-amount-dropdown/use-cases.md +0 -103
  573. package/docs/components/input-date/api-table.md +0 -58
  574. package/docs/components/input-date/index.md +0 -13
  575. package/docs/components/input-date/overview.md +0 -51
  576. package/docs/components/input-date/use-cases.md +0 -96
  577. package/docs/components/input-datepicker/api-table.md +0 -99
  578. package/docs/components/input-datepicker/index.md +0 -13
  579. package/docs/components/input-datepicker/overview.md +0 -54
  580. package/docs/components/input-datepicker/use-cases.md +0 -117
  581. package/docs/components/input-email/api-table.md +0 -53
  582. package/docs/components/input-email/index.md +0 -13
  583. package/docs/components/input-email/overview.md +0 -44
  584. package/docs/components/input-email/use-cases.md +0 -60
  585. package/docs/components/input-file/api-table.md +0 -157
  586. package/docs/components/input-file/index.md +0 -13
  587. package/docs/components/input-file/overview.md +0 -44
  588. package/docs/components/input-file/use-cases.md +0 -454
  589. package/docs/components/input-iban/api-table.md +0 -54
  590. package/docs/components/input-iban/index.md +0 -13
  591. package/docs/components/input-iban/overview.md +0 -49
  592. package/docs/components/input-iban/use-cases.md +0 -114
  593. package/docs/components/input-range/api-table.md +0 -71
  594. package/docs/components/input-range/index.md +0 -13
  595. package/docs/components/input-range/overview.md +0 -44
  596. package/docs/components/input-range/use-cases.md +0 -126
  597. package/docs/components/input-stepper/api-table.md +0 -74
  598. package/docs/components/input-stepper/index.md +0 -13
  599. package/docs/components/input-stepper/overview.md +0 -48
  600. package/docs/components/input-stepper/use-cases.md +0 -155
  601. package/docs/components/input-tel/api-table.md +0 -77
  602. package/docs/components/input-tel/index.md +0 -13
  603. package/docs/components/input-tel/overview.md +0 -74
  604. package/docs/components/input-tel/src/h-region-code-table.js +0 -109
  605. package/docs/components/input-tel/use-cases.md +0 -319
  606. package/docs/components/input-tel-dropdown/api-table.md +0 -85
  607. package/docs/components/input-tel-dropdown/extensions.md +0 -44
  608. package/docs/components/input-tel-dropdown/index.md +0 -13
  609. package/docs/components/input-tel-dropdown/overview.md +0 -50
  610. package/docs/components/input-tel-dropdown/src/intl-input-tel-dropdown.js +0 -138
  611. package/docs/components/input-tel-dropdown/use-cases.md +0 -171
  612. package/docs/components/listbox/api-table.md +0 -65
  613. package/docs/components/listbox/index.md +0 -13
  614. package/docs/components/listbox/overview.md +0 -54
  615. package/docs/components/listbox/src/listboxData.js +0 -131
  616. package/docs/components/listbox/use-cases.md +0 -154
  617. package/docs/components/menu/api-table.md +0 -44
  618. package/docs/components/menu/applyMenuDemoStyles.js +0 -173
  619. package/docs/components/menu/extensions.md +0 -262
  620. package/docs/components/menu/index.md +0 -13
  621. package/docs/components/menu/migration.md +0 -129
  622. package/docs/components/menu/overview.md +0 -448
  623. package/docs/components/menu/src/DemoNavigationBar.js +0 -441
  624. package/docs/components/menu/src/DemoSpinButton.js +0 -215
  625. package/docs/components/menu/src/DemoToolbar.js +0 -101
  626. package/docs/components/menu/src/DemoTree.js +0 -55
  627. package/docs/components/menu/src/navigation-bar-types.js +0 -41
  628. package/docs/components/menu/use-cases.md +0 -74
  629. package/docs/components/pagination/api-table.md +0 -45
  630. package/docs/components/pagination/index.md +0 -13
  631. package/docs/components/pagination/overview.md +0 -41
  632. package/docs/components/pagination/use-cases.md +0 -113
  633. package/docs/components/progress-indicator/api-table.md +0 -48
  634. package/docs/components/progress-indicator/assets/my-determinate-progress-bar.js +0 -30
  635. package/docs/components/progress-indicator/assets/my-indeterminate-progress-spinner.js +0 -55
  636. package/docs/components/progress-indicator/examples.md +0 -137
  637. package/docs/components/progress-indicator/index.md +0 -13
  638. package/docs/components/progress-indicator/overview.md +0 -48
  639. package/docs/components/radio-group/api-table.md +0 -63
  640. package/docs/components/radio-group/index.md +0 -13
  641. package/docs/components/radio-group/overview.md +0 -58
  642. package/docs/components/radio-group/use-cases.md +0 -146
  643. package/docs/components/select/api-table.md +0 -51
  644. package/docs/components/select/index.md +0 -13
  645. package/docs/components/select/overview.md +0 -56
  646. package/docs/components/select/use-cases.md +0 -128
  647. package/docs/components/select-rich/api-table.md +0 -123
  648. package/docs/components/select-rich/examples.md +0 -43
  649. package/docs/components/select-rich/index.md +0 -13
  650. package/docs/components/select-rich/overview.md +0 -55
  651. package/docs/components/select-rich/src/flagStyles.js +0 -1250
  652. package/docs/components/select-rich/src/intl-select-rich.js +0 -194
  653. package/docs/components/select-rich/src/regionMetaList.js +0 -1717
  654. package/docs/components/select-rich/use-cases.md +0 -387
  655. package/docs/components/steps/api-table.md +0 -92
  656. package/docs/components/steps/index.md +0 -13
  657. package/docs/components/steps/overview.md +0 -77
  658. package/docs/components/steps/use-cases.md +0 -181
  659. package/docs/components/switch/api-table.md +0 -82
  660. package/docs/components/switch/index.md +0 -13
  661. package/docs/components/switch/overview.md +0 -87
  662. package/docs/components/switch/reference.md +0 -51
  663. package/docs/components/switch/use-cases.md +0 -88
  664. package/docs/components/tabs/api-table.md +0 -51
  665. package/docs/components/tabs/examples.md +0 -35
  666. package/docs/components/tabs/index.md +0 -13
  667. package/docs/components/tabs/overview.md +0 -60
  668. package/docs/components/tabs/src/lea-tab-panel.js +0 -26
  669. package/docs/components/tabs/src/lea-tab.js +0 -50
  670. package/docs/components/tabs/src/lea-tabs.js +0 -28
  671. package/docs/components/tabs/use-cases.md +0 -231
  672. package/docs/components/textarea/api-table.md +0 -57
  673. package/docs/components/textarea/index.md +0 -13
  674. package/docs/components/textarea/overview.md +0 -47
  675. package/docs/components/textarea/use-cases.md +0 -107
  676. package/docs/components/tooltip/api-table.md +0 -62
  677. package/docs/components/tooltip/examples.md +0 -51
  678. package/docs/components/tooltip/index.md +0 -13
  679. package/docs/components/tooltip/overview.md +0 -56
  680. package/docs/components/tooltip/use-cases.md +0 -275
  681. package/docs/fundamentals/index.md +0 -19
  682. package/docs/fundamentals/node-tools/babel-plugin-extend-docs/index.md +0 -13
  683. package/docs/fundamentals/node-tools/babel-plugin-extend-docs/overview.md +0 -246
  684. package/docs/fundamentals/node-tools/index.md +0 -11
  685. package/docs/fundamentals/node-tools/providence-analytics/index.md +0 -13
  686. package/docs/fundamentals/node-tools/providence-analytics/overview.md +0 -108
  687. package/docs/fundamentals/node-tools/publish-docs/index.md +0 -13
  688. package/docs/fundamentals/node-tools/publish-docs/overview.md +0 -149
  689. package/docs/fundamentals/node-tools/remark-extend/index.md +0 -13
  690. package/docs/fundamentals/node-tools/remark-extend/overview.md +0 -287
  691. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/index.md +0 -13
  692. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +0 -298
  693. package/docs/fundamentals/rationales/TypeScript.md +0 -123
  694. package/docs/fundamentals/rationales/accessibility.md +0 -40
  695. package/docs/fundamentals/rationales/index.md +0 -11
  696. package/docs/fundamentals/rationales/side-effects.md +0 -215
  697. package/docs/fundamentals/rationales/versioning.md +0 -47
  698. package/docs/fundamentals/systems/core/SlotMixin.md +0 -168
  699. package/docs/fundamentals/systems/core/api-table.md +0 -43
  700. package/docs/fundamentals/systems/core/index.md +0 -15
  701. package/docs/fundamentals/systems/core/overview.md +0 -98
  702. package/docs/fundamentals/systems/form/api-table.md +0 -40
  703. package/docs/fundamentals/systems/form/assets/h-output.js +0 -156
  704. package/docs/fundamentals/systems/form/formatting-and-parsing.md +0 -258
  705. package/docs/fundamentals/systems/form/index.md +0 -15
  706. package/docs/fundamentals/systems/form/interaction-states.md +0 -195
  707. package/docs/fundamentals/systems/form/model-value.md +0 -57
  708. package/docs/fundamentals/systems/form/overview.md +0 -95
  709. package/docs/fundamentals/systems/form/styling.md +0 -92
  710. package/docs/fundamentals/systems/form/use-cases.md +0 -187
  711. package/docs/fundamentals/systems/form/validate.md +0 -985
  712. package/docs/fundamentals/systems/icon/index.md +0 -15
  713. package/docs/fundamentals/systems/icon/overview.md +0 -137
  714. package/docs/fundamentals/systems/index.md +0 -11
  715. package/docs/fundamentals/systems/localize/api-table.md +0 -72
  716. package/docs/fundamentals/systems/localize/assets/all-locales.js +0 -437
  717. package/docs/fundamentals/systems/localize/assets/en-AU.js +0 -6
  718. package/docs/fundamentals/systems/localize/assets/en-GB.js +0 -6
  719. package/docs/fundamentals/systems/localize/assets/en-US.js +0 -6
  720. package/docs/fundamentals/systems/localize/assets/en.js +0 -4
  721. package/docs/fundamentals/systems/localize/assets/nl-BE.js +0 -6
  722. package/docs/fundamentals/systems/localize/assets/nl-NL.js +0 -6
  723. package/docs/fundamentals/systems/localize/assets/nl.js +0 -4
  724. package/docs/fundamentals/systems/localize/dates.md +0 -208
  725. package/docs/fundamentals/systems/localize/index.md +0 -15
  726. package/docs/fundamentals/systems/localize/numbers.md +0 -230
  727. package/docs/fundamentals/systems/localize/overview.md +0 -61
  728. package/docs/fundamentals/systems/localize/rationale.md +0 -75
  729. package/docs/fundamentals/systems/localize/text.md +0 -139
  730. package/docs/fundamentals/systems/localize/use-cases.md +0 -334
  731. package/docs/fundamentals/systems/overlays/_configuration-positioning.md +0 -118
  732. package/docs/fundamentals/systems/overlays/_edge-cases.md +0 -149
  733. package/docs/fundamentals/systems/overlays/api-table.md +0 -174
  734. package/docs/fundamentals/systems/overlays/assets/applyDemoOverlayStyles.js +0 -23
  735. package/docs/fundamentals/systems/overlays/assets/demo-el-using-overlaymixin.js +0 -104
  736. package/docs/fundamentals/systems/overlays/assets/demo-overlay-backdrop.js +0 -47
  737. package/docs/fundamentals/systems/overlays/assets/demo-overlay-positioning.mjs +0 -537
  738. package/docs/fundamentals/systems/overlays/assets/umbrella-form.js +0 -140
  739. package/docs/fundamentals/systems/overlays/configuration.md +0 -516
  740. package/docs/fundamentals/systems/overlays/index.md +0 -15
  741. package/docs/fundamentals/systems/overlays/overview.md +0 -126
  742. package/docs/fundamentals/systems/overlays/rationale.md +0 -242
  743. package/docs/fundamentals/systems/overlays/use-cases.md +0 -717
  744. package/docs/fundamentals/tools/ajax/index.md +0 -15
  745. package/docs/fundamentals/tools/ajax/overview.md +0 -269
  746. package/docs/fundamentals/tools/ajax/use-cases.md +0 -469
  747. package/docs/fundamentals/tools/helpers/action-logger.md +0 -175
  748. package/docs/fundamentals/tools/helpers/index.md +0 -15
  749. package/docs/fundamentals/tools/helpers/overview.md +0 -43
  750. package/docs/fundamentals/tools/index.md +0 -11
  751. package/docs/fundamentals/tools/singleton-manager/example-complex/demo-app.js +0 -95
  752. package/docs/fundamentals/tools/singleton-manager/example-complex/index.md +0 -34
  753. package/docs/fundamentals/tools/singleton-manager/example-complex/node_modules/overlays/index.js +0 -30
  754. package/docs/fundamentals/tools/singleton-manager/example-complex/node_modules/overlays/instance.js +0 -4
  755. package/docs/fundamentals/tools/singleton-manager/example-complex/node_modules/page-a/page-a.js +0 -35
  756. package/docs/fundamentals/tools/singleton-manager/example-complex/node_modules/page-b/node_modules/overlays/index.js +0 -30
  757. package/docs/fundamentals/tools/singleton-manager/example-complex/node_modules/page-b/node_modules/overlays/instance.js +0 -4
  758. package/docs/fundamentals/tools/singleton-manager/example-complex/node_modules/page-b/page-b.js +0 -49
  759. package/docs/fundamentals/tools/singleton-manager/example-complex/overlayCompatibility.js +0 -69
  760. package/docs/fundamentals/tools/singleton-manager/example-fail/demo-app.js +0 -93
  761. package/docs/fundamentals/tools/singleton-manager/example-fail/index.md +0 -32
  762. package/docs/fundamentals/tools/singleton-manager/example-fail/node_modules/overlays/index.js +0 -30
  763. package/docs/fundamentals/tools/singleton-manager/example-fail/node_modules/overlays/instance.js +0 -3
  764. package/docs/fundamentals/tools/singleton-manager/example-fail/node_modules/page-c/page-c.js +0 -34
  765. package/docs/fundamentals/tools/singleton-manager/example-fail/node_modules/page-d/node_modules/overlays/index.js +0 -30
  766. package/docs/fundamentals/tools/singleton-manager/example-fail/node_modules/page-d/node_modules/overlays/instance.js +0 -3
  767. package/docs/fundamentals/tools/singleton-manager/example-fail/node_modules/page-d/page-d.js +0 -34
  768. package/docs/fundamentals/tools/singleton-manager/example-success/demo-app.js +0 -95
  769. package/docs/fundamentals/tools/singleton-manager/example-success/index.md +0 -31
  770. package/docs/fundamentals/tools/singleton-manager/example-success/node_modules/overlays/index.js +0 -30
  771. package/docs/fundamentals/tools/singleton-manager/example-success/node_modules/overlays/instance.js +0 -4
  772. package/docs/fundamentals/tools/singleton-manager/example-success/node_modules/page-e/page-e.js +0 -33
  773. package/docs/fundamentals/tools/singleton-manager/example-success/node_modules/page-f/node_modules/overlays/index.js +0 -30
  774. package/docs/fundamentals/tools/singleton-manager/example-success/node_modules/page-f/node_modules/overlays/instance.js +0 -4
  775. package/docs/fundamentals/tools/singleton-manager/example-success/node_modules/page-f/page-f.js +0 -34
  776. package/docs/fundamentals/tools/singleton-manager/example-success/overlayCompatibility.js +0 -23
  777. package/docs/fundamentals/tools/singleton-manager/index.md +0 -15
  778. package/docs/fundamentals/tools/singleton-manager/overview.md +0 -317
  779. package/docs/guides/how-to/create-a-custom-field.md +0 -212
  780. package/docs/guides/how-to/create-a-form.md +0 -101
  781. package/docs/guides/how-to/extend-a-native-input.md +0 -78
  782. package/docs/guides/how-to/get-started.md +0 -116
  783. package/docs/guides/how-to/index.md +0 -25
  784. package/docs/guides/index.md +0 -44
  785. package/docs/guides/principles/definitions-and-terms.md +0 -53
  786. package/docs/guides/principles/index.md +0 -36
  787. package/docs/guides/principles/scoped-elements.md +0 -119
  788. package/docs/guides/principles/styling.md +0 -43
  789. package/docs/guides/principles/subclasser-apis.md +0 -40
@@ -0,0 +1,3665 @@
1
+ import { html } from 'lit/static-html.js';
2
+ import {
3
+ getComboboxMembers,
4
+ getFilteredOptionValues,
5
+ mimicKeyPress,
6
+ mimicUserTyping,
7
+ mimicUserTypingAdvanced,
8
+ } from '@lion/ui/combobox-test-helpers.js';
9
+ import { LionCombobox } from '@lion/ui/combobox.js';
10
+ import { browserDetection, SlotMixin } from '@lion/ui/core.js';
11
+ import '@lion/ui/define/lion-combobox.js';
12
+ import '@lion/ui/define/lion-listbox.js';
13
+ import '@lion/ui/define/lion-option.js';
14
+ import { Required, Unparseable } from '@lion/ui/form-core.js';
15
+ import { defineCE, expect, fixture, unsafeStatic, waitUntil } from '@open-wc/testing';
16
+ import { sendKeys } from '@web/test-runner-commands';
17
+ import { LitElement, nothing } from 'lit';
18
+ import sinon from 'sinon';
19
+ import { resolveSuiteTag } from '../../core/test-helpers/resolveSuiteTag.js';
20
+ import { isActiveElement } from '../../core/test-helpers/isActiveElement.js';
21
+
22
+ /**
23
+ * @typedef {import('@lion/ui/types/test-suites.js').SuiteElement} SuiteElement
24
+ * Runs the `lion-combobox` behavioural suite. The caller hands the elements it should run against;
25
+ * anything left out keeps Lion's own element.
26
+ * @param {import('@lion/ui/types/test-suites.js').SuiteConfig<{host: SuiteElement; option: SuiteElement}, {optionTagString?: any}>} [customConfig]
27
+ */
28
+ export function runComboboxSuite({
29
+ classes = {},
30
+ tagString: legacyTagString,
31
+ optionTagString,
32
+ } = {}) {
33
+ const hostTag = unsafeStatic(resolveSuiteTag(classes.host, legacyTagString, 'lion-combobox'));
34
+ const tagNameOption = resolveSuiteTag(classes.option, optionTagString, 'lion-option');
35
+ const tagOption = unsafeStatic(tagNameOption);
36
+
37
+ /**
38
+ * @typedef {import('../types/SelectionDisplay.js').SelectionDisplay} SelectionDisplay
39
+ * @typedef {import('@lion/ui/listbox.js').LionOption} LionOption
40
+ */
41
+
42
+ /**
43
+ * @param {{ autocomplete?:'none'|'list'|'both', matchMode?:'begin'|'all', disabled?: boolean, readonly?: boolean }} config
44
+ */
45
+ async function fruitFixture({ autocomplete, matchMode, disabled, readonly } = {}) {
46
+ const el =
47
+ /** @type {LionCombobox} */
48
+ await fixture(html`
49
+ <${hostTag} label="Search" name="foo">
50
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
51
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
52
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
53
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
54
+ </${hostTag}>
55
+ `);
56
+ if (autocomplete) {
57
+ el.autocomplete = autocomplete;
58
+ }
59
+ if (matchMode) {
60
+ el.matchMode = matchMode;
61
+ }
62
+ if (disabled) {
63
+ el.disabled = disabled;
64
+ }
65
+ if (readonly) {
66
+ el.readOnly = readonly;
67
+ }
68
+ await el.updateComplete;
69
+ return [el, el.formElements];
70
+ }
71
+ describe('lion-combobox', () => {
72
+ describe('Rendering on API change', () => {
73
+ it('renders when options changed', async () => {
74
+ const entries = [
75
+ {
76
+ id: '1',
77
+ label: 'Apple',
78
+ },
79
+ {
80
+ id: '2',
81
+ label: 'Artichoke',
82
+ },
83
+ {
84
+ id: '3',
85
+ label: 'Asparagus',
86
+ },
87
+ {
88
+ id: '4',
89
+ label: 'Banana',
90
+ },
91
+ {
92
+ id: '5',
93
+ label: 'Pineapple',
94
+ },
95
+ ];
96
+ class Wrapper extends LitElement {
97
+ static properties = {
98
+ ...super.properties,
99
+ entries: {
100
+ type: Array,
101
+ },
102
+ };
103
+ constructor() {
104
+ super();
105
+ this.entries = entries;
106
+ }
107
+ render() {
108
+ return html`<${hostTag} name="foo">
109
+ ${this.entries.map((entry) => html`<${tagOption} .choiceValue="${entry.id}">${entry.label}</${tagOption}>`)}
110
+ </${hostTag}>`;
111
+ }
112
+ }
113
+ const tagString = defineCE(Wrapper);
114
+ const tag = unsafeStatic(tagString);
115
+ const wrapperElement = /** @type {Wrapper} */ await fixture(html`<${tag}></${tag}>`);
116
+ await waitUntil(
117
+ () =>
118
+ wrapperElement?.shadowRoot
119
+ ?.querySelector('lion-options')
120
+ ?.querySelectorAll(tagNameOption).length === entries.length,
121
+ );
122
+ wrapperElement.entries = [
123
+ {
124
+ id: '4',
125
+ label: 'Banana',
126
+ },
127
+ ];
128
+ await waitUntil(
129
+ () =>
130
+ wrapperElement?.shadowRoot
131
+ ?.querySelector('lion-options')
132
+ ?.querySelectorAll(tagNameOption).length === wrapperElement.entries.length,
133
+ );
134
+ const optionElements = wrapperElement.shadowRoot?.querySelectorAll(tagNameOption);
135
+ expect(optionElements?.length).to.equal(wrapperElement.entries.length);
136
+ const optionElement = optionElements?.item(0);
137
+ expect(optionElement?.textContent).to.equal('Banana');
138
+ });
139
+ });
140
+ describe('Options visibility', () => {
141
+ it('hides options when text in input node is cleared after typing something by default', async () => {
142
+ const el =
143
+ /** @type {LionCombobox} */
144
+ await fixture(html`
145
+ <${hostTag} name="foo">
146
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
147
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
148
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
149
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
150
+ </${hostTag}>
151
+ `);
152
+ const options = el.formElements;
153
+ const visibleOptions = () =>
154
+ options.filter((o) => o.getAttribute('aria-hidden') !== 'true');
155
+ async function performChecks() {
156
+ await mimicUserTyping(el, 'c');
157
+ await el.updateComplete;
158
+ expect(visibleOptions().length).to.equal(4, `autocompleteMode is ${el.autocomplete}`);
159
+ // Remove [hard], [c]
160
+ await mimicUserTypingAdvanced(el, ['Backspace', 'Backspace']);
161
+ await el.updateComplete;
162
+ expect(visibleOptions().length).to.equal(0, `autocompleteMode is ${el.autocomplete}`);
163
+ }
164
+ el.autocomplete = 'none';
165
+ await performChecks();
166
+ el.autocomplete = 'list';
167
+ await performChecks();
168
+ el.autocomplete = 'inline';
169
+ await performChecks();
170
+ el.autocomplete = 'both';
171
+ await performChecks();
172
+ });
173
+ it('hides all options on reset()', async () => {
174
+ const el =
175
+ /** @type {LionCombobox} */
176
+ await fixture(html`
177
+ <${hostTag} name="foo">
178
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
179
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
180
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
181
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
182
+ </${hostTag}>
183
+ `);
184
+ const options = el.formElements;
185
+ const visibleOptions = () => options.filter((o) => o.style.display !== 'none');
186
+ await mimicUserTyping(el, 'cha');
187
+ await el.updateComplete;
188
+ expect(visibleOptions().length).to.equal(1);
189
+ el.reset();
190
+ await el.updateComplete;
191
+ expect(visibleOptions().length).to.equal(0);
192
+ });
193
+ it('shows all options on reset() when showAllOnEmpty is set to true and overlay was open', async () => {
194
+ const el =
195
+ /** @type {LionCombobox} */
196
+ await fixture(html`
197
+ <${hostTag} name="foo" show-all-on-empty>
198
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
199
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
200
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
201
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
202
+ </${hostTag}>
203
+ `);
204
+ const options = el.formElements;
205
+ const visibleOptions = () =>
206
+ options.filter((o) => o.getAttribute('aria-hidden') !== 'true');
207
+ await mimicUserTyping(el, 'cha');
208
+ await el.updateComplete;
209
+ expect(visibleOptions().length).to.equal(1);
210
+ expect(el.opened).to.be.true;
211
+ el.reset();
212
+ await el.updateComplete;
213
+ expect(visibleOptions().length).to.equal(4);
214
+ });
215
+ it('hides listbox on click/enter (when multiple-choice is false)', async () => {
216
+ const el =
217
+ /** @type {LionCombobox} */
218
+ await fixture(html`
219
+ <${hostTag} name="foo">
220
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
221
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
222
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
223
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
224
+ </${hostTag}>
225
+ `);
226
+ const { _listboxNode } = getComboboxMembers(el);
227
+ async function open() {
228
+ await mimicUserTyping(el, 'ch');
229
+ return el.updateComplete;
230
+ }
231
+ await open();
232
+ expect(el.opened).to.be.true;
233
+ const visibleOptions = el.formElements.filter((o) => o.style.display !== 'none');
234
+ visibleOptions[0].click();
235
+ expect(el.opened).to.be.false;
236
+ await open();
237
+ expect(el.opened).to.be.true;
238
+ el.activeIndex = el.formElements.indexOf(visibleOptions[0]);
239
+ mimicKeyPress(_listboxNode, 'Enter');
240
+ await el.updateComplete;
241
+ expect(el.opened).to.be.false;
242
+ });
243
+ describe('With ".showAllOnEmpty"', () => {
244
+ it('keeps showing options when text in input node is cleared after typing something', async () => {
245
+ const el =
246
+ /** @type {LionCombobox} */
247
+ await fixture(html`
248
+ <${hostTag} name="foo" autocomplete="list" show-all-on-empty>
249
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
250
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
251
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
252
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
253
+ </${hostTag}>
254
+ `);
255
+ const options = el.formElements;
256
+ const visibleOptions = () =>
257
+ options.filter((o) => o.getAttribute('aria-hidden') !== 'true');
258
+ async function performChecks() {
259
+ await mimicUserTyping(el, 'c');
260
+ await el.updateComplete;
261
+ expect(visibleOptions().length).to.equal(4);
262
+ await mimicUserTyping(el, '');
263
+ await el.updateComplete;
264
+ expect(visibleOptions().length).to.equal(options.length);
265
+ }
266
+ el.autocomplete = 'none';
267
+ await performChecks();
268
+ el.autocomplete = 'list';
269
+ await performChecks();
270
+ el.autocomplete = 'inline';
271
+ await performChecks();
272
+ el.autocomplete = 'both';
273
+ await performChecks();
274
+ });
275
+ it('shows overlay on click', async () => {
276
+ const el =
277
+ /** @type {LionCombobox} */
278
+ await fixture(html`
279
+ <${hostTag} name="foo" .showAllOnEmpty="${true}">
280
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
281
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
282
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
283
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
284
+ </${hostTag}>
285
+ `);
286
+ const { _inputNode } = getComboboxMembers(el);
287
+ expect(el.opened).to.be.false;
288
+ _inputNode.dispatchEvent(
289
+ new Event('click', {
290
+ bubbles: true,
291
+ composed: true,
292
+ }),
293
+ );
294
+ await el.updateComplete;
295
+ expect(el.opened).to.be.true;
296
+ });
297
+ it('hides overlay on [Escape] after being opened', async () => {
298
+ const el =
299
+ /** @type {LionCombobox} */
300
+ await fixture(html`
301
+ <${hostTag} name="foo" .showAllOnEmpty="${true}" opened>
302
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
303
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
304
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
305
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
306
+ </${hostTag}>
307
+ `);
308
+ await el.updateComplete;
309
+ const { _inputNode } = getComboboxMembers(el);
310
+ _inputNode.dispatchEvent(
311
+ new KeyboardEvent('keyup', {
312
+ key: 'Escape',
313
+ }),
314
+ );
315
+ await el.updateComplete;
316
+ expect(el.opened).to.be.false;
317
+ });
318
+ it('hides listbox on click/enter (when multiple-choice is false)', async () => {
319
+ const el =
320
+ /** @type {LionCombobox} */
321
+ await fixture(html`
322
+ <${hostTag} name="foo" .showAllOnEmpty="${true}">
323
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
324
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
325
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
326
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
327
+ </${hostTag}>
328
+ `);
329
+ const { _listboxNode, _inputNode } = getComboboxMembers(el);
330
+ async function open() {
331
+ await mimicUserTyping(el, 'ch');
332
+ return el.updateComplete;
333
+ }
334
+
335
+ // FIXME: temp disable for Safari. Works locally, not in build
336
+ const isSafari = (() => {
337
+ const ua = navigator.userAgent.toLowerCase();
338
+ return ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1;
339
+ })();
340
+ if (isSafari) {
341
+ return;
342
+ }
343
+ await open();
344
+ expect(el.opened).to.be.true;
345
+ const visibleOptions = el.formElements.filter((o) => o.style.display !== 'none');
346
+ visibleOptions[0].click();
347
+ await el.updateComplete;
348
+ expect(el.opened).to.be.false;
349
+ _inputNode.value = '';
350
+ _inputNode.blur();
351
+ await open();
352
+ await el.updateComplete;
353
+ el.activeIndex = el.formElements.indexOf(visibleOptions[0]);
354
+ mimicKeyPress(_listboxNode, 'Enter');
355
+ expect(el.opened).to.be.false;
356
+ });
357
+ });
358
+ });
359
+ describe('Structure', () => {
360
+ it('has a listbox node', async () => {
361
+ const el =
362
+ /** @type {LionCombobox} */
363
+ await fixture(html`
364
+ <${hostTag} name="foo">
365
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
366
+ <${tagOption} .choiceValue="${'20'}">Item 2</${tagOption}>
367
+ </${hostTag}>
368
+ `);
369
+ const { _listboxNode } = getComboboxMembers(el);
370
+ expect(_listboxNode).to.exist;
371
+ // TODO: worked before, find out what has changed in ScopedElements
372
+ // expect(_listboxNode instanceof LionOptions).to.be.true;
373
+ expect(el.querySelector('[role=listbox]')).to.equal(_listboxNode);
374
+ });
375
+ it('has a textbox element', async () => {
376
+ const el =
377
+ /** @type {LionCombobox} */
378
+ await fixture(html`
379
+ <${hostTag} name="foo">
380
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
381
+ <${tagOption} .choiceValue="${'20'}">Item 2</${tagOption}>
382
+ </${hostTag}>
383
+ `);
384
+ const { _comboboxNode } = getComboboxMembers(el);
385
+ expect(_comboboxNode).to.exist;
386
+ expect(el.querySelector('[role=combobox]')).to.equal(_comboboxNode);
387
+ });
388
+ it('has validator "MatchesOption" applied by default', async () => {
389
+ const el =
390
+ /** @type {LionCombobox} */
391
+ await fixture(html`
392
+ <${hostTag} name="foo">
393
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
394
+ <${tagOption} .choiceValue="${'20'}">Item 2</${tagOption}>
395
+ </${hostTag}>
396
+ `);
397
+ await mimicUserTyping(el, '30');
398
+ await el.updateComplete;
399
+ expect(el.hasFeedbackFor).to.include('error');
400
+ expect(el.validationStates).to.have.property('error');
401
+ expect(el.validationStates.error).to.have.property('MatchesOption');
402
+ });
403
+ it('keeps slottable provided in `slots` getter as direct host child', async () => {
404
+ class MyEl extends SlotMixin(LionCombobox) {
405
+ // @ts-ignore
406
+ get slots() {
407
+ return {
408
+ ...super.slots,
409
+ _lazyRenderedSlot: () => ({
410
+ template: this.renderSlot
411
+ ? html`<span id="lazyRenderedSlotId">(Optional)</span>`
412
+ : html`${nothing}`,
413
+ renderAsDirectHostChild: true,
414
+ }),
415
+ };
416
+ }
417
+ _labelTemplate() {
418
+ return html`
419
+ <div class="form-field__label">
420
+ <slot name="label"></slot>
421
+ <slot name="_lazyRenderedSlot"></slot>
422
+ <slot></slot>
423
+ </div>
424
+ `;
425
+ }
426
+ constructor() {
427
+ super();
428
+ this.renderSlot = false;
429
+ }
430
+ }
431
+ const tagName = defineCE(MyEl);
432
+ const wrappingTag = unsafeStatic(tagName);
433
+ const el =
434
+ /** @type {MyEl} */
435
+ await fixture(html`
436
+ <${wrappingTag} label="my label">
437
+ <${tagOption} .choiceValue="${'1'}">${'one'}</${tagOption}>
438
+ </${wrappingTag}>
439
+ `);
440
+ await el.registrationComplete;
441
+ el.renderSlot = true;
442
+ await el.updateComplete;
443
+ const lazyRenderedSlot = el.querySelector('#lazyRenderedSlotId');
444
+ expect(lazyRenderedSlot?.parentElement === el).to.be.true;
445
+ });
446
+ });
447
+ describe('Values', () => {
448
+ it('syncs modelValue with textbox', async () => {
449
+ const el =
450
+ /** @type {LionCombobox} */
451
+ await fixture(html`
452
+ <${hostTag} name="foo" .modelValue="${'10'}">
453
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
454
+ <${tagOption} .choiceValue="${'20'}">Item 2</${tagOption}>
455
+ </${hostTag}>
456
+ `);
457
+ const { _inputNode } = getComboboxMembers(el);
458
+ expect(_inputNode.value).to.equal('10');
459
+ el.modelValue = '20';
460
+ await el.updateComplete;
461
+ expect(_inputNode.value).to.equal('20');
462
+ });
463
+ it('sets modelValue to Unparseable if no option is selected', async () => {
464
+ const el =
465
+ /** @type {LionCombobox} */
466
+ await fixture(html`
467
+ <${hostTag} name="foo" .modelValue="${'Artichoke'}">
468
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
469
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
470
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
471
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
472
+ </${hostTag}>
473
+ `);
474
+ const { _inputNode } = getComboboxMembers(el);
475
+ expect(el.modelValue).to.equal('Artichoke');
476
+ expect(el.formElements[0].checked).to.be.true;
477
+ el.setCheckedIndex(-1);
478
+ el.__shouldAutocompleteNextUpdate = true;
479
+ await el.updateComplete;
480
+ expect(el.modelValue instanceof Unparseable).to.be.true;
481
+ expect(el.modelValue.viewValue).to.equal('Artichoke');
482
+ expect(el.formElements[0].checked).to.be.false;
483
+ el.setCheckedIndex(-1);
484
+ _inputNode.value = 'Foo';
485
+ el.__shouldAutocompleteNextUpdate = true;
486
+ await el.updateComplete;
487
+ expect(el.modelValue instanceof Unparseable).to.be.true;
488
+ expect(el.modelValue.viewValue).to.equal('Foo');
489
+ expect(el.formElements[0].checked).to.be.false;
490
+ el.setCheckedIndex(0);
491
+ el.__shouldAutocompleteNextUpdate = true;
492
+ await el.updateComplete;
493
+ expect(el.modelValue instanceof Unparseable).to.be.false;
494
+ expect(el.modelValue).to.equal('Artichoke');
495
+ expect(el.formElements[0].checked).to.be.true;
496
+ });
497
+ it('sets modelValue to _inputNode.value if no option is selected when requireOptionMatch is false', async () => {
498
+ const el =
499
+ /** @type {LionCombobox} */
500
+ await fixture(html`
501
+ <${hostTag} name="foo" .modelValue="${'Artichoke'}" .requireOptionMatch="${false}">
502
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
503
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
504
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
505
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
506
+ </${hostTag}>
507
+ `);
508
+ const { _inputNode } = getComboboxMembers(el);
509
+ expect(el.modelValue).to.equal('Artichoke');
510
+ expect(el.formElements[0].checked).to.be.true;
511
+ el.setCheckedIndex(-1);
512
+ el.__shouldAutocompleteNextUpdate = true;
513
+ await el.updateComplete;
514
+ expect(el.modelValue).to.equal('Artichoke');
515
+ expect(el.formElements[0].checked).to.be.true;
516
+ el.setCheckedIndex(-1);
517
+ _inputNode.value = 'Foo';
518
+ el.__shouldAutocompleteNextUpdate = true;
519
+ await el.updateComplete;
520
+ expect(el.modelValue).to.equal('Foo');
521
+ expect(el.formElements[0].checked).to.be.false;
522
+ });
523
+ it('sets modelValue to empty array if no option is selected for multiple choice', async () => {
524
+ const el =
525
+ /** @type {LionCombobox} */
526
+ await fixture(html`
527
+ <${hostTag} name="foo" multiple-choice .modelValue="${['Artichoke']}">
528
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
529
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
530
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
531
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
532
+ </${hostTag}>
533
+ `);
534
+ expect(el.modelValue).to.deep.equal(['Artichoke']);
535
+ expect(el.formElements[0].checked).to.be.true;
536
+ el.setCheckedIndex([]);
537
+ await el.updateComplete;
538
+ expect(el.modelValue).to.deep.equal([]);
539
+ expect(el.formElements[0].checked).to.be.false;
540
+ });
541
+ it('clears modelValue and textbox value on clear()', async () => {
542
+ const el =
543
+ /** @type {LionCombobox} */
544
+ await fixture(html`
545
+ <${hostTag} name="foo" .modelValue="${'Artichoke'}">
546
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
547
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
548
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
549
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
550
+ </${hostTag}>
551
+ `);
552
+ const { _inputNode } = getComboboxMembers(el);
553
+ el.clear();
554
+ expect(el.modelValue).to.equal('');
555
+ expect(_inputNode.value).to.equal('');
556
+ const el2 =
557
+ /** @type {LionCombobox} */
558
+ await fixture(html`
559
+ <${hostTag} name="foo" multiple-choice .modelValue="${['Artichoke']}">
560
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
561
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
562
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
563
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
564
+ </${hostTag}>
565
+ `);
566
+ el2.clear();
567
+ expect(el2.modelValue).to.eql([]);
568
+ // @ts-ignore [allow-protected] in test
569
+ expect(el2._inputNode.value).to.equal('');
570
+ });
571
+ it('correctly emits event with an empty value when clear() is called', async () => {
572
+ const el =
573
+ /** @type {LionCombobox} */
574
+ await fixture(html`<${hostTag}>
575
+ <${tagOption} .choiceValue=${'red'}>Red</${tagOption}>
576
+ <${tagOption} .choiceValue=${'green'}>Green</${tagOption}>
577
+ <${tagOption} .choiceValue=${'blue'}>Blue</${tagOption}>
578
+ </${hostTag}>`);
579
+ el.modelValue = 'red';
580
+ await el.updateComplete;
581
+ el.addEventListener('model-value-changed', ({ target }) => {
582
+ expect(target).to.not.be.null;
583
+ const { modelValue, value } = /** @type {LionCombobox} */ target;
584
+ expect(value).to.equal('');
585
+ expect(modelValue).to.equal('');
586
+ });
587
+ el.clear();
588
+ await el.updateComplete;
589
+ });
590
+ it('updates option list after clear()', async () => {
591
+ const el =
592
+ /** @type {LionCombobox} */
593
+ await fixture(html`
594
+ <${hostTag} name="foo" multiple-choice>
595
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
596
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
597
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
598
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
599
+ </${hostTag}>
600
+ `);
601
+ const options = el.formElements;
602
+ await mimicUserTyping(el, 'a');
603
+ await el.updateComplete;
604
+ const visibleOptions = () => options.filter((o) => o.style.display !== 'none');
605
+ expect(visibleOptions().length).to.equal(3, 'after input');
606
+ el.clear();
607
+ await el.updateComplete;
608
+ expect(visibleOptions().length).to.equal(0, 'after clear');
609
+ });
610
+ it('keeps interaction states on clear()', async () => {
611
+ const el =
612
+ /** @type {LionCombobox} */
613
+ await fixture(html`
614
+ <${hostTag} name="foo">
615
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
616
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
617
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
618
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
619
+ </${hostTag}>
620
+ `);
621
+ await mimicUserTyping(el, 'a');
622
+ await el.updateComplete;
623
+ expect(el.dirty).to.equal(true);
624
+ el.clear();
625
+ await el.updateComplete;
626
+ expect(el.dirty).to.equal(true);
627
+ });
628
+ it('resets modelValue and textbox value on reset()', async () => {
629
+ const el =
630
+ /** @type {LionCombobox} */
631
+ await fixture(html`
632
+ <${hostTag} name="foo" .modelValue="${'Artichoke'}">
633
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
634
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
635
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
636
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
637
+ </${hostTag}>
638
+ `);
639
+ const { _inputNode } = getComboboxMembers(el);
640
+ el.modelValue = 'Chard';
641
+ el.reset();
642
+ expect(el.modelValue).to.equal('Artichoke');
643
+ expect(_inputNode.value).to.equal('Artichoke');
644
+ const el2 =
645
+ /** @type {LionCombobox} */
646
+ await fixture(html`
647
+ <${hostTag} name="foo" multiple-choice .modelValue="${['Artichoke']}">
648
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
649
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
650
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
651
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
652
+ </${hostTag}>
653
+ `);
654
+ el2.modelValue = ['Artichoke', 'Chard'];
655
+ el2.reset();
656
+ expect(el2.modelValue).to.deep.equal(['Artichoke']);
657
+ // @ts-ignore [allow-protected] in test
658
+ expect(el2._inputNode.value).to.equal('');
659
+ });
660
+ it('syncs textbox to modelValue', async () => {
661
+ for (const autocompleteMode of ['none', 'list', 'inline', 'both']) {
662
+ const el =
663
+ /** @type {LionCombobox} */
664
+ await fixture(html`
665
+ <${hostTag} name="foo" show-all-on-empty .autocomplete="${autocompleteMode}">
666
+ <${tagOption} .choiceValue="${'Aa'}" checked>Aa</${tagOption}>
667
+ <${tagOption} .choiceValue="${'Bb'}">Bb</${tagOption}>
668
+ </${hostTag}>
669
+ `);
670
+ const { _inputNode } = getComboboxMembers(el);
671
+ expect(_inputNode.value).to.equal('Aa', `autocompleteMode is ${autocompleteMode}`);
672
+ el.formElements[0].click();
673
+ await el.updateComplete;
674
+ expect(_inputNode.value).to.equal('Aa', `autocompleteMode is ${autocompleteMode}`);
675
+ expect(el.checkedIndex).to.equal(0, `autocompleteMode is ${autocompleteMode}`);
676
+ await mimicUserTyping(el, 'B');
677
+ await el.updateComplete;
678
+ if (autocompleteMode === 'none' || autocompleteMode === 'list') {
679
+ expect(el.modelValue).to.deep.equal(
680
+ {
681
+ type: 'unparseable',
682
+ viewValue: 'B',
683
+ },
684
+ `autocompleteMode is ${autocompleteMode}`,
685
+ );
686
+ await el.updateComplete;
687
+ expect(el.checkedIndex).to.equal(-1, `autocompleteMode is ${autocompleteMode}`);
688
+ } else {
689
+ expect(el.modelValue).to.equal('Bb', `autocompleteMode is ${autocompleteMode}`);
690
+ expect(el.checkedIndex).to.equal(1, `autocompleteMode is ${autocompleteMode}`);
691
+ }
692
+ }
693
+ });
694
+ it('works with Required validator', async () => {
695
+ const el =
696
+ /** @type {LionCombobox} */
697
+ await fixture(html`
698
+ <${hostTag} name="foo" .validators=${[new Required()]}>
699
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
700
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
701
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
702
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
703
+ </${hostTag}>
704
+ `);
705
+ el.submitted = true;
706
+ await el.feedbackComplete;
707
+ expect(el.hasFeedbackFor).to.include('error', 'hasFeedbackFor');
708
+ await el.feedbackComplete;
709
+ expect(el.showsFeedbackFor).to.include('error', 'showsFeedbackFor');
710
+ });
711
+ it('ignores empty string modelValue inputs', async () => {
712
+ const el =
713
+ /** @type {LionCombobox} */
714
+ await fixture(html`
715
+ <${hostTag} name="foo" multiple-choice autocomplete="none">
716
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
717
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
718
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
719
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
720
+ </${hostTag}>
721
+ `);
722
+ el.requireOptionMatch = false;
723
+ await el.updateComplete;
724
+ const { _inputNode } = getComboboxMembers(el);
725
+ mimicKeyPress(_inputNode, 'Enter');
726
+ await el.updateComplete;
727
+ expect(el.modelValue).to.eql([]);
728
+ await mimicUserTyping(el, ' ');
729
+ await el.updateComplete;
730
+ mimicKeyPress(_inputNode, 'Enter');
731
+ await el.updateComplete;
732
+ expect(el.modelValue).to.eql([]);
733
+ });
734
+ it('allows a value outside of the option list when requireOptionMatch is false', async () => {
735
+ const el =
736
+ /** @type {LionCombobox} */
737
+ await fixture(html`
738
+ <${hostTag} name="foo" .validators=${[new Required()]}>
739
+ <${tagOption} checked .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
740
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
741
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
742
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
743
+ </${hostTag}>
744
+ `);
745
+ el.requireOptionMatch = false;
746
+ const { _inputNode } = getComboboxMembers(el);
747
+ expect(el.checkedIndex).to.equal(0);
748
+ await mimicUserTyping(el, 'Foo');
749
+ _inputNode.dispatchEvent(new Event('input'));
750
+ await el.updateComplete;
751
+ expect(el.checkedIndex).to.equal(-1);
752
+ expect(el.modelValue).to.equal('Foo');
753
+ expect(_inputNode.value).to.equal('Foo');
754
+ });
755
+ it('fires "model-value-changed" on every textbox change when requireOptionMatch is false and autocomplete is both', async () => {
756
+ const el =
757
+ /** @type {LionCombobox} */
758
+ await fixture(html`
759
+ <${hostTag} name="foo" autocomplete="both" .requireOptionMatch="${false}">
760
+ <${tagOption} checked .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
761
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
762
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
763
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
764
+ </${hostTag}>
765
+ `);
766
+ const fake = sinon.fake((ev) => ev.target.modelValue);
767
+ el.addEventListener('model-value-changed', fake);
768
+ await mimicUserTyping(el, 'Foo');
769
+ await el.updateComplete;
770
+ expect(fake).to.have.been.calledThrice;
771
+ expect(fake.firstCall.returnValue).to.equal('F');
772
+ expect(fake.secondCall.returnValue).to.equal('Fo');
773
+ expect(fake.thirdCall.returnValue).to.equal('Foo');
774
+ expect(el.modelValue).to.equal('Foo');
775
+ });
776
+ it('fires "model-value-changed" on every textbox change when requireOptionMatch is false and autocomplete is none', async () => {
777
+ const el =
778
+ /** @type {LionCombobox} */
779
+ await fixture(html`
780
+ <${hostTag} name="foo" autocomplete="none" .requireOptionMatch="${false}">
781
+ <${tagOption} checked .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
782
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
783
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
784
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
785
+ </${hostTag}>
786
+ `);
787
+ const fake = sinon.fake((ev) => ev.target.modelValue);
788
+ el.addEventListener('model-value-changed', fake);
789
+ await mimicUserTyping(el, 'Foo');
790
+ await el.updateComplete;
791
+ expect(fake.callCount).to.equal(4);
792
+ expect(fake.firstCall.returnValue).to.equal('');
793
+ expect(fake.secondCall.returnValue).to.equal('F');
794
+ expect(fake.thirdCall.returnValue).to.equal('Fo');
795
+ expect(fake.getCall(3).returnValue).to.equal('Foo');
796
+ expect(el.modelValue).to.equal('Foo');
797
+ });
798
+ it('does not fire "model-value-changed" when requireOptionMatch is false but the textbox value did not change', async () => {
799
+ const el =
800
+ /** @type {LionCombobox} */
801
+ await fixture(html`
802
+ <${hostTag} name="foo" autocomplete="none" .requireOptionMatch="${false}">
803
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
804
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
805
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
806
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
807
+ </${hostTag}>
808
+ `);
809
+ await mimicUserTyping(el, 'Foo');
810
+ await el.updateComplete;
811
+ const spy = sinon.spy();
812
+ el.addEventListener('model-value-changed', spy);
813
+
814
+ // Re-triggering autocompletion with the exact same textbox value should not dispatch again
815
+ el.__shouldAutocompleteNextUpdate = true;
816
+ await el.updateComplete;
817
+ expect(spy).to.not.have.been.called;
818
+ });
819
+ it('fires "model-value-changed" via checked option repropagation (not the manual dispatch) when requireOptionMatch is true', async () => {
820
+ const el =
821
+ /** @type {LionCombobox} */
822
+ await fixture(html`
823
+ <${hostTag} name="foo" autocomplete="both">
824
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
825
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
826
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
827
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
828
+ </${hostTag}>
829
+ `);
830
+ const spy = sinon.spy();
831
+ el.addEventListener('model-value-changed', spy);
832
+ await mimicUserTyping(el, 'cha');
833
+ await el.updateComplete;
834
+ expect(el.modelValue).to.equal('Chard');
835
+ expect(spy).to.have.been.calledOnce;
836
+ spy.resetHistory();
837
+ // No option matches "xyz", so checkedIndex stays -1 and requireOptionMatch is true:
838
+ // no event should be manually dispatched (normal Unparseable flow, no double event)
839
+ await mimicUserTyping(el, 'xyz');
840
+ await el.updateComplete;
841
+ expect(el.modelValue instanceof Unparseable).to.be.true;
842
+ expect(spy).to.have.been.calledOnce;
843
+ });
844
+ it('fires "model-value-changed" via checked option repropagation (not the manual dispatch) when requireOptionMatch is false and no option matches the input', async () => {
845
+ const el =
846
+ /** @type {LionCombobox} */
847
+ await fixture(html`
848
+ <${hostTag} name="foo" autocomplete="both">
849
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
850
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
851
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
852
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
853
+ </${hostTag}>
854
+ `);
855
+ el.requireOptionMatch = false;
856
+ await el.updateComplete;
857
+ const spy = sinon.spy();
858
+ el.addEventListener('model-value-changed', spy);
859
+
860
+ // No option matches "Chards", so checkedIndex becomes -1 and requireOptionMatch is false:
861
+ // no event should be manually dispatched (normal Unparseable flow, no double event)
862
+ await mimicUserTyping(el, 'Chicorys');
863
+ await el.updateComplete;
864
+ expect(el.checkedIndex).to.equal(-1);
865
+ expect(spy.args.length).to.equal(3);
866
+ expect(spy.args[2][0].target.modelValue).to.equal('Chicorys');
867
+ });
868
+ it("doesn't select any similar options after using delete when requireOptionMatch is false", async () => {
869
+ const el =
870
+ /** @type {LionCombobox} */
871
+ await fixture(html`
872
+ <${hostTag} name="foo" .validators=${[new Required()]}>
873
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
874
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
875
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
876
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
877
+ </${hostTag}>
878
+ `);
879
+ el.requireOptionMatch = false;
880
+ const { _inputNode } = getComboboxMembers(el);
881
+ await mimicUserTyping(el, 'Art');
882
+ await el.updateComplete;
883
+ await mimicUserTypingAdvanced(el, ['Delete']);
884
+ await el.updateComplete;
885
+ await el.updateComplete;
886
+ mimicKeyPress(_inputNode, 'Enter');
887
+ await el.updateComplete;
888
+ expect(el.checkedIndex).to.equal(-1);
889
+ expect(el.modelValue).to.equal('Art');
890
+ expect(_inputNode.value).to.equal('Art');
891
+ });
892
+ it("when removing a letter it won't select the option", async () => {
893
+ // We don't autocomplete when characters are removed
894
+ const el =
895
+ /** @type {LionCombobox} */
896
+ await fixture(html`
897
+ <${hostTag} name="foo">
898
+ <${tagOption} checked .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
899
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
900
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
901
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
902
+ </${hostTag}>
903
+ `);
904
+ const { _inputNode } = getComboboxMembers(el);
905
+ expect(el.checkedIndex).to.equal(0);
906
+
907
+ // Simulate backspace deleting the char at the end of the string
908
+ mimicKeyPress(_inputNode, 'Backspace');
909
+ _inputNode.dispatchEvent(new Event('input'));
910
+ const arr = _inputNode.value.split('');
911
+ arr.splice(_inputNode.value.length - 1, 1);
912
+ _inputNode.value = arr.join('');
913
+ await el.updateComplete;
914
+ el.dispatchEvent(new Event('blur'));
915
+ expect(el.checkedIndex).to.equal(-1);
916
+ expect(el.modelValue instanceof Unparseable).to.be.true;
917
+ expect(el.modelValue.viewValue).to.equal('Artichok');
918
+ });
919
+ it('allows the user to override the parser', async () => {
920
+ const el =
921
+ /** @type {LionCombobox} */
922
+ await fixture(html`
923
+ <${hostTag}
924
+ name="foo"
925
+ .parser="${/** @param {string} value */ (value) => value.replace(/[0-9]/g, '')}"
926
+ >
927
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
928
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
929
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
930
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
931
+ </${hostTag}>
932
+ `);
933
+ const { _inputNode } = getComboboxMembers(el);
934
+ el.setCheckedIndex(-1);
935
+ _inputNode.value = 'Foo123';
936
+ el.__shouldAutocompleteNextUpdate = true;
937
+ await el.updateComplete;
938
+ expect(el.modelValue).to.equal('Foo');
939
+ expect(el.formElements[0].checked).to.be.false;
940
+ });
941
+ it('allows custom selections when multi-choice when requireOptionMatch is false', async () => {
942
+ const el =
943
+ /** @type {LionCombobox} */
944
+ await fixture(html`
945
+ <${hostTag}
946
+ name="foo"
947
+ multiple-choice
948
+ .validators=${[new Required()]}
949
+ autocomplete="none"
950
+ >
951
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
952
+ <${tagOption} checked .choiceValue="${'Chard'}">Chard</${tagOption}>
953
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
954
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
955
+ </${hostTag}>
956
+ `);
957
+ el.requireOptionMatch = false;
958
+ await el.updateComplete;
959
+ const { _inputNode } = getComboboxMembers(el);
960
+ expect(el.modelValue).to.eql(['Chard']);
961
+ expect(el.checkedIndex).to.eql([1]);
962
+ await mimicUserTyping(el, 'Foo');
963
+ await el.updateComplete;
964
+ mimicKeyPress(_inputNode, 'Enter');
965
+ await el.updateComplete;
966
+ expect(el.modelValue).to.eql(['Chard', 'Foo']);
967
+ expect(el.checkedIndex).to.eql([1]);
968
+ await mimicUserTyping(el, 'Bar');
969
+ await el.updateComplete;
970
+ mimicKeyPress(_inputNode, 'Enter');
971
+ await el.updateComplete;
972
+ });
973
+ it('allows many custom selections when multi-choice when requireOptionMatch is false', async () => {
974
+ const el =
975
+ /** @type {LionCombobox} */
976
+ await fixture(html`
977
+ <${hostTag}
978
+ name="foo"
979
+ multiple-choice
980
+ .validators=${[new Required()]}
981
+ autocomplete="none"
982
+ >
983
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
984
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
985
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
986
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
987
+ </${hostTag}>
988
+ `);
989
+ el.requireOptionMatch = false;
990
+ await el.updateComplete;
991
+ const { _inputNode } = getComboboxMembers(el);
992
+ await mimicUserTyping(el, 'Foo');
993
+ await el.updateComplete;
994
+ mimicKeyPress(_inputNode, 'Enter');
995
+ await el.updateComplete;
996
+ expect(el.modelValue).to.eql(['Foo']);
997
+ expect(el.checkedIndex).to.eql([]);
998
+ await mimicUserTyping(el, 'Bar');
999
+ await el.updateComplete;
1000
+ mimicKeyPress(_inputNode, 'Enter');
1001
+ await el.updateComplete;
1002
+ expect(el.modelValue).to.eql(['Foo', 'Bar']);
1003
+ expect(el.checkedIndex).to.eql([]);
1004
+ });
1005
+ it('allows new options when multi-choice when requireOptionMatch=false and autocomplete="both", without selecting similar values', async () => {
1006
+ const el =
1007
+ /** @type {LionCombobox} */
1008
+ await fixture(html`
1009
+ <${hostTag}
1010
+ name="foo"
1011
+ multiple-choice
1012
+ .requireOptionMatch=${false}
1013
+ autocomplete="both"
1014
+ >
1015
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1016
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1017
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1018
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1019
+ </${hostTag}>
1020
+ `);
1021
+ await el.updateComplete;
1022
+ const { _inputNode } = getComboboxMembers(el);
1023
+ await mimicUserTyping(el, 'Artist');
1024
+ await el.updateComplete;
1025
+ mimicKeyPress(_inputNode, 'Enter');
1026
+ await el.updateComplete;
1027
+ expect(el.modelValue).to.eql(['Artist']);
1028
+ });
1029
+ it('allows new options when multi-choice when requireOptionMatch=false and autocomplete="both", when deleting autocomplete values using Backspace', async () => {
1030
+ const el =
1031
+ /** @type {LionCombobox} */
1032
+ await fixture(html`
1033
+ <${hostTag}
1034
+ name="foo"
1035
+ multiple-choice
1036
+ .requireOptionMatch=${false}
1037
+ autocomplete="both"
1038
+ >
1039
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1040
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1041
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1042
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1043
+ </${hostTag}>
1044
+ `);
1045
+ await el.updateComplete;
1046
+ const { _inputNode } = getComboboxMembers(el);
1047
+ await mimicUserTyping(el, 'Art');
1048
+ await el.updateComplete;
1049
+ await mimicUserTypingAdvanced(el, ['Backspace']);
1050
+ await el.updateComplete;
1051
+ mimicKeyPress(_inputNode, 'Enter');
1052
+ await el.updateComplete;
1053
+ expect(el.modelValue).to.eql(['Art']);
1054
+ });
1055
+ it('allows new custom options when multi-choice when requireOptionMatch=false and autocomplete="both", when deleting autocompleted values using Delete', async () => {
1056
+ const el =
1057
+ /** @type {LionCombobox} */
1058
+ await fixture(html`
1059
+ <${hostTag}
1060
+ name="foo"
1061
+ multiple-choice
1062
+ .requireOptionMatch=${false}
1063
+ autocomplete="both"
1064
+ >
1065
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1066
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1067
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1068
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1069
+ </${hostTag}>
1070
+ `);
1071
+ await el.updateComplete;
1072
+ const { _inputNode } = getComboboxMembers(el);
1073
+ el.modelValue = [];
1074
+ await mimicUserTyping(el, 'Art');
1075
+ await el.updateComplete;
1076
+ await mimicUserTypingAdvanced(el, ['Delete']);
1077
+ await el.updateComplete;
1078
+ mimicKeyPress(_inputNode, 'Enter');
1079
+ await el.updateComplete;
1080
+ expect(el.modelValue).to.eql(['Art']);
1081
+ });
1082
+ it('allows prefilling the combobox', async () => {
1083
+ const autocompleteValues = ['inline', 'none', 'both', 'list'];
1084
+ for (const autocompleteValue of autocompleteValues) {
1085
+ const el =
1086
+ /** @type {LionCombobox} */
1087
+ await fixture(html`
1088
+ <${hostTag} autocomplete="${autocompleteValue}" .modelValue="${'Chard'}" name="foo">
1089
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1090
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1091
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1092
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1093
+ </${hostTag}>
1094
+ `);
1095
+ expect(el.modelValue).to.equal('Chard');
1096
+ // @ts-expect-error
1097
+ expect(el._inputNode.value).to.equal('Chard');
1098
+ }
1099
+ });
1100
+ it('submits form on [Enter] when listbox is closed', async () => {
1101
+ const submitSpy = sinon.spy((e) => e.preventDefault());
1102
+ const el =
1103
+ /** @type {HTMLFormElement} */
1104
+ await fixture(html`
1105
+ <form @submit=${submitSpy}>
1106
+ <${hostTag} name="foo">
1107
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1108
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1109
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1110
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1111
+ </${hostTag}>
1112
+ <button type="submit">submit</button>
1113
+ </form>
1114
+ `);
1115
+ const combobox = /** @type {LionCombobox} */ el.querySelector('[name="foo"]');
1116
+ const { _inputNode } = getComboboxMembers(combobox);
1117
+ await combobox.updateComplete;
1118
+ _inputNode.focus();
1119
+ await sendKeys({
1120
+ press: 'Enter',
1121
+ });
1122
+ expect(submitSpy.callCount).to.equal(1);
1123
+ });
1124
+ it('does not submit form on [Enter] when listbox is opened', async () => {
1125
+ const submitSpy = sinon.spy((e) => e.preventDefault());
1126
+ const el =
1127
+ /** @type {HTMLFormElement} */
1128
+ await fixture(html`
1129
+ <form @submit=${submitSpy}>
1130
+ <${hostTag} name="foo">
1131
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1132
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1133
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1134
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1135
+ </${hostTag}>
1136
+ <button type="submit">submit</button>
1137
+ </form>
1138
+ `);
1139
+ const combobox = /** @type {LionCombobox} */ el.querySelector('[name="foo"]');
1140
+ const { _inputNode } = getComboboxMembers(combobox);
1141
+ combobox.opened = true;
1142
+ await combobox.updateComplete;
1143
+ _inputNode.focus();
1144
+ await sendKeys({
1145
+ press: 'Enter',
1146
+ });
1147
+ expect(submitSpy.callCount).to.equal(0);
1148
+ });
1149
+ });
1150
+ describe('Overlay visibility', () => {
1151
+ it('does not show overlay on click', async () => {
1152
+ const el =
1153
+ /** @type {LionCombobox} */
1154
+ await fixture(html`
1155
+ <${hostTag} name="foo">
1156
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1157
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1158
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1159
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1160
+ </${hostTag}>
1161
+ `);
1162
+ const { _comboboxNode } = getComboboxMembers(el);
1163
+ expect(el.opened).to.equal(false);
1164
+ _comboboxNode.dispatchEvent(
1165
+ new Event('click', {
1166
+ bubbles: true,
1167
+ composed: true,
1168
+ }),
1169
+ );
1170
+ await el.updateComplete;
1171
+ expect(el.opened).to.equal(false);
1172
+ });
1173
+ it('does not flash the menu when _showOverlayCondition returns "false"', async () => {
1174
+ class ComplexCombobox extends LionCombobox {
1175
+ _showOverlayCondition() {
1176
+ return false;
1177
+ }
1178
+ }
1179
+ const tagName = defineCE(ComplexCombobox);
1180
+ const tag = unsafeStatic(tagName);
1181
+ const el =
1182
+ /** @type {LionCombobox} */
1183
+ await fixture(html`
1184
+ <${tag} name="combo" label="Display only the label once selected">
1185
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1186
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1187
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1188
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1189
+ </${tag}>
1190
+ `);
1191
+ const dialog = el.shadowRoot?.querySelector('dialog');
1192
+ /**
1193
+ * hasDropdownFlashed is `true` if the menu was shown for a short period of time and then got closed
1194
+ */
1195
+ let hasDropdownFlashed = false;
1196
+ const observer = new MutationObserver((mutationList) => {
1197
+ // eslint-disable-next-line no-unused-vars
1198
+ for (const mutation of mutationList) {
1199
+ if (dialog?.style.display === '') {
1200
+ hasDropdownFlashed = true;
1201
+ }
1202
+ }
1203
+ });
1204
+ observer.observe(/** @type {Node} */ dialog, {
1205
+ attributeFilter: ['style'],
1206
+ });
1207
+ const { _inputNode } = getComboboxMembers(el);
1208
+ _inputNode.focus();
1209
+ await sendKeys({
1210
+ type: 'art',
1211
+ });
1212
+ expect(hasDropdownFlashed).to.be.false;
1213
+ });
1214
+ it('shows overlay on click when filled', async () => {
1215
+ const el =
1216
+ /** @type {LionCombobox} */
1217
+ await fixture(html`
1218
+ <${hostTag} name="foo">
1219
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1220
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1221
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1222
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1223
+ </${hostTag}>
1224
+ `);
1225
+ const { _comboboxNode } = getComboboxMembers(el);
1226
+ el.modelValue = 'Art';
1227
+ expect(el.opened).to.equal(false);
1228
+ _comboboxNode.dispatchEvent(
1229
+ new Event('click', {
1230
+ bubbles: true,
1231
+ composed: true,
1232
+ }),
1233
+ );
1234
+ await el.updateComplete;
1235
+ expect(el.opened).to.equal(false);
1236
+ });
1237
+ it('shows overlay again after select and char keyup', async () => {
1238
+ /**
1239
+ * Scenario:
1240
+ * [1] user focuses textbox: overlay hidden
1241
+ * [2] user types char: overlay shows
1242
+ * [3] user selects "Artichoke": overlay closes, textbox gets value "Artichoke" and textbox
1243
+ * still has focus
1244
+ * [4] user changes textbox value to "Artichoke": the overlay should show again
1245
+ */
1246
+ const el =
1247
+ /** @type {LionCombobox} */
1248
+ await fixture(html`
1249
+ <${hostTag} name="foo">
1250
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1251
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1252
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1253
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1254
+ </${hostTag}>
1255
+ `);
1256
+ const options = el.formElements;
1257
+ const { _inputNode } = getComboboxMembers(el);
1258
+ expect(el.opened).to.equal(false);
1259
+
1260
+ // step [1]
1261
+ _inputNode.dispatchEvent(
1262
+ new Event('click', {
1263
+ bubbles: true,
1264
+ composed: true,
1265
+ }),
1266
+ );
1267
+ await el.updateComplete;
1268
+ expect(el.opened).to.equal(false);
1269
+
1270
+ // step [2]
1271
+ await mimicUserTyping(el, 'c');
1272
+ await el.updateComplete;
1273
+ expect(el.opened).to.equal(true);
1274
+
1275
+ // step [3]
1276
+ options[0].click();
1277
+ await el.updateComplete;
1278
+ expect(el.opened).to.equal(false);
1279
+ expect(isActiveElement(_inputNode)).to.be.true;
1280
+
1281
+ // step [4]
1282
+ await el.updateComplete;
1283
+ await mimicUserTyping(el, 'c');
1284
+ await el.updateComplete;
1285
+ expect(el.opened).to.equal(true);
1286
+ });
1287
+ it('syncs a manually checked selection on overlay close while the input remains focused', async () => {
1288
+ const el =
1289
+ /** @type {LionCombobox} */
1290
+ await fixture(html`
1291
+ <${hostTag} name="foo" autocomplete="list">
1292
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1293
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1294
+ </${hostTag}>
1295
+ `);
1296
+ const { _inputNode } = getComboboxMembers(el);
1297
+ _inputNode.focus();
1298
+ el.opened = true;
1299
+ await el.updateComplete;
1300
+ expect(el.opened).to.equal(true);
1301
+ expect(isActiveElement(_inputNode)).to.be.true;
1302
+ el.setCheckedIndex(0);
1303
+ await el.updateComplete;
1304
+ expect(el.modelValue).to.equal('Artichoke');
1305
+ expect(el.checkedIndex).to.equal(0);
1306
+ expect(_inputNode.value).to.equal('');
1307
+ el.opened = false;
1308
+ await el.updateComplete;
1309
+ expect(_inputNode.value).to.equal('Artichoke');
1310
+ expect(isActiveElement(_inputNode)).to.be.true;
1311
+ });
1312
+ it('hides (and clears) overlay on [Escape]', async () => {
1313
+ const el =
1314
+ /** @type {LionCombobox} */
1315
+ await fixture(html`
1316
+ <${hostTag} name="foo">
1317
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1318
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1319
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1320
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1321
+ </${hostTag}>
1322
+ `);
1323
+ const { _inputNode } = getComboboxMembers(el);
1324
+ await mimicUserTyping(el, 'art');
1325
+ await el.updateComplete;
1326
+ expect(el.opened).to.equal(true);
1327
+ expect(_inputNode.value).to.equal('Artichoke');
1328
+ _inputNode.dispatchEvent(
1329
+ new KeyboardEvent('keydown', {
1330
+ key: 'Escape',
1331
+ }),
1332
+ );
1333
+ expect(el.opened).to.equal(false);
1334
+ expect(_inputNode.value).to.equal('');
1335
+ });
1336
+ it('hides overlay on [Enter]', async () => {
1337
+ const el =
1338
+ /** @type {LionCombobox} */
1339
+ await fixture(html`
1340
+ <${hostTag} name="foo">
1341
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1342
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1343
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1344
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1345
+ </${hostTag}>
1346
+ `);
1347
+ const { _inputNode } = getComboboxMembers(el);
1348
+ await mimicUserTyping(el, 'art');
1349
+ await el.updateComplete;
1350
+ expect(el.opened).to.equal(true);
1351
+ expect(_inputNode.value).to.equal('Artichoke');
1352
+
1353
+ // N.B. we do only trigger keydown here (and not mimicKeypress (both keyup and down)),
1354
+ // because this closely mimics what happens in the browser
1355
+ _inputNode.dispatchEvent(
1356
+ new KeyboardEvent('keydown', {
1357
+ key: 'Enter',
1358
+ }),
1359
+ );
1360
+ expect(el.opened).to.equal(false);
1361
+ expect(_inputNode.value).to.equal('Artichoke');
1362
+ });
1363
+ it('hides overlay on [Tab]', async () => {
1364
+ const el =
1365
+ /** @type {LionCombobox} */
1366
+ await fixture(html`
1367
+ <${hostTag} name="foo">
1368
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1369
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1370
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1371
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1372
+ </${hostTag}>
1373
+ `);
1374
+ const { _inputNode } = getComboboxMembers(el);
1375
+ await mimicUserTyping(el, 'art');
1376
+ await el.updateComplete;
1377
+ expect(el.opened).to.equal(true);
1378
+ expect(_inputNode.value).to.equal('Artichoke');
1379
+
1380
+ // N.B. we do only trigger keydown here (and not mimicKeypress (both keyup and down)),
1381
+ // because this closely mimics what happens in the browser
1382
+ _inputNode.dispatchEvent(
1383
+ new KeyboardEvent('keydown', {
1384
+ key: 'Tab',
1385
+ }),
1386
+ );
1387
+ expect(el.opened).to.equal(false);
1388
+ expect(_inputNode.value).to.equal('Artichoke');
1389
+ });
1390
+ it('keeps overlay open on [Shift]', async () => {
1391
+ const el =
1392
+ /** @type {LionCombobox} */
1393
+ await fixture(html`
1394
+ <${hostTag} name="foo">
1395
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1396
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1397
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1398
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1399
+ </${hostTag}>
1400
+ `);
1401
+ const { _inputNode } = getComboboxMembers(el);
1402
+ await mimicUserTyping(el, 'art');
1403
+ await el.updateComplete;
1404
+ expect(el.opened).to.equal(true);
1405
+ expect(_inputNode.value).to.equal('Artichoke');
1406
+
1407
+ // N.B. we do only trigger keydown here (and not mimicKeypress (both keyup and down)),
1408
+ // because this closely mimics what happens in the browser
1409
+ _inputNode.dispatchEvent(
1410
+ new KeyboardEvent('keydown', {
1411
+ key: 'Shift',
1412
+ }),
1413
+ );
1414
+ expect(el.opened).to.equal(true);
1415
+ expect(_inputNode.value).to.equal('Artichoke');
1416
+ });
1417
+ it('clears checkedIndex on empty text', async () => {
1418
+ const el =
1419
+ /** @type {LionCombobox} */
1420
+ await fixture(html`
1421
+ <${hostTag} name="foo">
1422
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1423
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1424
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1425
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1426
+ </${hostTag}>
1427
+ `);
1428
+ const { _inputNode } = getComboboxMembers(el);
1429
+ await mimicUserTyping(el, 'art');
1430
+ await el.updateComplete;
1431
+ expect(el.opened).to.equal(true);
1432
+ expect(_inputNode.value).to.equal('Artichoke');
1433
+ expect(el.checkedIndex).to.equal(0);
1434
+
1435
+ // await mimicUserTyping(el, '');
1436
+ await sendKeys({
1437
+ press: 'Backspace',
1438
+ });
1439
+ await el.updateComplete;
1440
+ el.opened = false;
1441
+ await el.updateComplete;
1442
+ expect(el.checkedIndex).to.equal(-1);
1443
+ });
1444
+ it('has the correct Overlay Config defined', async () => {
1445
+ const el =
1446
+ /** @type {LionCombobox} */
1447
+ await fixture(html`
1448
+ <${hostTag} name="foo" .showAllOnEmpty="${true}">
1449
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1450
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1451
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1452
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1453
+ </${hostTag}>
1454
+ `);
1455
+ // @ts-expect-error [allow-protected] in test
1456
+ const overlayCfg = el._defineOverlayConfig();
1457
+ const equalsOrContainsInput = (
1458
+ /** @type {HTMLInputElement|HTMLDivElement} */ invokerNode,
1459
+ ) =>
1460
+ // @ts-expect-error [allow-protected] in test
1461
+ invokerNode === el._inputNode || invokerNode.contains(el._inputNode);
1462
+ expect(overlayCfg.elementToFocusAfterHide).to.equal(undefined);
1463
+ // @ts-expect-error
1464
+ expect(equalsOrContainsInput(overlayCfg.invokerNode)).to.be.true;
1465
+ expect(overlayCfg.visibilityTriggerFunction).to.equal(undefined);
1466
+ });
1467
+
1468
+ // NB: If this becomes a suite, move to separate file
1469
+ describe('Subclassers', () => {
1470
+ it('allows to control overlay visibility via "_showOverlayCondition": should not display overlay if currentValue length condition is not fulfilled', async () => {
1471
+ class ShowOverlayConditionCombobox extends LionCombobox {
1472
+ /** @param {{ currentValue: string, lastKey:string }} options */
1473
+ _showOverlayCondition(options) {
1474
+ return (
1475
+ // @ts-ignore
1476
+ this.__prevCboxValueNonSelected.length > 3 && super._showOverlayCondition(options)
1477
+ );
1478
+ }
1479
+ }
1480
+ const tagName = defineCE(ShowOverlayConditionCombobox);
1481
+ const tag = unsafeStatic(tagName);
1482
+ const el =
1483
+ /** @type {LionCombobox} */
1484
+ await fixture(html`
1485
+ <${tag} name="foo">
1486
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1487
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1488
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1489
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1490
+ </${tag}>
1491
+ `);
1492
+ await mimicUserTyping(el, 'aaa');
1493
+ expect(el.opened).to.be.false;
1494
+ });
1495
+ it('allows to control overlay visibility via "_showOverlayCondition": should display overlay if currentValue length condition is fulfilled', async () => {
1496
+ class ShowOverlayConditionCombobox extends LionCombobox {
1497
+ /** @param {{ currentValue: string, lastKey:string }} options */
1498
+ _showOverlayCondition(options) {
1499
+ return (
1500
+ // @ts-ignore
1501
+ this.__prevCboxValueNonSelected.length > 3 && super._showOverlayCondition(options)
1502
+ );
1503
+ }
1504
+ }
1505
+ const tagName = defineCE(ShowOverlayConditionCombobox);
1506
+ const tag = unsafeStatic(tagName);
1507
+ const el =
1508
+ /** @type {LionCombobox} */
1509
+ await fixture(html`
1510
+ <${tag} name="foo">
1511
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1512
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1513
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1514
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1515
+ </${tag}>
1516
+ `);
1517
+ await mimicUserTyping(el, 'aaaa');
1518
+ expect(el.opened).to.be.true;
1519
+ });
1520
+ it('allows to control overlay visibility via "_showOverlayCondition": should not display overlay if currentValue length condition is not fulfilled after once fulfilled', async () => {
1521
+ class ShowOverlayConditionCombobox extends LionCombobox {
1522
+ /** @param {{ currentValue: string, lastKey:string }} options */
1523
+ _showOverlayCondition(options) {
1524
+ return (
1525
+ // @ts-ignore
1526
+ this.__prevCboxValueNonSelected.length > 3 && super._showOverlayCondition(options)
1527
+ );
1528
+ }
1529
+ }
1530
+ const tagName = defineCE(ShowOverlayConditionCombobox);
1531
+ const tag = unsafeStatic(tagName);
1532
+ const el =
1533
+ /** @type {LionCombobox} */
1534
+ await fixture(html`
1535
+ <${tag} name="foo">
1536
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1537
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1538
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1539
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1540
+ </${tag}>
1541
+ `);
1542
+ await mimicUserTyping(el, 'aaaa');
1543
+ expect(el.opened).to.be.true;
1544
+ await mimicUserTyping(el, 'aaa');
1545
+ await el.updateComplete;
1546
+ expect(el.opened).to.be.false;
1547
+ });
1548
+ });
1549
+ describe('Accessibility', () => {
1550
+ it('sets "aria-posinset" and "aria-setsize" on visible entries', async () => {
1551
+ const el =
1552
+ /** @type {LionCombobox} */
1553
+ await fixture(html`
1554
+ <${hostTag} name="foo" multiple-choice>
1555
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1556
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1557
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1558
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1559
+ </${hostTag}>
1560
+ `);
1561
+ const options = el.formElements;
1562
+ expect(el.opened).to.equal(false);
1563
+ await mimicUserTyping(el, 'art');
1564
+ await el.updateComplete;
1565
+ expect(el.opened).to.equal(true);
1566
+ const visibleOptions = options.filter((o) => o.style.display !== 'none');
1567
+ expect(visibleOptions.length).to.not.equal(0);
1568
+ visibleOptions.forEach((o, i) => {
1569
+ expect(o.getAttribute('aria-posinset')).to.equal(`${i + 1}`);
1570
+ expect(o.getAttribute('aria-setsize')).to.equal(`${visibleOptions.length}`);
1571
+ });
1572
+ /**
1573
+ * @param {{ style: { display: string; }; }} o
1574
+ */
1575
+ const hiddenOptions = options.filter((o) => o.style.display === 'none');
1576
+ /**
1577
+ * @param {{ hasAttribute: (arg0: string) => any; }} o
1578
+ */
1579
+ hiddenOptions.forEach((o) => {
1580
+ expect(o.hasAttribute('aria-posinset')).to.equal(false);
1581
+ expect(o.hasAttribute('aria-setsize')).to.equal(false);
1582
+ });
1583
+ });
1584
+
1585
+ /**
1586
+ * Note that we use aria-hidden instead of 'display:none' to allow for animations
1587
+ * (like fade in/out)
1588
+ */
1589
+ it('sets aria-hidden="true" on hidden entries', async () => {
1590
+ const el =
1591
+ /** @type {LionCombobox} */
1592
+ await fixture(html`
1593
+ <${hostTag} name="foo">
1594
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1595
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1596
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1597
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1598
+ </${hostTag}>
1599
+ `);
1600
+ const options = el.formElements;
1601
+ expect(el.opened).to.equal(false);
1602
+ await mimicUserTyping(el, 'art');
1603
+ expect(el.opened).to.equal(true);
1604
+ await el.updateComplete;
1605
+ const visibleOptions = options.filter((o) => o.style.display !== 'none');
1606
+ visibleOptions.forEach((o) => {
1607
+ expect(o.hasAttribute('aria-hidden')).to.be.false;
1608
+ });
1609
+ const hiddenOptions = options.filter((o) => o.style.display === 'none');
1610
+ hiddenOptions.forEach((o) => {
1611
+ expect(o.getAttribute('aria-hidden')).to.equal('true');
1612
+ });
1613
+ });
1614
+ it('dropdown has a label', async () => {
1615
+ const el =
1616
+ /** @type {LionCombobox} */
1617
+ await fixture(html`
1618
+ <${hostTag} name="foo" .validators=${[new Required()]}>
1619
+ <${tagOption} checked .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1620
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1621
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1622
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1623
+ </${hostTag}>
1624
+ `);
1625
+ const { _overlayCtrl } = getComboboxMembers(el);
1626
+ expect(_overlayCtrl.contentWrapperNode.getAttribute('aria-label')).to.equal(
1627
+ 'Combobox options popup',
1628
+ );
1629
+ });
1630
+ });
1631
+ });
1632
+
1633
+ // Notice that the LionComboboxInvoker always needs to be used in conjunction with the
1634
+ // LionCombobox, and therefore will be tested integrated,
1635
+ describe('Invoker component integration', () => {
1636
+ describe('Accessibility', () => {
1637
+ it('sets role="combobox" on textbox wrapper/listbox sibling', async () => {
1638
+ const el =
1639
+ /** @type {LionCombobox} */
1640
+ await fixture(html`
1641
+ <${hostTag} name="foo">
1642
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
1643
+ <${tagOption} .choiceValue="${'20'}">Item 2</${tagOption}>
1644
+ </${hostTag}>
1645
+ `);
1646
+ const { _comboboxNode } = getComboboxMembers(el);
1647
+ expect(_comboboxNode.getAttribute('role')).to.equal('combobox');
1648
+ });
1649
+ it('sets aria-expanded to element with role="combobox" in wai-aria 1.0 and 1.1', async () => {
1650
+ const el =
1651
+ /** @type {LionCombobox} */
1652
+ await fixture(html`
1653
+ <${hostTag} name="foo" ._ariaVersion="${'1.0'}">
1654
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
1655
+ <${tagOption} .choiceValue="${'20'}">Item 2</${tagOption}>
1656
+ </${hostTag}>
1657
+ `);
1658
+ const { _comboboxNode } = getComboboxMembers(el);
1659
+ expect(_comboboxNode.getAttribute('aria-expanded')).to.equal('false');
1660
+ el.opened = true;
1661
+ await el.updateComplete;
1662
+ expect(_comboboxNode.getAttribute('aria-expanded')).to.equal('true');
1663
+ const el2 =
1664
+ /** @type {LionCombobox} */
1665
+ await fixture(html`
1666
+ <${hostTag} name="foo" ._ariaVersion="${'1.1'}">
1667
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
1668
+ <${tagOption} .choiceValue="${'20'}">Item 2</${tagOption}>
1669
+ </${hostTag}>
1670
+ `);
1671
+ const { _comboboxNode: comboboxNode2 } = getComboboxMembers(el2);
1672
+ expect(comboboxNode2.getAttribute('aria-expanded')).to.equal('false');
1673
+ el2.opened = true;
1674
+ await el2.updateComplete;
1675
+ expect(_comboboxNode.getAttribute('aria-expanded')).to.equal('true');
1676
+ });
1677
+ it('makes sure listbox node is not focusable', async () => {
1678
+ const el =
1679
+ /** @type {LionCombobox} */
1680
+ await fixture(html`
1681
+ <${hostTag} name="foo">
1682
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
1683
+ <${tagOption} .choiceValue="${'20'}">Item 2</${tagOption}>
1684
+ </${hostTag}>
1685
+ `);
1686
+ const { _listboxNode } = getComboboxMembers(el);
1687
+ expect(_listboxNode.hasAttribute('tabindex')).to.be.false;
1688
+ });
1689
+ });
1690
+ });
1691
+ describe('Selection display', () => {
1692
+ class MySelectionDisplay extends LitElement {
1693
+ /**
1694
+ * @param {import('lit').PropertyValues } changedProperties
1695
+ */
1696
+ onComboboxElementUpdated(changedProperties) {
1697
+ if (
1698
+ changedProperties.has('modelValue') &&
1699
+ // @ts-ignore
1700
+ this.comboboxElement.multipleChoice
1701
+ ) {
1702
+ // do smth..
1703
+ }
1704
+ }
1705
+ }
1706
+ const selectionDisplayTag = unsafeStatic(defineCE(MySelectionDisplay));
1707
+ it('stores internal reference _selectionDisplayNode in LionCombobox', async () => {
1708
+ const el =
1709
+ /** @type {LionCombobox} */
1710
+ await fixture(html`
1711
+ <${hostTag} name="foo">
1712
+ <${selectionDisplayTag} slot="selection-display"></${selectionDisplayTag}>
1713
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
1714
+ </${hostTag}>
1715
+ `);
1716
+ const { _selectionDisplayNode } = getComboboxMembers(el);
1717
+ expect(_selectionDisplayNode).to.equal(el.querySelector('[slot=selection-display]'));
1718
+ });
1719
+ it('sets a reference to combobox element in _selectionDisplayNode', async () => {
1720
+ const el =
1721
+ /** @type {LionCombobox} */
1722
+ await fixture(html`
1723
+ <${hostTag} name="foo">
1724
+ <${selectionDisplayTag} slot="selection-display"></${selectionDisplayTag}>
1725
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
1726
+ </${hostTag}>
1727
+ `);
1728
+ // @ts-ignore allow protected members
1729
+ expect(el._selectionDisplayNode.comboboxElement).to.equal(el);
1730
+ });
1731
+ it('calls "onComboboxElementUpdated(changedProperties)" on "updated" in _selectionDisplayNode', async () => {
1732
+ const el =
1733
+ /** @type {LionCombobox} */
1734
+ await fixture(html`
1735
+ <${hostTag} name="foo">
1736
+ <${selectionDisplayTag} slot="selection-display"></${selectionDisplayTag}>
1737
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
1738
+ </${hostTag}>
1739
+ `);
1740
+ // @ts-ignore sinon type error
1741
+ const spy = sinon.spy(el._selectionDisplayNode, 'onComboboxElementUpdated');
1742
+ el.requestUpdate('modelValue', undefined);
1743
+ await el.updateComplete;
1744
+ expect(spy).to.have.been.calledOnce;
1745
+ });
1746
+
1747
+ // TODO: put those in distinct file if ./docs/lion-combobox-selection-display.js is accessible
1748
+ // and exposable
1749
+ describe.skip('Selected chips display', () => {
1750
+ // it('displays chips next to textbox, ordered based on user selection', async () => {
1751
+ // const el = /** @type {LionCombobox} */ (await fixture(html`
1752
+ // <${hostTag} name="foo" multiple-choice>
1753
+ // <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1754
+ // <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1755
+ // <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1756
+ // <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1757
+ // </${hostTag}>
1758
+ // `));
1759
+ // const options = el.formElements;
1760
+ // options[2].checked = true; // Chicory
1761
+ // options[0].checked = true; // Artichoke
1762
+ // options[1].checked = true; // Chard
1763
+ // const chips = Array.from(el._comboboxNode.querySelectorAll('.selection-chip'));
1764
+ // expect(chips.map(elm => elm.textContent)).to.eql(['Chicory', 'Artichoke', 'Chard']);
1765
+ // expect(el._comboboxNode.selectedElements).to.eql([options[2], options[0], options[1]]);
1766
+ // });
1767
+ // it('stages deletable chips on [Backspace]', async () => {
1768
+ // const el = /** @type {LionCombobox} */ (await fixture(html`
1769
+ // <${hostTag} name="foo" multiple-choice>
1770
+ // <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1771
+ // <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1772
+ // <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1773
+ // </${hostTag}>
1774
+ // `));
1775
+ // const options = el.formElements;
1776
+ // options[0].checked = true; // Artichoke
1777
+ // options[1].checked = true; // Chard
1778
+ // expect(el._comboboxNode.removeChipOnNextBackspace).to.be.false;
1779
+ // el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Backspace' }));
1780
+ // expect(el._comboboxNode.removeChipOnNextBackspace).to.be.true;
1781
+ // el._inputNode.blur();
1782
+ // expect(el._comboboxNode.removeChipOnNextBackspace).to.be.false;
1783
+ // });
1784
+ // it('deletes staged chip on [Backspace]', async () => {
1785
+ // const el = /** @type {LionCombobox} */ (await fixture(html`
1786
+ // <${hostTag} name="foo" multiple-choice>
1787
+ // <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1788
+ // <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1789
+ // <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1790
+ // </${hostTag}>
1791
+ // `));
1792
+ // const options = el.formElements;
1793
+ // options[0].checked = true; // Artichoke
1794
+ // options[1].checked = true; // Chard
1795
+ // expect(el._comboboxNode.selectedElements).to.eql([options[0], options[1]]);
1796
+ // expect(el._comboboxNode.removeChipOnNextBackspace).to.be.false;
1797
+ // el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Backspace' }));
1798
+ // expect(el._comboboxNode.selectedElements).to.eql([options[0], options[1]]);
1799
+ // expect(el._comboboxNode.removeChipOnNextBackspace).to.be.true;
1800
+ // el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Backspace' }));
1801
+ // expect(el._comboboxNode.selectedElements).to.eql([options[0]]);
1802
+ // expect(el._comboboxNode.removeChipOnNextBackspace).to.be.true;
1803
+ // el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Backspace' }));
1804
+ // expect(el._comboboxNode.selectedElements).to.eql([]);
1805
+ // expect(el._comboboxNode.removeChipOnNextBackspace).to.be.true;
1806
+ // el._inputNode.blur();
1807
+ // expect(el._comboboxNode.removeChipOnNextBackspace).to.be.false;
1808
+ // });
1809
+ });
1810
+ });
1811
+ describe('Autocompletion', () => {
1812
+ it('has autocomplete "both" by default', async () => {
1813
+ const el =
1814
+ /** @type {LionCombobox} */
1815
+ await fixture(html`
1816
+ <${hostTag} name="foo">
1817
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
1818
+ </${hostTag}>
1819
+ `);
1820
+ expect(el.autocomplete).to.equal('both');
1821
+ });
1822
+ it('filters options when autocomplete is "both"', async () => {
1823
+ const el =
1824
+ /** @type {LionCombobox} */
1825
+ await fixture(html`
1826
+ <${hostTag} name="foo" autocomplete="both">
1827
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1828
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1829
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1830
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1831
+ </${hostTag}>
1832
+ `);
1833
+ await mimicUserTyping(el, 'ch');
1834
+ await el.updateComplete;
1835
+ expect(getFilteredOptionValues(el)).to.eql(['Artichoke', 'Chard', 'Chicory']);
1836
+ });
1837
+ it('completes textbox when autocomplete is "both"', async () => {
1838
+ const el =
1839
+ /** @type {LionCombobox} */
1840
+ await fixture(html`
1841
+ <${hostTag} name="foo" autocomplete="both">
1842
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1843
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1844
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1845
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1846
+ </${hostTag}>
1847
+ `);
1848
+ await mimicUserTyping(el, 'ch');
1849
+ await el.updateComplete;
1850
+ const { _inputNode } = getComboboxMembers(el);
1851
+ expect(_inputNode.value).to.equal('Chard');
1852
+ expect(_inputNode.selectionStart).to.equal(2);
1853
+ expect(_inputNode.selectionEnd).to.equal(_inputNode.value.length);
1854
+
1855
+ // We don't autocomplete when characters are removed
1856
+ await mimicUserTyping(el, 'c'); // The user pressed backspace (number of chars decreased)
1857
+ expect(_inputNode.value).to.equal('c');
1858
+ expect(_inputNode.selectionStart).to.equal(_inputNode.value.length);
1859
+ });
1860
+ it('filters options when autocomplete is "list"', async () => {
1861
+ const el =
1862
+ /** @type {LionCombobox} */
1863
+ await fixture(html`
1864
+ <${hostTag} name="foo" autocomplete="list">
1865
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1866
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1867
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1868
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1869
+ </${hostTag}>
1870
+ `);
1871
+ const { _inputNode } = getComboboxMembers(el);
1872
+ await mimicUserTyping(el, 'ch');
1873
+ await el.updateComplete;
1874
+ expect(getFilteredOptionValues(el)).to.eql(['Artichoke', 'Chard', 'Chicory']);
1875
+ expect(_inputNode.value).to.equal('ch');
1876
+ });
1877
+ it('does not filter options when autocomplete is "none"', async () => {
1878
+ const el =
1879
+ /** @type {LionCombobox} */
1880
+ await fixture(html`
1881
+ <${hostTag} name="foo" autocomplete="none">
1882
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1883
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1884
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1885
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1886
+ </${hostTag}>
1887
+ `);
1888
+ await mimicUserTyping(el, 'ch');
1889
+ await el.updateComplete;
1890
+ expect(getFilteredOptionValues(el)).to.eql([
1891
+ 'Artichoke',
1892
+ 'Chard',
1893
+ 'Chicory',
1894
+ 'Victoria Plum',
1895
+ ]);
1896
+ });
1897
+ it('does not autocomplete on [Enter] when textbox content does not match options', async () => {
1898
+ const el =
1899
+ /** @type {LionCombobox} */
1900
+ await fixture(html`
1901
+ <${hostTag} name="foo">
1902
+ <${tagOption} .choiceValue="${'Mango'}">Mango</${tagOption}>
1903
+ <${tagOption} .choiceValue="${'Lemon'}">Lemon</${tagOption}>
1904
+ <${tagOption} .choiceValue="${'Apple'}">Apple</${tagOption}>
1905
+ </${hostTag}>
1906
+ `);
1907
+ const { _inputNode } = getComboboxMembers(el);
1908
+ mimicUserTypingAdvanced(el, ['m', 'a', 'k']);
1909
+ await el.updateComplete;
1910
+ mimicKeyPress(_inputNode, 'Enter');
1911
+ await el.updateComplete;
1912
+ await el.updateComplete;
1913
+ expect(_inputNode.value).to.equal('Mak');
1914
+ });
1915
+ it('does not autocomplete on [Enter] when textbox content does not match options and content was cleared via [Backspace]', async () => {
1916
+ const el =
1917
+ /** @type {LionCombobox} */
1918
+ await fixture(html`
1919
+ <${hostTag} name="foo">
1920
+ <${tagOption} .choiceValue="${'Mango'}">Mango</${tagOption}>
1921
+ <${tagOption} .choiceValue="${'Lemon'}">Lemon</${tagOption}>
1922
+ <${tagOption} .choiceValue="${'Apple'}">Apple</${tagOption}>
1923
+ </${hostTag}>
1924
+ `);
1925
+ const { _inputNode } = getComboboxMembers(el);
1926
+ mimicUserTypingAdvanced(el, ['m', 'o', 'Backspace', 'Backspace', 'm', 'o']);
1927
+ await el.updateComplete;
1928
+ await el.updateComplete;
1929
+ await el.updateComplete;
1930
+ await el.updateComplete;
1931
+ mimicKeyPress(_inputNode, 'Enter');
1932
+ await el.updateComplete;
1933
+ await el.updateComplete;
1934
+ expect(_inputNode.value).to.equal('Mo');
1935
+ });
1936
+ it('does not filter options when autocomplete is "inline"', async () => {
1937
+ const el =
1938
+ /** @type {LionCombobox} */
1939
+ await fixture(html`
1940
+ <${hostTag} name="foo" autocomplete="inline">
1941
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1942
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1943
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1944
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1945
+ </${hostTag}>
1946
+ `);
1947
+ await mimicUserTyping(el, 'ch');
1948
+ await el.updateComplete;
1949
+ expect(getFilteredOptionValues(el)).to.eql([
1950
+ 'Artichoke',
1951
+ 'Chard',
1952
+ 'Chicory',
1953
+ 'Victoria Plum',
1954
+ ]);
1955
+ });
1956
+ it('resets "checkedIndex" when going from matched to unmatched textbox value', async () => {
1957
+ const el =
1958
+ /** @type {LionCombobox} */
1959
+ await fixture(html`
1960
+ <${hostTag} name="foo" autocomplete="inline">
1961
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1962
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1963
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1964
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1965
+ </${hostTag}>
1966
+ `);
1967
+ await mimicUserTyping(el, 'ch');
1968
+ await el.updateComplete;
1969
+ expect(el.checkedIndex).to.equal(1);
1970
+ await mimicUserTyping(el, 'cho');
1971
+ await el.updateComplete;
1972
+ expect(el.checkedIndex).to.equal(-1);
1973
+
1974
+ // Works for autocomplete 'both' as well.
1975
+ const el2 =
1976
+ /** @type {LionCombobox} */
1977
+ await fixture(html`
1978
+ <${hostTag} name="foo" autocomplete="both">
1979
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
1980
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
1981
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
1982
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
1983
+ </${hostTag}>
1984
+ `);
1985
+ await mimicUserTyping(el2, 'ch');
1986
+ await el2.updateComplete;
1987
+ expect(el2.checkedIndex).to.equal(1);
1988
+
1989
+ // Also works when 'diminishing amount of chars'
1990
+ await mimicUserTyping(el2, 'x');
1991
+ await el2.updateComplete;
1992
+ expect(el2.checkedIndex).to.equal(-1);
1993
+ });
1994
+ it('resets "checkedIndex" when going from matched to another matched textbox value', async () => {
1995
+ const el =
1996
+ /** @type {LionCombobox} */
1997
+ await fixture(html`
1998
+ <${hostTag} name="foo" autocomplete="both">
1999
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2000
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2001
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2002
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2003
+ </${hostTag}>
2004
+ `);
2005
+
2006
+ /** Goes from 2nd option Chard to 3rd option Chicory */
2007
+ await mimicUserTyping(el, 'ch');
2008
+ await el.updateComplete;
2009
+ expect(el.checkedIndex).to.equal(1);
2010
+ await mimicUserTyping(el, 'chi');
2011
+ await el.updateComplete;
2012
+ expect(el.checkedIndex).to.equal(2);
2013
+ const el2 =
2014
+ /** @type {LionCombobox} */
2015
+ await fixture(html`
2016
+ <${hostTag} name="foo" autocomplete="both">
2017
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2018
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2019
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2020
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2021
+ </${hostTag}>
2022
+ `);
2023
+ await mimicUserTyping(el2, 'ch');
2024
+ await el2.updateComplete;
2025
+ expect(el2.checkedIndex).to.equal(1);
2026
+
2027
+ // match-mode all ensures the user sees Artichoke option, but it's not
2028
+ // auto-completed or auto-selected, because it doesn't start with "cho"
2029
+ // See next test for more clarification
2030
+ await mimicUserTyping(el2, 'cho');
2031
+ await el2.updateComplete;
2032
+ expect(el2.checkedIndex).to.equal(-1);
2033
+ expect(getFilteredOptionValues(el2)).to.eql(['Artichoke']);
2034
+ });
2035
+ it('sets "checkedIndex" only if the match, matches from the beginning of the word', async () => {
2036
+ const el =
2037
+ /** @type {LionCombobox} */
2038
+ await fixture(html`
2039
+ <${hostTag} name="foo" autocomplete="both">
2040
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2041
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2042
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2043
+ <${tagOption} .choiceValue="${'Daikon'}">Daikon</${tagOption}>
2044
+ </${hostTag}>
2045
+ `);
2046
+
2047
+ // first match is Char'd', but better match is 'D'aikon
2048
+ await mimicUserTyping(el, 'd');
2049
+ await el.updateComplete;
2050
+ expect(el.checkedIndex).to.equal(3);
2051
+ expect(getFilteredOptionValues(el)).to.eql(['Chard', 'Daikon']);
2052
+ });
2053
+ it('completes chars inside textbox', async () => {
2054
+ const el =
2055
+ /** @type {LionCombobox} */
2056
+ await fixture(html`
2057
+ <${hostTag} name="foo" autocomplete="inline">
2058
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2059
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2060
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2061
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2062
+ </${hostTag}>
2063
+ `);
2064
+ const { _inputNode } = getComboboxMembers(el);
2065
+ await mimicUserTyping(el, 'ch');
2066
+ await el.updateComplete;
2067
+ expect(_inputNode.value).to.equal('Chard');
2068
+ expect(_inputNode.selectionStart).to.equal('ch'.length);
2069
+ expect(_inputNode.selectionEnd).to.equal('Chard'.length);
2070
+ await mimicUserTypingAdvanced(el, ['i', 'c']);
2071
+ await el.updateComplete;
2072
+ expect(_inputNode.value).to.equal('Chicory');
2073
+ expect(_inputNode.selectionStart).to.equal('chic'.length);
2074
+ expect(_inputNode.selectionEnd).to.equal('Chicory'.length);
2075
+
2076
+ // Diminishing chars, but autocompletion
2077
+ await mimicUserTypingAdvanced(el, ['Backspace', 'Backspace', 'Backspace']); // Ch
2078
+ await el.updateComplete;
2079
+ expect(_inputNode.value).to.equal('Ch');
2080
+ expect(_inputNode.selectionStart).to.equal('ch'.length);
2081
+ expect(_inputNode.selectionEnd).to.equal('ch'.length);
2082
+ });
2083
+ it('synchronizes textbox on overlay close', async () => {
2084
+ const el =
2085
+ /** @type {LionCombobox} */
2086
+ await fixture(html`
2087
+ <${hostTag} name="foo" autocomplete="none">
2088
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2089
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2090
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2091
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2092
+ </${hostTag}>
2093
+ `);
2094
+ const { _inputNode } = getComboboxMembers(el);
2095
+ await el.updateComplete;
2096
+ expect(_inputNode.value).to.equal('');
2097
+
2098
+ /**
2099
+ * @param {'none' | 'list' | 'inline' | 'both'} autocomplete
2100
+ * @param {number|number[]} index
2101
+ * @param {string} valueOnClose
2102
+ */
2103
+ async function performChecks(autocomplete, index, valueOnClose) {
2104
+ await el.updateComplete;
2105
+ el.opened = true;
2106
+ el.setCheckedIndex(-1);
2107
+ await el.updateComplete;
2108
+ el.autocomplete = autocomplete;
2109
+ el.setCheckedIndex(index);
2110
+ await el.updateComplete;
2111
+ el.opened = false;
2112
+ await el.updateComplete;
2113
+ expect(_inputNode.value).to.equal(valueOnClose, autocomplete);
2114
+ }
2115
+ await performChecks('none', 0, 'Artichoke');
2116
+ await performChecks('list', 0, 'Artichoke');
2117
+ await performChecks('inline', 0, 'Artichoke');
2118
+ await performChecks('both', 0, 'Artichoke');
2119
+ el.multipleChoice = true;
2120
+ await performChecks('none', [0, 1], '');
2121
+ await performChecks('list', [0, 1], '');
2122
+ await performChecks('inline', [0, 1], '');
2123
+ await performChecks('both', [0, 1], '');
2124
+ });
2125
+ it('is possible to adjust textbox synchronize condition on overlay close', async () => {
2126
+ const el =
2127
+ /** @type {LionCombobox} */
2128
+ await fixture(html`
2129
+ <${hostTag} name="foo" autocomplete="none" ._syncToTextboxCondition="${() => false}">
2130
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2131
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2132
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2133
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2134
+ </${hostTag}>
2135
+ `);
2136
+ const { _inputNode } = getComboboxMembers(el);
2137
+ expect(_inputNode.value).to.equal('');
2138
+
2139
+ /**
2140
+ * @param {'none' | 'list' | 'inline' | 'both'} autocomplete
2141
+ * @param {number|number[]} index
2142
+ * @param {string} valueOnClose
2143
+ */
2144
+ async function performChecks(autocomplete, index, valueOnClose) {
2145
+ await el.updateComplete;
2146
+ el.opened = true;
2147
+ el.setCheckedIndex(-1);
2148
+ await el.updateComplete;
2149
+ el.autocomplete = autocomplete;
2150
+ el.setCheckedIndex(index);
2151
+ el.opened = false;
2152
+ await el.updateComplete;
2153
+ expect(_inputNode.value).to.equal(valueOnClose);
2154
+ }
2155
+ await performChecks('none', 0, '');
2156
+ await performChecks('list', 0, '');
2157
+ await performChecks('inline', 0, '');
2158
+ await performChecks('both', 0, '');
2159
+ el.multipleChoice = true;
2160
+ await performChecks('none', [0, 1], '');
2161
+ await performChecks('list', [0, 1], '');
2162
+ await performChecks('inline', [0, 1], '');
2163
+ await performChecks('both', [0, 1], '');
2164
+ });
2165
+ it('does inline autocompletion when adding chars', async () => {
2166
+ const el =
2167
+ /** @type {LionCombobox} */
2168
+ await fixture(html`
2169
+ <${hostTag} name="foo" autocomplete="inline">
2170
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2171
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2172
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2173
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2174
+ </${hostTag}>
2175
+ `);
2176
+ await mimicUserTyping(el, 'ch'); // ch
2177
+ await el.updateComplete; // Ch[ard]
2178
+ expect(el.activeIndex).to.equal(1);
2179
+ expect(el.checkedIndex).to.equal(1);
2180
+ await mimicUserTypingAdvanced(el, ['i', 'c']); // Chic
2181
+ await el.updateComplete; // Chic[ory]
2182
+ expect(el.activeIndex).to.equal(2);
2183
+ expect(el.checkedIndex).to.equal(2);
2184
+ await mimicUserTypingAdvanced(el, [
2185
+ 'Backspace',
2186
+ 'Backspace',
2187
+ 'Backspace',
2188
+ 'Backspace',
2189
+ 'h',
2190
+ ]); // Ch
2191
+ await el.updateComplete; // Ch[ard]
2192
+ expect(el.activeIndex).to.equal(1);
2193
+ expect(el.checkedIndex).to.equal(1);
2194
+ });
2195
+ it('does inline autocompletion when changing the word', async () => {
2196
+ const el =
2197
+ /** @type {LionCombobox} */
2198
+ await fixture(html`
2199
+ <${hostTag} name="foo" autocomplete="inline">
2200
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2201
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2202
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2203
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2204
+ </${hostTag}>
2205
+ `);
2206
+ await mimicUserTyping(el, 'ch');
2207
+ await el.updateComplete;
2208
+ expect(el.activeIndex).to.equal(1);
2209
+ expect(el.checkedIndex).to.equal(1);
2210
+ await mimicUserTypingAdvanced(el, ['i']);
2211
+ await el.updateComplete;
2212
+ expect(el.activeIndex).to.equal(2);
2213
+ expect(el.checkedIndex).to.equal(2);
2214
+
2215
+ // Diminishing chars, but autocompletion
2216
+ await mimicUserTypingAdvanced(el, [
2217
+ 'Backspace',
2218
+ 'Backspace',
2219
+ 'Backspace',
2220
+ 'Backspace',
2221
+ 'a',
2222
+ ]); // a
2223
+ await el.updateComplete;
2224
+ expect(el.activeIndex).to.equal(0);
2225
+ expect(el.checkedIndex).to.equal(0);
2226
+ });
2227
+ it('filters options correctly when changing the middle of the word', async () => {
2228
+ const el =
2229
+ /** @type {LionCombobox} */
2230
+ await fixture(html`
2231
+ <${hostTag} name="foo" autocomplete="list" match-mode="all">
2232
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2233
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2234
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2235
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2236
+ </${hostTag}>
2237
+ `);
2238
+ const { _inputNode } = getComboboxMembers(el);
2239
+ await mimicUserTyping(el, 'char');
2240
+ expect(_inputNode.value).to.equal('char');
2241
+ await el.updateComplete; // Char
2242
+
2243
+ expect(getFilteredOptionValues(el)).to.eql(['Chard']);
2244
+ _inputNode.setSelectionRange(3, 3);
2245
+ await mimicUserTypingAdvanced(el, ['Backspace', 'i', 'c', 'o']);
2246
+ await el.updateComplete; // Chicor
2247
+
2248
+ expect(_inputNode.value).to.equal('chicor');
2249
+ expect(getFilteredOptionValues(el)).to.eql(['Chicory']);
2250
+ });
2251
+ it('computation of "user intends autofill" works correctly afer autofill', async () => {
2252
+ const el =
2253
+ /** @type {LionCombobox} */
2254
+ await fixture(html`
2255
+ <${hostTag} name="foo" autocomplete="inline">
2256
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2257
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2258
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2259
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2260
+ </${hostTag}>
2261
+ `);
2262
+ const { _inputNode } = getComboboxMembers(el);
2263
+ await mimicUserTyping(el, 'ch');
2264
+ await el.updateComplete;
2265
+ expect(_inputNode.value).to.equal('Chard');
2266
+ expect(_inputNode.selectionStart).to.equal('Ch'.length);
2267
+ expect(_inputNode.selectionEnd).to.equal('Chard'.length);
2268
+
2269
+ // Autocompletion happened. When we go backwards ('Ch[ard]' => 'Ch'), we should not
2270
+ // autocomplete to 'Chard' anymore.
2271
+ await mimicUserTypingAdvanced(el, ['Backspace']);
2272
+ await el.updateComplete;
2273
+ expect(_inputNode.value).to.equal('Ch'); // so not 'Chard'
2274
+ expect(_inputNode.selectionStart).to.equal('Ch'.length);
2275
+ expect(_inputNode.selectionEnd).to.equal('Ch'.length);
2276
+ });
2277
+ describe('Server side completion support', () => {
2278
+ const listboxData = ['lorem', 'ipsum', 'dolor', 'sit', 'amet'];
2279
+ class MyEl extends LitElement {
2280
+ constructor() {
2281
+ super();
2282
+ /** @type {string[]} */
2283
+ this.options = [...listboxData];
2284
+ }
2285
+ clearOptions() {
2286
+ /** @type {string[]} */
2287
+ this.options = [];
2288
+ this.requestUpdate();
2289
+ }
2290
+ addOption() {
2291
+ this.options.push(`option ${this.options.length + 1}`);
2292
+ this.requestUpdate();
2293
+ }
2294
+ fillAllOptions() {
2295
+ this.options = [...listboxData];
2296
+ this.requestUpdate();
2297
+ }
2298
+ get combobox() {
2299
+ return /** @type {LionCombobox} */ this.shadowRoot?.querySelector('#combobox');
2300
+ }
2301
+ render() {
2302
+ return html`
2303
+ <${hostTag} id="combobox" label="Server side completion">
2304
+ ${this.options.map((option) => html` <${tagOption} .choiceValue="${option}">${option}</${tagOption}> `)}
2305
+ </${hostTag}>
2306
+ `;
2307
+ }
2308
+ }
2309
+ const tagName = defineCE(MyEl);
2310
+ const wrappingTag = unsafeStatic(tagName);
2311
+ it('calls "_handleAutocompletion" after externally changing options', async () => {
2312
+ const el = /** @type {MyEl} */ await fixture(html`<${wrappingTag}></${wrappingTag}>`);
2313
+ await el.combobox.registrationComplete;
2314
+ // @ts-ignore [allow-protected] in test
2315
+ const spy = sinon.spy(el.combobox, '_handleAutocompletion');
2316
+ el.addOption();
2317
+ await el.updateComplete;
2318
+ await el.updateComplete;
2319
+ expect(spy).to.have.been.calledOnce;
2320
+ el.clearOptions();
2321
+ await el.updateComplete;
2322
+ await el.updateComplete;
2323
+ expect(spy).to.have.been.calledTwice;
2324
+ });
2325
+ it('should handle dynamic options', async () => {
2326
+ // Arrange
2327
+ const el = /** @type {MyEl} */ await fixture(html`<${wrappingTag}></${wrappingTag}>`);
2328
+ await el.combobox.registrationComplete;
2329
+ const { _inputNode } = getComboboxMembers(el.combobox);
2330
+
2331
+ // Act (start typing)
2332
+ await mimicUserTypingAdvanced(el.combobox, ['l']);
2333
+ // simulate fetching data from server
2334
+ el.clearOptions();
2335
+ await el.updateComplete;
2336
+ await el.updateComplete;
2337
+ el.fillAllOptions();
2338
+ await el.updateComplete;
2339
+ await el.updateComplete;
2340
+
2341
+ // Assert
2342
+ expect(_inputNode.value).to.equal('lorem');
2343
+ expect(_inputNode.selectionStart).to.equal(1);
2344
+ expect(_inputNode.selectionEnd).to.equal(_inputNode.value.length);
2345
+ expect(getFilteredOptionValues(el.combobox)).to.eql(['lorem', 'dolor']);
2346
+
2347
+ // Act (continue typing)
2348
+ await mimicUserTypingAdvanced(el.combobox, ['o']);
2349
+ // simulate fetching data from server
2350
+ el.clearOptions();
2351
+ await el.updateComplete;
2352
+ await el.updateComplete;
2353
+ el.fillAllOptions();
2354
+ await el.updateComplete;
2355
+ await el.updateComplete;
2356
+
2357
+ // Assert
2358
+ expect(_inputNode.value).to.equal('lorem');
2359
+ expect(_inputNode.selectionStart).to.equal(2);
2360
+ expect(_inputNode.selectionEnd).to.equal(_inputNode.value.length);
2361
+ expect(getFilteredOptionValues(el.combobox)).to.eql(['lorem', 'dolor']);
2362
+
2363
+ // We don't autocomplete when characters are removed
2364
+ await mimicUserTypingAdvanced(el.combobox, ['Backspace', 'Backspace']);
2365
+ expect(_inputNode.value).to.equal('l');
2366
+ expect(_inputNode.selectionStart).to.equal(_inputNode.value.length);
2367
+ });
2368
+ });
2369
+ describe('Subclassers', () => {
2370
+ it('allows to configure autoselect', async () => {
2371
+ class X extends LionCombobox {
2372
+ _autoSelectCondition() {
2373
+ return true;
2374
+ }
2375
+ }
2376
+ const tagName = defineCE(X);
2377
+ const tag = unsafeStatic(tagName);
2378
+ const el =
2379
+ /** @type {LionCombobox} */
2380
+ await fixture(html`
2381
+ <${tag} name="foo" opened>
2382
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2383
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2384
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2385
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2386
+ </${tag}>
2387
+ `);
2388
+ await mimicUserTypingAdvanced(el, ['v', 'i']); // so we have options ['Victoria Plum']
2389
+ await el.updateComplete;
2390
+ expect(el.checkedIndex).to.equal(3);
2391
+ });
2392
+ });
2393
+ it('highlights first occurrence of matching option', async () => {
2394
+ const el =
2395
+ /** @type {LionCombobox} */
2396
+ await fixture(html`
2397
+ <${hostTag} name="foo" match-mode="all">
2398
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2399
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2400
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2401
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2402
+ </${hostTag}>
2403
+ `);
2404
+ const options = el.formElements;
2405
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'c');
2406
+ await el.updateComplete;
2407
+ expect(options[0]).lightDom.to.equal(
2408
+ `<span aria-label="Artichoke">Arti<b>c</b>hoke</span>`,
2409
+ );
2410
+ expect(options[1]).lightDom.to.equal(`<span aria-label="Chard"><b>C</b>hard</span>`);
2411
+ expect(options[2]).lightDom.to.equal(`<span aria-label="Chicory"><b>C</b>hicory</span>`);
2412
+ expect(options[3]).lightDom.to.equal(
2413
+ `<span aria-label="Victoria Plum">Vi<b>c</b>toria Plum</span>`,
2414
+ );
2415
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'ch');
2416
+ await el.updateComplete;
2417
+ expect(options[0]).lightDom.to.equal(
2418
+ `<span aria-label="Artichoke">Arti<b>ch</b>oke</span>`,
2419
+ );
2420
+ expect(options[1]).lightDom.to.equal(`<span aria-label="Chard"><b>Ch</b>ard</span>`);
2421
+ expect(options[2]).lightDom.to.equal(`<span aria-label="Chicory"><b>Ch</b>icory</span>`);
2422
+ expect(options[3]).lightDom.to.equal(`Victoria Plum`);
2423
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'D');
2424
+ await el.updateComplete;
2425
+ expect(options[0]).lightDom.to.equal(`Artichoke`);
2426
+ expect(options[1]).lightDom.to.equal(`<span aria-label="Chard">Char<b>d</b></span>`);
2427
+ expect(options[2]).lightDom.to.equal(`Chicory`);
2428
+ expect(options[3]).lightDom.to.equal(`Victoria Plum`);
2429
+ });
2430
+ it('highlights matching complex options', async () => {
2431
+ const el =
2432
+ /** @type {LionCombobox} */
2433
+ await fixture(html`
2434
+ <${hostTag} name="foo" match-mode="all">
2435
+ <${tagOption} .choiceValue="${'Artichoke'}">
2436
+ <div>Artichoke</div>
2437
+ <small>Cardoon</small>
2438
+ </${tagOption}>
2439
+ <${tagOption} .choiceValue="${'Chard'}">
2440
+ <div>Chard</div>
2441
+ <small>Beet</small>
2442
+ </${tagOption}>
2443
+ <${tagOption} .choiceValue="${'Chicory'}">
2444
+ <div>Chicory</div>
2445
+ <small>Chicory</small>
2446
+ </${tagOption}>
2447
+ <${tagOption} .choiceValue="${'Victoria Plum'}">
2448
+ <div>Victoria Plum</div>
2449
+ <small>Prunus domestica</small>
2450
+ </${tagOption}>
2451
+ </${hostTag}>
2452
+ `);
2453
+ const options = el.formElements;
2454
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'ch');
2455
+ await el.updateComplete;
2456
+ expect(options[0]).lightDom.to.equal(
2457
+ `<span aria-label=" Artichoke Cardoon "><div>Arti<b>ch</b>oke</div><small>Cardoon</small>`,
2458
+ );
2459
+ expect(options[1]).lightDom.to.equal(
2460
+ `<span aria-label=" Chard Beet "><div><b>Ch</b>ard</div><small>Beet</small>`,
2461
+ );
2462
+ expect(options[2]).lightDom.to.equal(
2463
+ `<span aria-label=" Chicory Chicory "><div><b>Ch</b>icory</div><small><b>Ch</b>icory</small>`,
2464
+ );
2465
+ expect(options[3]).lightDom.to.equal(
2466
+ `<div>Victoria Plum</div><small>Prunus domestica</small>`,
2467
+ );
2468
+ });
2469
+ it('resets removes highlights on reset()', async () => {
2470
+ const el =
2471
+ /** @type {LionCombobox} */
2472
+ await fixture(html`
2473
+ <${hostTag} name="foo" match-mode="all">
2474
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2475
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2476
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2477
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2478
+ </${hostTag}>
2479
+ `);
2480
+ const options = el.formElements;
2481
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'c');
2482
+ await el.updateComplete;
2483
+ expect(options[0]).lightDom.to.equal(
2484
+ `<span aria-label="Artichoke">Arti<b>c</b>hoke</span>`,
2485
+ );
2486
+ el.reset();
2487
+ await el.updateComplete;
2488
+ expect(options[0]).lightDom.to.equal(`Artichoke`);
2489
+ });
2490
+ it('synchronizes textbox when autocomplete is "inline" or "both"', async () => {
2491
+ const el =
2492
+ /** @type {LionCombobox} */
2493
+ await fixture(html`
2494
+ <${hostTag} name="foo" autocomplete="none">
2495
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2496
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2497
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2498
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2499
+ </${hostTag}>
2500
+ `);
2501
+ const { _inputNode } = getComboboxMembers(el);
2502
+ expect(_inputNode.value).to.equal('');
2503
+ el.setCheckedIndex(-1);
2504
+ el.autocomplete = 'list';
2505
+ await mimicUserTypingAdvanced(el, ['a', 'r', 't']);
2506
+ expect(_inputNode.value).to.equal('art');
2507
+ el.setCheckedIndex(-1);
2508
+ el.value = '';
2509
+ el.autocomplete = 'none';
2510
+ await mimicUserTypingAdvanced(el, ['a', 'r', 't']);
2511
+ expect(_inputNode.value).to.equal('art');
2512
+ el.setCheckedIndex(-1);
2513
+ el.value = '';
2514
+ el.autocomplete = 'inline';
2515
+ await mimicUserTypingAdvanced(el, ['a', 'r', 't']);
2516
+ expect(_inputNode.value).to.equal('Artichoke');
2517
+ el.setCheckedIndex(-1);
2518
+ el.value = '';
2519
+ el.autocomplete = 'both';
2520
+ await mimicUserTypingAdvanced(el, ['a', 'r', 't']);
2521
+ expect(_inputNode.value).to.equal('Artichoke');
2522
+ });
2523
+ it('synchronizes last index to textbox when autocomplete is "inline" or "both" when multipleChoice', async () => {
2524
+ const el =
2525
+ /** @type {LionCombobox} */
2526
+ await fixture(html`
2527
+ <${hostTag} name="foo" autocomplete="none" multiple-choice>
2528
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2529
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2530
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2531
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2532
+ </${hostTag}>
2533
+ `);
2534
+ const { _inputNode } = getComboboxMembers(el);
2535
+ expect(_inputNode.value).to.eql('');
2536
+ el.setCheckedIndex(-1);
2537
+ el.setCheckedIndex(-1);
2538
+ el.autocomplete = 'list';
2539
+ await mimicUserTypingAdvanced(el, ['a', 'r', 't']);
2540
+ expect(_inputNode.value).to.equal('art');
2541
+ el.setCheckedIndex(-1);
2542
+ el.value = '';
2543
+ el.autocomplete = 'none';
2544
+ await mimicUserTypingAdvanced(el, ['a', 'r', 't']);
2545
+ expect(_inputNode.value).to.equal('art');
2546
+ el.setCheckedIndex(-1);
2547
+ el.value = '';
2548
+ el.autocomplete = 'inline';
2549
+ await mimicUserTypingAdvanced(el, ['a', 'r', 't']);
2550
+ expect(_inputNode.value).to.equal('Artichoke');
2551
+ el.setCheckedIndex(-1);
2552
+ el.value = '';
2553
+ el.autocomplete = 'both';
2554
+ await mimicUserTypingAdvanced(el, ['a', 'r', 't']);
2555
+ expect(_inputNode.value).to.equal('Artichoke');
2556
+ });
2557
+ describe('Subclassers', () => {
2558
+ it('allows to override "_syncCheckedWithTextboxMultiple"', async () => {
2559
+ class X extends LionCombobox {
2560
+ // eslint-disable-next-line no-unused-vars
2561
+ _syncToTextboxCondition() {
2562
+ return true;
2563
+ }
2564
+
2565
+ /**
2566
+ * @param {any} modelValue
2567
+ * @param {any} oldModelValue
2568
+ */
2569
+ // eslint-disable-next-line no-unused-vars
2570
+ _syncToTextboxMultiple(modelValue, oldModelValue) {
2571
+ // In a real scenario (depending on how selection display works),
2572
+ // you could override the default (last selected option) with '' for instance
2573
+ this._setTextboxValue(`${modelValue}-${oldModelValue}-multi`);
2574
+ }
2575
+ }
2576
+ const tagName = defineCE(X);
2577
+ const tag = unsafeStatic(tagName);
2578
+ const el =
2579
+ /** @type {LionCombobox} */
2580
+ await fixture(html`
2581
+ <${tag} name="foo" autocomplete="none" multiple-choice>
2582
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2583
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2584
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2585
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2586
+ </${tag}>
2587
+ `);
2588
+ const { _inputNode } = getComboboxMembers(el);
2589
+ el.setCheckedIndex(-1);
2590
+ el.autocomplete = 'none';
2591
+ el.setCheckedIndex([0]);
2592
+ expect(_inputNode.value).to.equal('Artichoke--multi');
2593
+ el.setCheckedIndex(-1);
2594
+ el.autocomplete = 'list';
2595
+ el.setCheckedIndex([0]);
2596
+ expect(_inputNode.value).to.equal('Artichoke--multi');
2597
+ el.setCheckedIndex(-1);
2598
+ el.autocomplete = 'inline';
2599
+ el.setCheckedIndex([0]);
2600
+ expect(_inputNode.value).to.equal('Artichoke--multi');
2601
+ el.setCheckedIndex(-1);
2602
+ el.autocomplete = 'both';
2603
+ el.setCheckedIndex([0]);
2604
+ expect(_inputNode.value).to.equal('Artichoke--multi');
2605
+ });
2606
+ describe('Override _getTextboxValueFromOption', () => {
2607
+ it('allows to override "_getTextboxValueFromOption" and sync to textbox when multiple', async () => {
2608
+ class X extends LionCombobox {
2609
+ /**
2610
+ * Return the value to be used for the input value
2611
+ * @overridable
2612
+ * @param {any} option
2613
+ * @returns {string}
2614
+ */
2615
+ // eslint-disable-next-line class-methods-use-this
2616
+ _getTextboxValueFromOption(option) {
2617
+ return option.label;
2618
+ }
2619
+ }
2620
+ const tagName = defineCE(X);
2621
+ const tag = unsafeStatic(tagName);
2622
+ const el =
2623
+ /** @type {LionCombobox} */
2624
+ await fixture(html`
2625
+ <${tag} name="foo" autocomplete="both" multiple-choice>
2626
+ <${tagOption} .label="${'Artichoke as label'}" .choiceValue="${{
2627
+ value: 'Artichoke',
2628
+ }}">Artichoke</${tagOption}>
2629
+ <${tagOption} .label="${'Chard as label'}" .choiceValue="${{
2630
+ value: 'Chard',
2631
+ }}">Chard</${tagOption}>
2632
+ <${tagOption} .label="${'Chicory as label'}" .choiceValue="${{
2633
+ value: 'Chicory',
2634
+ }}">Chicory</${tagOption}>
2635
+ <${tagOption} .label="${'Victoria Plum as label'}" .choiceValue="${{
2636
+ value: 'Victoria Plum',
2637
+ }}">Victoria Plum</${tagOption}>
2638
+ </${tag}>
2639
+ `);
2640
+ const { _inputNode } = getComboboxMembers(el);
2641
+ el.setCheckedIndex(-1);
2642
+
2643
+ // Act
2644
+ el.setCheckedIndex([0]);
2645
+
2646
+ // Assert
2647
+ expect(_inputNode.value).to.equal('Artichoke as label');
2648
+
2649
+ // Act
2650
+ el.setCheckedIndex([3]);
2651
+
2652
+ // Assert
2653
+ expect(_inputNode.value).to.equal('Victoria Plum as label');
2654
+ });
2655
+ it('allows to override "_getTextboxValueFromOption" and sync modelValue with textbox', async () => {
2656
+ class X extends LionCombobox {
2657
+ /**
2658
+ * Return the value to be used for the input value
2659
+ * @overridable
2660
+ * @param {any} option
2661
+ * @returns {string}
2662
+ */
2663
+ // eslint-disable-next-line class-methods-use-this
2664
+ _getTextboxValueFromOption(option) {
2665
+ if (option && option.label) {
2666
+ return option.label;
2667
+ }
2668
+ return this.modelValue;
2669
+ }
2670
+ }
2671
+ const tagName = defineCE(X);
2672
+ const tag = unsafeStatic(tagName);
2673
+ const el =
2674
+ /** @type {LionCombobox} */
2675
+ await fixture(html`
2676
+ <${tag} name="foo">
2677
+ <${tagOption} .label="${'Artichoke as label'}" .choiceValue="${{
2678
+ value: 'Artichoke',
2679
+ }}">Artichoke</${tagOption}>
2680
+ <${tagOption} .label="${'Chard as label'}" .choiceValue="${{
2681
+ value: 'Chard',
2682
+ }}" checked>Chard</${tagOption}>
2683
+ <${tagOption} .label="${'Chicory as label'}" .choiceValue="${{
2684
+ value: 'Chicory',
2685
+ }}">Chicory</${tagOption}>
2686
+ <${tagOption} .label="${'Victoria Plum as label'}" .choiceValue="${{
2687
+ value: 'Victoria Plum',
2688
+ }}">Victoria Plum</${tagOption}>
2689
+ </${tag}>
2690
+ `);
2691
+ const { _inputNode } = getComboboxMembers(el);
2692
+
2693
+ // Assume
2694
+ expect(_inputNode.value).to.equal('Chard as label');
2695
+
2696
+ // Act
2697
+ el.modelValue = {
2698
+ value: 'Chicory',
2699
+ };
2700
+ await el.updateComplete;
2701
+
2702
+ // Assert
2703
+ expect(_inputNode.value).to.equal('Chicory as label');
2704
+ });
2705
+ it('allows to override "_getTextboxValueFromOption" and clears modelValue and textbox value on clear()', async () => {
2706
+ class X extends LionCombobox {
2707
+ /**
2708
+ * Return the value to be used for the input value
2709
+ * @overridable
2710
+ * @param {any} option
2711
+ * @returns {string}
2712
+ */
2713
+ // eslint-disable-next-line class-methods-use-this
2714
+ _getTextboxValueFromOption(option) {
2715
+ if (option && option.label) {
2716
+ return option.label;
2717
+ }
2718
+ return this.modelValue;
2719
+ }
2720
+ }
2721
+ const tagName = defineCE(X);
2722
+ const tag = unsafeStatic(tagName);
2723
+ const el =
2724
+ /** @type {LionCombobox} */
2725
+ await fixture(html`
2726
+ <${tag} name="foo" .modelValue="${{
2727
+ value: 'Artichoke',
2728
+ }}">
2729
+ <${tagOption} .label="${'Artichoke as label'}" .choiceValue="${{
2730
+ value: 'Artichoke',
2731
+ }}">Artichoke</${tagOption}>
2732
+ <${tagOption} .label="${'Chard as label'}" .choiceValue="${{
2733
+ value: 'Chard',
2734
+ }}">Chard</${tagOption}>
2735
+ <${tagOption} .label="${'Chicory as label'}" .choiceValue="${{
2736
+ value: 'Chicory',
2737
+ }}">Chicory</${tagOption}>
2738
+ <${tagOption} .label="${'Victoria Plum as label'}" .choiceValue="${{
2739
+ value: 'Victoria Plum',
2740
+ }}">Victoria Plum</${tagOption}>
2741
+ </${tag}>
2742
+ `);
2743
+ const { _inputNode } = getComboboxMembers(el);
2744
+
2745
+ // Assume
2746
+ expect(_inputNode.value).to.equal('Artichoke as label');
2747
+
2748
+ // Act
2749
+ el.clear();
2750
+
2751
+ // Assert
2752
+ expect(el.modelValue).to.equal('');
2753
+ expect(_inputNode.value).to.equal('');
2754
+ });
2755
+ it('allows to override "_getTextboxValueFromOption" and syncs textbox to modelValue', async () => {
2756
+ class X extends LionCombobox {
2757
+ /**
2758
+ * Return the value to be used for the input value
2759
+ * @overridable
2760
+ * @param {any} option
2761
+ * @returns {string}
2762
+ */
2763
+ // eslint-disable-next-line class-methods-use-this
2764
+ _getTextboxValueFromOption(option) {
2765
+ return option.label;
2766
+ }
2767
+ }
2768
+ const tagName = defineCE(X);
2769
+ const tag = unsafeStatic(tagName);
2770
+ const el =
2771
+ /** @type {LionCombobox} */
2772
+ await fixture(html`
2773
+ <${tag} name="foo">
2774
+ <${tagOption} .label="${'Artichoke as label'}" .choiceValue="${{
2775
+ value: 'Artichoke',
2776
+ }}">Artichoke</${tagOption}>
2777
+ <${tagOption} .label="${'Chard as label'}" .choiceValue="${{
2778
+ value: 'Chard',
2779
+ }}">Chard</${tagOption}>
2780
+ <${tagOption} .label="${'Chicory as label'}" .choiceValue="${{
2781
+ value: 'Chicory',
2782
+ }}">Chicory</${tagOption}>
2783
+ <${tagOption} .label="${'Victoria Plum as label'}" .choiceValue="${{
2784
+ value: 'Victoria Plum',
2785
+ }}">Victoria Plum</${tagOption}>
2786
+ </${tag}>
2787
+ `);
2788
+ const { _inputNode } = getComboboxMembers(el);
2789
+
2790
+ /** @param {string} autocompleteMode */
2791
+ async function performChecks(autocompleteMode) {
2792
+ await el.updateComplete;
2793
+ await mimicUserTypingAdvanced(el, ['A', 'r', 't', 'i']);
2794
+ await el.updateComplete;
2795
+ expect(_inputNode.value).to.equal('Arti', `autocompleteMode is ${autocompleteMode}`);
2796
+ await el.updateComplete;
2797
+ expect(el.checkedIndex).to.equal(-1, `autocompleteMode is ${autocompleteMode}`);
2798
+ _inputNode.value = '';
2799
+ }
2800
+ el.autocomplete = 'none';
2801
+ await performChecks('none');
2802
+ el.autocomplete = 'list';
2803
+ await performChecks('list');
2804
+ });
2805
+ });
2806
+ it('allows to override "_onFilterMatch" and "_onFilterUmatch"', async () => {
2807
+ class X extends LionCombobox {
2808
+ /**
2809
+ * @overridable
2810
+ * @param {LionOption & {__originalInnerHTML?:string}} option
2811
+ * @param {string} matchingString
2812
+ * @protected
2813
+ */
2814
+ _onFilterMatch(option, matchingString) {
2815
+ Array.from(option.children).forEach((child) => {
2816
+ if (child.hasAttribute('data-key')) {
2817
+ this._highlightMatchedOption(child, matchingString);
2818
+ }
2819
+ });
2820
+ // Alternatively, an extension can add an animation here
2821
+ // option.style.display = '';
2822
+ }
2823
+
2824
+ /**
2825
+ * @overridable
2826
+ * @param {LionOption & {__originalInnerHTML?:string}} option
2827
+ * @param {string} [curValue]
2828
+ * @param {string} [prevValue]
2829
+ * @protected
2830
+ */
2831
+ // eslint-disable-next-line no-unused-vars
2832
+ _onFilterUnmatch(option, curValue, prevValue) {
2833
+ Array.from(option.children).forEach((child) => {
2834
+ if (child.hasAttribute('data-key')) {
2835
+ this._unhighlightMatchedOption(child);
2836
+ }
2837
+ });
2838
+ // Alternatively, an extension can add an animation here
2839
+ // option.style.display = 'none';
2840
+ }
2841
+ }
2842
+ const tagName = defineCE(X);
2843
+ const tag = unsafeStatic(tagName);
2844
+ const el =
2845
+ /** @type {LionCombobox} */
2846
+ await fixture(html`
2847
+ <${tag} name="foo" autocomplete="both">
2848
+ <${tagOption} .choiceValue="${'Artichoke'}">
2849
+ <div data-key>Artichoke</div>
2850
+ <small>Cardoon</small>
2851
+ </${tagOption}>
2852
+ <${tagOption} .choiceValue="${'Chard'}">
2853
+ <div data-key>Chard</div>
2854
+ <small>Beet</small>
2855
+ </${tagOption}>
2856
+ <${tagOption} .choiceValue="${'Chicory'}">
2857
+ <div data-key>Chicory</div>
2858
+ <small>Chicory</small>
2859
+ </${tagOption}>
2860
+ <${tagOption} .choiceValue="${'Victoria Plum'}">
2861
+ <div data-key>Victoria Plum</div>
2862
+ <small>Prunus domestica</small>
2863
+ </${tagOption}>
2864
+ </${tag}>
2865
+ `);
2866
+ const options = el.formElements;
2867
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'ch');
2868
+ await el.updateComplete;
2869
+ expect(options[0]).lightDom.to.equal(
2870
+ `<div data-key><span aria-label="Artichoke">Arti<b>ch</b>oke</span></div><small>Cardoon</small>`,
2871
+ );
2872
+ expect(options[1]).lightDom.to.equal(
2873
+ `<div data-key><span aria-label="Chard"><b>Ch</b>ard</div></span><small>Beet</small>`,
2874
+ );
2875
+ expect(options[2]).lightDom.to.equal(
2876
+ `<div data-key><span aria-label="Chicory"><b>Ch</b>icory</span></div><small>Chicory</small>`,
2877
+ );
2878
+ expect(options[3]).lightDom.to.equal(
2879
+ `<div data-key>Victoria Plum</div><small>Prunus domestica</small>`,
2880
+ );
2881
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'D');
2882
+ await el.updateComplete;
2883
+ expect(options[0]).lightDom.to.equal(
2884
+ `<div data-key>Artichoke</div><small>Cardoon</small>`,
2885
+ );
2886
+ expect(options[1]).lightDom.to.equal(
2887
+ `<div data-key><span aria-label="Chard">Char<b>d</b></div></span><small>Beet</small>`,
2888
+ );
2889
+ expect(options[2]).lightDom.to.equal(`<div data-key>Chicory</div><small>Chicory</small>`);
2890
+ expect(options[3]).lightDom.to.equal(
2891
+ `<div data-key>Victoria Plum</div><small>Prunus domestica</small>`,
2892
+ );
2893
+ });
2894
+ });
2895
+ describe('Active index behavior', () => {
2896
+ it('sets the active index to the closest match on open by default', async () => {
2897
+ const el =
2898
+ /** @type {LionCombobox} */
2899
+ await fixture(html`
2900
+ <${hostTag} name="foo">
2901
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2902
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2903
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2904
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2905
+ </${hostTag}>
2906
+ `);
2907
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'cha');
2908
+ await el.updateComplete;
2909
+ expect(el.activeIndex).to.equal(1);
2910
+ });
2911
+ it('changes whether activeIndex is set to the closest match automatically depending on autocomplete', async () => {
2912
+ /**
2913
+ * Automatic selection (setting activeIndex to closest matching option) in lion is set for
2914
+ * 'inline' & 'both' autocomplete, because it is unavoidable there
2915
+ * For 'list' & 'none' autocomplete, it is turned off and manual selection is required.
2916
+ * TODO: Make this configurable for list & none autocomplete?
2917
+ */
2918
+ const el =
2919
+ /** @type {LionCombobox} */
2920
+ await fixture(html`
2921
+ <${hostTag} name="foo" autocomplete="none">
2922
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
2923
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
2924
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
2925
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
2926
+ </${hostTag}>
2927
+ `);
2928
+ const { _inputNode } = getComboboxMembers(el);
2929
+
2930
+ /**
2931
+ * @param {LionCombobox} elm
2932
+ * @param {'none'|'list'|'inline'|'both'} autocomplete
2933
+ */
2934
+ async function setup(elm, autocomplete) {
2935
+ // eslint-disable-next-line no-param-reassign
2936
+ elm.autocomplete = autocomplete;
2937
+ // eslint-disable-next-line no-param-reassign
2938
+ elm.activeIndex = -1;
2939
+ // eslint-disable-next-line no-param-reassign
2940
+ elm.setCheckedIndex(-1);
2941
+ // eslint-disable-next-line no-param-reassign
2942
+ elm.opened = true;
2943
+ await elm.updateComplete;
2944
+ }
2945
+
2946
+ // https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
2947
+ // Example 1. List Autocomplete with Manual Selection:
2948
+ // does not set active at all until user selects
2949
+ await setup(el, 'none');
2950
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'cha');
2951
+ await el.updateComplete;
2952
+ expect(el.activeIndex).to.equal(-1);
2953
+ expect(el.opened).to.be.true;
2954
+ mimicKeyPress(_inputNode, 'Enter');
2955
+ expect(el.opened).to.be.false;
2956
+ expect(el.activeIndex).to.equal(-1);
2957
+
2958
+ // https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
2959
+ // Example 2. List Autocomplete with Automatic Selection:
2960
+ // does not set active at all until user selects
2961
+ await setup(el, 'list');
2962
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'cha');
2963
+ await el.updateComplete;
2964
+ expect(el.opened).to.be.true;
2965
+ expect(el.activeIndex).to.equal(-1);
2966
+ mimicKeyPress(_inputNode, 'Enter');
2967
+ expect(el.activeIndex).to.equal(-1);
2968
+ expect(el.opened).to.be.false;
2969
+
2970
+ // https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
2971
+ // Example 3. List with Inline Autocomplete (mostly, but with aria-autocomplete="inline")
2972
+ await setup(el, 'inline');
2973
+ await mimicUserTyping(/** @type {LionCombobox} */ el, '');
2974
+ await el.updateComplete;
2975
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'cha');
2976
+ await el.updateComplete;
2977
+ await el.updateComplete;
2978
+ expect(el.opened).to.be.true;
2979
+ expect(el.activeIndex).to.equal(1);
2980
+ mimicKeyPress(_inputNode, 'Enter');
2981
+ await el.updateComplete;
2982
+ await el.updateComplete;
2983
+ expect(el.activeIndex).to.equal(-1);
2984
+ expect(el.opened).to.be.false;
2985
+
2986
+ // https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
2987
+ // Example 3. List with Inline Autocomplete
2988
+ await setup(el, 'both');
2989
+ await mimicUserTyping(/** @type {LionCombobox} */ el, '');
2990
+ await el.updateComplete;
2991
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'cha');
2992
+ await el.updateComplete;
2993
+ mimicKeyPress(_inputNode, 'Enter');
2994
+ expect(el.activeIndex).to.equal(1);
2995
+ expect(el.opened).to.be.false;
2996
+ });
2997
+ it('sets the active index to the closest match on autocomplete', async () => {
2998
+ const el =
2999
+ /** @type {LionCombobox} */
3000
+ await fixture(html`
3001
+ <${hostTag} name="foo" autocomplete="both">
3002
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
3003
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
3004
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
3005
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
3006
+ </${hostTag}>
3007
+ `);
3008
+ const { _inputNode } = getComboboxMembers(el);
3009
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'cha');
3010
+ await el.updateComplete;
3011
+ expect(el.activeIndex).to.equal(1);
3012
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'ch');
3013
+ await el.updateComplete;
3014
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'chi');
3015
+ // Chard no longer matches, so should switch active to Chicory
3016
+ await el.updateComplete;
3017
+ expect(el.activeIndex).to.equal(2);
3018
+
3019
+ // select artichoke
3020
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'artichoke');
3021
+ await el.updateComplete;
3022
+ mimicKeyPress(_inputNode, 'Enter');
3023
+ await mimicUserTyping(/** @type {LionCombobox} */ el, '');
3024
+ await el.updateComplete;
3025
+ // change selection, active index should update to closest match
3026
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'vic');
3027
+ await el.updateComplete;
3028
+ expect(el.activeIndex).to.equal(3);
3029
+ });
3030
+ it('supports clearing by [Escape] key and resets active state on all options', async () => {
3031
+ const el =
3032
+ /** @type {LionCombobox} */
3033
+ await fixture(html`
3034
+ <${hostTag} name="foo">
3035
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
3036
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
3037
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
3038
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
3039
+ </${hostTag}>
3040
+ `);
3041
+ const { _inputNode } = getComboboxMembers(el);
3042
+
3043
+ // Select something
3044
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'cha');
3045
+ await el.updateComplete;
3046
+ mimicKeyPress(_inputNode, 'Enter');
3047
+ expect(el.activeIndex).to.equal(1);
3048
+ mimicKeyPress(_inputNode, 'Escape');
3049
+ await el.updateComplete;
3050
+ expect(_inputNode.textContent).to.equal('');
3051
+ el.formElements.forEach((option) => expect(option.active).to.be.false);
3052
+
3053
+ // change selection, active index should update to closest match
3054
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'vic');
3055
+ await el.updateComplete;
3056
+ expect(el.activeIndex).to.equal(3);
3057
+ });
3058
+ });
3059
+ describe('Accessibility', () => {
3060
+ it('synchronizes autocomplete option to textbox', async () => {
3061
+ let el;
3062
+ [el] = await fruitFixture({
3063
+ autocomplete: 'both',
3064
+ });
3065
+ expect(
3066
+ getComboboxMembers(/** @type {LionCombobox} */ el)._inputNode.getAttribute(
3067
+ 'aria-autocomplete',
3068
+ ),
3069
+ ).to.equal('both');
3070
+ [el] = await fruitFixture({
3071
+ autocomplete: 'list',
3072
+ });
3073
+ expect(
3074
+ getComboboxMembers(/** @type {LionCombobox} */ el)._inputNode.getAttribute(
3075
+ 'aria-autocomplete',
3076
+ ),
3077
+ ).to.equal('list');
3078
+ [el] = await fruitFixture({
3079
+ autocomplete: 'none',
3080
+ });
3081
+ expect(
3082
+ getComboboxMembers(/** @type {LionCombobox} */ el)._inputNode.getAttribute(
3083
+ 'aria-autocomplete',
3084
+ ),
3085
+ ).to.equal('none');
3086
+ });
3087
+ it('updates aria-activedescendant on textbox node', async () => {
3088
+ const el =
3089
+ /** @type {LionCombobox} */
3090
+ await fixture(html`
3091
+ <${hostTag} name="foo" autocomplete="none">
3092
+ <${tagOption} .choiceValue="${'Artichoke'}" id="artichoke-option"
3093
+ >Artichoke</${tagOption}
3094
+ >
3095
+ <${tagOption} .choiceValue="${'Chard'}" id="chard-option">Chard</${tagOption}>
3096
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
3097
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
3098
+ </${hostTag}>
3099
+ `);
3100
+ const elProts = getComboboxMembers(el);
3101
+ expect(elProts._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal(
3102
+ null,
3103
+ );
3104
+ expect(el.formElements[1].active).to.equal(false);
3105
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'ch');
3106
+ await el.updateComplete;
3107
+ expect(elProts._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal(
3108
+ null,
3109
+ );
3110
+ // el._inputNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
3111
+ mimicKeyPress(elProts._inputNode, 'ArrowDown');
3112
+ expect(elProts._activeDescendantOwnerNode.getAttribute('aria-activedescendant')).to.equal(
3113
+ 'artichoke-option',
3114
+ );
3115
+ expect(el.formElements[1].active).to.equal(false);
3116
+ const el2 =
3117
+ /** @type {LionCombobox} */
3118
+ await fixture(html`
3119
+ <${hostTag} name="foo" autocomplete="both" match-mode="begin">
3120
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
3121
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
3122
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
3123
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
3124
+ </${hostTag}>
3125
+ `);
3126
+ const el2Prots = getComboboxMembers(el2);
3127
+ await mimicUserTyping(/** @type {LionCombobox} */ el2, 'ch');
3128
+ await el2.updateComplete;
3129
+ expect(
3130
+ el2Prots._activeDescendantOwnerNode.getAttribute('aria-activedescendant'),
3131
+ ).to.equal(el2.formElements[1].id);
3132
+ expect(el2.formElements[1].active).to.equal(true);
3133
+ el2.autocomplete = 'list';
3134
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'ch');
3135
+ await el2.updateComplete;
3136
+ expect(
3137
+ el2Prots._activeDescendantOwnerNode.getAttribute('aria-activedescendant'),
3138
+ ).to.equal(el2.formElements[1].id);
3139
+ expect(el2.formElements[1].active).to.equal(true);
3140
+ });
3141
+ it('adds aria-label to highlighted options', async () => {
3142
+ const [el, options] = await fruitFixture({
3143
+ autocomplete: 'both',
3144
+ matchMode: 'all',
3145
+ });
3146
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'choke');
3147
+ await el.updateComplete;
3148
+ // @ts-ignore [dynamic-key-access-in-test]
3149
+ const labelledElement = options[0].querySelector('span[aria-label="Artichoke"]');
3150
+ expect(labelledElement).to.not.be.null;
3151
+ expect(labelledElement.innerText).to.equal('Artichoke');
3152
+ });
3153
+ it('adds aria-label to highlighted complex options', async () => {
3154
+ const el =
3155
+ /** @type {LionCombobox} */
3156
+ await fixture(html`
3157
+ <${hostTag} name="foo" match-mode="all">
3158
+ <${tagOption} .choiceValue="${'Artichoke'}">
3159
+ <div>Artichoke</div>
3160
+ <small>Cardoon</small>
3161
+ </${tagOption}>
3162
+ </${hostTag}>
3163
+ `);
3164
+ const options = el.formElements;
3165
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'choke');
3166
+ await el.updateComplete;
3167
+ const labelledElement = options[0].querySelector(
3168
+ 'span[aria-label=" Artichoke Cardoon "]',
3169
+ );
3170
+ expect(labelledElement).to.not.be.null;
3171
+ expect(labelledElement.innerText).to.equal('Artichoke\nCardoon');
3172
+ });
3173
+ });
3174
+ });
3175
+ describe('Accessibility', () => {
3176
+ describe('Aria versions', () => {
3177
+ it('[role=combobox] wraps input node in v1.1', async () => {
3178
+ const el =
3179
+ /** @type {LionCombobox} */
3180
+ await fixture(html`
3181
+ <${hostTag} name="foo" ._ariaVersion="${'1.1'}">
3182
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
3183
+ </${hostTag}>
3184
+ `);
3185
+ const { _comboboxNode, _inputNode } = getComboboxMembers(el);
3186
+ expect(_comboboxNode.contains(_inputNode)).to.be.true;
3187
+ });
3188
+ it('has one input node with [role=combobox] in v1.0', async () => {
3189
+ const el =
3190
+ /** @type {LionCombobox} */
3191
+ await fixture(html`
3192
+ <${hostTag} name="foo" ._ariaVersion="${'1.0'}">
3193
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
3194
+ </${hostTag}>
3195
+ `);
3196
+ const { _comboboxNode, _inputNode } = getComboboxMembers(el);
3197
+ expect(_comboboxNode).to.equal(_inputNode);
3198
+ });
3199
+ it('autodetects aria version and sets it to 1.1 on Chromium browsers', async () => {
3200
+ const browserDetectionIsChromiumOriginal = browserDetection.isChromium;
3201
+ browserDetection.isChromium = true;
3202
+ const el =
3203
+ /** @type {LionCombobox} */
3204
+ await fixture(html`
3205
+ <${hostTag} name="foo">
3206
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
3207
+ </${hostTag}>
3208
+ `);
3209
+ const elProts = getComboboxMembers(el);
3210
+ expect(elProts._ariaVersion).to.equal('1.1');
3211
+ browserDetection.isChromium = false;
3212
+ const el2 =
3213
+ /** @type {LionCombobox} */
3214
+ await fixture(html`
3215
+ <${hostTag} name="foo">
3216
+ <${tagOption} .choiceValue="${'10'}" checked>Item 1</${tagOption}>
3217
+ </${hostTag}>
3218
+ `);
3219
+ const el2Prots = getComboboxMembers(el2);
3220
+ expect(el2Prots._ariaVersion).to.equal('1.0');
3221
+
3222
+ // restore...
3223
+ browserDetection.isChromium = browserDetectionIsChromiumOriginal;
3224
+ });
3225
+ });
3226
+ });
3227
+ describe('Multiple Choice', () => {
3228
+ // TODO: possibly later share test with select-rich if it officially supports multipleChoice
3229
+ it('does not close listbox on click', async () => {
3230
+ const el =
3231
+ /** @type {LionCombobox} */
3232
+ await fixture(html`
3233
+ <${hostTag} name="foo" multiple-choice>
3234
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
3235
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
3236
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
3237
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
3238
+ </${hostTag}>
3239
+ `);
3240
+
3241
+ // activate opened listbox
3242
+ await mimicUserTyping(el, 'ch');
3243
+ await el.updateComplete;
3244
+ expect(el.opened).to.equal(true);
3245
+ const visibleOptions = el.formElements.filter((o) => o.style.display !== 'none');
3246
+ visibleOptions[0].click();
3247
+ expect(el.opened).to.equal(true);
3248
+ });
3249
+ it('does not close listbox on [Enter]', async () => {
3250
+ const el =
3251
+ /** @type {LionCombobox} */
3252
+ await fixture(html`
3253
+ <${hostTag} name="foo" multiple-choice>
3254
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
3255
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
3256
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
3257
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
3258
+ </${hostTag}>
3259
+ `);
3260
+ const { _inputNode } = getComboboxMembers(el);
3261
+ await mimicUserTyping(el, 'art');
3262
+ await el.updateComplete;
3263
+ expect(el.opened).to.equal(true);
3264
+
3265
+ // N.B. we do only trigger keydown here (and not mimicKeypress (both keyup and down)),
3266
+ // because this closely mimics what happens in the browser
3267
+ _inputNode.dispatchEvent(
3268
+ new KeyboardEvent('keydown', {
3269
+ key: 'Enter',
3270
+ }),
3271
+ );
3272
+ expect(el.opened).to.equal(true);
3273
+ });
3274
+ it('clears textbox after selection of a new item on [enter]', async () => {
3275
+ const el =
3276
+ /** @type {LionCombobox} */
3277
+ await fixture(html`
3278
+ <${hostTag} name="foo" multiple-choice>
3279
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
3280
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
3281
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
3282
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
3283
+ </${hostTag}>
3284
+ `);
3285
+ const { _inputNode } = getComboboxMembers(el);
3286
+ const options = el.formElements;
3287
+ await mimicUserTyping(el, 'art');
3288
+ await el.updateComplete;
3289
+ expect(el.opened).to.equal(true);
3290
+ const visibleOptions = () => options.filter((o) => o.style.display !== 'none');
3291
+ expect(visibleOptions().length).to.equal(1);
3292
+
3293
+ // N.B. we do only trigger keydown here (and not mimicKeypress (both keyup and down)),
3294
+ // because this closely mimics what happens in the browser
3295
+ _inputNode.dispatchEvent(
3296
+ new KeyboardEvent('keydown', {
3297
+ key: 'Enter',
3298
+ }),
3299
+ );
3300
+ expect(el.opened).to.equal(true);
3301
+ expect(visibleOptions().length).to.equal(0);
3302
+ expect(_inputNode.value).to.equal('');
3303
+ });
3304
+ it('clears textbox after selection of a new item on click', async () => {
3305
+ const el =
3306
+ /** @type {LionCombobox} */
3307
+ await fixture(html`
3308
+ <${hostTag} name="foo" multiple-choice>
3309
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
3310
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
3311
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
3312
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
3313
+ </${hostTag}>
3314
+ `);
3315
+ const { _inputNode } = getComboboxMembers(el);
3316
+ const options = el.formElements;
3317
+ await mimicUserTyping(el, 'art');
3318
+ await el.updateComplete;
3319
+ expect(el.opened).to.equal(true);
3320
+ const visibleOptions = () => options.filter((o) => o.style.display !== 'none');
3321
+ expect(visibleOptions().length).to.equal(1);
3322
+ visibleOptions()[0].click();
3323
+ expect(el.opened).to.equal(true);
3324
+ expect(visibleOptions().length).to.equal(0);
3325
+ expect(_inputNode.value).to.equal('');
3326
+ });
3327
+ it('submits form on [Enter] when listbox is closed', async () => {
3328
+ const submitSpy = sinon.spy((e) => e.preventDefault());
3329
+ const el =
3330
+ /** @type {HTMLFormElement} */
3331
+ await fixture(html`
3332
+ <form @submit=${submitSpy}>
3333
+ <${hostTag} name="foo" multiple-choice>
3334
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
3335
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
3336
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
3337
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
3338
+ </${hostTag}>
3339
+ <button type="submit">submit</button>
3340
+ </form>
3341
+ `);
3342
+ const combobox = /** @type {LionCombobox} */ el.querySelector('[name="foo"]');
3343
+ const { _inputNode } = getComboboxMembers(combobox);
3344
+ await combobox.updateComplete;
3345
+ _inputNode.focus();
3346
+ await sendKeys({
3347
+ press: 'Enter',
3348
+ });
3349
+ expect(submitSpy.callCount).to.equal(1);
3350
+ });
3351
+ it('does not submit form on [Enter] when listbox is opened', async () => {
3352
+ const submitSpy = sinon.spy((e) => e.preventDefault());
3353
+ const el =
3354
+ /** @type {HTMLFormElement} */
3355
+ await fixture(html`
3356
+ <form @submit=${submitSpy}>
3357
+ <${hostTag} name="foo" multiple-choice>
3358
+ <${tagOption} .choiceValue="${'Artichoke'}">Artichoke</${tagOption}>
3359
+ <${tagOption} .choiceValue="${'Chard'}">Chard</${tagOption}>
3360
+ <${tagOption} .choiceValue="${'Chicory'}">Chicory</${tagOption}>
3361
+ <${tagOption} .choiceValue="${'Victoria Plum'}">Victoria Plum</${tagOption}>
3362
+ </${hostTag}>
3363
+ <button type="submit">submit</button>
3364
+ </form>
3365
+ `);
3366
+ const combobox = /** @type {LionCombobox} */ el.querySelector('[name="foo"]');
3367
+ const { _inputNode } = getComboboxMembers(combobox);
3368
+ combobox.opened = true;
3369
+ await combobox.updateComplete;
3370
+ _inputNode.focus();
3371
+ await sendKeys({
3372
+ press: 'Enter',
3373
+ });
3374
+ expect(submitSpy.callCount).to.equal(0);
3375
+ });
3376
+ });
3377
+ describe('Match Mode', () => {
3378
+ it('has a default value of "all"', async () => {
3379
+ const [el] = await fruitFixture();
3380
+ expect(el.matchMode).to.equal('all');
3381
+ });
3382
+ it('will suggest partial matches (in the middle of the word) when set to "all"', async () => {
3383
+ const [el] = await fruitFixture();
3384
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'c');
3385
+ await el.updateComplete;
3386
+ expect(getFilteredOptionValues(/** @type {LionCombobox} */ el)).to.eql([
3387
+ 'Artichoke',
3388
+ 'Chard',
3389
+ 'Chicory',
3390
+ 'Victoria Plum',
3391
+ ]);
3392
+ });
3393
+ it('will only suggest beginning matches when set to "begin"', async () => {
3394
+ const [el] = await fruitFixture({
3395
+ matchMode: 'begin',
3396
+ });
3397
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'ch');
3398
+ await el.updateComplete;
3399
+ expect(getFilteredOptionValues(/** @type {LionCombobox} */ el)).to.eql([
3400
+ 'Chard',
3401
+ 'Chicory',
3402
+ ]);
3403
+ });
3404
+ describe('Subclassers', () => {
3405
+ it('allows for custom matching functions', async () => {
3406
+ const [el] = await fruitFixture();
3407
+ /**
3408
+ * @param {{ value: any; }} option
3409
+ * @param {any} curValue
3410
+ */
3411
+ function onlyExactMatches(option, curValue) {
3412
+ return option.value === curValue;
3413
+ }
3414
+ el.matchCondition = onlyExactMatches;
3415
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'Chicory');
3416
+ await el.updateComplete;
3417
+ expect(getFilteredOptionValues(/** @type {LionCombobox} */ el)).to.eql(['Chicory']);
3418
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'Chicor');
3419
+ await el.updateComplete;
3420
+ expect(getFilteredOptionValues(/** @type {LionCombobox} */ el)).to.eql([]);
3421
+ });
3422
+ });
3423
+ });
3424
+ describe('Disabled state', () => {
3425
+ it('does not open overlay or allow input when disabled', async () => {
3426
+ const [el] = await fruitFixture({
3427
+ disabled: true,
3428
+ });
3429
+ expect(el.disabled).to.equal(true);
3430
+ const { _inputNode } = getComboboxMembers(/** @type {LionCombobox} */ el);
3431
+ expect(el.disabled).to.be.true;
3432
+ expect(_inputNode.disabled).to.be.true;
3433
+
3434
+ // Try to open overlay by clicking input
3435
+ _inputNode.dispatchEvent(
3436
+ new Event('click', {
3437
+ bubbles: true,
3438
+ composed: true,
3439
+ }),
3440
+ );
3441
+ await el.updateComplete;
3442
+ expect(el.opened).to.be.false;
3443
+ });
3444
+ it('does open overlay or allow input when disabled state is removed after it was previously disabled', async () => {
3445
+ const [el] = await fruitFixture({
3446
+ disabled: true,
3447
+ });
3448
+ expect(el.disabled).to.equal(true);
3449
+ const { _inputNode } = getComboboxMembers(/** @type {LionCombobox} */ el);
3450
+ expect(el.disabled).to.be.true;
3451
+ expect(_inputNode.disabled).to.be.true;
3452
+
3453
+ // Try to open overlay by clicking input
3454
+ _inputNode.dispatchEvent(
3455
+ new Event('click', {
3456
+ bubbles: true,
3457
+ composed: true,
3458
+ }),
3459
+ );
3460
+ await el.updateComplete;
3461
+ expect(el.opened).to.be.false;
3462
+ el.disabled = false;
3463
+ await el.updateComplete;
3464
+ expect(el.disabled).to.be.false;
3465
+ expect(_inputNode.disabled).to.be.false;
3466
+
3467
+ // Try to open overlay by clicking input
3468
+ async function open() {
3469
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'ch');
3470
+ return el.updateComplete;
3471
+ }
3472
+ await open();
3473
+ expect(el.opened).to.be.true;
3474
+ });
3475
+ it('sets aria-disabled and disables focus when combobox is disabled', async () => {
3476
+ const [el] = await fruitFixture({
3477
+ disabled: true,
3478
+ });
3479
+ const { _comboboxNode, _inputNode } = getComboboxMembers(/** @type {LionCombobox} */ el);
3480
+
3481
+ // input should not be focusable
3482
+ expect(_comboboxNode.tabIndex).to.equal(-1);
3483
+ expect(_inputNode.tabIndex).to.equal(-1);
3484
+
3485
+ // aria-disabled should be set
3486
+ expect(el.getAttribute('aria-disabled')).to.equal('true');
3487
+
3488
+ // aria-disabled should be set
3489
+ expect(_inputNode.getAttribute('aria-disabled')).to.equal('true');
3490
+ await expect(el).to.be.accessible();
3491
+ });
3492
+ it('ensure focus when combobox is readonly', async () => {
3493
+ const [el] = await fruitFixture({
3494
+ readonly: true,
3495
+ });
3496
+ const { _comboboxNode, _inputNode } = getComboboxMembers(/** @type {LionCombobox} */ el);
3497
+
3498
+ // input should be focusable
3499
+ expect(_inputNode.tabIndex).to.equal(0);
3500
+
3501
+ // aria-disabled should be set
3502
+ expect(_comboboxNode.getAttribute('aria-readonly')).to.equal('true');
3503
+
3504
+ // aria-disabled should be set
3505
+ expect(_inputNode.getAttribute('aria-readonly')).to.equal('true');
3506
+ await expect(el).to.be.accessible();
3507
+ });
3508
+ });
3509
+ describe('Readonly state', () => {
3510
+ it('does not open overlay or allow input when readonly', async () => {
3511
+ const [el] = await fruitFixture({
3512
+ readonly: true,
3513
+ });
3514
+ const { _inputNode } = getComboboxMembers(/** @type {LionCombobox} */ el);
3515
+ expect(el.readOnly).to.equal(true);
3516
+ expect(el.readOnly).to.be.true;
3517
+ expect(_inputNode.readOnly).to.be.true;
3518
+
3519
+ // Try to open overlay by clicking input
3520
+ _inputNode.dispatchEvent(
3521
+ new Event('click', {
3522
+ bubbles: true,
3523
+ composed: true,
3524
+ }),
3525
+ );
3526
+ await el.updateComplete;
3527
+ expect(el.opened).to.be.false;
3528
+ });
3529
+ it('does open overlay or allow input when readonly state is removed after it was previously set', async () => {
3530
+ const [el] = await fruitFixture({
3531
+ readonly: true,
3532
+ });
3533
+ expect(el.readOnly).to.equal(true);
3534
+ const { _inputNode } = getComboboxMembers(/** @type {LionCombobox} */ el);
3535
+ expect(el.readOnly).to.be.true;
3536
+ expect(_inputNode.readOnly).to.be.true;
3537
+
3538
+ // Try to open overlay by clicking input
3539
+ _inputNode.dispatchEvent(
3540
+ new Event('click', {
3541
+ bubbles: true,
3542
+ composed: true,
3543
+ }),
3544
+ );
3545
+ await el.updateComplete;
3546
+ expect(el.opened).to.be.false;
3547
+ el.readOnly = false;
3548
+ await el.updateComplete;
3549
+ expect(el.readOnly).to.be.false;
3550
+ expect(_inputNode.readOnly).to.be.false;
3551
+
3552
+ // Try to open overlay by clicking input
3553
+ async function open() {
3554
+ await mimicUserTyping(/** @type {LionCombobox} */ el, 'ch');
3555
+ return el.updateComplete;
3556
+ }
3557
+ await open();
3558
+ expect(el.opened).to.be.true;
3559
+ });
3560
+ });
3561
+ describe('Special Characters in Search', () => {
3562
+ it('correctly escapes all regex special characters in search queries', async () => {
3563
+ const el =
3564
+ /** @type {LionCombobox} */
3565
+ await fixture(html`
3566
+ <${hostTag} label="Search" name="foo">
3567
+ <${tagOption} .choiceValue="${'test.com'}">test.com</${tagOption}>
3568
+ <${tagOption} .choiceValue="${'func(arg)'}">func(arg)</${tagOption}>
3569
+ <${tagOption} .choiceValue="${'array[0]'}">array[0]</${tagOption}>
3570
+ <${tagOption} .choiceValue="${'C++'}">C++</${tagOption}>
3571
+ <${tagOption} .choiceValue="${'a * b'}">a * b</${tagOption}>
3572
+ <${tagOption} .choiceValue="${'What?'}">What?</${tagOption}>
3573
+ <${tagOption} .choiceValue="${'$100'}">$100</${tagOption}>
3574
+ <${tagOption} .choiceValue="${'x^2'}">x^2</${tagOption}>
3575
+ <${tagOption} .choiceValue="${'a | b'}">a | b</${tagOption}>
3576
+ <${tagOption} .choiceValue="${'path\\file'}">path\\file</${tagOption}>
3577
+ <${tagOption} .choiceValue="${'{name}'}">{name}</${tagOption}>
3578
+ <${tagOption} .choiceValue="${'user+tag@example.com'}">user+tag@example.com</${tagOption}>
3579
+ <${tagOption} .choiceValue="${'(a+b)*c^2$'}">>(a+b)*c^2$</${tagOption}>
3580
+ <${tagOption} .choiceValue="${'test|value?[x]'}">test|value?[x]</${tagOption}>
3581
+ <${tagOption} .choiceValue="${'file.{txt|md}\\n'}">file.{txt|md}\\n</${tagOption}>
3582
+ <${tagOption} .choiceValue="${'testXcom'}">testXcom</${tagOption}>
3583
+ <${tagOption} .choiceValue="${'funcXargX'}">funcXargX</${tagOption}>
3584
+ <${tagOption} .choiceValue="${'arrayX0X'}">arrayX0X</${tagOption}>
3585
+ </${hostTag}>
3586
+ `);
3587
+
3588
+ // Test dot - should not act as wildcard
3589
+ await mimicUserTyping(el, 'test.com');
3590
+ await el.updateComplete;
3591
+ expect(getFilteredOptionValues(el)).to.eql(['test.com']);
3592
+
3593
+ // Test parentheses
3594
+ await mimicUserTyping(el, '(arg)');
3595
+ await el.updateComplete;
3596
+ expect(getFilteredOptionValues(el)).to.eql(['func(arg)']);
3597
+
3598
+ // Test square brackets
3599
+ await mimicUserTyping(el, '[0]');
3600
+ await el.updateComplete;
3601
+ expect(getFilteredOptionValues(el)).to.eql(['array[0]']);
3602
+
3603
+ // Test plus sign
3604
+ await mimicUserTyping(el, '+');
3605
+ await el.updateComplete;
3606
+ expect(getFilteredOptionValues(el)).to.eql(['C++', 'user+tag@example.com', '(a+b)*c^2$']);
3607
+
3608
+ // Test asterisk
3609
+ await mimicUserTyping(el, '*');
3610
+ await el.updateComplete;
3611
+ expect(getFilteredOptionValues(el)).to.eql(['a * b', '(a+b)*c^2$']);
3612
+
3613
+ // Test question mark
3614
+ await mimicUserTyping(el, '?');
3615
+ await el.updateComplete;
3616
+ expect(getFilteredOptionValues(el)).to.eql(['What?', 'test|value?[x]']);
3617
+
3618
+ // Test dollar sign
3619
+ await mimicUserTyping(el, '$');
3620
+ await el.updateComplete;
3621
+ expect(getFilteredOptionValues(el)).to.eql(['$100', '(a+b)*c^2$']);
3622
+
3623
+ // Test caret
3624
+ await mimicUserTyping(el, '^2');
3625
+ await el.updateComplete;
3626
+ expect(getFilteredOptionValues(el)).to.eql(['x^2', '(a+b)*c^2$']);
3627
+
3628
+ // Test pipe
3629
+ await mimicUserTyping(el, '|');
3630
+ await el.updateComplete;
3631
+ expect(getFilteredOptionValues(el)).to.eql(['a | b', 'test|value?[x]', 'file.{txt|md}\\n']);
3632
+
3633
+ // Test backslash
3634
+ await mimicUserTyping(el, '\\');
3635
+ await el.updateComplete;
3636
+ expect(getFilteredOptionValues(el)).to.eql(['path\\file', 'file.{txt|md}\\n']);
3637
+
3638
+ // Test curly braces
3639
+ await mimicUserTyping(el, '{name}');
3640
+ await el.updateComplete;
3641
+ expect(getFilteredOptionValues(el)).to.eql(['{name}']);
3642
+
3643
+ // Test complex combination with multiple special characters
3644
+ await mimicUserTyping(el, '(a+b)*c');
3645
+ await el.updateComplete;
3646
+ expect(getFilteredOptionValues(el)).to.eql(['(a+b)*c^2$']);
3647
+
3648
+ // Test email with plus and dot
3649
+ await mimicUserTyping(el, 'user+tag@example.com');
3650
+ await el.updateComplete;
3651
+ expect(getFilteredOptionValues(el)).to.eql(['user+tag@example.com']);
3652
+
3653
+ // Test combination with brackets, pipe and question mark
3654
+ await mimicUserTyping(el, 'value?[x]');
3655
+ await el.updateComplete;
3656
+ expect(getFilteredOptionValues(el)).to.eql(['test|value?[x]']);
3657
+
3658
+ // Test combination with dot, curly braces, pipe and backslash
3659
+ await mimicUserTyping(el, '.{txt|md}');
3660
+ await el.updateComplete;
3661
+ expect(getFilteredOptionValues(el)).to.eql(['file.{txt|md}\\n']);
3662
+ });
3663
+ });
3664
+ });
3665
+ }