@api-client/ui 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/.vscode/settings.json +3 -3
  2. package/build/src/elements/authorization/ui/ApiKeyAuthorization.d.ts +1 -1
  3. package/build/src/elements/authorization/ui/ApiKeyAuthorization.d.ts.map +1 -1
  4. package/build/src/elements/authorization/ui/ApiKeyAuthorization.js +7 -7
  5. package/build/src/elements/authorization/ui/ApiKeyAuthorization.js.map +1 -1
  6. package/build/src/elements/authorization/ui/Authorization.styles.js +4 -4
  7. package/build/src/elements/authorization/ui/Authorization.styles.js.map +1 -1
  8. package/build/src/elements/authorization/ui/BasicAuthorization.d.ts +1 -1
  9. package/build/src/elements/authorization/ui/BasicAuthorization.d.ts.map +1 -1
  10. package/build/src/elements/authorization/ui/BasicAuthorization.js +5 -5
  11. package/build/src/elements/authorization/ui/BasicAuthorization.js.map +1 -1
  12. package/build/src/elements/authorization/ui/BearerAuthorization.d.ts +1 -1
  13. package/build/src/elements/authorization/ui/BearerAuthorization.d.ts.map +1 -1
  14. package/build/src/elements/authorization/ui/BearerAuthorization.js +3 -3
  15. package/build/src/elements/authorization/ui/BearerAuthorization.js.map +1 -1
  16. package/build/src/elements/authorization/ui/NtlmAuthorization.d.ts +1 -1
  17. package/build/src/elements/authorization/ui/NtlmAuthorization.d.ts.map +1 -1
  18. package/build/src/elements/authorization/ui/NtlmAuthorization.js +7 -7
  19. package/build/src/elements/authorization/ui/NtlmAuthorization.js.map +1 -1
  20. package/build/src/elements/authorization/ui/OAuth2Authorization.d.ts +1 -1
  21. package/build/src/elements/authorization/ui/OAuth2Authorization.d.ts.map +1 -1
  22. package/build/src/elements/authorization/ui/OAuth2Authorization.js +32 -27
  23. package/build/src/elements/authorization/ui/OAuth2Authorization.js.map +1 -1
  24. package/build/src/elements/authorization/ui/OidcAuthorization.js +4 -4
  25. package/build/src/elements/authorization/ui/OidcAuthorization.js.map +1 -1
  26. package/build/src/elements/autocomplete/autocomplete-input.d.ts +10 -0
  27. package/build/src/elements/autocomplete/autocomplete-input.d.ts.map +1 -0
  28. package/build/src/{md/text-field/ui-text-field.js → elements/autocomplete/autocomplete-input.js} +9 -9
  29. package/build/src/elements/autocomplete/autocomplete-input.js.map +1 -0
  30. package/build/src/elements/autocomplete/internals/autocomplete.d.ts +209 -0
  31. package/build/src/elements/autocomplete/internals/autocomplete.d.ts.map +1 -0
  32. package/build/src/elements/autocomplete/internals/autocomplete.js +493 -0
  33. package/build/src/elements/autocomplete/internals/autocomplete.js.map +1 -0
  34. package/build/src/elements/autocomplete/internals/autocomplete.styles.d.ts +3 -0
  35. package/build/src/elements/autocomplete/internals/autocomplete.styles.d.ts.map +1 -0
  36. package/build/src/elements/autocomplete/internals/autocomplete.styles.js +25 -0
  37. package/build/src/elements/autocomplete/internals/autocomplete.styles.js.map +1 -0
  38. package/build/src/elements/dialog/internals/DeleteCookieAction.element.d.ts +1 -1
  39. package/build/src/elements/dialog/internals/DeleteCookieAction.element.d.ts.map +1 -1
  40. package/build/src/elements/dialog/internals/DeleteCookieAction.element.js +5 -5
  41. package/build/src/elements/dialog/internals/DeleteCookieAction.element.js.map +1 -1
  42. package/build/src/elements/dialog/internals/Rename.d.ts +1 -1
  43. package/build/src/elements/dialog/internals/Rename.d.ts.map +1 -1
  44. package/build/src/elements/dialog/internals/Rename.js +3 -3
  45. package/build/src/elements/dialog/internals/Rename.js.map +1 -1
  46. package/build/src/elements/dialog/internals/SetCookieAction.element.d.ts +1 -1
  47. package/build/src/elements/dialog/internals/SetCookieAction.element.d.ts.map +1 -1
  48. package/build/src/elements/dialog/internals/SetCookieAction.element.js +9 -9
  49. package/build/src/elements/dialog/internals/SetCookieAction.element.js.map +1 -1
  50. package/build/src/elements/environment/EnvironmentEditor.d.ts +1 -1
  51. package/build/src/elements/environment/EnvironmentEditor.d.ts.map +1 -1
  52. package/build/src/elements/environment/EnvironmentEditor.js +3 -3
  53. package/build/src/elements/environment/EnvironmentEditor.js.map +1 -1
  54. package/build/src/elements/environment/EnvironmentEditor.styles.js +1 -1
  55. package/build/src/elements/environment/EnvironmentEditor.styles.js.map +1 -1
  56. package/build/src/elements/environment/ServerEditor.d.ts +1 -1
  57. package/build/src/elements/environment/ServerEditor.d.ts.map +1 -1
  58. package/build/src/elements/environment/ServerEditor.js +7 -7
  59. package/build/src/elements/environment/ServerEditor.js.map +1 -1
  60. package/build/src/elements/environment/ServerEditor.styles.js +1 -1
  61. package/build/src/elements/environment/ServerEditor.styles.js.map +1 -1
  62. package/build/src/elements/http/BodyMultipartEditor.d.ts.map +1 -1
  63. package/build/src/elements/http/BodyMultipartEditor.js +4 -0
  64. package/build/src/elements/http/BodyMultipartEditor.js.map +1 -1
  65. package/build/src/elements/http/CertificateAdd.element.d.ts +1 -1
  66. package/build/src/elements/http/CertificateAdd.element.d.ts.map +1 -1
  67. package/build/src/elements/http/CertificateAdd.element.js +8 -8
  68. package/build/src/elements/http/CertificateAdd.element.js.map +1 -1
  69. package/build/src/elements/http/CertificateAdd.styles.js +1 -1
  70. package/build/src/elements/http/CertificateAdd.styles.js.map +1 -1
  71. package/build/src/elements/http/HttpAssertions.element.js +3 -3
  72. package/build/src/elements/http/HttpAssertions.element.js.map +1 -1
  73. package/build/src/elements/http/HttpFlows.element.js +3 -3
  74. package/build/src/elements/http/HttpFlows.element.js.map +1 -1
  75. package/build/src/elements/http/HttpFlowsUi.d.ts +1 -1
  76. package/build/src/elements/http/HttpFlowsUi.d.ts.map +1 -1
  77. package/build/src/elements/http/HttpFlowsUi.js +31 -31
  78. package/build/src/elements/http/HttpFlowsUi.js.map +1 -1
  79. package/build/src/elements/http/RequestConfigElement.d.ts +1 -1
  80. package/build/src/elements/http/RequestConfigElement.d.ts.map +1 -1
  81. package/build/src/elements/http/RequestConfigElement.js +7 -7
  82. package/build/src/elements/http/RequestConfigElement.js.map +1 -1
  83. package/build/src/elements/http/UrlParamsForm.d.ts +1 -1
  84. package/build/src/elements/http/UrlParamsForm.d.ts.map +1 -1
  85. package/build/src/elements/http/UrlParamsForm.js +1 -1
  86. package/build/src/elements/http/UrlParamsForm.js.map +1 -1
  87. package/build/src/elements/project/ProjectRunner.d.ts +1 -1
  88. package/build/src/elements/project/ProjectRunner.d.ts.map +1 -1
  89. package/build/src/elements/project/ProjectRunner.js +5 -5
  90. package/build/src/elements/project/ProjectRunner.js.map +1 -1
  91. package/build/src/md/input/Input.d.ts +0 -15
  92. package/build/src/md/input/Input.d.ts.map +1 -1
  93. package/build/src/md/input/Input.js +7 -42
  94. package/build/src/md/input/Input.js.map +1 -1
  95. package/build/src/md/list/internals/List.d.ts +7 -2
  96. package/build/src/md/list/internals/List.d.ts.map +1 -1
  97. package/build/src/md/list/internals/List.js +6 -0
  98. package/build/src/md/list/internals/List.js.map +1 -1
  99. package/build/src/md/list/internals/ListItem.styles.d.ts.map +1 -1
  100. package/build/src/md/list/internals/ListItem.styles.js +8 -0
  101. package/build/src/md/list/internals/ListItem.styles.js.map +1 -1
  102. package/build/src/md/listbox/internals/Listbox.d.ts +2 -2
  103. package/build/src/md/listbox/internals/Listbox.d.ts.map +1 -1
  104. package/build/src/md/listbox/internals/Listbox.js.map +1 -1
  105. package/build/src/md/text-area/internals/TextAreaElement.d.ts.map +1 -1
  106. package/build/src/md/text-area/internals/TextAreaElement.js +0 -5
  107. package/build/src/md/text-area/internals/TextAreaElement.js.map +1 -1
  108. package/build/src/md/text-area/ui-text-area.d.ts.map +1 -1
  109. package/build/src/md/text-area/ui-text-area.js +3 -2
  110. package/build/src/md/text-area/ui-text-area.js.map +1 -1
  111. package/build/src/md/text-field/internals/{TextFieldElement.d.ts → TextField.d.ts} +2 -2
  112. package/build/src/md/text-field/internals/TextField.d.ts.map +1 -0
  113. package/build/src/md/text-field/internals/{TextFieldElement.js → TextField.js} +2 -5
  114. package/build/src/md/text-field/internals/TextField.js.map +1 -0
  115. package/build/src/md/text-field/internals/{TextField.styles.d.ts → common.styles.d.ts} +1 -1
  116. package/build/src/md/text-field/internals/common.styles.d.ts.map +1 -0
  117. package/build/src/md/text-field/internals/{TextField.styles.js → common.styles.js} +8 -94
  118. package/build/src/md/text-field/internals/common.styles.js.map +1 -0
  119. package/build/src/md/text-field/internals/filled.styles.d.ts +3 -0
  120. package/build/src/md/text-field/internals/filled.styles.d.ts.map +1 -0
  121. package/build/src/md/text-field/internals/filled.styles.js +107 -0
  122. package/build/src/md/text-field/internals/filled.styles.js.map +1 -0
  123. package/build/src/md/text-field/internals/outlined.styles.d.ts +3 -0
  124. package/build/src/md/text-field/internals/outlined.styles.d.ts.map +1 -0
  125. package/build/src/md/text-field/internals/outlined.styles.js +43 -0
  126. package/build/src/md/text-field/internals/outlined.styles.js.map +1 -0
  127. package/build/src/md/text-field/ui-filled-text-field.d.ts +11 -0
  128. package/build/src/md/text-field/ui-filled-text-field.d.ts.map +1 -0
  129. package/build/src/md/text-field/ui-filled-text-field.js +28 -0
  130. package/build/src/md/text-field/ui-filled-text-field.js.map +1 -0
  131. package/build/src/md/text-field/ui-outlined-text-field.d.ts +11 -0
  132. package/build/src/md/text-field/ui-outlined-text-field.d.ts.map +1 -0
  133. package/build/src/md/text-field/ui-outlined-text-field.js +28 -0
  134. package/build/src/md/text-field/ui-outlined-text-field.js.map +1 -0
  135. package/build/src/types/input.d.ts +1 -1
  136. package/build/src/types/input.d.ts.map +1 -1
  137. package/build/src/types/input.js.map +1 -1
  138. package/demo/elements/authorization/oauth-authorize.html +4 -4
  139. package/demo/elements/authorization/oauth-authorize.ts +1 -1
  140. package/demo/elements/autocomplete/index.html +24 -0
  141. package/demo/elements/autocomplete/index.ts +123 -0
  142. package/demo/elements/http/body-editor.ts +3 -3
  143. package/demo/elements/index.html +15 -11
  144. package/demo/md/index.html +1 -1
  145. package/demo/md/inputs/input.html +10 -15
  146. package/demo/md/inputs/input.ts +389 -101
  147. package/demo/page.css +4 -0
  148. package/package.json +1 -1
  149. package/src/elements/authorization/ui/ApiKeyAuthorization.ts +7 -7
  150. package/src/elements/authorization/ui/Authorization.styles.ts +4 -4
  151. package/src/elements/authorization/ui/BasicAuthorization.ts +5 -5
  152. package/src/elements/authorization/ui/BearerAuthorization.ts +3 -3
  153. package/src/elements/authorization/ui/NtlmAuthorization.ts +7 -7
  154. package/src/elements/authorization/ui/OAuth2Authorization.ts +32 -27
  155. package/src/elements/authorization/ui/OidcAuthorization.ts +4 -4
  156. package/src/elements/autocomplete/autocomplete-input.ts +14 -0
  157. package/src/elements/autocomplete/internals/autocomplete.styles.ts +25 -0
  158. package/src/elements/autocomplete/internals/autocomplete.ts +490 -0
  159. package/src/elements/dialog/internals/DeleteCookieAction.element.ts +5 -5
  160. package/src/elements/dialog/internals/Rename.ts +3 -3
  161. package/src/elements/dialog/internals/SetCookieAction.element.ts +9 -9
  162. package/src/elements/environment/EnvironmentEditor.styles.ts +1 -1
  163. package/src/elements/environment/EnvironmentEditor.ts +3 -3
  164. package/src/elements/environment/ServerEditor.styles.ts +1 -1
  165. package/src/elements/environment/ServerEditor.ts +7 -7
  166. package/src/elements/http/BodyMultipartEditor.ts +4 -0
  167. package/src/elements/http/CertificateAdd.element.ts +8 -8
  168. package/src/elements/http/CertificateAdd.styles.ts +1 -1
  169. package/src/elements/http/HttpAssertions.element.ts +3 -3
  170. package/src/elements/http/HttpFlows.element.ts +3 -3
  171. package/src/elements/http/HttpFlowsUi.ts +31 -31
  172. package/src/elements/http/RequestConfigElement.ts +7 -7
  173. package/src/elements/http/UrlParamsForm.ts +1 -1
  174. package/src/elements/project/ProjectRunner.ts +5 -5
  175. package/src/md/input/Input.ts +6 -21
  176. package/src/md/list/internals/List.ts +14 -2
  177. package/src/md/list/internals/ListItem.styles.ts +8 -0
  178. package/src/md/listbox/internals/Listbox.ts +2 -2
  179. package/src/md/text-area/internals/TextAreaElement.ts +0 -5
  180. package/src/md/text-area/ui-text-area.ts +3 -2
  181. package/src/md/text-field/internals/{TextFieldElement.ts → TextField.ts} +1 -4
  182. package/src/md/text-field/internals/{TextField.styles.ts → common.styles.ts} +7 -93
  183. package/src/md/text-field/internals/filled.styles.ts +107 -0
  184. package/src/md/text-field/internals/outlined.styles.ts +43 -0
  185. package/src/md/text-field/ui-filled-text-field.ts +16 -0
  186. package/src/md/text-field/ui-outlined-text-field.ts +16 -0
  187. package/src/types/input.ts +0 -1
  188. package/test/elements/authorization/basic-method.test.ts +3 -3
  189. package/test/elements/authorization/bearer-method.test.ts +2 -2
  190. package/test/elements/authorization/ntlm-method.test.ts +4 -4
  191. package/test/elements/autocomplete/autocomplete-input.spec.ts +448 -0
  192. package/test/elements/http/BodyMultipartEditorElement.test.ts +15 -16
  193. package/test/elements/http/CertificateAdd.test.ts +11 -11
  194. package/test/elements/http/HttpAssertions.test.ts +9 -9
  195. package/test/elements/http/HttpFlows.test.ts +4 -4
  196. package/build/src/md/text-field/internals/TextField.styles.d.ts.map +0 -1
  197. package/build/src/md/text-field/internals/TextField.styles.js.map +0 -1
  198. package/build/src/md/text-field/internals/TextFieldElement.d.ts.map +0 -1
  199. package/build/src/md/text-field/internals/TextFieldElement.js.map +0 -1
  200. package/build/src/md/text-field/ui-text-field.d.ts +0 -11
  201. package/build/src/md/text-field/ui-text-field.d.ts.map +0 -1
  202. package/build/src/md/text-field/ui-text-field.js.map +0 -1
  203. package/src/md/text-field/ui-text-field.ts +0 -15
