@api-client/ui 0.5.38 → 0.5.40

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 (299) hide show
  1. package/build/src/md/list/internals/ListItem.d.ts +24 -15
  2. package/build/src/md/list/internals/ListItem.d.ts.map +1 -1
  3. package/build/src/md/list/internals/ListItem.js +85 -59
  4. package/build/src/md/list/internals/ListItem.js.map +1 -1
  5. package/build/src/md/list/internals/ListItem.styles.js +11 -11
  6. package/build/src/md/list/internals/ListItem.styles.js.map +1 -1
  7. package/build/src/md/select/internals/Option.d.ts +4 -14
  8. package/build/src/md/select/internals/Option.d.ts.map +1 -1
  9. package/build/src/md/select/internals/Option.js +13 -28
  10. package/build/src/md/select/internals/Option.js.map +1 -1
  11. package/build/src/md/select/internals/Select.d.ts +1 -1
  12. package/build/src/md/select/internals/Select.d.ts.map +1 -1
  13. package/build/src/md/select/internals/Select.js +15 -5
  14. package/build/src/md/select/internals/Select.js.map +1 -1
  15. package/build/tsconfig.tsbuildinfo +1 -0
  16. package/package.json +1 -1
  17. package/src/md/list/internals/ListItem.styles.ts +11 -11
  18. package/src/md/list/internals/ListItem.ts +68 -43
  19. package/src/md/select/internals/Option.ts +14 -26
  20. package/src/md/select/internals/Select.ts +15 -5
  21. package/.aiexclude +0 -3
  22. package/.cursor/rules/html-and-css-best-practices.mdc +0 -63
  23. package/.cursor/rules/lit-best-practices.mdc +0 -89
  24. package/.editorconfig +0 -29
  25. package/.github/CONTRIBUTING.md +0 -24
  26. package/.github/instructions/html-and-css-best-practices.instructions.md +0 -70
  27. package/.github/instructions/lit-best-practices.instructions.md +0 -90
  28. package/.github/release.yml +0 -14
  29. package/.github/stale.yml +0 -23
  30. package/.github/workflows/auto-release.yml +0 -182
  31. package/.github/workflows/release.yml +0 -82
  32. package/.prettierrc.js +0 -14
  33. package/.vscode/settings.json +0 -18
  34. package/RELEASE.md +0 -163
  35. package/RELEASE_SETUP.md +0 -235
  36. package/build/src/demo/DemoPage.d.ts +0 -81
  37. package/build/src/demo/DemoPage.d.ts.map +0 -1
  38. package/build/src/demo/DemoPage.js +0 -175
  39. package/build/src/demo/DemoPage.js.map +0 -1
  40. package/build/src/demo/DemoStyles.d.ts +0 -3
  41. package/build/src/demo/DemoStyles.d.ts.map +0 -1
  42. package/build/src/demo/DemoStyles.js +0 -60
  43. package/build/src/demo/DemoStyles.js.map +0 -1
  44. package/build/test/elements/navigation/Navigation.test.d.ts +0 -3
  45. package/build/test/elements/navigation/Navigation.test.d.ts.map +0 -1
  46. package/build/test/elements/navigation/Navigation.test.js +0 -113
  47. package/build/test/elements/navigation/Navigation.test.js.map +0 -1
  48. package/commitlint.config.cjs +0 -2
  49. package/demo/elements/authorization/AuthPlugin.js +0 -57
  50. package/demo/elements/authorization/AuthProxy.js +0 -215
  51. package/demo/elements/authorization/api-key.html +0 -27
  52. package/demo/elements/authorization/api-key.ts +0 -44
  53. package/demo/elements/authorization/basic.html +0 -27
  54. package/demo/elements/authorization/basic.ts +0 -43
  55. package/demo/elements/authorization/bearer.html +0 -27
  56. package/demo/elements/authorization/bearer.ts +0 -43
  57. package/demo/elements/authorization/env.js +0 -8
  58. package/demo/elements/authorization/index.html +0 -44
  59. package/demo/elements/authorization/ntlm.html +0 -27
  60. package/demo/elements/authorization/ntlm.ts +0 -43
  61. package/demo/elements/authorization/oauth-authorize.html +0 -75
  62. package/demo/elements/authorization/oauth-authorize.ts +0 -40
  63. package/demo/elements/authorization/oauth-error.html +0 -18
  64. package/demo/elements/authorization/oauth-error.ts +0 -10
  65. package/demo/elements/authorization/oauth-popup.html +0 -36
  66. package/demo/elements/authorization/oauth2.html +0 -27
  67. package/demo/elements/authorization/oauth2.ts +0 -100
  68. package/demo/elements/authorization/oidc.html +0 -27
  69. package/demo/elements/authorization/oidc.ts +0 -139
  70. package/demo/elements/authorization/private.crt +0 -31
  71. package/demo/elements/authorization/private.csr +0 -28
  72. package/demo/elements/authorization/private.key +0 -51
  73. package/demo/elements/authorization/private.pem +0 -31
  74. package/demo/elements/authorization/redirect.html +0 -20
  75. package/demo/elements/authorization/ssl-commands.sh +0 -30
  76. package/demo/elements/authorization/ssl.conf +0 -24
  77. package/demo/elements/autocomplete/index.html +0 -64
  78. package/demo/elements/autocomplete/index.ts +0 -171
  79. package/demo/elements/code-editor/CodeEditorDemo.ts +0 -173
  80. package/demo/elements/code-editor/index.html +0 -19
  81. package/demo/elements/context-menu/DemoIcons.ts +0 -21
  82. package/demo/elements/context-menu/basic.html +0 -25
  83. package/demo/elements/context-menu/basic.ts +0 -119
  84. package/demo/elements/context-menu/custom-data.html +0 -25
  85. package/demo/elements/context-menu/custom-data.ts +0 -62
  86. package/demo/elements/context-menu/demo.css +0 -28
  87. package/demo/elements/context-menu/enabled-state.html +0 -25
  88. package/demo/elements/context-menu/enabled-state.ts +0 -73
  89. package/demo/elements/context-menu/icons.html +0 -25
  90. package/demo/elements/context-menu/icons.ts +0 -64
  91. package/demo/elements/context-menu/index.html +0 -43
  92. package/demo/elements/context-menu/nested.html +0 -25
  93. package/demo/elements/context-menu/nestedt.ts +0 -152
  94. package/demo/elements/context-menu/no-execute.html +0 -25
  95. package/demo/elements/context-menu/no-execute.ts +0 -134
  96. package/demo/elements/context-menu/radio-menu.html +0 -25
  97. package/demo/elements/context-menu/radio-menu.ts +0 -83
  98. package/demo/elements/context-menu/separators.html +0 -25
  99. package/demo/elements/context-menu/separators.ts +0 -172
  100. package/demo/elements/currency/index.html +0 -91
  101. package/demo/elements/currency/index.ts +0 -352
  102. package/demo/elements/environment/environment-editor.html +0 -20
  103. package/demo/elements/environment/environment-editor.ts +0 -49
  104. package/demo/elements/environment/index.html +0 -33
  105. package/demo/elements/environment/server-editor.html +0 -20
  106. package/demo/elements/environment/server-editor.ts +0 -67
  107. package/demo/elements/environment/variables-editor.html +0 -20
  108. package/demo/elements/environment/variables-editor.ts +0 -94
  109. package/demo/elements/har/har-viewer.html +0 -20
  110. package/demo/elements/har/har-viewer.ts +0 -76
  111. package/demo/elements/har/har1.har +0 -3044
  112. package/demo/elements/har/har2.json +0 -439
  113. package/demo/elements/har/index.html +0 -26
  114. package/demo/elements/highlight/example.md +0 -27
  115. package/demo/elements/highlight/index.html +0 -31
  116. package/demo/elements/highlight/marked-highlight.html +0 -132
  117. package/demo/elements/highlight/marked-highlight.ts +0 -22
  118. package/demo/elements/highlight/prism-highlight.html +0 -62
  119. package/demo/elements/highlight/prism-highlight.ts +0 -17
  120. package/demo/elements/http/body-editor.html +0 -17
  121. package/demo/elements/http/body-editor.ts +0 -115
  122. package/demo/elements/http/headers.html +0 -17
  123. package/demo/elements/http/headers.ts +0 -59
  124. package/demo/elements/http/http-assertions.html +0 -20
  125. package/demo/elements/http/http-assertions.ts +0 -89
  126. package/demo/elements/http/http-flows.html +0 -23
  127. package/demo/elements/http/http-flows.ts +0 -89
  128. package/demo/elements/http/index.html +0 -45
  129. package/demo/elements/http/request-editor.html +0 -26
  130. package/demo/elements/http/request-editor.ts +0 -197
  131. package/demo/elements/http/request-log.html +0 -16
  132. package/demo/elements/http/request-log.ts +0 -136
  133. package/demo/elements/http/url-editing.html +0 -17
  134. package/demo/elements/http/url-editing.ts +0 -112
  135. package/demo/elements/icons/index.html +0 -81
  136. package/demo/elements/icons/index.ts +0 -52
  137. package/demo/elements/index.html +0 -72
  138. package/demo/elements/mention-textarea/index.html +0 -19
  139. package/demo/elements/mention-textarea/index.ts +0 -205
  140. package/demo/elements/navigation/navigation-item.html +0 -49
  141. package/demo/elements/navigation/navigation-item.ts +0 -131
  142. package/demo/elements/navigation/navigation.html +0 -20
  143. package/demo/elements/navigation/navigation.ts +0 -45
  144. package/demo/elements/project/index.html +0 -29
  145. package/demo/elements/project/project-run-report.html +0 -20
  146. package/demo/elements/project/project-run-report.ts +0 -132
  147. package/demo/elements/project/request-editor.html +0 -23
  148. package/demo/elements/project/request-editor.ts +0 -232
  149. package/demo/elements/user/user-avatar.html +0 -17
  150. package/demo/elements/user/user-avatar.ts +0 -60
  151. package/demo/env.js +0 -4
  152. package/demo/index.html +0 -34
  153. package/demo/layout/index.html +0 -94
  154. package/demo/layout/index.ts +0 -190
  155. package/demo/md/DemoStyles.ts +0 -61
  156. package/demo/md/UiDemoPage.ts +0 -6
  157. package/demo/md/buttons/button.html +0 -121
  158. package/demo/md/buttons/button.ts +0 -246
  159. package/demo/md/buttons/group.html +0 -36
  160. package/demo/md/buttons/group.ts +0 -171
  161. package/demo/md/checkbox/index.html +0 -39
  162. package/demo/md/checkbox/index.ts +0 -220
  163. package/demo/md/chip/chip.html +0 -70
  164. package/demo/md/chip/chip.ts +0 -219
  165. package/demo/md/chip/pawel6c9a.jpg +0 -0
  166. package/demo/md/collapse/CustomDetail.ts +0 -89
  167. package/demo/md/collapse/collapse.html +0 -21
  168. package/demo/md/collapse/collapse.ts +0 -78
  169. package/demo/md/date-picker/date-picker.ts +0 -336
  170. package/demo/md/date-picker/index.html +0 -171
  171. package/demo/md/dialog/confirm-dialog.html +0 -49
  172. package/demo/md/dialog/confirm-dialog.ts +0 -121
  173. package/demo/md/dialog/dialog.html +0 -25
  174. package/demo/md/dialog/dialog.ts +0 -468
  175. package/demo/md/dropdown-list/index.html +0 -31
  176. package/demo/md/dropdown-list/index.ts +0 -158
  177. package/demo/md/icon-button/index.html +0 -122
  178. package/demo/md/icon-button/index.ts +0 -132
  179. package/demo/md/index.html +0 -73
  180. package/demo/md/inputs/input.html +0 -73
  181. package/demo/md/inputs/input.ts +0 -278
  182. package/demo/md/inputs/radio.html +0 -39
  183. package/demo/md/inputs/radio.ts +0 -156
  184. package/demo/md/inputs/switch.html +0 -45
  185. package/demo/md/inputs/switch.ts +0 -144
  186. package/demo/md/list/list.html +0 -65
  187. package/demo/md/list/list.ts +0 -204
  188. package/demo/md/listbox/listbox.html +0 -31
  189. package/demo/md/listbox/listbox.ts +0 -27
  190. package/demo/md/menu/index.html +0 -19
  191. package/demo/md/menu/index.ts +0 -514
  192. package/demo/md/notification/snack.html +0 -21
  193. package/demo/md/notification/snack.ts +0 -70
  194. package/demo/md/progress/progress.html +0 -46
  195. package/demo/md/progress/progress.ts +0 -161
  196. package/demo/md/segmented-button/index.html +0 -21
  197. package/demo/md/segmented-button/index.ts +0 -55
  198. package/demo/md/select/index.html +0 -16
  199. package/demo/md/select/index.ts +0 -207
  200. package/demo/md/tabs/tabs.html +0 -40
  201. package/demo/md/tabs/tabs.ts +0 -214
  202. package/demo/oauth-popup.html +0 -36
  203. package/demo/page.css +0 -8
  204. package/demo/resources/calendar-month.png +0 -0
  205. package/demo/resources/favorite.png +0 -0
  206. package/demo/resources/fingerprint.png +0 -0
  207. package/demo/resources/home-work.png +0 -0
  208. package/demo/resources/mood.png +0 -0
  209. package/demo/resources/print.png +0 -0
  210. package/demo/resources/stars.png +0 -0
  211. package/demo/resources/theaters.png +0 -0
  212. package/demo/tsconfig.json +0 -4
  213. package/eslint.config.js +0 -97
  214. package/scripts/copy-assets.js +0 -21
  215. package/scripts/release.js +0 -66
  216. package/src/demo/DemoPage.ts +0 -169
  217. package/src/demo/DemoStyles.ts +0 -60
  218. package/test/README.md +0 -375
  219. package/test/contextual-menu/ContextMenu.test.ts +0 -760
  220. package/test/contextual-menu/ContextMenuElement.test.ts +0 -569
  221. package/test/core/activity.spec.ts +0 -413
  222. package/test/core/activity_manager.spec.ts +0 -544
  223. package/test/core/application.spec.ts +0 -218
  224. package/test/core/fragment.spec.ts +0 -565
  225. package/test/core/fragment_manager.spec.ts +0 -404
  226. package/test/core/live_data.spec.ts +0 -558
  227. package/test/core/renderer.spec.ts +0 -113
  228. package/test/dom-assertions.test.ts +0 -182
  229. package/test/elements/MonacoSetup.ts +0 -65
  230. package/test/elements/authorization/basic-method.test.ts +0 -177
  231. package/test/elements/authorization/bearer-method.test.ts +0 -143
  232. package/test/elements/authorization/ntlm-method.test.ts +0 -219
  233. package/test/elements/authorization/oauth2-client-credentials-method.test.ts +0 -334
  234. package/test/elements/authorization/oauth2-code-method.test.ts +0 -320
  235. package/test/elements/authorization/oauth2-custom-grant-method.test.ts +0 -255
  236. package/test/elements/authorization/oauth2-device-code-method.test.ts +0 -371
  237. package/test/elements/authorization/oauth2-implicit-method.test.ts +0 -407
  238. package/test/elements/authorization/oauth2-jwt-method.test.ts +0 -217
  239. package/test/elements/authorization/oauth2-password-method.test.ts +0 -275
  240. package/test/elements/authorization/openid-method.test.ts +0 -591
  241. package/test/elements/autocomplete/autocomplete-input.spec.ts +0 -646
  242. package/test/elements/code-editor/code-editor.accessibility.test.ts +0 -298
  243. package/test/elements/code-editor/code-editor.test.ts +0 -574
  244. package/test/elements/currency/CurrencyPicker.accessibility.test.ts +0 -328
  245. package/test/elements/currency/CurrencyPicker.core.test.ts +0 -318
  246. package/test/elements/currency/CurrencyPicker.integration.test.ts +0 -482
  247. package/test/elements/currency/CurrencyPicker.test.ts +0 -486
  248. package/test/elements/data-table/DataTable.browser.test.ts +0 -649
  249. package/test/elements/har/HarUtils.test.ts +0 -45
  250. package/test/elements/har/HarViewerElement.test.ts +0 -687
  251. package/test/elements/har/test-data/har1.har +0 -3044
  252. package/test/elements/highlight/MarkedHighlightElement.test.ts +0 -452
  253. package/test/elements/highlight/PrismHighlightElement.test.ts +0 -79
  254. package/test/elements/highlight/PrismHighlighter.test.ts +0 -94
  255. package/test/elements/highlight/remoteSanitization.md +0 -1
  256. package/test/elements/highlight/test.md +0 -3
  257. package/test/elements/highlight/test1.md +0 -3
  258. package/test/elements/highlight/test2.md +0 -1
  259. package/test/elements/http/BodyFormdataEditorElement.test.ts +0 -482
  260. package/test/elements/http/BodyMultipartEditorElement.test.ts +0 -658
  261. package/test/elements/http/BodyRawEditorElement.test.ts +0 -90
  262. package/test/elements/http/CertificateAdd.test.ts +0 -457
  263. package/test/elements/http/HttpAssertions.test.ts +0 -994
  264. package/test/elements/http/HttpFlows.test.ts +0 -502
  265. package/test/elements/http/UrlEncodeUtils.test.ts +0 -202
  266. package/test/elements/layout/SplitItem.test.ts +0 -440
  267. package/test/elements/layout/SplitLayoutManager.test.ts +0 -1501
  268. package/test/elements/layout/SplitPanel.test.ts +0 -1109
  269. package/test/elements/mention-textarea/MentionTextArea.basic.test.ts +0 -114
  270. package/test/elements/mention-textarea/MentionTextArea.test.ts +0 -613
  271. package/test/elements/navigation/Navigation.test.ts +0 -120
  272. package/test/env.ts +0 -15
  273. package/test/events/EventTypes.test.ts +0 -363
  274. package/test/events/EventsTestHelpers.ts +0 -16
  275. package/test/helpers/TestUtils.ts +0 -243
  276. package/test/helpers/UiMock.ts +0 -185
  277. package/test/lib/Dom.test.ts +0 -231
  278. package/test/md/button/UiButton.test.ts +0 -347
  279. package/test/md/button/UiIconButton.test.ts +0 -155
  280. package/test/md/chip/UiChip.test.ts +0 -219
  281. package/test/md/collapse/UiCollapse.test.ts +0 -250
  282. package/test/md/collapse/flex-layout.test.ts +0 -105
  283. package/test/md/date-time/DateTime.test.ts +0 -348
  284. package/test/md/dialog/UiConfirmDialog.test.ts +0 -131
  285. package/test/md/dialog/UiDialog.test.ts +0 -759
  286. package/test/md/menu/Menu.test.ts +0 -855
  287. package/test/md/menu/MenuIntegration.test.ts +0 -426
  288. package/test/md/menu/MenuItem.test.ts +0 -652
  289. package/test/md/menu/SubMenu.test.ts +0 -410
  290. package/test/md/progress/UiCircularProgressElement.test.ts +0 -481
  291. package/test/md/progress/UiProgressElement.test.ts +0 -117
  292. package/test/md/progress/UiRangeElement.test.ts +0 -156
  293. package/test/md/select/Select.test.ts +0 -925
  294. package/test/plugins/takeScreenshotPlugin.js +0 -35
  295. package/test/setup.test.ts +0 -217
  296. package/test/setup.ts +0 -117
  297. package/test/tsconfig.json +0 -7
  298. package/web-dev-server.config.js +0 -21
  299. package/web-test-runner.config.js +0 -90
