@api-client/ui 0.5.39 → 0.5.41

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 (292) hide show
  1. package/build/src/elements/contextual-menu/{internals/ContextualMenu.d.ts → ContextualMenu.d.ts} +11 -6
  2. package/build/src/elements/contextual-menu/ContextualMenu.d.ts.map +1 -0
  3. package/build/src/elements/contextual-menu/{internals/ContextualMenu.js → ContextualMenu.js} +25 -3
  4. package/build/src/elements/contextual-menu/ContextualMenu.js.map +1 -0
  5. package/build/src/elements/contextual-menu/internals/types.d.ts +16 -7
  6. package/build/src/elements/contextual-menu/internals/types.d.ts.map +1 -1
  7. package/build/src/elements/contextual-menu/internals/types.js.map +1 -1
  8. package/build/tsconfig.tsbuildinfo +1 -0
  9. package/package.json +1 -1
  10. package/src/elements/contextual-menu/{internals/ContextualMenu.ts → ContextualMenu.ts} +31 -7
  11. package/src/elements/contextual-menu/internals/types.ts +17 -7
  12. package/.aiexclude +0 -3
  13. package/.cursor/rules/html-and-css-best-practices.mdc +0 -63
  14. package/.cursor/rules/lit-best-practices.mdc +0 -89
  15. package/.editorconfig +0 -29
  16. package/.github/CONTRIBUTING.md +0 -24
  17. package/.github/instructions/html-and-css-best-practices.instructions.md +0 -70
  18. package/.github/instructions/lit-best-practices.instructions.md +0 -90
  19. package/.github/release.yml +0 -14
  20. package/.github/stale.yml +0 -23
  21. package/.github/workflows/auto-release.yml +0 -182
  22. package/.github/workflows/release.yml +0 -82
  23. package/.prettierrc.js +0 -14
  24. package/.vscode/settings.json +0 -18
  25. package/RELEASE.md +0 -163
  26. package/RELEASE_SETUP.md +0 -235
  27. package/build/src/demo/DemoPage.d.ts +0 -81
  28. package/build/src/demo/DemoPage.d.ts.map +0 -1
  29. package/build/src/demo/DemoPage.js +0 -175
  30. package/build/src/demo/DemoPage.js.map +0 -1
  31. package/build/src/demo/DemoStyles.d.ts +0 -3
  32. package/build/src/demo/DemoStyles.d.ts.map +0 -1
  33. package/build/src/demo/DemoStyles.js +0 -60
  34. package/build/src/demo/DemoStyles.js.map +0 -1
  35. package/build/src/elements/contextual-menu/internals/ContextualMenu.d.ts.map +0 -1
  36. package/build/src/elements/contextual-menu/internals/ContextualMenu.js.map +0 -1
  37. package/build/test/elements/navigation/Navigation.test.d.ts +0 -3
  38. package/build/test/elements/navigation/Navigation.test.d.ts.map +0 -1
  39. package/build/test/elements/navigation/Navigation.test.js +0 -113
  40. package/build/test/elements/navigation/Navigation.test.js.map +0 -1
  41. package/commitlint.config.cjs +0 -2
  42. package/demo/elements/authorization/AuthPlugin.js +0 -57
  43. package/demo/elements/authorization/AuthProxy.js +0 -215
  44. package/demo/elements/authorization/api-key.html +0 -27
  45. package/demo/elements/authorization/api-key.ts +0 -44
  46. package/demo/elements/authorization/basic.html +0 -27
  47. package/demo/elements/authorization/basic.ts +0 -43
  48. package/demo/elements/authorization/bearer.html +0 -27
  49. package/demo/elements/authorization/bearer.ts +0 -43
  50. package/demo/elements/authorization/env.js +0 -8
  51. package/demo/elements/authorization/index.html +0 -44
  52. package/demo/elements/authorization/ntlm.html +0 -27
  53. package/demo/elements/authorization/ntlm.ts +0 -43
  54. package/demo/elements/authorization/oauth-authorize.html +0 -75
  55. package/demo/elements/authorization/oauth-authorize.ts +0 -40
  56. package/demo/elements/authorization/oauth-error.html +0 -18
  57. package/demo/elements/authorization/oauth-error.ts +0 -10
  58. package/demo/elements/authorization/oauth-popup.html +0 -36
  59. package/demo/elements/authorization/oauth2.html +0 -27
  60. package/demo/elements/authorization/oauth2.ts +0 -100
  61. package/demo/elements/authorization/oidc.html +0 -27
  62. package/demo/elements/authorization/oidc.ts +0 -139
  63. package/demo/elements/authorization/private.crt +0 -31
  64. package/demo/elements/authorization/private.csr +0 -28
  65. package/demo/elements/authorization/private.key +0 -51
  66. package/demo/elements/authorization/private.pem +0 -31
  67. package/demo/elements/authorization/redirect.html +0 -20
  68. package/demo/elements/authorization/ssl-commands.sh +0 -30
  69. package/demo/elements/authorization/ssl.conf +0 -24
  70. package/demo/elements/autocomplete/index.html +0 -64
  71. package/demo/elements/autocomplete/index.ts +0 -171
  72. package/demo/elements/code-editor/CodeEditorDemo.ts +0 -173
  73. package/demo/elements/code-editor/index.html +0 -19
  74. package/demo/elements/context-menu/DemoIcons.ts +0 -21
  75. package/demo/elements/context-menu/basic.html +0 -25
  76. package/demo/elements/context-menu/basic.ts +0 -119
  77. package/demo/elements/context-menu/custom-data.html +0 -25
  78. package/demo/elements/context-menu/custom-data.ts +0 -62
  79. package/demo/elements/context-menu/demo.css +0 -28
  80. package/demo/elements/context-menu/enabled-state.html +0 -25
  81. package/demo/elements/context-menu/enabled-state.ts +0 -73
  82. package/demo/elements/context-menu/icons.html +0 -25
  83. package/demo/elements/context-menu/icons.ts +0 -64
  84. package/demo/elements/context-menu/index.html +0 -43
  85. package/demo/elements/context-menu/nested.html +0 -25
  86. package/demo/elements/context-menu/nestedt.ts +0 -152
  87. package/demo/elements/context-menu/no-execute.html +0 -25
  88. package/demo/elements/context-menu/no-execute.ts +0 -134
  89. package/demo/elements/context-menu/radio-menu.html +0 -25
  90. package/demo/elements/context-menu/radio-menu.ts +0 -83
  91. package/demo/elements/context-menu/separators.html +0 -25
  92. package/demo/elements/context-menu/separators.ts +0 -172
  93. package/demo/elements/currency/index.html +0 -91
  94. package/demo/elements/currency/index.ts +0 -352
  95. package/demo/elements/environment/environment-editor.html +0 -20
  96. package/demo/elements/environment/environment-editor.ts +0 -49
  97. package/demo/elements/environment/index.html +0 -33
  98. package/demo/elements/environment/server-editor.html +0 -20
  99. package/demo/elements/environment/server-editor.ts +0 -67
  100. package/demo/elements/environment/variables-editor.html +0 -20
  101. package/demo/elements/environment/variables-editor.ts +0 -94
  102. package/demo/elements/har/har-viewer.html +0 -20
  103. package/demo/elements/har/har-viewer.ts +0 -76
  104. package/demo/elements/har/har1.har +0 -3044
  105. package/demo/elements/har/har2.json +0 -439
  106. package/demo/elements/har/index.html +0 -26
  107. package/demo/elements/highlight/example.md +0 -27
  108. package/demo/elements/highlight/index.html +0 -31
  109. package/demo/elements/highlight/marked-highlight.html +0 -132
  110. package/demo/elements/highlight/marked-highlight.ts +0 -22
  111. package/demo/elements/highlight/prism-highlight.html +0 -62
  112. package/demo/elements/highlight/prism-highlight.ts +0 -17
  113. package/demo/elements/http/body-editor.html +0 -17
  114. package/demo/elements/http/body-editor.ts +0 -115
  115. package/demo/elements/http/headers.html +0 -17
  116. package/demo/elements/http/headers.ts +0 -59
  117. package/demo/elements/http/http-assertions.html +0 -20
  118. package/demo/elements/http/http-assertions.ts +0 -89
  119. package/demo/elements/http/http-flows.html +0 -23
  120. package/demo/elements/http/http-flows.ts +0 -89
  121. package/demo/elements/http/index.html +0 -45
  122. package/demo/elements/http/request-editor.html +0 -26
  123. package/demo/elements/http/request-editor.ts +0 -197
  124. package/demo/elements/http/request-log.html +0 -16
  125. package/demo/elements/http/request-log.ts +0 -136
  126. package/demo/elements/http/url-editing.html +0 -17
  127. package/demo/elements/http/url-editing.ts +0 -112
  128. package/demo/elements/icons/index.html +0 -81
  129. package/demo/elements/icons/index.ts +0 -52
  130. package/demo/elements/index.html +0 -72
  131. package/demo/elements/mention-textarea/index.html +0 -19
  132. package/demo/elements/mention-textarea/index.ts +0 -205
  133. package/demo/elements/navigation/navigation-item.html +0 -49
  134. package/demo/elements/navigation/navigation-item.ts +0 -131
  135. package/demo/elements/navigation/navigation.html +0 -20
  136. package/demo/elements/navigation/navigation.ts +0 -45
  137. package/demo/elements/project/index.html +0 -29
  138. package/demo/elements/project/project-run-report.html +0 -20
  139. package/demo/elements/project/project-run-report.ts +0 -132
  140. package/demo/elements/project/request-editor.html +0 -23
  141. package/demo/elements/project/request-editor.ts +0 -232
  142. package/demo/elements/user/user-avatar.html +0 -17
  143. package/demo/elements/user/user-avatar.ts +0 -60
  144. package/demo/env.js +0 -4
  145. package/demo/index.html +0 -34
  146. package/demo/layout/index.html +0 -94
  147. package/demo/layout/index.ts +0 -190
  148. package/demo/md/DemoStyles.ts +0 -61
  149. package/demo/md/UiDemoPage.ts +0 -6
  150. package/demo/md/buttons/button.html +0 -121
  151. package/demo/md/buttons/button.ts +0 -246
  152. package/demo/md/buttons/group.html +0 -36
  153. package/demo/md/buttons/group.ts +0 -171
  154. package/demo/md/checkbox/index.html +0 -39
  155. package/demo/md/checkbox/index.ts +0 -220
  156. package/demo/md/chip/chip.html +0 -70
  157. package/demo/md/chip/chip.ts +0 -219
  158. package/demo/md/chip/pawel6c9a.jpg +0 -0
  159. package/demo/md/collapse/CustomDetail.ts +0 -89
  160. package/demo/md/collapse/collapse.html +0 -21
  161. package/demo/md/collapse/collapse.ts +0 -78
  162. package/demo/md/date-picker/date-picker.ts +0 -336
  163. package/demo/md/date-picker/index.html +0 -171
  164. package/demo/md/dialog/confirm-dialog.html +0 -49
  165. package/demo/md/dialog/confirm-dialog.ts +0 -121
  166. package/demo/md/dialog/dialog.html +0 -25
  167. package/demo/md/dialog/dialog.ts +0 -468
  168. package/demo/md/dropdown-list/index.html +0 -31
  169. package/demo/md/dropdown-list/index.ts +0 -158
  170. package/demo/md/icon-button/index.html +0 -122
  171. package/demo/md/icon-button/index.ts +0 -132
  172. package/demo/md/index.html +0 -73
  173. package/demo/md/inputs/input.html +0 -73
  174. package/demo/md/inputs/input.ts +0 -278
  175. package/demo/md/inputs/radio.html +0 -39
  176. package/demo/md/inputs/radio.ts +0 -156
  177. package/demo/md/inputs/switch.html +0 -45
  178. package/demo/md/inputs/switch.ts +0 -144
  179. package/demo/md/list/list.html +0 -65
  180. package/demo/md/list/list.ts +0 -204
  181. package/demo/md/listbox/listbox.html +0 -31
  182. package/demo/md/listbox/listbox.ts +0 -27
  183. package/demo/md/menu/index.html +0 -19
  184. package/demo/md/menu/index.ts +0 -514
  185. package/demo/md/notification/snack.html +0 -21
  186. package/demo/md/notification/snack.ts +0 -70
  187. package/demo/md/progress/progress.html +0 -46
  188. package/demo/md/progress/progress.ts +0 -161
  189. package/demo/md/segmented-button/index.html +0 -21
  190. package/demo/md/segmented-button/index.ts +0 -55
  191. package/demo/md/select/index.html +0 -16
  192. package/demo/md/select/index.ts +0 -217
  193. package/demo/md/tabs/tabs.html +0 -40
  194. package/demo/md/tabs/tabs.ts +0 -214
  195. package/demo/oauth-popup.html +0 -36
  196. package/demo/page.css +0 -8
  197. package/demo/resources/calendar-month.png +0 -0
  198. package/demo/resources/favorite.png +0 -0
  199. package/demo/resources/fingerprint.png +0 -0
  200. package/demo/resources/home-work.png +0 -0
  201. package/demo/resources/mood.png +0 -0
  202. package/demo/resources/print.png +0 -0
  203. package/demo/resources/stars.png +0 -0
  204. package/demo/resources/theaters.png +0 -0
  205. package/demo/tsconfig.json +0 -4
  206. package/eslint.config.js +0 -97
  207. package/scripts/copy-assets.js +0 -21
  208. package/scripts/release.js +0 -66
  209. package/src/demo/DemoPage.ts +0 -169
  210. package/src/demo/DemoStyles.ts +0 -60
  211. package/test/README.md +0 -375
  212. package/test/contextual-menu/ContextMenu.test.ts +0 -760
  213. package/test/contextual-menu/ContextMenuElement.test.ts +0 -569
  214. package/test/core/activity.spec.ts +0 -413
  215. package/test/core/activity_manager.spec.ts +0 -544
  216. package/test/core/application.spec.ts +0 -218
  217. package/test/core/fragment.spec.ts +0 -565
  218. package/test/core/fragment_manager.spec.ts +0 -404
  219. package/test/core/live_data.spec.ts +0 -558
  220. package/test/core/renderer.spec.ts +0 -113
  221. package/test/dom-assertions.test.ts +0 -182
  222. package/test/elements/MonacoSetup.ts +0 -65
  223. package/test/elements/authorization/basic-method.test.ts +0 -177
  224. package/test/elements/authorization/bearer-method.test.ts +0 -143
  225. package/test/elements/authorization/ntlm-method.test.ts +0 -219
  226. package/test/elements/authorization/oauth2-client-credentials-method.test.ts +0 -334
  227. package/test/elements/authorization/oauth2-code-method.test.ts +0 -320
  228. package/test/elements/authorization/oauth2-custom-grant-method.test.ts +0 -255
  229. package/test/elements/authorization/oauth2-device-code-method.test.ts +0 -371
  230. package/test/elements/authorization/oauth2-implicit-method.test.ts +0 -407
  231. package/test/elements/authorization/oauth2-jwt-method.test.ts +0 -217
  232. package/test/elements/authorization/oauth2-password-method.test.ts +0 -275
  233. package/test/elements/authorization/openid-method.test.ts +0 -591
  234. package/test/elements/autocomplete/autocomplete-input.spec.ts +0 -646
  235. package/test/elements/code-editor/code-editor.accessibility.test.ts +0 -298
  236. package/test/elements/code-editor/code-editor.test.ts +0 -574
  237. package/test/elements/currency/CurrencyPicker.accessibility.test.ts +0 -328
  238. package/test/elements/currency/CurrencyPicker.core.test.ts +0 -318
  239. package/test/elements/currency/CurrencyPicker.integration.test.ts +0 -482
  240. package/test/elements/currency/CurrencyPicker.test.ts +0 -486
  241. package/test/elements/data-table/DataTable.browser.test.ts +0 -649
  242. package/test/elements/har/HarUtils.test.ts +0 -45
  243. package/test/elements/har/HarViewerElement.test.ts +0 -687
  244. package/test/elements/har/test-data/har1.har +0 -3044
  245. package/test/elements/highlight/MarkedHighlightElement.test.ts +0 -452
  246. package/test/elements/highlight/PrismHighlightElement.test.ts +0 -79
  247. package/test/elements/highlight/PrismHighlighter.test.ts +0 -94
  248. package/test/elements/highlight/remoteSanitization.md +0 -1
  249. package/test/elements/highlight/test.md +0 -3
  250. package/test/elements/highlight/test1.md +0 -3
  251. package/test/elements/highlight/test2.md +0 -1
  252. package/test/elements/http/BodyFormdataEditorElement.test.ts +0 -482
  253. package/test/elements/http/BodyMultipartEditorElement.test.ts +0 -658
  254. package/test/elements/http/BodyRawEditorElement.test.ts +0 -90
  255. package/test/elements/http/CertificateAdd.test.ts +0 -457
  256. package/test/elements/http/HttpAssertions.test.ts +0 -994
  257. package/test/elements/http/HttpFlows.test.ts +0 -502
  258. package/test/elements/http/UrlEncodeUtils.test.ts +0 -202
  259. package/test/elements/layout/SplitItem.test.ts +0 -440
  260. package/test/elements/layout/SplitLayoutManager.test.ts +0 -1501
  261. package/test/elements/layout/SplitPanel.test.ts +0 -1109
  262. package/test/elements/mention-textarea/MentionTextArea.basic.test.ts +0 -114
  263. package/test/elements/mention-textarea/MentionTextArea.test.ts +0 -613
  264. package/test/elements/navigation/Navigation.test.ts +0 -120
  265. package/test/env.ts +0 -15
  266. package/test/events/EventTypes.test.ts +0 -363
  267. package/test/events/EventsTestHelpers.ts +0 -16
  268. package/test/helpers/TestUtils.ts +0 -243
  269. package/test/helpers/UiMock.ts +0 -185
  270. package/test/lib/Dom.test.ts +0 -231
  271. package/test/md/button/UiButton.test.ts +0 -347
  272. package/test/md/button/UiIconButton.test.ts +0 -155
  273. package/test/md/chip/UiChip.test.ts +0 -219
  274. package/test/md/collapse/UiCollapse.test.ts +0 -250
  275. package/test/md/collapse/flex-layout.test.ts +0 -105
  276. package/test/md/date-time/DateTime.test.ts +0 -348
  277. package/test/md/dialog/UiConfirmDialog.test.ts +0 -131
  278. package/test/md/dialog/UiDialog.test.ts +0 -759
  279. package/test/md/menu/Menu.test.ts +0 -855
  280. package/test/md/menu/MenuIntegration.test.ts +0 -426
  281. package/test/md/menu/MenuItem.test.ts +0 -652
  282. package/test/md/menu/SubMenu.test.ts +0 -410
  283. package/test/md/progress/UiCircularProgressElement.test.ts +0 -481
  284. package/test/md/progress/UiProgressElement.test.ts +0 -117
  285. package/test/md/progress/UiRangeElement.test.ts +0 -156
  286. package/test/md/select/Select.test.ts +0 -925
  287. package/test/plugins/takeScreenshotPlugin.js +0 -35
  288. package/test/setup.test.ts +0 -217
  289. package/test/setup.ts +0 -117
  290. package/test/tsconfig.json +0 -7
  291. package/web-dev-server.config.js +0 -21
  292. 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
- })