@@ -0,0 +1,490 @@
1
+ import { html, LitElement, PropertyValues, TemplateResult } from 'lit'
2
+ import { state } from 'lit/decorators.js'
3
+ import type UiListbox from '../../../md/listbox/internals/Listbox.js'
4
+ import type UiListItem from '../../../md/list/internals/ListItem.js'
5
+ import { bound } from '../../../decorators/bound.js'
6
+
7
+ /**
8
+ * An accessible and performant autocomplete component that enhances a text input with a list of suggestions.
9
+ *
10
+ * The `autocomplete-input` component provides a flexible way to add autocomplete functionality
11
+ * to any text input. It works by coordinating a slotted input element with a slotted `ui-listbox`
12
+ * containing suggestions.
13
+ *
14
+ * Key Features:
15
+ *
16
+ * - **Popover Management**: Automatically shows and hides the suggestions popover based on input focus
17
+ * and user interaction.
18
+ * - **Keyboard Navigation**: Allows users to navigate suggestions using ArrowUp, ArrowDown keys directly
19
+ * from the input. Enter selects a suggestion, and Escape closes the popover.
20
+ * The suggestion list itself never gains focus.
21
+ * - **Dynamic Filtering**: Filters the list of suggestions as the user types into the input. Items not matching
22
+ * the query are hidden (not removed).
23
+ * - By default, filtering checks `item.dataset.value` and then `item.textContent`.
24
+ * - If a `ui-list-item` has a `data-index` attribute (e.g., `data-index="name email"`), filtering will search
25
+ * within the specified `data-*` attributes (e.g., `data-name`, `data-email`).
26
+ * - **Mutation Awareness**: Reacts to changes in slotted suggestions, re-filtering them if necessary.
27
+ * - **Event-Driven**: Notifies the parent application via an `autocomplete` event when a suggestion is selected,
28
+ * without directly modifying the input's value. This allows the application author to control the input update logic.
29
+ * - **Accessibility**: Designed with accessibility in mind, ensuring keyboard navigability and proper ARIA attribute
30
+ * management (handled by `ui-listbox`).
31
+ *
32
+ * The component uses CSS anchor positioning to place the suggestions popover relative to the input.
33
+ * Ensure your `ui-listbox` is styled appropriately for popover display (e.g., `popover="manual"`, `position-anchor`).
34
+ * The component will manage `showPopover()` and `hidePopover()` calls.
35
+ *
36
+ * @slot input - The input element that will be used for autocomplete. This element should be an `HTMLInputElement`
37
+ * or behave like one (have a `value` property and dispatch `input`, `focus`, `blur`, and `keydown`
38
+ * events).
39
+ * @slot suggestions - The `ui-listbox` element containing `ui-list-item` elements as suggestions.
40
+ *
41
+ * @fires autocomplete - Dispatched when a suggestion is selected by the user (e.g., via click or Enter key).
42
+ * The `event.detail` object contains:
43
+ * - `item`: The selected `UiListItem` instance.
44
+ *
45
+ * @example
46
+ * ```html
47
+ * <autocomplete-input @autocomplete="${this.handleAutocompleteSelection}">
48
+ * <input slot="input" type="text" placeholder="Search fruits..." />
49
+ * <ui-listbox slot="suggestions">
50
+ * <ui-list-item data-value="apple">Apple</ui-list-item>
51
+ * <ui-list-item data-value="banana">Banana</ui-list-item>
52
+ * <ui-list-item data-value="cherry">Cherry</ui-list-item>
53
+ * </ui-listbox>
54
+ * </autocomplete-input>
55
+ *
56
+ * <script>
57
+ * // In your component/script
58
+ * handleAutocompleteSelection(event) {
59
+ * const selectedItem = event.detail.item;
60
+ * const inputElement = this.shadowRoot.querySelector('input[slot="input"]');
61
+ * if (inputElement) {
62
+ * inputElement.value = selectedItem.dataset.value || selectedItem.textContent;
63
+ * }
64
+ * console.log('Selected:', selectedItem.dataset.value);
65
+ * }
66
+ * </script>
67
+ * ```
68
+ *
69
+ * @example Dynamic filtering with `data-index`
70
+ * ```html
71
+ * <autocomplete-input @autocomplete="${this.handleUserSelection}">
72
+ * <input slot="input" type="text" placeholder="Search users..." />
73
+ * <ui-listbox slot="suggestions">
74
+ * <ui-list-item data-id="1" data-name="Alice Wonderland" data-email="alice@example.com" data-index="name email">
75
+ * Alice Wonderland
76
+ * <span slot="supporting-text">alice@example.com</span>
77
+ * </ui-list-item>
78
+ * <ui-list-item data-id="2" data-name="Bob The Builder" data-email="bob@example.com" data-index="name email">
79
+ * Bob The Builder
80
+ * <span slot="supporting-text">bob@example.com</span>
81
+ * </ui-list-item>
82
+ * </ui-listbox>
83
+ * </autocomplete-input>
84
+ *
85
+ * <script>
86
+ * // In your component/script
87
+ * handleUserSelection(event) {
88
+ * const selectedItem = event.detail.item;
89
+ * const inputElement = this.shadowRoot.querySelector('input[slot="input"]');
90
+ * if (inputElement) {
91
+ * // You might want to display the name, but store the ID
92
+ * inputElement.value = selectedItem.dataset.name;
93
+ * }
94
+ * console.log('Selected user ID:', selectedItem.dataset.id);
95
+ * }
96
+ * </script>
97
+ * ```
98
+ */
99
+ export default class Autocomplete extends LitElement {
100
+ /**
101
+ * The MutationObserver instance used to watch for changes in slotted children.
102
+ */
103
+ protected observer?: MutationObserver
104
+
105
+ /**
106
+ * The ID of the input element, generated if not provided.
107
+ * This is used for CSS anchoring and to ensure unique IDs for accessibility.
108
+ */
109
+ @state() protected accessor inputId = ''
110
+
111
+ /**
112
+ * The reference to the slotted input element.
113
+ * This should be an `HTMLInputElement` or behave like one.
114
+ */
115
+ protected inputRef?: HTMLInputElement | HTMLElement | null
116
+ /**
117
+ * The reference to the slotted suggestions element, which should be a `ui-listbox`.
118
+ * This is used to manage the suggestions popover and filtering.
119
+ */
120
+ protected suggestionsRef?: UiListbox | null
121
+
122
+ /**
123
+ * Checks if the suggestions popover is currently open.
124
+ */
125
+ get opened(): boolean {
126
+ return this.suggestionsRef?.matches(':popover-open') || false
127
+ }
128
+
129
+ override connectedCallback(): void {
130
+ super.connectedCallback()
131
+ this.observer = new MutationObserver(this.handleMutations.bind(this))
132
+ this.observer.observe(this, { childList: true })
133
+ }
134
+
135
+ override disconnectedCallback(): void {
136
+ super.disconnectedCallback()
137
+ this.observer?.disconnect()
138
+ this.observer = undefined
139
+ this.clearInputListeners()
140
+ this.clearSuggestionsListeners()
141
+ }
142
+
143
+ protected override firstUpdated(cp: PropertyValues): void {
144
+ super.firstUpdated(cp)
145
+ this.firstSetup()
146
+ }
147
+
148
+ /**
149
+ * Performs initial setup after the first update, ensuring that slotted input
150
+ * and suggestions elements are configured.
151
+ */
152
+ async firstSetup(): Promise<void> {
153
+ await this.updateComplete
154
+ const input = this.shadowRoot?.querySelector('slot[name="input"]') as HTMLSlotElement | null
155
+ const suggestions = this.shadowRoot?.querySelector('slot[name="suggestions"]') as HTMLSlotElement | null
156
+ if (input) {
157
+ const elements = input.assignedElements()
158
+ if (elements.length > 0 && elements[0] instanceof HTMLElement) {
159
+ this.setupInput(elements[0])
160
+ }
161
+ }
162
+ if (suggestions) {
163
+ const elements = suggestions.assignedElements()
164
+ if (elements.length > 0 && elements[0] instanceof HTMLElement) {
165
+ // Assuming the slotted element is ui-listbox or compatible
166
+ this.setupSuggestions(elements[0] as UiListbox)
167
+ }
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Handles mutations observed on the component's slotted children.
173
+ * This is used to set up or tear down input and suggestions elements when they are added or removed.
174
+ * @param mutations An array of MutationRecord objects.
175
+ */
176
+ protected handleMutations(mutations: MutationRecord[]): void {
177
+ for (const mutation of mutations) {
178
+ if (mutation.type === 'childList') {
179
+ for (const node of mutation.removedNodes) {
180
+ if (node instanceof HTMLElement) {
181
+ if (node.slot === 'input' && this.inputRef === node) {
182
+ this.clearInputListeners()
183
+ } else if (node.slot === 'suggestions' && this.suggestionsRef === node) {
184
+ this.clearSuggestionsListeners()
185
+ }
186
+ }
187
+ }
188
+ for (const node of mutation.addedNodes) {
189
+ if (node instanceof HTMLElement) {
190
+ if (node.slot === 'input') {
191
+ this.setupInput(node)
192
+ } else if (node.slot === 'suggestions') {
193
+ this.setupSuggestions(node as UiListbox)
194
+ }
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Clears event listeners from the current input reference and resets it.
203
+ */
204
+ protected clearInputListeners(): void {
205
+ if (this.inputRef) {
206
+ this.inputRef.removeEventListener('focus', this.handleInputFocus)
207
+ this.inputRef.removeEventListener('input', this.handleInput)
208
+ this.inputRef.removeEventListener('keydown', this.handleKeydown as EventListener)
209
+ this.inputRef.removeEventListener('blur', this.handleInputBlur)
210
+ }
211
+ this.inputRef = null
212
+ }
213
+
214
+ /**
215
+ * Clears event listeners from the current suggestions reference and resets it.
216
+ */
217
+ protected clearSuggestionsListeners(): void {
218
+ if (this.suggestionsRef) {
219
+ this.suggestionsRef.removeEventListener('select', this.handleSuggestionSelect as EventListener)
220
+ // If ui-listbox uses a slot for its items, listen to its slotchange
221
+ this.suggestionsRef.removeEventListener('itemschange', this.handleSuggestionsSlotChange)
222
+ if (this.suggestionsRef.matches(':popover-open')) {
223
+ this.suggestionsRef.hidePopover()
224
+ }
225
+ }
226
+ this.suggestionsRef = null
227
+ }
228
+
229
+ /**
230
+ * Sets up the slotted input element.
231
+ * Assigns an ID if necessary, sets up CSS anchoring, and attaches event listeners.
232
+ * @param input The HTMLElement to be used as the input.
233
+ */
234
+ protected setupInput(input: HTMLElement): void {
235
+ this.clearInputListeners() // Clear any old listeners
236
+ if (!input.id) {
237
+ this.inputId = `autocomplete-input-${Math.random().toString(36).substring(2, 15)}`
238
+ input.id = this.inputId
239
+ } else {
240
+ this.inputId = input.id
241
+ }
242
+ // Ensure CSS anchor positioning can work
243
+ input.style.setProperty('anchor-name', `--${this.inputId}`)
244
+
245
+ this.inputRef = input as HTMLInputElement // Assuming it behaves like an input
246
+ this.inputRef.addEventListener('focus', this.handleInputFocus)
247
+ this.inputRef.addEventListener('input', this.handleInput)
248
+ this.inputRef.addEventListener('keydown', this.handleKeydown as EventListener)
249
+ this.inputRef.addEventListener('blur', this.handleInputBlur)
250
+
251
+ // Apply initial filtering if suggestions are already present
252
+ if (this.suggestionsRef) {
253
+ this.suggestionsRef.style.setProperty('position-anchor', `--${this.inputId}`)
254
+ this.filterSuggestions((this.inputRef as HTMLInputElement).value || '')
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Sets up the slotted suggestions element (assumed to be a `UiListbox`).
260
+ * Configures popover behavior, CSS anchoring, and attaches event listeners.
261
+ * @param suggestionsElement The `UiListbox` element to be used for suggestions.
262
+ */
263
+ protected setupSuggestions(suggestionsElement: UiListbox): void {
264
+ this.clearSuggestionsListeners() // Clear any old listeners
265
+
266
+ this.suggestionsRef = suggestionsElement
267
+ this.suggestionsRef.popover = 'manual'
268
+ this.suggestionsRef.tabIndex = -1 // Prevent direct focus
269
+
270
+ if (this.inputId) {
271
+ this.suggestionsRef.style.setProperty('position-anchor', `--${this.inputId}`)
272
+ }
273
+
274
+ this.suggestionsRef.addEventListener('select', this.handleSuggestionSelect as EventListener)
275
+ // The `List` dispatches `itemschange` when the slot changes, so we can listen to it.
276
+ this.suggestionsRef.addEventListener('itemschange', this.handleSuggestionsSlotChange)
277
+
278
+ // Initial filter
279
+ this.filterSuggestions(this.inputRef ? (this.inputRef as HTMLInputElement).value : '')
280
+ }
281
+
282
+ /**
283
+ * Handles the focus event on the input element.
284
+ * Opens the suggestions popover if there are items to display.
285
+ */
286
+ @bound
287
+ protected handleInputFocus(): void {
288
+ const items = this.suggestionsRef?.items
289
+ if (!items || items.length === 0) {
290
+ // If no suggestions, do not open
291
+ return
292
+ }
293
+ const active = items.find((item) => !item.hidden)
294
+ if (active) {
295
+ this.openSuggestions()
296
+ this.filterSuggestions(this.inputRef ? (this.inputRef as HTMLInputElement).value : '')
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Handles the input event on the input element.
302
+ * Filters suggestions based on the input query and opens/closes the popover accordingly.
303
+ * @param event The input event.
304
+ */
305
+ @bound
306
+ protected handleInput(event: Event): void {
307
+ this.openSuggestions()
308
+ const query = (event.target as HTMLInputElement).value
309
+ this.filterSuggestions(query)
310
+ }
311
+
312
+ /**
313
+ * Handles keydown events on the input element for navigating and selecting suggestions.
314
+ * - ArrowDown/ArrowUp: Navigates the suggestion list.
315
+ * - Enter: Selects the highlighted suggestion.
316
+ * - Escape: Closes the suggestions popover.
317
+ * @param event The keyboard event.
318
+ */
319
+ @bound
320
+ protected handleKeydown(event: KeyboardEvent): void {
321
+ if (!this.suggestionsRef) return
322
+
323
+ const { key } = event
324
+ const isSuggestionsOpen = this.suggestionsRef.matches(':popover-open')
325
+
326
+ if (['ArrowDown', 'ArrowUp'].includes(key)) {
327
+ event.preventDefault()
328
+ if (!isSuggestionsOpen) {
329
+ this.openSuggestions()
330
+ // Give popover a moment to open before trying to highlight
331
+ requestAnimationFrame(() => this.navigateList(key))
332
+ } else {
333
+ this.navigateList(key)
334
+ }
335
+ } else if (key === 'Enter') {
336
+ if (isSuggestionsOpen && this.suggestionsRef.highlightListItem) {
337
+ event.preventDefault()
338
+ this.suggestionsRef.notifySelect(this.suggestionsRef.highlightListItem)
339
+ }
340
+ } else if (key === 'Escape') {
341
+ if (isSuggestionsOpen) {
342
+ event.preventDefault()
343
+ this.closeSuggestions()
344
+ }
345
+ }
346
+ }
347
+
348
+ /**
349
+ * Navigates the suggestion list based on the pressed key.
350
+ * @param key The key that was pressed (ArrowDown or ArrowUp).
351
+ */
352
+ protected navigateList(key: string): void {
353
+ if (!this.suggestionsRef) return
354
+ switch (key) {
355
+ case 'ArrowDown':
356
+ this.suggestionsRef.highlightNext()
357
+ break
358
+ case 'ArrowUp':
359
+ this.suggestionsRef.highlightPrevious()
360
+ break
361
+ // Don't handle Home or End keys here, as they break the usability of the input.
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Handles the blur event on the input element.
367
+ * Closes the suggestions popover if focus moves outside the autocomplete component.
368
+ */
369
+ @bound
370
+ protected handleInputBlur(): void {
371
+ // We use the manual popover mode, so we need to close suggestions
372
+ // when the input loses focus, but only the current active element is not part of the suggestions.
373
+ requestAnimationFrame(() => {
374
+ if (
375
+ !this.inputRef?.contains(document.activeElement) &&
376
+ (!this.suggestionsRef || !this.suggestionsRef.contains(document.activeElement))
377
+ ) {
378
+ this.closeSuggestions()
379
+ }
380
+ })
381
+ }
382
+
383
+ /**
384
+ * Handles the `select` event dispatched by the `ui-listbox` when a suggestion is chosen.
385
+ * Dispatches an `autocomplete` event and closes the popover.
386
+ * @param event The custom event from `ui-listbox`.
387
+ */
388
+ @bound
389
+ protected handleSuggestionSelect(event: CustomEvent): void {
390
+ event.stopPropagation()
391
+ const selectedItem = event.detail.item as UiListItem
392
+ this.dispatchEvent(
393
+ new CustomEvent('autocomplete', {
394
+ detail: { item: selectedItem },
395
+ bubbles: false,
396
+ composed: false,
397
+ })
398
+ )
399
+ this.closeSuggestions()
400
+ }
401
+
402
+ /**
403
+ * Handles the `itemschange` event dispatched by the `ui-listbox` when its slotted items change.
404
+ * Re-filters the suggestions.
405
+ */
406
+ @bound
407
+ protected handleSuggestionsSlotChange(): void {
408
+ const value = this.inputRef ? (this.inputRef as HTMLInputElement).value : ''
409
+ if (!value) {
410
+ return
411
+ }
412
+ this.openSuggestions()
413
+ this.filterSuggestions(value)
414
+ }
415
+
416
+ /**
417
+ * Opens the suggestions popover if it's not already open and there are visible items.
418
+ */
419
+ protected openSuggestions(): void {
420
+ if (this.suggestionsRef && !this.suggestionsRef.matches(':popover-open')) {
421
+ this.suggestionsRef.showPopover()
422
+ }
423
+ }
424
+
425
+ /**
426
+ * Closes the suggestions popover if it's open and clears any highlighted item.
427
+ */
428
+ protected closeSuggestions(): void {
429
+ if (this.suggestionsRef && this.suggestionsRef.matches(':popover-open')) {
430
+ this.suggestionsRef.hidePopover()
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Filters the suggestions based on the provided query.
436
+ * Hides items that do not match and manages the highlighted item state.
437
+ * @param query The search query string.
438
+ */
439
+ protected filterSuggestions(query: string): void {
440
+ if (!this.suggestionsRef) {
441
+ return
442
+ }
443
+
444
+ const lowerCaseQuery = query.toLowerCase().trim()
445
+ let firstVisibleItem: UiListItem | null = null
446
+
447
+ // The `items` getter in `List.ts` (parent of UiListbox) correctly gets assigned elements.
448
+ const items = this.suggestionsRef.items as UiListItem[]
449
+
450
+ for (const item of items) {
451
+ let matches = false
452
+ if (lowerCaseQuery === '') {
453
+ matches = true
454
+ } else if (item.dataset.index) {
455
+ const indexFields = item.dataset.index.split(' ').filter(Boolean)
456
+ for (const field of indexFields) {
457
+ const valueToSearch = item.dataset[field] || ''
458
+ if (valueToSearch.toLowerCase().includes(lowerCaseQuery)) {
459
+ matches = true
460
+ break
461
+ }
462
+ }
463
+ } else {
464
+ const valueToSearch = item.dataset.value || item.textContent || ''
465
+ matches = valueToSearch.toLowerCase().includes(lowerCaseQuery)
466
+ }
467
+ item.hidden = !matches
468
+ if (matches && !firstVisibleItem) {
469
+ firstVisibleItem = item
470
+ }
471
+ }
472
+
473
+ // If the currently highlighted item is now hidden, try to highlight the first visible one or clear.
474
+ if (this.suggestionsRef.highlightListItem?.hidden) {
475
+ // the highlightListItem clears the highlighted item if it is not passed an argument.
476
+ this.suggestionsRef?.highlightItem(firstVisibleItem)
477
+ }
478
+ if (!firstVisibleItem) {
479
+ // Close if no items are visible
480
+ this.closeSuggestions()
481
+ }
482
+ }
483
+
484
+ protected override render(): TemplateResult {
485
+ return html`
486
+ <slot name="input"></slot>
487
+ <slot name="suggestions"></slot>
488
+ `
489
+ }
490
+ }
@@ -4,7 +4,7 @@ import { live } from 'lit/directives/live.js'
4
4
  import { ClassInfo, classMap } from 'lit/directives/class-map.js'
5
5
  import UiDialog from '../../../md/dialog/internals/Dialog.js'
6
6
  import type Input from '../../../md/input/Input.js'
7
- import '../../../md/text-field/ui-text-field.js'
7
+ import '../../../md/text-field/ui-filled-text-field.js'
8
8
  import {
9
9
  DeleteCookieStep,
10
10
  type IDeleteCookieStep,
@@ -74,7 +74,7 @@ export default class DeleteCookieAction extends UiDialog {
74
74
  return html`
75
75
  <div class="content">
76
76
  <div class="param-row">
77
- <ui-text-field
77
+ <ui-filled-text-field
78
78
  name="name"
79
79
  label="Cookie name"
80
80
  type="text"
@@ -82,10 +82,10 @@ export default class DeleteCookieAction extends UiDialog {
82
82
  @change="${this.handleNameChange}"
83
83
  class="input"
84
84
  supportingText="Leave empty to delete all cookies for the URL."
85
- ></ui-text-field>
85
+ ></ui-filled-text-field>
86
86
  </div>
87
87
  <div class="param-row">
88
- <ui-text-field
88
+ <ui-filled-text-field
89
89
  name="url"
90
90
  label="Cookie URL"
91
91
  type="url"
@@ -93,7 +93,7 @@ export default class DeleteCookieAction extends UiDialog {
93
93
  @change="${this.handleUrlChange}"
94
94
  class="input"
95
95
  supportingText="When the URL is not set then the request URL is used."
96
- ></ui-text-field>
96
+ ></ui-filled-text-field>
97
97
  </div>
98
98
  </div>
99
99
  `
@@ -7,7 +7,7 @@ import type Input from '../../../md/input/Input.js'
7
7
 
8
8
  import '../../../md/button/ui-text-button.js'
9
9
  import '../../../md/icons/ui-icon.js'
10
- import '../../../md/text-field/ui-text-field.js'
10
+ import '../../../md/text-field/ui-filled-text-field.js'
11
11
 
12
12
  /**
13
13
  * A dialog that requests the user for the new name.
@@ -94,7 +94,7 @@ export default class RenameDialogElement extends UiDialog {
94
94
  const value = this.newValue || this.name || ''
95
95
  return html`
96
96
  <div class="content">
97
- <ui-text-field
97
+ <ui-filled-text-field
98
98
  class="file-name"
99
99
  type="text"
100
100
  name="name"
@@ -104,7 +104,7 @@ export default class RenameDialogElement extends UiDialog {
104
104
  @keydown="${this.handleInputKeyDown}"
105
105
  .value="${live(value)}"
106
106
  label="Enter new name"
107
- ></ui-text-field>
107
+ ></ui-filled-text-field>
108
108
  </div>
109
109
  `
110
110
  }
@@ -8,7 +8,7 @@ import type CheckboxElement from '../../../md/checkbox/internals/CheckboxElement
8
8
  import { type ISetCookieStep, SetCookieStep } from '@api-client/core/models/http-flows/steps/data/SetCookieStep.js'
9
9
  import type { SameSiteValue } from '@api-client/core/cookies/CookieParser.js'
10
10
 
11
- import '../../../md/text-field/ui-text-field.js'
11
+ import '../../../md/text-field/ui-filled-text-field.js'
12
12
  import '../../../md/checkbox/ui-checkbox.js'
13
13
 
14
14
  /**
@@ -91,7 +91,7 @@ export default class SetCookieAction extends UiDialog {
91
91
  return html`
92
92
  <div class="content">
93
93
  <div class="param-row">
94
- <ui-text-field
94
+ <ui-filled-text-field
95
95
  name="name"
96
96
  label="Cookie name"
97
97
  type="text"
@@ -100,11 +100,11 @@ export default class SetCookieAction extends UiDialog {
100
100
  @change="${this.handleValueChange}"
101
101
  class="input"
102
102
  supportingText="The name of the cookie to set."
103
- ></ui-text-field>
103
+ ></ui-filled-text-field>
104
104
  </div>
105
105
 
106
106
  <div class="param-row">
107
- <ui-text-field
107
+ <ui-filled-text-field
108
108
  name="url"
109
109
  label="Cookie URL"
110
110
  type="url"
@@ -112,29 +112,29 @@ export default class SetCookieAction extends UiDialog {
112
112
  @change="${this.handleValueChange}"
113
113
  class="input"
114
114
  supportingText="When the URL is not set then the request URL is used."
115
- ></ui-text-field>
115
+ ></ui-filled-text-field>
116
116
  </div>
117
117
 
118
118
  <div class="param-row">
119
- <ui-text-field
119
+ <ui-filled-text-field
120
120
  name="expires"
121
121
  label="Expires"
122
122
  .value="${live(expires)}"
123
123
  @change="${this.handleValueChange}"
124
124
  class="input"
125
125
  supportingText="A valid cookie date or relative time like 4d, 600s, etc."
126
- ></ui-text-field>
126
+ ></ui-filled-text-field>
127
127
  </div>
128
128
 
129
129
  <div class="param-row">
130
- <ui-text-field
130
+ <ui-filled-text-field
131
131
  name="sameSite"
132
132
  label="Same site"
133
133
  .value="${live(sameSite)}"
134
134
  @change="${this.handleSameSiteChange}"
135
135
  class="input"
136
136
  .list="${['Lax', 'Strict', 'None']}"
137
- ></ui-text-field>
137
+ ></ui-filled-text-field>
138
138
  </div>
139
139
 
140
140
  <div class="checkbox-row">
@@ -6,7 +6,7 @@ export default css`
6
6
  max-width: 900px;
7
7
  }
8
8
 
9
- ui-text-field {
9
+ ui-filled-text-field {
10
10
  margin: 0;
11
11
  width: 100%;
12
12
  }
@@ -20,7 +20,7 @@ import { TabSelectionDetail } from '../../md/tabs/internals/Tabs.js'
20
20
  import '../../define/environment/server-editor.js'
21
21
  import '../../define/environment/variables-editor.js'
22
22
  import '../../md/icons/ui-icon.js'
23
- import '../../md/text-field/ui-text-field.js'
23
+ import '../../md/text-field/ui-filled-text-field.js'
24
24
  import '../../md/switch/ui-switch.js'
25
25
  import '../../md/icon-button/ui-icon-button.js'
26
26
  import '../../md/button/ui-filled-tonal-button.js'
@@ -256,13 +256,13 @@ export default class EnvironmentEditor extends ApiElement {
256
256
  protected renderNameInput(): TemplateResult {
257
257
  const { environment } = this
258
258
  return html` <div class="form-item">
259
- <ui-text-field
259
+ <ui-filled-text-field
260
260
  name="name"
261
261
  label="Environment name"
262
262
  required
263
263
  .value="${live(environment.info.name || '')}"
264
264
  @change="${this.handleNameChange}"
265
- ></ui-text-field>
265
+ ></ui-filled-text-field>
266
266
  </div>`
267
267
  }
268
268
 
@@ -9,7 +9,7 @@ export default css`
9
9
  margin: 20px 0;
10
10
  }
11
11
 
12
- ui-text-field {
12
+ ui-filled-text-field {
13
13
  margin: 0;
14
14
  width: 100%;
15
15
  }