@sapudex/web-components 2.39.0 → 2.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cypress/specs/Dropdown.cy.tsx +90 -0
- package/cypress/specs/PasswordField.cy.tsx +37 -0
- package/cypress/specs/Search.cy.tsx +244 -138
- package/cypress/specs/SelectBox.cy.tsx +457 -0
- package/cypress/specs/Tile.cy.tsx +96 -0
- package/cypress/support/component-imports.instrumented.ts +4 -0
- package/cypress/support/component-imports.src.ts +4 -0
- package/dist/Dropdown.d.ts +5 -0
- package/dist/Dropdown.js +56 -0
- package/dist/Dropdown.js.map +1 -1
- package/dist/List.d.ts +1 -6
- package/dist/List.js +2 -10
- package/dist/List.js.map +1 -1
- package/dist/MastheadCountrySelector.d.ts +7 -0
- package/dist/MastheadCountrySelector.js +3 -0
- package/dist/MastheadCountrySelector.js.map +1 -1
- package/dist/PasswordField.d.ts +10 -0
- package/dist/PasswordField.js +17 -2
- package/dist/PasswordField.js.map +1 -1
- package/dist/PasswordFieldTemplate.js +1 -1
- package/dist/PasswordFieldTemplate.js.map +1 -1
- package/dist/Search.d.ts +1 -1
- package/dist/Search.js +13 -9
- package/dist/Search.js.map +1 -1
- package/dist/SearchTemplate.js +3 -3
- package/dist/SearchTemplate.js.map +1 -1
- package/dist/SearchableListBase.d.ts +2 -6
- package/dist/SearchableListBase.js +6 -3
- package/dist/SearchableListBase.js.map +1 -1
- package/dist/SearchableListBaseTemplate.js +1 -1
- package/dist/SearchableListBaseTemplate.js.map +1 -1
- package/dist/SelectBase.d.ts +20 -2
- package/dist/SelectBase.js +189 -13
- package/dist/SelectBase.js.map +1 -1
- package/dist/SelectBox.js +2 -1
- package/dist/SelectBox.js.map +1 -1
- package/dist/SelectBoxTemplate.js +4 -4
- package/dist/SelectBoxTemplate.js.map +1 -1
- package/dist/TextField.d.ts +1 -1
- package/dist/TextField.js +2 -2
- package/dist/TextField.js.map +1 -1
- package/dist/TextFieldTemplate.js +1 -1
- package/dist/TextFieldTemplate.js.map +1 -1
- package/dist/assets/{InputSuggestions-jTW8u1vD.js → InputSuggestions-BSg7bjcx.js} +1 -1
- package/dist/assets/{ListItemStandardExpandableTextTemplate-ShRY1eSJ.js → ListItemStandardExpandableTextTemplate-CCEQur_J.js} +1 -1
- package/dist/assets/{bundle.esm-VI-guBTG.js → bundle.esm-I7iOjLgn.js} +232 -232
- package/dist/assets/messagebundle_en-BZqDcZHN.js +1 -0
- package/dist/assets/messagebundle_et-yYsmBPXN.js +1 -0
- package/dist/assets/messagebundle_fi-DeMNrHC9.js +1 -0
- package/dist/assets/messagebundle_fr-qEm5Eykz.js +1 -0
- package/dist/assets/messagebundle_hr-NBdiYo-T.js +1 -0
- package/dist/assets/messagebundle_hu-CBV3ISbF.js +1 -0
- package/dist/css/themes/MastheadCountrySelector.css +1 -1
- package/dist/css/themes/MastheadProfileMenu.css +1 -1
- package/dist/css/themes/SelectBase.css +1 -1
- package/dist/css/themes/SelectBox.css +1 -1
- package/dist/css/themes/StatusMessage.css +1 -1
- package/dist/css/themes/TextField.css +1 -1
- package/dist/css/themes/Tile.css +1 -1
- package/dist/css/themes/Toast.css +1 -1
- package/dist/custom-elements-internal.json +85 -161
- package/dist/custom-elements.json +56 -120
- package/dist/features/SearchSuggestionsPopover.d.ts +6 -9
- package/dist/features/SearchSuggestionsPopover.js +47 -163
- package/dist/features/SearchSuggestionsPopover.js.map +1 -1
- package/dist/generated/assets/i18n/messagebundle_en.json +1 -1
- package/dist/generated/assets/i18n/messagebundle_et.json +1 -1
- package/dist/generated/assets/i18n/messagebundle_fi.json +1 -1
- package/dist/generated/assets/i18n/messagebundle_fr.json +1 -1
- package/dist/generated/assets/i18n/messagebundle_hr.json +1 -1
- package/dist/generated/assets/i18n/messagebundle_hu.json +1 -1
- package/dist/generated/i18n/i18n-defaults.d.ts +3 -2
- package/dist/generated/i18n/i18n-defaults.js +3 -2
- package/dist/generated/i18n/i18n-defaults.js.map +1 -1
- package/dist/generated/templates/MastheadCountrySelectorTemplate.lit.js +6 -5
- package/dist/generated/templates/MastheadCountrySelectorTemplate.lit.js.map +1 -1
- package/dist/generated/templates/MastheadMobileMenuTemplate.lit.js +1 -1
- package/dist/generated/templates/MastheadMobileMenuTemplate.lit.js.map +1 -1
- package/dist/generated/themes/MastheadCountrySelector.css.d.ts +1 -1
- package/dist/generated/themes/MastheadCountrySelector.css.js +1 -1
- package/dist/generated/themes/MastheadCountrySelector.css.js.map +1 -1
- package/dist/generated/themes/MastheadProfileMenu.css.d.ts +1 -1
- package/dist/generated/themes/MastheadProfileMenu.css.js +1 -1
- package/dist/generated/themes/MastheadProfileMenu.css.js.map +1 -1
- package/dist/generated/themes/SelectBase.css.d.ts +1 -1
- package/dist/generated/themes/SelectBase.css.js +1 -1
- package/dist/generated/themes/SelectBase.css.js.map +1 -1
- package/dist/generated/themes/SelectBox.css.d.ts +1 -1
- package/dist/generated/themes/SelectBox.css.js +1 -1
- package/dist/generated/themes/SelectBox.css.js.map +1 -1
- package/dist/generated/themes/StatusMessage.css.d.ts +1 -1
- package/dist/generated/themes/StatusMessage.css.js +1 -1
- package/dist/generated/themes/StatusMessage.css.js.map +1 -1
- package/dist/generated/themes/TextField.css.d.ts +1 -1
- package/dist/generated/themes/TextField.css.js +1 -1
- package/dist/generated/themes/TextField.css.js.map +1 -1
- package/dist/generated/themes/Tile.css.d.ts +1 -1
- package/dist/generated/themes/Tile.css.js +1 -1
- package/dist/generated/themes/Tile.css.js.map +1 -1
- package/dist/generated/themes/Toast.css.d.ts +1 -1
- package/dist/generated/themes/Toast.css.js +1 -1
- package/dist/generated/themes/Toast.css.js.map +1 -1
- package/dist/messagebundle.properties +2 -1
- package/dist/messagebundle_en.properties +2 -1
- package/dist/messagebundle_et.properties +126 -0
- package/dist/messagebundle_fi.properties +127 -0
- package/dist/messagebundle_fr.properties +120 -0
- package/dist/messagebundle_hr.properties +125 -0
- package/dist/messagebundle_hu.properties +125 -0
- package/dist/vscode.html-custom-data.json +12 -27
- package/dist/web-types.json +36 -44
- package/package.json +1 -1
- package/src/Dropdown.ts +62 -0
- package/src/List.ts +3 -18
- package/src/MastheadCountrySelector.hbs +2 -0
- package/src/MastheadCountrySelector.ts +9 -0
- package/src/MastheadMobileMenu.hbs +1 -0
- package/src/PasswordField.ts +24 -0
- package/src/PasswordFieldTemplate.tsx +2 -2
- package/src/Search.ts +13 -9
- package/src/SearchTemplate.tsx +0 -3
- package/src/SearchableListBase.ts +8 -9
- package/src/SearchableListBaseTemplate.tsx +0 -1
- package/src/SelectBase.ts +203 -16
- package/src/SelectBox.ts +2 -1
- package/src/SelectBoxTemplate.tsx +1 -0
- package/src/TextField.ts +1 -1
- package/src/TextFieldTemplate.tsx +1 -1
- package/src/features/SearchSuggestionsPopover.ts +55 -177
- package/src/generated/i18n/i18n-defaults.ts +2 -2
- package/src/generated/templates/MastheadCountrySelectorTemplate.lit.ts +6 -5
- package/src/generated/templates/MastheadMobileMenuTemplate.lit.ts +1 -1
- package/src/generated/themes/MastheadCountrySelector.css.ts +1 -1
- package/src/generated/themes/MastheadProfileMenu.css.ts +1 -1
- package/src/generated/themes/SelectBase.css.ts +1 -1
- package/src/generated/themes/SelectBox.css.ts +1 -1
- package/src/generated/themes/StatusMessage.css.ts +1 -1
- package/src/generated/themes/TextField.css.ts +1 -1
- package/src/generated/themes/Tile.css.ts +1 -1
- package/src/generated/themes/Toast.css.ts +1 -1
- package/src/i18n/messagebundle.properties +2 -1
- package/src/i18n/messagebundle_en.properties +2 -1
- package/src/i18n/messagebundle_et.properties +126 -0
- package/src/i18n/messagebundle_fi.properties +127 -0
- package/src/i18n/messagebundle_fr.properties +120 -0
- package/src/i18n/messagebundle_hr.properties +125 -0
- package/src/i18n/messagebundle_hu.properties +125 -0
- package/src/themes/MastheadCountrySelector.css +5 -2
- package/src/themes/MastheadProfileMenu.css +0 -5
- package/src/themes/SelectBase.css +7 -0
- package/src/themes/SelectBox.css +4 -0
- package/src/themes/StatusMessage.css +4 -4
- package/src/themes/TextField.css +3 -0
- package/src/themes/Tile.css +11 -3
- package/src/themes/Toast.css +14 -2
- package/dist/assets/messagebundle_en-BxjFAY2S.js +0 -1
- package/dist/assets/messagebundle_et-BCYOcg2t.js +0 -1
- package/dist/assets/messagebundle_fi-CssCIjmp.js +0 -1
- package/dist/assets/messagebundle_fr-CvOyuAVw.js +0 -1
- package/dist/assets/messagebundle_hr-CashhWbN.js +0 -1
- package/dist/assets/messagebundle_hu-BEMbu-EE.js +0 -1
package/src/SelectBase.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
|
|
2
|
+
import slot from "@ui5/webcomponents-base/dist/decorators/slot-strict.js";
|
|
2
3
|
import executeTemplate from "@ui5/webcomponents-base/dist/renderer/executeTemplate.js";
|
|
3
|
-
import type { ChangeInfo } from "@ui5/webcomponents-base/dist/UI5Element.js";
|
|
4
|
+
import type { ChangeInfo, Slot } from "@ui5/webcomponents-base/dist/UI5Element.js";
|
|
4
5
|
import {
|
|
5
|
-
isEscape, isDown, isUp, isHome, isEnd, isTabNext, isTabPrevious,
|
|
6
|
+
isEscape, isDown, isUp, isHome, isEnd, isTabNext, isTabPrevious, isLeft, isRight,
|
|
6
7
|
} from "@ui5/webcomponents-base/dist/Keys.js";
|
|
7
8
|
import { getAllAccessibleNameRefTexts } from "@ui5/webcomponents-base/dist/util/AccessibilityTextsHelper.js";
|
|
8
9
|
import type { UDExListItemBase } from "./List.js";
|
|
@@ -163,6 +164,16 @@ class UDExSelectBase extends UDExSearchableListBase {
|
|
|
163
164
|
@property({ type: Boolean, noAttribute: true })
|
|
164
165
|
_requestRender = false;
|
|
165
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Defines the footer content for the component dropdown.
|
|
169
|
+
* Place any content (e.g. an action button) here. Clicking footer content does not close the dropdown —
|
|
170
|
+
* use the <code>close</code> method if needed.
|
|
171
|
+
*
|
|
172
|
+
* @public
|
|
173
|
+
*/
|
|
174
|
+
@slot({ type: HTMLElement })
|
|
175
|
+
footer!: Slot<HTMLElement>;
|
|
176
|
+
|
|
166
177
|
_paneWidthVariableName = "--udex-dropdown-pane-width";
|
|
167
178
|
_labelBaseClass = "udex-text-field__label";
|
|
168
179
|
_dropdownClass = "udex-select-box__dropdown";
|
|
@@ -177,10 +188,13 @@ class UDExSelectBase extends UDExSearchableListBase {
|
|
|
177
188
|
_inputValue: string = "";
|
|
178
189
|
_lastSelectedItemId: string = "";
|
|
179
190
|
_handleResizeBound: ResizeObserverCallback;
|
|
191
|
+
_handleListKeyDownBound: (e: KeyboardEvent) => void;
|
|
192
|
+
_listKeyDownListenerAdded: boolean = false;
|
|
180
193
|
|
|
181
194
|
constructor() {
|
|
182
195
|
super();
|
|
183
196
|
this._handleResizeBound = this._handleResize.bind(this);
|
|
197
|
+
this._handleListKeyDownBound = this._handleListKeyDownCapture.bind(this);
|
|
184
198
|
}
|
|
185
199
|
|
|
186
200
|
onEnterDOM() {
|
|
@@ -192,6 +206,10 @@ class UDExSelectBase extends UDExSearchableListBase {
|
|
|
192
206
|
|
|
193
207
|
onExitDOM() {
|
|
194
208
|
ResizeHandler.deregister(this, this._handleResizeBound);
|
|
209
|
+
if (this._list && this._listKeyDownListenerAdded) {
|
|
210
|
+
this._list.removeEventListener("keydown", this._handleListKeyDownBound, { capture: true });
|
|
211
|
+
this._listKeyDownListenerAdded = false;
|
|
212
|
+
}
|
|
195
213
|
}
|
|
196
214
|
|
|
197
215
|
onInvalidation({ name }: ChangeInfo): void {
|
|
@@ -210,6 +228,11 @@ class UDExSelectBase extends UDExSearchableListBase {
|
|
|
210
228
|
if (this._requestRender) {
|
|
211
229
|
this._requestRender = false;
|
|
212
230
|
}
|
|
231
|
+
|
|
232
|
+
if (!this._listKeyDownListenerAdded && this._list) {
|
|
233
|
+
this._list.addEventListener("keydown", this._handleListKeyDownBound, { capture: true });
|
|
234
|
+
this._listKeyDownListenerAdded = true;
|
|
235
|
+
}
|
|
213
236
|
}
|
|
214
237
|
|
|
215
238
|
_handleResize() {
|
|
@@ -224,14 +247,44 @@ class UDExSelectBase extends UDExSearchableListBase {
|
|
|
224
247
|
return;
|
|
225
248
|
}
|
|
226
249
|
|
|
227
|
-
if (
|
|
250
|
+
if (this.hasFooter) {
|
|
251
|
+
const isInFooter = (e.target as HTMLElement).closest?.("[slot=\"footer\"]");
|
|
252
|
+
if (isInFooter) {
|
|
253
|
+
if (isEscape(e)) {
|
|
254
|
+
this.handleEscKeyPress(e);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (isTabNext(e)) {
|
|
258
|
+
e.preventDefault();
|
|
259
|
+
this.closeDropdown();
|
|
260
|
+
this._moveFocusAfterComponent();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (isTabPrevious(e)) {
|
|
264
|
+
e.preventDefault();
|
|
265
|
+
this._focusLastListItem();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (isTabNext(e)) {
|
|
272
|
+
e.preventDefault();
|
|
273
|
+
(this._firstFocusableIn(this.footer[0]) ?? this.footer[0])?.focus();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (isTabPrevious(e)) {
|
|
228
279
|
e.preventDefault();
|
|
229
280
|
this._search.focus();
|
|
230
281
|
return;
|
|
231
282
|
}
|
|
232
283
|
|
|
233
|
-
if (isTabNext(e)
|
|
284
|
+
if (isTabNext(e)) {
|
|
285
|
+
e.preventDefault();
|
|
234
286
|
this.closeDropdown();
|
|
287
|
+
this._moveFocusAfterComponent();
|
|
235
288
|
return;
|
|
236
289
|
}
|
|
237
290
|
|
|
@@ -240,15 +293,32 @@ class UDExSelectBase extends UDExSearchableListBase {
|
|
|
240
293
|
return;
|
|
241
294
|
}
|
|
242
295
|
|
|
243
|
-
if (!items
|
|
296
|
+
if (!items) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (isRight(e)) {
|
|
301
|
+
e.preventDefault();
|
|
302
|
+
this._handleArrowRightInRow(e.target as HTMLElement);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (isLeft(e)) {
|
|
307
|
+
e.preventDefault();
|
|
308
|
+
this._handleArrowLeftInRow(e.target as HTMLElement);
|
|
244
309
|
return;
|
|
245
310
|
}
|
|
246
311
|
|
|
247
|
-
this.
|
|
312
|
+
const iconSlot = this._getIconButtonSlot(e.target as HTMLElement);
|
|
313
|
+
const effectiveTarget = iconSlot
|
|
314
|
+
? (e.target as HTMLElement).parentElement as UDExListItemBase | undefined
|
|
315
|
+
: undefined;
|
|
316
|
+
|
|
317
|
+
this.handleListNavigationKeys(e, items, effectiveTarget);
|
|
248
318
|
}
|
|
249
319
|
|
|
250
|
-
handleListNavigationKeys(e: KeyboardEvent, items: UDExListItemBase[]) {
|
|
251
|
-
const currentSelectedIndex = this._list?.getCurrentIndex(items, e.target as UDExListItemBase);
|
|
320
|
+
handleListNavigationKeys(e: KeyboardEvent, items: UDExListItemBase[], effectiveTarget?: UDExListItemBase) {
|
|
321
|
+
const currentSelectedIndex = this._list?.getCurrentIndex(items, effectiveTarget ?? e.target as UDExListItemBase);
|
|
252
322
|
const lastItemIndex = items.length - 1;
|
|
253
323
|
|
|
254
324
|
if (isDown(e)) {
|
|
@@ -352,10 +422,7 @@ class UDExSelectBase extends UDExSearchableListBase {
|
|
|
352
422
|
e.stopPropagation();
|
|
353
423
|
this.closeDropdown();
|
|
354
424
|
this._search.focus();
|
|
355
|
-
|
|
356
|
-
if (!this.tabNavigationEnabled) {
|
|
357
|
-
this._selectedOption = "";
|
|
358
|
-
}
|
|
425
|
+
this._selectedOption = "";
|
|
359
426
|
}
|
|
360
427
|
|
|
361
428
|
handleItemSelection(e:CustomEvent) {
|
|
@@ -412,10 +479,7 @@ class UDExSelectBase extends UDExSearchableListBase {
|
|
|
412
479
|
const firstIndex = (items[0]?.nonInteractive && !items[0]?.collapsible) ? 1 : 0;
|
|
413
480
|
const firstItem = items[firstIndex];
|
|
414
481
|
this._list.setTabIndexByIndex(items, firstIndex);
|
|
415
|
-
|
|
416
|
-
if (!this.tabNavigationEnabled) {
|
|
417
|
-
this._selectedOption = firstItem.label;
|
|
418
|
-
}
|
|
482
|
+
this._selectedOption = firstItem.label;
|
|
419
483
|
|
|
420
484
|
if (firstItem.isGroup) {
|
|
421
485
|
this._list.focusHeader(firstItem);
|
|
@@ -537,6 +601,129 @@ class UDExSelectBase extends UDExSearchableListBase {
|
|
|
537
601
|
}
|
|
538
602
|
}
|
|
539
603
|
|
|
604
|
+
get hasFooter(): boolean {
|
|
605
|
+
return this.footer.length > 0;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
_getIconButtonSlot(el: HTMLElement): string | null {
|
|
609
|
+
const iconSlot = el.getAttribute?.("slot");
|
|
610
|
+
return (iconSlot === "firstIcon" || iconSlot === "secondIcon") ? iconSlot : null;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
_handleArrowRightInRow(target: HTMLElement): void {
|
|
614
|
+
const iconSlot = this._getIconButtonSlot(target);
|
|
615
|
+
if (!iconSlot) {
|
|
616
|
+
const firstIcon = target.querySelector<HTMLElement>("[slot=\"firstIcon\"]");
|
|
617
|
+
firstIcon?.focus();
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
if (iconSlot === "firstIcon") {
|
|
621
|
+
const parentItem = target.parentElement;
|
|
622
|
+
if (parentItem) {
|
|
623
|
+
const secondIcon = parentItem.querySelector<HTMLElement>("[slot=\"secondIcon\"]");
|
|
624
|
+
secondIcon?.focus();
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
_handleArrowLeftInRow(target: HTMLElement): void {
|
|
630
|
+
const iconSlot = this._getIconButtonSlot(target);
|
|
631
|
+
if (!iconSlot) { return; }
|
|
632
|
+
const parentItem = target.parentElement;
|
|
633
|
+
if (!parentItem) { return; }
|
|
634
|
+
if (iconSlot === "secondIcon") {
|
|
635
|
+
const firstIcon = parentItem.querySelector<HTMLElement>("[slot=\"firstIcon\"]");
|
|
636
|
+
firstIcon?.focus();
|
|
637
|
+
} else if (iconSlot === "firstIcon") {
|
|
638
|
+
(parentItem as UDExListItemBase).focus();
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
_handleListKeyDownCapture(e: KeyboardEvent): void {
|
|
643
|
+
const target = e.target as HTMLElement;
|
|
644
|
+
if (!this._getIconButtonSlot(target)) { return; }
|
|
645
|
+
if (!isDown(e) && !isUp(e)) { return; }
|
|
646
|
+
|
|
647
|
+
const items = this._list?.getKeyboardNavigationItems();
|
|
648
|
+
if (!items?.length) { return; }
|
|
649
|
+
|
|
650
|
+
e.stopImmediatePropagation();
|
|
651
|
+
e.preventDefault();
|
|
652
|
+
|
|
653
|
+
const parentItem = target.parentElement as UDExListItemBase | null;
|
|
654
|
+
if (!parentItem) { return; }
|
|
655
|
+
|
|
656
|
+
const currentIndex = this._list.getCurrentIndex(items, parentItem);
|
|
657
|
+
if (currentIndex < 0) { return; }
|
|
658
|
+
|
|
659
|
+
const lastItemIndex = items.length - 1;
|
|
660
|
+
let targetIndex: number;
|
|
661
|
+
|
|
662
|
+
if (isDown(e)) {
|
|
663
|
+
const nextIndex = currentIndex + 1 > lastItemIndex ? 0 : currentIndex + 1;
|
|
664
|
+
const isNonInteractive = items[nextIndex].nonInteractive && !items[nextIndex].collapsible;
|
|
665
|
+
const wrappedNext = nextIndex + 1 > lastItemIndex ? 0 : nextIndex + 1;
|
|
666
|
+
targetIndex = isNonInteractive ? wrappedNext : nextIndex;
|
|
667
|
+
} else {
|
|
668
|
+
const prevIndex = currentIndex - 1 < 0 ? lastItemIndex : currentIndex - 1;
|
|
669
|
+
const isNonInteractive = items[prevIndex].nonInteractive && !items[prevIndex].collapsible;
|
|
670
|
+
const wrappedPrev = prevIndex === 0 ? lastItemIndex : prevIndex - 1;
|
|
671
|
+
targetIndex = isNonInteractive ? wrappedPrev : prevIndex;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
this._list.unsetTabIndexByIndex(items, currentIndex);
|
|
675
|
+
this._list.setTabIndexByIndex(items, targetIndex);
|
|
676
|
+
items[targetIndex].focus();
|
|
677
|
+
this._selectedOption = items[targetIndex].label;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
_focusLastListItem() {
|
|
681
|
+
const items = this._list?.getKeyboardNavigationItems();
|
|
682
|
+
if (!items || items.length === 0) {
|
|
683
|
+
this._search.focus();
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
const lastIndex = items.length - 1;
|
|
687
|
+
const lastItem = items[lastIndex];
|
|
688
|
+
this._list.setTabIndexByIndex(items, lastIndex);
|
|
689
|
+
this._selectedOption = lastItem.label;
|
|
690
|
+
if (lastItem.isGroup) {
|
|
691
|
+
this._list.focusHeader(lastItem);
|
|
692
|
+
} else {
|
|
693
|
+
lastItem.focus();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
_firstFocusableIn(el: Element | undefined): HTMLElement | null {
|
|
698
|
+
if (!(el instanceof HTMLElement)) { return null; }
|
|
699
|
+
if (el.tabIndex >= 0 && !el.hidden && !(el as HTMLElement & { disabled?: boolean }).disabled && (!!el.offsetParent || el.matches("body"))) {
|
|
700
|
+
return el;
|
|
701
|
+
}
|
|
702
|
+
return Array.from(el.children).reduce<HTMLElement | null>(
|
|
703
|
+
(found, child) => found ?? this._firstFocusableIn(child), null,
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
_moveFocusAfterComponent() {
|
|
708
|
+
const findAndFocus = (from: Element) => {
|
|
709
|
+
let node: Element | null = from;
|
|
710
|
+
while (node && node !== document.documentElement) {
|
|
711
|
+
let sibling = node.nextElementSibling;
|
|
712
|
+
while (sibling) {
|
|
713
|
+
const focusable = this._firstFocusableIn(sibling);
|
|
714
|
+
if (focusable) {
|
|
715
|
+
focusable.focus();
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
sibling = sibling.nextElementSibling;
|
|
719
|
+
}
|
|
720
|
+
node = node.parentElement;
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
findAndFocus(this);
|
|
725
|
+
}
|
|
726
|
+
|
|
540
727
|
get ariaDescribedbyText(): string {
|
|
541
728
|
if (this.modeSingleSelect) {
|
|
542
729
|
return this._selectedOption;
|
package/src/SelectBox.ts
CHANGED
|
@@ -211,9 +211,10 @@ class UDExSelectBox extends UDExSelectBase {
|
|
|
211
211
|
handleInputFocusOut(e: FocusEvent) {
|
|
212
212
|
const relatedTarget = e.relatedTarget as HTMLElement;
|
|
213
213
|
const isInsideComponent = relatedTarget?.closest(`.${this._rootClass}`);
|
|
214
|
+
const isFooterElement = this.hasFooter && !!relatedTarget && this.footer.some(el => el === relatedTarget || el.contains(relatedTarget));
|
|
214
215
|
const hasTags = this.tokenizer?.tokens.length > 0;
|
|
215
216
|
|
|
216
|
-
if (!isInsideComponent) {
|
|
217
|
+
if (!isInsideComponent && !isFooterElement) {
|
|
217
218
|
this.closeDropdown();
|
|
218
219
|
this.resetSearchState();
|
|
219
220
|
|
|
@@ -95,6 +95,7 @@ export default function SelectBoxTemplate(this: UDExSelectBox) {
|
|
|
95
95
|
{this._showTagsDropdownContent
|
|
96
96
|
? renderTruncatableTagContent.call(this)
|
|
97
97
|
: this.searchableList}
|
|
98
|
+
{this.hasFooter && <slot name="footer" slot="footer" />}
|
|
98
99
|
</UI5Popover>
|
|
99
100
|
<span aria-live="polite" aria-atomic="true" class="hidden">{this._accSearchResultInfo}</span>
|
|
100
101
|
</div>
|
package/src/TextField.ts
CHANGED
|
@@ -49,7 +49,7 @@ export default function TextFieldTemplate(this: TextField) {
|
|
|
49
49
|
part="textfield-control-btn"
|
|
50
50
|
class={`${rootClass}__icon-container`}
|
|
51
51
|
size="Large"
|
|
52
|
-
tooltip={this.
|
|
52
|
+
tooltip={this.buttonTooltip}
|
|
53
53
|
active={this.active}
|
|
54
54
|
disabled={this.disabled}
|
|
55
55
|
readonly={this.readonly}
|
|
@@ -10,7 +10,7 @@ interface SuggestionComponent extends UI5Element {
|
|
|
10
10
|
open: boolean;
|
|
11
11
|
value?: string;
|
|
12
12
|
suggestionItems?: string;
|
|
13
|
-
openSuggestionPopup
|
|
13
|
+
openSuggestionPopup: boolean;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
type SearchInputSuggestion = {
|
|
@@ -32,8 +32,6 @@ class Suggestion {
|
|
|
32
32
|
focusabledList?: HTMLDivElement;
|
|
33
33
|
lists?: Array<HTMLDivElement>;
|
|
34
34
|
_focusHandler: UDExListWithFirstLetterFocus = new UDExFirstLetterFocusHandler();
|
|
35
|
-
_tabEscapedFromPopover: boolean = false;
|
|
36
|
-
_shiftTabEscapedFromPopover: boolean = false;
|
|
37
35
|
|
|
38
36
|
constructor(component: SuggestionComponent) {
|
|
39
37
|
this.component = component;
|
|
@@ -76,22 +74,15 @@ class Suggestion {
|
|
|
76
74
|
this._focusHandler.focusMatchingListItem(key);
|
|
77
75
|
}
|
|
78
76
|
}
|
|
79
|
-
initLists(): void {
|
|
80
|
-
const list = this.popover?.querySelectorAll(".udex-search-suggestions__list") ? this.popover?.querySelectorAll<HTMLDivElement>(".udex-search-suggestions__list") : [];
|
|
81
|
-
const newLists = Array.from(list);
|
|
82
|
-
|
|
83
|
-
if (this.focusabledList && this.lists?.length && newLists.length) {
|
|
84
|
-
const oldIndex = this.lists.indexOf(this.focusabledList);
|
|
85
|
-
if (oldIndex >= 0 && oldIndex < newLists.length) {
|
|
86
|
-
this.focusabledList = newLists[oldIndex];
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
77
|
|
|
90
|
-
|
|
78
|
+
initLists(): void {
|
|
79
|
+
const list = this.popover?.querySelectorAll<HTMLDivElement>(".udex-search-suggestions__list") ?? [];
|
|
80
|
+
this.lists = Array.from(list);
|
|
91
81
|
}
|
|
92
82
|
|
|
93
83
|
close(): void {
|
|
94
84
|
this._getSuggestionPopover();
|
|
85
|
+
this.getAllFocusableItems().forEach(item => item.removeAttribute("aria-selected"));
|
|
95
86
|
this._getComponent().open = false;
|
|
96
87
|
this.popover!.open = false;
|
|
97
88
|
this.selectedItemIndex = -1;
|
|
@@ -114,192 +105,79 @@ class Suggestion {
|
|
|
114
105
|
}
|
|
115
106
|
}
|
|
116
107
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
this._tabEscapedFromPopover = false;
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (this.lists?.length && this._getComponent().open) {
|
|
123
|
-
this._shiftTabEscapedFromPopover = false;
|
|
124
|
-
e.preventDefault();
|
|
125
|
-
this.focusabledList = this.lists[0];
|
|
126
|
-
this.selectedItemIndex = -1;
|
|
127
|
-
this.focusabledList.focus();
|
|
128
|
-
}
|
|
108
|
+
getAllFocusableItems(): Element[] {
|
|
109
|
+
return Array.from(this.popover?.querySelectorAll<Button>("[ui5-button]") ?? []);
|
|
129
110
|
}
|
|
130
111
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
onShiftTab(e: KeyboardEvent): void {
|
|
137
|
-
if (this._shiftTabEscapedFromPopover) {
|
|
138
|
-
this._shiftTabEscapedFromPopover = false;
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
const keepOpen = !!this.component.openSuggestionPopup;
|
|
142
|
-
if (keepOpen && this.lists?.length && this._getComponent().open) {
|
|
143
|
-
this._tabEscapedFromPopover = false;
|
|
144
|
-
e.preventDefault();
|
|
145
|
-
this.focusabledList = this.lists[this.lists.length - 1];
|
|
146
|
-
this.focusabledList.focus();
|
|
147
|
-
this.selectedItemIndex = -1;
|
|
112
|
+
private _updateItemSelection(items: Element[], nextIndex: number): void {
|
|
113
|
+
if (this.selectedItemIndex >= 0 && this.selectedItemIndex < items.length) {
|
|
114
|
+
items[this.selectedItemIndex].removeAttribute("aria-selected");
|
|
148
115
|
}
|
|
116
|
+
const button = items[nextIndex] as UI5Element;
|
|
117
|
+
button.setAttribute("aria-selected", "true");
|
|
118
|
+
button.focus();
|
|
119
|
+
this.selectedItemIndex = nextIndex;
|
|
149
120
|
}
|
|
150
121
|
|
|
151
|
-
|
|
122
|
+
onKeyDown(e: KeyboardEvent): void {
|
|
152
123
|
e.preventDefault();
|
|
153
|
-
this.
|
|
154
|
-
|
|
155
|
-
|
|
124
|
+
const items = this.getAllFocusableItems();
|
|
125
|
+
if (!items.length) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
156
128
|
|
|
157
|
-
|
|
158
|
-
|
|
129
|
+
const nextIndex = this.selectedItemIndex < 0 || this.selectedItemIndex >= items.length - 1
|
|
130
|
+
? 0
|
|
131
|
+
: this.selectedItemIndex + 1;
|
|
159
132
|
|
|
160
|
-
|
|
161
|
-
if (this.focusabledList === this.lists[1]) {
|
|
162
|
-
e.preventDefault();
|
|
163
|
-
if (keepOpen) {
|
|
164
|
-
this.focusabledList = undefined;
|
|
165
|
-
this.selectedItemIndex = -1;
|
|
166
|
-
this._tabEscapedFromPopover = true;
|
|
167
|
-
this.onFocusInput();
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
this.close();
|
|
171
|
-
this.onFocusInput();
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (this.focusabledList === this.lists[0]) {
|
|
175
|
-
e.preventDefault();
|
|
176
|
-
this.focusabledList = this.lists[1];
|
|
177
|
-
this.focusabledList.focus();
|
|
178
|
-
this.selectedItemIndex = -1;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (this.lists?.length === 1) {
|
|
182
|
-
e.preventDefault();
|
|
183
|
-
if (keepOpen) {
|
|
184
|
-
this.focusabledList = undefined;
|
|
185
|
-
this.selectedItemIndex = -1;
|
|
186
|
-
this._tabEscapedFromPopover = true;
|
|
187
|
-
this.onFocusInput();
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
this.close();
|
|
191
|
-
this.onFocusInput();
|
|
192
|
-
}
|
|
133
|
+
this._updateItemSelection(items, nextIndex);
|
|
193
134
|
}
|
|
194
135
|
|
|
195
|
-
|
|
196
|
-
const keepOpen = !!this.component.openSuggestionPopup;
|
|
136
|
+
onKeyUp(e: KeyboardEvent): void {
|
|
197
137
|
e.preventDefault();
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
this.focusabledList = undefined;
|
|
202
|
-
this.selectedItemIndex = -1;
|
|
203
|
-
this._shiftTabEscapedFromPopover = true;
|
|
204
|
-
}
|
|
205
|
-
this.onFocusInput();
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (this.focusabledList === this.lists[1]) {
|
|
209
|
-
this.focusabledList = this.lists[0];
|
|
210
|
-
this.focusabledList.focus();
|
|
211
|
-
this.selectedItemIndex = -1;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
if (this.lists?.length === 1) {
|
|
215
|
-
if (keepOpen) {
|
|
216
|
-
this.focusabledList = undefined;
|
|
217
|
-
this.selectedItemIndex = -1;
|
|
218
|
-
this._shiftTabEscapedFromPopover = true;
|
|
219
|
-
}
|
|
220
|
-
this.onFocusInput();
|
|
138
|
+
const items = this.getAllFocusableItems();
|
|
139
|
+
if (!items.length) {
|
|
140
|
+
return;
|
|
221
141
|
}
|
|
222
|
-
}
|
|
223
142
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const focusabledItems = this.getFocusableItems();
|
|
228
|
-
const itemCount = focusabledItems.length - 1;
|
|
143
|
+
const prevIndex = this.selectedItemIndex <= 0
|
|
144
|
+
? items.length - 1
|
|
145
|
+
: this.selectedItemIndex - 1;
|
|
229
146
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const button = focusabledItems[0] as UI5Element;
|
|
233
|
-
|
|
234
|
-
button.setAttribute("aria-selected", "true");
|
|
235
|
-
button.focus();
|
|
236
|
-
this.selectedItemIndex = 0;
|
|
237
|
-
this.focusabledList?.setAttribute("aria-activedescendant", button.id);
|
|
238
|
-
return true;
|
|
239
|
-
}
|
|
240
|
-
if (nextSelectedIdx >= 0 && nextSelectedIdx !== itemCount) {
|
|
241
|
-
const button = focusabledItems[nextSelectedIdx + 1] as UI5Element;
|
|
147
|
+
this._updateItemSelection(items, prevIndex);
|
|
148
|
+
}
|
|
242
149
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
this.selectedItemIndex = nextSelectedIdx + 1;
|
|
247
|
-
this.focusabledList?.setAttribute("aria-activedescendant", button.id);
|
|
248
|
-
return true;
|
|
249
|
-
}
|
|
250
|
-
if (nextSelectedIdx === itemCount && this.focusabledList === this.lists[0] && this.lists.length === 2) {
|
|
251
|
-
focusabledItems[nextSelectedIdx].removeAttribute("aria-selected");
|
|
252
|
-
this.focusabledList?.removeAttribute("aria-activedescendant");
|
|
253
|
-
this.focusabledList = this.lists[1];
|
|
254
|
-
this.focusabledList.focus();
|
|
255
|
-
this.selectedItemIndex = -1;
|
|
256
|
-
return true;
|
|
257
|
-
}
|
|
150
|
+
onTab(): void {
|
|
151
|
+
if (!this._getComponent().openSuggestionPopup) {
|
|
152
|
+
this.close();
|
|
258
153
|
}
|
|
154
|
+
this.resetFocusState();
|
|
259
155
|
}
|
|
260
156
|
|
|
261
|
-
|
|
157
|
+
onEscape(e: KeyboardEvent): void {
|
|
262
158
|
e.preventDefault();
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (this.lists?.length) {
|
|
268
|
-
if (previousSelectedIdx === -1) {
|
|
269
|
-
const button = focusabledItems[0] as UI5Element;
|
|
270
|
-
|
|
271
|
-
button.setAttribute("aria-selected", "true");
|
|
272
|
-
button.focus();
|
|
273
|
-
this.selectedItemIndex = 0;
|
|
274
|
-
this.focusabledList?.setAttribute("aria-activedescendant", button.id);
|
|
275
|
-
return true;
|
|
276
|
-
}
|
|
277
|
-
if (previousSelectedIdx > 0 && previousSelectedIdx <= itemCount) {
|
|
278
|
-
const button = focusabledItems[previousSelectedIdx - 1] as UI5Element;
|
|
159
|
+
this.close();
|
|
160
|
+
this.onFocusInput();
|
|
161
|
+
}
|
|
279
162
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
this.focusabledList = this.lists[0];
|
|
291
|
-
this.focusabledList.focus();
|
|
292
|
-
this.selectedItemIndex = -1;
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
163
|
+
onKeyTabList(e: KeyboardEvent): void {
|
|
164
|
+
e.preventDefault();
|
|
165
|
+
this.close();
|
|
166
|
+
this.resetFocusState();
|
|
167
|
+
const shadow = this._getComponent().shadowRoot;
|
|
168
|
+
const firstBtn = shadow?.querySelector<HTMLElement>("#reset-button, #voice-button, #submit-button");
|
|
169
|
+
if (firstBtn) {
|
|
170
|
+
firstBtn.focus();
|
|
171
|
+
} else {
|
|
172
|
+
this.onFocusInput();
|
|
295
173
|
}
|
|
296
174
|
}
|
|
297
175
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
176
|
+
onPrevKeyTabList(e: KeyboardEvent): void {
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
this.close();
|
|
179
|
+
this.resetFocusState();
|
|
180
|
+
this.onFocusInput();
|
|
303
181
|
}
|
|
304
182
|
|
|
305
183
|
static get dependencies() {
|