@@ -1,646 +0,0 @@
1
- import { fixture, assert, html, oneEvent, nextFrame, aTimeout } from '@open-wc/testing'
2
- import sinon from 'sinon'
3
-
4
- import { AutocompleteInput } from '../../../src/elements/autocomplete/autocomplete-input.js'
5
- import '../../../src/elements/autocomplete/autocomplete-input.js' // Registers the element
6
-
7
- import '../../../src/md/list/ui-list-item.js'
8
- import '../../../src/md/listbox/ui-listbox.js'
9
-
10
- import type MdListItem from '../../../src/md/list/internals/ListItem.js'
11
- import type MdListbox from '../../../src/md/listbox/internals/Listbox.js'
12
-
13
- describe('AutocompleteInput', () => {
14
- async function basicFixture(): Promise<AutocompleteInput> {
15
- return fixture(html`
16
- <autocomplete-input>
17
- <input id="test-input" slot="input" type="text" placeholder="Search..." />
18
- <ui-listbox slot="suggestions" aria-label="Suggestions">
19
- <ui-list-item data-value="apple">Apple</ui-list-item>
20
- <ui-list-item data-value="banana">Banana</ui-list-item>
21
- <ui-list-item data-value="cherry" data-index="value customField" data-custom-field="Sweet Cherry"
22
- >Cherry</ui-list-item
23
- >
24
- </ui-listbox>
25
- </autocomplete-input>
26
- `)
27
- }
28
-
29
- async function noSuggestionsFixture(): Promise<AutocompleteInput> {
30
- return fixture(html`
31
- <autocomplete-input>
32
- <input slot="input" type="text" placeholder="Search..." />
33
- <ui-listbox slot="suggestions" aria-label="Suggestions"></ui-listbox>
34
- </autocomplete-input>
35
- `)
36
- }
37
-
38
- async function noListboxFixture(): Promise<AutocompleteInput> {
39
- return fixture(html`
40
- <autocomplete-input>
41
- <input slot="input" type="text" placeholder="Search..." />
42
- </autocomplete-input>
43
- `)
44
- }
45
-
46
- function getSlottedInput(el: AutocompleteInput): HTMLInputElement | null {
47
- return el.querySelector('[slot="input"]') as HTMLInputElement | null
48
- }
49
-
50
- function getSlottedSuggestions(el: AutocompleteInput): MdListbox | null {
51
- return el.querySelector('[slot="suggestions"]') as MdListbox | null
52
- }
53
-
54
- function getSuggestionItems(suggestionsEl: MdListbox): MdListItem[] {
55
- // Assuming MdListbox.items returns the slotted items that are MdListItem
56
- return (suggestionsEl.items as MdListItem[]).filter(
57
- (item) => item instanceof HTMLElement && item.matches('ui-list-item')
58
- )
59
- }
60
-
61
- describe('Initialization', () => {
62
- it('correctly identifies slotted input and suggestions', async () => {
63
- const el = await basicFixture()
64
- await el.updateComplete // Ensure firstUpdated has run
65
-
66
- const input = getSlottedInput(el)
67
- const suggestions = getSlottedSuggestions(el)
68
-
69
- assert.ok(input)
70
- assert.equal(input?.id, 'test-input')
71
- assert.ok(suggestions)
72
- assert.equal(suggestions?.popover, 'manual')
73
- })
74
-
75
- it('generates an ID for the input if not provided and sets up anchor names', async () => {
76
- const el = (await fixture(html`
77
- <autocomplete-input>
78
- <input slot="input" type="text" />
79
- <ui-listbox slot="suggestions"></ui-listbox>
80
- </autocomplete-input>
81
- `)) as AutocompleteInput
82
- await el.updateComplete
83
- await nextFrame() // Allow for internal state updates
84
-
85
- const input = getSlottedInput(el)!
86
- const suggestions = getSlottedSuggestions(el)!
87
-
88
- assert.match(input.id, /^autocomplete-input-/)
89
- // @ts-expect-error _inputId is a protected member
90
- const internalInputId = el.inputId
91
- assert.equal(input.style.getPropertyValue('anchor-name'), `--${internalInputId}`, 'input has anchor name')
92
- const anchorValue = getComputedStyle(suggestions).getPropertyValue('position-anchor')
93
- assert.equal(anchorValue, `--${internalInputId}`, 'suggestions element has position-anchor')
94
- })
95
-
96
- it('popover is initially closed', async () => {
97
- const el = await basicFixture()
98
- await el.updateComplete
99
- const suggestions = getSlottedSuggestions(el)
100
- assert.isFalse(suggestions?.matches(':popover-open'))
101
- assert.isFalse(el.opened)
102
- })
103
- })
104
-
105
- describe('Popover behavior', () => {
106
- it('opens on input focus with suggestions', async () => {
107
- const el = await basicFixture()
108
- await el.updateComplete
109
- const input = getSlottedInput(el)!
110
- const suggestions = getSlottedSuggestions(el)!
111
-
112
- input.focus()
113
- await nextFrame() // Allow focus event to propagate and popover to open
114
-
115
- assert.isTrue(suggestions.matches(':popover-open'))
116
- assert.isTrue(el.opened)
117
- })
118
-
119
- it('does not open on input focus without suggestions', async () => {
120
- const el = await noSuggestionsFixture()
121
- await el.updateComplete
122
- const input = getSlottedInput(el)!
123
- const suggestions = getSlottedSuggestions(el)!
124
-
125
- input.focus()
126
- await nextFrame()
127
-
128
- assert.isFalse(suggestions.matches(':popover-open'))
129
- })
130
-
131
- it('does not open on input focus if suggestions element is missing', async () => {
132
- const el = await noListboxFixture()
133
- await el.updateComplete
134
- const input = getSlottedInput(el)!
135
-
136
- input.focus()
137
- await nextFrame()
138
- // No suggestions ref, so opened should be false
139
- assert.isFalse(el.opened)
140
- })
141
-
142
- it('closes on input blur', async () => {
143
- const el = await basicFixture()
144
- await el.updateComplete
145
- const input = getSlottedInput(el)!
146
- const suggestions = getSlottedSuggestions(el)!
147
-
148
- input.focus()
149
- await nextFrame()
150
- assert.isTrue(suggestions.matches(':popover-open'))
151
-
152
- input.blur()
153
- // Blur handler uses requestAnimationFrame
154
- await aTimeout(0) // Wait for rAF
155
- await nextFrame() // Wait for potential re-render
156
-
157
- assert.isFalse(suggestions.matches(':popover-open'))
158
- assert.isFalse(el.opened)
159
- })
160
-
161
- it('closes on Escape key', async () => {
162
- const el = await basicFixture()
163
- await el.updateComplete
164
- const input = getSlottedInput(el)!
165
- const suggestions = getSlottedSuggestions(el)!
166
-
167
- input.focus()
168
- await nextFrame()
169
- assert.isTrue(suggestions.matches(':popover-open'))
170
-
171
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, composed: true }))
172
- await nextFrame()
173
-
174
- assert.isFalse(suggestions.matches(':popover-open'))
175
- })
176
- })
177
-
178
- describe('Filtering', () => {
179
- let el: AutocompleteInput
180
- let input: HTMLInputElement
181
- let suggestionsBox: MdListbox
182
- let items: MdListItem[]
183
-
184
- beforeEach(async () => {
185
- el = await basicFixture()
186
- await el.updateComplete
187
- input = getSlottedInput(el)!
188
- suggestionsBox = getSlottedSuggestions(el)!
189
- items = getSuggestionItems(suggestionsBox)
190
- input.focus() // Open popover for filtering tests
191
- await nextFrame()
192
- })
193
-
194
- it('shows all items for empty query', () => {
195
- input.value = ''
196
- input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
197
- items.forEach((item) => assert.isFalse(item.hidden))
198
- })
199
-
200
- it('filters items based on data-value', async () => {
201
- input.value = 'app'
202
- input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
203
- await nextFrame()
204
-
205
- assert.isFalse(items[0].hidden) // Apple
206
- assert.isTrue(items[1].hidden) // Banana
207
- assert.isTrue(items[2].hidden) // Cherry
208
- })
209
-
210
- it('filters items based on textContent if data-value is not present or does not match', async () => {
211
- // Modify first item to not have data-value for this test
212
- items[0].removeAttribute('data-value')
213
- items[0].textContent = 'Pineapple'
214
- await nextFrame()
215
-
216
- input.value = 'pine'
217
- input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
218
- await nextFrame()
219
-
220
- assert.isFalse(items[0].hidden) // Pineapple (via textContent)
221
- assert.isTrue(items[1].hidden) // Banana
222
- assert.isTrue(items[2].hidden) // Cherry
223
- })
224
-
225
- it('filters items based on data-index fields', async () => {
226
- input.value = 'Sweet' // Matches data-custom-field="Sweet Cherry" via data-index on items[2]
227
- input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
228
- await nextFrame()
229
-
230
- assert.isTrue(items[0].hidden) // Apple
231
- assert.isTrue(items[1].hidden) // Banana
232
- assert.isFalse(items[2].hidden) // Cherry
233
- })
234
-
235
- it('is case-insensitive', async () => {
236
- input.value = 'BaNaNa'
237
- input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
238
- await nextFrame()
239
-
240
- assert.isTrue(items[0].hidden) // Apple
241
- assert.isFalse(items[1].hidden) // Banana
242
- assert.isTrue(items[2].hidden) // Cherry
243
- })
244
-
245
- it('closes popover if no items match', async () => {
246
- input.value = 'nonexistent'
247
- input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
248
- await nextFrame()
249
- await aTimeout(0) // filterSuggestions might call closeSuggestions, which might be async
250
-
251
- items.forEach((item) => assert.isTrue(item.hidden))
252
- assert.isFalse(suggestionsBox.matches(':popover-open'))
253
- })
254
- })
255
-
256
- describe('Keyboard navigation', () => {
257
- let el: AutocompleteInput
258
- let input: HTMLInputElement
259
- let suggestionsBox: MdListbox
260
- let notifySelectSpy: sinon.SinonSpy
261
-
262
- beforeEach(async () => {
263
- el = await basicFixture()
264
- await el.updateComplete
265
- input = getSlottedInput(el)!
266
- suggestionsBox = getSlottedSuggestions(el)!
267
- // For keyboard nav, listbox needs to handle highlight internally.
268
- // We spy on its methods.
269
- sinon.spy(suggestionsBox, 'highlightNext')
270
- sinon.spy(suggestionsBox, 'highlightPrevious')
271
- notifySelectSpy = sinon.spy(suggestionsBox, 'notifySelect')
272
- })
273
-
274
- afterEach(() => {
275
- sinon.restore()
276
- })
277
-
278
- it('ArrowDown highlights next item and opens popover if closed', async () => {
279
- assert.isFalse(suggestionsBox.matches(':popover-open'))
280
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true, composed: true }))
281
- // handleKeydown uses rAF if popover was closed
282
- await aTimeout(0) // for rAF
283
- await nextFrame() // for popover to open and highlight
284
-
285
- assert.isTrue(suggestionsBox.matches(':popover-open'))
286
- assert.isTrue((suggestionsBox.highlightNext as sinon.SinonSpy).calledOnce)
287
- })
288
-
289
- it('ArrowUp highlights previous item', async () => {
290
- input.focus() // Open popover
291
- await nextFrame()
292
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true, composed: true }))
293
- await nextFrame()
294
- assert.isTrue((suggestionsBox.highlightPrevious as sinon.SinonSpy).calledOnce)
295
- })
296
-
297
- it('Enter selects highlighted item and dispatches event', async () => {
298
- input.focus() // Open popover
299
- await nextFrame()
300
-
301
- // Simulate item being highlighted by listbox (e.g., first item)
302
- const items = getSuggestionItems(suggestionsBox)
303
- suggestionsBox.highlightListItem = items[0]
304
- await nextFrame()
305
-
306
- const eventPromise = oneEvent(el, 'autocomplete')
307
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, composed: true }))
308
-
309
- const event = await eventPromise
310
-
311
- assert.isTrue((suggestionsBox.notifySelect as sinon.SinonSpy).calledWith(items[0]))
312
- assert.equal(event.detail.item, items[0])
313
- assert.isFalse(suggestionsBox.matches(':popover-open')) // Popover should close
314
- })
315
-
316
- it('Enter does nothing if popover closed or no item highlighted', async () => {
317
- assert.isFalse(suggestionsBox.matches(':popover-open'))
318
- suggestionsBox.highlightListItem = null // Ensure no item is highlighted
319
-
320
- const spy = sinon.spy()
321
- el.addEventListener('autocomplete', spy)
322
-
323
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, composed: true }))
324
- await nextFrame()
325
-
326
- assert.isFalse(spy.called)
327
- assert.isFalse(notifySelectSpy.called)
328
- })
329
- })
330
-
331
- describe('Suggestion selection (click)', () => {
332
- it('selects item on click and dispatches event', async () => {
333
- const el = await basicFixture()
334
- await el.updateComplete
335
- const input = getSlottedInput(el)!
336
- const suggestionsBox = getSlottedSuggestions(el)!
337
- const items = getSuggestionItems(suggestionsBox)
338
-
339
- input.focus() // Open popover
340
- await nextFrame()
341
-
342
- const eventPromise = oneEvent(el, 'autocomplete')
343
- // Simulate click on the first item.
344
- // The `ui-listbox` should dispatch 'select' upon item click.
345
- // We simulate the 'select' event from the listbox.
346
- suggestionsBox.dispatchEvent(
347
- new CustomEvent('select', {
348
- detail: { item: items[1] }, // Select 'Banana'
349
- bubbles: true, // Ensure it bubbles to autocomplete-input if needed by its listener
350
- composed: true,
351
- })
352
- )
353
-
354
- const event = await eventPromise
355
- assert.equal(event.detail.item, items[1])
356
- assert.isFalse(suggestionsBox.matches(':popover-open')) // Popover should close
357
- })
358
- })
359
-
360
- describe('Dynamic content', () => {
361
- it('handles dynamically added input', async () => {
362
- const el = await fixture<AutocompleteInput>(html`<autocomplete-input></autocomplete-input>`)
363
- await el.updateComplete
364
-
365
- const inputEl = document.createElement('input')
366
- inputEl.slot = 'input'
367
- inputEl.type = 'text'
368
- el.appendChild(inputEl)
369
- await nextFrame() // MutationObserver is async
370
- await el.updateComplete // Ensure component processes the new input
371
-
372
- const slottedInput = getSlottedInput(el)
373
- assert.equal(slottedInput, inputEl)
374
- assert.match(inputEl.id, /^autocomplete-input-/)
375
- })
376
-
377
- it('handles dynamically added suggestions', async () => {
378
- const el = await fixture<AutocompleteInput>(
379
- html`<autocomplete-input><input slot="input" id="dyn-input" /></autocomplete-input>`
380
- )
381
- await el.updateComplete
382
- const input = getSlottedInput(el)!
383
-
384
- const suggestionsEl = document.createElement('ui-listbox') as MdListbox
385
- suggestionsEl.slot = 'suggestions'
386
- el.appendChild(suggestionsEl)
387
- await nextFrame() // MutationObserver
388
- await el.updateComplete
389
-
390
- const slottedSuggestions = getSlottedSuggestions(el)
391
- assert.equal(slottedSuggestions, suggestionsEl)
392
- assert.equal(suggestionsEl.popover, 'manual')
393
- const anchorValue = getComputedStyle(suggestionsEl).getPropertyValue('position-anchor')
394
- assert.equal(anchorValue, `--${input.id}`)
395
- })
396
-
397
- it('re-filters when suggestion items change (via itemschange event)', async () => {
398
- const el = await basicFixture()
399
- await el.updateComplete
400
- const input = getSlottedInput(el)!
401
- const suggestionsBox = getSlottedSuggestions(el)!
402
- let items = getSuggestionItems(suggestionsBox)
403
-
404
- input.focus()
405
- input.value = 'ban' // Should show "Banana"
406
- input.dispatchEvent(new Event('input', { bubbles: true, composed: true }))
407
- await nextFrame()
408
- assert.isTrue(items[0].hidden) // Apple
409
- assert.isFalse(items[1].hidden) // Banana
410
-
411
- // Add a new item that matches "ban"
412
- const newItem = document.createElement('ui-list-item') as MdListItem
413
- newItem.dataset.value = 'bandana'
414
- newItem.textContent = 'Bandana'
415
- suggestionsBox.appendChild(newItem)
416
-
417
- // Simulate 'itemschange' event from listbox
418
- suggestionsBox.dispatchEvent(new CustomEvent('itemschange'))
419
- await nextFrame() // Allow handler to run
420
-
421
- items = getSuggestionItems(suggestionsBox) // Re-fetch items
422
- assert.isFalse(items.find((i) => i.dataset.value === 'bandana')?.hidden)
423
- assert.isFalse(items.find((i) => i.dataset.value === 'banana')?.hidden)
424
- assert.isTrue(items.find((i) => i.dataset.value === 'apple')?.hidden)
425
- })
426
- })
427
-
428
- describe('`opened` property', () => {
429
- it('reflects popover state', async () => {
430
- const el = await basicFixture()
431
- await el.updateComplete
432
- const input = getSlottedInput(el)!
433
- const suggestions = getSlottedSuggestions(el)!
434
-
435
- assert.isFalse(el.opened)
436
-
437
- input.focus()
438
- await nextFrame()
439
- assert.isTrue(el.opened)
440
- assert.isTrue(suggestions.matches(':popover-open'))
441
-
442
- // @ts-expect-error protected method
443
- el.closeSuggestions()
444
- await nextFrame()
445
- assert.isFalse(el.opened)
446
- assert.isFalse(suggestions.matches(':popover-open'))
447
- })
448
- })
449
-
450
- describe('Popover Positioning', () => {
451
- let el: AutocompleteInput
452
- let input: HTMLInputElement
453
- let suggestionsBox: MdListbox
454
- let getBoundingClientRectStub: sinon.SinonStub
455
-
456
- // Define a standard popover height for consistent threshold calculation
457
- const popoverVisibleHeight = 200 // px, used for scrollHeight
458
-
459
- beforeEach(async () => {
460
- el = await basicFixture() // This fixture has suggestions
461
- await el.updateComplete // Ensures inputRef is set up
462
- input = getSlottedInput(el)!
463
- suggestionsBox = getSlottedSuggestions(el)!
464
-
465
- // Set a known height for the suggestions box to make its scrollHeight predictable
466
- suggestionsBox.style.height = `${popoverVisibleHeight}px`
467
- suggestionsBox.style.display = 'block' // Ensure it's not display:none from other CSS
468
- suggestionsBox.style.overflow = 'auto'
469
- // Ensure it has some content to have a scrollHeight if not stubbed
470
- if (getSuggestionItems(suggestionsBox).length === 0) {
471
- const item = document.createElement('ui-list-item')
472
- item.textContent = 'Dummy Item'
473
- suggestionsBox.appendChild(item)
474
- }
475
- await nextFrame() // Allow DOM to update for scrollHeight calculation
476
-
477
- // @ts-expect-error accessing protected member `inputRef`
478
- const anchorEl = el.inputRef as HTMLElement // Default anchor
479
- if (!anchorEl) {
480
- throw new Error('el.inputRef was not initialized')
481
- }
482
- getBoundingClientRectStub = sinon.stub(anchorEl, 'getBoundingClientRect')
483
- })
484
-
485
- afterEach(() => {
486
- sinon.restore() // Restores window.innerHeight and the getBoundingClientRectStub
487
- })
488
-
489
- function setAnchorPosition(
490
- anchorRect: Partial<DOMRect & { height: number; width: number }>,
491
- viewportHeight: number
492
- ) {
493
- getBoundingClientRectStub.returns(anchorRect as DOMRect)
494
- // Stub window.innerHeight for this call, will be restored by sinon.restore() in afterEach
495
- sinon.stub(window, 'innerHeight').get(() => viewportHeight)
496
- }
497
-
498
- it('positions at bottom when ample space below', async () => {
499
- // Anchor near top, viewport large. popoverThresholdHeight will be popoverVisibleHeight (200)
500
- setAnchorPosition({ top: 50, bottom: 80, height: 30, x: 0, y: 50, width: 100, left: 0, right: 100 }, 800)
501
- // spaceBelow = 800 - 80 = 720. 720 > 200.
502
-
503
- input.focus() // Triggers openSuggestions
504
- await el.updateComplete // For positionArea state change and re-render
505
- await nextFrame() // For DOM to reflect changes
506
-
507
- assert.equal(el.positionArea, 'bottom')
508
- })
509
-
510
- it('positions at top when insufficient space below but sufficient space above', async () => {
511
- // popoverThresholdHeight = 200
512
- // Anchor bottom: 750. Viewport: 800. spaceBelow = 50. (50 < 200)
513
- // Anchor top: 500. spaceAbove = 500. (500 > 50) && (500 > 200) -> true
514
- setAnchorPosition({ top: 500, bottom: 750, height: 250, x: 0, y: 500, width: 100, left: 0, right: 100 }, 800)
515
-
516
- input.focus()
517
- await el.updateComplete
518
- await nextFrame()
519
-
520
- assert.equal(el.positionArea, 'top')
521
- })
522
-
523
- it('positions at top when insufficient space below and more space above', async () => {
524
- // popoverThresholdHeight = 200
525
- // spaceBelow = 50 (anchor.bottom = viewportHeight - 50)
526
- // spaceAbove = 100 (anchor.top = 100)
527
- // viewportHeight = anchor.top + anchor.height + spaceBelow = 100 + 30 + 50 = 180
528
- // Test case: spaceBelow=50, spaceAbove=100. popoverThresholdHeight=200.
529
- // (50 < 200) is true.
530
- // (100 > 50) is true.
531
- // (100 > 200) is false. -> first 'top' condition fails.
532
- // Second 'top' condition: (spaceBelow < popoverThresholdHeight && spaceAbove > spaceBelow) -> true.
533
- setAnchorPosition({ top: 100, bottom: 130, height: 30, x: 0, y: 100, width: 100, left: 0, right: 100 }, 180)
534
-
535
- input.focus()
536
- await el.updateComplete
537
- await nextFrame()
538
-
539
- assert.equal(el.positionArea, 'top')
540
- })
541
-
542
- it('positions at bottom when insufficient space below and also insufficient (or less) space above', async () => {
543
- // popoverThresholdHeight = 200
544
- // spaceBelow = 50 (anchor.bottom = viewportHeight - 50)
545
- // spaceAbove = 40 (anchor.top = 40)
546
- // viewportHeight = anchor.top + anchor.height + spaceBelow = 40 + 30 + 50 = 120
547
- // Test case: spaceBelow=50, spaceAbove=40.
548
- // (50 < 200) is true.
549
- // (40 > 50) is false. -> both 'top' conditions fail. Defaults to 'bottom'.
550
- setAnchorPosition({ top: 40, bottom: 70, height: 30, x: 0, y: 40, width: 100, left: 0, right: 100 }, 120)
551
-
552
- input.focus()
553
- await el.updateComplete
554
- await nextFrame()
555
-
556
- assert.equal(el.positionArea, 'bottom')
557
- })
558
-
559
- it('uses fallback threshold of 150px if popover scrollHeight is 0', async () => {
560
- // Stub scrollHeight to be 0 for this test.
561
- const scrollHeightStub = sinon.stub(suggestionsBox, 'scrollHeight').get(() => 0)
562
-
563
- // popoverThresholdHeight will be 150 (the fallback).
564
- // Scenario: spaceBelow = 100, spaceAbove = 200.
565
- // (100 < 150) is true.
566
- // (200 > 100) is true. (200 > 150) is true. -> Should be 'top'.
567
- setAnchorPosition({ top: 500, bottom: 700, height: 200, x: 0, y: 500, width: 100, left: 0, right: 100 }, 800)
568
- // spaceBelow = 800 - 700 = 100
569
- // spaceAbove = 500 (mistake in manual calc above, should be 500)
570
- // Corrected: spaceBelow = 100. spaceAbove = 500. Threshold = 150.
571
- // (100 < 150) -> true
572
- // (500 > 100) -> true. (500 > 150) -> true. Result: 'top'.
573
-
574
- input.focus()
575
- await el.updateComplete
576
- await nextFrame()
577
-
578
- assert.equal(el.positionArea, 'top')
579
- scrollHeightStub.restore()
580
- })
581
-
582
- it('uses slotted anchor for positioning if provided', async () => {
583
- // Need to create a new fixture for this specific setup
584
- el = await fixture(html`
585
- <autocomplete-input>
586
- <div slot="anchor" id="custom-anchor" style="height: 20px; width: 100px; border: 1px solid red;"></div>
587
- <input slot="input" type="text" />
588
- <ui-listbox slot="suggestions" style="height: ${popoverVisibleHeight}px; overflow: auto;">
589
- <ui-list-item>Item 1</ui-list-item>
590
- </ui-listbox>
591
- </autocomplete-input>
592
- `)
593
- await el.updateComplete
594
- input = getSlottedInput(el)! // Still need to focus input
595
- const customAnchor = el.querySelector('#custom-anchor') as HTMLElement
596
-
597
- getBoundingClientRectStub.restore() // remove stub from default input anchor
598
- getBoundingClientRectStub = sinon.stub(customAnchor, 'getBoundingClientRect')
599
-
600
- // Scenario: custom anchor is near bottom, should open top. popoverThresholdHeight = 200.
601
- setAnchorPosition({ top: 720, bottom: 750, height: 30, x: 0, y: 720, width: 100, left: 0, right: 100 }, 800)
602
-
603
- input.focus() // Triggers openSuggestions
604
- await el.updateComplete
605
- await nextFrame()
606
-
607
- assert.equal(el.positionArea, 'top')
608
- assert.isTrue(getBoundingClientRectStub.called)
609
- })
610
- })
611
-
612
- describe('Accessibility', () => {
613
- it('is accessible when initially rendered', async () => {
614
- const el = await basicFixture()
615
- await el.updateComplete
616
- await assert.isAccessible(el)
617
- })
618
-
619
- it('is accessible when popover is open', async () => {
620
- const el = await basicFixture()
621
- await el.updateComplete
622
- const input = getSlottedInput(el)!
623
-
624
- input.focus() // Opens popover
625
- await nextFrame() // Allow popover to open
626
- await el.updateComplete // Ensure state updates related to opening are done
627
-
628
- await assert.isAccessible(el)
629
- })
630
-
631
- it('is accessible when popover is open and an item is highlighted', async () => {
632
- const el = await basicFixture()
633
- await el.updateComplete
634
- const input = getSlottedInput(el)!
635
-
636
- input.focus() // Opens popover
637
- await nextFrame()
638
-
639
- // Simulate highlighting the first item
640
- input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true, composed: true }))
641
- await aTimeout(0) // for rAF in handleKeydown
642
- await nextFrame() // for highlight to apply
643
- await assert.isAccessible(el)
644
- })
645
- })
646
- })