@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
|
@@ -1317,4 +1317,461 @@ describe("SelectBox", () => {
|
|
|
1317
1317
|
cy.get("#test-modal").should("not.have.attr", "open");
|
|
1318
1318
|
});
|
|
1319
1319
|
});
|
|
1320
|
+
|
|
1321
|
+
describe("Select Box footer slot", () => {
|
|
1322
|
+
const selectBoxWithFooter = <div style="width: 350px; position:relative;">
|
|
1323
|
+
<UDExSelectBox id="select-box" label="Programming languages">
|
|
1324
|
+
<UDExSelectBoxItem label="JavaScript"></UDExSelectBoxItem>
|
|
1325
|
+
<UDExSelectBoxItem label="Python"></UDExSelectBoxItem>
|
|
1326
|
+
<UDExSelectBoxItem label="Java"></UDExSelectBoxItem>
|
|
1327
|
+
<UDExButton id="footer-action" slot="footer" design="Tertiary">Apply</UDExButton>
|
|
1328
|
+
</UDExSelectBox>
|
|
1329
|
+
<button id="next-btn">Next</button>
|
|
1330
|
+
</div>;
|
|
1331
|
+
|
|
1332
|
+
const selectBoxWithoutFooter = <div style="width: 350px; position:relative;">
|
|
1333
|
+
<UDExSelectBox id="select-box" label="Programming languages">
|
|
1334
|
+
<UDExSelectBoxItem label="JavaScript"></UDExSelectBoxItem>
|
|
1335
|
+
<UDExSelectBoxItem label="Python"></UDExSelectBoxItem>
|
|
1336
|
+
</UDExSelectBox>
|
|
1337
|
+
</div>;
|
|
1338
|
+
|
|
1339
|
+
it("hasFooter getter is true when a slot=\"footer\" element is provided", () => {
|
|
1340
|
+
cy.mount(selectBoxWithFooter);
|
|
1341
|
+
|
|
1342
|
+
cy.get("#select-box").then($el => {
|
|
1343
|
+
expect(($el[0] as any).hasFooter).to.equal(true);
|
|
1344
|
+
});
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
it("hasFooter getter is false when no slot=\"footer\" element is provided", () => {
|
|
1348
|
+
cy.mount(selectBoxWithoutFooter);
|
|
1349
|
+
|
|
1350
|
+
cy.get("#select-box").then($el => {
|
|
1351
|
+
expect(($el[0] as any).hasFooter).to.equal(false);
|
|
1352
|
+
});
|
|
1353
|
+
});
|
|
1354
|
+
|
|
1355
|
+
it("renders the footer slot inside the popover when slot=\"footer\" content is provided and dropdown is open", () => {
|
|
1356
|
+
cy.mount(selectBoxWithFooter);
|
|
1357
|
+
cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
|
|
1358
|
+
|
|
1359
|
+
cy.get("#select-box").shadow()
|
|
1360
|
+
.find(".udex-select-box__dropdown")
|
|
1361
|
+
.should("have.attr", "open");
|
|
1362
|
+
|
|
1363
|
+
// The component template renders an internal <slot name="footer" slot="footer">
|
|
1364
|
+
// inside the popover, which forwards the projected button into the popover's footer area.
|
|
1365
|
+
cy.get("#select-box").shadow()
|
|
1366
|
+
.find(".udex-select-box__dropdown")
|
|
1367
|
+
.find('slot[name="footer"][slot="footer"]')
|
|
1368
|
+
.should("exist");
|
|
1369
|
+
|
|
1370
|
+
// The slotted button itself remains in light DOM with slot="footer".
|
|
1371
|
+
cy.get("#select-box")
|
|
1372
|
+
.find('[slot="footer"]#footer-action')
|
|
1373
|
+
.should("exist");
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
it("does NOT render the internal footer slot in the popover when no slot=\"footer\" content is provided", () => {
|
|
1377
|
+
cy.mount(selectBoxWithoutFooter);
|
|
1378
|
+
cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
|
|
1379
|
+
|
|
1380
|
+
cy.get("#select-box").shadow()
|
|
1381
|
+
.find(".udex-select-box__dropdown")
|
|
1382
|
+
.should("have.attr", "open");
|
|
1383
|
+
|
|
1384
|
+
// No footer-forwarding slot should be rendered when hasFooter is false.
|
|
1385
|
+
cy.get("#select-box").shadow()
|
|
1386
|
+
.find(".udex-select-box__dropdown")
|
|
1387
|
+
.find('slot[name="footer"]')
|
|
1388
|
+
.should("not.exist");
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
// Updated: covers Fix 1 — handleInputFocusOut now treats footer elements as
|
|
1392
|
+
// "inside the component" so a real click on the footer button moves focus to it
|
|
1393
|
+
// without closing the dropdown.
|
|
1394
|
+
it("clicking the footer button moves focus to it and keeps the dropdown open", () => {
|
|
1395
|
+
cy.mount(selectBoxWithFooter);
|
|
1396
|
+
cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
|
|
1397
|
+
|
|
1398
|
+
cy.get("#select-box").shadow()
|
|
1399
|
+
.find(".udex-select-box__dropdown")
|
|
1400
|
+
.should("have.attr", "open");
|
|
1401
|
+
|
|
1402
|
+
// realClick triggers a real focusout on the search input with the footer
|
|
1403
|
+
// button as the relatedTarget — the path that previously closed the dropdown.
|
|
1404
|
+
cy.get("#select-box").find("#footer-action").realClick();
|
|
1405
|
+
|
|
1406
|
+
cy.get("#select-box").find("#footer-action").should("be.focused");
|
|
1407
|
+
|
|
1408
|
+
cy.get("#select-box").shadow()
|
|
1409
|
+
.find(".udex-select-box__dropdown")
|
|
1410
|
+
.should("have.attr", "open");
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
it("pressing Escape while the footer button is focused closes the dropdown", () => {
|
|
1414
|
+
cy.mount(selectBoxWithFooter);
|
|
1415
|
+
cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
|
|
1416
|
+
|
|
1417
|
+
cy.get("#select-box").shadow()
|
|
1418
|
+
.find(".udex-select-box__dropdown")
|
|
1419
|
+
.should("have.attr", "open");
|
|
1420
|
+
|
|
1421
|
+
cy.get("#select-box").find("#footer-action").then($btn => {
|
|
1422
|
+
($btn[0] as HTMLElement).focus();
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1425
|
+
cy.get("#select-box").find("#footer-action").should("be.focused");
|
|
1426
|
+
|
|
1427
|
+
cy.realPress("Escape");
|
|
1428
|
+
|
|
1429
|
+
cy.get("#select-box").shadow()
|
|
1430
|
+
.find(".udex-select-box__dropdown")
|
|
1431
|
+
.should("not.have.attr", "open");
|
|
1432
|
+
});
|
|
1433
|
+
|
|
1434
|
+
// Updated: handleKeyDownDropdown footer branch now intercepts Tab to close the
|
|
1435
|
+
// dropdown and move focus to the next focusable element after the component.
|
|
1436
|
+
// (Previously Tab from the footer kept the dropdown open and propagated natively.)
|
|
1437
|
+
it("pressing Tab while focus is in the footer closes the dropdown and moves focus to the next element after the component", () => {
|
|
1438
|
+
cy.mount(selectBoxWithFooter);
|
|
1439
|
+
cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
|
|
1440
|
+
|
|
1441
|
+
cy.get("#select-box").shadow()
|
|
1442
|
+
.find(".udex-select-box__dropdown")
|
|
1443
|
+
.should("have.attr", "open");
|
|
1444
|
+
|
|
1445
|
+
cy.get("#select-box").find("#footer-action").then($btn => {
|
|
1446
|
+
($btn[0] as HTMLElement).focus();
|
|
1447
|
+
});
|
|
1448
|
+
cy.get("#select-box").find("#footer-action").should("be.focused");
|
|
1449
|
+
|
|
1450
|
+
cy.realPress("Tab");
|
|
1451
|
+
|
|
1452
|
+
cy.get("#select-box").shadow()
|
|
1453
|
+
.find(".udex-select-box__dropdown")
|
|
1454
|
+
.should("not.have.attr", "open");
|
|
1455
|
+
|
|
1456
|
+
cy.get("#next-btn").should("be.focused");
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
// Updated: handleKeyDownDropdown footer branch now redirects Shift+Tab to the
|
|
1460
|
+
// last list item (via _focusLastListItem) instead of jumping straight back to
|
|
1461
|
+
// the search input. The dropdown stays open. The base-class behaviour that
|
|
1462
|
+
// moves focus from the last list item back to the search input on a second
|
|
1463
|
+
// Shift+Tab is covered elsewhere in this spec.
|
|
1464
|
+
it("pressing Shift+Tab while focus is in the footer moves focus to the last list item and keeps the dropdown open", () => {
|
|
1465
|
+
cy.mount(selectBoxWithFooter);
|
|
1466
|
+
cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
|
|
1467
|
+
|
|
1468
|
+
cy.get("#select-box").shadow()
|
|
1469
|
+
.find(".udex-select-box__dropdown")
|
|
1470
|
+
.should("have.attr", "open");
|
|
1471
|
+
|
|
1472
|
+
cy.get("#select-box").find("#footer-action").then($btn => {
|
|
1473
|
+
($btn[0] as HTMLElement).focus();
|
|
1474
|
+
});
|
|
1475
|
+
cy.get("#select-box").find("#footer-action").should("be.focused");
|
|
1476
|
+
|
|
1477
|
+
cy.realPress(["Shift", "Tab"]);
|
|
1478
|
+
|
|
1479
|
+
cy.get("#select-box").shadow()
|
|
1480
|
+
.find(".udex-select-box__dropdown")
|
|
1481
|
+
.should("have.attr", "open");
|
|
1482
|
+
|
|
1483
|
+
cy.get("#select-box").shadow()
|
|
1484
|
+
.find("udex-list-item")
|
|
1485
|
+
.last()
|
|
1486
|
+
.should("be.focused");
|
|
1487
|
+
});
|
|
1488
|
+
|
|
1489
|
+
// when hasFooter is true, pressing Tab on a focused list item moves focus to the
|
|
1490
|
+
// first footer element and keeps the dropdown open.
|
|
1491
|
+
it("pressing Tab while a list item is focused moves focus to the first footer element and keeps the dropdown open", () => {
|
|
1492
|
+
cy.mount(selectBoxWithFooter);
|
|
1493
|
+
|
|
1494
|
+
// Open the dropdown and move focus into the list via ArrowDown.
|
|
1495
|
+
cy.get("#select-box").shadow()
|
|
1496
|
+
.find(".udex-text-field__search")
|
|
1497
|
+
.click()
|
|
1498
|
+
.realPress("ArrowDown");
|
|
1499
|
+
|
|
1500
|
+
cy.get("#select-box").shadow()
|
|
1501
|
+
.find(".udex-select-box__dropdown")
|
|
1502
|
+
.should("have.attr", "open");
|
|
1503
|
+
|
|
1504
|
+
// A list item should now be focused (not the search input).
|
|
1505
|
+
cy.get("#select-box").shadow()
|
|
1506
|
+
.find("udex-list-item").eq(0)
|
|
1507
|
+
.should("be.focused");
|
|
1508
|
+
|
|
1509
|
+
// Tab should be intercepted and focus redirected into the footer.
|
|
1510
|
+
cy.realPress("Tab");
|
|
1511
|
+
|
|
1512
|
+
cy.get("#select-box").find("#footer-action").should("be.focused");
|
|
1513
|
+
|
|
1514
|
+
cy.get("#select-box").shadow()
|
|
1515
|
+
.find(".udex-select-box__dropdown")
|
|
1516
|
+
.should("have.attr", "open");
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
it("calling close() programmatically while focus is in the footer closes the dropdown", () => {
|
|
1520
|
+
cy.mount(selectBoxWithFooter);
|
|
1521
|
+
cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
|
|
1522
|
+
|
|
1523
|
+
cy.get("#select-box").shadow()
|
|
1524
|
+
.find(".udex-select-box__dropdown")
|
|
1525
|
+
.should("have.attr", "open");
|
|
1526
|
+
|
|
1527
|
+
cy.get("#select-box").find("#footer-action").then($btn => {
|
|
1528
|
+
($btn[0] as HTMLElement).focus();
|
|
1529
|
+
});
|
|
1530
|
+
|
|
1531
|
+
cy.get("#select-box").then($el => {
|
|
1532
|
+
($el[0] as any).close();
|
|
1533
|
+
});
|
|
1534
|
+
|
|
1535
|
+
cy.get("#select-box").shadow()
|
|
1536
|
+
.find(".udex-select-box__dropdown")
|
|
1537
|
+
.should("not.have.attr", "open");
|
|
1538
|
+
});
|
|
1539
|
+
|
|
1540
|
+
// Added: covers the SearchableListBase._validItems filter — foreign elements
|
|
1541
|
+
// placed in the default slot without slot="footer" (e.g. a stray udex-button)
|
|
1542
|
+
// must be ignored by getProcessedItems/selectedItems/nonGroupItems and never
|
|
1543
|
+
// rendered as udex-list-item entries in the dropdown.
|
|
1544
|
+
it("ignores non-list-item elements placed in the default slot and does not render them as udex-list-item entries", () => {
|
|
1545
|
+
const selectBoxWithStrayButton = <div style="width: 350px; position:relative;">
|
|
1546
|
+
<UDExSelectBox id="select-box-stray" label="Programming languages">
|
|
1547
|
+
<UDExSelectBoxItem label="JavaScript"></UDExSelectBoxItem>
|
|
1548
|
+
<UDExSelectBoxItem label="Python"></UDExSelectBoxItem>
|
|
1549
|
+
<UDExSelectBoxItem label="Java"></UDExSelectBoxItem>
|
|
1550
|
+
<UDExButton id="stray-button" design="Tertiary">Stray</UDExButton>
|
|
1551
|
+
</UDExSelectBox>
|
|
1552
|
+
</div>;
|
|
1553
|
+
|
|
1554
|
+
cy.mount(selectBoxWithStrayButton);
|
|
1555
|
+
cy.get("#select-box-stray").shadow().find(".udex-text-field__trigger").click();
|
|
1556
|
+
|
|
1557
|
+
cy.get("#select-box-stray").shadow()
|
|
1558
|
+
.find(".udex-select-box__dropdown")
|
|
1559
|
+
.should("have.attr", "open");
|
|
1560
|
+
|
|
1561
|
+
// The stray button must not be promoted into the rendered list.
|
|
1562
|
+
cy.get("#select-box-stray").shadow()
|
|
1563
|
+
.find("udex-list-item#stray-button")
|
|
1564
|
+
.should("not.exist");
|
|
1565
|
+
|
|
1566
|
+
// Only the three UDExSelectBoxItem children should render as list items.
|
|
1567
|
+
cy.get("#select-box-stray").shadow()
|
|
1568
|
+
.find("udex-list-item")
|
|
1569
|
+
.should("have.length", 3);
|
|
1570
|
+
|
|
1571
|
+
// The stray button still exists in light DOM — it is just silently ignored
|
|
1572
|
+
// by the list, not removed from the component.
|
|
1573
|
+
cy.get("#select-box-stray").find("#stray-button").should("exist");
|
|
1574
|
+
});
|
|
1575
|
+
|
|
1576
|
+
it("emits close event when Escape is pressed inside the footer", () => {
|
|
1577
|
+
cy.mount(selectBoxWithFooter);
|
|
1578
|
+
cy.get("#select-box").then(select => {
|
|
1579
|
+
select.get(0).addEventListener("close", cy.stub().as("close"));
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
cy.get("#select-box").shadow().find(".udex-text-field__trigger").click();
|
|
1583
|
+
|
|
1584
|
+
cy.get("#select-box").shadow()
|
|
1585
|
+
.find(".udex-select-box__dropdown")
|
|
1586
|
+
.should("have.attr", "open");
|
|
1587
|
+
|
|
1588
|
+
cy.get("#select-box").find("#footer-action").then($btn => {
|
|
1589
|
+
($btn[0] as HTMLElement).focus();
|
|
1590
|
+
});
|
|
1591
|
+
cy.realPress("Escape");
|
|
1592
|
+
|
|
1593
|
+
cy.get("@close").should("have.been.called");
|
|
1594
|
+
});
|
|
1595
|
+
});
|
|
1596
|
+
|
|
1597
|
+
describe("SelectBox keyboard navigation with action icon buttons", () => {
|
|
1598
|
+
const shortComponentWithFocusTargets = <div style="width: 350px; position:relative;">
|
|
1599
|
+
<a id="previous-focus" href="#">Previous focusable element</a>
|
|
1600
|
+
<UDExSelectBox id="select-box" height="400px" label="Programming languages" supportingText="category lists">
|
|
1601
|
+
<UDExSelectBoxItem label="JavaScript" additionalText="Additional Text">
|
|
1602
|
+
<UDExButton id='firstIcon' accessible-name='Action' slot='firstIcon' design='Tertiary' icon='action' size='Small'></UDExButton>
|
|
1603
|
+
<UDExButton id='secondIcon' accessible-name='Email' slot='secondIcon' design='Tertiary' icon='email' size='Small'></UDExButton>
|
|
1604
|
+
</UDExSelectBoxItem>
|
|
1605
|
+
<UDExSelectBoxItem label="Python"></UDExSelectBoxItem>
|
|
1606
|
+
<UDExSelectBoxItem label="Java"></UDExSelectBoxItem>
|
|
1607
|
+
</UDExSelectBox>
|
|
1608
|
+
<a id="next-focus" href="#">Next focusable element</a>
|
|
1609
|
+
</div>;
|
|
1610
|
+
|
|
1611
|
+
const openDropdownOnFirstItem = () => {
|
|
1612
|
+
cy.get("#select-box").shadow()
|
|
1613
|
+
.find(".udex-text-field__search")
|
|
1614
|
+
.click()
|
|
1615
|
+
.realPress("ArrowDown");
|
|
1616
|
+
};
|
|
1617
|
+
|
|
1618
|
+
it("Tab from list item closes dropdown and moves focus to next element outside component", () => {
|
|
1619
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1620
|
+
openDropdownOnFirstItem();
|
|
1621
|
+
|
|
1622
|
+
cy.realPress("Tab");
|
|
1623
|
+
|
|
1624
|
+
cy.get("#select-box").shadow()
|
|
1625
|
+
.find(".udex-select-box__dropdown")
|
|
1626
|
+
.should("not.have.attr", "open");
|
|
1627
|
+
|
|
1628
|
+
cy.get("#next-focus").should("be.focused");
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
it("Shift+Tab from list item closes dropdown and moves focus to search input", () => {
|
|
1632
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1633
|
+
openDropdownOnFirstItem();
|
|
1634
|
+
|
|
1635
|
+
cy.realPress(["Shift", "Tab"]);
|
|
1636
|
+
|
|
1637
|
+
cy.get("#select-box").shadow()
|
|
1638
|
+
.find(".udex-text-field__search")
|
|
1639
|
+
.should("be.focused");
|
|
1640
|
+
});
|
|
1641
|
+
|
|
1642
|
+
it("Escape from list item closes dropdown and returns focus to trigger", () => {
|
|
1643
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1644
|
+
openDropdownOnFirstItem();
|
|
1645
|
+
|
|
1646
|
+
cy.realPress("Escape");
|
|
1647
|
+
|
|
1648
|
+
cy.get("#select-box").shadow()
|
|
1649
|
+
.find(".udex-select-box__dropdown")
|
|
1650
|
+
.should("not.have.attr", "open");
|
|
1651
|
+
|
|
1652
|
+
cy.get("#select-box").shadow()
|
|
1653
|
+
.find(".udex-text-field__search")
|
|
1654
|
+
.should("be.focused");
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
it("ArrowRight from list item focuses firstIcon button", () => {
|
|
1658
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1659
|
+
openDropdownOnFirstItem();
|
|
1660
|
+
|
|
1661
|
+
cy.realPress("ArrowRight");
|
|
1662
|
+
|
|
1663
|
+
cy.get("#select-box").shadow().find("#firstIcon").should("be.focused");
|
|
1664
|
+
});
|
|
1665
|
+
|
|
1666
|
+
it("ArrowRight from firstIcon button focuses secondIcon button", () => {
|
|
1667
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1668
|
+
openDropdownOnFirstItem();
|
|
1669
|
+
|
|
1670
|
+
cy.realPress("ArrowRight");
|
|
1671
|
+
cy.realPress("ArrowRight");
|
|
1672
|
+
|
|
1673
|
+
cy.get("#select-box").shadow().find("#secondIcon").should("be.focused");
|
|
1674
|
+
});
|
|
1675
|
+
|
|
1676
|
+
it("ArrowRight from secondIcon button stays on secondIcon", () => {
|
|
1677
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1678
|
+
openDropdownOnFirstItem();
|
|
1679
|
+
|
|
1680
|
+
cy.realPress("ArrowRight");
|
|
1681
|
+
cy.realPress("ArrowRight");
|
|
1682
|
+
cy.realPress("ArrowRight");
|
|
1683
|
+
|
|
1684
|
+
cy.get("#select-box").shadow().find("#secondIcon").should("be.focused");
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
it("ArrowLeft from secondIcon button focuses firstIcon button", () => {
|
|
1688
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1689
|
+
openDropdownOnFirstItem();
|
|
1690
|
+
|
|
1691
|
+
cy.realPress("ArrowRight");
|
|
1692
|
+
cy.realPress("ArrowRight");
|
|
1693
|
+
cy.realPress("ArrowLeft");
|
|
1694
|
+
|
|
1695
|
+
cy.get("#select-box").shadow().find("#firstIcon").should("be.focused");
|
|
1696
|
+
});
|
|
1697
|
+
|
|
1698
|
+
it("ArrowLeft from firstIcon button focuses list item", () => {
|
|
1699
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1700
|
+
openDropdownOnFirstItem();
|
|
1701
|
+
|
|
1702
|
+
cy.realPress("ArrowRight");
|
|
1703
|
+
cy.realPress("ArrowLeft");
|
|
1704
|
+
|
|
1705
|
+
cy.get("#select-box").shadow()
|
|
1706
|
+
.find("udex-list-item").eq(0)
|
|
1707
|
+
.should("be.focused");
|
|
1708
|
+
});
|
|
1709
|
+
|
|
1710
|
+
it("ArrowDown from firstIcon button navigates to next list item", () => {
|
|
1711
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1712
|
+
openDropdownOnFirstItem();
|
|
1713
|
+
|
|
1714
|
+
cy.realPress("ArrowRight");
|
|
1715
|
+
cy.realPress("ArrowDown");
|
|
1716
|
+
|
|
1717
|
+
cy.get("#select-box").shadow()
|
|
1718
|
+
.find("udex-list-item").eq(1)
|
|
1719
|
+
.should("be.focused");
|
|
1720
|
+
});
|
|
1721
|
+
|
|
1722
|
+
it("ArrowUp from firstIcon button navigates to previous list item (wraps to last)", () => {
|
|
1723
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1724
|
+
openDropdownOnFirstItem();
|
|
1725
|
+
|
|
1726
|
+
cy.realPress("ArrowRight");
|
|
1727
|
+
cy.realPress("ArrowUp");
|
|
1728
|
+
|
|
1729
|
+
cy.get("#select-box").shadow()
|
|
1730
|
+
.find("udex-list-item").last()
|
|
1731
|
+
.should("be.focused");
|
|
1732
|
+
});
|
|
1733
|
+
|
|
1734
|
+
it("ArrowDown from secondIcon button navigates to next list item", () => {
|
|
1735
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1736
|
+
openDropdownOnFirstItem();
|
|
1737
|
+
|
|
1738
|
+
cy.realPress("ArrowRight");
|
|
1739
|
+
cy.realPress("ArrowRight");
|
|
1740
|
+
cy.realPress("ArrowDown");
|
|
1741
|
+
|
|
1742
|
+
cy.get("#select-box").shadow()
|
|
1743
|
+
.find("udex-list-item").eq(1)
|
|
1744
|
+
.should("be.focused");
|
|
1745
|
+
});
|
|
1746
|
+
|
|
1747
|
+
it("Escape from icon button closes dropdown and returns focus to input", () => {
|
|
1748
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1749
|
+
openDropdownOnFirstItem();
|
|
1750
|
+
|
|
1751
|
+
cy.realPress("ArrowRight");
|
|
1752
|
+
cy.realPress("Escape");
|
|
1753
|
+
|
|
1754
|
+
cy.get("#select-box").shadow()
|
|
1755
|
+
.find(".udex-select-box__dropdown")
|
|
1756
|
+
.should("not.have.attr", "open");
|
|
1757
|
+
|
|
1758
|
+
cy.get("#select-box").shadow()
|
|
1759
|
+
.find(".udex-text-field__search")
|
|
1760
|
+
.should("be.focused");
|
|
1761
|
+
});
|
|
1762
|
+
|
|
1763
|
+
it("Tab from icon button closes dropdown and moves focus to next element", () => {
|
|
1764
|
+
cy.mount(shortComponentWithFocusTargets);
|
|
1765
|
+
openDropdownOnFirstItem();
|
|
1766
|
+
|
|
1767
|
+
cy.realPress("ArrowRight");
|
|
1768
|
+
cy.realPress("Tab");
|
|
1769
|
+
|
|
1770
|
+
cy.get("#select-box").shadow()
|
|
1771
|
+
.find(".udex-select-box__dropdown")
|
|
1772
|
+
.should("not.have.attr", "open");
|
|
1773
|
+
|
|
1774
|
+
cy.get("#next-focus").should("be.focused");
|
|
1775
|
+
});
|
|
1776
|
+
});
|
|
1320
1777
|
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { UDExTile } from "../support/component-imports.js";
|
|
2
|
+
|
|
3
|
+
const interactiveTile = (
|
|
4
|
+
<UDExTile interactive href="https://example.com" accessibleName="Interactive tile">
|
|
5
|
+
Interactive content
|
|
6
|
+
</UDExTile>
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
const nonInteractiveTile = (
|
|
10
|
+
<UDExTile>Generic content</UDExTile>
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
const focusFixture = (
|
|
14
|
+
<>
|
|
15
|
+
<button id="before-tile" type="button">before</button>
|
|
16
|
+
<UDExTile interactive href="https://example.com" accessibleName="Interactive tile">
|
|
17
|
+
Interactive content
|
|
18
|
+
</UDExTile>
|
|
19
|
+
</>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const nonInteractiveFocusFixture = (
|
|
23
|
+
<>
|
|
24
|
+
<button id="before-tile" type="button">before</button>
|
|
25
|
+
<UDExTile>Generic content</UDExTile>
|
|
26
|
+
</>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
describe("UDExTile - interactive mode", () => {
|
|
30
|
+
it("reflects the interactive property as the [interactive] attribute on the host", () => {
|
|
31
|
+
cy.mount(interactiveTile);
|
|
32
|
+
cy.get("[udex-tile]").should("have.attr", "interactive");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("renders an anchor (a.udex-tile) so the :host([interactive]) cursor rule applies", () => {
|
|
36
|
+
// The :host([interactive]) .udex-tile { cursor: pointer; } rule fires when
|
|
37
|
+
// both preconditions hold:
|
|
38
|
+
// 1. The host carries the [interactive] attribute.
|
|
39
|
+
// 2. An element with class .udex-tile exists in the shadow root.
|
|
40
|
+
// Asserting both preconditions is the JS-observable proxy for "cursor:pointer
|
|
41
|
+
// is in effect" without reading computed style.
|
|
42
|
+
cy.mount(interactiveTile);
|
|
43
|
+
cy.get("[udex-tile]").should("have.attr", "interactive");
|
|
44
|
+
cy.get("[udex-tile]")
|
|
45
|
+
.shadow()
|
|
46
|
+
.find("a.udex-tile")
|
|
47
|
+
.should("exist")
|
|
48
|
+
.and("have.attr", "href", "https://example.com");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("does not render an anchor when interactive is not set (non-interactive root is a div)", () => {
|
|
52
|
+
cy.mount(nonInteractiveTile);
|
|
53
|
+
cy.get("[udex-tile]").should("not.have.attr", "interactive");
|
|
54
|
+
cy.get("[udex-tile]").shadow().find("a.udex-tile").should("not.exist");
|
|
55
|
+
cy.get("[udex-tile]").shadow().find("div.udex-tile").should("exist");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("UDExTile - keyboard focus", () => {
|
|
60
|
+
it("interactive tile becomes focused when reached via Tab", () => {
|
|
61
|
+
// The :host([interactive]) .udex-tile:focus-visible { outline: 2px solid ... }
|
|
62
|
+
// rule fires when the inner anchor matches :focus-visible. We verify the
|
|
63
|
+
// JS-observable precondition: after Tab from the previous focusable element,
|
|
64
|
+
// the inner a.udex-tile receives DOM focus and the host carries [interactive].
|
|
65
|
+
cy.mount(focusFixture);
|
|
66
|
+
cy.get("#before-tile").focus();
|
|
67
|
+
cy.realPress("Tab");
|
|
68
|
+
|
|
69
|
+
cy.get("[udex-tile]").should("have.attr", "interactive");
|
|
70
|
+
cy.get("[udex-tile]")
|
|
71
|
+
.shadow()
|
|
72
|
+
.find("a.udex-tile")
|
|
73
|
+
.should("have.attr", "tabindex", "0")
|
|
74
|
+
.and("be.focused");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("non-interactive tile is not reachable via Tab and does not receive focus", () => {
|
|
78
|
+
// Non-interactive renders <div class="udex-tile"> with no tabindex, so Tab
|
|
79
|
+
// does not land focus inside the shadow root. This is the JS-observable
|
|
80
|
+
// proxy for "no focus outline appears on non-interactive tiles".
|
|
81
|
+
cy.mount(nonInteractiveFocusFixture);
|
|
82
|
+
cy.get("#before-tile").focus();
|
|
83
|
+
cy.realPress("Tab");
|
|
84
|
+
|
|
85
|
+
cy.get("[udex-tile]").should("not.have.attr", "interactive");
|
|
86
|
+
cy.get("[udex-tile]").shadow().find("div.udex-tile").should("not.have.attr", "tabindex");
|
|
87
|
+
cy.get("[udex-tile]")
|
|
88
|
+
.shadow()
|
|
89
|
+
.find(".udex-tile")
|
|
90
|
+
.then(($el) => {
|
|
91
|
+
// The shadow root's :focus pseudo should not match the tile root, since
|
|
92
|
+
// it is not focusable.
|
|
93
|
+
expect($el.is(":focus")).to.equal(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -12,6 +12,7 @@ import UDExContact from "../../instrumented/Contact.js";
|
|
|
12
12
|
import UDExContactItem from "../../instrumented/ContactItem.js";
|
|
13
13
|
import UDExCountrySelector from "../../instrumented/CountrySelector.js";
|
|
14
14
|
import UDExDivider from "../../instrumented/Divider.js";
|
|
15
|
+
import UDExDropdown from "../../instrumented/Dropdown.js";
|
|
15
16
|
import UDExEventRibbon from "../../instrumented/EventRibbon.js";
|
|
16
17
|
import UDExFeed from "../../instrumented/Feed.js";
|
|
17
18
|
import UDExFeedItem from "../../instrumented/FeedItem.js";
|
|
@@ -75,6 +76,7 @@ import UDExTag from "../../instrumented/Tag.js";
|
|
|
75
76
|
import UDExTagGroup from "../../instrumented/TagGroup.js";
|
|
76
77
|
import UDExTextArea from "../../instrumented/TextArea.js";
|
|
77
78
|
import UDExTextField from "../../instrumented/TextField.js";
|
|
79
|
+
import UDExTile from "../../instrumented/Tile.js";
|
|
78
80
|
import UDExToast from "../../instrumented/Toast.js";
|
|
79
81
|
import UDExTooltip from "../../instrumented/Tooltip.js";
|
|
80
82
|
import UDExTruncatedText from "../../instrumented/TruncatedText.js";
|
|
@@ -98,6 +100,7 @@ export {
|
|
|
98
100
|
UDExContactItem,
|
|
99
101
|
UDExCountrySelector,
|
|
100
102
|
UDExDivider,
|
|
103
|
+
UDExDropdown,
|
|
101
104
|
UDExEventRibbon,
|
|
102
105
|
UDExFeed,
|
|
103
106
|
UDExFeedItem,
|
|
@@ -161,6 +164,7 @@ export {
|
|
|
161
164
|
UDExTagGroup,
|
|
162
165
|
UDExTextArea,
|
|
163
166
|
UDExTextField,
|
|
167
|
+
UDExTile,
|
|
164
168
|
UDExToast,
|
|
165
169
|
UDExTooltip,
|
|
166
170
|
UDExTruncatedText,
|
|
@@ -12,6 +12,7 @@ import UDExContact from "../../src/Contact.js";
|
|
|
12
12
|
import UDExContactItem from "../../src/ContactItem.js";
|
|
13
13
|
import UDExCountrySelector from "../../src/CountrySelector.js";
|
|
14
14
|
import UDExDivider from "../../src/Divider.js";
|
|
15
|
+
import UDExDropdown from "../../src/Dropdown.js";
|
|
15
16
|
import UDExEventRibbon from "../../src/EventRibbon.js";
|
|
16
17
|
import UDExFeed from "../../src/Feed.js";
|
|
17
18
|
import UDExFeedItem from "../../src/FeedItem.js";
|
|
@@ -75,6 +76,7 @@ import UDExTag from "../../src/Tag.js";
|
|
|
75
76
|
import UDExTagGroup from "../../src/TagGroup.js";
|
|
76
77
|
import UDExTextArea from "../../src/TextArea.js";
|
|
77
78
|
import UDExTextField from "../../src/TextField.js";
|
|
79
|
+
import UDExTile from "../../src/Tile.js";
|
|
78
80
|
import UDExToast from "../../src/Toast.js";
|
|
79
81
|
import UDExTooltip from "../../src/Tooltip.js";
|
|
80
82
|
import UDExTruncatedText from "../../src/TruncatedText.js";
|
|
@@ -98,6 +100,7 @@ export {
|
|
|
98
100
|
UDExContactItem,
|
|
99
101
|
UDExCountrySelector,
|
|
100
102
|
UDExDivider,
|
|
103
|
+
UDExDropdown,
|
|
101
104
|
UDExEventRibbon,
|
|
102
105
|
UDExFeed,
|
|
103
106
|
UDExFeedItem,
|
|
@@ -161,6 +164,7 @@ export {
|
|
|
161
164
|
UDExTagGroup,
|
|
162
165
|
UDExTextArea,
|
|
163
166
|
UDExTextField,
|
|
167
|
+
UDExTile,
|
|
164
168
|
UDExToast,
|
|
165
169
|
UDExTooltip,
|
|
166
170
|
UDExTruncatedText,
|
package/dist/Dropdown.d.ts
CHANGED
|
@@ -26,5 +26,10 @@ declare class UDExDropdown extends Popover {
|
|
|
26
26
|
constructor();
|
|
27
27
|
onAfterRendering(): void;
|
|
28
28
|
setContentHeight(): void;
|
|
29
|
+
forwardToLast(): Promise<void>;
|
|
30
|
+
forwardToFirst(): Promise<void>;
|
|
31
|
+
private _getOpenerHost;
|
|
32
|
+
private _moveFocusAfter;
|
|
33
|
+
private _firstFocusableIn;
|
|
29
34
|
}
|
|
30
35
|
export default UDExDropdown;
|